diff --git a/client/src/components/modals/ModuleEditPanel.tsx b/client/src/components/modals/ModuleEditPanel.tsx index efe8e7b..3f6d6c3 100644 --- a/client/src/components/modals/ModuleEditPanel.tsx +++ b/client/src/components/modals/ModuleEditPanel.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, type FormEvent } from 'react'; +import { Trash2 } from 'lucide-react'; import { toast } from 'sonner'; import type { Module } from '../../types'; import { Modal } from '../ui/Modal'; @@ -15,7 +16,7 @@ interface ModuleEditPanelProps { } export function ModuleEditPanel({ module, open, onClose }: ModuleEditPanelProps) { - const { updateModuleLocal } = useRackStore(); + const { updateModuleLocal, removeModuleLocal } = useRackStore(); const [name, setName] = useState(module.name); const [ipAddress, setIpAddress] = useState(module.ipAddress ?? ''); const [manufacturer, setManufacturer] = useState(module.manufacturer ?? ''); @@ -23,9 +24,12 @@ export function ModuleEditPanel({ module, open, onClose }: ModuleEditPanelProps) const [notes, setNotes] = useState(module.notes ?? ''); const [uSize, setUSize] = useState(module.uSize); const [loading, setLoading] = useState(false); + const [confirmingDelete, setConfirmingDelete] = useState(false); + const [deleting, setDeleting] = useState(false); useEffect(() => { if (open) { + setConfirmingDelete(false); setName(module.name); setIpAddress(module.ipAddress ?? ''); setManufacturer(module.manufacturer ?? ''); @@ -35,6 +39,21 @@ export function ModuleEditPanel({ module, open, onClose }: ModuleEditPanelProps) } }, [open, module]); + async function handleDelete() { + setDeleting(true); + try { + await apiClient.modules.delete(module.id); + removeModuleLocal(module.id); + toast.success(`${module.name} removed`); + onClose(); + } catch (e) { + toast.error(e instanceof Error ? e.message : 'Delete failed'); + } finally { + setDeleting(false); + setConfirmingDelete(false); + } + } + async function handleSubmit(e: FormEvent) { e.preventDefault(); setLoading(true); @@ -132,13 +151,51 @@ export function ModuleEditPanel({ module, open, onClose }: ModuleEditPanelProps) /> -
- - +
+ {/* Delete — left side with inline confirm */} + {confirmingDelete ? ( +
+ Remove this module? + + +
+ ) : ( + + )} + + {/* Save / Cancel — right side */} +
+ + +
diff --git a/client/src/components/rack/ModuleBlock.tsx b/client/src/components/rack/ModuleBlock.tsx index ca5cc66..05a45ee 100644 --- a/client/src/components/rack/ModuleBlock.tsx +++ b/client/src/components/rack/ModuleBlock.tsx @@ -1,11 +1,10 @@ import { useState, useCallback, useRef } from 'react'; import { useDraggable } from '@dnd-kit/core'; -import { Trash2, GripVertical, GripHorizontal } from 'lucide-react'; +import { GripHorizontal } from 'lucide-react'; import { toast } from 'sonner'; import type { Module } from '../../types'; import { cn } from '../../lib/utils'; import { MODULE_TYPE_COLORS, U_HEIGHT_PX, PORTS_PER_ROW } from '../../lib/constants'; -import { ConfirmDialog } from '../ui/ConfirmDialog'; import { ModuleEditPanel } from '../modals/ModuleEditPanel'; import { PortConfigModal } from '../modals/PortConfigModal'; import { useRackStore } from '../../store/useRackStore'; @@ -16,11 +15,9 @@ interface ModuleBlockProps { } export function ModuleBlock({ module }: ModuleBlockProps) { - const { racks, removeModuleLocal, updateModuleLocal } = useRackStore(); + const { racks, updateModuleLocal } = 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); @@ -107,20 +104,6 @@ export function ModuleBlock({ module }: ModuleBlockProps) { } } - 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); @@ -130,11 +113,13 @@ export function ModuleBlock({ module }: ModuleBlockProps) { <>
e.key === 'Enter' && setEditOpen(true)} > - {/* Drag handle — slim left strip */} -
e.stopPropagation()} - aria-label={`Drag ${module.name}`} - > - -
- {/* Port grid — primary face content */} {hasPorts && previewUSize === null ? ( -
e.stopPropagation()} - > +
{visibleRows.map((row, rowIdx) => (
{row.map((port) => { @@ -172,7 +143,8 @@ export function ModuleBlock({ module }: ModuleBlockProps) { return (
) : ( - /* No ports or resizing — show nothing (color communicates type) */ previewUSize === null && ( -
no ports
+
no ports
) )} @@ -206,20 +177,6 @@ export function ModuleBlock({ module }: ModuleBlockProps) {
)} - {/* Delete button — hover only */} - {hovered && previewUSize === null && ( - - )} - {/* Resize handle — bottom edge */}
{ e.stopPropagation(); handleResizePointerDown(e); }} onPointerMove={handleResizePointerMove} onPointerUp={handleResizePointerUp} onClick={(e) => e.stopPropagation()} @@ -241,16 +198,6 @@ export function ModuleBlock({ module }: ModuleBlockProps) { 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 && (