Route boards to circuit-first editor

This commit is contained in:
2026-07-22 19:23:30 +02:00
parent 7580ad0ade
commit 689cfd3c65
3 changed files with 82 additions and 18 deletions
@@ -239,3 +239,29 @@ Acceptance criteria:
- the codebase can later add rule-based sizing without redesigning circuits/devices
- users can manually override calculated suggestions
## Phase 9: Development Environment and Docker
Goal:
Make short development sessions quick to start and stop.
Tasks:
- add a Docker-based local development setup
- start frontend, API and SQLite persistence with one command
- keep the database in an explicit persistent volume
- run or document pending database migrations during startup
- add concise start, stop and reset instructions
- retain a non-Docker development path for direct debugging
Acceptance criteria:
- a new development session starts with one documented command
- stopping and restarting does not lose the local database
- frontend and API health can be checked without manual port setup
- the development workflow works consistently across short sessions
Note:
This phase may be moved earlier if repeated local startup overhead begins to slow down feature development.
@@ -11,18 +11,21 @@ export default function CircuitTreeEditPage() {
<main className="container py-4">
<div className="d-flex justify-content-between align-items-center mb-3">
<div>
<h1 className="h4 mb-1">Circuit Tree Editor</h1>
<p className="text-secondary mb-0">Basic editing for the circuit-first model.</p>
<h1 className="h4 mb-1">Stromkreisliste</h1>
<p className="text-secondary mb-0">Stromkreise und zugeordnete Geräte bearbeiten.</p>
</div>
<div className="d-flex gap-2">
<Link
href={`/projects/${params.projectId}/circuit-lists/${params.circuitListId}/tree`}
className="btn btn-outline-secondary btn-sm"
>
Read-only tree
Strukturvorschau
</Link>
<Link href={`/projects/${params.projectId}`} className="btn btn-outline-secondary btn-sm">
Zum Projekt
</Link>
<Link href={`/projects/${params.projectId}/circuit-lists`} className="btn btn-outline-secondary btn-sm">
Legacy editor
Legacy-Editor
</Link>
</div>
</div>
+49 -14
View File
@@ -11,6 +11,7 @@ import {
createProjectDevice,
createRoom,
deleteProjectDevice,
listCircuitLists,
listDistributionBoards,
listFloors,
listGlobalDevices,
@@ -21,6 +22,7 @@ import {
updateProjectSettings,
} from "../../../frontend/utils/api";
import type {
CircuitListDto,
CreateProjectDeviceInput,
DistributionBoardDto,
FloorDto,
@@ -56,6 +58,7 @@ export default function ProjectDetailPage() {
const [projectId, setProjectId] = useState("");
const [project, setProject] = useState<ProjectDto | null>(null);
const [boards, setBoards] = useState<DistributionBoardDto[]>([]);
const [circuitLists, setCircuitLists] = useState<CircuitListDto[]>([]);
const [floors, setFloors] = useState<FloorDto[]>([]);
const [rooms, setRooms] = useState<RoomDto[]>([]);
const [projectDevices, setProjectDevices] = useState<ProjectDeviceDto[]>([]);
@@ -94,12 +97,21 @@ export default function ProjectDetailPage() {
Promise.all([
listProjects(),
listDistributionBoards(projectId),
listCircuitLists(projectId),
listFloors(projectId),
listRooms(projectId),
listProjectDevices(projectId),
listGlobalDevices(),
])
.then(([projects, distributionBoards, loadedFloors, loadedRooms, loadedProjectDevices, loadedGlobalDevices]) => {
.then(([
projects,
distributionBoards,
loadedCircuitLists,
loadedFloors,
loadedRooms,
loadedProjectDevices,
loadedGlobalDevices,
]) => {
const currentProject = projects.find((item) => item.id === projectId) ?? null;
setProject(currentProject);
if (currentProject) {
@@ -107,6 +119,7 @@ export default function ProjectDetailPage() {
setThreePhaseVoltageV(String(currentProject.threePhaseVoltageV));
}
setBoards(distributionBoards);
setCircuitLists(loadedCircuitLists);
setFloors(loadedFloors);
setRooms(loadedRooms);
setProjectDevices(loadedProjectDevices);
@@ -123,6 +136,10 @@ export default function ProjectDetailPage() {
const roomCount = useMemo(() => rooms.length, [rooms.length]);
const projectDeviceCount = useMemo(() => projectDevices.length, [projectDevices.length]);
const floorById = useMemo(() => new Map(floors.map((item) => [item.id, item])), [floors]);
const circuitListByBoardId = useMemo(
() => new Map(circuitLists.map((circuitList) => [circuitList.distributionBoardId, circuitList])),
[circuitLists]
);
async function handleCreateBoard(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
@@ -134,6 +151,7 @@ export default function ProjectDetailPage() {
try {
const created = await createDistributionBoard(projectId, boardName.trim());
setBoards((current) => [...current, created]);
setCircuitLists(await listCircuitLists(projectId));
setBoardName("");
} catch (err) {
setError(err instanceof Error ? err.message : "Verteilung konnte nicht erstellt werden.");
@@ -415,19 +433,36 @@ export default function ProjectDetailPage() {
</tr>
</thead>
<tbody>
{boards.map((board) => (
<tr key={board.id}>
<td>{board.name}</td>
<td className="text-end">
<Link
className="btn btn-sm btn-outline-primary"
href={`/projects/${projectId}/circuit-lists?boardId=${board.id}`}
>
Stromkreisliste öffnen
</Link>
</td>
</tr>
))}
{boards.map((board) => {
const circuitList = circuitListByBoardId.get(board.id);
return (
<tr key={board.id}>
<td>{board.name}</td>
<td className="text-end">
<div className="d-inline-flex gap-2">
{circuitList ? (
<Link
className="btn btn-sm btn-primary"
href={`/projects/${projectId}/circuit-lists/${circuitList.id}/tree-edit`}
>
Stromkreisliste öffnen
</Link>
) : (
<span className="text-secondary small align-self-center">
Keine Stromkreisliste vorhanden
</span>
)}
<Link
className="btn btn-sm btn-outline-secondary"
href={`/projects/${projectId}/circuit-lists?boardId=${board.id}`}
>
Legacy
</Link>
</div>
</td>
</tr>
);
})}
{!boards.length ? (
<tr>
<td colSpan={2} className="text-center text-secondary py-4">