'use client'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslations } from 'next-intl'; import { formatApiErrorMessage } from '@/components/shared/formatApiError'; import { useAuth } from '@/lib/hooks/useAuth'; import { useToast } from '@/lib/hooks/useToast'; import { organizationApi } from '@/lib/api/organization'; import { treatmentCatalogApi } from '@/lib/api/treatment-catalog'; import { treatmentTypeLabelFromCatalog } from '@/components/ui/treatment/treatmentTypeDisplay'; import { Badge, type BadgeVariant } from '@/components/ui/shared/Badge'; import { Button } from '@/components/ui/shared/Button'; import { SearchBar } from '@/components/ui/shared/SearchBar'; import { ToastStack } from '@/components/ui/shared/Toast'; import type { CounterpartItemDto } from '@/lib/api/organization'; import type { LabCaseDetail, LabCaseListItem, LabTaskStatus } from '@/types/cases'; import type { TreatmentCatalogEntry } from '@/types/treatment-catalog'; const PAGE_SIZE = 20; function taskStatusVariant(status: LabTaskStatus): BadgeVariant { switch (status) { case 'COMPLETED': return 'success'; case 'IN_PROGRESS': return 'default'; default: return 'warning'; } } function formatPatientName(patient: { firstName: string; lastName: string }) { return `${patient.firstName} ${patient.lastName}`.trim(); } function formatDateTime(value: string | null, locale: string) { if (!value) return '—'; return new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeStyle: 'short', }).format(new Date(value)); } function TaskProgressBar({ completed, total }: { completed: number; total: number }) { const pct = total > 0 ? Math.round((completed / total) * 100) : 0; return (
{completed}/{total} {pct}%
); } interface ConnectionCaseHistoryContentProps { connection: CounterpartItemDto; onBack: () => void; } export function ConnectionCaseHistoryContent({ connection, onBack, }: ConnectionCaseHistoryContentProps) { const t = useTranslations('organizations'); const tCases = useTranslations('cases'); const tCommon = useTranslations('common'); const { currentOrganization, user } = useAuth(); const { showError, setError, messages: toastMessages } = useToast(); const [search, setSearch] = useState(''); const [page, setPage] = useState(1); const [cases, setCases] = useState([]); const [pagination, setPagination] = useState({ page: 1, limit: PAGE_SIZE, total: 0, totalPages: 1, }); const [selectedCaseId, setSelectedCaseId] = useState(null); const [selectedCase, setSelectedCase] = useState(null); const [treatmentCatalog, setTreatmentCatalog] = useState([]); const [loadingList, setLoadingList] = useState(false); const [loadingDetail, setLoadingDetail] = useState(false); const locale = user?.language ?? 'en'; const isClinic = currentOrganization?.type === 'CLINIC'; const tRef = useRef(t); tRef.current = t; const treatmentLabel = useCallback( (type: string) => treatmentTypeLabelFromCatalog(type, treatmentCatalog), [treatmentCatalog], ); useEffect(() => { void treatmentCatalogApi.list().then((r) => setTreatmentCatalog(r.data)).catch(() => {}); }, []); const statusOptions: { value: LabTaskStatus; label: string }[] = useMemo( () => [ { value: 'PENDING', label: tCases('statusPending') }, { value: 'IN_PROGRESS', label: tCases('statusInProgress') }, { value: 'COMPLETED', label: tCases('statusCompleted') }, ], [tCases], ); useEffect(() => { let cancelled = false; const timeout = setTimeout(() => { void (async () => { setLoadingList(true); setError(''); try { const response = await organizationApi.listConnectionCases(connection.id, { q: search.trim() || undefined, page, limit: PAGE_SIZE, }); if (cancelled) return; setCases(response.data.items); setPagination(response.data.pagination); } catch (error: unknown) { if (cancelled) return; showError(formatApiErrorMessage(error, tRef.current('caseHistoryErrorLoadList'))); } finally { if (!cancelled) setLoadingList(false); } })(); }, search ? 300 : 0); return () => { cancelled = true; clearTimeout(timeout); }; }, [search, page, connection.id, showError, setError]); useEffect(() => { if (!selectedCaseId) { setSelectedCase(null); return; } let cancelled = false; void (async () => { setLoadingDetail(true); setError(''); try { const response = await organizationApi.getConnectionCase(connection.id, selectedCaseId); if (cancelled) return; setSelectedCase(response.data); } catch (error: unknown) { if (cancelled) return; showError(formatApiErrorMessage(error, tRef.current('caseHistoryErrorLoadDetail'))); setSelectedCase(null); } finally { if (!cancelled) setLoadingDetail(false); } })(); return () => { cancelled = true; }; }, [selectedCaseId, connection.id, showError, setError]); return (

{t('caseHistoryTitle', { name: connection.organizationName })}

{isClinic ? t('caseHistorySubtitleClinic') : t('caseHistorySubtitleLab')}

{ setSearch(value); setPage(1); }} placeholder={tCases('searchPlaceholder')} />
{loadingList ? (

{tCommon('loading')}

) : cases.length === 0 ? (

{t('caseHistoryEmpty')}

) : (
    {cases.map((item) => { const isActive = item.id === selectedCaseId; return (
  • ); })}
)}
{pagination.totalPages > 1 ? (
{tCases('pageSummary', { page: pagination.page, totalPages: pagination.totalPages, total: pagination.total, })}
) : null}
{!selectedCaseId ? (

{tCases('selectCaseHint')}

) : loadingDetail || !selectedCase ? (

{tCommon('loading')}

) : (

{formatPatientName(selectedCase.patient)}

{tCases('patientMobile')}: {selectedCase.patient.mobile}

{!isClinic ? (

{tCases('fromClinic', { name: selectedCase.clinic.name })}

) : (

{t('caseHistorySentToLab', { name: connection.organizationName })}

)}

{tCases('sentAt', { date: formatDateTime(selectedCase.sentAt, locale) })}

{tCases('taskProgressLabel', { completed: selectedCase.taskProgress.completed, total: selectedCase.taskProgress.total, })}

{selectedCase.labComment ? (

{tCases('labComment')}:{' '} {selectedCase.labComment}

) : null}
{selectedCase.details.length > 0 && (

{tCases('treatmentDetails')}

    {selectedCase.details.map((detail) => (
  • {treatmentLabel(detail.treatmentType)}
    {tCases('teethLabel')}: {detail.teeth.join(', ') || '—'}
    {detail.comment ? (
    {detail.comment}
    ) : null}
  • ))}
)}

{tCases('tasksByTooth')}

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

{tCases('noTasks')}

) : ( selectedCase.tasksByTooth.map((group) => (
{tCases('toothGroupTitle', { tooth: group.tooth, prosthesis: group.prosthesisTypeLabel, type: treatmentLabel(group.treatmentType), })}
    {group.tasks.map((task) => (
  • {task.stepOrder}. {task.stepLabel} {statusOptions.find((opt) => opt.value === task.status)?.label ?? task.status}
  • ))}
)) )}
)}
); }