mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 03:54:17 +02:00
Removed new Spoolbuddy frontend
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user