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>
7.5 KiB
وضعیت «نوبتدهی فعال/غیرفعال» پروفایل پزشک از 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
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
{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 بده:
<AppointmentList doctor={doctor} doctorSlug={slug} bookingLocations={bookingLocations} />
(نام prop و محل رندر را با کد واقعی صفحه تطبیق بده — AppointmentList ممکن است از طریق
کامپوننت میانی رندر شود؛ در آن صورت prop را از همان مسیر عبور بده.)
۲. قاعدهٔ واحد فعال/غیرفعال
یک helper کوچک در همان components/doctor/appointmentList/ (نه util سراسری جدید):
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_turnbackend همخانواده باشد. الگوی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هر دو سبز.