improvement: attachment selection for lab dispatch added. attachment preview added to cases feature.

This commit is contained in:
2026-07-07 18:43:10 +03:30
parent b2d40b3e97
commit 86b1e3afff
25 changed files with 767 additions and 84 deletions

View File

@@ -9,7 +9,7 @@ import { PastTreatmentsPanel } from '@/components/ui/treatment/PastTreatmentsPan
import { TreatmentDetailsEditor } from '@/components/ui/treatment/TreatmentDetailsEditor';
import { TreatmentPreviewCard } from '@/components/ui/treatment/TreatmentPreviewCard';
import { ToastStack } from '@/components/ui/shared/Toast';
import { treatmentTypeLabelFromCatalog } from '@/components/ui/treatment/treatmentTypeDisplay';
import { treatmentTypeLabelFromCatalog, treatmentTypeColor } from '@/components/ui/treatment/treatmentTypeDisplay';
import {
addCalendarDays,
compareLocalDayStart,
@@ -133,6 +133,7 @@ function newLabCaseDraft(): LabCaseDraft {
destinationOrganizationId: null,
detailClientIds: [],
toothProsthesis: [],
attachmentIds: [],
sentAt: null,
sends: [],
};
@@ -180,6 +181,7 @@ function mapLabCaseDraftFromApi(lc: PastLabCase): LabCaseDraft {
tooth: tp.tooth,
prosthesisTypeCode: tp.prosthesisTypeCode,
})),
attachmentIds: (lc.attachments ?? []).map((a) => a.id),
sentAt: lc.sentAt ?? null,
sends: lc.sends ?? [],
};
@@ -296,6 +298,7 @@ export function TreatmentWorkspace({ userId, currentOrganization }: TreatmentWor
const [uploadBusyDetailId, setUploadBusyDetailId] = useState<string | null>(null);
const [organizationSearch, setOrganizationSearch] = useState('');
const [recentOrganizationIds, setRecentOrganizationIds] = useState<string[]>([]);
const [showWholeTreatmentPlan, setShowWholeTreatmentPlan] = useState(false);
const isDetailLocked = useCallback(
(detail: TreatmentDetailDraft) =>
@@ -388,6 +391,35 @@ export function TreatmentWorkspace({ userId, currentOrganization }: TreatmentWor
const selectedTeethSet = useMemo(() => new Set(activeDetail?.teeth ?? []), [activeDetail?.teeth]);
const wholePlanTeethSet = useMemo(() => {
const set = new Set<FdiToothId>();
for (const detail of details) {
for (const tooth of detail.teeth) set.add(tooth);
}
return set;
}, [details]);
const wholePlanToothColors = useMemo(() => {
const colors: Partial<Record<FdiToothId, string>> = {};
for (let i = 0; i < details.length; i++) {
const detail = details[i];
const catalogIndex = treatmentCatalog.findIndex((e) => e.code === detail.treatmentType);
const color = treatmentTypeColor(detail.treatmentType, catalogIndex >= 0 ? catalogIndex : i);
for (const tooth of detail.teeth) {
if (!(tooth in colors)) colors[tooth] = color;
}
}
return colors;
}, [details, treatmentCatalog]);
const chartSelectedTeeth = showWholeTreatmentPlan ? wholePlanTeethSet : selectedTeethSet;
const chartToothColors = showWholeTreatmentPlan ? wholePlanToothColors : undefined;
// Reset whole-plan overview when switching details.
useEffect(() => {
setShowWholeTreatmentPlan(false);
}, [activeDetailId]);
// Sync active lab shipment when the selected treatment detail changes.
useEffect(() => {
const match = labCaseDrafts.find((lc) => lc.detailClientIds.includes(activeDetailId));
@@ -817,6 +849,7 @@ export function TreatmentWorkspace({ userId, currentOrganization }: TreatmentWor
};
})
.filter((row): row is { treatmentDetailId: string; tooth: string; prosthesisTypeCode: string } => row !== null),
attachmentIds: lc.attachmentIds,
}));
if (payload.length === 0) {
@@ -1040,9 +1073,24 @@ export function TreatmentWorkspace({ userId, currentOrganization }: TreatmentWor
<div className="space-y-3 min-w-0 w-full">
<FdiToothChart
selected={selectedTeethSet}
selected={chartSelectedTeeth}
toothColors={chartToothColors}
readOnly={showWholeTreatmentPlan}
headerControl={
details.length > 1 ? (
<label className="flex items-center gap-2 text-[11px] text-text-muted cursor-pointer select-none">
<input
type="checkbox"
checked={showWholeTreatmentPlan}
onChange={(e) => setShowWholeTreatmentPlan(e.target.checked)}
className="rounded border-border"
/>
{t('toothChartWholePlan')}
</label>
) : undefined
}
onToggle={(fdi) => {
if (!canEditTreatmentForDay || isDetailLocked(activeDetail)) return;
if (!canEditTreatmentForDay || isDetailLocked(activeDetail) || showWholeTreatmentPlan) return;
setDetails((prev) =>
prev.map((d) => {
if (d.clientId !== activeDetailId) return d;