Create feeder circuits while linking boards

This commit is contained in:
Julian Appel 2026-09-28 22:14:30 +02:00
parent 552362bad1
commit ade21fe386
11 changed files with 960 additions and 40 deletions

View file

@ -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<DistributionBoardInfoDraft>(
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
}
>
<option value="">Nur Verteilung bekannt</option>
<option value={createFeederCircuitSelectValue}>
+ Neuen Einspeiseabgang anlegen …
</option>
{selectedFeedingCircuits.map(({ circuit }) => (
<option key={circuit.id} value={circuit.id}>
{circuit.equipmentIdentifier}
@ -1600,7 +1689,62 @@ export default function ProjectDetailPage() {
))}
</select>
</div>
{editingFeedingBoardId && !editingFeedingCircuitId ? (
{isCreatingFeederCircuit ? (
<div className="col-12">
<div className="border rounded p-3">
<div className="row g-3">
<div className="col-12 col-md-6">
<label className="form-label" htmlFor="new-feeder-circuit-section">
Stromkreisgruppe
</label>
<select
className="form-select"
id="new-feeder-circuit-section"
onChange={(event) =>
setEditingFeederCircuitSectionId(event.target.value)
}
required
value={editingFeederCircuitSectionId}
>
{feederCircuitSections.map((section) => (
<option key={section.id} value={section.id}>
{section.displayName}
</option>
))}
</select>
<div className="form-text">
Vorgesehenes BMK: {newFeederCircuitIdentifier ?? "–"}
</div>
</div>
<div className="col-12 col-md-6">
<label className="form-label" htmlFor="new-feeder-circuit-name">
Abgangsbezeichnung (optional)
</label>
<input
className="form-control"
id="new-feeder-circuit-name"
onChange={(event) =>
setEditingFeederCircuitDisplayName(event.target.value)
}
placeholder="z. B. Steigetrasse Ost"
value={editingFeederCircuitDisplayName}
/>
<div className="form-text">
Ohne Zusatzbezeichnung wird der Name der gespeisten
Verteilung angezeigt.
</div>
</div>
</div>
<div className="form-text mt-2">
Der Abgang wird erst beim Speichern angelegt und gemeinsam
mit dieser Verteilung verknüpft. Es entsteht keine Gerätezeile.
</div>
</div>
</div>
) : null}
{editingFeedingBoardId &&
!editingFeedingCircuitId &&
!isCreatingFeederCircuit ? (
<div className="col-12">
<div className="alert alert-warning mb-0 py-2">
Ohne speisenden Abgang wird die Hierarchie angezeigt, die
@ -1618,8 +1762,8 @@ export default function ProjectDetailPage() {
</div>
</div>
) : null}
{selectedFeedingCircuit &&
selectedFeedingCircuit.section.category !== "three_phase" ? (
{selectedFeedingCircuitCategory !== undefined &&
selectedFeedingCircuitCategory !== "three_phase" ? (
<div className="col-12">
<div className="alert alert-warning mb-0 py-2">
Der gewählte Abgang ist 1-phasig. Bitte prüfen, ob diese