import { useState } from 'react'; import { Trash2 } from 'lucide-react'; import { toast } from 'sonner'; import type { Module } from '../../types'; import { cn } from '../../lib/utils'; import { MODULE_TYPE_COLORS, MODULE_TYPE_LABELS, U_HEIGHT_PX } from '../../lib/constants'; import { Badge } from '../ui/Badge'; import { ConfirmDialog } from '../ui/ConfirmDialog'; import { ModuleEditPanel } from '../modals/ModuleEditPanel'; import { PortConfigModal } from '../modals/PortConfigModal'; import { useRackStore } from '../../store/useRackStore'; import { apiClient } from '../../api/client'; interface ModuleBlockProps { module: Module; } export function ModuleBlock({ module }: ModuleBlockProps) { const { removeModuleLocal } = useRackStore(); const [hovered, setHovered] = useState(false); const [editOpen, setEditOpen] = useState(false); const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false); const [deletingLoading, setDeletingLoading] = useState(false); const [portModalOpen, setPortModalOpen] = useState(false); const [selectedPortId, setSelectedPortId] = useState(null); const colors = MODULE_TYPE_COLORS[module.type]; const height = module.uSize * U_HEIGHT_PX; const hasPorts = module.ports.length > 0; async function handleDelete() { setDeletingLoading(true); try { await apiClient.modules.delete(module.id); removeModuleLocal(module.id); toast.success(`${module.name} removed`); } catch (e) { toast.error(e instanceof Error ? e.message : 'Delete failed'); } finally { setDeletingLoading(false); setConfirmDeleteOpen(false); } } function openPort(portId: string) { setSelectedPortId(portId); setPortModalOpen(true); } return ( <>
setHovered(true)} onMouseLeave={() => setHovered(false)} onClick={() => setEditOpen(true)} role="button" tabIndex={0} aria-label={`Edit ${module.name}`} onKeyDown={(e) => e.key === 'Enter' && setEditOpen(true)} > {/* Main content */}
{module.name} {MODULE_TYPE_LABELS[module.type]}
{module.ipAddress && (
{module.ipAddress}
)} {/* Port dots — only if module has ports and enough height */} {hasPorts && height >= 28 && (
e.stopPropagation()} > {module.ports.slice(0, 32).map((port) => { const hasVlan = port.vlans.length > 0; return (
)} {/* Delete button — hover only */} {hovered && ( )}
setEditOpen(false)} /> setConfirmDeleteOpen(false)} onConfirm={handleDelete} title="Remove Module" message={`Remove "${module.name}" from the rack? This will also delete all associated port configuration.`} confirmLabel="Remove" loading={deletingLoading} /> {selectedPortId && ( { setPortModalOpen(false); setSelectedPortId(null); }} /> )} ); }