From ade21fe386eb0f8da3464391874d13e41f087a80 Mon Sep 17 00:00:00 2001 From: Julian Appel Date: Mon, 28 Sep 2026 22:14:30 +0200 Subject: [PATCH 1/4] Create feeder circuits while linking boards --- src/app/projects/[projectId]/page.tsx | 198 +++++++++++++-- ...it-structure-project-command.repository.ts | 8 +- ...rd-structure-project-command.repository.ts | 98 +++++++ ...rd-feeder-circuit-project-command.model.ts | 132 ++++++++++ ...n-board-structure-project-command.store.ts | 12 + .../services/project-command.service.ts | 13 + src/frontend/utils/api.ts | 59 ++++- .../distribution-board-feeder-circuit.ts | 68 +++++ src/frontend/utils/project-version-history.ts | 4 + ...circuit-project-command.repository.test.ts | 240 ++++++++++++++++++ .../distribution-board-feeder-circuit.test.ts | 168 ++++++++++++ 11 files changed, 960 insertions(+), 40 deletions(-) create mode 100644 src/domain/models/distribution-board-feeder-circuit-project-command.model.ts create mode 100644 src/frontend/utils/distribution-board-feeder-circuit.ts create mode 100644 tests/distribution-board-feeder-circuit-project-command.repository.test.ts create mode 100644 tests/distribution-board-feeder-circuit.test.ts diff --git a/src/app/projects/[projectId]/page.tsx b/src/app/projects/[projectId]/page.tsx index 4a99cf0..37813c1 100644 --- a/src/app/projects/[projectId]/page.tsx +++ b/src/app/projects/[projectId]/page.tsx @@ -47,6 +47,7 @@ import { import { copyGlobalDeviceToProject, copyDistributionBoard, + createFeederCircuitAndUpdateDistributionBoard, copyProjectDeviceToGlobal, createDistributionBoard, createProjectDevice, @@ -71,6 +72,12 @@ import { updateProjectDevice, updateProjectSettings, } from "../../../frontend/utils/api"; +import { createClientId } from "../../../frontend/utils/client-id"; +import { + buildFeederCircuitSnapshot, + getDefaultFeederCircuitSection, + getNextFeederCircuitIdentifier, +} from "../../../frontend/utils/distribution-board-feeder-circuit"; import type { BuildingDto, CircuitListDto, @@ -146,6 +153,7 @@ const boardFilterCollapsedStorageKey = "leistungsbilanz:overview-filter-collapsed:distribution-boards"; const projectDeviceFilterCollapsedStorageKey = "leistungsbilanz:overview-filter-collapsed:project-devices"; +const createFeederCircuitSelectValue = "__create_feeder_circuit__"; export default function ProjectDetailPage() { const params = useParams<{ projectId: string }>(); @@ -175,6 +183,12 @@ export default function ProjectDetailPage() { const [editingBoardCopyName, setEditingBoardCopyName] = useState(""); const [editingFeedingBoardId, setEditingFeedingBoardId] = useState(""); const [editingFeedingCircuitId, setEditingFeedingCircuitId] = useState(""); + const [isCreatingFeederCircuit, setIsCreatingFeederCircuit] = + useState(false); + const [editingFeederCircuitSectionId, setEditingFeederCircuitSectionId] = + useState(""); + const [editingFeederCircuitDisplayName, setEditingFeederCircuitDisplayName] = + useState(""); const [editingBoardInfo, setEditingBoardInfo] = useState( toDistributionBoardInfoDraft({ @@ -350,6 +364,21 @@ export default function ProjectDetailPage() { const selectedFeedingCircuit = selectedFeedingCircuits.find( ({ circuit }) => circuit.id === editingFeedingCircuitId ); + const feederCircuitSections = + selectedFeedingTree?.sections.filter( + (section) => section.category !== undefined + ) ?? []; + const selectedNewFeederCircuitSection = feederCircuitSections.find( + (section) => section.id === editingFeederCircuitSectionId + ); + const newFeederCircuitIdentifier = selectedNewFeederCircuitSection + ? getNextFeederCircuitIdentifier(selectedNewFeederCircuitSection) + : null; + const selectedFeedingCircuitCategory = + selectedFeedingCircuit?.section.category ?? + (isCreatingFeederCircuit + ? selectedNewFeederCircuitSection?.category + : undefined); const selectedFeedingBoard = boards.find( (board) => board.id === editingFeedingBoardId ); @@ -556,6 +585,9 @@ export default function ProjectDetailPage() { setEditingBoardInfo(toDistributionBoardInfoDraft(board)); setEditingFeedingBoardId(board.feedingDistributionBoardId ?? ""); setEditingFeedingCircuitId(board.feedingCircuitId ?? ""); + setIsCreatingFeederCircuit(false); + setEditingFeederCircuitSectionId(""); + setEditingFeederCircuitDisplayName(""); } function openBoardCreator() { @@ -584,28 +616,56 @@ export default function ProjectDetailPage() { setIsSaving(true); setError(null); try { - const result = await updateDistributionBoard( - projectId, - editingBoard.id, - { - floorId: editingBoardFloorId || null, - supplyType: editingBoardSupplyType, - simultaneityFactor, - feedingDistributionBoardId: editingFeedingBoardId || null, - feedingCircuitId: editingFeedingCircuitId || null, - ...fromDistributionBoardInfoDraft(editingBoardInfo), - }, - project.currentRevision - ); - setBoards((current) => - current.map((board) => - board.id === result.distributionBoard.id - ? result.distributionBoard - : board - ) - ); + const boardInput = { + floorId: editingBoardFloorId || null, + supplyType: editingBoardSupplyType, + simultaneityFactor, + feedingDistributionBoardId: editingFeedingBoardId || null, + feedingCircuitId: editingFeedingCircuitId || null, + ...fromDistributionBoardInfoDraft(editingBoardInfo), + }; + let result; + if (isCreatingFeederCircuit) { + if (!editingFeedingBoardId || !selectedFeedingTree) { + throw new Error("Bitte eine speisende Verteilung wählen."); + } + if (!selectedNewFeederCircuitSection) { + throw new Error( + "Bitte eine Stromkreisgruppe für den neuen Abgang wählen." + ); + } + const circuit = buildFeederCircuitSnapshot({ + id: createClientId(), + tree: selectedFeedingTree, + sectionId: selectedNewFeederCircuitSection.id, + displayName: editingFeederCircuitDisplayName, + }); + result = await createFeederCircuitAndUpdateDistributionBoard( + projectId, + editingBoard.id, + { + ...boardInput, + feedingDistributionBoardId: editingFeedingBoardId, + feedingCircuitId: circuit.id, + }, + circuit, + project.currentRevision + ); + } else { + result = await updateDistributionBoard( + projectId, + editingBoard.id, + boardInput, + project.currentRevision + ); + } applyProjectRevision(result.history.currentRevision); - setCircuitTrees((await getProjectCircuitTrees(projectId)).trees); + const [updatedBoards, updatedTrees] = await Promise.all([ + listDistributionBoards(projectId), + getProjectCircuitTrees(projectId), + ]); + setBoards(updatedBoards); + setCircuitTrees(updatedTrees.trees); setEditingBoard(null); } catch (err) { setError( @@ -1567,8 +1627,16 @@ export default function ProjectDetailPage() { className="form-select" id="feeding-distribution-board" onChange={(event) => { - setEditingFeedingBoardId(event.target.value); + const boardId = event.target.value; + setEditingFeedingBoardId(boardId); setEditingFeedingCircuitId(""); + setIsCreatingFeederCircuit(false); + setEditingFeederCircuitSectionId( + getDefaultFeederCircuitSection( + circuitTreeByBoardId.get(boardId) + )?.id ?? "" + ); + setEditingFeederCircuitDisplayName(""); }} value={editingFeedingBoardId} > @@ -1588,10 +1656,31 @@ export default function ProjectDetailPage() { className="form-select" disabled={!editingFeedingBoardId} id="feeding-circuit" - onChange={(event) => setEditingFeedingCircuitId(event.target.value)} - value={editingFeedingCircuitId} + onChange={(event) => { + if (event.target.value === createFeederCircuitSelectValue) { + setEditingFeedingCircuitId(""); + setIsCreatingFeederCircuit(true); + setEditingFeederCircuitSectionId( + (current) => + current || + getDefaultFeederCircuitSection(selectedFeedingTree)?.id || + "" + ); + return; + } + setIsCreatingFeederCircuit(false); + setEditingFeedingCircuitId(event.target.value); + }} + value={ + isCreatingFeederCircuit + ? createFeederCircuitSelectValue + : editingFeedingCircuitId + } > + {selectedFeedingCircuits.map(({ circuit }) => (