Files
dyolink/.cursor/skills/treatment-workspace/SKILL.md

191 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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** (not the whole days schedule). Non-owners see plans where `Treatment.providerUserId` or linked `Appointment.providerUserId` matches the logged-in user; org owners see all saved plans for the patient. New saves set `Treatment.providerUserId` to the logged-in clinician.
## 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 and workspace is live (not browsing), prepend a synthetic **current draft** row (`id: 'current-draft'`) if it has pending lab-dependent details.
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` on patient + org; provider scoping for non-owners. History is **per selected patient**, not per day or all appointments on the strip.
**UI filters** (not shipped, date) are client-side only — do not add API params unless product explicitly requires server-side filtering.