From 51d3a922637344eb4e46a4785d042fd3344a582b Mon Sep 17 00:00:00 2001 From: Matteo Parenti Date: Fri, 13 Feb 2026 17:01:16 +0100 Subject: [PATCH 1/4] 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({ + {/* Disable Filament Warnings toggle - applies to both modes */} +
+
+

{t('settings.disableFilamentWarnings')}

+

+ {t('settings.disableFilamentWarningsDesc')} +

+
+ +
+ {/* Built-in Inventory details */} {!localEnabled && (
diff --git a/frontend/src/i18n/locales/de.ts b/frontend/src/i18n/locales/de.ts index 2049b7fdd..6e3eb73e2 100644 --- a/frontend/src/i18n/locales/de.ts +++ b/frontend/src/i18n/locales/de.ts @@ -1162,6 +1162,8 @@ export default { // Filament Tracking Mode filamentTracking: 'Filament-Verfolgung', filamentTrackingDesc: 'Wählen Sie, wie Sie Ihre Filamentspulen verfolgen möchten. Sie können das integrierte Inventar oder einen externen Spoolman-Server verwenden.', + disableFilamentWarnings: 'Filament-Warnungen deaktivieren', + disableFilamentWarningsDesc: 'Keine Warnungen über unzureichendes Filament beim Drucken oder Einreihen anzeigen', trackingModeBuiltIn: 'Integriertes Inventar', trackingModeBuiltInDesc: 'RFID-Erkennung und Verbrauchserfassung inklusive', trackingModeSpoolmanDesc: 'Externer Filament-Management-Server', @@ -2649,6 +2651,10 @@ export default { rightNozzle: 'R', leftNozzleTooltip: 'Linke Düse', rightNozzleTooltip: 'Rechte Düse', + insufficientFilamentTitle: 'Not enough filament', + insufficientFilamentMessage: 'Some assigned spools have less filament remaining than this print needs:', + insufficientFilamentLine: '{{printer}} - {{slot}}: needs {{required}}g, remaining {{remaining}}g', + printAnyway: 'Print anyway', }, // Backup diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 8139c3b69..1964383df 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -1162,6 +1162,8 @@ export default { // Filament Tracking Mode filamentTracking: 'Filament Tracking', filamentTrackingDesc: 'Choose how to track your filament spools. You can use the built-in inventory or connect an external Spoolman server.', + disableFilamentWarnings: 'Disable filament warnings', + disableFilamentWarningsDesc: 'Don\'t show warnings about insufficient filament when printing or queueing', trackingModeBuiltIn: 'Built-in Inventory', trackingModeBuiltInDesc: 'RFID auto-matching and usage tracking included', trackingModeSpoolmanDesc: 'External filament management server', @@ -2653,6 +2655,10 @@ export default { rightNozzle: 'R', leftNozzleTooltip: 'Left nozzle', rightNozzleTooltip: 'Right nozzle', + insufficientFilamentTitle: 'Not enough filament', + insufficientFilamentMessage: 'Some assigned spools have less filament remaining than this print needs:', + insufficientFilamentLine: '{{printer}} - {{slot}}: needs {{required}}g, remaining {{remaining}}g', + printAnyway: 'Print anyway', }, // Backup diff --git a/frontend/src/i18n/locales/fr.ts b/frontend/src/i18n/locales/fr.ts index 64bcefcef..d88408e6d 100644 --- a/frontend/src/i18n/locales/fr.ts +++ b/frontend/src/i18n/locales/fr.ts @@ -1158,6 +1158,8 @@ export default { // Filament Tracking Mode filamentTracking: 'Suivi de Filament', filamentTrackingDesc: 'Choisissez comment suivre vos bobines. Utilisez l\'inventaire intégré ou connectez un serveur Spoolman.', + disableFilamentWarnings: 'Désactiver les avertissements de filament', + disableFilamentWarningsDesc: 'Ne pas afficher les avertissements de filament insuffisant lors de l\'impression ou de la mise en file d\'attente', trackingModeBuiltIn: 'Inventaire Intégré', trackingModeBuiltInDesc: 'Correspondance RFID et suivi de consommation inclus', trackingModeSpoolmanDesc: 'Serveur de gestion externe', @@ -2649,6 +2651,10 @@ export default { rightNozzle: 'D', leftNozzleTooltip: 'Buse gauche', rightNozzleTooltip: 'Buse droite', + insufficientFilamentTitle: 'Not enough filament', + insufficientFilamentMessage: 'Some assigned spools have less filament remaining than this print needs:', + insufficientFilamentLine: '{{printer}} - {{slot}}: needs {{required}}g, remaining {{remaining}}g', + printAnyway: 'Print anyway', }, // Backup diff --git a/frontend/src/i18n/locales/it.ts b/frontend/src/i18n/locales/it.ts index 77e150cb0..1c308dfb1 100644 --- a/frontend/src/i18n/locales/it.ts +++ b/frontend/src/i18n/locales/it.ts @@ -1092,6 +1092,17 @@ export default { turnOn: 'Accendi', turnOff: 'Spegni', }, + // Filament Tracking Mode + filamentTracking: 'Tracciamento filamento', + filamentTrackingDesc: 'Scegli come tracciare le tue bobine di filamento. Puoi utilizzare l\'inventario integrato o collegare un server Spoolman esterno.', + disableFilamentWarnings: 'Disabilita avvisi filamento', + disableFilamentWarningsDesc: 'Non mostrare avvisi per filamento insufficiente durante la stampa o l\'accodamento', + trackingModeBuiltIn: 'Inventario integrato', + trackingModeBuiltInDesc: 'Corrispondenza RFID automatica e tracciamento utilizzo inclusi', + trackingModeSpoolmanDesc: 'Server di gestione filamenti esterno', + builtInFeatureRfid: 'Rileva automaticamente le bobine RFID Bambu Lab nell\'AMS', + builtInFeatureUsage: 'Traccia il consumo di filamento per stampa', + builtInFeatureCatalog: 'Gestisci bobine, colori e profili K-factor', // Spoolman spoolmanEnabled: 'Abilita integrazione Spoolman', spoolmanUrl: 'URL Spoolman', @@ -2379,6 +2390,10 @@ export default { rightNozzle: 'R', leftNozzleTooltip: 'Ugello sinistro', rightNozzleTooltip: 'Ugello destro', + insufficientFilamentTitle: 'Filamento insufficiente', + insufficientFilamentMessage: 'Alcune bobine assegnate hanno meno filamento rimanente di quanto necessario per questa stampa:', + insufficientFilamentLine: '{{printer}} - {{slot}}: necessita di {{required}}g, rimanenti {{remaining}}g', + printAnyway: 'Stampa comunque', }, // Backup diff --git a/frontend/src/i18n/locales/ja.ts b/frontend/src/i18n/locales/ja.ts index c3dca2061..4a8eaa50c 100644 --- a/frontend/src/i18n/locales/ja.ts +++ b/frontend/src/i18n/locales/ja.ts @@ -1402,6 +1402,8 @@ export default { // フィラメント追跡モード filamentTracking: 'フィラメント追跡', filamentTrackingDesc: 'フィラメントスプールの追跡方法を選択してください。内蔵インベントリまたは外部Spoolmanサーバーを使用できます。', + disableFilamentWarnings: 'フィラメント警告を無効化', + disableFilamentWarningsDesc: '印刷またはキュー追加時にフィラメント不足の警告を表示しない', trackingModeBuiltIn: '内蔵インベントリ', trackingModeBuiltInDesc: 'RFID自動検出と使用量追跡を含む', trackingModeSpoolmanDesc: '外部フィラメント管理サーバー', @@ -2569,6 +2571,10 @@ export default { rightNozzle: 'R', leftNozzleTooltip: '左ノズル', rightNozzleTooltip: '右ノズル', + insufficientFilamentTitle: 'Not enough filament', + insufficientFilamentMessage: 'Some assigned spools have less filament remaining than this print needs:', + insufficientFilamentLine: '{{printer}} - {{slot}}: needs {{required}}g, remaining {{remaining}}g', + printAnyway: 'Print anyway', }, backup: { restoreBackup: 'バックアップの復元', From 33c4bd8f8450603b954af4839fff104520ff15a9 Mon Sep 17 00:00:00 2001 From: Matteo Parenti Date: Sat, 14 Feb 2026 09:00:17 +0100 Subject: [PATCH 3/4] Fix spool assignments query, mismatch dialog premature closing, confirm modal black text, add missing de.ts, fr.ts, and ja.ts locales --- frontend/src/components/AssignSpoolModal.tsx | 3 --- frontend/src/components/ConfirmModal.tsx | 6 ++---- frontend/src/components/PrintModal/index.tsx | 2 +- frontend/src/i18n/locales/de.ts | 8 ++++---- frontend/src/i18n/locales/fr.ts | 8 ++++---- frontend/src/i18n/locales/ja.ts | 8 ++++---- 6 files changed, 15 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/AssignSpoolModal.tsx b/frontend/src/components/AssignSpoolModal.tsx index 1d1c9c968..54c795ae5 100644 --- a/frontend/src/components/AssignSpoolModal.tsx +++ b/frontend/src/components/AssignSpoolModal.tsx @@ -118,8 +118,6 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr const handleConfirmMismatch = () => { if (!pendingAssignId) return; assignMutation.mutate(pendingAssignId); - setShowMismatchConfirm(false); - setPendingAssignId(null); }; return ( @@ -268,7 +266,6 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr location: trayInfo.location, })} confirmText={t('inventory.assignMismatchConfirm')} - confirmClassName="!text-black" variant="warning" isLoading={assignMutation.isPending} onConfirm={handleConfirmMismatch} diff --git a/frontend/src/components/ConfirmModal.tsx b/frontend/src/components/ConfirmModal.tsx index ef4c20254..a9f697f48 100644 --- a/frontend/src/components/ConfirmModal.tsx +++ b/frontend/src/components/ConfirmModal.tsx @@ -11,7 +11,6 @@ interface ConfirmModalProps { cancelText?: string; cancelVariant?: 'primary' | 'secondary' | 'danger' | 'ghost'; cardClassName?: string; - confirmClassName?: string; variant?: 'danger' | 'warning' | 'default'; isLoading?: boolean; loadingText?: string; @@ -26,7 +25,6 @@ export function ConfirmModal({ cancelText, cancelVariant, cardClassName, - confirmClassName, variant = 'default', isLoading = false, loadingText, @@ -53,7 +51,7 @@ export function ConfirmModal({ }, warning: { icon: 'text-yellow-400', - button: 'bg-yellow-500 hover:bg-yellow-600', + button: 'bg-yellow-500 hover:bg-yellow-600 text-black', }, default: { icon: 'text-bambu-green', @@ -93,7 +91,7 @@ export function ConfirmModal({