fix(rack-planner): compute port data synchronously in PortConfigModal to prevent empty first render

This commit is contained in:
2026-03-22 15:11:00 -05:00
parent 5de001c630
commit b26f88a89e

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, type FormEvent } from 'react'; import { useState, useEffect, useMemo, type FormEvent } from 'react';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { Port, Vlan, VlanMode } from '../../types'; import type { Port, Vlan, VlanMode } from '../../types';
import { Modal } from '../ui/Modal'; import { Modal } from '../ui/Modal';
@@ -14,42 +14,45 @@ interface PortConfigModalProps {
} }
export function PortConfigModal({ portId, open, onClose }: PortConfigModalProps) { export function PortConfigModal({ portId, open, onClose }: PortConfigModalProps) {
const { racks, fetchRacks } = useRackStore(); const { racks, fetchRacks, deleteConnection } = useRackStore();
const [port, setPort] = useState<Port | null>(null);
const [vlans, setVlans] = useState<Vlan[]>([]); const [vlans, setVlans] = useState<Vlan[]>([]);
const [loading, setLoading] = useState(false);
const [fetching, setFetching] = useState(false);
// Synchronously find the port from the global store
const port = useMemo(() => {
for (const rack of racks) {
for (const mod of rack.modules) {
const found = mod.ports.find((p) => p.id === portId);
if (found) return found;
}
}
return null;
}, [racks, portId]);
// Form state
const [label, setLabel] = useState(''); const [label, setLabel] = useState('');
const [mode, setMode] = useState<VlanMode>('ACCESS'); const [mode, setMode] = useState<VlanMode>('ACCESS');
const [nativeVlanId, setNativeVlanId] = useState<string>(''); const [nativeVlanId, setNativeVlanId] = useState<string>('');
const [taggedVlanIds, setTaggedVlanIds] = useState<string[]>([]); const [taggedVlanIds, setTaggedVlanIds] = useState<string[]>([]);
const [notes, setNotes] = useState(''); const [notes, setNotes] = useState('');
const [loading, setLoading] = useState(false);
const [fetching, setFetching] = useState(false);
// Quick-create VLAN // Quick-create VLAN
const [newVlanId, setNewVlanId] = useState(''); const [newVlanId, setNewVlanId] = useState('');
const [newVlanName, setNewVlanName] = useState(''); const [newVlanName, setNewVlanName] = useState('');
const [newVlanColor, setNewVlanColor] = useState('#3b82f6'); const [newVlanColor, setNewVlanColor] = useState('#3b82f6');
const [creatingVlan, setCreatingVlan] = useState(false); const [creatingVlan, setCreatingVlan] = useState(false);
// Find the port from store // Reset form state when port is found or changed
useEffect(() => { useEffect(() => {
if (!open) return; if (port && open) {
let found: Port | undefined; setLabel(port.label ?? '');
for (const rack of racks) { setMode(port.mode);
for (const mod of rack.modules) { setNativeVlanId(port.nativeVlan?.toString() ?? '');
found = mod.ports.find((p) => p.id === portId); setTaggedVlanIds(port.vlans.filter((v) => v.tagged).map((v) => v.vlanId));
if (found) break; setNotes(port.notes ?? '');
}
if (found) break;
} }
if (found) { }, [port, open]);
setPort(found);
setLabel(found.label ?? '');
setMode(found.mode);
setNativeVlanId(found.nativeVlan?.toString() ?? '');
setTaggedVlanIds(found.vlans.filter((v) => v.tagged).map((v) => v.vlanId));
setNotes(found.notes ?? '');
}
}, [open, portId, racks]);
// Load VLAN list // Load VLAN list
useEffect(() => { useEffect(() => {
@@ -125,7 +128,6 @@ export function PortConfigModal({ portId, open, onClose }: PortConfigModalProps)
if (!port) return null; if (!port) return null;
const { deleteConnection } = useRackStore();
const connections = [...(port.sourceConnections || []), ...(port.targetConnections || [])]; const connections = [...(port.sourceConnections || []), ...(port.targetConnections || [])];
async function handleDisconnect(connId: string) { async function handleDisconnect(connId: string) {