From 94b97aa0fa25c57e61ad1c301fbb77a5071003f0 Mon Sep 17 00:00:00 2001 From: Admin Date: Sun, 28 Jun 2026 18:48:06 +0330 Subject: [PATCH] improvement: lab case dispatch UI improved. --- frontend/messages/en.json | 3 + frontend/messages/fa.json | 3 + frontend/messages/nl.json | 3 + .../ui/treatment/LabCasesDispatchPanel.tsx | 359 ++++++++++++------ 4 files changed, 247 insertions(+), 121 deletions(-) diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 19821a4..5836698 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -467,6 +467,9 @@ "addLabShipment": "Add lab shipment", "labShipmentLabel": "Shipment {n}", "includeDetails": "Include treatment details", + "labShipmentIncludedDetails": "Included in this shipment", + "labShipmentNoIncludedDetails": "No details were included in this shipment.", + "labShipmentNoDetailsAvailable": "All lab details are already in other shipments or have been sent.", "labDetailLine": "Detail {n} · {type} · {teeth}", "noLabDetails": "No lab-dependent treatment details yet. Add a lab type (e.g. endo) in treatment details above.", "labDispatchEmpty": "Add a lab shipment to group details and send them to a lab.", diff --git a/frontend/messages/fa.json b/frontend/messages/fa.json index 6302772..c5560b2 100644 --- a/frontend/messages/fa.json +++ b/frontend/messages/fa.json @@ -467,6 +467,9 @@ "addLabShipment": "افزودن محموله لاب", "labShipmentLabel": "محموله {n}", "includeDetails": "شامل جزئیات درمان", + "labShipmentIncludedDetails": "شامل این محموله", + "labShipmentNoIncludedDetails": "جزئیاتی در این محموله گنجانده نشده است.", + "labShipmentNoDetailsAvailable": "همه جزئیات لاب در محموله‌های دیگر هستند یا ارسال شده‌اند.", "labDetailLine": "جزئیات {n} · {type} · {teeth}", "noLabDetails": "هنوز جزئیات وابسته به لاب وجود ندارد. نوع لاب (مثلاً اندو) در جزئیات درمان بالا اضافه کنید.", "labDispatchEmpty": "یک محموله لاب اضافه کنید تا جزئیات را گروه‌بندی و ارسال کنید.", diff --git a/frontend/messages/nl.json b/frontend/messages/nl.json index 917473f..56a2e0f 100644 --- a/frontend/messages/nl.json +++ b/frontend/messages/nl.json @@ -467,6 +467,9 @@ "addLabShipment": "Labzending toevoegen", "labShipmentLabel": "Zending {n}", "includeDetails": "Behandeldetails opnemen", + "labShipmentIncludedDetails": "Opgenomen in deze zending", + "labShipmentNoIncludedDetails": "Geen details opgenomen in deze zending.", + "labShipmentNoDetailsAvailable": "Alle labdetails zitten al in andere zendingen of zijn verzonden.", "labDetailLine": "Detail {n} · {type} · {teeth}", "noLabDetails": "Nog geen lab-afhankelijke details. Voeg een labtype (bijv. endo) toe in de behandeldetails hierboven.", "labDispatchEmpty": "Voeg een labzending toe om details te groeperen en naar een lab te sturen.", diff --git a/frontend/src/components/ui/treatment/LabCasesDispatchPanel.tsx b/frontend/src/components/ui/treatment/LabCasesDispatchPanel.tsx index f0aec59..a2ab79c 100644 --- a/frontend/src/components/ui/treatment/LabCasesDispatchPanel.tsx +++ b/frontend/src/components/ui/treatment/LabCasesDispatchPanel.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useMemo } from 'react'; import { useTranslations } from 'next-intl'; import { Button } from '@/components/ui/shared/Button'; import { Checkbox } from '@/components/ui/shared/Checkbox'; @@ -29,6 +30,65 @@ interface LabCasesDispatchPanelProps { onSendLabCase: (labCase: LabCaseDraft) => void; } +function sentDetailClientIds(labCases: LabCaseDraft[]): Set { + const ids = new Set(); + for (const lc of labCases) { + if (!lc.sentAt) continue; + for (const id of lc.detailClientIds) ids.add(id); + } + return ids; +} + +function detailInOtherDraftShipment( + detailClientId: string, + labCases: LabCaseDraft[], + activeLabCaseClientId: string, +): boolean { + return labCases.some( + (lc) => + !lc.sentAt && + lc.clientId !== activeLabCaseClientId && + lc.detailClientIds.includes(detailClientId), + ); +} + +/** Lab-dependent details not yet sent to any lab. */ +function unsentLabDetails( + details: TreatmentDetailDraft[], + labCases: LabCaseDraft[], + labDependentCodes: Set, +): TreatmentDetailDraft[] { + const sent = sentDetailClientIds(labCases); + return details.filter((d) => labDependentCodes.has(d.treatmentType) && !sent.has(d.clientId)); +} + +/** Unsent lab details not already assigned to another draft shipment. */ +function detailsAvailableForNewShipment( + details: TreatmentDetailDraft[], + labCases: LabCaseDraft[], + labDependentCodes: Set, +): TreatmentDetailDraft[] { + return unsentLabDetails(details, labCases, labDependentCodes).filter( + (d) => !detailInOtherDraftShipment(d.clientId, labCases, ''), + ); +} + +/** Details the user can pick for the active draft shipment. */ +function selectableDetailsForDraftShipment( + details: TreatmentDetailDraft[], + labCases: LabCaseDraft[], + labDependentCodes: Set, + activeLabCase: LabCaseDraft, +): TreatmentDetailDraft[] { + const sent = sentDetailClientIds(labCases); + return details.filter((d) => { + if (!labDependentCodes.has(d.treatmentType)) return false; + if (sent.has(d.clientId)) return false; + if (activeLabCase.detailClientIds.includes(d.clientId)) return true; + return !detailInOtherDraftShipment(d.clientId, labCases, activeLabCase.clientId); + }); +} + export function LabCasesDispatchPanel({ details, labCases, @@ -58,19 +118,37 @@ export function LabCasesDispatchPanel({ .map((id) => activeLinkedOrganizations.find((o) => o.id === id)) .filter(Boolean) as LinkedOrganizationOption[]; - const labEligibleDetails = details.filter((d) => labDependentCodes.has(d.treatmentType)); + const labEligibleDetails = useMemo( + () => details.filter((d) => labDependentCodes.has(d.treatmentType)), + [details, labDependentCodes], + ); + + const canAddLabShipment = useMemo( + () => detailsAvailableForNewShipment(details, labCases, labDependentCodes).length > 0, + [details, labCases, labDependentCodes], + ); + const activeLabCase = labCases.find((lc) => lc.clientId === activeLabCaseId) ?? labCases[0] ?? null; const sent = Boolean(activeLabCase?.sentAt); - function detailSummary(d: TreatmentDetailDraft, idx: number) { + const activeLabOrgName = activeLabCase?.destinationOrganizationId + ? orgs.find((o) => o.id === activeLabCase.destinationOrganizationId)?.name + : null; + + function detailNumber(d: TreatmentDetailDraft) { + const idx = details.findIndex((row) => row.clientId === d.clientId); + return idx >= 0 ? idx + 1 : 0; + } + + function detailSummary(d: TreatmentDetailDraft) { const typeKey = treatmentTypeLabelKey(d.treatmentType); const typeLabel = d.treatmentType in TREATMENT_TYPE_KEYS ? t(typeKey as 'typeEndo') : d.treatmentType; const teeth = d.teeth.length ? d.teeth.join(', ') : t('teethNone'); - return `${t('detailLabel', { n: idx + 1 })} · ${typeLabel} · ${teeth}`; + return `${t('detailLabel', { n: detailNumber(d) })} · ${typeLabel} · ${teeth}`; } function updateActiveLabCase(patch: Partial) { @@ -105,16 +183,6 @@ export function LabCasesDispatchPanel({ ); } - function detailAssignedElsewhere(detailClientId: string): boolean { - if (!activeLabCase) return false; - return labCases.some( - (lc) => - !lc.sentAt && - lc.clientId !== activeLabCase.clientId && - lc.detailClientIds.includes(detailClientId), - ); - } - if (labEligibleDetails.length === 0) { return (
@@ -124,6 +192,15 @@ export function LabCasesDispatchPanel({ ); } + const includedInActiveShipment = activeLabCase + ? labEligibleDetails.filter((d) => activeLabCase.detailClientIds.includes(d.clientId)) + : []; + + const pickableForActiveDraft = + activeLabCase && !sent + ? selectableDetailsForDraftShipment(details, labCases, labDependentCodes, activeLabCase) + : []; + return (
@@ -133,14 +210,16 @@ export function LabCasesDispatchPanel({ {t('labDispatchSubtitle')} {t('labDispatchSendHint')}

- + {canAddLabShipment && ( + + )}
{labCases.length === 0 ? ( @@ -182,105 +261,44 @@ export function LabCasesDispatchPanel({ {activeLabCase && (
-
-

{t('includeDetails')}

-
- {labEligibleDetails.map((d, idx) => { - const assignedElsewhere = detailAssignedElsewhere(d.clientId); - const inSentCase = labCases.some( - (lc) => lc.sentAt && lc.detailClientIds.includes(d.clientId), - ); - const checked = activeLabCase.detailClientIds.includes(d.clientId); - const itemDisabled = - disabled || sent || inSentCase || assignedElsewhere; - - return ( - toggleDetailInActiveLabCase(d.clientId, next)} - label={detailSummary(d, idx)} - /> - ); - })} -
-
- -