Show logical feeder loads in project and circuit views
This commit is contained in:
parent
dfeb0e3cb6
commit
042a050876
12 changed files with 319 additions and 11 deletions
|
|
@ -56,6 +56,7 @@ import {
|
|||
disconnectProjectDeviceRows,
|
||||
exportProjectTransfer,
|
||||
getProject,
|
||||
getProjectCircuitTrees,
|
||||
getProjectDeviceSyncPreview,
|
||||
listBuildings,
|
||||
listCircuitLists,
|
||||
|
|
@ -73,6 +74,7 @@ import {
|
|||
import type {
|
||||
BuildingDto,
|
||||
CircuitListDto,
|
||||
CircuitTreeResponseDto,
|
||||
CreateRoomInput,
|
||||
CreateProjectDeviceInput,
|
||||
DistributionBoardDto,
|
||||
|
|
@ -151,6 +153,7 @@ export default function ProjectDetailPage() {
|
|||
const [project, setProject] = useState<ProjectDto | null>(null);
|
||||
const [boards, setBoards] = useState<DistributionBoardDto[]>([]);
|
||||
const [circuitLists, setCircuitLists] = useState<CircuitListDto[]>([]);
|
||||
const [circuitTrees, setCircuitTrees] = useState<CircuitTreeResponseDto[]>([]);
|
||||
const [buildings, setBuildings] = useState<BuildingDto[]>([]);
|
||||
const [floors, setFloors] = useState<FloorDto[]>([]);
|
||||
const [rooms, setRooms] = useState<RoomDto[]>([]);
|
||||
|
|
@ -170,6 +173,8 @@ export default function ProjectDetailPage() {
|
|||
setEditingBoardSimultaneityFactor,
|
||||
] = useState("1");
|
||||
const [editingBoardCopyName, setEditingBoardCopyName] = useState("");
|
||||
const [editingFeedingBoardId, setEditingFeedingBoardId] = useState("");
|
||||
const [editingFeedingCircuitId, setEditingFeedingCircuitId] = useState("");
|
||||
const [editingBoardInfo, setEditingBoardInfo] =
|
||||
useState<DistributionBoardInfoDraft>(
|
||||
toDistributionBoardInfoDraft({
|
||||
|
|
@ -209,6 +214,7 @@ export default function ProjectDetailPage() {
|
|||
getProject(projectId),
|
||||
listDistributionBoards(projectId),
|
||||
listCircuitLists(projectId),
|
||||
getProjectCircuitTrees(projectId),
|
||||
listBuildings(projectId),
|
||||
listFloors(projectId),
|
||||
listRooms(projectId),
|
||||
|
|
@ -219,6 +225,7 @@ export default function ProjectDetailPage() {
|
|||
currentProject,
|
||||
distributionBoards,
|
||||
loadedCircuitLists,
|
||||
loadedCircuitTrees,
|
||||
loadedBuildings,
|
||||
loadedFloors,
|
||||
loadedRooms,
|
||||
|
|
@ -228,6 +235,7 @@ export default function ProjectDetailPage() {
|
|||
setProject(currentProject);
|
||||
setBoards(distributionBoards);
|
||||
setCircuitLists(loadedCircuitLists);
|
||||
setCircuitTrees(loadedCircuitTrees.trees);
|
||||
setBuildings(loadedBuildings);
|
||||
setFloors(loadedFloors);
|
||||
setRooms(loadedRooms);
|
||||
|
|
@ -310,6 +318,48 @@ export default function ProjectDetailPage() {
|
|||
() => new Map(circuitLists.map((circuitList) => [circuitList.distributionBoardId, circuitList])),
|
||||
[circuitLists]
|
||||
);
|
||||
const circuitTreeByBoardId = useMemo(
|
||||
() => new Map(circuitTrees.map((tree) => [tree.distributionBoardId, tree])),
|
||||
[circuitTrees]
|
||||
);
|
||||
const feedingBoardOptions = useMemo(() => {
|
||||
if (!editingBoard) return [];
|
||||
const boardById = new Map(boards.map((board) => [board.id, board]));
|
||||
return boards.filter((candidate) => {
|
||||
if (candidate.id === editingBoard.id) return false;
|
||||
const visited = new Set<string>();
|
||||
let current: DistributionBoardDto | undefined = candidate;
|
||||
while (current && current.feedingDistributionBoardId && !visited.has(current.id)) {
|
||||
if (current.feedingDistributionBoardId === editingBoard.id) return false;
|
||||
visited.add(current.id);
|
||||
current = boardById.get(current.feedingDistributionBoardId);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, [boards, editingBoard]);
|
||||
const selectedFeedingTree = editingFeedingBoardId
|
||||
? circuitTreeByBoardId.get(editingFeedingBoardId)
|
||||
: undefined;
|
||||
const selectedFeedingCircuits = useMemo(
|
||||
() =>
|
||||
selectedFeedingTree?.sections.flatMap((section) =>
|
||||
section.circuits.map((circuit) => ({ circuit, section }))
|
||||
) ?? [],
|
||||
[selectedFeedingTree]
|
||||
);
|
||||
const selectedFeedingCircuit = selectedFeedingCircuits.find(
|
||||
({ circuit }) => circuit.id === editingFeedingCircuitId
|
||||
);
|
||||
const selectedFeedingBoard = boards.find(
|
||||
(board) => board.id === editingFeedingBoardId
|
||||
);
|
||||
const selectedFeedingCircuitUseCount = editingFeedingCircuitId
|
||||
? boards.filter(
|
||||
(board) =>
|
||||
board.id !== editingBoard?.id &&
|
||||
board.feedingCircuitId === editingFeedingCircuitId
|
||||
).length
|
||||
: 0;
|
||||
const enabledBoardSupplyTypes =
|
||||
project?.enabledDistributionBoardSupplyTypes ?? [];
|
||||
const usedBoardSupplyTypes = useMemo(
|
||||
|
|
@ -329,6 +379,21 @@ export default function ProjectDetailPage() {
|
|||
[boards]
|
||||
);
|
||||
|
||||
function formatBoardFeedingReference(board: DistributionBoardDto) {
|
||||
if (!board.feedingDistributionBoardId) return board.fedFrom ?? "–";
|
||||
const feedingBoard = boards.find(
|
||||
(candidate) => candidate.id === board.feedingDistributionBoardId
|
||||
);
|
||||
if (!feedingBoard) return "Unbekannte Verteilung";
|
||||
const boardLabel = feedingBoard.boardNumber ?? feedingBoard.name;
|
||||
if (!board.feedingCircuitId) return boardLabel;
|
||||
const circuit = circuitTreeByBoardId
|
||||
.get(feedingBoard.id)
|
||||
?.sections.flatMap((section) => section.circuits)
|
||||
.find((candidate) => candidate.id === board.feedingCircuitId);
|
||||
return `${boardLabel}${circuit?.equipmentIdentifier ?? ""}`;
|
||||
}
|
||||
|
||||
function applyProjectRevision(currentRevision: number) {
|
||||
setProject((current) =>
|
||||
current ? { ...current, currentRevision } : current
|
||||
|
|
@ -358,6 +423,7 @@ export default function ProjectDetailPage() {
|
|||
]);
|
||||
setCircuitLists(await listCircuitLists(projectId));
|
||||
applyProjectRevision(result.history.currentRevision);
|
||||
setCircuitTrees((await getProjectCircuitTrees(projectId)).trees);
|
||||
setBoardName("");
|
||||
setBoardFloorId("");
|
||||
setBoardSupplyType(
|
||||
|
|
@ -488,6 +554,8 @@ export default function ProjectDetailPage() {
|
|||
);
|
||||
setEditingBoardCopyName(`${board.name} Kopie`);
|
||||
setEditingBoardInfo(toDistributionBoardInfoDraft(board));
|
||||
setEditingFeedingBoardId(board.feedingDistributionBoardId ?? "");
|
||||
setEditingFeedingCircuitId(board.feedingCircuitId ?? "");
|
||||
}
|
||||
|
||||
function openBoardCreator() {
|
||||
|
|
@ -523,6 +591,8 @@ export default function ProjectDetailPage() {
|
|||
floorId: editingBoardFloorId || null,
|
||||
supplyType: editingBoardSupplyType,
|
||||
simultaneityFactor,
|
||||
feedingDistributionBoardId: editingFeedingBoardId || null,
|
||||
feedingCircuitId: editingFeedingCircuitId || null,
|
||||
...fromDistributionBoardInfoDraft(editingBoardInfo),
|
||||
},
|
||||
project.currentRevision
|
||||
|
|
@ -535,6 +605,7 @@ export default function ProjectDetailPage() {
|
|||
)
|
||||
);
|
||||
applyProjectRevision(result.history.currentRevision);
|
||||
setCircuitTrees((await getProjectCircuitTrees(projectId)).trees);
|
||||
setEditingBoard(null);
|
||||
} catch (err) {
|
||||
setError(
|
||||
|
|
@ -590,6 +661,7 @@ export default function ProjectDetailPage() {
|
|||
result.circuitList,
|
||||
]);
|
||||
applyProjectRevision(result.history.currentRevision);
|
||||
setCircuitTrees((await getProjectCircuitTrees(projectId)).trees);
|
||||
setEditingBoard(null);
|
||||
} catch (err) {
|
||||
setError(
|
||||
|
|
@ -633,6 +705,7 @@ export default function ProjectDetailPage() {
|
|||
)
|
||||
);
|
||||
applyProjectRevision(result.history.currentRevision);
|
||||
setCircuitTrees((await getProjectCircuitTrees(projectId)).trees);
|
||||
setEditingBoard(null);
|
||||
} catch (err) {
|
||||
setError(
|
||||
|
|
@ -989,6 +1062,7 @@ export default function ProjectDetailPage() {
|
|||
<th>Örtlichkeit</th>
|
||||
<th>Etage</th>
|
||||
<th>Netzart</th>
|
||||
<th>Gespeist von</th>
|
||||
<th>GZF</th>
|
||||
<th className="text-end">Aktionen</th>
|
||||
</tr>
|
||||
|
|
@ -1015,6 +1089,7 @@ export default function ProjectDetailPage() {
|
|||
]
|
||||
: "Nicht festgelegt"}
|
||||
</td>
|
||||
<td>{formatBoardFeedingReference(board)}</td>
|
||||
<td>
|
||||
{board.simultaneityFactor.toLocaleString(
|
||||
"de-DE",
|
||||
|
|
@ -1392,7 +1467,7 @@ export default function ProjectDetailPage() {
|
|||
|
||||
{editingBoard ? (
|
||||
<FormModal
|
||||
description={`${editingBoard.name}: Etagenzuordnung, Netzart und Gleichzeitigkeitsfaktor ändern.`}
|
||||
description={`${editingBoard.name}: Etagenzuordnung, Netzart, Einspeisung und Gleichzeitigkeitsfaktor ändern.`}
|
||||
isSaving={isSaving}
|
||||
onClose={() => setEditingBoard(null)}
|
||||
onSubmit={handleUpdateBoard}
|
||||
|
|
@ -1479,6 +1554,91 @@ export default function ProjectDetailPage() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<section className="card mt-4" aria-labelledby="feeding-reference-heading">
|
||||
<div className="card-header" id="feeding-reference-heading">
|
||||
Logische Einspeisung
|
||||
</div>
|
||||
<div className="card-body row g-3">
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="feeding-distribution-board">
|
||||
Speisende Verteilung
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="feeding-distribution-board"
|
||||
onChange={(event) => {
|
||||
setEditingFeedingBoardId(event.target.value);
|
||||
setEditingFeedingCircuitId("");
|
||||
}}
|
||||
value={editingFeedingBoardId}
|
||||
>
|
||||
<option value="">Keine Projektverteilung</option>
|
||||
{feedingBoardOptions.map((board) => (
|
||||
<option key={board.id} value={board.id}>
|
||||
{board.boardNumber ?? board.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="feeding-circuit">
|
||||
Speisender Abgang
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
disabled={!editingFeedingBoardId}
|
||||
id="feeding-circuit"
|
||||
onChange={(event) => setEditingFeedingCircuitId(event.target.value)}
|
||||
value={editingFeedingCircuitId}
|
||||
>
|
||||
<option value="">Nur Verteilung bekannt</option>
|
||||
{selectedFeedingCircuits.map(({ circuit }) => (
|
||||
<option key={circuit.id} value={circuit.id}>
|
||||
{circuit.equipmentIdentifier}
|
||||
{circuit.displayName ? ` · ${circuit.displayName}` : ""}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{editingFeedingBoardId && !editingFeedingCircuitId ? (
|
||||
<div className="col-12">
|
||||
<div className="alert alert-warning mb-0 py-2">
|
||||
Ohne speisenden Abgang wird die Hierarchie angezeigt, die
|
||||
Leistung dieser Verteilung aber keinem Stromkreis zugerechnet.
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedFeedingBoard?.supplyType &&
|
||||
editingBoardSupplyType !== selectedFeedingBoard.supplyType ? (
|
||||
<div className="col-12">
|
||||
<div className="alert alert-warning mb-0 py-2">
|
||||
Die Netzarten weichen ab: {selectedFeedingBoard.supplyType}
|
||||
{" → "}{editingBoardSupplyType}. Bitte Umschaltung und
|
||||
Versorgungskonzept prüfen.
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedFeedingCircuit &&
|
||||
selectedFeedingCircuit.section.category !== "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
|
||||
Einspeisung für die Unterverteilung geeignet ist.
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{selectedFeedingCircuitUseCount > 0 ? (
|
||||
<div className="col-12">
|
||||
<div className="alert alert-info mb-0 py-2">
|
||||
Dieser Abgang speist bereits {selectedFeedingCircuitUseCount}
|
||||
{selectedFeedingCircuitUseCount === 1
|
||||
? " weitere Verteilung."
|
||||
: " weitere Verteilungen."}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
<div className="mt-4">
|
||||
<DistributionBoardInfoCard
|
||||
draft={editingBoardInfo}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue