import { useCallback, useState, useEffect } from 'react' import { Trash2, Minus, Spline, GitBranch, BringToFront, SendToBack } from 'lucide-react' import { cn } from '@/lib/utils' import type { DeviceProperties, DiagramEdge } from '@/types' import type { Node, Edge } from '@xyflow/react' import type { DeviceNodeData } from '../nodes/DeviceNode' interface PropertiesPanelProps { selectedNode: Node | null selectedEdge: Edge | null onNodeUpdate: (nodeId: string, data: Partial) => void onEdgeUpdate: (edgeId: string, data: Partial) => void onEdgeTypeChange: (edgeId: string, edgeType: string) => void onBringToFront: (nodeId: string) => void onSendToBack: (nodeId: string) => void onDeleteNode: (nodeId: string) => void onDeleteEdge: (edgeId: string) => void } type NodeStatus = 'online' | 'offline' | 'degraded' | 'unknown' const STATUS_CONFIG: Record = { online: { color: '#34d399', label: 'Online' }, offline: { color: '#f87171', label: 'Offline' }, degraded: { color: '#fbbf24', label: 'Degraded' }, unknown: { color: '#94a3b8', label: 'Unknown' }, } const STATUS_OPTIONS = Object.keys(STATUS_CONFIG) as NodeStatus[] const CONNECTION_TYPE_OPTIONS = ['ethernet', 'fiber', 'wifi', 'vpn', 'vlan', 'wan'] as const function FieldLabel({ children }: { children: React.ReactNode }) { return ( ) } function FieldInput({ value, onChange, placeholder, mono }: { value: string onChange: (val: string) => void placeholder?: string mono?: boolean }) { return ( onChange(e.target.value)} placeholder={placeholder} className={cn( 'w-full rounded border border-default bg-input px-2 py-1.5 text-xs text-primary placeholder:text-muted-foreground focus:border-accent focus:outline-none', mono && 'font-mono', )} /> ) } function SectionDivider({ label }: { label: string }) { return (
{label}
) } export function PropertiesPanel({ selectedNode, selectedEdge, onNodeUpdate, onEdgeUpdate, onEdgeTypeChange, onBringToFront, onSendToBack, onDeleteNode, onDeleteEdge, }: PropertiesPanelProps) { const [deleteConfirm, setDeleteConfirm] = useState(false) // Reset confirm state whenever the selection changes useEffect(() => { setDeleteConfirm(false) }, [selectedNode?.id, selectedEdge?.id]) const handlePropertyChange = useCallback((field: keyof DeviceProperties, value: string) => { if (!selectedNode) return const nodeData = selectedNode.data as unknown as DeviceNodeData onNodeUpdate(selectedNode.id, { properties: { ...nodeData.properties, [field]: value }, } as Partial) }, [selectedNode, onNodeUpdate]) const handleLabelChange = useCallback((value: string) => { if (!selectedNode) return onNodeUpdate(selectedNode.id, { label: value } as Partial) }, [selectedNode, onNodeUpdate]) if (!selectedNode && !selectedEdge) { return (

Select a device or connection to edit its properties

Hover a device to preview its info

) } if (selectedEdge) { const edgeData = (selectedEdge.data || {}) as Record const connectionType = (edgeData.connectionType as string) || 'ethernet' const isCustomType = !CONNECTION_TYPE_OPTIONS.includes(connectionType as typeof CONNECTION_TYPE_OPTIONS[number]) return (

Connection

Label onEdgeUpdate(selectedEdge.id, { label: val || null })} placeholder="Connection label" />
Type {isCustomType && ( onEdgeUpdate(selectedEdge.id, { connectionType: val })} placeholder="Custom type name" /> )}
Speed onEdgeUpdate(selectedEdge.id, { speed: val || null })} placeholder="e.g. 1 Gbps" />
Notes onEdgeUpdate(selectedEdge.id, { notes: val || null })} placeholder="Port info, cable type…" mono />
Line Style
{([ { value: null, icon: Minus, label: 'Straight' }, { value: 'curved', icon: Spline, label: 'Curved' }, { value: 'step', icon: GitBranch, label: 'Step' }, ] as const).map(({ value, icon: Icon, label }) => { const routing = (edgeData.routing as string | null | undefined) ?? null const active = routing === value return ( ) })}
Show Traffic
{deleteConfirm ? (

Delete this connection?

) : ( )}
) } const nodeData = selectedNode!.data as unknown as DeviceNodeData const props = nodeData.properties || {} as DeviceProperties const currentStatus = (props.status || 'unknown') as NodeStatus return (

Device Properties

{/* Identity */}
Name
{/* Layering */}
Layer
{/* Status badge grid */}
Status
{STATUS_OPTIONS.map(opt => { const { color, label } = STATUS_CONFIG[opt] const active = currentStatus === opt return ( ) })}
{/* Network section */}
IP Address handlePropertyChange('ip', v)} placeholder="e.g. 10.0.0.1" mono />
Subnet handlePropertyChange('subnet', v)} placeholder="e.g. 10.0.0.0/24" mono />
VLAN handlePropertyChange('vlan', v)} placeholder="e.g. 10" mono />
{/* Hardware section */}
Hostname handlePropertyChange('hostname', v)} placeholder="e.g. core-rtr-01" mono />
Vendor handlePropertyChange('vendor', v)} placeholder="e.g. Cisco" />
Model handlePropertyChange('model', v)} placeholder="e.g. ISR 4331" />
Role handlePropertyChange('role', v)} placeholder="e.g. Core gateway" />
{/* Notes */}