From 51d3a922637344eb4e46a4785d042fd3344a582b Mon Sep 17 00:00:00 2001 From: Matteo Parenti Date: Fri, 13 Feb 2026 17:01:16 +0100 Subject: [PATCH] Add material mismatch check --- frontend/src/components/AssignSpoolModal.tsx | 58 +++++++++++++++++++- frontend/src/components/ConfirmModal.tsx | 4 +- frontend/src/i18n/locales/de.ts | 3 + frontend/src/i18n/locales/en.ts | 3 + frontend/src/i18n/locales/fr.ts | 3 + frontend/src/i18n/locales/it.ts | 3 + frontend/src/i18n/locales/ja.ts | 3 + 7 files changed, 74 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/AssignSpoolModal.tsx b/frontend/src/components/AssignSpoolModal.tsx index 6ec9b0ca2..1d1c9c968 100644 --- a/frontend/src/components/AssignSpoolModal.tsx +++ b/frontend/src/components/AssignSpoolModal.tsx @@ -5,6 +5,7 @@ import { X, Loader2, Package, Check, Search } from 'lucide-react'; import { api } from '../api/client'; import type { InventorySpool, SpoolAssignment } from '../api/client'; import { Button } from './Button'; +import { ConfirmModal } from './ConfirmModal'; import { useToast } from '../contexts/ToastContext'; interface AssignSpoolModalProps { @@ -26,6 +27,8 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr const { showToast } = useToast(); const [selectedSpoolId, setSelectedSpoolId] = useState(null); const [searchFilter, setSearchFilter] = useState(''); + const [pendingAssignId, setPendingAssignId] = useState(null); + const [showMismatchConfirm, setShowMismatchConfirm] = useState(false); const { data: spools, isLoading } = useQuery({ queryKey: ['inventory-spools'], @@ -53,6 +56,8 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr }); queryClient.invalidateQueries({ queryKey: ['spool-assignments'] }); showToast(t('inventory.assignSuccess'), 'success'); + setShowMismatchConfirm(false); + setPendingAssignId(null); onClose(); }, onError: (error: Error) => { @@ -84,10 +89,37 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr ); }); + const normalizeMaterial = (value: string | undefined | null) => + (value ?? '').trim().toUpperCase(); + + const materialsMatch = (spoolMaterial: string, trayMaterial: string) => { + const normalizedSpool = normalizeMaterial(spoolMaterial); + const normalizedTray = normalizeMaterial(trayMaterial); + if (!normalizedSpool || !normalizedTray) return true; + if (normalizedTray.includes(normalizedSpool)) return true; + if (normalizedSpool.includes(normalizedTray)) return true; + return false; + }; + const handleAssign = () => { - if (selectedSpoolId) { - assignMutation.mutate(selectedSpoolId); + if (!selectedSpoolId) return; + const selectedSpool = spools?.find((spool: InventorySpool) => spool.id === selectedSpoolId); + if (selectedSpool && trayInfo?.type) { + const mismatch = !materialsMatch(selectedSpool.material, trayInfo.type); + if (mismatch) { + setPendingAssignId(selectedSpoolId); + setShowMismatchConfirm(true); + return; + } } + assignMutation.mutate(selectedSpoolId); + }; + + const handleConfirmMismatch = () => { + if (!pendingAssignId) return; + assignMutation.mutate(pendingAssignId); + setShowMismatchConfirm(false); + setPendingAssignId(null); }; return ( @@ -226,6 +258,28 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr )} + + {showMismatchConfirm && trayInfo && selectedSpoolId && ( + spool.id === selectedSpoolId)?.material ?? '', + trayMaterial: trayInfo.type, + location: trayInfo.location, + })} + confirmText={t('inventory.assignMismatchConfirm')} + confirmClassName="!text-black" + variant="warning" + isLoading={assignMutation.isPending} + onConfirm={handleConfirmMismatch} + onCancel={() => { + if (!assignMutation.isPending) { + setShowMismatchConfirm(false); + setPendingAssignId(null); + } + }} + /> + )} ); } diff --git a/frontend/src/components/ConfirmModal.tsx b/frontend/src/components/ConfirmModal.tsx index 93da3b54e..895b23619 100644 --- a/frontend/src/components/ConfirmModal.tsx +++ b/frontend/src/components/ConfirmModal.tsx @@ -11,6 +11,7 @@ interface ConfirmModalProps { cancelText?: string; cancelVariant?: 'primary' | 'secondary' | 'danger' | 'ghost'; cardClassName?: string; + confirmClassName?: string; variant?: 'danger' | 'warning' | 'default'; isLoading?: boolean; loadingText?: string; @@ -25,6 +26,7 @@ export function ConfirmModal({ cancelText, cancelVariant, cardClassName, + confirmClassName, variant = 'default', isLoading = false, loadingText, @@ -91,7 +93,7 @@ export function ConfirmModal({