-
-
{t('spoolbuddy.ams.printerDisconnected', 'Printer disconnected')}
+
+ {/* AMS slots */}
+
+ {showPrinterPicker && (
+
+ 2
+ {t('printModal.selectSlot')}
-
- ) : amsUnits.length === 0 && vtTrays.length === 0 ? (
-
-
-
-
{t('spoolbuddy.ams.noData', 'No AMS detected')}
-
{t('spoolbuddy.ams.connectAms', 'Connect an AMS to see filament slots')}
-
-
- ) : (
- <>
- {/* Regular AMS — 2-col grid */}
- {regularAms.length > 0 && (
-
- {regularAms.map((unit) => (
-
handleSlotClick(unit.id, trayId)}
- isDualNozzle={isDualNozzle}
- nozzleSide={getNozzleSide(unit.id)}
- thresholds={amsThresholds}
- fillOverrides={fillOverrides}
- />
- ))}
-
- )}
+ )}
- {/* Single-slot items (HT + External) */}
- {singleSlots.length > 0 && (
-
- {singleSlots.map(({ key, label, amsId, trayId, tray, isEmpty, nozzleSide, effectiveFill }) => {
- const color = trayColorToCSS(tray.tray_color);
- const isActive = !!currentAssignment &&
- currentAssignment.ams_id === amsId &&
- currentAssignment.tray_id === trayId;
- return (
-
handleSlotClick(amsId, trayId)}
- className={`bg-bambu-dark-secondary rounded-lg px-3 py-2 cursor-pointer hover:bg-bambu-dark-secondary/80 transition-all flex items-center gap-2 ${
- isActive ? 'ring-2 ring-bambu-green' : ''
- } ${isWaiting ? 'opacity-50 pointer-events-none' : ''}`}
- >
-
- {isEmpty ? (
-
- ) : (
-
- )}
-
-
-
- {label}
- {nozzleSide && }
-
-
- {isEmpty ? 'Empty' : tray.tray_type || '?'}
-
-
- {!isEmpty && effectiveFill != null && effectiveFill >= 0 && (
-
- )}
-
- );
- })}
+ {!targetPrinterId ? (
+
+
+
+
{t('printModal.selectPrinter')}
- )}
- >
- )}
+
+ ) : statusLoading ? (
+
+
+ {t('common.loading')}
+
+ ) : !isConnected ? (
+
+
{t('spoolbuddy.ams.printerDisconnected', 'Printer disconnected')}
+
+ ) : amsUnits.length === 0 && vtTrays.length === 0 ? (
+
+
+
+
{t('spoolbuddy.ams.noData', 'No AMS detected')}
+
{t('spoolbuddy.ams.connectAms', 'Connect an AMS to see filament slots')}
+
+
+ ) : (
+ <>
+ {/* Regular AMS — 2-col grid */}
+ {regularAms.length > 0 && (
+
+ {regularAms.map((unit) => (
+
handleSlotClick(unit.id, trayId)}
+ isDualNozzle={isDualNozzle}
+ nozzleSide={getNozzleSide(unit.id)}
+ thresholds={amsThresholds}
+ fillOverrides={fillOverrides}
+ />
+ ))}
+
+ )}
+
+ {/* Single-slot items (HT + External) */}
+ {singleSlots.length > 0 && (
+
+ {singleSlots.map(({ key, label, amsId, trayId, tray, isEmpty, nozzleSide, effectiveFill }) => {
+ const color = trayColorToCSS(tray.tray_color);
+ const isActive = selectedSlot
+ ? selectedSlot.amsId === amsId && selectedSlot.trayId === trayId
+ : !!currentAssignment && currentAssignment.ams_id === amsId && currentAssignment.tray_id === trayId;
+ return (
+
+ );
+ })}
+
+ )}
+ >
+ )}
+
{/* Footer */}
-
+
+ {requiresSlotConfirmation && (
+
+ {t('printModal.selectSlot')}:
+
+ {selectedSlotLabel || '—'}
+
+
+ )}
+ {requiresSlotConfirmation && (
+
+ )}
+
{showMismatchConfirm && mismatchDetails && (() => {
let message = '';
@@ -571,6 +798,7 @@ export function AssignToAmsModal({ isOpen, onClose, spool, printerId, spoolmanMo
message={message}
confirmText={t('inventory.assignMismatchConfirm')}
variant="warning"
+ overlayZIndex="z-[70]"
isLoading={configureMutation.isPending}
onConfirm={handleConfirmMismatch}
onCancel={() => {
diff --git a/frontend/src/i18n/locales/de.ts b/frontend/src/i18n/locales/de.ts
index 1a0ccf2b0..566fdde3c 100644
--- a/frontend/src/i18n/locales/de.ts
+++ b/frontend/src/i18n/locales/de.ts
@@ -343,6 +343,7 @@ export default {
// Printer card
readyToPrint: 'Druckbereit',
external: 'Extern',
+ ext: 'Extern',
extL: 'Ext-L',
extR: 'Ext-R',
deleteArchives: 'Druckarchive löschen',
diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts
index 3d6e4d4ef..850fd1d32 100644
--- a/frontend/src/i18n/locales/en.ts
+++ b/frontend/src/i18n/locales/en.ts
@@ -343,6 +343,7 @@ export default {
// Printer card
readyToPrint: 'Ready to print',
external: 'External',
+ ext: 'Ext',
extL: 'Ext-L',
extR: 'Ext-R',
deleteArchives: 'Delete print archives',
diff --git a/frontend/src/i18n/locales/es.ts b/frontend/src/i18n/locales/es.ts
index 6e1cfbace..f33794d30 100644
--- a/frontend/src/i18n/locales/es.ts
+++ b/frontend/src/i18n/locales/es.ts
@@ -343,6 +343,7 @@ export default {
// Printer card
readyToPrint: 'Lista para imprimir',
external: 'Externo',
+ ext: 'Externo',
extL: 'Ext-L',
extR: 'Ext-R',
deleteArchives: 'Eliminar archivos de impresión',
diff --git a/frontend/src/i18n/locales/fr.ts b/frontend/src/i18n/locales/fr.ts
index e0b4a3e6f..09c9bd370 100644
--- a/frontend/src/i18n/locales/fr.ts
+++ b/frontend/src/i18n/locales/fr.ts
@@ -343,6 +343,7 @@ export default {
// Printer card
readyToPrint: 'Prête à imprimer',
external: 'Externe',
+ ext: 'Externe',
extL: 'Ext-L',
extR: 'Ext-R',
deleteArchives: 'Supprimer les archives d\'impression',
diff --git a/frontend/src/i18n/locales/it.ts b/frontend/src/i18n/locales/it.ts
index 9267b2e5b..ad3d7d91c 100644
--- a/frontend/src/i18n/locales/it.ts
+++ b/frontend/src/i18n/locales/it.ts
@@ -343,6 +343,7 @@ export default {
// Printer card
readyToPrint: 'Pronta a stampare',
external: 'Esterna',
+ ext: 'Esterno',
extL: 'Ext-L',
extR: 'Ext-R',
deleteArchives: 'Elimina archivi stampa',
diff --git a/frontend/src/i18n/locales/ja.ts b/frontend/src/i18n/locales/ja.ts
index 70cc6579c..c187ca84e 100644
--- a/frontend/src/i18n/locales/ja.ts
+++ b/frontend/src/i18n/locales/ja.ts
@@ -342,6 +342,7 @@ export default {
// Printer card
readyToPrint: '印刷可能',
external: '外部',
+ ext: '外部',
extL: 'Ext-L',
extR: 'Ext-R',
deleteArchives: '印刷アーカイブを削除',
diff --git a/frontend/src/i18n/locales/ko.ts b/frontend/src/i18n/locales/ko.ts
index 499607d37..362df7b77 100644
--- a/frontend/src/i18n/locales/ko.ts
+++ b/frontend/src/i18n/locales/ko.ts
@@ -327,6 +327,7 @@ export default {
},
readyToPrint: '인쇄 준비 완료',
external: '외부',
+ ext: '외부',
extL: '외부-L',
extR: '외부-R',
deleteArchives: '인쇄 아카이브 삭제',
diff --git a/frontend/src/i18n/locales/nl.ts b/frontend/src/i18n/locales/nl.ts
index 823c8105f..d4ebc8c23 100644
--- a/frontend/src/i18n/locales/nl.ts
+++ b/frontend/src/i18n/locales/nl.ts
@@ -343,6 +343,7 @@ export default {
// Printer card
readyToPrint: 'Klaar om af te drukken',
external: 'Extern',
+ ext: 'Extern',
extL: 'Ext-L',
extR: 'Ext-R',
deleteArchives: 'Afdrukarchieven verwijderen',
diff --git a/frontend/src/i18n/locales/pt-BR.ts b/frontend/src/i18n/locales/pt-BR.ts
index efa78c259..f0739f726 100644
--- a/frontend/src/i18n/locales/pt-BR.ts
+++ b/frontend/src/i18n/locales/pt-BR.ts
@@ -343,6 +343,7 @@ export default {
// Printer card
readyToPrint: 'Pronto para imprimir',
external: 'Externo',
+ ext: 'Externo',
extL: 'Ext-L',
extR: 'Ext-R',
deleteArchives: 'Excluir arquivos de impressão',
diff --git a/frontend/src/i18n/locales/ru.ts b/frontend/src/i18n/locales/ru.ts
index df1e827f1..584dc5a56 100644
--- a/frontend/src/i18n/locales/ru.ts
+++ b/frontend/src/i18n/locales/ru.ts
@@ -332,6 +332,7 @@ export default {
},
readyToPrint: "Готов к печати",
external: "Внешняя подача",
+ ext: "Внешняя",
extL: "Внешняя, левое",
extR: "Внешняя, правое",
deleteArchives: "Удалить архивы печати",
diff --git a/frontend/src/i18n/locales/sv.ts b/frontend/src/i18n/locales/sv.ts
index a413d56ed..30e135557 100644
--- a/frontend/src/i18n/locales/sv.ts
+++ b/frontend/src/i18n/locales/sv.ts
@@ -343,6 +343,7 @@ export default {
// Printer card
readyToPrint: 'Redo att skriva ut',
external: 'Extern',
+ ext: 'Extern',
extL: 'Ext-V',
extR: 'Ext-H',
deleteArchives: 'Ta bort utskriftsarkiv',
diff --git a/frontend/src/i18n/locales/tr.ts b/frontend/src/i18n/locales/tr.ts
index 22bda1874..dc51c0b0b 100644
--- a/frontend/src/i18n/locales/tr.ts
+++ b/frontend/src/i18n/locales/tr.ts
@@ -343,6 +343,7 @@ export default {
// Yazıcı kartı
readyToPrint: 'Baskıya hazır',
external: 'Harici',
+ ext: 'Harici',
extL: 'Ext-L',
extR: 'Ext-R',
deleteArchives: 'Baskı arşivlerini sil',
diff --git a/frontend/src/i18n/locales/uk.ts b/frontend/src/i18n/locales/uk.ts
index 92a2f27e9..48c92883d 100644
--- a/frontend/src/i18n/locales/uk.ts
+++ b/frontend/src/i18n/locales/uk.ts
@@ -343,6 +343,7 @@ export default {
// Printer card
readyToPrint: "Готовий до друку",
external: "Зовнішній",
+ ext: "Зовнішня",
extL: "Ext-L",
extR: "Ext-R",
deleteArchives: "Видалити архіви друку",
diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts
index 193f4c5ad..db1ed2038 100644
--- a/frontend/src/i18n/locales/zh-CN.ts
+++ b/frontend/src/i18n/locales/zh-CN.ts
@@ -343,6 +343,7 @@ export default {
// Printer card
readyToPrint: '准备打印',
external: '外部',
+ ext: '外置',
extL: '外置左',
extR: '外置右',
deleteArchives: '删除打印归档',
diff --git a/frontend/src/i18n/locales/zh-TW.ts b/frontend/src/i18n/locales/zh-TW.ts
index 13e61890d..97133d382 100644
--- a/frontend/src/i18n/locales/zh-TW.ts
+++ b/frontend/src/i18n/locales/zh-TW.ts
@@ -343,6 +343,7 @@ export default {
// Printer card
readyToPrint: '準備列印',
external: '外部',
+ ext: '外接',
extL: '外接左',
extR: '外接右',
deleteArchives: '刪除列印歸檔',
diff --git a/frontend/src/pages/spoolbuddy/SpoolBuddyDashboard.tsx b/frontend/src/pages/spoolbuddy/SpoolBuddyDashboard.tsx
index 1f9bf53e5..37bf1fe5a 100644
--- a/frontend/src/pages/spoolbuddy/SpoolBuddyDashboard.tsx
+++ b/frontend/src/pages/spoolbuddy/SpoolBuddyDashboard.tsx
@@ -694,6 +694,7 @@ export function SpoolBuddyDashboard() {
onClose={() => setShowAssignAmsModal(false)}
spool={effectiveModalSpool}
printerId={selectedPrinterId}
+ variant="kiosk"
spoolmanMode={spoolmanMode}
/>
)}
diff --git a/frontend/src/pages/spoolbuddy/SpoolBuddyInventoryPage.tsx b/frontend/src/pages/spoolbuddy/SpoolBuddyInventoryPage.tsx
index 3fbd228d5..4b65fd2a4 100644
--- a/frontend/src/pages/spoolbuddy/SpoolBuddyInventoryPage.tsx
+++ b/frontend/src/pages/spoolbuddy/SpoolBuddyInventoryPage.tsx
@@ -291,6 +291,7 @@ export function SpoolBuddyInventoryPage() {
onClose={() => setShowAssignAmsModal(false)}
spool={liveSpool}
printerId={selectedPrinterId}
+ variant="kiosk"
spoolmanMode={spoolmanMode}
/>
>
diff --git a/frontend/src/utils/inventorySearch.ts b/frontend/src/utils/inventorySearch.ts
index fa7165e52..a797bac1c 100644
--- a/frontend/src/utils/inventorySearch.ts
+++ b/frontend/src/utils/inventorySearch.ts
@@ -6,8 +6,17 @@ import { resolveSpoolColorName } from './colors';
* Case-insensitive. Empty query always returns true.
*/
export function spoolMatchesQuery(spool: InventorySpool, query: string): boolean {
- if (!query) return true;
- const q = query.toLowerCase();
+ const q = query.trim().toLowerCase();
+ if (!q) return true;
+
+ // A hash-prefixed number is an explicit spool ID lookup. Keep plain numeric
+ // searches backwards-compatible ("3" can still match #3, #13, #30, ...),
+ // while "#3" selects only the physical spool whose label says #3.
+ const exactIdQuery = q.match(/^#(\d+)$/);
+ if (exactIdQuery) {
+ return spool.id === Number(exactIdQuery[1]);
+ }
+
return (
String(spool.id).includes(q) ||
spool.material.toLowerCase().includes(q) ||