From 344285dce5b3acb760dffe0559bdef3d83241b9c Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Tue, 16 Jun 2026 22:27:39 +0330 Subject: [PATCH] feat(specialties): show per-city doctor count on /specialties Fetch active specialties with number_of_doctors from GET /api/v1/specialties/doctor-counts (scoped to the current city via matchedCity.id) instead of the static specialties.json, so each specialty card shows the real doctor count. Co-Authored-By: Claude Opus 4.8 --- .../prompt/specialties-doctor-count-wire.md | 103 ++++++++++++++++++ app/specialties/page.js | 5 +- components/specialties/index.js | 26 +++-- services/response.js | 5 + 4 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 .claude/prompt/specialties-doctor-count-wire.md diff --git a/.claude/prompt/specialties-doctor-count-wire.md b/.claude/prompt/specialties-doctor-count-wire.md new file mode 100644 index 0000000..b863553 --- /dev/null +++ b/.claude/prompt/specialties-doctor-count-wire.md @@ -0,0 +1,103 @@ +# نمایش تعداد پزشکان هر تخصص در صفحه /specialties + +## پروژه + +`nobat724_front` (سایت عمومی). + +> **Cross-repo:** وابسته به endpoint بک‌اند `GET /api/v1/specialties/doctor-counts?city_id=` (پرامپت `clinicpro/.claude/prompt/specialty-doctor-counts.md`). آن **اول** اجرا شود. + +## زمینه + +صفحه‌ی `/specialties` کارت هر تخصص را از `data/specialties.json` (ثابت) رندر می‌کند و زیرش `{data.number_of_doctors} پزشک` می‌نویسد — اما `specialties.json` فیلد `number_of_doctors` ندارد، پس همیشه خالی است. باید تعداد واقعی پزشکانِ هر تخصص **در شهرِ دامنه‌ی جاری** از API گرفته و نمایش داده شود. + +شهر از subdomain تشخیص داده می‌شود: server-side با `getStateInfo()` → `matchedCity.id` (همان city id بک‌اند). + +## مشکل / هدف + +صفحه به‌جای json ثابت، لیست تخصص‌ها را با `number_of_doctors` از endpoint جدید (با `city_id` شهر جاری) بگیرد و نمایش دهد. + +## فایل‌های مرتبط + +| فایل | نقش | +|------|-----| +| `app/specialties/page.js` | Server Component — `matchedCity.id` را از `getStateInfo` بگیر و به صفحه بده | +| `components/specialties/index.js` | `"use client"` — fetch count از API به‌جای json ثابت | +| `components/specialties/list/ItemSpecialties.js` | نمایش `data.number_of_doctors` (موجود — تغییر لازم ندارد) | +| `services/response.js` | افزودن wrapper `getSpecialtyDoctorCounts(cityId)` | + +## وضعیت فعلی (کد واقعی) + +`ItemSpecialties.js` از قبل تعداد را نشان می‌دهد (فقط داده ندارد): +```jsx +

+ {data.number_of_doctors} پزشک +

