improvement: attachment selection for lab dispatch added. attachment preview added to cases feature.
This commit is contained in:
@@ -11,6 +11,8 @@ import { useToast } from '@/lib/hooks/useToast';
|
||||
import { canEditCases, canEditTasks } from '@/components/shared/permissions';
|
||||
import { Badge } from '@/components/ui/shared/Badge';
|
||||
import { LabCaseCommentsPanel } from '@/components/ui/lab/LabCaseCommentsPanel';
|
||||
import { CaseToothChartPanel } from '@/components/ui/lab/CaseToothChartPanel';
|
||||
import { LabCaseAttachmentPreview } from '@/components/ui/lab/LabCaseAttachmentPreview';
|
||||
import { labTaskStatusVariant } from '@/components/ui/lab/labTaskStatusDisplay';
|
||||
import { casesApi } from '@/lib/api/cases';
|
||||
import { tasksApi } from '@/lib/api/tasks';
|
||||
@@ -210,6 +212,39 @@ export default function CasesPage() {
|
||||
document.getElementById('case-comments')?.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
const loadCaseAttachmentBlob = useCallback(
|
||||
(caseId: string, attachmentId: string) => casesApi.getAttachmentFileBlob(caseId, attachmentId),
|
||||
[],
|
||||
);
|
||||
|
||||
const latestCaseAttachment = useMemo(() => {
|
||||
if (!selectedCase?.attachments.length) return null;
|
||||
return [...selectedCase.attachments].sort(
|
||||
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
|
||||
)[0];
|
||||
}, [selectedCase?.attachments]);
|
||||
|
||||
const caseProsthesisRows = useMemo(() => {
|
||||
if (!selectedCase) return [];
|
||||
if (selectedCase.toothProsthesis.length > 0) {
|
||||
const byCode = new Map<string, string[]>();
|
||||
for (const row of selectedCase.toothProsthesis) {
|
||||
const key = row.prosthesisTypeCode;
|
||||
const teeth = byCode.get(key) ?? [];
|
||||
if (!teeth.includes(row.tooth)) teeth.push(row.tooth);
|
||||
byCode.set(key, teeth);
|
||||
}
|
||||
return [...byCode.entries()].map(([prosthesisTypeCode, teeth]) => ({
|
||||
prosthesisTypeCode,
|
||||
teeth,
|
||||
}));
|
||||
}
|
||||
return selectedCase.tasksByTooth.map((g) => ({
|
||||
prosthesisTypeCode: g.prosthesisTypeCode,
|
||||
teeth: g.teeth,
|
||||
}));
|
||||
}, [selectedCase]);
|
||||
|
||||
function clearFilters() {
|
||||
setSearch('');
|
||||
setClinicId('');
|
||||
@@ -446,6 +481,25 @@ export default function CasesPage() {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="flex flex-wrap items-start gap-4">
|
||||
<CaseToothChartPanel
|
||||
details={selectedCase.details}
|
||||
prosthesisRows={caseProsthesisRows}
|
||||
scale={0.5}
|
||||
className="min-w-0 flex-1"
|
||||
/>
|
||||
{latestCaseAttachment && selectedCaseId ? (
|
||||
<div className="shrink-0 space-y-1">
|
||||
<p className="text-xs font-medium text-text-secondary">{t('latestAttachment')}</p>
|
||||
<LabCaseAttachmentPreview
|
||||
caseId={selectedCaseId}
|
||||
attachment={latestCaseAttachment}
|
||||
loadBlob={loadCaseAttachmentBlob}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{selectedCase.details.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-medium text-text-primary">{t('treatmentDetails')}</h3>
|
||||
@@ -476,12 +530,13 @@ export default function CasesPage() {
|
||||
className="rounded-md border border-border p-3 space-y-2"
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span
|
||||
className="inline-flex items-center rounded px-2 py-0.5 text-xs font-medium border"
|
||||
<Badge
|
||||
truncate
|
||||
title={group.prosthesisTypeLabel}
|
||||
style={prosthesisTypeBadgeStyle(group.prosthesisTypeCode, groupIndex)}
|
||||
>
|
||||
{group.prosthesisTypeLabel}
|
||||
</span>
|
||||
</Badge>
|
||||
<span className="text-sm font-medium text-text-primary">
|
||||
{t('toothGroupTitle', {
|
||||
teeth: formatToothList(group.teeth),
|
||||
|
||||
Reference in New Issue
Block a user