import { useState, useEffect } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Plus, Wifi, WifiOff, Thermometer, Clock, MoreVertical, Trash2, RefreshCw, Box, HardDrive, AlertTriangle, Terminal, Power, PowerOff, Zap, } from 'lucide-react'; import { api } from '../api/client'; import type { Printer, PrinterCreate } from '../api/client'; import { Card, CardContent } from '../components/Card'; import { Button } from '../components/Button'; import { ConfirmModal } from '../components/ConfirmModal'; import { FileManagerModal } from '../components/FileManagerModal'; import { MQTTDebugModal } from '../components/MQTTDebugModal'; import { HMSErrorModal } from '../components/HMSErrorModal'; function formatTime(seconds: number): string { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`; } function CoverImage({ url, printName }: { url: string | null; printName?: string }) { const [loaded, setLoaded] = useState(false); const [error, setError] = useState(false); const [showOverlay, setShowOverlay] = useState(false); return ( <>
url && loaded && setShowOverlay(true)} > {url && !error ? ( <> Print preview setLoaded(true)} onError={() => setError(true)} /> {!loaded && } ) : ( )}
{/* Cover Image Overlay */} {showOverlay && url && (
setShowOverlay(false)} >
Print preview {printName && (

{printName}

)}
)} ); } function PrinterCard({ printer, hideIfDisconnected }: { printer: Printer; hideIfDisconnected?: boolean }) { const queryClient = useQueryClient(); const [showMenu, setShowMenu] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showFileManager, setShowFileManager] = useState(false); const [showMQTTDebug, setShowMQTTDebug] = useState(false); const [showPowerOnConfirm, setShowPowerOnConfirm] = useState(false); const [showPowerOffConfirm, setShowPowerOffConfirm] = useState(false); const [showHMSModal, setShowHMSModal] = useState(false); const { data: status } = useQuery({ queryKey: ['printerStatus', printer.id], queryFn: () => api.getPrinterStatus(printer.id), refetchInterval: 30000, // Fallback polling, WebSocket handles real-time }); // Fetch smart plug for this printer const { data: smartPlug } = useQuery({ queryKey: ['smartPlugByPrinter', printer.id], queryFn: () => api.getSmartPlugByPrinter(printer.id), }); // Fetch smart plug status if plug exists const { data: plugStatus } = useQuery({ queryKey: ['smartPlugStatus', smartPlug?.id], queryFn: () => smartPlug ? api.getSmartPlugStatus(smartPlug.id) : null, enabled: !!smartPlug, refetchInterval: 30000, }); // Determine if this card should be hidden const shouldHide = hideIfDisconnected && status && !status.connected; const deleteMutation = useMutation({ mutationFn: () => api.deletePrinter(printer.id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['printers'] }); }, }); const connectMutation = useMutation({ mutationFn: () => api.connectPrinter(printer.id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['printerStatus', printer.id] }); }, }); // Smart plug control mutations const powerControlMutation = useMutation({ mutationFn: (action: 'on' | 'off') => smartPlug ? api.controlSmartPlug(smartPlug.id, action) : Promise.reject('No plug'), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['smartPlugStatus', smartPlug?.id] }); }, }); const toggleAutoOffMutation = useMutation({ mutationFn: (enabled: boolean) => smartPlug ? api.updateSmartPlug(smartPlug.id, { auto_off: enabled }) : Promise.reject('No plug'), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['smartPlugByPrinter', printer.id] }); }, }); if (shouldHide) { return null; } return ( {/* Header */}

{printer.name}

{printer.model || 'Unknown Model'}

{status?.connected ? ( ) : ( )} {status?.connected ? 'Connected' : 'Offline'} {/* HMS Status Indicator */} {status?.connected && ( )}
{showMenu && (
)}
{/* Delete Confirmation */} {showDeleteConfirm && ( { deleteMutation.mutate(); setShowDeleteConfirm(false); }} onCancel={() => setShowDeleteConfirm(false)} /> )} {/* Status */} {status?.connected && ( <> {/* Current Print or Idle Placeholder */}
{/* Cover Image */} {/* Print Info */}
{status.current_print && status.state === 'RUNNING' ? ( <>

Printing

{status.subtask_name || status.current_print}

{Math.round(status.progress || 0)}%
{status.remaining_time != null && status.remaining_time > 0 && ( {formatTime(status.remaining_time * 60)} )} {status.layer_num != null && status.total_layers != null && status.total_layers > 0 && ( Layer {status.layer_num}/{status.total_layers} )}
) : ( <>

Status

{status.state?.toLowerCase() || 'Idle'}

Ready to print

)}
{/* Temperatures */} {status.temperatures && (

Nozzle

{Math.round(status.temperatures.nozzle || 0)}°C

Bed

{Math.round(status.temperatures.bed || 0)}°C

{status.temperatures.chamber !== undefined && (

Chamber

{Math.round(status.temperatures.chamber || 0)}°C

)}
)} )} {/* Smart Plug Controls */} {smartPlug && (
{/* Plug name and status */}
{smartPlug.name} {plugStatus && ( {plugStatus.state || '?'} )}
{/* Spacer */}
{/* Power buttons */}
{/* Auto-off toggle */}
Auto-off
)} {/* Connection Info & Actions */}

{printer.ip_address}

{printer.serial_number}

{/* File Manager Modal */} {showFileManager && ( setShowFileManager(false)} /> )} {/* MQTT Debug Modal */} {showMQTTDebug && ( setShowMQTTDebug(false)} /> )} {/* Power On Confirmation */} {showPowerOnConfirm && smartPlug && ( { powerControlMutation.mutate('on'); setShowPowerOnConfirm(false); }} onCancel={() => setShowPowerOnConfirm(false)} /> )} {/* Power Off Confirmation */} {showPowerOffConfirm && smartPlug && ( { powerControlMutation.mutate('off'); setShowPowerOffConfirm(false); }} onCancel={() => setShowPowerOffConfirm(false)} /> )} {/* HMS Error Modal */} {showHMSModal && ( setShowHMSModal(false)} /> )} ); } function AddPrinterModal({ onClose, onAdd, }: { onClose: () => void; onAdd: (data: PrinterCreate) => void; }) { const [form, setForm] = useState({ name: '', serial_number: '', ip_address: '', access_code: '', model: '', auto_archive: true, }); // Close on Escape key useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [onClose]); return (
e.stopPropagation()}>

Add Printer

{ e.preventDefault(); onAdd(form); }} className="space-y-4" >
setForm({ ...form, name: e.target.value })} placeholder="My Printer" />
setForm({ ...form, ip_address: e.target.value })} placeholder="192.168.1.100" />
setForm({ ...form, serial_number: e.target.value })} placeholder="01P00A000000000" />
setForm({ ...form, access_code: e.target.value })} placeholder="From printer settings" />
setForm({ ...form, auto_archive: e.target.checked })} className="rounded border-bambu-dark-tertiary bg-bambu-dark text-bambu-green focus:ring-bambu-green" />
); } export function PrintersPage() { const [showAddModal, setShowAddModal] = useState(false); const [hideDisconnected, setHideDisconnected] = useState(() => { return localStorage.getItem('hideDisconnectedPrinters') === 'true'; }); const queryClient = useQueryClient(); const { data: printers, isLoading } = useQuery({ queryKey: ['printers'], queryFn: api.getPrinters, }); const addMutation = useMutation({ mutationFn: api.createPrinter, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['printers'] }); setShowAddModal(false); }, }); const toggleHideDisconnected = () => { const newValue = !hideDisconnected; setHideDisconnected(newValue); localStorage.setItem('hideDisconnectedPrinters', String(newValue)); }; return (

Printers

Manage your Bambu Lab printers

{isLoading ? (
Loading printers...
) : printers?.length === 0 ? (

No printers configured yet

) : (
{printers?.map((printer) => ( ))}
)} {showAddModal && ( setShowAddModal(false)} onAdd={(data) => addMutation.mutate(data)} /> )}
); }