Files
nobat724_front/.claude/prompt/booking-locations-day-aware.md
T
hamedandClaude Opus 4.8 b37096048c feat(booking): show only locations that can actually be booked that day
The site offered a "personal practice" for a doctor who has no personal address
at all — the schedule existed but its shifts pointed at the clinic's address, so
there was nowhere to go. The backend now filters those out; this consumes the
filtered contract and adds the per-day dimension.

- getBookingLocations takes an optional date and the appointment page refetches
  on it, merging available_on_date into the existing list rather than replacing
  it, so browsing the calendar never resets the user's choice.
- The browsed day had to be lifted out of the Date step: selectedDate is only
  set once a slot is confirmed, far too late to drive availability.
- A location closed on the chosen day renders disabled with «در این روز نوبت
  ندارد», and when every location is closed the step says so instead of showing
  an empty slot list. If the already-selected location closes, a notice appears
  with a link back to the picker — silently showing nothing was the failure mode
  worth avoiding.
- Doctor profile: workLocation in the Physician JSON-LD is limited to addresses
  that appear in booking_locations, since schema.org presents them as places a
  patient can attend. The address card still lists the others — they are real
  practice details — tagged «بدون نوبت‌دهی آنلاین».

Verified end-to-end with a temporary unused address on the test doctor: the
visible card listed both and tagged the unused one, while workLocation carried
only the bookable one. The row was removed afterwards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 14:44:49 +03:30

9.8 KiB
Raw Blame History

نمایش محل‌های نوبت‌دهی فقط بر اساس برنامهٔ همان روز

پروژه

nobat724_front

پرامپت همتای backend که باید اول اجرا شود: clinicpro/.claude/prompt/fix-booking-context-slots-and-phantom-locations.md

زمینه

صفحهٔ رزرو حالا محل نوبت‌دهی (مطب شخصی / کلینیک) را از GET /api/v1/appointment-booking-locations/{doctorUuid} می‌گیرد و کاربر یکی را انتخاب می‌کند.

اما این فهرست وضعیت واقعی رزرو در یک روز مشخص را نشان نمی‌دهد. برای «دکتر تست» (bcabb3a8-cae3-45ec-876c-548f9c1e1569) گزینهٔ «مطب شخصی» نمایش داده می‌شود، در حالی که در دیتابیس این پزشک هیچ آدرس شخصی ثبت‌شده‌ای ندارد و شیفت برنامهٔ شخصی‌اش هم location_id = NULL است. یعنی محلی که اصلاً قابل رزرو نیست، به بیمار پیشنهاد می‌شود.

مشکل / هدف

قاعدهٔ درست نمایش یک محل:

  1. برای آن محل حداقل یک آدرس ثبت‌شده وجود داشته باشد، و
  2. در برنامهٔ کاری، همان آدرس برای شیفت‌های آن محل انتخاب شده باشد، و
  3. برای روز انتخاب‌شده حداقل یک اسلات آزاد داشته باشد.

اگر هر کدام برقرار نباشد، آن محل نباید به‌عنوان گزینهٔ قابل انتخاب نمایش داده شود — نه در /appointment/[doctorId] و نه در پروفایل پزشک /doctor/[slug].

شرط‌های ۱ و ۲ در backend اعمال می‌شوند (پرامپت همتا). این پرامپت شرط ۳ و مصرف درست فهرست را پوشش می‌دهد.

قرارداد API بعد از تغییر backend

GET /api/v1/appointment-booking-locations/{doctorUuid}
GET /api/v1/appointment-booking-locations/{doctorUuid}?date=YYYY-MM-DD
  • بدون date: فقط محل‌های معتبر (آدرس دارند و شیفت روی آدرس دارند). محل بدون آدرس دیگر اصلاً برنمی‌گردد.
  • با date: هر آیتم فیلد available_on_date (بولین) می‌گیرد و پاسخ date را echo می‌کند.
  • opening_hours هر آیتم حالا day_index و location_id هم دارد.

بقیهٔ قرارداد بدون تغییر: مرتب بر اساس next_available_at صعودی، پاسخ double-nested (json.data.data).

فایل‌های مرتبط

فایل نقش
services/response.js getBookingLocations — باید date بگیرد
components/appointment/index.js AppointmentPage — نگه‌دارندهٔ bookingLocations و selectedLocation
components/appointment/Container.js مرحلهٔ انتخاب محل / سرویس / تاریخ
components/appointment/location/LocationSelect.js کارت‌های انتخاب محل
components/appointment/date/index.js مرحلهٔ تاریخ + دکمهٔ «تغییر محل»
app/component/date/datePicker/index.js تقویم ماه (getMonthAvailability)
app/doctor/[slug]/page.js پروفایل پزشک + JSON-LD
components/doctor/detailDoctor/cards/locations/index.js کارت «موقعیت مکانی» در پروفایل

وضعیت فعلی

فهرست محل‌ها وابسته به روز نیست

