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

5.9 KiB
Raw Blame History

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)

  1. Appointments stripAppointmentsStrip.tsx + ScheduleDayPicker.tsx (Today checkbox) + pickAutoAppointment() in components/shared/treatmentSelection.ts

  2. Treatment previewTreatmentPreviewCard.tsx (read-only summary; no load button for current draft)

  3. Treatment historyPastTreatmentsPanel.tsx (past saved plans for patient; client-side filters in treatmentHistoryFilters.ts)

  4. EditorTreatmentDetailsEditor.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


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.