Removed new Spoolbuddy frontend

This commit is contained in:
maziggy
2026-02-22 18:01:01 +01:00
parent 38af5df050
commit 9381edf0cb
27 changed files with 412 additions and 1987 deletions
@@ -1,41 +0,0 @@
interface AmsSlotCardProps {
material: string | null;
colorHex: string | null;
colorName: string | null;
remaining: number | null;
isEmpty: boolean;
onClick: () => void;
}
export function AmsSlotCard({ material, colorHex, remaining, isEmpty, onClick }: AmsSlotCardProps) {
const color = colorHex ? `#${colorHex.substring(0, 6)}` : '#808080';
return (
<button
onClick={onClick}
className="w-[120px] h-[120px] rounded-xl border border-bambu-dark-tertiary bg-bg-secondary flex flex-col items-center justify-center gap-2 active:bg-bambu-dark-tertiary transition-colors"
>
{isEmpty ? (
<span className="text-text-muted text-[13px]">Empty</span>
) : (
<>
<div
className="w-[48px] h-[48px] rounded-full border-2 border-bambu-dark-tertiary"
style={{ backgroundColor: color }}
/>
<span className="text-text-primary text-[13px] font-medium truncate max-w-[100px]">
{material || '---'}
</span>
{remaining !== null && remaining >= 0 && (
<div className="w-[80px] h-1.5 bg-bambu-dark-tertiary rounded-full overflow-hidden">
<div
className="h-full rounded-full bg-bambu-green"
style={{ width: `${Math.min(100, remaining)}%` }}
/>
</div>
)}
</>
)}
</button>
);
}
@@ -1,37 +0,0 @@
import { useTranslation } from 'react-i18next';
import { Button } from '../Button';
import { Scale, Edit, Cpu, History } from 'lucide-react';
interface QuickActionGridProps {
onUpdateWeight: () => void;
onEditSpool: () => void;
onAssignAms: () => void;
onViewHistory: () => void;
}
export function QuickActionGrid({ onUpdateWeight, onEditSpool, onAssignAms, onViewHistory }: QuickActionGridProps) {
const { t } = useTranslation();
const actions = [
{ icon: Cpu, label: t('spoolbuddy.actions.assignAms'), onClick: onAssignAms, variant: 'primary' as const },
{ icon: Scale, label: t('spoolbuddy.actions.updateWeight'), onClick: onUpdateWeight, variant: 'secondary' as const },
{ icon: Edit, label: t('spoolbuddy.actions.editSpool'), onClick: onEditSpool, variant: 'secondary' as const },
{ icon: History, label: t('spoolbuddy.actions.viewHistory'), onClick: onViewHistory, variant: 'secondary' as const },
];
return (
<div className="grid grid-cols-2 gap-3">
{actions.map(({ icon: Icon, label, onClick, variant }) => (
<Button
key={label}
variant={variant}
className="h-[64px] text-[14px] flex-col gap-1"
onClick={onClick}
>
<Icon size={20} />
<span>{label}</span>
</Button>
))}
</div>
);
}
@@ -1,30 +0,0 @@
import { Outlet, useSearchParams } from 'react-router-dom';
import { SpoolBuddyNav } from './SpoolBuddyNav';
import { SpoolBuddyStatusBar } from './SpoolBuddyStatusBar';
import { useSpoolBuddyState } from '../../hooks/useSpoolBuddyState';
export function SpoolBuddyLayout() {
const [searchParams] = useSearchParams();
const isKiosk = searchParams.get('kiosk') === '1' || window.innerHeight <= 600;
const state = useSpoolBuddyState();
return (
<div
className="w-[1024px] h-[600px] mx-auto flex flex-col bg-bg-primary overflow-hidden"
style={{ touchAction: 'manipulation' }}
>
<SpoolBuddyNav isKiosk={isKiosk} />
<main className="flex-1 overflow-hidden">
<Outlet context={state} />
</main>
<SpoolBuddyStatusBar
weightGrams={state.weight?.weight_grams ?? null}
stable={state.weight?.stable ?? false}
nfcOk={state.deviceOnline}
deviceOnline={state.deviceOnline}
/>
</div>
);
}
@@ -1,56 +0,0 @@
import { NavLink } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Scale, Cpu, Package, Printer, Settings, ArrowLeft } from 'lucide-react';
const navItems = [
{ to: '/spoolbuddy', icon: Scale, labelKey: 'spoolbuddy.nav.dashboard', end: true },
{ to: '/spoolbuddy/ams', icon: Cpu, labelKey: 'spoolbuddy.nav.ams' },
{ to: '/spoolbuddy/inventory', icon: Package, labelKey: 'spoolbuddy.nav.inventory' },
{ to: '/spoolbuddy/printers', icon: Printer, labelKey: 'spoolbuddy.nav.printers' },
{ to: '/spoolbuddy/settings', icon: Settings, labelKey: 'spoolbuddy.nav.settings' },
];
interface SpoolBuddyNavProps {
isKiosk: boolean;
}
export function SpoolBuddyNav({ isKiosk }: SpoolBuddyNavProps) {
const { t } = useTranslation();
return (
<nav className="h-[48px] bg-bg-secondary border-b border-bambu-dark-tertiary flex items-center px-2 gap-1">
{!isKiosk && (
<NavLink
to="/"
className="flex items-center gap-1 px-3 h-[40px] rounded-lg text-text-secondary hover:text-white hover:bg-bambu-dark-tertiary text-[13px]"
>
<ArrowLeft size={16} />
</NavLink>
)}
<div className="flex items-center gap-1 px-2">
<span className="text-bambu-green font-bold text-[15px]">SpoolBuddy</span>
</div>
<div className="flex items-center gap-1 flex-1">
{navItems.map(({ to, icon: Icon, labelKey, end }) => (
<NavLink
key={to}
to={to}
end={end}
className={({ isActive }) =>
`flex items-center gap-1.5 px-4 h-[40px] rounded-lg text-[13px] font-medium transition-colors ${
isActive
? 'bg-bambu-green text-white'
: 'text-text-secondary hover:text-white hover:bg-bambu-dark-tertiary'
}`
}
>
<Icon size={16} />
<span>{t(labelKey)}</span>
</NavLink>
))}
</div>
</nav>
);
}
@@ -1,42 +0,0 @@
import { useTranslation } from 'react-i18next';
import { Scale, Nfc } from 'lucide-react';
interface SpoolBuddyStatusBarProps {
weightGrams: number | null;
stable: boolean;
nfcOk: boolean;
deviceOnline: boolean;
}
export function SpoolBuddyStatusBar({ weightGrams, stable, nfcOk, deviceOnline }: SpoolBuddyStatusBarProps) {
const { t } = useTranslation();
return (
<div className="h-[40px] bg-bg-secondary border-t border-bambu-dark-tertiary flex items-center px-4 text-[13px]">
<div className="flex items-center gap-4 flex-1">
<div className="flex items-center gap-2">
<Scale size={14} className="text-text-secondary" />
<span className="text-text-primary font-mono">
{weightGrams !== null ? `${weightGrams.toFixed(1)}g` : '---'}
</span>
{weightGrams !== null && (
<span className={`w-2 h-2 rounded-full ${stable ? 'bg-green-500' : 'bg-yellow-500 animate-pulse'}`} />
)}
</div>
<div className="flex items-center gap-2">
<Nfc size={14} className="text-text-secondary" />
<span className={nfcOk ? 'text-green-500' : 'text-text-muted'}>
{nfcOk ? t('spoolbuddy.status.nfcReady') : t('spoolbuddy.status.nfcOff')}
</span>
</div>
{!deviceOnline && (
<span className="text-red-400 text-[12px]">{t('spoolbuddy.status.offline')}</span>
)}
</div>
<span className="text-text-muted text-[12px]">SpoolBuddy</span>
</div>
);
}
@@ -1,68 +0,0 @@
import { useTranslation } from 'react-i18next';
interface SpoolInfo {
id: number;
material: string;
subtype: string | null;
color_name: string | null;
rgba: string | null;
brand: string | null;
label_weight: number;
core_weight: number;
weight_used: number;
}
interface SpoolInfoCardProps {
spool: SpoolInfo;
}
export function SpoolInfoCard({ spool }: SpoolInfoCardProps) {
const { t } = useTranslation();
const remaining = Math.max(0, spool.label_weight - spool.weight_used);
const pct = spool.label_weight > 0 ? Math.round((remaining / spool.label_weight) * 100) : 0;
// Convert RRGGBBAA to CSS color
const color = spool.rgba
? `#${spool.rgba.substring(0, 6)}`
: '#808080';
const materialLabel = spool.subtype
? `${spool.material} ${spool.subtype}`
: spool.material;
return (
<div className="space-y-4">
<div className="flex items-center gap-4">
<div
className="w-[48px] h-[48px] rounded-full border-2 border-bambu-dark-tertiary flex-shrink-0"
style={{ backgroundColor: color }}
/>
<div className="flex-1 min-w-0">
<h3 className="text-[18px] font-semibold text-text-primary truncate">
{materialLabel}
</h3>
<p className="text-[14px] text-text-secondary truncate">
{[spool.brand, spool.color_name].filter(Boolean).join(' - ')}
</p>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between text-[14px]">
<span className="text-text-secondary">{t('spoolbuddy.spool.remaining')}</span>
<span className="text-text-primary font-medium">{remaining}g ({pct}%)</span>
</div>
<div className="w-full h-3 bg-bambu-dark-tertiary rounded-full overflow-hidden">
<div
className="h-full rounded-full transition-all duration-300"
style={{
width: `${pct}%`,
backgroundColor: pct > 20 ? 'var(--accent)' : pct > 5 ? '#f59e0b' : '#ef4444',
}}
/>
</div>
</div>
</div>
);
}
@@ -1,47 +0,0 @@
import { useTranslation } from 'react-i18next';
import { Button } from '../Button';
import { AlertTriangle } from 'lucide-react';
interface UnknownTagCardProps {
tagUid: string;
sak?: number;
tagType?: string;
onLinkExisting: () => void;
onCreateNew: () => void;
}
export function UnknownTagCard({ tagUid, sak, tagType, onLinkExisting, onCreateNew }: UnknownTagCardProps) {
const { t } = useTranslation();
return (
<div className="flex flex-col items-center justify-center h-full px-6">
<AlertTriangle size={48} className="text-yellow-500 mb-4" />
<h3 className="text-[24px] font-semibold text-text-primary mb-2">
{t('spoolbuddy.tag.unknownTitle')}
</h3>
<p className="text-[14px] text-text-secondary mb-1 font-mono">{tagUid}</p>
{tagType && (
<p className="text-[13px] text-text-muted mb-6">
{tagType}{sak !== undefined ? ` (SAK: 0x${sak.toString(16).toUpperCase().padStart(2, '0')})` : ''}
</p>
)}
<div className="w-full space-y-3 max-w-[320px]">
<Button
variant="primary"
className="w-full h-[64px] text-[16px]"
onClick={onLinkExisting}
>
{t('spoolbuddy.tag.linkExisting')}
</Button>
<Button
variant="secondary"
className="w-full h-[64px] text-[16px]"
onClick={onCreateNew}
>
{t('spoolbuddy.tag.createNew')}
</Button>
</div>
</div>
);
}
@@ -1,62 +0,0 @@
import { useTranslation } from 'react-i18next';
import { Button } from '../Button';
interface WeightDisplayProps {
weightGrams: number | null;
stable: boolean;
rawAdc: number | null;
onTare: () => void;
onCalibrate: () => void;
}
export function WeightDisplay({ weightGrams, stable, onTare, onCalibrate }: WeightDisplayProps) {
const { t } = useTranslation();
return (
<div className="flex flex-col items-center justify-center h-full px-4">
<div className="flex-1 flex flex-col items-center justify-center">
<span
className="text-[72px] font-bold text-text-primary leading-none"
style={{ fontVariantNumeric: 'tabular-nums' }}
>
{weightGrams !== null ? weightGrams.toFixed(1) : '---'}
</span>
<span className="text-[24px] text-text-secondary mt-1">g</span>
<div className="flex items-center gap-2 mt-3">
<span className={`w-3 h-3 rounded-full ${
weightGrams === null
? 'bg-bambu-gray'
: stable
? 'bg-green-500'
: 'bg-yellow-500 animate-pulse'
}`} />
<span className="text-[16px] text-text-secondary">
{weightGrams === null
? t('spoolbuddy.weight.noReading')
: stable
? t('spoolbuddy.weight.stable')
: t('spoolbuddy.weight.measuring')}
</span>
</div>
</div>
<div className="w-full flex gap-3 pb-4">
<Button
variant="secondary"
className="flex-1 h-[64px] text-[16px]"
onClick={onTare}
>
{t('spoolbuddy.weight.tare')}
</Button>
<Button
variant="secondary"
className="flex-1 h-[64px] text-[16px]"
onClick={onCalibrate}
>
{t('spoolbuddy.weight.calibrate')}
</Button>
</div>
</div>
);
}