feat(rack-planner): implement port-to-port connections (patch cables) with dynamic SVG visualization layer

This commit is contained in:
2026-03-22 14:55:33 -05:00
parent 444d694a06
commit becb55d57c
13 changed files with 449 additions and 28 deletions

View File

@@ -109,6 +109,35 @@ export function ModuleBlock({ module }: ModuleBlockProps) {
setPortModalOpen(true);
}
const { cablingFromPortId, setCablingFromPortId, createConnection } = useRackStore();
async function handlePortClick(e: React.MouseEvent, portId: string) {
e.stopPropagation();
// If shift key is pressed, open config modal as before
if (e.shiftKey) {
openPort(portId);
return;
}
// Toggle cabling mode
if (!cablingFromPortId) {
setCablingFromPortId(portId);
} else if (cablingFromPortId === portId) {
setCablingFromPortId(null);
} else {
// Connect!
try {
await createConnection(cablingFromPortId, portId);
setCablingFromPortId(null);
toast.success('Patch cable connected');
} catch (err) {
toast.error(err instanceof Error ? err.message : 'Connection failed');
setCablingFromPortId(null);
}
}
}
return (
<>
<div
@@ -140,18 +169,28 @@ export function ModuleBlock({ module }: ModuleBlockProps) {
<div key={rowIdx} className="flex gap-[3px]">
{row.map((port) => {
const hasVlan = port.vlans.length > 0;
const vlanColor = hasVlan
? port.mode === 'ACCESS'
? port.vlans[0]?.vlan?.color || '#10b981'
: '#8b5cf6'
: '#475569';
const isCablingSource = cablingFromPortId === port.id;
return (
<button
key={port.id}
data-port-id={port.id}
onPointerDown={(e) => e.stopPropagation()}
onClick={(e) => { e.stopPropagation(); openPort(port.id); }}
onClick={(e) => handlePortClick(e, port.id)}
aria-label={`Port ${port.portNumber}`}
title={`Port ${port.portNumber}${port.label ? ` · ${port.label}` : ''}`}
title={`Port ${port.portNumber}${port.label ? ` · ${port.label}` : ''}${
hasVlan ? ` (VLAN ${port.vlans.map((v) => v.vlan.vlanId).join(',')})` : ''
}\nShift+Click for settings`}
style={{ backgroundColor: vlanColor, borderColor: 'rgba(0,0,0,0.2)' }}
className={cn(
'w-2.5 h-2.5 rounded-sm border transition-colors shrink-0',
hasVlan
? 'bg-green-400 border-green-500 hover:bg-green-300'
: 'bg-slate-600 border-slate-500 hover:bg-slate-400'
'w-2.5 h-2.5 rounded-sm border transition-all shrink-0 hover:scale-110 hover:brightness-125',
isCablingSource &&
'ring-2 ring-blue-400 ring-offset-1 ring-offset-slate-900 animate-pulse'
)}
/>
);