6.0 KiB
name, description
| name | description |
|---|---|
| dyolink-lab-cases | 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
Backend: backend/src/modules/cases/
Shared prosthesis rows: LabCaseProsthesisGroupsList.tsx (also used in Treatment lab shipments rail)
List behavior
- Default sort:
startedAt/sentAtdesc (newest first). Lab-origin drafts (origin === LAB_INTERNAL && !startedAt) show a Draft badge. - Page size:
PAGE_SIZE = 10inCasesPage.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 /casesthen replace the right pane withCaseCreatePanel(inline wizard, not a modal). Prosthesis-only lines, interactiveFdiToothChart(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; noLabCaseSend, 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-fullwithlg:items-stretchon the grid; case list wrapper isflex-1 min-h-0 overflow-y-auto(do not use a fixedmax-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+tasksApicomment routes (viewerSide="LAB"). Newest-first; sent/received use logical start/end alignment (RTL-safe). Compacth-9composer with primary send + visibility controls. Lab-origin cases (LAB_INTERNAL, including after Start) are visible to lab comments/mark-read — do not requiresentAt/LabCaseSend. - Mark read:
POST /notifications/mark-case-readon select (Cases tab badge) - FDI chart (
CaseToothChartPanel): prosthesis colors + connected bridge dots fromselectionGroupId(buildCaseConnectedTeeth/buildCaseProsthesisRowsincaseDetailUtils.ts). - Important + external code (
TAB_CASES_EDIT): row is Important Case label then checkbox (CheckboxlabelPosition="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):
shareUrlon 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 → off-screen CaseSheetPrintLayout.tsx → html2canvas → jspdf A4 (ISO √2 — prints cleanly onto A3–A6).
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 (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 (idwithout hyphens, first 8, uppercased). - Share QR when
shareUrlpresent.
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.