Adds first version of the app. Local storage only.

This commit is contained in:
2026-09-01 14:44:49 -05:00
commit 9710f1ee5a
44 changed files with 5865 additions and 0 deletions
@@ -0,0 +1,109 @@
import { useMemo } from 'react'
import { cableTypesForConnection, getPortType } from '../../domain/project'
import { useProjectStore } from '../../state/projectStore'
export default function ConnectionInspector({ connectionId }: { connectionId: string }) {
const project = useProjectStore((s) => s.project)
const updateConnection = useProjectStore((s) => s.updateConnection)
const removeConnection = useProjectStore((s) => s.removeConnection)
const connection = project.connections.find((c) => c.id === connectionId)
const sourceDevice = project.devices.find((d) => d.id === connection?.sourceDeviceId)
const targetDevice = project.devices.find((d) => d.id === connection?.targetDeviceId)
const sourcePort = sourceDevice?.ports.find((p) => p.id === connection?.sourcePortId)
const targetPort = targetDevice?.ports.find((p) => p.id === connection?.targetPortId)
const availableCableTypes = useMemo(() => {
if (!sourcePort || !targetPort) return []
const sourcePortType = getPortType(project, sourcePort.portTypeId)
const targetPortType = getPortType(project, targetPort.portTypeId)
return sourcePortType && targetPortType ? cableTypesForConnection(project, sourcePortType, targetPortType) : []
}, [project, sourcePort, targetPort])
if (!connection || !sourceDevice || !targetDevice || !sourcePort || !targetPort) return null
return (
<div className="space-y-3 p-3">
<div className="rounded border border-slate-200 bg-slate-50 p-2 text-xs">
<div className="flex items-center justify-between py-0.5">
<span className="text-slate-500">From</span>
<span className="font-medium text-slate-700">
{sourceDevice.name} · {sourcePort.name}
</span>
</div>
<div className="flex items-center justify-between py-0.5">
<span className="text-slate-500">To</span>
<span className="font-medium text-slate-700">
{targetDevice.name} · {targetPort.name}
</span>
</div>
</div>
<label className="block text-xs font-medium text-slate-600">
Cable type
<select
value={connection.cableTypeId}
onChange={(e) => updateConnection(connection.id, { cableTypeId: e.target.value })}
className="mt-1 w-full rounded border border-slate-300 px-2 py-1 text-sm"
>
{availableCableTypes.map((ct) => (
<option key={ct.id} value={ct.id}>
{ct.name}
</option>
))}
</select>
</label>
<label className="block text-xs font-medium text-slate-600">
Distance (manual estimate)
<div className="mt-1 flex items-center gap-2">
<input
type="number"
min={0}
step={0.5}
value={connection.distance ?? ''}
onChange={(e) =>
updateConnection(connection.id, {
distance: e.target.value === '' ? undefined : Number(e.target.value),
})
}
className="w-full rounded border border-slate-300 px-2 py-1 text-sm"
placeholder="e.g. 12"
/>
<span className="text-xs text-slate-500">
{availableCableTypes.find((ct) => ct.id === connection.cableTypeId)?.unit ?? 'ft'}
</span>
</div>
</label>
<label className="block text-xs font-medium text-slate-600">
Label
<input
value={connection.label ?? ''}
onChange={(e) => updateConnection(connection.id, { label: e.target.value })}
className="mt-1 w-full rounded border border-slate-300 px-2 py-1 text-sm"
placeholder="e.g. Runs through wall conduit"
/>
</label>
<label className="block text-xs font-medium text-slate-600">
Notes
<textarea
value={connection.notes ?? ''}
onChange={(e) => updateConnection(connection.id, { notes: e.target.value })}
rows={2}
className="mt-1 w-full rounded border border-slate-300 px-2 py-1 text-sm"
/>
</label>
<div className="border-t border-slate-100 pt-3">
<button
onClick={() => removeConnection(connection.id)}
className="w-full rounded border border-red-200 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50"
>
Remove cable
</button>
</div>
</div>
)
}