# وضعیت «نوبت‌دهی فعال/غیرفعال» پروفایل پزشک از booking_locations ## پروژه `nobat724_front` پرامپت همتای backend که **باید اول اجرا شود**: `clinicpro/.claude/prompt/fix-public-booking-state-and-admin-context-slots.md` ## زمینه برای «دکتر تست» (`bcabb3a8-cae3-45ec-876c-548f9c1e1569`) صفحهٔ `/doctor/bcabb3a8-…` پیام «نوبت‌دهی غیرفعال است» نشان می‌دهد در حالی که نوبت‌دهی کلینیکش فعال است و همین صفحه، `booking_locations` معتبر (با `next_available_at` غیرتهی) را server-side می‌گیرد. علت (با curl تأیید شد): فیلدهای `active` / `free_turn` پاسخ `GET /api/v1/doctor/{uuid}` در backend فقط از **برنامهٔ شخصی** پزشک ساخته می‌شدند؛ برنامهٔ شخصیِ دکتر تست غیرفعال است و برنامهٔ کلینیکش دیده نمی‌شد. پرامپت همتا این را با تجمیع همهٔ برنامه‌ها اصلاح می‌کند. این پرامپت سمت سایت را defensive می‌کند: بنر و دکمهٔ نوبت‌دهی پروفایل نباید فقط به `doctor.active` تکیه کند وقتی خودِ صفحه دادهٔ دقیق‌تر (`booking_locations`) را در دست دارد. دو منبع نباید بتوانند حرف متناقض بزنند. ## مشکل / هدف قاعدهٔ واحد برای پروفایل پزشک: - **نوبت‌دهی فعال** ⇔ `booking_locations` غیرخالی است (backend فقط محل‌های واقعاً قابل‌رزرو را برمی‌گرداند: آدرس‌دار + شیفت فعال روی همان آدرس). - برچسب «اولین نوبت آزاد» از کمینهٔ `next_available_at` بین محل‌ها؛ اگر همه `null` بودند، محل‌ها فعالند ولی فعلاً ظرفیت ندارند → «فعلاً نوبت خالی ندارد» (نه «غیرفعال»). - `doctor.active === false` همراه با `booking_locations` خالی → «نوبت‌دهی غیرفعال است» (رفتار فعلی، درست). ## فایل‌های مرتبط | فایل | نقش | |------|-----| | `app/doctor/[slug]/page.js:43,128-150` | `getBookingLocations` server-side — از قبل موجود | | `components/doctor/appointmentList/index.js:8,20-24` | نوار موبایل — `doctor?.active === false \|\| !doctor?.free_turn` | | `components/doctor/appointmentList/ItemAppointment.js:10,42-56` | کارت دسکتاپ — `bookingDisabled = turn?.active === false` | | `app/component/ItemDoctor.js:89` | کارت لیست پزشکان — فقط فیلد backend (بدون تغییر) | ## وضعیت فعلی ### کارت دسکتاپ — `components/doctor/appointmentList/ItemAppointment.js:10` ```js function ItemAppointment({ turn, loading, doctorSlug }) { const bookingDisabled = !loading && turn?.active === false; ... {bookingDisabled || !turn?.free_turn ? "نوبت‌دهی غیرفعال است" : `اولین نوبت آزاد: ${turn.free_turn}`} ``` ### نوار موبایل — `components/doctor/appointmentList/index.js:20-24` ```js {doctor?.active === false || !doctor?.free_turn ? "نوبت‌دهی غیرفعال است" : `اولین نوبت آزاد: ${doctor.free_turn}`} ``` `AppointmentList` از `app/doctor/[slug]/page.js` رندر می‌شود که `bookingLocations` را همان‌جا دارد (`:128-129`) ولی به این کامپوننت پاس نمی‌دهد. ## وظایف ### ۱. پاس‌دادن `bookingLocations` به AppointmentList در `app/doctor/[slug]/page.js` همان آرایهٔ گرفته‌شده را prop بده: ```jsx ``` (نام prop و محل رندر را با کد واقعی صفحه تطبیق بده — `AppointmentList` ممکن است از طریق کامپوننت میانی رندر شود؛ در آن صورت prop را از همان مسیر عبور بده.) ### ۲. قاعدهٔ واحد فعال/غیرفعال یک helper کوچک در همان `components/doctor/appointmentList/` (نه util سراسری جدید): ```js export function bookingState(doctor, bookingLocations) { const hasLocations = Array.isArray(bookingLocations) && bookingLocations.length > 0; if (!hasLocations && doctor?.active === false) return { enabled: false, label: "نوبت‌دهی غیرفعال است" }; if (!hasLocations) return { enabled: doctor?.active !== false, label: doctor?.free_turn ? `اولین نوبت آزاد: ${doctor.free_turn}` : "نوبت‌دهی غیرفعال است" }; const earliest = bookingLocations .map((l) => l.next_available_at) .filter((t) => t != null) .sort((a, b) => a - b)[0] ?? null; return { enabled: true, label: earliest != null ? `اولین نوبت آزاد: ${formatJalali(earliest)}` : "فعلاً نوبت خالی ندارد", }; } ``` - `formatJalali` با `jalali-moment`/`moment-jalaali` موجود پروژه: روزِ هفته + ساعت (مثل «شنبه ۰۹:۰۰») تا با فرمت `free_turn` backend هم‌خانواده باشد. الگوی `moment.unix(ts)` مثل `app/component/date/dateTime/index.js`. - هر دو کامپوننت (`index.js` نوار موبایل و `ItemAppointment.js`) از همین helper استفاده کنند؛ شرط‌های تکراری فعلی حذف شوند. - دکمهٔ «دریافت نوبت» با `enabled === true` فعال است حتی وقتی `earliest === null` (صفحهٔ رزرو خودش روزهای بدون ظرفیت را نشان می‌دهد). ### ۳. کارت لیست پزشکان — بدون تغییر `app/component/ItemDoctor.js:89` فقط `free_turn`/`active` پاسخ لیست را دارد و `booking_locations` برای هر آیتم لیست fetch نمی‌شود (N درخواست اضافه ممنوع). بعد از فیکس backend همین فیلدها درست می‌شوند — این فایل را دست نزن. ## نکات مهم - **backend اول.** قبل از اجرای پرامپت همتا، `doctor.active` برای دکتر تست همچنان false برمی‌گردد؛ helper این را می‌پوشاند ولی تست کامل فقط بعد از هر دو ممکن است. - پاسخ‌های API double-nested: `json?.data?.data ?? json?.data` — `getBookingLocations` موجود در صفحه همین را رعایت می‌کند؛ عوضش نکن. - `booking_locations` فقط محل‌های معتبر را دارد (فیلتر backend از پرامپت‌های قبلی) — دوباره فیلتر نکن. - رشته‌های جدید فارسی، RTL، تم موجود (MUI v5 + Tailwind، فونت Vazir) — طراحی جدید نساز. - slug پزشک = `uuid`؛ `params` همیشه `await`؛ صفحه `generateMetadata` دارد — دست نزن. - تست دستی: `/doctor/bcabb3a8-cae3-45ec-876c-548f9c1e1569` باید «اولین نوبت آزاد» و دکمهٔ فعال «دریافت نوبت» نشان دهد (دکتر تست فقط برنامهٔ کلینیکی فعال دارد). یک پزشک واقعاً غیرفعال (بدون هیچ محل) همچنان «نوبت‌دهی غیرفعال است» ببیند. - بعد از تغییرات: `npm run lint` و `npm run build` هر دو سبز.