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

5.0 KiB
Raw Blame History

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: sentAt desc (newest first).
  • 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.
  • Right panel: Always shows detail for selected case when list non-empty (loading state while fetching).
  • 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).

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-optionsclinics, 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.
  • 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 → off-screen CaseSheetPrintLayout.tsxhtml2canvasjspdf 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 (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.creatednotifyTabBadgesChanged() → 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.