feat(rack-planner): implement port-to-port connections (patch cables) with dynamic SVG visualization layer
This commit is contained in:
@@ -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'
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user