5.9 KiB
name, description
| name | description |
|---|---|
| dyolink-treatment-workspace | 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)
-
Appointments strip —
AppointmentsStrip.tsx+ScheduleDayPicker.tsx(Today checkbox) +pickAutoAppointment()incomponents/shared/treatmentSelection.ts -
Treatment preview —
TreatmentPreviewCard.tsx(read-only summary; no load button for current draft) -
Treatment history —
PastTreatmentsPanel.tsx(past saved plans for patient; client-side filters intreatmentHistoryFilters.ts) -
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 day’s 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
treatmentAtmatching 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+LabShipmentBlockedNoticeabove dispatch when active detail qualifies (isLabDependentDetailMissingTeeth). handleAddLabCaseshows toast withlabShipmentBlockedBody.- 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)— requiressentAt,labCaseId, and!isLabCaseCompleted(taskProgress).- UI:
DetailLabCaseCommentsSection→ existingLabCaseCommentsPanel+treatmentsApicomment endpoints. - Backend includes
tasks: { select: { id, status } }on lab cases;mapDetailexposestaskProgress: { 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
canEditTreatmentForDay = canEdit && selectedAppointment && !isViewingPastDay && workspaceMode === 'live'
Optional fields
-
@IsOptional()email: use@Transformempty string →undefinedbefore@IsEmail(see patients DTO). -
Form validation → inline errors; transient feedback → global
useToast()viaToastProvider.
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.