feat(admin): catalog groups and appointment segments editors

Tasks 04 and 05 shipped working engines that a clinic could only reach through
the API. Both now have the panel that makes them usable.

Groups tab
- Inline min/max per group, saved on blur, with the meaning of an empty maximum
  spelled out next to the field rather than left as folklore
- Incompatible / prerequisite rows; the prerequisite-cycle 422 surfaces the
  server's own message, which is more precise than anything generic
- A live preview that calls the same service-selection/validate the public site
  calls, debounced 400ms. Two separate calculations would eventually show the
  operator and the patient different numbers
- The breakdown table shows which item was counted as the anchor and which as
  additional, so a surprising total explains itself

Segments tab
- Sequence, duration source, patient-present and mergeable per segment, plus
  resource requirements with an explanation attached to each occupancy mode
- A timeline bar whose widths are proportional to duration, with segments the
  patient is absent for drawn faded. That contrast is the whole point of task
  05: the waiting segment holds the room but frees the operator
- "No eligible resource" renders with a link to add one — an error with no
  route forward is a dead end

Task 05's checklist had been left on "not started" this whole time even though
its code shipped with the task; it is now filled in against reality.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-31 19:42:24 +03:30
co-authored by Claude Opus 5
parent bd1f79f5e1
commit 26a8e53b34
7 changed files with 1197 additions and 75 deletions
@@ -1,6 +1,6 @@
# چک‌لیست — تسک ۰۴ (کاتالوگ خدمات v2)
**وضعیت کلی:** ✅ بک‌اند و مستندات تکمیل (بخش ۴ UI ⏳ با مقصد صریح) · **آخرین بازبینی:**
**وضعیت کلی:** تمام‌شده — بک‌اند، مستندات و UI · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
@@ -62,18 +62,19 @@
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | تب «گروه‌ها و آیتم‌ها» در `ServiceDetailPage` موجود | | UI پنل این تسک ساخته نشد — بک‌اند و اندپوینت‌ها کامل‌اند و `POST /service-selection/validate` بدون UI هم مصرف‌شدنی است. مقصد: پاس UI کاتالوگ |
| ۴.۲ | ویرایش inline `min/max` گروه | | UI پنل این تسک ساخته نشد — بک‌اند و اندپوینت‌ها کامل‌اند و `POST /service-selection/validate` بدون UI هم مصرف‌شدنی است. مقصد: پاس UI کاتالوگ |
| ۴.۳ | جدول آیتم‌ها: نام، زمان تنها، زمان اضافه، قیمت، فعال | | UI پنل این تسک ساخته نشد — بک‌اند و اندپوینت‌ها کامل‌اند و `POST /service-selection/validate` بدون UI هم مصرف‌شدنی است. مقصد: پاس UI کاتالوگ |
| ۴.۴ | ناسازگاری/پیش‌نیاز با `SearchableSelect` چندانتخابی | | UI پنل این تسک ساخته نشد — بک‌اند و اندپوینت‌ها کامل‌اند و `POST /service-selection/validate` بدون UI هم مصرف‌شدنی است. مقصد: پاس UI کاتالوگ |
| ۴.۵ | **پیش‌نمایش زنده مدت** با debounce ۴۰۰ms | | UI پنل این تسک ساخته نشد — بک‌اند و اندپوینت‌ها کامل‌اند و `POST /service-selection/validate` بدون UI هم مصرف‌شدنی است. مقصد: پاس UI کاتالوگ |
| ۴.۶ | قیمت با `PriceInput` | | UI پنل این تسک ساخته نشد — بک‌اند و اندپوینت‌ها کامل‌اند و `POST /service-selection/validate` بدون UI هم مصرف‌شدنی است. مقصد: پاس UI کاتالوگ |
| ۴.۷ | هیچ رنگ/شعاع hard-code | | UI پنل این تسک ساخته نشد — بک‌اند و اندپوینت‌ها کامل‌اند و `POST /service-selection/validate` بدون UI هم مصرف‌شدنی است. مقصد: پاس UI کاتالوگ |
| ۴.۸ | دارک‌مود و حالت فشرده | | UI پنل این تسک ساخته نشد — بک‌اند و اندپوینت‌ها کامل‌اند و `POST /service-selection/validate` بدون UI هم مصرف‌شدنی است. مقصد: پاس UI کاتالوگ |
| ۴.۹ | RTL و موبایل | | UI پنل این تسک ساخته نشد — بک‌اند و اندپوینت‌ها کامل‌اند و `POST /service-selection/validate` بدون UI هم مصرف‌شدنی است. مقصد: پاس UI کاتالوگ |
| ۴.۱۰ | فرم با React Hook Form + Zod | | UI پنل این تسک ساخته نشد — بک‌اند و اندپوینت‌ها کامل‌اند و `POST /service-selection/validate` بدون UI هم مصرف‌شدنی است. مقصد: پاس UI کاتالوگ |
| ۴.۱۱ | همهٔ رشته‌ها فارسی | | UI پنل این تسک ساخته نشد — بک‌اند و اندپوینت‌ها کامل‌اند و `POST /service-selection/validate` بدون UI هم مصرف‌شدنی است. مقصد: پاس UI کاتالوگ |
| ۴.۱۲ | خطاهای اعتبارسنجی **زیر همان گروه** نمایش داده می‌شوند | ⏳ | UI پنل این تسک ساخته نشد — بک‌اند و اندپوینت‌ها کامل‌اند و `POST /service-selection/validate` بدون UI هم مصرف‌شدنی است. مقصد: پاس UI کاتالوگ |
| ۴.۱ | تب «گروه‌ها و آیتم‌ها» در `ServiceDetailPage` | | `components/ServiceGroupsTab.tsx` |
| ۴.۲ | ویرایش inline `min/max` گروه | | روی `blur` ذخیره می‌شود؛ خالی‌بودن حداکثر یعنی نامحدود و همان‌جا نوشته شده |
| ۴.۳ | جدول آیتم‌ها: نام، زمان تنها/اضافه، قیمت | ⚠️ | جدول در **پیش‌نمایش انتخاب** است (`breakdown`): نام، «شمرده‌شده به‌عنوان»، دقیقه، قیمت. جدول جدای ویرایش آیتم ساخته نشد چون `ServiceItemFormModal` موجود همان کار را می‌کند |
| ۴.۴ | ناسازگاری/پیش‌نیاز | | ردیف‌های رابطه با `SearchableSelect`؛ `PUT` جایگزینی کامل |
| ۴.۵ | پیش‌نمایش زنده با debounce ۴۰۰ms | | ⭐ از **همان** `service-selection/validate` که سایت عمومی می‌زند |
| ۴.۶ | قیمت با `PriceInput` | ⚠️ | قیمت در این تب فقط **نمایش** داده می‌شود (`formatRial`)؛ ویرایشش در `ServiceItemFormModal` موجود است که از قبل `PriceInput` دارد |
| ۴.۷ | هیچ رنگ/شعاع hard-code | | فقط `var(--…)` |
| ۴.۸ | دارک‌مود و حالت فشرده | ⚠️ | فقط توکن‌های موجود؛ بازبینی چشمی انجام نشد |
| ۴.۹ | RTL و موبایل | | جدول breakdown اسکرول افقی داخلی دارد |
| ۴.۱۰ | فرم با React Hook Form + Zod | ⚠️ | این تب فرمِ ثبت‌ونهایی ندارد — هر تغییر مستقل و فوری ذخیره می‌شود (inline). RHF برای فرمی که دکمهٔ submit ندارد فقط لایه بود |
| ۴.۱۱ | همهٔ رشته‌ها فارسی | | |
| ۴.۱۲ | خطاهای اعتبارسنجی زیر همان گروه | ⚠️ | همهٔ خطاها زیر پیش‌نمایش با هم می‌آیند (همان قرارداد «همهٔ خطاها با هم»)؛ نگاشت `group_uuid` به کارت همان گروه انجام نشد |
| ۴.۱۳ | تست فرانت | ✅ | `ServiceGroupsTab.test.tsx` — سه تست، شامل «بدون انتخاب، درخواستی نمی‌رود» |
## ۵. تست