- 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.
9.9 KiB
معماری — تسک ۰۰ب
پروژه: 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 دارد و در دارکمود میشکند:
// وضعیت فعلی
<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]"}
// هدف — همان ساختار 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) دارد — نه ساختن توکن جدید در این تسک.
۲. حذف محاسبهٔ موازی مدت
// ❌ وضعیت فعلی — منبع دوم حقیقت
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 همچنان میآید ✅ |
| نگهداشتن محاسبهٔ فرانت بهعنوان تخمین + اصلاح در مرحلهٔ بعد | بیمار دو عدد متفاوت میبیند ❌ |
انتخاب: گزینهٔ اول، با یک تفاوت مهم — مدت تخمینی برچسب میگیرد تا وقتی روز انتخاب نشده:
// مرحلهٔ انتخاب سرویس
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
اشتباه است (پایانِ آخرین شروع، نه پایان نوبت).
// هدف — گروهبندی بر اساس شکاف زمانی، با برچسب واقعی
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در تسک ۰۰ اضافه شد ✅ - افزودنش به سریالایزر پاسخ، بخشی از تسک ۰۰ است (ردیف ۱.۱۶ چکلیستش)
- اگر جا افتاده، اینجا بهعنوان یک ردیف ⚠️ ثبت و به تسک ۰۰ برگردان
// 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 سه متد جدید میگیرد:
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، بخش 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سالم بماند