feat(DeviceDetailOverlay, DevicesPage): enhance device handling with physical device support and improve navigation logic

This commit is contained in:
Jaime Idolpx 2026-06-11 22:16:25 -04:00
parent 9a7d93b9a7
commit fefb7699fa
2 changed files with 29 additions and 10 deletions

View File

@ -14,6 +14,8 @@ interface Device {
enabled: boolean | number; enabled: boolean | number;
url?: string; url?: string;
mode?: number; mode?: number;
physical?: boolean;
physicalModel?: string;
} }
interface DeviceDetailOverlayProps { interface DeviceDetailOverlayProps {
@ -249,9 +251,16 @@ export default function DeviceDetailOverlay({
<button onClick={onClose} className="p-2 -m-2"> <button onClick={onClose} className="p-2 -m-2">
<X className="w-6 h-6" /> <X className="w-6 h-6" />
</button> </button>
<div className={`flex flex-col items-center gap-0.5 ${deviceData.enabled ? 'text-blue-600' : 'text-neutral-400'}`}> <div className={`flex flex-col items-center gap-0.5 ${
device.physical ? 'text-green-600' : deviceData.enabled ? 'text-blue-600' : 'text-neutral-400'
}`}>
<span className="text-sm font-semibold leading-none">{device.number}</span> <span className="text-sm font-semibold leading-none">{device.number}</span>
{getDeviceIcon(device.type)} {getDeviceIcon(device.type)}
{device.physical && (
<span className="text-xs text-green-700 px-1 py-0.5 bg-green-50 border border-green-200 rounded leading-none">
Physical
</span>
)}
</div> </div>
<button <button
onClick={() => setShowCommandMenu(!showCommandMenu)} onClick={() => setShowCommandMenu(!showCommandMenu)}

View File

@ -158,7 +158,7 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
// Auto-open the overlay when the parent passes a device ID // Auto-open the overlay when the parent passes a device ID
useEffect(() => { useEffect(() => {
if (!openDeviceId) return; if (!openDeviceId) return;
const idx = devices.findIndex(d => d.id === openDeviceId); const idx = displayDevices.findIndex(d => d.id === openDeviceId);
if (idx >= 0) setSelectedDeviceIndex(idx); if (idx >= 0) setSelectedDeviceIndex(idx);
onClearOpenDevice?.(); onClearOpenDevice?.();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -195,8 +195,7 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
}; };
const handleDeviceClick = (dd: DisplayDevice) => { const handleDeviceClick = (dd: DisplayDevice) => {
if (dd.physical) return; const idx = displayDevices.findIndex(d => d.id === dd.id);
const idx = devices.findIndex(d => d.number === dd.number);
if (idx >= 0) setSelectedDeviceIndex(idx); if (idx >= 0) setSelectedDeviceIndex(idx);
}; };
@ -206,7 +205,7 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
if (selectedDeviceIndex === null) return; if (selectedDeviceIndex === null) return;
if (direction === 'prev' && selectedDeviceIndex > 0) if (direction === 'prev' && selectedDeviceIndex > 0)
setSelectedDeviceIndex(selectedDeviceIndex - 1); setSelectedDeviceIndex(selectedDeviceIndex - 1);
else if (direction === 'next' && selectedDeviceIndex < devices.length - 1) else if (direction === 'next' && selectedDeviceIndex < displayDevices.length - 1)
setSelectedDeviceIndex(selectedDeviceIndex + 1); setSelectedDeviceIndex(selectedDeviceIndex + 1);
}; };
@ -235,6 +234,19 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
found.sort((a, b) => parseInt(a.number) - parseInt(b.number)); found.sort((a, b) => parseInt(a.number) - parseInt(b.number));
setPhysicalDevices(found); setPhysicalDevices(found);
// Disable any virtual device that now has a physical device at the same address.
const newConfig = JSON.parse(JSON.stringify(config));
let disabledCount = 0;
for (const p of found) {
const virt = newConfig.devices?.iec?.[p.number];
if (virt && virt.enabled) {
virt.enabled = 0;
disabledCount++;
}
}
if (disabledCount > 0) setConfig(newConfig);
setIsScanning(false); setIsScanning(false);
toast.dismiss(toastId); toast.dismiss(toastId);
toast.success(`Found ${found.length} physical device${found.length !== 1 ? 's' : ''} on the bus`); toast.success(`Found ${found.length} physical device${found.length !== 1 ? 's' : ''} on the bus`);
@ -348,8 +360,7 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
{/* Info — clickable for virtual devices */} {/* Info — clickable for virtual devices */}
<button <button
onClick={() => handleDeviceClick(device)} onClick={() => handleDeviceClick(device)}
disabled={device.physical} className="flex-1 min-w-0 text-left"
className="flex-1 min-w-0 text-left disabled:cursor-default"
> >
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
<span className={!device.physical && !device.enabled ? 'text-neutral-400' : 'text-neutral-900'}> <span className={!device.physical && !device.enabled ? 'text-neutral-400' : 'text-neutral-900'}>
@ -401,16 +412,15 @@ export default function DevicesPage({ config, setConfig, openDeviceId, onClearOp
))} ))}
</div> </div>
{/* DeviceDetailOverlay — virtual devices only */}
{selectedDeviceIndex !== null && ( {selectedDeviceIndex !== null && (
<DeviceDetailOverlay <DeviceDetailOverlay
device={devices[selectedDeviceIndex]} device={displayDevices[selectedDeviceIndex]}
config={config} config={config}
setConfig={setConfig} setConfig={setConfig}
onClose={handleCloseOverlay} onClose={handleCloseOverlay}
onNavigate={handleNavigate} onNavigate={handleNavigate}
hasPrev={selectedDeviceIndex > 0} hasPrev={selectedDeviceIndex > 0}
hasNext={selectedDeviceIndex < devices.length - 1} hasNext={selectedDeviceIndex < displayDevices.length - 1}
/> />
)} )}