'use client'; import { useEffect, useMemo, useState, type ReactNode } from 'react'; import { useLocale, useTranslations } from 'next-intl'; import { Download, MessageSquare } from 'lucide-react'; import { Badge } from '@/components/ui/shared/Badge'; import { Button } from '@/components/ui/shared/Button'; import { Checkbox } from '@/components/ui/shared/Checkbox'; import { Input } from '@/components/ui/shared/Input'; import { CaseToothChartPanel } from '@/components/ui/lab/CaseToothChartPanel'; import { FORM_SELECT_CLASS } from '@/components/shared/formSelectStyles'; import { LabCaseAttachmentPreview } from '@/components/ui/lab/LabCaseAttachmentPreview'; import { LabCaseAttachmentsDialog } from '@/components/ui/lab/LabCaseAttachmentsDialog'; import { LabCaseShareQrDialog, LabCaseShareQrThumb, } from '@/components/ui/lab/LabCaseShareQrDialog'; import { labTaskStatusVariant } from '@/components/lab/labTaskStatusDisplay'; import { LabCaseDueDateBadge } from '@/components/lab/LabCaseDueDateBadge'; import { LabCaseOriginBadge } from '@/components/ui/lab/LabCaseOriginBadge'; import { formatToothList } from '@/components/treatment/prosthesisTypeDisplay'; import { ProsthesisStackedTypeLabel } from '@/components/ui/lab/ProsthesisStackedTypeLabel'; import { prosthesisCatalogApi } from '@/lib/api/prosthesis-catalog'; import { buildCaseConnectedTeeth, buildCaseProsthesisRows, formatCaseDateTime, formatPatientName, latestCaseAttachment, } from '@/components/lab/caseDetailUtils'; import { downloadCaseSheetPdf } from '@/components/lab/caseSheetPdf'; import { ConnectedSelectionBadge } from '@/components/ui/treatment/ConnectedSelectionBadge'; import { useToast } from '@/lib/hooks/useToast'; import type { AssignableTaskStaff, LabCaseDetail, LabTaskStatus } from '@/types/cases'; import type { ProsthesisCatalogEntry } from '@/types/treatment-catalog'; function CaseTaskProgressBar({ completed, total }: { completed: number; total: number }) { const pct = total > 0 ? Math.round((completed / total) * 100) : 0; return (
{completed}/{total} {pct}%
); } export interface CaseDetailPanelProps { labCase: LabCaseDetail; locale: string; treatmentLabel: (type: string) => string; statusOptions: { value: LabTaskStatus; label: string }[]; loadAttachmentBlob: (caseId: string, attachmentId: string) => Promise; /** Extra lines below patient mobile (e.g. connection-specific clinic/lab line). */ headerMetaLines?: ReactNode; showCommentsButton?: boolean; commentCount?: number; onCommentsClick?: () => void; canEditImportant?: boolean; updatingImportant?: boolean; onImportantChange?: (checked: boolean) => void; updatingExternalCode?: boolean; onExternalCodeChange?: (externalCode: string | null) => void; commentsSection?: ReactNode; assignableStaff?: AssignableTaskStaff[]; canAssignTasks?: boolean; assigningTaskId?: string | null; onAssignTask?: (taskId: string, assigneeUserId: string | null) => void; } export function CaseDetailPanel({ labCase, locale, treatmentLabel, statusOptions, loadAttachmentBlob, headerMetaLines, showCommentsButton = false, commentCount = 0, onCommentsClick, canEditImportant = false, updatingImportant = false, onImportantChange, updatingExternalCode = false, onExternalCodeChange, commentsSection, assignableStaff = [], canAssignTasks = false, assigningTaskId = null, onAssignTask, }: CaseDetailPanelProps) { const t = useTranslations('cases'); const tTreatment = useTranslations('treatment'); const intlLocale = useLocale(); const toast = useToast(); const [attachmentsDialogOpen, setAttachmentsDialogOpen] = useState(false); const [shareQrDialogOpen, setShareQrDialogOpen] = useState(false); const [prosthesisCatalog, setProsthesisCatalog] = useState([]); const [downloadingPdf, setDownloadingPdf] = useState(false); const [externalCodeDraft, setExternalCodeDraft] = useState(labCase.externalCode ?? ''); useEffect(() => { setExternalCodeDraft(labCase.externalCode ?? ''); }, [labCase.id, labCase.externalCode]); useEffect(() => { void prosthesisCatalogApi .list() .then((response) => setProsthesisCatalog(response.data)) .catch(() => {}); }, []); const prosthesisRows = useMemo(() => buildCaseProsthesisRows(labCase), [labCase]); const connectedTeeth = useMemo(() => buildCaseConnectedTeeth(labCase), [labCase]); const previewAttachment = useMemo(() => latestCaseAttachment(labCase), [labCase]); async function handleDownloadCaseSheet() { setDownloadingPdf(true); try { await downloadCaseSheetPdf({ labCase, locale: intlLocale, prosthesisCatalog, labels: { title: t('caseSheetTitle'), orderNumber: t('caseSheetOrderNumber'), orderDate: t('caseSheetOrderDate'), dateDue: t('caseSheetDateDue'), sender: t('caseSheetSender'), recipient: t('caseSheetRecipient'), patient: t('caseSheetPatient'), patientId: t('caseSheetPatientId'), prosthesis: t('caseSheetProsthesis'), toothChart: t('caseSheetToothChart'), connected: tTreatment('connectedBadge'), teeth: t('teethLabel'), archUpper: tTreatment('selectedArchUpper'), archLower: tTreatment('selectedArchLower'), comments: t('caseSheetComments'), noComments: t('caseSheetNoComments'), }, }); } catch { toast.showError(t('errorDownloadCaseSheet')); } finally { setDownloadingPdf(false); } } return (

{formatPatientName(labCase.patient)}

{!canEditImportant && labCase.isImportant ? ( {t('importantLabel')} ) : null}

{t('patientMobile')}: {labCase.patient.mobile}

{headerMetaLines}

{t('sentAt', { date: formatCaseDateTime(labCase.sentAt, locale) })}

{t('taskProgressLabel', { completed: labCase.taskProgress.completed, total: labCase.taskProgress.total, })}

{canEditImportant ? (
onImportantChange?.(checked)} />
setExternalCodeDraft(e.target.value)} onBlur={() => { const next = externalCodeDraft.trim() || null; const prev = labCase.externalCode?.trim() || null; if (next === prev) return; onExternalCodeChange?.(next); }} />
) : labCase.externalCode?.trim() ? (

{labCase.externalCode.trim()}

) : null}
{showCommentsButton && onCommentsClick ? ( ) : null}
{labCase.shareUrl || (previewAttachment && labCase.attachments.length > 0) ? (
{previewAttachment && labCase.attachments.length > 0 ? ( ) : null} {labCase.shareUrl ? ( setShareQrDialogOpen(true)} /> ) : null}
) : null}
{labCase.detail ? (

{t('treatmentDetails')}

{treatmentLabel(labCase.detail.treatmentType)}
{t('teethLabel')}: {labCase.detail.teeth.join(', ') || '—'} {connectedTeeth.size > 0 ? : null}
{labCase.detail.comment ? (
{labCase.detail.comment}
) : null}
) : null}

{t('tasksByTooth')}

{labCase.tasksByTooth.length === 0 ? (

{t('noTasks')}

) : ( labCase.tasksByTooth.map((group) => (
{formatToothList(group.teeth, { UA: tTreatment('selectedArchUpper'), LA: tTreatment('selectedArchLower'), })} {group.connected ? : null}
    {group.tasks.map((task) => (
  • {task.stepOrder}. {task.stepLabel} {task.lastStatusChangedBy ? t('lastUpdatedBy', { name: task.lastStatusChangedBy.name }) : t('lastUpdatedUnknown')} {task.lastStatusChangedAt ? ` · ${formatCaseDateTime(task.lastStatusChangedAt, locale)}` : ''} {canAssignTasks && onAssignTask ? ( ) : null} {statusOptions.find((opt) => opt.value === task.status)?.label ?? task.status}
  • ))}
)) )}
{commentsSection} setAttachmentsDialogOpen(false)} caseId={labCase.id} attachments={labCase.attachments} loadBlob={loadAttachmentBlob} /> {labCase.shareUrl ? ( setShareQrDialogOpen(false)} shareUrl={labCase.shareUrl} /> ) : null}
); } export { CaseTaskProgressBar };