Add configurable distribution supply types
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user