# نمایش روزهای تعطیل/غیرقابل‌انتخاب روی تقویم صفحه‌ی نوبت + احترام به بازه‌ی رزرو ## پروژه `nobat724_front` — سایت عمومی نوبت‌دهی. **این پرامپت بعد از پرامپت backend اجرا شود.** > **Cross-repo:** این کار به endpoint جدید backend وابسته است: > `GET /api/v1/appointment-settings/month-availability/{doctorUuid}?year=&month=` > که در پرامپت `clinicpro/.claude/prompt/doctor-booking-window-and-month-availability.md` ساخته می‌شود. اگر آن endpoint هنوز نیست، **متوقف شو و اول backend را اجرا کن.** ## زمینه تقویم دوماهه‌ی صفحه‌ی نوبت (`/appointment/[doctorId]`) الان همه‌ی روزهای آینده را قابل‌انتخاب نشان می‌دهد، چون در اصلاح قبلی فراخوانی endpoint ناموجود `appointment/not-available` حذف شد و `disabledDates={[]}` پاس داده می‌شود. در نتیجه روزهای تعطیلِ پزشک (مثلاً ۲۷/۰۳/۱۴۰۵ که با date override بسته شده) روی تقویم خاکستری نمی‌شوند و کاربر می‌تواند رویشان کلیک کند — بعد فقط لیست اسلات خالی می‌بیند. همچنین بازه‌ی رزرو (مثلاً «تا ۲ ماه جلوتر») روی تقویم اعمال نمی‌شود. ## مشکل / هدف تقویم باید روزهای غیرقابل‌انتخاب را خاکستری و disable کند. این روزها از endpoint `month-availability` می‌آیند: تعطیلات، date overrideهای بسته، روزهای بدون شیفت، و روزهای خارج از بازه‌ی رزرو پزشک. چون تقویم دوماهه است، باید برای **هر دو ماه نمایش‌داده‌شده** داده گرفته شود و با تغییر ماه (navigation) داده‌ی ماه جدید هم لود شود. ## فایل‌های مرتبط | فایل | نقش | |------|-----| | `services/response.js` | افزودن `getMonthAvailability(doctorUuid, year, month)` | | `app/appointment/[doctorId]/page.js` | الان `disabledDates={[]}` می‌فرستد (سرور-ساید) | | `components/appointment/index.js` → `Container` → `date/index.js` → `Time` → `SelectDatePicker` | زنجیره‌ی پاس‌دادن `disabledDates` | | `app/component/date/datePicker/index.js` | تقویم دوماهه‌ی client؛ `disabledDates` آرایه‌ی Unix timestamp است | | `components/common/InlineJalaliMonth.js` | رندر یک ماه؛ `isDisabled(date)` روز را خاکستری/disable می‌کند | | `lib/appointmentSlots.js` | adapter موجود اسلات‌ها (الگوی adapter جدا) | ## وضعیت فعلی (کد واقعی) ### `app/appointment/[doctorId]/page.js` — disabledDates خالی ```js let doctor = null; try { const doctorRes = await axiosInstance.get(`${API_URL}/api/v1/doctor/${doctorId}`); doctor = doctorRes.data?.data?.data; } catch (error) {} return ( ); ``` ### `app/component/date/datePicker/index.js` — مصرف disabledDates ```js function DatePicker({ setDate, disabledDates = [] }) { const [baseMonth, setBaseMonth] = useState(moment().tz("Asia/Tehran")); // ... const isDisabled = (date) => { const day = moment(date).startOf("day"); if (day.isBefore(moment().startOf("day"))) return true; return disabledDates.some((ts) => day.isSame(moment.unix(ts).startOf("day"))); }; // auto-select nearest available day از همین isDisabled استفاده می‌کند // دو InlineJalaliMonth: baseMonth (راست) + secondMonth = baseMonth+1 (چپ) } ``` > `disabledDates` آرایه‌ی Unix timestamp (ثانیه) است؛ `isDisabled` گذشته را هم می‌بندد. `baseMonth` با فلش‌های ناوبری تغییر می‌کند. ### `services/response.js` — توابع slot موجود ```js getAppointmentSlots: (doctor_uuid, date) => api.get(`api/v1/appointment-slots?doctor_uuid=${doctor_uuid}&date=${date}`, removeTokenHead), ``` ## قرارداد endpoint جدید backend (مرجع) ``` GET /api/v1/appointment-settings/month-availability/{doctorUuid}?year=&month= (PUBLIC) ``` ```json { "success": true, "data": { "year": 2026, "month": 6, "disabled_dates": ["2026-06-16", "2026-06-20"], "online_booking_enabled": true, "booking_window": { "value": 2, "unit": "month" } } } ``` > `disabled_dates` فرمت `Y-m-d` میلادی. **قبل از پیاده‌سازی، با پرامپت backend چک کن که endpoint ورودی شمسی می‌خواهد یا میلادی** — اگر میلادی است، ماه شمسیِ نمایش‌داده‌شده را باید به بازه‌ی میلادی تبدیل و برای ماه(های) میلادی متناظر صدا بزنی. interceptor `services/api.js` یک‌بار پاسخ را باز می‌کند → داده در `res.data`. ## وظایف اجرای مرحله‌به‌مرحله؛ بعد از هر قابلیت `npm run build` و سپس commit جدا. ### ۱. افزودن `getMonthAvailability` به `services/response.js` ```js getMonthAvailability: (doctor_uuid, year, month) => api.get(`api/v1/appointment-settings/month-availability/${doctor_uuid}`, { params: { year, month }, }), ``` (public — بدون `requireAuth`؛ از همان الگوی `getAppointmentSlots` پیروی کن.) ### ۲. لود داده‌ی در‌دسترس‌بودن در `DatePicker` و disable‌کردن روزها - چون `DatePicker` کامپوننت client است و `doctor.uuid` در آن در دسترس نیست (الان فقط `setDate`/`disabledDates` می‌گیرد)، یا `doctorUuid` را به‌عنوان prop از زنجیره پاس بده، یا از `useParams().doctorId` داخل `DatePicker` بخوان (همان uuid است — الگوی موجود `SendAppo`). - داخل `DatePicker` یک state `disabledTimestamps` نگه‌دار و با `useEffect` وابسته به `baseMonth`: - برای ماهِ `baseMonth` و ماهِ `secondMonth` (baseMonth+1) `getMonthAvailability` را صدا بزن (تبدیل ماه نمایشی به year/month مطابق قرارداد backend). - `disabled_dates` (`Y-m-d`) را به Unix timestamp (`moment(d,"YYYY-MM-DD").startOf("day").unix()`) تبدیل و در state بریز. - `isDisabled` فعلی را نگه‌دار ولی منبع `disabledDates` را از این state بگیر (یا prop `disabledDates` را با state داخلی merge کن). گذشته هم‌چنان بسته بماند. - **auto-select**: منطق انتخاب نزدیک‌ترین روز قابل‌انتخاب باید بعد از لود `disabledTimestamps` اجرا/بازاجرا شود تا روی روز تعطیل auto-select نشود. ### ۳. هندل ناوبری ماه - با کلیک فلش‌ها `baseMonth` تغییر می‌کند → `useEffect` دوباره داده‌ی دو ماه جدید را می‌گیرد. مطمئن شو state تجمعی است (ماه‌های قبلی پاک نشوند یا حداقل ماه‌های نمایش‌فعلی پوشش داده شوند) و درخواست تکراری بی‌مورد نزن (می‌توانی ماه‌های لودشده را cache کنی با یک `Set`/object key=`year-month`). ### ۴. (اختیاری ولی توصیه‌شده) نمایش وضعیت نوبت‌دهی خاموش - اگر `online_booking_enabled === false` در پاسخ، به‌جای تقویم یک پیام «نوبت‌دهی آنلاین این پزشک غیرفعال است» نشان بده و دکمه‌ی «تایید نوبت» را disable کن. (اگر می‌خواهی این بخش را جدا کنی، در گزارش ذکر کن.) ## نکات مهم - **وابستگی cross-repo:** بدون endpoint `month-availability` این کار کامل نمی‌شود. اگر نبود متوقف شو. - **شمسی/میلادی:** تقویم سایت شمسی است؛ endpoint احتمالاً میلادی می‌خواهد. تبدیل را با `moment-jalaali` انجام بده و **حدس نزن** — قرارداد دقیق را از پرامپت/داک backend بگیر. یک ماه شمسی روی دو ماه میلادی می‌افتد؛ یا برای پوشش کامل، بازه‌ی میلادیِ روزهای نمایش‌داده‌شده را محاسبه کن. - **timestampها Unix (ثانیه):** `disabledDates` که `isDisabled` می‌خواند آرایه‌ی Unix ثانیه است؛ تبدیل `Y-m-d → unix` را با `startOf("day")` در `Asia/Tehran` انجام بده تا با منطق فعلی هم‌خوان شود. - **interceptor:** `request.*` بدنه را یک‌بار باز می‌کند → `res.data.disabled_dates`. - **عدم رگرسیون:** تقویم دوماهه، ترتیب RTL (خرداد راست، تیر چپ)، فلش‌ها، و انتخاب خودکار نباید بشکنند. `InlineJalaliMonth` فقط `isDisabled` را مصرف می‌کند — منطق رنگ خاکستری از قبل هست. - **Multi-domain/Jalali/RTL:** حفظ شوند. - **تست:** `npm run build`؛ سپس دستی با پزشک `4a0594b1-008b-478a-a593-259b95d8c2dd` که یک date override بسته روی ۲۷/۰۳/۱۴۰۵ دارد — آن روز باید خاکستری و غیرقابل‌کلیک باشد، و روزهای خارج از بازه‌ی رزرو هم همین‌طور. سپس commit با پیام توصیفی.