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 && (