Files
bambuddy/frontend/src/components/PrinterInfoModal.tsx
T
maziggy 121b3d53df fix: remove incorrect ethernet badge on printer cards
home_flag bit 18 was incorrectly interpreted as "wired/ethernet
  connection", causing the ethernet badge to always show — even on
  printers without an ethernet port (e.g. A1, P1S). This hid the
  WiFi signal indicator entirely.

  Removed the wired_network field and ethernet badge UI. The WiFi
  signal badge now shows correctly whenever the printer reports
  signal strength.
2026-03-09 12:16:22 +01:00

247 lines
7.2 KiB
TypeScript

import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { X, Copy, Check, Signal } from 'lucide-react';
import { Card, CardContent } from './Card';
import { formatDateOnly } from '../utils/date';
import { getPrinterImage, getWifiStrength } from '../utils/printer';
import type { Printer, PrinterStatus } from '../api/client';
interface PrinterInfoModalProps {
printer: Printer;
status?: PrinterStatus;
totalPrintHours?: number;
onClose: () => void;
}
function CopyButton({ value }: { value: string }) {
const { t } = useTranslation();
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(value);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch {
// Clipboard may not be available in non-secure contexts
}
};
return (
<button
onClick={handleCopy}
className="ml-2 p-1 rounded hover:bg-bambu-dark-tertiary text-bambu-gray hover:text-white transition-colors"
title={copied ? t('printers.copied') : t('printers.copyToClipboard')}
>
{copied ? <Check className="w-3.5 h-3.5 text-bambu-green" /> : <Copy className="w-3.5 h-3.5" />}
</button>
);
}
export function PrinterInfoModal({ printer, status, totalPrintHours, onClose }: PrinterInfoModalProps) {
const { t } = useTranslation();
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', handleKey);
return () => window.removeEventListener('keydown', handleKey);
}, [onClose]);
const rows: { label: string; value: React.ReactNode }[] = [];
// Model
rows.push({
label: t('printers.model'),
value: printer.model ?? '—',
});
// Connection Status
rows.push({
label: t('common.status'),
value: (
<span className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium ${
status?.connected
? 'bg-bambu-green/20 text-bambu-green'
: 'bg-red-500/20 text-red-400'
}`}>
<span className={`w-1.5 h-1.5 rounded-full ${status?.connected ? 'bg-bambu-green' : 'bg-red-400'}`} />
{status?.connected ? t('printers.status.available') : t('printers.status.offline')}
</span>
),
});
// State
if (status?.state) {
const stateMap: Record<string, string> = {
IDLE: 'printers.status.idle',
RUNNING: 'printers.status.printing',
PAUSE: 'printers.status.paused',
FINISH: 'printers.status.finished',
FAILED: 'printers.status.error',
};
rows.push({
label: t('printers.state'),
value: t(stateMap[status.state] ?? 'printers.status.unknown'),
});
}
// IP Address
rows.push({
label: t('printers.ipAddress'),
value: (
<span className="flex items-center">
<span className="font-mono">{printer.ip_address}</span>
<CopyButton value={printer.ip_address} />
</span>
),
});
// Serial Number
rows.push({
label: t('printers.serialNumber'),
value: (
<span className="flex items-center">
<span className="font-mono truncate">{printer.serial_number}</span>
<CopyButton value={printer.serial_number} />
</span>
),
});
// WiFi signal
if (status?.wifi_signal != null) {
const wifi = getWifiStrength(status.wifi_signal);
rows.push({
label: t('printers.wifiSignalLabel'),
value: (
<span className="flex items-center gap-2">
<Signal className={`w-4 h-4 ${wifi.color}`} />
<span className={wifi.color}>{t(wifi.labelKey)}</span>
<span className="text-bambu-gray text-xs">({status.wifi_signal} dBm)</span>
</span>
),
});
}
// Firmware
rows.push({
label: t('printers.firmware'),
value: status?.firmware_version ?? '—',
});
// Developer Mode
if (status?.developer_mode != null) {
rows.push({
label: t('printers.developerMode'),
value: (
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
status.developer_mode
? 'bg-bambu-green/20 text-bambu-green'
: 'bg-bambu-dark-tertiary text-bambu-gray'
}`}>
{status.developer_mode ? t('printers.enabled') : t('printers.disabled')}
</span>
),
});
}
// Nozzle Count
rows.push({
label: t('printers.nozzleCount'),
value: printer.nozzle_count,
});
// SD Card
if (status?.sdcard != null) {
rows.push({
label: t('printers.sdCard'),
value: (
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
status.sdcard
? 'bg-bambu-green/20 text-bambu-green'
: 'bg-bambu-dark-tertiary text-bambu-gray'
}`}>
{status.sdcard ? t('printers.inserted') : t('printers.notInserted')}
</span>
),
});
}
// Auto-Archive
rows.push({
label: t('printers.autoArchive'),
value: (
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
printer.auto_archive
? 'bg-bambu-green/20 text-bambu-green'
: 'bg-bambu-dark-tertiary text-bambu-gray'
}`}>
{printer.auto_archive ? t('printers.enabled') : t('printers.disabled')}
</span>
),
});
// Total Print Hours
if (totalPrintHours != null && totalPrintHours > 0) {
rows.push({
label: t('printers.totalPrintHours'),
value: `${Math.round(totalPrintHours)}h`,
});
}
// Location
if (printer.location) {
rows.push({
label: t('printers.sort.location'),
value: printer.location,
});
}
// Added date
rows.push({
label: t('printers.addedOn'),
value: formatDateOnly(printer.created_at),
});
return (
<div
className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4"
role="dialog"
aria-modal="true"
onClick={onClose}
>
<Card className="w-full max-w-md" onClick={(e: React.MouseEvent) => e.stopPropagation()}>
<CardContent>
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-white">
{printer.name}
</h2>
<button onClick={onClose} className="p-1 hover:bg-bambu-dark rounded flex-shrink-0">
<X className="w-5 h-5 text-bambu-gray" />
</button>
</div>
{/* Printer Image */}
<div className="flex justify-center mb-4">
<img
src={getPrinterImage(printer.model)}
alt={printer.model ?? printer.name}
className="h-24 object-contain"
/>
</div>
<div className="space-y-0">
{rows.map((row, i) => (
<div key={i} className="flex items-center justify-between gap-4 py-2.5 border-b border-bambu-dark-tertiary last:border-0">
<span className="text-sm text-bambu-gray whitespace-nowrap">{row.label}</span>
<span className="text-sm text-white text-right">{row.value}</span>
</div>
))}
</div>
</CardContent>
</Card>
</div>
);
}