Files
dyolink/.cursor/skills/lab-cases/SKILL.md

86 lines
6.0 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-lab-cases
description: Lab Cases tab — list, filters, detail panel, assignment, share QR, card UX. Use when changing CasesPage, cases API, CaseDetailPanel, or case list cards.
---
# Lab Cases tab
**UI:** [`frontend/src/components/ui/lab/CasesPage.tsx`](frontend/src/components/ui/lab/CasesPage.tsx)
**Backend:** [`backend/src/modules/cases/`](backend/src/modules/cases/)
**Shared prosthesis rows:** [`LabCaseProsthesisGroupsList.tsx`](frontend/src/components/ui/lab/LabCaseProsthesisGroupsList.tsx) (also used in Treatment lab shipments rail)
## List behavior
- **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.
- **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 |
|-------|----------|
| `prosthesisTypeCode` | Cases with **≥1 task** of that prosthesis type (`tasks.some`) — not treatment-type filter |
| `clinicOrganizationId` | From URL (`Today` case partners chart) or dropdown |
| `q`, `sentFrom`, `sentTo` | Search + date range |
Filter options: `GET /cases/filter-options``clinics`, `prosthesisTypes` (distinct codes from sent-case tasks, catalog-ordered).
## List card UI
Match Treatment shipment cards: patient name, clinic, **colored prosthesis groups + teeth** (`LabCaseProsthesisGroupsList`), sent date, progress bar, due-date badge, unread dot. **No patient mobile** on list cards.
List item shape: `prosthesisGroups: { prosthesisTypeCode, teeth[] }[]` from task teeth aggregation.
## 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. 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`.
- **Case Sheet PDF** — see below.
- **Share link (sent cases):** `shareUrl` on detail; attachment preview left + QR thumb right; `LabCaseShareQrDialog` (`react-qr-code`). Full flow: `.cursor/skills/lab-case-share-link/SKILL.md`.
## Case Sheet PDF
**Entry:** Download PDF beside Comments on `CaseDetailPanel`.
**Builder:** [`caseSheetPdf.ts`](frontend/src/components/lab/caseSheetPdf.ts) → off-screen [`CaseSheetPrintLayout.tsx`](frontend/src/components/ui/lab/CaseSheetPrintLayout.tsx) → `html2canvas``jspdf` A4 (ISO √2 — prints cleanly onto A3A6).
### html2canvas + Tailwind v4 (required)
Stock `html2canvas` **cannot** parse `lab()` / `oklch()` from app CSS. Do **not** mount live `FdiToothChart` / Tailwind color classes into the capture tree.
| Do | Don't |
|----|--------|
| Hex/rgb **inline styles** only in print layout | Tailwind color utilities / CSS variables in capture |
| [`CaseSheetFdiChart`](frontend/src/components/ui/lab/CaseSheetFdiChart.tsx) (SVG, catalog hex colors) | Reuse interactive `FdiToothChart` for PDF |
| `onclone`: strip `style` + `link[rel=stylesheet]` | Rely on global stylesheets during capture |
| Prosthesis labels from **catalog** (then task label) | Fallback `code.replace(/_/g,' ')` alone (breaks casing like `ti base…`) |
### Layout conventions
- Sections: meta → parties → **Prosthesis** rows → **Tooth Chart** heading *above* the horizontal rule → chart → Comments.
- Connected badge: bold + underline (no border box — html2canvas centering is unreliable).
- Order number: `externalCode.trim()` when set, else short case id (`id` without hyphens, first 8, uppercased).
- Share QR when `shareUrl` present.
### API
- `LabCase.externalCode` (optional string, max 64)
- `PATCH /cases/:id/external-code` `{ externalCode: string | null }` — same edit permission as important flag
## Live soft refresh
Inbox Socket.IO `notification.created``notifyTabBadgesChanged()` → silent `loadCases` + selected `loadDetail` (keeps filters/selection; no full remount). Same event refreshes sidebar Cases badge via `useTabBadgeCounts`. See `.cursor/skills/notifications-inbox/SKILL.md`.
## Permissions
`TAB_CASES_READ` / `TAB_CASES_EDIT`; owner always has Cases access. `LabOrgGuard` on routes.