192 lines
5.9 KiB
Markdown
192 lines
5.9 KiB
Markdown
---
|
|
|
|
name: dyolink-treatment-workspace
|
|
|
|
description: Treatment tab workspace — appointments strip, preview vs form, history, load flow, draft autosave, lab dispatch. Use when changing treatment UX, preview/history, or lab dispatch in TreatmentWorkspace.
|
|
|
|
---
|
|
|
|
|
|
|
|
# Treatment workspace
|
|
|
|
|
|
|
|
Main orchestrator: `frontend/src/components/ui/treatment/TreatmentWorkspace.tsx`
|
|
|
|
Thin route: `app/[locale]/(dashboard)/treatment/page.tsx` (supports `?appointmentId=`).
|
|
|
|
|
|
|
|
## Layout (top → bottom)
|
|
|
|
|
|
|
|
1. **Appointments strip** — `AppointmentsStrip.tsx` + `ScheduleDayPicker.tsx` (Today checkbox) + `pickAutoAppointment()` in `components/shared/treatmentSelection.ts`
|
|
|
|
2. **Treatment preview** — `TreatmentPreviewCard.tsx` (read-only summary; no load button for current draft)
|
|
|
|
3. **Treatment history** — `PastTreatmentsPanel.tsx` (past saved plans for patient; **client-side** filters in `treatmentHistoryFilters.ts`)
|
|
|
|
4. **Editor** — `TreatmentDetailsEditor.tsx`, `FdiToothChart.tsx`, `LabCasesDispatchPanel.tsx`
|
|
|
|
|
|
|
|
## Two layers of state (critical)
|
|
|
|
|
|
|
|
| Layer | State | Updated when |
|
|
|
|
|-------|--------|--------------|
|
|
|
|
| **Preview** | `previewTreatment`; `selectedPreviewId !== null` = **browse mode** | History click updates preview only |
|
|
|
|
| **Form** | `details[]`, `labCaseDrafts[]` | Appointment change → draft API; **Load into workspace** → hydrate |
|
|
|
|
|
|
|
|
**Browse mode:** banner + “Load into workspace” / “Back to current draft”. No Open button on preview card.
|
|
|
|
|
|
|
|
**Lab dispatch attention:** `LabDispatchAttentionPanel` lists lab-dependent unsent details (current draft + user history). “Go to dispatch” / “Load & dispatch”.
|
|
|
|
|
|
|
|
## History API
|
|
|
|
|
|
|
|
`GET /treatments/patients/:id/history` returns saved treatments for **that patient** scoped to the **logged-in clinician** (`Treatment.providerUserId` or linked `Appointment.providerUserId`). **Owners are not exempt** — each user only sees plans they created or own via their appointments.
|
|
|
|
|
|
|
|
## History filters (client-side only)
|
|
|
|
|
|
|
|
`PastTreatmentsPanel` filters **already-fetched** history — no extra API params.
|
|
|
|
- **Not shipped to lab** — show treatments that have at least one lab-dependent detail (prosthesis via `labDependentCodes`) with `!sentAt`.
|
|
- **Date** — filter on `treatmentAt` matching that local calendar day.
|
|
- When **not shipped** is on, filters **already-fetched** history only (excludes the active appointment row).
|
|
- **Previous treatments rail** excludes the active appointment; live draft stays in **Current draft** preview only. Refresh history after lab send.
|
|
|
|
Helpers: `frontend/src/components/treatment/treatmentHistoryFilters.ts`.
|
|
|
|
|
|
|
|
## Lab shipment without teeth
|
|
|
|
|
|
|
|
Saved lab-dependent detail with **no teeth** can autosave but **cannot** create a lab shipment.
|
|
|
|
- Inline banner in `TreatmentDetailsEditor` + `LabShipmentBlockedNotice` above dispatch when active detail qualifies (`isLabDependentDetailMissingTeeth`).
|
|
- `handleAddLabCase` shows toast with `labShipmentBlockedBody`.
|
|
- Dispatch panel only appears when a detail passes `isDetailReadyForLabDispatch` (persisted + lab-dependent + teeth).
|
|
|
|
|
|
|
|
## Lab case comments on details
|
|
|
|
|
|
|
|
Below each detail in the editor when the linked lab case is **in progress** (not all tasks completed):
|
|
|
|
- `canCommentOnDetailLabCase(detail)` — requires `sentAt`, `labCaseId`, and `!isLabCaseCompleted(taskProgress)`.
|
|
- UI: `DetailLabCaseCommentsSection` → existing `LabCaseCommentsPanel` + `treatmentsApi` comment endpoints.
|
|
- Backend includes `tasks: { select: { id, status } }` on lab cases; `mapDetail` exposes `taskProgress: { completed, total }`.
|
|
|
|
|
|
|
|
## Appointments default selection
|
|
|
|
|
|
|
|
On today: in-progress slot first, else nearest start time to `now`. Other days: first appointment. Re-runs every 60s on today unless `selectionLocked`. Frontend filters appointments to `providerUserId === userId`.
|
|
|
|
|
|
|
|
**Today checkbox** (`ScheduleDayPicker`): unchecked when `selectedDay` is not today (e.g. after loading a historical treatment). Checking Today calls `onSelectDay(today)` which unlocks selection (`selectionLocked = false`) and resets browse/historical context; appointments reload and auto-select nearest to now.
|
|
|
|
|
|
|
|
## Lab org search (dispatch)
|
|
|
|
|
|
|
|
`LinkedOrganizationSearchCombobox` in `LabCasesDispatchPanel` — search-only results (no dropdown). No match + org tab access → **Invite a lab** navigates to `/organizations?action=invite-lab`. No org access → show permission message; dispatch stops.
|
|
|
|
|
|
|
|
Pattern mirrors `PatientSearchCombobox` in appointments.
|
|
|
|
|
|
|
|
## Scroll
|
|
|
|
|
|
|
|
Use `scrollWithinMainScrollContainer()` (not raw `scrollIntoView`) when jumping to lab dispatch panel — dashboard `<main>` is the scroll container; document scroll conflicts with `.app-web-bg { overflow: hidden }`.
|
|
|
|
|
|
|
|
Use shared `Checkbox` (not native `<input type="checkbox">`) to avoid focus-driven scroll jumps.
|
|
|
|
|
|
|
|
## Backend APIs
|
|
|
|
|
|
|
|
| Endpoint | Purpose |
|
|
|
|
|----------|---------|
|
|
|
|
| `GET /appointments?from&to` | Strip |
|
|
|
|
| `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) |
|
|
|
|
|
|
|
|
Draft writes require provider match (`ensureAppointmentProvider`) unless org owner.
|
|
|
|
|
|
|
|
## Edit gating
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
canEditTreatmentForDay = canEdit && selectedAppointment && !isViewingPastDay && workspaceMode === 'live'
|
|
|
|
```
|
|
|
|
|
|
|
|
## Optional fields
|
|
|
|
|
|
|
|
- `@IsOptional()` email: use `@Transform` empty string → `undefined` before `@IsEmail` (see patients DTO).
|
|
|
|
- Form validation → inline errors; transient feedback → global `useToast()` via `ToastProvider`.
|
|
|
|
|
|
|
|
## When changing history scope
|
|
|
|
|
|
|
|
Filter in **backend** `listPatientHistory` / lab-case lists on patient + org + **provider scope** (`common/treatment-provider-scope.ts`). History is **per selected patient and per clinician**, not per day or all org plans.
|
|
|
|
**UI filters** (not shipped, date) are client-side only — do not add API params unless product explicitly requires server-side filtering.
|
|
|