improvement: v1 standalone treatment/case creation made possible.
This commit is contained in:
@@ -11,12 +11,17 @@ description: Lab Cases tab — list, filters, detail panel, assignment, share QR
|
||||
|
||||
## List behavior
|
||||
|
||||
- **Default sort:** `sentAt` desc (newest first).
|
||||
- **Default sort:** `startedAt` / `sentAt` desc (newest first). Lab-origin drafts (`origin === LAB_INTERNAL && !startedAt`) show a **Draft** badge.
|
||||
- **Page size:** `PAGE_SIZE = 10` in `CasesPage.tsx`.
|
||||
- **Auto-select:** On tab open / after filter reload, select first list item if none selected; keep selection when still in list; `?caseId=` URL wins.
|
||||
- **Right panel:** Always shows detail for selected case when list non-empty (loading state while fetching).
|
||||
- **Add case:** `TAB_CASES_EDIT` — `POST /cases` then replace the **right pane** with `CaseCreatePanel` (inline wizard, not a modal). Prosthesis-only lines, interactive `FdiToothChart` (same connect/Shift rules as Treatment). Header: referring clinic/dentist, patient name/mobile, optional ACTIVE partner clinic, due date (`AppDateInput`). **Start** (`POST /cases/:id/start`) generates tasks; no `LabCaseSend`, no clinic inbox/`CASE_SENT`.
|
||||
- **Right panel:** Draft lab-origin → wizard; started/received → `CaseDetailPanel`.
|
||||
- **Left rail layout:** section is `flex flex-col` + `lg:min-h-[420px]` / `h-full` with `lg:items-stretch` on the grid; case list wrapper is `flex-1 min-h-0 overflow-y-auto` (do **not** use a fixed `max-h-[55vh]` — that leaves empty space above pagination).
|
||||
|
||||
List `where`: received clinic cases (`sends` + `sentAt`) **OR** `origin = LAB_INTERNAL` and `destinationOrganizationId = lab`. Map clinic/patient from `treatment` **or** snapshots / partner org. Search includes snapshot names.
|
||||
|
||||
Share QR remains clinic-sent only (`sentAt` / `shareUrl`). Lab-origin has no share link in this pass.
|
||||
|
||||
## Filters (`GET /cases`)
|
||||
|
||||
| Param | Behavior |
|
||||
@@ -36,7 +41,7 @@ List item shape: `prosthesisGroups: { prosthesisTypeCode, teeth[] }[]` from task
|
||||
## Detail panel
|
||||
|
||||
- Task assignment: `PATCH /cases/:caseId/tasks/:taskId/assign` (`TAB_CASES_EDIT`)
|
||||
- Comments: shared `LabCaseCommentsPanel` + `tasksApi` comment routes (`viewerSide="LAB"`). Newest-first; sent/received use logical start/end alignment (RTL-safe). Compact `h-9` composer with primary send + visibility controls.
|
||||
- Comments: shared `LabCaseCommentsPanel` + `tasksApi` comment routes (`viewerSide="LAB"`). Newest-first; sent/received use logical start/end alignment (RTL-safe). Compact `h-9` composer with primary send + visibility controls. Lab-origin cases (`LAB_INTERNAL`, including after Start) are visible to lab comments/mark-read — do not require `sentAt` / `LabCaseSend`.
|
||||
- Mark read: `POST /notifications/mark-case-read` on select (Cases tab badge)
|
||||
- FDI chart (`CaseToothChartPanel`): prosthesis colors + **connected bridge dots** from `selectionGroupId` (`buildCaseConnectedTeeth` / `buildCaseProsthesisRows` in `caseDetailUtils.ts`).
|
||||
- **Important + external code** (`TAB_CASES_EDIT`): row is **Important Case** label then checkbox (`Checkbox` `labelPosition="start"`), then optional external-code input (no title; placeholder only). Save code on blur → `PATCH /cases/:id/external-code`.
|
||||
|
||||
@@ -22,7 +22,7 @@ Thin route: `app/[locale]/(dashboard)/treatment/page.tsx` (supports `?appointmen
|
||||
|
||||
|
||||
|
||||
1. **Appointments strip** — `AppointmentsStrip.tsx` + `ScheduleDayPicker.tsx` (Today checkbox) + `pickAutoAppointment()` in `components/shared/treatmentSelection.ts`
|
||||
1. **Day strip** — `AppointmentsStrip.tsx` renders `DayStripItem[]` (`appointment` | `unscheduled`) via `DayStripCard`. Timed appointments show the slot; standalone treatments show i18n “No appointment” and sort after timed cards. Empty unscheduled cards (`details.length === 0`) show a trash control (`DELETE /treatments/:id`). Workspace fetches `GET /appointments` **and** `GET /treatments/day`. Strip stays dumb (no draft API). New treatment / Walk-in use shared `Button`.
|
||||
|
||||
2. **Treatment preview** — `TreatmentPreviewCard.tsx` (read-only summary; no load button for current draft)
|
||||
|
||||
@@ -180,27 +180,37 @@ Use shared `Checkbox` (not native `<input type="checkbox">`) to avoid focus-driv
|
||||
|
||||
|----------|---------|
|
||||
|
||||
| `GET /appointments?from&to` | Strip |
|
||||
| `GET /appointments?from&to` | Timed strip cards |
|
||||
|
||||
| `GET /treatments/day?from&to` | Standalone (unscheduled) strip cards |
|
||||
|
||||
| `POST /treatments` | Create standalone `{ patientId?, walkIn?, treatmentAt }` |
|
||||
|
||||
| `DELETE /treatments/:id` | Empty standalone only (`appointmentId` null, no details) |
|
||||
|
||||
| `GET /treatments/patients/:patientId/history` | History (patient + org; filtered by provider) |
|
||||
|
||||
| `GET /treatments/appointments/:id/draft` | Load form on appointment select |
|
||||
|
||||
| `PUT .../draft`, `PUT .../lab-cases` | Autosave (600ms debounce) |
|
||||
| `GET/PUT /treatments/:treatmentId/draft` | Load/save when there is no appointment |
|
||||
|
||||
| `PUT .../lab-cases` | Autosave (600ms debounce) — appointment or treatment id |
|
||||
|
||||
|
||||
|
||||
Draft writes require provider match (`ensureAppointmentProvider`) unless org owner.
|
||||
Walk-in uses one sentinel `Patient` per clinic (`isWalkIn`, hidden from Patients/search/booking). Display via i18n, never the stored name. Patient search: same workspace patient → no-op; else load latest history into the editor; **no history → auto-create** standalone on the selected day.
|
||||
|
||||
Draft writes for appointments require provider match (`ensureAppointmentProvider`). Standalone requires `treatment.providerUserId === actor`.
|
||||
|
||||
|
||||
|
||||
## Edit gating
|
||||
|
||||
```typescript
|
||||
canEditTreatmentForDay = canEdit && selectedAppointment && !isViewingPastDay && workspaceMode === 'live'
|
||||
canEditTreatmentForDay = canEdit && hasLiveContext && !isViewingPastDay && workspaceMode === 'live'
|
||||
```
|
||||
|
||||
Past day or `historical` workspace mode freezes the treatment form + most lab-dispatch fields.
|
||||
`hasLiveContext` is a selected live appointment **or** standalone treatment. Past day or `historical` workspace mode freezes the treatment form + most lab-dispatch fields.
|
||||
|
||||
### Per-detail sent lock
|
||||
|
||||
|
||||
Reference in New Issue
Block a user