+``` + +`components/specialties/index.js`: +```jsx +import specialtiesData from "@/data/specialties.json"; +const [filteredSpecialties, setFilteredSpecialties] = useState(specialtiesData); +const handleSearch = (value) => setFilteredSpecialties(searchOnList(specialtiesData, value, "name")); +``` + +`app/specialties/page.js`: +```jsx +function Specialties() { + return (); +} +``` + +پاسخ بک‌اند `GET /api/v1/specialties/doctor-counts?city_id=`: `{ success, data: { data: [ {id, name, slug, parent_id, number_of_doctors, ...} ] } }` (double-nested — `success(['data'=>...])`). + +## وظایف + +### ۱. wrapper در `services/response.js` +```js +getSpecialtyDoctorCounts: (cityId) => + api.get(`api/v1/specialties/doctor-counts`, { params: { city_id: cityId } }), +``` + +### ۲. پاس‌دادن city id از صفحه‌ی server به کامپوننت client +در `app/specialties/page.js`: +```jsx +import { getStateInfo } from "@/lib/getStateInfo"; +async function Specialties() { + const { matchedCity } = await getStateInfo(); + return ( + + + + ); +} +``` +(صفحه باید `async` شود؛ `generateMetadata` موجود دست‌نخورده.) + +### ۳. fetch در `components/specialties/index.js` +- prop `cityId` بگیر. +- در `useEffect` (mount / تغییر cityId) `request.getSpecialtyDoctorCounts(cityId)` را صدا بزن، `res.data.data` را در state بگذار. +- تا رسیدن داده، می‌توان از `specialties.json` به‌عنوان نمایش اولیه استفاده کرد (یا اسکلت)، ولی منبعِ نهایی API است. +- جستجو روی همان لیستِ API اعمال شود (`searchOnList(list, value, "name")`). +- فقط تخصص‌های ریشه (یا همان رفتار فعلی) نمایش داده شوند؛ اگر API همه را می‌دهد و قبلاً json هم همه را داشت، رفتار را حفظ کن. + +```jsx +const [all, setAll] = useState([]); +const [filtered, setFiltered] = useState([]); +useEffect(() => { + request.getSpecialtyDoctorCounts(cityId) + .then((res) => { const items = res?.data?.data ?? []; setAll(items); setFiltered(items); }) + .catch(() => { setAll([]); setFiltered([]); }); +}, [cityId]); +const handleSearch = (value) => setFiltered(searchOnList(all, value, "name")); +``` + +### ۴. `ItemSpecialties.js` +بدون تغییر ساختار؛ فقط مطمئن شو `data.number_of_doctors` (که حالا از API می‌آید) و `data.id` (برای لینک `/doctors?specialties=`) درست‌اند. اگر `number_of_doctors` صفر بود، «۰ پزشک» نمایش داده شود (یا در صورت تمایل پنهان شود). + +## نکات مهم + +- پاسخ double-nested است → `res.data.data`. +- `matchedCity?.id` ممکن است `null` باشد (دامنه‌ی ناشناخته) → بدون `city_id` ارسال شود؛ بک‌اند شمارش سراسری می‌دهد (fallback). +- لینک کارت تخصص از `data.id` استفاده می‌کند (`/doctors?specialties=`) — مطمئن شو `id` در پاسخ API همان است که `/doctors` با `specialty_id`/`specialties` می‌پذیرد. +- App Router؛ `generateMetadata` و `getStateInfo` الگوی موجود؛ RTL/Vazir. +- بعد از تغییر: `npm run build` بدون خطا؛ روی `/specialties` تعداد واقعی پزشکانِ شهر زیر هر تخصص دیده شود؛ جستجو همچنان کار کند. diff --git a/app/specialties/page.js b/app/specialties/page.js index 21c627f..202f259 100644 --- a/app/specialties/page.js +++ b/app/specialties/page.js @@ -17,10 +17,11 @@ export async function generateMetadata() { }; } -function Specialties() { +async function Specialties() { + const { matchedCity } = await getStateInfo(); return ( - + ); } diff --git a/components/specialties/index.js b/components/specialties/index.js index 41dfb92..a44436d 100644 --- a/components/specialties/index.js +++ b/components/specialties/index.js @@ -1,18 +1,30 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import List from "./list"; import SmSearch from "../searchHead/smSearch"; import HeadPageList from "@/app/component/head"; import specialtiesData from "@/data/specialties.json"; import { searchOnList } from "@/helper"; +import { request } from "@/services/response"; -function SpecialtiesPage() { - const [filteredSpecialties, setFilteredSpecialties] = - useState(specialtiesData); +function SpecialtiesPage({ cityId }) { + const [all, setAll] = useState(specialtiesData); + const [filtered, setFiltered] = useState(specialtiesData); + + useEffect(() => { + request + .getSpecialtyDoctorCounts(cityId) + .then((res) => { + const items = res?.data?.data ?? []; + setAll(items); + setFiltered(items); + }) + .catch(() => {}); + }, [cityId]); const handleSearch = (value) => - setFilteredSpecialties(searchOnList(specialtiesData, value, "name")); + setFiltered(searchOnList(all, value, "name")); return ( <> @@ -21,12 +33,12 @@ function SpecialtiesPage() { detail="تخصص مورد نظرتان را جستجو کرده و یا از لیست زیر انتخاب نمایید:" > - + ); } diff --git a/services/response.js b/services/response.js index bd6a74d..082e47a 100644 --- a/services/response.js +++ b/services/response.js @@ -51,6 +51,11 @@ export const request = { Authorization: "", }, }), + getSpecialtyDoctorCounts: (cityId) => + api.get("api/v1/specialties/doctor-counts", { + params: cityId ? { city_id: cityId } : {}, + headers: { Authorization: "" }, + }), getAppointmentWeeklySchedule: (uuid) => api.get(`api/v1/appointment-settings/weekly-schedule/${uuid}`), postAppointmentWeeklySchedule: () =>