'use client'; import { useEffect, useState } from 'react'; import { useTranslations } from 'next-intl'; import { Button } from '@/components/ui/shared/Button'; import { Checkbox } from '@/components/ui/shared/Checkbox'; import { Dropdown } from '@/components/ui/shared/Dropdown'; import { FORM_SELECT_CLASS } from '@/components/shared/formSelectStyles'; import { SearchBar } from '@/components/ui/shared/SearchBar'; import { CaseSentLabel } from '@/components/ui/treatment/CaseSentLabel'; import { LabCaseCommentsPanel } from '@/components/ui/lab/LabCaseCommentsPanel'; import { treatmentTypeLabelFromCatalog } from '@/components/shared/treatmentTypeDisplay'; import { treatmentsApi } from '@/lib/api/treatments'; import { prosthesisCatalogApi } from '@/lib/api/prosthesis-catalog'; import type { ProsthesisCatalogEntry, TreatmentCatalogEntry } from '@/types/treatment-catalog'; import type { LabCaseDraft, LinkedOrganizationOption, TreatmentDetailDraft } from '@/types/treatment'; interface LabCasesDispatchPanelProps { details: TreatmentDetailDraft[]; activeDetailId: string; labCases: LabCaseDraft[]; labDependentCodes: Set; treatmentCatalog: TreatmentCatalogEntry[]; activeLabCaseId: string | null; onLabCasesChange: (labCases: LabCaseDraft[]) => void; disabled: boolean; canEdit: boolean; orgs: LinkedOrganizationOption[]; organizationSearch: string; onOrganizationSearchChange: (value: string) => void; recentOrganizationIds: string[]; onRecentOrganizationPick: (orgId: string) => void; sendBusyId: string | null; onAddLabCase: () => void; onSendLabCase: (labCase: LabCaseDraft, comment?: string) => void; onCommentError?: (message: string) => void; } function sentDetailClientIds(labCases: LabCaseDraft[]): Set { const ids = new Set(); for (const lc of labCases) { if (lc.sentAt && lc.detailClientId) ids.add(lc.detailClientId); } return ids; } function prosthesisTeethRows( labCase: LabCaseDraft, activeDetail: TreatmentDetailDraft, detailNumber: number, ): Array<{ detailClientId: string; tooth: string; detailNumber: number }> { if (labCase.detailClientId !== activeDetail.clientId) return []; if (activeDetail.treatmentType !== 'prosthesis') return []; return activeDetail.teeth.map((tooth) => ({ detailClientId: activeDetail.clientId, tooth, detailNumber, })); } function isProsthesisMapComplete( labCase: LabCaseDraft, rows: Array<{ detailClientId: string; tooth: string }>, ): boolean { if (rows.length === 0) return true; return rows.every((row) => labCase.toothProsthesis.some( (tp) => tp.detailClientId === row.detailClientId && tp.tooth === row.tooth && Boolean(tp.prosthesisTypeCode), ), ); } export function LabCasesDispatchPanel({ details, activeDetailId, labCases, labDependentCodes, treatmentCatalog, activeLabCaseId, onLabCasesChange, disabled, canEdit, orgs, organizationSearch, onOrganizationSearchChange, recentOrganizationIds, onRecentOrganizationPick, sendBusyId, onAddLabCase, onSendLabCase, onCommentError, }: LabCasesDispatchPanelProps) { const t = useTranslations('treatment'); const [prosthesisOptions, setProsthesisOptions] = useState([]); const [applyAllProsthesis, setApplyAllProsthesis] = useState(''); const [pendingComment, setPendingComment] = useState(''); const activeLinkedOrganizations = orgs.filter((o) => o.active); const filteredOrganizations = (() => { const q = organizationSearch.trim().toLowerCase(); if (!q) return activeLinkedOrganizations; return activeLinkedOrganizations.filter((o) => o.name.toLowerCase().includes(q)); })(); const recentOrganizations = recentOrganizationIds .map((id) => activeLinkedOrganizations.find((o) => o.id === id)) .filter(Boolean) as LinkedOrganizationOption[]; const activeDetail = details.find((d) => d.clientId === activeDetailId) ?? null; const isLabDependentDetail = Boolean( activeDetail && labDependentCodes.has(activeDetail.treatmentType), ); const labCaseForActiveDetail = labCases.find((lc) => lc.detailClientId === activeDetailId) ?? null; const activeLabCase = labCaseForActiveDetail ?? (activeLabCaseId ? labCases.find((lc) => lc.clientId === activeLabCaseId) : null); const detailAlreadyInShipment = Boolean(labCaseForActiveDetail); const canAddLabShipment = !detailAlreadyInShipment && !sentDetailClientIds(labCases).has(activeDetailId); const sent = Boolean(activeLabCase?.sentAt); const activeDetailNumber = details.findIndex((d) => d.clientId === activeDetailId) + 1; const activeLabOrgName = activeLabCase?.destinationOrganizationId ? orgs.find((o) => o.id === activeLabCase.destinationOrganizationId)?.name : null; const prosthesisRows = activeLabCase && activeDetail ? prosthesisTeethRows(activeLabCase, activeDetail, activeDetailNumber) : []; const prosthesisComplete = activeLabCase ? isProsthesisMapComplete(activeLabCase, prosthesisRows) : true; useEffect(() => { if (!activeLabCase?.destinationOrganizationId) { setProsthesisOptions([]); return; } let cancelled = false; void prosthesisCatalogApi .list(activeLabCase.destinationOrganizationId) .then((res) => { if (!cancelled) setProsthesisOptions(res.data); }) .catch(() => { if (!cancelled) setProsthesisOptions([]); }); return () => { cancelled = true; }; }, [activeLabCase?.destinationOrganizationId]); useEffect(() => { setPendingComment(''); }, [activeLabCase?.clientId]); if (!activeDetail || !isLabDependentDetail) { return null; } function detailSummary(d: TreatmentDetailDraft) { const typeLabel = treatmentTypeLabelFromCatalog(d.treatmentType, treatmentCatalog); const teeth = d.teeth.length ? d.teeth.join(', ') : t('teethNone'); return `${t('detailLabel', { n: activeDetailNumber })} · ${typeLabel} · ${teeth}`; } function updateActiveLabCase(patch: Partial) { if (!activeLabCase) return; onLabCasesChange( labCases.map((lc) => (lc.clientId === activeLabCase.clientId ? { ...lc, ...patch } : lc)), ); } function setToothProsthesis( detailClientId: string, tooth: string, prosthesisTypeCode: string, ) { if (!activeLabCase) return; const rest = activeLabCase.toothProsthesis.filter( (tp) => !(tp.detailClientId === detailClientId && tp.tooth === tooth), ); const next = prosthesisTypeCode ? [...rest, { detailClientId, tooth, prosthesisTypeCode }] : rest; updateActiveLabCase({ toothProsthesis: next }); } function applyProsthesisToAll(code: string) { if (!activeLabCase || !code) return; const next = prosthesisRows.map((row) => ({ detailClientId: row.detailClientId, tooth: row.tooth, prosthesisTypeCode: code, })); updateActiveLabCase({ toothProsthesis: next }); } function toggleAttachmentInActiveLabCase(attachmentId: string, checked: boolean) { if (!activeLabCase || sent) return; const set = new Set(activeLabCase.attachmentIds); if (checked) set.add(attachmentId); else set.delete(attachmentId); updateActiveLabCase({ attachmentIds: [...set] }); } const activeDetailAttachments = activeDetail.attachmentMetas ?? []; return (

{t('labDispatchTitle')}

{t('labDispatchSubtitle')} {t('labDispatchSendHint')}

{canAddLabShipment && ( )}
{!detailAlreadyInShipment ? (

{t('labDispatchEmpty')}

) : activeLabCase ? (
{sent ? ( <>

{t('labShipmentIncludedDetails')}

{detailSummary(activeDetail)}

{activeLabCase.id ? ( { const r = await treatmentsApi.listLabCaseComments(activeLabCase.id!); return r.data; }} onPost={async () => { throw new Error('Read-only'); }} onError={onCommentError} /> ) : null} {activeLabOrgName ? (

{t('selectLab')}

{activeLabOrgName}

) : null} ) : ( <>

{t('labShipmentIncludedDetails')}

{detailSummary(activeDetail)}

{!sent && activeDetailAttachments.length > 0 ? (

{t('labShipmentAttachments')}

{t('labShipmentAttachmentsHint')}

{activeDetailAttachments.map((att) => ( toggleAttachmentInActiveLabCase(att.id, next)} label={`${att.fileName} (${(att.sizeBytes / 1024).toFixed(1)} KB)`} /> ))}
) : null} {activeLabCase.id ? ( { const r = await treatmentsApi.listLabCaseComments(activeLabCase.id!); return r.data; }} onPost={async (body) => { const r = await treatmentsApi.addLabCaseComment(activeLabCase.id!, { body }); return r.data; }} onError={onCommentError} /> ) : null}

{t('selectLab')}

{recentOrganizations.length > 0 && (
{t('recent')} {recentOrganizations.map((o) => ( ))}
)} { const nextOrgId = e.target.value || null; updateActiveLabCase({ destinationOrganizationId: nextOrgId, toothProsthesis: [], }); setApplyAllProsthesis(''); }} disabled={disabled || filteredOrganizations.length === 0} > {filteredOrganizations.map((o) => ( ))} {filteredOrganizations.length === 0 && (

{t('noOrgMatch')}

)}
{prosthesisRows.length > 0 && activeLabCase.destinationOrganizationId ? (

{t('prosthesisTypesTitle')}

{prosthesisRows.map((row) => { const current = activeLabCase.toothProsthesis.find( (tp) => tp.detailClientId === row.detailClientId && tp.tooth === row.tooth, )?.prosthesisTypeCode ?? ''; return ( ); })}
{t('prosthesisColTooth')} {t('prosthesisColDetail')} {t('prosthesisColType')}
{row.tooth} {t('detailLabel', { n: row.detailNumber })}
) : null}
)}
) : null}
); }