components/appointment/index.js — یک‌بار در mount گرفته می‌شود و تا آخر ثابت می‌ماند:

  useEffect(() => {
    if (!doctor?.uuid) return;
    request
      .getBookingLocations(doctor.uuid)
      .then((res) => {
        const d = res?.data?.data ?? res?.data ?? {};
        const list = Array.isArray(d.booking_locations) ? d.booking_locations : [];
        setBookingLocations(list);
        ...
      })

selectedDate بعداً در همین کامپوننت ست می‌شود ولی هیچ‌وقت به این فراخوانی برنمی‌گردد.

پروفایل پزشک همهٔ آدرس‌ها را نشان می‌دهد

app/doctor/[slug]/page.jsworkLocation در JSON-LD و کارت «موقعیت مکانی» از getDoctorAddresses(doctor.id) می‌آیند که همهٔ آدرس‌ها را برمی‌گرداند، مستقل از اینکه در برنامهٔ نوبت‌دهی استفاده شده باشند یا نه.

وظایف

۱. date در لایهٔ سرویس

services/response.js:

  getBookingLocations: (doctor_uuid, date = null) =>
    api.get(`api/v1/appointment-booking-locations/${doctor_uuid}`, {
      params: date ? { date } : {},
      ...removeTokenHead,
    }),

۲. واکشی دوباره با تغییر روز

در components/appointment/index.js:

  • فهرست اولیه بدون date گرفته شود (برای مرحلهٔ انتخاب محل، قبل از انتخاب روز).
  • بعد از انتخاب روز، دوباره با date گرفته شود و available_on_date روی کارت‌ها اعمال شود.

selectedDate در این کامپوننت timestamp است؛ برای پارامتر API باید به YYYY-MM-DD تبدیل شود (از moment-jalaali که در پروژه هست استفاده کن، همان الگوی app/component/date/dateTime/index.js که moment.unix(date).format("YYYY-MM-DD") می‌زند).

حالت مرزی مهم: اگر روزِ انتخاب‌شده محلِ انتخاب‌شده را غیرفعال کند (available_on_date === false)، نباید بی‌صدا اسلات خالی نشان دهی. یا کاربر را به مرحلهٔ انتخاب محل برگردان با پیام روشن، یا خودکار به اولین محلِ باز در آن روز سوییچ کن و این جابه‌جایی را اطلاع بده. بی‌صدا نگه‌داشتن محلِ بسته = صفحهٔ خالی بدون توضیح.

۳. UI کارت‌های محل

components/appointment/location/LocationSelect.js:

  • وقتی available_on_date === false، کارت غیرفعال شود (disabled، cursor-not-allowed، کم‌رنگ) با برچسب «در این روز نوبت ندارد».
  • کارت غیرفعال قابل کلیک نباشد.
  • اگر هیچ محلی در آن روز باز نبود، پیام روشن بده و کاربر را به انتخاب روز دیگر هدایت کن.

از تم و کامپوننت‌های موجود استفاده کن (MUI v5 + Tailwind، RTL، فونت Vazir) — طراحی جدید نساز.

۴. تقویم ماه هم per-location است

app/component/date/datePicker/index.js الان clinicUuid می‌گیرد و getMonthAvailability را با آن صدا می‌زند — این درست است و نیازی به تغییر ندارد. فقط مطمئن شو بعد از سوییچ محل، کش ماه‌ها پاک می‌شود (همان effect موجود روی clinicUuid).

۵. پروفایل پزشک — فقط محل‌های قابل رزرو

در app/doctor/[slug]/page.js:

  • getBookingLocations (بدون date) را server-side بگیر — همین الان برای availableService و openingHoursSpecification گرفته می‌شود.

  • کارت «موقعیت مکانی» (components/doctor/detailDoctor/cards/locations/) و workLocation در JSON-LD باید فقط آدرس‌هایی را نشان دهند که location_uuid آن‌ها در booking_locations آمده است.

    const bookableUuids = new Set(
      bookingLocations.map((l) => l.location_uuid).filter(Boolean)
    );
    const bookableAddresses = addresses.filter((a) => bookableUuids.has(a.uuid));
    
  • تصمیم لازم: آدرسی که ثبت شده ولی در هیچ برنامه‌ای استفاده نشده، اطلاعات واقعی مطب است و حذف کاملش از پروفایل ممکن است خواسته نباشد. پیشنهاد: در کارت «موقعیت مکانی» نمایش داده شود ولی بدون دکمهٔ «دریافت نوبت»، و در JSON-LD نیاید (چون schema.org آن را قابل مراجعه اعلام می‌کند). اگر تصمیم دیگری گرفتی، در PR بنویس.

۶. لینک CTA

components/doctor/appointmentList/index.js — اگر پروفایل محل مشخصی را برجسته کرد، CTA همان را حمل کند: /appointment/${doctorSlug}?clinic_uuid=${clinicUuid}. اگر محلی برجسته نشده، پارامتر ندهد تا صفحهٔ رزرو خودش انتخابگر را نشان دهد (رفتار فعلی، درست است).

نکات مهم

  • backend اول. تا وقتی فیلتر سمت سرور اعمال نشده، «مطب شخصی» جعلی همچنان برمی‌گردد و کار فرانت قابل تست نیست.
  • پاسخ double-nested: json?.data?.data ?? json?.data.
  • endpoint عمومی است و removeTokenHead می‌گیرد؛ برای رزرو نهایی { requireAuth: true }.
  • تاریخ‌ها شمسی با jalali-moment / moment-jalaali؛ رشته‌های جدید فارسی؛ RTL.
  • slug پزشک = uuid.
  • هر صفحه generateMetadata صادر کند و params همیشه await شود.
  • تست دستی: «دکتر تست» (bcabb3a8-cae3-45ec-876c-548f9c1e1569) — بعد از فیلتر backend نباید هیچ گزینهٔ «مطب شخصی» ببیند، فقط کلینیک «علی بهروزی». روزی که کلینیک شیفت ندارد هم باید محل را غیرفعال نشان دهد.
  • بعد از تغییرات: npm run lint و npm run build هر دو سبز.