feat(treatment): search and edit for treatment cases

The list had no way to tell two cases apart. TreatmentCase::toArray() carried
no patient, so four cases of the same service rendered as four identical
cards — same service, same supervisor, same date, same areas. Search would have
been meaningless without fixing that first, so the payload now carries the
patient (name, mobile, record number) and the card leads with the name.

Search: `?q=` on the list endpoint, matching patient name, mobile, national
code, record number and service name — the same keys a secretary already types
into the booking form. It lives in the URL via useUrlState, debounced, so back
and refresh keep the view.

Edit: PATCH /api/v1/treatment-case/{uuid} covering status, supervising doctor,
areas and session count, driven from a modal on the list. Rules live in
TreatmentCaseEditor, not the controller, around one boundary: no edit may
overwrite work already done. An area with session records cannot be removed, and
the session count cannot drop below the sessions that are booked or finished —
both 409, both tested. Reopening a closed case clears closed_at.

`areas[]` now also exposes `category_uuid`; the edit form selects catalog
categories, while `uuid` identifies the snapshot row.

Page fixes from the redesign checklist: the status filter was a hand-rolled
primary/secondary button pair, now `.seg` with `.on`; the raw `<progress>` bar
took the browser's own appearance and ignored the theme tokens, now a token-
styled bar with an explicit progressbar role; session counts go through
formatNumber; a failed request rendered as "no cases found", which reads as an
empty clinic rather than a broken one, and an empty search now says so in its
own words.

Adds the test files neither the page nor the case editor had.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-08-07 13:48:57 +03:30
co-authored by Claude Opus 5
parent 00349cdb44
commit 952e09bd6a
11 changed files with 1120 additions and 58 deletions
+37 -2
View File
@@ -209,6 +209,7 @@ single-session again. Idempotent: deleting a service that has no protocol still
| Query | توضیح |
|---|---|
| `status` | `active` \| `completed` \| `abandoned` — نبودش یعنی همه |
| `q` | جستجو روی نام بیمار، موبایل، کد ملی، شمارهٔ پرونده و نام سرویس |
```json
{
@@ -220,13 +221,47 @@ single-session again. Idempotent: deleting a service that has no protocol still
"closed_at": null,
"service": { "uuid": "…", "name": "لیزر توتال" },
"supervisor": { "uuid": "…", "name": "دکتر ناظر" },
"areas": [ { "uuid": "…", "name": "بیکینی" } ]
"patient": {
"record_uuid": "…",
"name": "محمد رسولی",
"mobile": "09120001111",
"record_number": "۱۲"
},
"areas": [ { "uuid": "…", "name": "بیکینی", "category_uuid": "…" } ]
}
```
`areas[].uuid` شناسهٔ همان ردیفِ ناحیه است و `category_uuid` شناسهٔ دستهٔ کاتالوگ.
ویرایش با دومی کار می‌کند؛ `null` یعنی دسته حذف شده و ناحیه فقط در سابقه مانده.
## GET `/api/v1/treatment-case/{uuid}`
همان شکل، به‌علاوهٔ `sessions`. پروندهٔ محیط دیگر `404` می‌گیرد.
همان شکل، به‌علاوهٔ `sessions` و `available_areas` — نواحیِ قابل انتخاب برای همین
سرویس، تا فرم ویرایش اندپوینت دومی نخواهد. پروندهٔ محیط دیگر `404` می‌گیرد.
## PATCH `/api/v1/treatment-case/{uuid}`
ویرایش پروندهٔ درمان. هر فیلد اختیاری است؛ فقط کلیدهای فرستاده‌شده اعمال می‌شوند.
| فیلد | توضیح |
|---|---|
| `status` | `active` \| `completed` \| `abandoned`. برگرداندن به `active` پروندهٔ بسته را باز می‌کند و `closed_at` را پاک می‌کند |
| `supervisor_doctor_uuid` | پزشک ناظر؛ `null` یعنی بدون ناظر |
| `area_uuids` | فهرست **دستهٔ کاتالوگ**، جایگزین کامل. حداقل یکی |
| `total_sessions` | بین `TreatmentProtocol::MIN_STEPS` و `MAX_STEPS`. کم‌کردن جلسات را از انتها حذف می‌کند |
مرزِ ثابت: **هیچ ویرایشی سابقهٔ انجام‌شده را بازنویسی نمی‌کند.**
| کد | HTTP | فیلد | شرط |
|---|---|---|---|
| ERR_VALIDATION_001 | 422 | `status` | وضعیت نامعتبر |
| ERR_VALIDATION_001 | 422 | `area_uuids` | فهرست خالی یا نامعتبر |
| ERR_VALIDATION_001 | 422 | `total_sessions` | خارج از بازهٔ مجاز |
| ERR_NOT_FOUND_001 | 404 | `supervisor_doctor_uuid` / `area_uuids` | پزشک یا ناحیه یافت نشد |
| ERR_CONFLICT_001 | 409 | `area_uuids` | ناحیه در جلسه‌ای ثبت شده و حذف نمی‌شود |
| ERR_CONFLICT_001 | 409 | `total_sessions` | کمتر از جلساتی که نوبت دارند یا انجام شده‌اند |
قواعدش در `TreatmentCaseEditor` است نه کنترلر.
---