From 5cb1ca3ae218c9d25210c1112798bc110662064b Mon Sep 17 00:00:00 2001 From: maziggy Date: Mon, 23 Feb 2026 13:02:46 +0100 Subject: [PATCH] Spoolbuddy frontend work --- .../src/components/spoolbuddy/AmsUnitCard.tsx | 47 +- .../components/spoolbuddy/LinkSpoolModal.tsx | 144 +++ .../spoolbuddy/SpoolBuddyBottomNav.tsx | 6 +- .../spoolbuddy/SpoolBuddyLayout.tsx | 17 +- .../spoolbuddy/SpoolBuddyStatusBar.tsx | 46 + .../spoolbuddy/SpoolBuddyTopBar.tsx | 38 +- .../src/components/spoolbuddy/SpoolIcon.tsx | 27 + .../components/spoolbuddy/SpoolInfoCard.tsx | 279 ++++-- frontend/src/hooks/useSpoolBuddyState.ts | 2 + frontend/src/i18n/locales/de.ts | 16 + frontend/src/i18n/locales/en.ts | 16 + frontend/src/i18n/locales/fr.ts | 16 + frontend/src/i18n/locales/it.ts | 16 + frontend/src/i18n/locales/ja.ts | 16 + frontend/src/i18n/locales/pt-BR.ts | 16 + .../pages/spoolbuddy/SpoolBuddyAmsPage.tsx | 72 +- .../pages/spoolbuddy/SpoolBuddyDashboard.tsx | 351 +++++-- .../spoolbuddy/SpoolBuddyInventoryPage.tsx | 30 +- static/assets/index-BU6YJVpb.css | 1 + static/assets/index-COFMpymr.css | 1 - .../{index-DLCtwgng.js => index-VMGdyBYb.js} | 926 +++++++++--------- static/index.html | 4 +- 22 files changed, 1379 insertions(+), 708 deletions(-) create mode 100644 frontend/src/components/spoolbuddy/LinkSpoolModal.tsx create mode 100644 frontend/src/components/spoolbuddy/SpoolBuddyStatusBar.tsx create mode 100644 frontend/src/components/spoolbuddy/SpoolIcon.tsx create mode 100644 static/assets/index-BU6YJVpb.css delete mode 100644 static/assets/index-COFMpymr.css rename static/assets/{index-DLCtwgng.js => index-VMGdyBYb.js} (58%) diff --git a/frontend/src/components/spoolbuddy/AmsUnitCard.tsx b/frontend/src/components/spoolbuddy/AmsUnitCard.tsx index 7cade8c84..818fa5d59 100644 --- a/frontend/src/components/spoolbuddy/AmsUnitCard.tsx +++ b/frontend/src/components/spoolbuddy/AmsUnitCard.tsx @@ -1,4 +1,3 @@ -import { useTranslation } from 'react-i18next'; import type { AMSUnit, AMSTray } from '../../api/client'; function trayColorToCSS(color: string | null): string { @@ -10,6 +9,18 @@ function isTrayEmpty(tray: AMSTray): boolean { return !tray.tray_type || tray.tray_type === ''; } +function getAmsName(id: number): string { + if (id <= 3) return `AMS ${String.fromCharCode(65 + id)}`; + if (id >= 128 && id <= 135) return `AMS HT ${String.fromCharCode(65 + id - 128)}`; + return `AMS ${id}`; +} + +function formatHumidity(value: number | null): string { + if (value === null || value === undefined) return '-'; + if (value > 5) return `${value}%`; + return `Level ${value}`; +} + interface SpoolSlotProps { tray: AMSTray; slotIndex: number; @@ -21,47 +32,47 @@ function SpoolSlot({ tray, slotIndex, isActive }: SpoolSlotProps) { const color = trayColorToCSS(tray.tray_color); return ( -
+
{/* Spool visualization */}
{isEmpty ? ( -
-
+
+
) : ( - - + + )} {isActive && ( -
+
)}
{/* Material type */} - + {isEmpty ? 'Empty' : tray.tray_type || 'Unknown'} {/* Fill level bar */} {!isEmpty && tray.remain !== null && tray.remain !== undefined && tray.remain >= 0 && ( -
+
50 ? '#22c55e' : tray.remain > 15 ? '#f59e0b' : '#ef4444', + backgroundColor: tray.remain > 50 ? '#22c55e' : tray.remain > 20 ? '#f59e0b' : '#ef4444', }} />
)} {/* Slot number */} - {slotIndex + 1} + {slotIndex + 1}
); } @@ -72,29 +83,21 @@ interface AmsUnitCardProps { } export function AmsUnitCard({ unit, activeSlot }: AmsUnitCardProps) { - const { t } = useTranslation(); const trays = unit.tray || []; const isHt = unit.is_ams_ht; - - const getAmsName = (id: number): string => { - if (id <= 3) return `AMS ${String.fromCharCode(65 + id)}`; - if (id >= 128 && id <= 135) return `AMS HT ${String.fromCharCode(65 + id - 128)}`; - return `AMS ${id}`; - }; - const slotCount = isHt ? 1 : 4; return ( -
+
{/* Header */}
{getAmsName(unit.id)} {unit.humidity !== null && unit.humidity !== undefined && ( -
+
- {unit.humidity > 5 ? `${unit.humidity}%` : `${t('spoolbuddy.ams.level', 'Level')} ${unit.humidity}`} + {formatHumidity(unit.humidity)}
)}
diff --git a/frontend/src/components/spoolbuddy/LinkSpoolModal.tsx b/frontend/src/components/spoolbuddy/LinkSpoolModal.tsx new file mode 100644 index 000000000..d9e177cfa --- /dev/null +++ b/frontend/src/components/spoolbuddy/LinkSpoolModal.tsx @@ -0,0 +1,144 @@ +import { useState, useEffect, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { X } from 'lucide-react'; +import type { InventorySpool } from '../../api/client'; +import { SpoolIcon } from './SpoolIcon'; + +interface LinkSpoolModalProps { + isOpen: boolean; + onClose: () => void; + tagId: string; + untaggedSpools: InventorySpool[]; + onLink: (spool: InventorySpool) => void; +} + +export function LinkSpoolModal({ + isOpen, + onClose, + tagId, + untaggedSpools, + onLink, +}: LinkSpoolModalProps) { + const { t } = useTranslation(); + const [selectedSpool, setSelectedSpool] = useState(null); + + const handleClose = useCallback(() => { + setSelectedSpool(null); + onClose(); + }, [onClose]); + + // Handle escape key + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === 'Escape') { + handleClose(); + } + }, [handleClose]); + + useEffect(() => { + if (isOpen) { + document.addEventListener('keydown', handleKeyDown); + document.body.style.overflow = 'hidden'; + } + return () => { + document.removeEventListener('keydown', handleKeyDown); + document.body.style.overflow = ''; + }; + }, [isOpen, handleKeyDown]); + + if (!isOpen) return null; + + const handleConfirm = () => { + if (selectedSpool) { + onLink(selectedSpool); + setSelectedSpool(null); + } + }; + + return ( +
+
e.stopPropagation()} + > + {/* Header */} +
+
+

+ {t('spoolbuddy.dashboard.linkTagTitle', 'Link Tag to Spool')} +

+

{tagId}

+
+ +
+ + {/* Content */} +
+

+ {t('spoolbuddy.dashboard.selectSpool', 'Select a spool to link this tag to:')} +

+ + {untaggedSpools.length === 0 ? ( +
+ {t('spoolbuddy.dashboard.noUntagged', 'No spools without tags found')} +
+ ) : ( +
+ {untaggedSpools.map((spool) => ( + + ))} +
+ )} +
+ + {/* Footer */} +
+ + +
+
+
+ ); +} diff --git a/frontend/src/components/spoolbuddy/SpoolBuddyBottomNav.tsx b/frontend/src/components/spoolbuddy/SpoolBuddyBottomNav.tsx index 74205f705..80701f82b 100644 --- a/frontend/src/components/spoolbuddy/SpoolBuddyBottomNav.tsx +++ b/frontend/src/components/spoolbuddy/SpoolBuddyBottomNav.tsx @@ -49,7 +49,7 @@ export function SpoolBuddyBottomNav() { const { t } = useTranslation(); return ( -