Add configurable distribution supply types

This commit is contained in:
2026-07-29 09:31:20 +02:00
parent 194bc9c0b1
commit b1a11397b3
43 changed files with 5697 additions and 126 deletions
+226 -4
View File
@@ -23,6 +23,7 @@ import {
listRooms,
importProjectTransfer,
synchronizeProjectDeviceRows,
updateDistributionBoard,
updateProjectDevice,
updateProjectSettings,
} from "../../../frontend/utils/api";
@@ -41,6 +42,10 @@ import {
projectDeviceSyncFields,
type ProjectDeviceSyncField,
} from "../../../shared/constants/project-device-sync-fields";
import {
distributionBoardSupplyTypeLabels,
type DistributionBoardSupplyType,
} from "../../../shared/constants/distribution-board";
import { ProjectVersionHistory } from "../../../frontend/components/project-version-history";
import {
ProjectSettingsModal,
@@ -74,6 +79,14 @@ export default function ProjectDetailPage() {
const [projectDevices, setProjectDevices] = useState<ProjectDeviceDto[]>([]);
const [globalDevices, setGlobalDevices] = useState<GlobalDeviceDto[]>([]);
const [boardName, setBoardName] = useState("");
const [boardFloorId, setBoardFloorId] = useState("");
const [boardSupplyType, setBoardSupplyType] =
useState<DistributionBoardSupplyType>("AV");
const [editingBoard, setEditingBoard] =
useState<DistributionBoardDto | null>(null);
const [editingBoardFloorId, setEditingBoardFloorId] = useState("");
const [editingBoardSupplyType, setEditingBoardSupplyType] =
useState<DistributionBoardSupplyType>("AV");
const [floorName, setFloorName] = useState("");
const [roomNumber, setRoomNumber] = useState("");
const [roomName, setRoomName] = useState("");
@@ -158,6 +171,24 @@ export default function ProjectDetailPage() {
() => new Map(circuitLists.map((circuitList) => [circuitList.distributionBoardId, circuitList])),
[circuitLists]
);
const enabledBoardSupplyTypes =
project?.enabledDistributionBoardSupplyTypes ?? [];
const usedBoardSupplyTypes = useMemo(
() =>
[
...new Set(
boards
.map((board) => board.supplyType)
.filter(
(
supplyType
): supplyType is DistributionBoardSupplyType =>
supplyType !== null
)
),
],
[boards]
);
function applyProjectRevision(currentRevision: number) {
setProject((current) =>
@@ -175,7 +206,11 @@ export default function ProjectDetailPage() {
try {
const result = await createDistributionBoard(
projectId,
boardName.trim(),
{
name: boardName.trim(),
floorId: boardFloorId || null,
supplyType: boardSupplyType,
},
project.currentRevision
);
setBoards((current) => [
@@ -185,6 +220,10 @@ export default function ProjectDetailPage() {
setCircuitLists(await listCircuitLists(projectId));
applyProjectRevision(result.history.currentRevision);
setBoardName("");
setBoardFloorId("");
setBoardSupplyType(
project.enabledDistributionBoardSupplyTypes[0]
);
setStructureModal(null);
} catch (err) {
setError(err instanceof Error ? err.message : "Verteilung konnte nicht erstellt werden.");
@@ -351,6 +390,60 @@ export default function ProjectDetailPage() {
}
}
function openBoardEditor(board: DistributionBoardDto) {
setEditingBoard(board);
setEditingBoardFloorId(board.floorId ?? "");
setEditingBoardSupplyType(
board.supplyType ??
project?.enabledDistributionBoardSupplyTypes[0] ??
"AV"
);
}
function openBoardCreator() {
setBoardSupplyType(
project?.enabledDistributionBoardSupplyTypes[0] ?? "AV"
);
setStructureModal("board");
}
async function handleUpdateBoard(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!projectId || !project || !editingBoard) {
return;
}
setIsSaving(true);
setError(null);
try {
const result = await updateDistributionBoard(
projectId,
editingBoard.id,
{
floorId: editingBoardFloorId || null,
supplyType: editingBoardSupplyType,
},
project.currentRevision
);
setBoards((current) =>
current.map((board) =>
board.id === result.distributionBoard.id
? result.distributionBoard
: board
)
);
applyProjectRevision(result.history.currentRevision);
setEditingBoard(null);
} catch (err) {
setError(
err instanceof Error
? err.message
: "Verteilung konnte nicht bearbeitet werden."
);
} finally {
setIsSaving(false);
}
}
async function handleExportProject() {
if (!project) {
return;
@@ -596,7 +689,7 @@ export default function ProjectDetailPage() {
</div>
<button
className="btn btn-sm btn-primary"
onClick={() => setStructureModal("board")}
onClick={openBoardCreator}
type="button"
>
Verteilung hinzufügen
@@ -607,6 +700,8 @@ export default function ProjectDetailPage() {
<thead>
<tr>
<th>Verteilung</th>
<th>Etage</th>
<th>Netzart</th>
<th className="text-end">Aktionen</th>
</tr>
</thead>
@@ -616,8 +711,28 @@ export default function ProjectDetailPage() {
return (
<tr key={board.id}>
<td>{board.name}</td>
<td>
{board.floorId
? floorById.get(board.floorId)?.name ?? "Unbekannt"
: "Ohne Etage"}
</td>
<td>
{board.supplyType
? distributionBoardSupplyTypeLabels[
board.supplyType
]
: "Nicht festgelegt"}
</td>
<td className="text-end">
<div className="d-inline-flex gap-2">
<div className="d-inline-flex flex-wrap justify-content-end gap-2">
<button
className="btn btn-sm btn-outline-secondary"
disabled={isSaving}
onClick={() => openBoardEditor(board)}
type="button"
>
Bearbeiten
</button>
{circuitList ? (
<Link
className="btn btn-sm btn-primary"
@@ -637,7 +752,7 @@ export default function ProjectDetailPage() {
})}
{!boards.length ? (
<tr>
<td colSpan={2} className="text-center text-secondary py-4">
<td colSpan={4} className="text-center text-secondary py-4">
Noch keine Verteilungen vorhanden.
</td>
</tr>
@@ -967,6 +1082,112 @@ export default function ProjectDetailPage() {
required
value={boardName}
/>
<div className="row g-3 mt-0">
<div className="col-12 col-md-6">
<label className="form-label" htmlFor="distribution-board-floor">
Etage
</label>
<select
className="form-select"
id="distribution-board-floor"
onChange={(event) => setBoardFloorId(event.target.value)}
value={boardFloorId}
>
<option value="">Ohne Etage</option>
{floors.map((floor) => (
<option key={floor.id} value={floor.id}>
{floor.name}
</option>
))}
</select>
</div>
<div className="col-12 col-md-6">
<label
className="form-label"
htmlFor="distribution-board-supply-type"
>
Netzart
</label>
<select
className="form-select"
id="distribution-board-supply-type"
onChange={(event) =>
setBoardSupplyType(
event.target.value as DistributionBoardSupplyType
)
}
value={boardSupplyType}
>
{enabledBoardSupplyTypes.map((supplyType) => (
<option key={supplyType} value={supplyType}>
{distributionBoardSupplyTypeLabels[supplyType]}
</option>
))}
</select>
</div>
</div>
</FormModal>
) : null}
{editingBoard ? (
<FormModal
description={`${editingBoard.name}: Etagenzuordnung und Netzart ändern.`}
isSaving={isSaving}
onClose={() => setEditingBoard(null)}
onSubmit={handleUpdateBoard}
submitLabel="Änderungen speichern"
title="Verteilung bearbeiten"
>
<div className="row g-3">
<div className="col-12 col-md-6">
<label
className="form-label"
htmlFor="edit-distribution-board-floor"
>
Etage
</label>
<select
autoFocus
className="form-select"
id="edit-distribution-board-floor"
onChange={(event) =>
setEditingBoardFloorId(event.target.value)
}
value={editingBoardFloorId}
>
<option value="">Ohne Etage</option>
{floors.map((floor) => (
<option key={floor.id} value={floor.id}>
{floor.name}
</option>
))}
</select>
</div>
<div className="col-12 col-md-6">
<label
className="form-label"
htmlFor="edit-distribution-board-supply-type"
>
Netzart
</label>
<select
className="form-select"
id="edit-distribution-board-supply-type"
onChange={(event) =>
setEditingBoardSupplyType(
event.target.value as DistributionBoardSupplyType
)
}
value={editingBoardSupplyType}
>
{enabledBoardSupplyTypes.map((supplyType) => (
<option key={supplyType} value={supplyType}>
{distributionBoardSupplyTypeLabels[supplyType]}
</option>
))}
</select>
</div>
</div>
</FormModal>
) : null}
@@ -1082,6 +1303,7 @@ export default function ProjectDetailPage() {
onImport={handleImportProject}
onSave={handleSaveProjectSettings}
project={project}
usedDistributionBoardSupplyTypes={usedBoardSupplyTypes}
/>
) : null}
</main>