Files
nobat724_front/.claude/prompt/appointment-calendar-disabled-dates.md
hamedandClaude Opus 4.8 96f4126c0b feat(appointment): add getMonthAvailability request wrapper
Public wrapper for GET /api/v1/appointment-settings/month-availability
/{doctor_uuid}?year=&month= so the calendar can learn which days are
bookable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:29:31 +03:30

133 lines
9.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.
# نمایش روزهای تعطیل/غیرقابل‌انتخاب روی تقویم صفحه‌ی نوبت + احترام به بازه‌ی رزرو
## پروژه
`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 (
<AppointmentPage doctor={doctor} disabledDates={[]} matchedCity={matchedCity} />
);
```
### `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 با پیام توصیفی.