import { useState, useEffect } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { useAuth } from "../auth/AuthContext"; import api from "../api/client"; const BOARD_TYPE_LABELS = { vesper: "Vesper", vesper_plus: "Vesper+", vesper_pro: "Vesper Pro", chronos: "Chronos", chronos_pro: "Chronos Pro", agnus_mini: "Agnus Mini", agnus: "Agnus", }; const STATUS_STYLES = { manufactured: { bg: "var(--bg-card-hover)", color: "var(--text-muted)" }, flashed: { bg: "var(--badge-blue-bg)", color: "var(--badge-blue-text)" }, provisioned: { bg: "#0a2e2a", color: "#4dd6c8" }, sold: { bg: "#1e1036", color: "#c084fc" }, claimed: { bg: "#2e1a00", color: "#fb923c" }, decommissioned:{ bg: "var(--danger-bg)", color: "var(--danger-text)" }, }; const STATUS_OPTIONS = [ "manufactured", "flashed", "provisioned", "sold", "claimed", "decommissioned", ]; // Statuses that require serial confirmation before delete const PROTECTED_STATUSES = ["sold", "claimed"]; function formatHwVersion(v) { if (!v) return "—"; if (/^\d+\.\d+/.test(v)) return `Rev ${v}`; const n = parseInt(v, 10); if (!isNaN(n)) return `Rev ${n}.0`; return `Rev ${v}`; } function StatusBadge({ status }) { const style = STATUS_STYLES[status] || STATUS_STYLES.manufactured; return ( {status} ); } function Field({ label, value, mono = false }) { return (

{label}

{value || "—"}

); } // ─── Delete Confirmation Modal ──────────────────────────────────────────────── function DeleteModal({ device, onConfirm, onCancel, deleting }) { const isProtected = PROTECTED_STATUSES.includes(device?.mfg_status); const [typed, setTyped] = useState(""); const confirmed = !isProtected || typed === device?.serial_number; return (

{isProtected ? "⚠ Delete Protected Device" : "Delete Device"}

{isProtected ? ( <>

This device has status {device.mfg_status} and is linked to a customer. Deleting it is irreversible and may break the customer's access.

To confirm, type the serial number exactly:

{device.serial_number}

setTyped(e.target.value)} onPaste={(e) => e.preventDefault()} placeholder="Type serial number here…" autoComplete="off" className="w-full px-3 py-2 rounded-md text-sm border mb-4 font-mono" style={{ backgroundColor: "var(--bg-input)", borderColor: typed === device.serial_number ? "var(--accent)" : "var(--border-input)", color: "var(--text-primary)", }} /> ) : (

Are you sure you want to delete {device?.serial_number}? This cannot be undone.

)}
); } // ─── Main Component ─────────────────────────────────────────────────────────── export default function DeviceInventoryDetail() { const { sn } = useParams(); const navigate = useNavigate(); const { hasPermission } = useAuth(); const canEdit = hasPermission("manufacturing", "edit"); const canDelete = hasPermission("manufacturing", "delete"); const [device, setDevice] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(""); const [editingStatus, setEditingStatus] = useState(false); const [newStatus, setNewStatus] = useState(""); const [statusNote, setStatusNote] = useState(""); const [statusSaving, setStatusSaving] = useState(false); const [statusError, setStatusError] = useState(""); const [nvsDownloading, setNvsDownloading] = useState(false); const [assignEmail, setAssignEmail] = useState(""); const [assignName, setAssignName] = useState(""); const [assignSaving, setAssignSaving] = useState(false); const [assignError, setAssignError] = useState(""); const [assignSuccess, setAssignSuccess] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [deleting, setDeleting] = useState(false); const loadDevice = async () => { setLoading(true); setError(""); try { const data = await api.get(`/manufacturing/devices/${sn}`); setDevice(data); } catch (err) { setError(err.message); } finally { setLoading(false); } }; useEffect(() => { loadDevice(); }, [sn]); const handleStatusSave = async () => { setStatusError(""); setStatusSaving(true); try { const updated = await api.request(`/manufacturing/devices/${sn}/status`, { method: "PATCH", body: JSON.stringify({ status: newStatus, note: statusNote || null }), }); setDevice(updated); setEditingStatus(false); setStatusNote(""); } catch (err) { setStatusError(err.message); } finally { setStatusSaving(false); } }; const handleAssign = async () => { setAssignError(""); setAssignSaving(true); try { const updated = await api.request(`/manufacturing/devices/${sn}/assign`, { method: "POST", body: JSON.stringify({ customer_email: assignEmail, customer_name: assignName || null }), }); setDevice(updated); setAssignSuccess(true); setAssignEmail(""); setAssignName(""); } catch (err) { setAssignError(err.message); } finally { setAssignSaving(false); } }; const downloadNvs = async () => { setNvsDownloading(true); try { const token = localStorage.getItem("access_token"); const response = await fetch(`/api/manufacturing/devices/${sn}/nvs.bin`, { headers: { Authorization: `Bearer ${token}` }, }); if (!response.ok) { const err = await response.json().catch(() => ({})); throw new Error(err.detail || "Download failed"); } const blob = await response.blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `${sn}_nvs.bin`; a.click(); URL.revokeObjectURL(url); } catch (err) { setError(err.message); } finally { setNvsDownloading(false); } }; const handleDelete = async () => { setDeleting(true); try { const isProtected = PROTECTED_STATUSES.includes(device?.mfg_status); const url = `/manufacturing/devices/${sn}${isProtected ? "?force=true" : ""}`; await api.request(url, { method: "DELETE" }); navigate("/manufacturing"); } catch (err) { setError(err.message); setShowDeleteModal(false); } finally { setDeleting(false); } }; const formatDate = (iso) => { if (!iso) return "—"; try { return new Date(iso).toLocaleString("en-US", { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", }); } catch { return iso; } }; if (loading) { return (

Loading…

); } if (error && !device) { return (
{error}
); } return (
{/* Title row */}

{device?.serial_number}

{device?.device_name && (

{device.device_name}

)}
{canDelete && ( )}
{error && (
{error}
)} {/* Identity card */}

Device Identity

{device?.device_name && ( )}
{/* Status card */}

Status

{canEdit && !editingStatus && ( )}
{!editingStatus ? ( ) : (
{statusError && (
{statusError}
)} setStatusNote(e.target.value)} className="w-full px-3 py-2 rounded-md text-sm border" style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-input)", color: "var(--text-primary)" }} />
)}
{/* Actions card */}

Actions

NVS binary encodes device_uid, hw_type, hw_version. Flash at 0x9000.

{/* Assign to Customer card */} {canEdit && ["provisioned", "flashed"].includes(device?.mfg_status) && (

Assign to Customer

{assignSuccess ? (
Device assigned and invitation email sent to {device?.owner}.
) : (
{assignError && (
{assignError}
)} setAssignEmail(e.target.value)} className="w-full px-3 py-2 rounded-md text-sm border" style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-input)", color: "var(--text-primary)" }} /> setAssignName(e.target.value)} className="w-full px-3 py-2 rounded-md text-sm border" style={{ backgroundColor: "var(--bg-input)", borderColor: "var(--border-input)", color: "var(--text-primary)" }} />

Sets device status to sold and emails the customer their serial number.

)}
)} {/* Delete modal */} {showDeleteModal && ( setShowDeleteModal(false)} deleting={deleting} /> )}
); }