feat: implement service mode completion for nobat724_front
- Add task for completing service mode in clinicpro with detailed objectives and acceptance criteria. - Create architecture documentation for task 00b, outlining involved components and necessary changes. - Develop checklist for task 00b to ensure all requirements are met. - Document implementation notes for task 00b, emphasizing API contract checks and design system adherence. - Update task documentation for task 00b, specifying goals and current issues with service mode.
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
# معماری — تسک ۰۰ب
|
||||
|
||||
پروژه: `nobat724_front` · Next.js 15 App Router · MUI v5 + Tailwind · RTL · Vazir
|
||||
|
||||
## فایلهای درگیر
|
||||
|
||||
```
|
||||
components/appointment/
|
||||
├── index.js # ارکستراتور مراحل — تغییر جزئی
|
||||
├── service/index.js # ⚠️ بازنویسی با توکن تم
|
||||
├── date/index.js # مصرف adaptServiceSlots
|
||||
└── detail/SubmitData.js # تغییر جزئی
|
||||
|
||||
lib/appointmentSlots.js # adaptServiceSlots شیفتآگاه
|
||||
services/response.js # endpoint های جدید تسک ۰۰
|
||||
|
||||
components/dashboard/userAccount/sidebars/turns/
|
||||
├── Card.js # + سرویس و مدت
|
||||
├── isTurnsDetails/DetailLg.js
|
||||
├── isTurnsDetails/DetailSm.js
|
||||
└── isTurnsDetails/ButtonData.js # + جابهجایی سرویسآگاه
|
||||
```
|
||||
|
||||
## ۱. بازنویسی `service/index.js` — توکن، نه hex
|
||||
|
||||
وضعیت فعلی چهار رنگ hard-code دارد و در دارکمود میشکند:
|
||||
|
||||
```jsx
|
||||
// وضعیت فعلی
|
||||
<h2 className="text-[16px] font-bold text-[#3B3B3B] mb-4">۱. انتخاب سرویس</h2>
|
||||
className={active ? "border-[#5559CE] bg-[#5559CE]/5"
|
||||
: "border-gray-200 bg-white hover:border-[#5559CE]"}
|
||||
```
|
||||
|
||||
```jsx
|
||||
// هدف — همان ساختار DOM، رنگ از تم
|
||||
<h2 className="text-base font-bold text-foreground mb-4">۱. انتخاب سرویس</h2>
|
||||
className={active
|
||||
? "border-primary bg-primary/5"
|
||||
: "border-border bg-surface hover:border-primary"}
|
||||
```
|
||||
|
||||
⚠️ **نام دقیق کلاسها را از `tailwind.config.js` و `mui/index.js` همین پروژه بردار.**
|
||||
اسمهای بالا نمونهاند. قاعده: هر رنگی که در بقیهٔ مراحل رزرو (`location/`، `date/`،
|
||||
`information/`) استفاده میشود، اینجا هم همان — نه یک پالت جدید.
|
||||
|
||||
**رفتار عوض نمیشود:** همان toggle، همان ساختار، همان متنها. فقط منبع رنگ.
|
||||
|
||||
اگر پروژه توکن معادل ندارد (مثلاً `bg-surface` تعریف نشده)، از همان الگویی استفاده کن
|
||||
که مرحلهٔ قبلی (`location/index.js`) دارد — نه ساختن توکن جدید در این تسک.
|
||||
|
||||
## ۲. حذف محاسبهٔ موازی مدت
|
||||
|
||||
```js
|
||||
// ❌ وضعیت فعلی — منبع دوم حقیقت
|
||||
const totalMinutes = services
|
||||
.filter((s) => draft.includes(s.uuid))
|
||||
.reduce((sum, s) => sum + (Number(s.duration_minutes) || 0), 0);
|
||||
```
|
||||
|
||||
مدت باید از پاسخ `appointment-service-slots` بیاید که از قبل `total_duration_minutes` و
|
||||
`buffer_minutes` دارد. ولی یک مسئلهٔ ترتیبی هست: مرحلهٔ انتخاب سرویس **پیش از** انتخاب
|
||||
روز است، و آن endpoint تاریخ میخواهد.
|
||||
|
||||
دو گزینه:
|
||||
|
||||
| گزینه | ارزیابی |
|
||||
|---|---|
|
||||
| فراخوانی `appointment-service-slots` با تاریخ امروز فقط برای گرفتن مدت | یک درخواست اضافه، و اگر امروز تعطیل باشد پاسخ خالی است ولی `total_duration_minutes` همچنان میآید ✅ |
|
||||
| نگهداشتن محاسبهٔ فرانت بهعنوان تخمین + اصلاح در مرحلهٔ بعد | بیمار دو عدد متفاوت میبیند ❌ |
|
||||
|
||||
**انتخاب: گزینهٔ اول**، با یک تفاوت مهم — مدت **تخمینی** برچسب میگیرد تا وقتی روز
|
||||
انتخاب نشده:
|
||||
|
||||
```js
|
||||
// مرحلهٔ انتخاب سرویس
|
||||
const { data } = useServiceDuration(doctorUuid, clinicUuid, draft); // hook جدید
|
||||
const minutes = data?.total_duration_minutes ?? fallbackSum(draft); // fallback با console.warn
|
||||
|
||||
<span>مدت تقریبی: {minutes} دقیقه</span> // پیش از انتخاب روز
|
||||
<span>مدت نوبت: {minutes} دقیقه</span> // پس از انتخاب روز، از همان پاسخ
|
||||
```
|
||||
|
||||
`fallbackSum` فقط برای بکاند قدیمی است و `console.warn` میزند. حذفش پس از deploy تسک ۰۰.
|
||||
|
||||
## ۳. `adaptServiceSlots` شیفتآگاه
|
||||
|
||||
مشکل: همهٔ زمانها در یک تب با برچسب ثابت «زمانهای خالی» جمع میشوند و `end_time`
|
||||
اشتباه است (پایانِ آخرین **شروع**، نه پایان نوبت).
|
||||
|
||||
```js
|
||||
// هدف — گروهبندی بر اساس شکاف زمانی، با برچسب واقعی
|
||||
export function adaptServiceSlots(slotsResponse) {
|
||||
const payload = slotsResponse?.data ?? slotsResponse ?? {};
|
||||
const starts = payload.start_times ?? [];
|
||||
if (!starts.length) return [];
|
||||
|
||||
const durationMin = Number(payload.total_duration_minutes) || 0;
|
||||
const GAP_THRESHOLD_MIN = 60; // شکاف بیشتر از یک ساعت = شیفت جدا
|
||||
|
||||
const groups = [];
|
||||
let current = null;
|
||||
|
||||
for (const s of starts) {
|
||||
const gapMin = current
|
||||
? (s.start - current.slots[current.slots.length - 1].start) / 60
|
||||
: Infinity;
|
||||
|
||||
if (!current || gapMin > GAP_THRESHOLD_MIN) {
|
||||
current = { slots: [] };
|
||||
groups.push(current);
|
||||
}
|
||||
current.slots.push({ ...s, is_available: true });
|
||||
}
|
||||
|
||||
return groups.map((g) => {
|
||||
const first = g.slots[0];
|
||||
const last = g.slots[g.slots.length - 1];
|
||||
const endTime = last.end_time ?? addMinutes(last.start_time, durationMin);
|
||||
return {
|
||||
start_time: first.start_time,
|
||||
end_time: endTime,
|
||||
label: `${first.start_time} - ${endTime}`, // ← همان قالب حالت اسلاتی
|
||||
slots: g.slots,
|
||||
};
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
`end_time` هر start از قبل در پاسخ بکاند هست (`getServiceStartTimes` هر آیتم را با
|
||||
`end_time` میدهد) — پس `addMinutes` فقط fallback است.
|
||||
|
||||
**چرا آستانهٔ شکاف و نه اطلاعات شیفت از بکاند؟** پاسخ `appointment-service-slots`
|
||||
امروز فقط `start_times` مسطح میدهد و شیفت را نمیگوید. دو راه بود:
|
||||
|
||||
| راه | ارزیابی |
|
||||
|---|---|
|
||||
| افزودن گروهبندی شیفت به پاسخ بکاند | درستتر، ولی تغییر قرارداد endpoint که سه کلاینت مصرفش میکنند — و تسک ۰۰ آن را قفل نکرده ولی بازش هم نکرده |
|
||||
| **گروهبندی هیوریستیک در فرانت** ✅ | بدون تغییر قرارداد؛ برای شیفت صبح/عصر (شکاف معمولاً ۲-۳ ساعت) دقیق است |
|
||||
|
||||
انتخاب دوم برای این تسک. اگر بعداً دقت کافی نبود، تسک ۰۶ که `AvailabilityEngine` را
|
||||
میسازد میتواند گروهبندی واقعی را در پاسخِ **endpoint جدید** بدهد — بدون دست زدن به
|
||||
این یکی. این تصمیم را در `docs/api/appointment.md` سمت بکاند هم یادداشت کن.
|
||||
|
||||
⛔ `adaptSlots()` (حالت اسلاتی) **یک خط هم** عوض نمیشود.
|
||||
|
||||
## ۴. سرویس و مدت در پنل کاربر
|
||||
|
||||
`GET /api/v1/appointments/user` از قبل چه میدهد؟ **پیش از کدنویسی بررسی کن.** اگر
|
||||
`service_items` و `service_total_minutes` در پاسخ نیست:
|
||||
|
||||
- ستون `service_total_minutes` در تسک ۰۰ اضافه شد ✅
|
||||
- افزودنش به سریالایزر پاسخ، **بخشی از تسک ۰۰** است (ردیف ۱.۱۶ چکلیستش)
|
||||
- اگر جا افتاده، اینجا بهعنوان یک ردیف ⚠️ ثبت و به تسک ۰۰ برگردان
|
||||
|
||||
```jsx
|
||||
// Card.js — دو خط جدید، فقط وقتی داده هست
|
||||
{turn.service_items?.length > 0 && (
|
||||
<span className="…">{turn.service_items.map((s) => s.name).join("، ")}</span>
|
||||
)}
|
||||
{turn.service_total_minutes && (
|
||||
<span className="…">{turn.service_total_minutes} دقیقه</span>
|
||||
)}
|
||||
```
|
||||
|
||||
شرط `&&` اجباری است: نوبت اسلاتی این دو را ندارد و کارتش باید **دقیقاً** مثل امروز
|
||||
بماند. نوبت سرویسیِ قدیمی هم ممکن است `service_items` خالی داشته باشد → نام «—».
|
||||
|
||||
## ۵. جابهجایی سرویسآگاه از پنل
|
||||
|
||||
`services/response.js` سه متد جدید میگیرد:
|
||||
|
||||
```js
|
||||
serviceReschedule: (uuid, body) =>
|
||||
request.post(`api/v1/appointment/${uuid}/service-reschedule`, body, { requireAuth: true }),
|
||||
|
||||
getServiceSlotsForReschedule: (doctor_uuid, date, service_uuids, exclude_uuid, clinic_uuid) =>
|
||||
request.get(
|
||||
`api/v1/appointment-service-slots?doctor_uuid=${doctor_uuid}&date=${date}` +
|
||||
service_uuids.map((u) => `&service_item_uuids[]=${encodeURIComponent(u)}`).join("") +
|
||||
`&exclude_appointment_uuid=${exclude_uuid}` + clinicQuery(clinic_uuid),
|
||||
{ requireAuth: true }
|
||||
),
|
||||
```
|
||||
|
||||
`ButtonData.js` یک دکمهٔ «جابهجایی» میگیرد که مودال موجود
|
||||
(`isTurnsDetails/modal/index.js`) را با کامپوننت انتخاب زمان باز میکند — همان
|
||||
`components/appointment/date/` بازاستفاده میشود، نه یک انتخابگر جدید.
|
||||
|
||||
بیمار **مدت را وارد نمیکند**: `service-reschedule` فقط `start` میگیرد و مدت را از
|
||||
سرویسهای موجود نوبت حساب میکند (تسک ۰۰، بخش `ServiceRescheduleService`).
|
||||
|
||||
## UI — قواعد اجباری
|
||||
|
||||
رجوع: [_shared/ui-conventions.md](../_shared/ui-conventions.md)، بخش `nobat724_front`
|
||||
|
||||
- تم MUI از `mui/index.js` — تم جدید نساز
|
||||
- فونت فقط Vazir از `app/globals.css`
|
||||
- `darkMode: "class"`؛ صفحات عمومی `data-theme`، پنل `class` — هر دو بررسی شوند
|
||||
- کامپوننتهای موجود `components/appointment/*` توسعه داده شوند، مسیر موازی نه
|
||||
- تاریخ شمسی با `jalali-moment`
|
||||
- RTL — `ms-*`/`me-*`
|
||||
- هر صفحهای که دست خورد، `generateMetadata` و `await params` سالم بماند
|
||||
@@ -0,0 +1,123 @@
|
||||
# چکلیست — تسک ۰۰ب (سازگارسازی nobat724_front)
|
||||
|
||||
**وضعیت کلی:** ⏳ شروع نشده
|
||||
**آخرین بازبینی:** —
|
||||
|
||||
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
||||
خط سرخها: [_shared/red-lines.md](../_shared/red-lines.md) ·
|
||||
UI: [_shared/ui-conventions.md](../_shared/ui-conventions.md)
|
||||
|
||||
---
|
||||
|
||||
## ۰. خط سرخ — مسیر اسلاتی سایت
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۰.۱ | `adaptSlots()` یک خط هم عوض نشد | ⏳ | |
|
||||
| ۰.۲ | رندر تبهای شیفت در حالت اسلاتی دستنخورده | ⏳ | |
|
||||
| ۰.۳ | مسیر رزرو اسلاتی سرتاسر دستی تست شد — بیتبهبیت مثل قبل | ⏳ | سناریو ۳ |
|
||||
| ۰.۴ | کارت نوبت اسلاتی در پنل بدون تغییر | ⏳ | سناریو ۵ |
|
||||
|
||||
## ۱. پیشبررسی قرارداد API
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۱.۱ | `appointments/user` فیلد `service_items` دارد | ⏳ | اگر نه → به تسک ۰۰ برگردان |
|
||||
| ۱.۲ | `appointments/user` فیلد `service_total_minutes` دارد | ⏳ | همان |
|
||||
| ۱.۳ | هر `start_times[i]` فیلد `end_time` دارد | ⏳ | |
|
||||
| ۱.۴ | `total_duration_minutes` و `buffer_minutes` در پاسخ هستند | ⏳ | |
|
||||
| ۱.۵ | `exclude_appointment_uuid` روی `appointment-service-slots` کار میکند | ⏳ | تسک ۰۰ ساخته |
|
||||
|
||||
## ۲. انتخاب سرویس — دیزاین و منطق
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۲.۱ | چهار رنگ hard-code (`#3B3B3B` `#7A7A7A` `#5559CE` `bg-white`) حذف شد | ⏳ | |
|
||||
| ۲.۲ | کلاسها از همان الگوی `location/` و `date/` کپی شد، توکن جدید ساخته نشد | ⏳ | |
|
||||
| ۲.۳ | ساختار DOM و رفتار toggle عوض نشد | ⏳ | فقط منبع رنگ |
|
||||
| ۲.۴ | محاسبهٔ `reduce` مدت از فرانت حذف شد | ⏳ | |
|
||||
| ۲.۵ | مدت از `total_duration_minutes` بکاند میآید | ⏳ | |
|
||||
| ۲.۶ | `fallbackSum` با `console.warn` — موقت، تسک مقصد حذفش ثبت شد | ⏳ | |
|
||||
| ۲.۷ | برچسب «مدت تقریبی» پیش از انتخاب روز، «مدت نوبت» پس از آن | ⏳ | |
|
||||
| ۲.۸ | انتخاب صفر سرویس → دکمهٔ ادامه غیرفعال با راهنمای فارسی | ⏳ | |
|
||||
| ۲.۹ | محل سرویسی بدون سرویس `bookable` → پیام روشن + پیشنهاد محل دیگر | ⏳ | |
|
||||
|
||||
## ۳. `adaptServiceSlots` شیفتآگاه
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۳.۱ | گروهبندی بر اساس شکاف زمانی پیاده شد | ⏳ | |
|
||||
| ۳.۲ | آستانه = `max(60, durationMin)` | ⏳ | وگرنه نوبت بلند به تبهای تکعضوی میشکند |
|
||||
| ۳.۳ | برچسب واقعی `"HH:MM - HH:MM"` — نه «زمانهای خالی» ثابت | ⏳ | |
|
||||
| ۳.۴ | `end_time` از پاسخ بکاند، `addMinutes` فقط fallback | ⏳ | |
|
||||
| ۳.۵ | کامنت: هیوریستیک است، راه دقیق endpoint تسک ۰۶ | ⏳ | |
|
||||
| ۳.۶ | `start_times` خالی → `[]` و پیام دلیلدار در UI | ⏳ | |
|
||||
|
||||
## ۴. پنل کاربر — سرویس، مدت، جابهجایی
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۴.۱ | `Card.js` نام سرویسها را نشان میدهد (با شرط `&&`) | ⏳ | |
|
||||
| ۴.۲ | `Card.js` مدت را نشان میدهد (با شرط `&&`) | ⏳ | |
|
||||
| ۴.۳ | `DetailLg.js` و `DetailSm.js` هر دو | ⏳ | |
|
||||
| ۴.۴ | نوبت رزرو: فقط سرویس، بدون مدت | ⏳ | زمان ندارد |
|
||||
| ۴.۵ | نوبت سرویسی بدون `service_items` → «—»، بدون کرش | ⏳ | |
|
||||
| ۴.۶ | `services/response.js`: `serviceReschedule` اضافه شد | ⏳ | |
|
||||
| ۴.۷ | `services/response.js`: `getServiceSlotsForReschedule` با `exclude_appointment_uuid` | ⏳ | |
|
||||
| ۴.۸ | `ButtonData.js` دکمهٔ جابهجایی + مودال موجود | ⏳ | |
|
||||
| ۴.۹ | انتخابگر زمان: `components/appointment/date/` بازاستفاده شد، نه ساخت جدید | ⏳ | |
|
||||
| ۴.۱۰ | بیمار مدت وارد نمیکند — بکاند حساب میکند | ⏳ | |
|
||||
| ۴.۱۱ | خطای بکاند با پیام فارسی خودش نمایش داده میشود | ⏳ | نه «خطای نامشخص» |
|
||||
| ۴.۱۲ | پس از خطای تداخل، `refetchSlots()` اجرا میشود | ⏳ | |
|
||||
|
||||
## ۵. UI — قواعد سایت
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۵.۱ | تم MUI از `mui/index.js` — تم جدید ساخته نشد | ⏳ | |
|
||||
| ۵.۲ | فونت فقط Vazir — فونت جدید اضافه نشد | ⏳ | |
|
||||
| ۵.۳ | دارکمود صفحات عمومی (`data-theme`) بررسی شد | ⏳ | سناریو ۲ |
|
||||
| ۵.۴ | دارکمود پنل (`class`) بررسی شد | ⏳ | سناریو ۷ — مکانیزم متفاوت |
|
||||
| ۵.۵ | کامپوننت موازی ساخته نشد؛ `components/appointment/*` توسعه یافت | ⏳ | |
|
||||
| ۵.۶ | RTL بررسی شد (`ms/me` نه `ml/mr`) | ⏳ | |
|
||||
| ۵.۷ | موبایل بررسی شد — بدون اسکرول افقی | ⏳ | سناریو ۱۰ |
|
||||
| ۵.۸ | تاریخها شمسی با `jalali-moment` | ⏳ | |
|
||||
| ۵.۹ | همهٔ رشتهها فارسی | ⏳ | |
|
||||
| ۵.۱۰ | صفحاتی که دست خوردند `generateMetadata` و `await params` سالم دارند | ⏳ | |
|
||||
| ۵.۱۱ | دامنه گسترش نیافت — صفحهٔ رزرو بازطراحی نشد | ⏳ | انحراف بقیهٔ مراحل، اگر بود، ⚠️ ثبت شود |
|
||||
|
||||
## ۶. تست دستی — ده سناریو
|
||||
|
||||
| # | سناریو | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۶.۱ | رزرو سرویسی کامل تا پیامک | ⏳ | |
|
||||
| ۶.۲ | همان در دارکمود عمومی | ⏳ | |
|
||||
| ۶.۳ | رزرو اسلاتی کامل — بدون تغییر | ⏳ | ⛔ خط سرخ |
|
||||
| ۶.۴ | پزشک دو-شیفته سرویسی → دو تب با برچسب واقعی | ⏳ | |
|
||||
| ۶.۵ | پنل با نوبت اسلاتی تنها → بدون تغییر | ⏳ | |
|
||||
| ۶.۶ | پنل با نوبت سرویسی → سرویس و مدت | ⏳ | |
|
||||
| ۶.۷ | پنل در دارکمود | ⏳ | |
|
||||
| ۶.۸ | جابهجایی سرویسی → مدت حفظ | ⏳ | |
|
||||
| ۶.۹ | جابهجایی به زمان اشغال → پیام فارسی + refetch | ⏳ | |
|
||||
| ۶.۱۰ | همهٔ موارد بالا روی موبایل | ⏳ | |
|
||||
| ۶.۱۱ | تست واحد `adaptServiceSlots` (پنج حالت) | ⏳ | تابع خالص، بهترین کاندید |
|
||||
|
||||
## ۷. مستندات
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۷.۱ | `nobat724_front/CLAUDE.md` بخش «حالتهای نوبتدهی» | ⏳ | |
|
||||
| ۷.۲ | یادداشت هیوریستیک شیفت در `clinicpro/docs/api/appointment.md` | ⏳ | |
|
||||
|
||||
## ۸. بازبینی پایانی
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۸.۱ | همهٔ ردیفهای بالا وضعیت نهایی دارند (هیچ 🔄 و ⏳ بیدلیل) | ⏳ | |
|
||||
| ۸.۲ | `npm run build` بدون خطا | ⏳ | |
|
||||
| ۸.۳ | `npm run lint` بدون خطای جدید | ⏳ | |
|
||||
| ۸.۴ | ده سناریوی دستی بخش ۶ اجرا شد | ⏳ | |
|
||||
| ۸.۵ | چکلیست UI (بخش ۵) کامل شد | ⏳ | |
|
||||
| ۸.۶ | `clinic-pro-tauri` دستی بررسی شد — قرارداد مشترک نشکسته | ⏳ | همان `service_item` تکی |
|
||||
| ۸.۷ | commit شد، سپس `graphify update .` | ⏳ | |
|
||||
| ۸.۸ | موارد بهتعویقافتاده با دلیل و تسک مقصد ثبت شدند | ⏳ | `fallbackSum` |
|
||||
@@ -0,0 +1,157 @@
|
||||
# نکات پیادهسازی — تسک ۰۰ب
|
||||
|
||||
## ۱. اول قرارداد پاسخ را بررسی کن، بعد کد بزن
|
||||
|
||||
سه چیز را پیش از شروع تأیید کن:
|
||||
|
||||
```bash
|
||||
# ۱. پاسخ appointments/user چه فیلدهایی دارد؟
|
||||
curl -s -H "Authorization: Bearer $TOKEN" \
|
||||
https://clinic-pro.ddev.site/api/v1/appointments/user | jq '.data[0] | keys'
|
||||
|
||||
# ۲. start_times هر آیتم end_time دارد؟
|
||||
curl -s "https://clinic-pro.ddev.site/api/v1/appointment-service-slots?doctor_uuid=…&date=…&service_item_uuids[]=…" \
|
||||
| jq '.data.start_times[0]'
|
||||
|
||||
# ۳. total_duration_minutes و buffer_minutes در پاسخ هستند؟
|
||||
```
|
||||
|
||||
اگر `service_items` یا `service_total_minutes` در پاسخ `appointments/user` نیست،
|
||||
**به تسک ۰۰ برگردان** — سریالایزر آنجا اصلاح میشود، نه اینکه اینجا از endpoint دیگری
|
||||
دور بزنیم.
|
||||
|
||||
## ۲. رنگها را از همسایه کپی کن، نه از حافظه
|
||||
|
||||
```bash
|
||||
# ببین مرحلهٔ قبلی رزرو چه کلاسی میزند
|
||||
grep -n "className" components/appointment/location/index.js | head -30
|
||||
grep -n "className" components/appointment/date/index.js | head -30
|
||||
```
|
||||
|
||||
هدف: کامپوننت انتخاب سرویس **از بقیهٔ مراحل قابل تشخیص نباشد**. اگر بقیه `bg-white`
|
||||
میزنند و توکن ندارند، تو هم توکن جدید نساز — همان کاری را بکن که آنها میکنند،
|
||||
و اگر دارکمود در آنها هم شکسته است، این یک مسئلهٔ جدا است که در چکلیست ⚠️ ثبت
|
||||
میشود، نه اینکه در این تسک کل صفحهٔ رزرو بازطراحی شود.
|
||||
|
||||
**دامنه را گسترش نده.** فقط `service/index.js` که خودمان اضافه کردیم و از بقیه منحرف است.
|
||||
|
||||
## ۳. `fallbackSum` موقت است و باید هشدار بدهد
|
||||
|
||||
```js
|
||||
const minutes = data?.total_duration_minutes ?? (() => {
|
||||
console.warn('[booking] total_duration_minutes missing — falling back to client sum');
|
||||
return fallbackSum(draft, services);
|
||||
})();
|
||||
```
|
||||
|
||||
بدون `console.warn`، بکاندی که فیلد را نمیدهد بیصدا کار میکند و شش ماه بعد کسی
|
||||
نمیفهمد چرا مدت با نوبت نمیخواند. حذف `fallbackSum` پس از deploy تسک ۰۰ یک ردیف
|
||||
⏳ در چکلیست است با تسک مقصد مشخص.
|
||||
|
||||
## ۴. آستانهٔ شکاف: ۶۰ دقیقه، با دلیل
|
||||
|
||||
```js
|
||||
const GAP_THRESHOLD_MIN = 60;
|
||||
```
|
||||
|
||||
شیفت صبح/عصر معمولاً ۲-۳ ساعت فاصله دارد. یک نوبت ۹۰ دقیقهای هم میتواند شکاف ۹۰
|
||||
دقیقهای بسازد بدون اینکه شیفت جدا باشد — پس آستانه نباید کمتر از مدت نوبت باشد:
|
||||
|
||||
```js
|
||||
const threshold = Math.max(GAP_THRESHOLD_MIN, durationMin);
|
||||
```
|
||||
|
||||
این خط را فراموش نکن، وگرنه نوبتهای بلند به تبهای تکعضوی تقسیم میشوند.
|
||||
|
||||
هیوریستیک است و در کامنت باید بنویسی: راه دقیق، گروهبندی از سمت بکاند است که تسک ۰۶
|
||||
در endpoint جدید میدهد.
|
||||
|
||||
## ۵. شرط `&&` روی فیلدهای سرویسی در پنل
|
||||
|
||||
```jsx
|
||||
{turn.service_items?.length > 0 && ( … )}
|
||||
```
|
||||
|
||||
نه `turn.service_items.map(...)` خالی. نوبت اسلاتی این فیلد را ندارد و بدون شرط، کارت
|
||||
همهٔ نوبتهای اسلاتی کرش میکند — یعنی کل پنل کاربر میشکند، نه فقط یک خط.
|
||||
|
||||
تست: پنل کاربری که **فقط** نوبت اسلاتی دارد باید بدون هیچ تغییری رندر شود.
|
||||
|
||||
## ۶. جابهجایی: مودال موجود، انتخابگر موجود
|
||||
|
||||
```
|
||||
ButtonData.js → دکمهٔ «جابهجایی» → isTurnsDetails/modal/index.js
|
||||
└─ components/appointment/date/ بازاستفاده
|
||||
```
|
||||
|
||||
انتخابگر تاریخ/ساعت جدید نساز. کامپوننت `date/` از قبل هر دو حالت را میشناسد
|
||||
(`adaptSlots` و `adaptServiceSlots`) و همان را با props متفاوت صدا بزن.
|
||||
|
||||
## ۷. خطای بکاند را نمایش بده، نه پیام عمومی
|
||||
|
||||
```js
|
||||
// ❌
|
||||
catch { toast.error('خطایی رخ داد'); }
|
||||
|
||||
// ✅
|
||||
catch (err) {
|
||||
const msg = err?.response?.data?.errors?.[0]?.message ?? 'خطایی رخ داد';
|
||||
toast.error(msg);
|
||||
refetchSlots(); // ← فهرست زمانها بهروز شود
|
||||
}
|
||||
```
|
||||
|
||||
`ERR_SLOT_TAKEN` پیام فارسی دقیق دارد («این بازه زمانی قبلاً رزرو شده است»). نشان دادن
|
||||
«خطای نامشخص» یعنی بیمار همان دکمه را ده بار میزند. `refetchSlots()` بعد از خطای تداخل
|
||||
اجباری است.
|
||||
|
||||
## ۸. edge case ها
|
||||
|
||||
| حالت | رفتار درست |
|
||||
|---|---|
|
||||
| محل سرویسی بدون سرویس `bookable` | پیام روشن + پیشنهاد محل دیگر اگر باشد |
|
||||
| پزشک اسلاتی در مطب، سرویسی در کلینیک | تعویض محل، مرحلهٔ سرویس را ظاهر/پنهان میکند و انتخابها باطل میشوند (رفتار موجود `changeLocation`) |
|
||||
| `start_times` خالی | پیام دلیلدار، نه فهرست خالی |
|
||||
| `total_duration_minutes` غایب | `fallbackSum` + `console.warn` |
|
||||
| نوبت سرویسی قدیمی بدون `service_items` | نام «—»، مدت اگر هست نمایش، بدون کرش |
|
||||
| پنل کاربری فقط با نوبت اسلاتی | بیتبهبیت مثل امروز |
|
||||
| نوبت رزرو (`is_reserve`) در پنل | مدت نمایش داده نشود (زمان ندارد)، فقط سرویسها |
|
||||
| جابهجایی به زمان اشغالشده | پیام فارسی بکاند + `refetch` |
|
||||
| دارکمود در پنل (`class`) و صفحات عمومی (`data-theme`) | هر دو بررسی شوند — دو مکانیزم متفاوتاند |
|
||||
| یک سرویس با `duration_minutes = null` | بکاند `422` میدهد؛ UI پیامش را نشان دهد و آن سرویس را برجسته کند |
|
||||
|
||||
## ۹. تست
|
||||
|
||||
پروژه تست خودکار محدودی دارد. سناریوهای دستی اجباری (در چکلیست ثبت شوند):
|
||||
|
||||
```
|
||||
۱. رزرو سرویسی کامل: انتخاب محل سرویسی → سرویس → روز → ساعت → ثبت → پیامک
|
||||
۲. همان مسیر در دارکمود (صفحات عمومی، data-theme)
|
||||
۳. رزرو اسلاتی کامل — باید بیتبهبیت مثل قبل باشد
|
||||
۴. پزشک با دو شیفت در حالت سرویسی → دو تب زمانی با برچسب واقعی
|
||||
۵. پنل کاربر با نوبت اسلاتی تنها → بدون تغییر
|
||||
۶. پنل کاربر با نوبت سرویسی → سرویسها و مدت دیده میشود
|
||||
۷. پنل کاربر در دارکمود (class)
|
||||
۸. جابهجایی نوبت سرویسی → مدت حفظ میشود
|
||||
۹. جابهجایی به زمان اشغالشده → پیام فارسی + refetch
|
||||
۱۰. موبایل: هر ده مورد بالا، بدون اسکرول افقی
|
||||
```
|
||||
|
||||
اگر تست خودکار اضافه میکنی، `adaptServiceSlots` تابع خالص است و بهترین کاندید:
|
||||
|
||||
```
|
||||
tests/appointmentSlots.test.js
|
||||
- یک شیفت → یک گروه
|
||||
- دو شیفت با شکاف ۳ ساعت → دو گروه با برچسب درست
|
||||
- نوبت ۹۰ دقیقهای با شکاف ۹۰ دقیقه → یک گروه (آستانه = max(60, duration))
|
||||
- start_times خالی → []
|
||||
- end_time از پاسخ میآید، نه محاسبه
|
||||
```
|
||||
|
||||
## ۱۰. مستندات
|
||||
|
||||
`nobat724_front/CLAUDE.md` یک بخش کوتاه «حالتهای نوبتدهی» بگیرد: `slot` و `service`،
|
||||
اینکه per محل تعیین میشوند، و اینکه `adaptSlots`/`adaptServiceSlots` نقطهٔ تفکیکاند.
|
||||
|
||||
در `clinicpro/docs/api/appointment.md` یادداشت کن که گروهبندی شیفت در حالت سرویسی
|
||||
هیوریستیک سمت فرانت است و راه دقیقش endpoint تسک ۰۶ است.
|
||||
@@ -0,0 +1,136 @@
|
||||
# تسک ۰۰ب — سازگارسازی nobat724_front با وضعیت فعلی نوبتدهی سرویسی
|
||||
|
||||
**پروژه:** `nobat724_front` (سایت عمومی) · **فاز:** ۰ · **وابستگی:** ۰۰ · **زمان:** ۱۰-۱۴ ساعت
|
||||
**پیشنیاز همهٔ تسکهای ۰۱ به بعد**
|
||||
|
||||
---
|
||||
|
||||
## ⛔ خط سرخ
|
||||
|
||||
مسیر اسلاتی سایت دستکاری نمیشود: `adaptSlots()`، رندر تبهای شیفت، و همهٔ رفتار
|
||||
`booking_mode === 'slot'` عیناً میماند.
|
||||
رجوع: [_shared/red-lines.md](../_shared/red-lines.md)
|
||||
|
||||
---
|
||||
|
||||
## هدف
|
||||
|
||||
سایت حالت سرویسی را **میشناسد** ولی سه دسته مشکل دارد: انحراف از دیزاینسیستم،
|
||||
محاسبهٔ موازی مدت در فرانت، و نبود سرویس/مدت در پنل کاربر. این تسک همه را میبندد و
|
||||
سایت را با endpoint های جدید تسک ۰۰ همگام میکند.
|
||||
|
||||
## وضعیت فعلی
|
||||
|
||||
### ✅ کار میکند
|
||||
|
||||
| مورد | فایل |
|
||||
|---|---|
|
||||
| تشخیص حالت per محل | `components/appointment/index.js:125` — `selectedLocation?.booking_mode === "service"` |
|
||||
| مرحلهٔ انتخاب سرویس | `components/appointment/service/index.js` |
|
||||
| فراخوانی endpoint ها | `services/response.js:78,83` |
|
||||
| تبدیل پاسخ به قالب اسلات | `lib/appointmentSlots.js` → `adaptServiceSlots()` |
|
||||
| ارسال سرویسها در ثبت | `components/appointment/detail/SubmitData.js:152` |
|
||||
| JSON-LD `availableService` با `estimatedDuration` | `app/doctor/[slug]/page.js:212` |
|
||||
| باطلکردن انتخابها با تعویض محل | `changeLocation()` در `index.js` |
|
||||
|
||||
### ❌ مشکلات این تسک
|
||||
|
||||
**۱. انحراف از دیزاینسیستم — رنگهای hard-code.**
|
||||
|
||||
`components/appointment/service/index.js`:
|
||||
|
||||
```jsx
|
||||
<h2 className="text-[16px] font-bold text-[#3B3B3B] mb-4">۱. انتخاب سرویس</h2>
|
||||
<p className="text-[14px] text-[#7A7A7A]">…</p>
|
||||
className={active
|
||||
? "border-[#5559CE] bg-[#5559CE]/5"
|
||||
: "border-gray-200 bg-white hover:border-[#5559CE]"}
|
||||
```
|
||||
|
||||
چهار رنگ hard-code. سایت `darkMode: "class"` دارد و صفحات عمومی با `data-theme` تم
|
||||
عوض میکنند — این کامپوننت در دارکمود میشکند. بقیهٔ مراحل رزرو از تم MUI/Tailwind
|
||||
استفاده میکنند و این یکی نمیکند.
|
||||
|
||||
**۲. محاسبهٔ موازی مدت در فرانت.**
|
||||
|
||||
```js
|
||||
// components/appointment/service/index.js
|
||||
const totalMinutes = services
|
||||
.filter((s) => draft.includes(s.uuid))
|
||||
.reduce((sum, s) => sum + (Number(s.duration_minutes) || 0), 0);
|
||||
```
|
||||
|
||||
بکاند همان عدد را در `total_duration_minutes` پاسخ `appointment-service-slots`
|
||||
برمیگرداند. دو محاسبه یعنی: وقتی تسک ۰۴ فرمول را به «زمان تنها / زمان اضافه» عوض کند،
|
||||
سایت عدد قدیمی نشان میدهد و بیمار مدتی میبیند که با مدت واقعی نوبتش نمیخواند.
|
||||
|
||||
**۳. `adaptServiceSlots` برچسب گمراهکننده میسازد.**
|
||||
|
||||
```js
|
||||
return [{
|
||||
start_time: starts[0].start_time,
|
||||
end_time: starts[starts.length - 1].start_time, // ← پایانِ آخرین شروع، نه پایان نوبت
|
||||
label: "زمانهای خالی",
|
||||
slots: …,
|
||||
}];
|
||||
```
|
||||
|
||||
همهٔ زمانها در یک تب جمع میشوند و مرز شیفتها (صبح/عصر) از بین میرود — در حالی که
|
||||
حالت اسلاتی همان اطلاعات را از بکاند دارد و نشان میدهد. برای پزشکی با شیفت صبح و عصر،
|
||||
بیمار یک فهرست بلند بیساختار میبیند.
|
||||
|
||||
**۴. پنل کاربر سرویس و مدت نوبت را نشان نمیدهد.**
|
||||
|
||||
`components/dashboard/userAccount/sidebars/turns/Card.js` و `isTurnsDetails/*` هیچ ارجاعی
|
||||
به `service` یا مدت ندارند. بیمار نوبت سرویسی گرفته و در پنلش نمیبیند چه سرویسی رزرو
|
||||
کرده یا نوبتش چند دقیقه است.
|
||||
|
||||
**۵. جابهجایی نوبت در پنل کاربر، سرویسآگاه نیست.**
|
||||
|
||||
پس از تسک ۰۰، endpoint `POST /appointment/{uuid}/service-reschedule` وجود دارد.
|
||||
`ButtonData.js` هیچ مسیری برای جابهجایی ندارد.
|
||||
|
||||
## دامنه
|
||||
|
||||
**هست:**
|
||||
- بازنویسی `components/appointment/service/index.js` با توکنهای تم (بدون تغییر رفتار)
|
||||
- حذف محاسبهٔ مدت از فرانت — مصرف `total_duration_minutes` بکاند
|
||||
- `adaptServiceSlots` گروهبندی per شیفت
|
||||
- نمایش سرویسها و مدت در کارت و جزئیات نوبت پنل کاربر
|
||||
- جابهجایی سرویسآگاه از پنل کاربر
|
||||
- بهروزرسانی `services/response.js` برای endpoint های جدید تسک ۰۰
|
||||
|
||||
**نیست:** تغییری در مسیر اسلاتی · حالت `resource` (تسک ۰۶ و پس از آن، یک تسک frontend جدا)
|
||||
|
||||
## معیار پذیرش
|
||||
|
||||
- ✅ موفق: مرحلهٔ انتخاب سرویس در دارکمود درست رندر میشود (هیچ متن سیاه روی زمینهٔ
|
||||
تیره، هیچ کارت سفید).
|
||||
- ✅ موفق: مدت نمایشدادهشده در مرحلهٔ انتخاب سرویس **از پاسخ بکاند** میآید؛ اگر
|
||||
بکاند عدد متفاوتی بدهد، UI همان را نشان میدهد.
|
||||
- ✅ موفق: پزشکی با دو شیفت (صبح ۹-۱۳، عصر ۱۶-۲۰) در حالت سرویسی → دو تب زمانی،
|
||||
با برچسب واقعی هر شیفت.
|
||||
- ✅ موفق: کارت نوبت در پنل کاربر نام سرویسها و مدت را نشان میدهد؛ نوبت اسلاتی
|
||||
دقیقاً مثل امروز (بدون این دو خط).
|
||||
- ✅ موفق: بیمار از پنل نوبت سرویسیاش را جابهجا میکند → مدت خودکار حفظ میشود،
|
||||
بیمار عددی وارد نمیکند.
|
||||
- ❌ خطا: جابهجایی به زمان اشغالشده → پیام فارسی از بکاند نمایش داده میشود
|
||||
(نه «خطای نامشخص»)، و فهرست زمانها خودکار بهروز میشود.
|
||||
- ❌ خطا: انتخاب صفر سرویس → دکمهٔ ادامه غیرفعال با راهنمای فارسی.
|
||||
- ⚠️ مرزی: محلی که `booking_mode = 'service'` است ولی هیچ سرویس `bookable` ندارد →
|
||||
پیام روشن («سرویسی برای نوبتدهی آنلاین تعریف نشده است») + پیشنهاد محل دیگر اگر باشد.
|
||||
- ⚠️ مرزی: پزشک در مطب شخصی اسلاتی و در کلینیک سرویسی → تعویض محل، مرحلهٔ سرویس را
|
||||
ظاهر/پنهان میکند و انتخابهای قبلی باطل میشوند (رفتار موجود، حفظ شود).
|
||||
- ⚠️ مرزی: پاسخ `appointment-service-slots` خالی → پیام دلیلدار، نه فهرست خالی بیتوضیح.
|
||||
- ⚠️ مرزی: نوبت قدیمی سرویسی بدون `service_items` → کارت مدت را نشان میدهد و نام
|
||||
سرویس را «—»؛ کرش نمیکند.
|
||||
- ⚠️ مرزی: `total_duration_minutes` در پاسخ نبود (بکاند قدیمی) → fallback به محاسبهٔ
|
||||
فرانت با یک `console.warn`، نه صفحهٔ خالی.
|
||||
|
||||
## خروجی
|
||||
|
||||
- `components/appointment/service/index.js` بازنویسیشده با توکن تم
|
||||
- `lib/appointmentSlots.js` — `adaptServiceSlots` شیفتآگاه
|
||||
- `components/dashboard/userAccount/sidebars/turns/*` — سرویس و مدت
|
||||
- `services/response.js` — endpoint های جدید
|
||||
- [checklist.md](checklist.md) کاملشده
|
||||
Reference in New Issue
Block a user