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>
Starting a session created area records with no device, and the panel only ever
read the device it never set — so every "اتمام این ناحیه" came back 422 with
"دستگاه این ناحیه مشخص نیست". The backend tests passed because they sent
resource_uuid explicitly; from the UI the flow was unusable end to end.
The device now inherits from the appointment's resource, which the secretary
already chose at booking; asking the operator again is taking one decision
twice. The session screen offers a picker per area on top of that, because one
session really does run bikini on an alexandrite and underarms on a diode.
Treating without a device is allowed: botox is an injection, and requiring a
device would make clinics invent a fake resource per injection. Sending readings
with no device is still rejected — there would be no schema to validate against.
A protocol whose service has no ResourceServiceOffering rows now says so in the
tab where the manager is standing. It does not block booking: "no offering means
any resource" is a deliberate, tested rule. But silence meant the gap surfaced
only when the operator was already in front of a patient.
Also adds the live timer the spec asked for, and wires slot-suggestions into the
unbooked queue — the endpoint existed and tested green but no screen called it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Completes the panel side. A clinic picks its practice domain in settings, where
the copy says plainly that this is not the specialty label the public site shows;
picking nothing stays valid and changes nothing.
Cases and the unbooked queue share one page rather than two, because they answer
the same question — which patient is where in their course and what is still
owed. The queue explains why booking is not automatic instead of leaving the
reader to wonder.
Platform admins get domain CRUD with a column showing whether a domain has a
dedicated workflow or falls back to the default, so the gap is visible rather
than guessed at; the code field is locked after creation because workflows bind
to it.
Also puts the staff session screens in the sidebar — they were reachable only by
typing the URL.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>