Files
nobat724_front/.claude/prompt/doctor-profile-booking-state-from-locations.md
hamedandClaude Fable 5 36816eded2 fix(doctor): derive booking state from booking_locations
The profile decided "نوبت‌دهی غیرفعال است" from `doctor.active` alone, while the
page already had `booking_locations` — the more precise source, since the backend
only returns locations that are genuinely bookable. The two could disagree, and
for a doctor bookable only at a clinic they did.

A shared `bookingState` helper now drives both the desktop card and the mobile
bar: any location means bookable, the label comes from the earliest
`next_available_at`, and locations with no capacity yet read as "فعلاً نوبت خالی
ندارد" rather than disabled. With no locations at all it falls back to the
previous `doctor.active` / `free_turn` fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 16:25:37 +03:30

132 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# وضعیت «نوبت‌دهی فعال/غیرفعال» پروفایل پزشک از 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
<AppointmentList doctor={doctor} doctorSlug={slug} bookingLocations={bookingLocations} />
```
(نام 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` هر دو سبز.