Files
clinicpro/docs/new_feture/taskes/task-00b-nobat724-service-mode/architecture.md
T
hamed 158dcb58aa 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.
2026-07-30 11:56:08 +03:30

9.9 KiB
Raw Blame History

معماری — تسک ۰۰ب

پروژه: 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 سالم بماند