forked from jappel/leistungsbilanz-ts
Merge origin/main into feature/user-management
Renumber the users migration to 0013_users so it follows the upstream 0012_good_gateway (logical board feeds); AGENTS.md deferred-work list keeps both the new feed items and the auth items. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
95bfba2f67
63 changed files with 5546 additions and 443 deletions
|
|
@ -22,6 +22,9 @@ export function deriveCircuitPhase(
|
|||
* Rows without cos φ count as 1; a circuit without load yields 1.
|
||||
*/
|
||||
export function deriveCircuitCosPhi(circuit: CircuitTreeCircuitDto): number {
|
||||
if (Number.isFinite(circuit.circuitCosPhi) && circuit.circuitCosPhi > 0) {
|
||||
return Math.round(circuit.circuitCosPhi * 100) / 100;
|
||||
}
|
||||
let activePowerKw = 0;
|
||||
let apparentPowerKva = 0;
|
||||
for (const row of circuit.deviceRows) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { DragEvent, KeyboardEvent, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
ArrowDown,
|
||||
|
|
@ -3293,6 +3294,23 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||
threePhaseVoltageV={data.threePhaseVoltageV}
|
||||
/>
|
||||
) : null}
|
||||
{data.feedingReference ? (
|
||||
<div className="alert alert-info py-2" role="status">
|
||||
Gespeist von{" "}
|
||||
<Link
|
||||
href={`/projects/${projectId}/circuit-lists/${
|
||||
data.feedingReference.circuitListId
|
||||
}/tree-edit`}
|
||||
>
|
||||
{data.feedingReference.distributionBoardNumber ??
|
||||
data.feedingReference.distributionBoardName}
|
||||
{data.feedingReference.circuitEquipmentIdentifier ?? ""}
|
||||
</Link>
|
||||
{data.feedingReference.circuitId
|
||||
? null
|
||||
: " (speisender Stromkreis noch nicht festgelegt)"}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="editor-toolbar">
|
||||
<IconButton
|
||||
disabled={!historyState || historyState.undoDepth === 0 || historyBusy || isSaving}
|
||||
|
|
@ -4062,6 +4080,8 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||
? "summary-row"
|
||||
: row.rowType === "deviceRow"
|
||||
? "device-row"
|
||||
: row.rowType === "downstreamBoardLoad"
|
||||
? "downstream-board-load-row"
|
||||
: row.rowType === "reserveCircuit"
|
||||
? "empty-circuit-row"
|
||||
: row.rowType === "placeholder"
|
||||
|
|
@ -4667,7 +4687,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||
: ""
|
||||
}`}
|
||||
>
|
||||
{row.circuit && row.rowType !== "deviceRow" ? (
|
||||
{row.circuit &&
|
||||
(row.rowType === "circuitCompact" ||
|
||||
row.rowType === "circuitSummary" ||
|
||||
row.rowType === "reserveCircuit") ? (
|
||||
<>
|
||||
<IconButton
|
||||
disabled={isSaving}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export type DistributionBoardInfoDraft = Record<DistributionBoardInfoField, stri
|
|||
const placeholders: Record<DistributionBoardInfoField, string> = {
|
||||
boardNumber: "z. B. UV 1.1",
|
||||
location: "z. B. Raum 003 Technik",
|
||||
fedFrom: "z. B. HV 0.1 oder HAK / Zähleranlage",
|
||||
fedFrom: "z. B. HAK, Zähleranlage, Trafo oder NEA",
|
||||
remark: "",
|
||||
};
|
||||
|
||||
|
|
@ -98,8 +98,8 @@ export function DistributionBoardInfoCard({
|
|||
)}
|
||||
{field === "fedFrom" ? (
|
||||
<div className="form-text">
|
||||
Vorerst Freitext. Die logische Verknüpfung mit dem
|
||||
speisenden Verteiler folgt.
|
||||
Für externe Einspeisungen oder ergänzende Hinweise. Eine
|
||||
speisende Projektverteilung wird oben logisch verknüpft.
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ export interface PrintBoardSummary {
|
|||
circuitCount: number;
|
||||
/** Σ quantity × power per unit, without any simultaneity factor. */
|
||||
installedPowerKw: number;
|
||||
/** Σ row totals, i.e. with the device-row simultaneity factors. */
|
||||
devicePowerKw: number;
|
||||
/** Device-row demand plus downstream-board demand before this board's GZF. */
|
||||
powerBeforeBoardSimultaneityKw: number;
|
||||
simultaneityFactor: number;
|
||||
/** Device power with the board simultaneity factor applied. */
|
||||
/** Total active power with this board's simultaneity factor applied. */
|
||||
demandPowerKw: number;
|
||||
/**
|
||||
* Symmetrical three-phase design current from the apparent power
|
||||
* S = Σ(row total / cos φ) × board GZF. A missing cos φ counts as 1.
|
||||
* Design current from the apparent power including downstream boards.
|
||||
* The selected feeder circuit determines whether one or three phases apply.
|
||||
*/
|
||||
designCurrentA: number;
|
||||
}
|
||||
|
|
@ -46,8 +46,13 @@ export function summarizePrintBoard(input: {
|
|||
circuitCount += 1;
|
||||
for (const row of circuit.deviceRows) {
|
||||
installedPowerKw += row.quantity * row.powerPerUnit;
|
||||
apparentPowerKva += row.rowTotalPower / (row.cosPhi || 1);
|
||||
}
|
||||
apparentPowerKva +=
|
||||
circuit.circuitApparentPower ??
|
||||
circuit.deviceRows.reduce(
|
||||
(sum, row) => sum + row.rowTotalPower / (row.cosPhi || 1),
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
const simultaneityFactor = tree.distributionBoardSimultaneityFactor;
|
||||
|
|
@ -55,12 +60,13 @@ export function summarizePrintBoard(input: {
|
|||
...input,
|
||||
circuitCount,
|
||||
installedPowerKw,
|
||||
devicePowerKw: tree.distributionBoardTotalPower,
|
||||
powerBeforeBoardSimultaneityKw: tree.distributionBoardTotalPower,
|
||||
simultaneityFactor,
|
||||
demandPowerKw: tree.distributionBoardTotalPowerWithSimultaneityFactor,
|
||||
designCurrentA:
|
||||
(apparentPowerKva * simultaneityFactor * 1000) /
|
||||
(Math.sqrt(3) * tree.threePhaseVoltageV),
|
||||
designCurrentA: calculateDesignCurrent(
|
||||
apparentPowerKva * simultaneityFactor,
|
||||
tree
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -68,8 +74,6 @@ export interface PrintBoardHierarchyEntry {
|
|||
summary: PrintBoardSummary;
|
||||
depth: number;
|
||||
parentBoardId: string | null;
|
||||
/** Own demand plus the plain sum of all resolved downstream boards. */
|
||||
cumulatedDemandPowerKw: number;
|
||||
}
|
||||
|
||||
export function assertPrintRevisionConsistency(
|
||||
|
|
@ -82,31 +86,21 @@ export function assertPrintRevisionConsistency(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PRELIMINARY: `fedFrom` is free text until the logical feed link is
|
||||
* decided. A board is shown below another board only when its `fedFrom`
|
||||
* text equals that board's number or name (case-insensitive). The
|
||||
* cumulated value is a plain sum without an upstream simultaneity factor.
|
||||
* Unresolved texts and cycles keep a board at the top level.
|
||||
*/
|
||||
export function buildPreliminaryBoardHierarchy(
|
||||
export function buildBoardHierarchy(
|
||||
summaries: PrintBoardSummary[]
|
||||
): PrintBoardHierarchyEntry[] {
|
||||
const normalize = (value: string | null) =>
|
||||
value?.trim().toLocaleLowerCase("de") || null;
|
||||
const summaryById = new Map(
|
||||
summaries.map((summary) => [summary.board.id, summary])
|
||||
);
|
||||
const parentById = new Map<string, string | null>();
|
||||
for (const summary of summaries) {
|
||||
const fedFrom = normalize(summary.board.fedFrom);
|
||||
const candidates = fedFrom
|
||||
? summaries.filter(
|
||||
(candidate) =>
|
||||
candidate !== summary &&
|
||||
(normalize(candidate.board.boardNumber) === fedFrom ||
|
||||
normalize(candidate.board.name) === fedFrom)
|
||||
)
|
||||
: [];
|
||||
const parent = candidates.length === 1 ? candidates[0] : undefined;
|
||||
parentById.set(summary.board.id, parent?.board.id ?? null);
|
||||
const parentId = summary.board.feedingDistributionBoardId;
|
||||
parentById.set(
|
||||
summary.board.id,
|
||||
parentId && parentId !== summary.board.id && summaryById.has(parentId)
|
||||
? parentId
|
||||
: null
|
||||
);
|
||||
}
|
||||
for (const summary of summaries) {
|
||||
const visited = new Set<string>([summary.board.id]);
|
||||
|
|
@ -125,20 +119,12 @@ export function buildPreliminaryBoardHierarchy(
|
|||
summaries.filter(
|
||||
(summary) => parentById.get(summary.board.id) === boardId
|
||||
);
|
||||
const cumulated = (summary: PrintBoardSummary): number =>
|
||||
summary.demandPowerKw +
|
||||
childrenOf(summary.board.id).reduce(
|
||||
(sum, child) => sum + cumulated(child),
|
||||
0
|
||||
);
|
||||
|
||||
const entries: PrintBoardHierarchyEntry[] = [];
|
||||
const walk = (summary: PrintBoardSummary, depth: number) => {
|
||||
entries.push({
|
||||
summary,
|
||||
depth,
|
||||
parentBoardId: parentById.get(summary.board.id) ?? null,
|
||||
cumulatedDemandPowerKw: cumulated(summary),
|
||||
});
|
||||
childrenOf(summary.board.id).forEach((child) => walk(child, depth + 1));
|
||||
};
|
||||
|
|
@ -146,6 +132,25 @@ export function buildPreliminaryBoardHierarchy(
|
|||
return entries;
|
||||
}
|
||||
|
||||
export function formatFeedingReference(summary: PrintBoardSummary): string {
|
||||
const reference = summary.tree.feedingReference;
|
||||
if (!reference) return summary.board.fedFrom ?? "–";
|
||||
return `${
|
||||
reference.distributionBoardNumber ?? reference.distributionBoardName
|
||||
}${reference.circuitEquipmentIdentifier ?? ""}`;
|
||||
}
|
||||
|
||||
function calculateDesignCurrent(
|
||||
apparentPowerKva: number,
|
||||
tree: CircuitTreeResponseDto
|
||||
) {
|
||||
const category = tree.feedingReference?.circuitCategory;
|
||||
return category === "lighting" || category === "single_phase"
|
||||
? (apparentPowerKva * 1000) / tree.singlePhaseVoltageV
|
||||
: (apparentPowerKva * 1000) /
|
||||
(Math.sqrt(3) * tree.threePhaseVoltageV);
|
||||
}
|
||||
|
||||
const kilowattFormat = new Intl.NumberFormat("de-DE", {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
formatCircuitCable,
|
||||
formatCurrent,
|
||||
formatFactor,
|
||||
formatFeedingReference,
|
||||
formatKilowatt,
|
||||
formatProtectionDevice,
|
||||
formatUnitPower,
|
||||
|
|
@ -269,8 +270,8 @@ function OverviewSheet({
|
|||
stand: string;
|
||||
}) {
|
||||
const roots = entries.filter((entry) => entry.parentBoardId === null);
|
||||
// Top-level boards whose "fed from" text names no other board are grouped
|
||||
// below that text as an external feed (e.g. "HAK / Zähleranlage").
|
||||
// Root boards may still carry an external feed description such as
|
||||
// "HAK / Zähleranlage"; logical board links determine the actual tree.
|
||||
const feedGroups = new Map<string, PrintBoardHierarchyEntry[]>();
|
||||
for (const root of roots) {
|
||||
const feed = root.summary.board.fedFrom ?? "";
|
||||
|
|
@ -320,11 +321,10 @@ function OverviewSheet({
|
|||
<th>Nr.</th><th>Bezeichnung</th><th>Örtlichkeit</th><th>Etage</th><th>Netz</th><th>Gespeist von</th>
|
||||
<th className="pd-r">SK</th>
|
||||
<th className="pd-r">P<sub>inst</sub><br />[kW]</th>
|
||||
<th className="pd-r">P<sub>Geräte</sub><br />[kW]</th>
|
||||
<th className="pd-r">P vor Vert.-GZF<br />[kW]</th>
|
||||
<th className="pd-r">GZF<br />Vert.</th>
|
||||
<th className="pd-r">P<sub>gl</sub><br />[kW]</th>
|
||||
<th className="pd-r">P<sub>gl</sub> inkl. UV<br />[kW]</th>
|
||||
<th className="pd-r">I<sub>B</sub><br />[A]</th>
|
||||
<th className="pd-r">Σ inkl.<br />unterlagert [kW]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
@ -340,41 +340,45 @@ function OverviewSheet({
|
|||
<td>{board.location ?? "–"}</td>
|
||||
<td>{entry.summary.floorName ?? "–"}</td>
|
||||
<td>{board.supplyType ?? "–"}</td>
|
||||
<td>{board.fedFrom ?? "–"}</td>
|
||||
<td>{formatFeedingReference(entry.summary)}</td>
|
||||
<td className="pd-r">{entry.summary.circuitCount}</td>
|
||||
<td className="pd-r">{formatKilowatt(entry.summary.installedPowerKw)}</td>
|
||||
<td className="pd-r">{formatKilowatt(entry.summary.devicePowerKw)}</td>
|
||||
<td className="pd-r">{formatKilowatt(entry.summary.powerBeforeBoardSimultaneityKw)}</td>
|
||||
<td className="pd-r">{formatFactor(entry.summary.simultaneityFactor)}</td>
|
||||
<td className="pd-r"><strong>{formatKilowatt(entry.summary.demandPowerKw)}</strong></td>
|
||||
<td className="pd-r">{formatCurrent(entry.summary.designCurrentA)}</td>
|
||||
<td className="pd-r">{formatKilowatt(entry.cumulatedDemandPowerKw)}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colSpan={7}>Summe aller Verteiler (ohne übergeordneten GZF)</td>
|
||||
<td colSpan={7}>Summe der Einspeisungen</td>
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.installedPowerKw))}</td>
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.devicePowerKw))}</td>
|
||||
<td />
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.demandPowerKw))}</td>
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) =>
|
||||
entry.parentBoardId === null
|
||||
? entry.summary.powerBeforeBoardSimultaneityKw
|
||||
: 0
|
||||
))}</td>
|
||||
<td />
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) =>
|
||||
entry.parentBoardId === null ? entry.summary.demandPowerKw : 0
|
||||
))}</td>
|
||||
<td />
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<p className="pd-note">
|
||||
<strong>Vorläufig:</strong> „Gespeist von“ ist noch ein Freitextfeld. Struktur und
|
||||
„Σ inkl. unterlagert“ werden nur über eindeutig passende Verteilernummern bzw.
|
||||
-bezeichnungen zugeordnet und einfach addiert (kein übergeordneter GZF).
|
||||
Mehrdeutige Angaben bleiben ohne Zuordnung. Die fachliche
|
||||
Verknüpfung und Summierung folgt.
|
||||
Die Struktur folgt den logischen Verknüpfungen. Eine Unterverteilung
|
||||
wird im gewählten Abgang mit ihrer Leistung nach eigenem
|
||||
Verteiler-GZF berücksichtigt. Ohne gewählten Abgang erscheint sie
|
||||
nur in der Hierarchie.
|
||||
</p>
|
||||
<p className="pd-legend">
|
||||
P<sub>inst</sub> = Anzahl × Leistung je Stück · P<sub>Geräte</sub> = mit Geräte-GZF ·
|
||||
P<sub>gl</sub> = mit Verteiler-GZF · I<sub>B</sub> = S / (√3 · {project.threePhaseVoltageV} V),
|
||||
S aus cos φ je Gerätezeile
|
||||
P<sub>inst</sub> = lokale Geräte ohne GZF · P vor Vert.-GZF =
|
||||
Geräte-GZF einschließlich gespeister Unterverteilungen · P<sub>gl</sub> =
|
||||
zusätzlich mit Verteiler-GZF · I<sub>B</sub> aus S und der Phasenart
|
||||
des speisenden Abgangs
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -404,6 +408,7 @@ function ComponentRow({ component }: { component: CircuitTreeComponentDto }) {
|
|||
|
||||
function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
||||
const rows = circuit.deviceRows;
|
||||
const downstreamBoards = circuit.downstreamBoardLoads ?? [];
|
||||
const circuitCells = (
|
||||
<>
|
||||
<td>{formatProtectionDevice(circuit.protectionDevice) ?? <Open />}</td>
|
||||
|
|
@ -414,7 +419,7 @@ function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
|||
<td>{circuit.remark ?? ""}</td>
|
||||
</>
|
||||
);
|
||||
if (rows.length === 0) {
|
||||
if (rows.length === 0 && downstreamBoards.length === 0) {
|
||||
return (
|
||||
<tr className="pd-circuit pd-reserve">
|
||||
<td className="pd-bmk">{circuit.equipmentIdentifier}</td>
|
||||
|
|
@ -426,7 +431,7 @@ function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
|||
</tr>
|
||||
);
|
||||
}
|
||||
if (rows.length === 1) {
|
||||
if (rows.length === 1 && downstreamBoards.length === 0) {
|
||||
const row = rows[0];
|
||||
const title = circuit.displayName ?? row.displayName;
|
||||
return (
|
||||
|
|
@ -468,6 +473,16 @@ function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
|||
<td colSpan={4} />
|
||||
</tr>
|
||||
))}
|
||||
{downstreamBoards.map((load) => (
|
||||
<tr className="pd-device" key={`downstream:${load.distributionBoardId}`}>
|
||||
<td />
|
||||
<td>Unterverteilung {load.boardNumber ?? load.name}</td>
|
||||
<td>–</td>
|
||||
<td /><td /><td />
|
||||
<td className="pd-r">{formatKilowatt(load.activePowerKw)}</td>
|
||||
<td colSpan={4}>Automatisch aus der verknüpften Verteilung</td>
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -501,7 +516,7 @@ function BoardPlanSheet({
|
|||
rows={[
|
||||
["Örtlichkeit", [board.location, summary.floorName].filter(Boolean).join(" · ") || "–"],
|
||||
["Netzart", board.supplyType ?? "–"],
|
||||
["Gespeist von", board.fedFrom ?? "–"],
|
||||
["Gespeist von", formatFeedingReference(summary)],
|
||||
[
|
||||
<>P<sub>gl</sub></>,
|
||||
<>
|
||||
|
|
@ -542,7 +557,7 @@ function BoardPlanSheet({
|
|||
<td colSpan={5}>
|
||||
Summe Verteiler (P<sub>inst</sub> {formatKilowatt(summary.installedPowerKw)} kW)
|
||||
</td>
|
||||
<td className="pd-r">{formatKilowatt(summary.devicePowerKw)}</td>
|
||||
<td className="pd-r">{formatKilowatt(summary.powerBeforeBoardSimultaneityKw)}</td>
|
||||
<td colSpan={4} />
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
|||
|
|
@ -195,6 +195,8 @@ export interface DistributionBoardDto extends DistributionBoardInfoDto {
|
|||
floorId: string | null;
|
||||
supplyType: DistributionBoardSupplyType | null;
|
||||
simultaneityFactor: number;
|
||||
feedingDistributionBoardId: string | null;
|
||||
feedingCircuitId: string | null;
|
||||
}
|
||||
|
||||
export interface DocumentLogoMetadataDto {
|
||||
|
|
@ -442,7 +444,10 @@ export interface CircuitTreeCircuitDto {
|
|||
isReserve: boolean;
|
||||
remark?: string;
|
||||
circuitTotalPower: number;
|
||||
circuitApparentPower: number;
|
||||
circuitCosPhi: number;
|
||||
deviceRows: CircuitTreeDeviceRowDto[];
|
||||
downstreamBoardLoads: CircuitTreeDownstreamBoardLoadDto[];
|
||||
protectionDevice?: CircuitTreeProtectionDeviceDto;
|
||||
}
|
||||
|
||||
|
|
@ -461,6 +466,11 @@ export interface CircuitTreeSectionDto {
|
|||
|
||||
export interface CircuitTreeResponseDto {
|
||||
circuitListId: string;
|
||||
distributionBoardId: string;
|
||||
distributionBoardName: string;
|
||||
distributionBoardNumber?: string;
|
||||
distributionBoardSupplyType?: DistributionBoardSupplyType;
|
||||
feedingReference?: CircuitTreeFeedingReferenceDto;
|
||||
currentRevision: number;
|
||||
singlePhaseVoltageV: number;
|
||||
threePhaseVoltageV: number;
|
||||
|
|
@ -472,6 +482,32 @@ export interface CircuitTreeResponseDto {
|
|||
footerComponents: CircuitTreeComponentDto[];
|
||||
}
|
||||
|
||||
export interface CircuitTreeDownstreamBoardLoadDto {
|
||||
distributionBoardId: string;
|
||||
name: string;
|
||||
boardNumber?: string;
|
||||
supplyType?: DistributionBoardSupplyType;
|
||||
activePowerKw: number;
|
||||
apparentPowerKva: number;
|
||||
cosPhi: number;
|
||||
}
|
||||
|
||||
export interface CircuitTreeFeedingReferenceDto {
|
||||
distributionBoardId: string;
|
||||
circuitListId: string;
|
||||
distributionBoardName: string;
|
||||
distributionBoardNumber?: string;
|
||||
circuitId?: string;
|
||||
circuitEquipmentIdentifier?: string;
|
||||
circuitDisplayName?: string;
|
||||
circuitCategory?: CircuitGroupCategory;
|
||||
}
|
||||
|
||||
export interface ProjectCircuitTreesResponseDto {
|
||||
currentRevision: number;
|
||||
trees: CircuitTreeResponseDto[];
|
||||
}
|
||||
|
||||
export interface ExternalCircuitListObjectDto {
|
||||
id: string;
|
||||
ifcGuid: string;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import type {
|
|||
ProjectDto,
|
||||
RoomDto,
|
||||
CircuitTreeResponseDto,
|
||||
ProjectCircuitTreesResponseDto,
|
||||
ExternalCsvConfigurationSnapshotDto,
|
||||
ExternalCsvPreviewDto,
|
||||
ExternalInitialImportPlanDto,
|
||||
|
|
@ -372,7 +373,13 @@ export function updateDistributionBoard(
|
|||
floorId: string | null;
|
||||
supplyType: NonNullable<DistributionBoardDto["supplyType"]>;
|
||||
simultaneityFactor: number;
|
||||
} & Partial<DistributionBoardInfoDto>,
|
||||
} & Partial<DistributionBoardInfoDto> &
|
||||
Partial<
|
||||
Pick<
|
||||
DistributionBoardDto,
|
||||
"feedingDistributionBoardId" | "feedingCircuitId"
|
||||
>
|
||||
>,
|
||||
expectedRevision: number
|
||||
) {
|
||||
return request<DistributionBoardCommandResultDto>(
|
||||
|
|
@ -523,6 +530,12 @@ export function getCircuitTree(projectId: string, circuitListId: string) {
|
|||
return request<CircuitTreeResponseDto>(`/api/projects/${projectId}/circuit-lists/${circuitListId}/tree`);
|
||||
}
|
||||
|
||||
export function getProjectCircuitTrees(projectId: string) {
|
||||
return request<ProjectCircuitTreesResponseDto>(
|
||||
`/api/projects/${projectId}/circuit-trees`
|
||||
);
|
||||
}
|
||||
|
||||
export function insertDistributionBoardComponentCommand(
|
||||
projectId: string,
|
||||
expectedRevision: number,
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export type RowType =
|
|||
| "circuitCompact"
|
||||
| "circuitSummary"
|
||||
| "deviceRow"
|
||||
| "downstreamBoardLoad"
|
||||
| "reserveCircuit"
|
||||
| "placeholder"
|
||||
| "footerComponent";
|
||||
|
|
@ -614,6 +615,7 @@ export function getCellKind(rowType: RowType, key: CellKey): CellKind {
|
|||
return "readonly";
|
||||
}
|
||||
if (rowType === "deviceRow") return deviceFieldKeys.has(key) ? "deviceField" : "readonly";
|
||||
if (rowType === "downstreamBoardLoad") return "readonly";
|
||||
if (rowType === "circuitSummary") return circuitFieldKeys.has(key) ? "circuitField" : "readonly";
|
||||
if (rowType === "reserveCircuit" || rowType === "circuitCompact") {
|
||||
if (deviceFieldKeys.has(key)) return "deviceField";
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type {
|
|||
CircuitTreeCircuitDto,
|
||||
CircuitTreeComponentDto,
|
||||
CircuitTreeDeviceRowDto,
|
||||
CircuitTreeDownstreamBoardLoadDto,
|
||||
CircuitTreeSectionDto,
|
||||
} from "../types.js";
|
||||
import { buildCircuitStructureProjection } from "./circuit-structure-projection";
|
||||
|
|
@ -45,6 +46,7 @@ export interface VisibleGridRow {
|
|||
circuit?: CircuitTreeCircuitDto;
|
||||
device?: CircuitTreeDeviceRowDto;
|
||||
component?: CircuitTreeComponentDto;
|
||||
downstreamBoardLoad?: CircuitTreeDownstreamBoardLoadDto;
|
||||
cells: VisibleGridCell[];
|
||||
}
|
||||
|
||||
|
|
@ -126,13 +128,16 @@ function makeVisibleGridRow(
|
|||
rowType: RowType,
|
||||
sectionId: string,
|
||||
circuit?: CircuitTreeCircuitDto,
|
||||
device?: CircuitTreeDeviceRowDto
|
||||
device?: CircuitTreeDeviceRowDto,
|
||||
downstreamBoardLoad?: CircuitTreeDownstreamBoardLoadDto
|
||||
): VisibleGridRow {
|
||||
const rowKey =
|
||||
rowType === "placeholder"
|
||||
? `placeholder:${sectionId}`
|
||||
: rowType === "deviceRow" && device
|
||||
? `device:${device.id}`
|
||||
: rowType === "downstreamBoardLoad" && downstreamBoardLoad
|
||||
? `downstream-board:${circuit?.id}:${downstreamBoardLoad.distributionBoardId}`
|
||||
: `${rowType}:${circuit?.id ?? sectionId}`;
|
||||
|
||||
const cells = allColumns.map((column): VisibleGridCell => {
|
||||
|
|
@ -140,7 +145,17 @@ function makeVisibleGridRow(
|
|||
const editable = kind === "circuitField" || kind === "deviceField";
|
||||
let value: GridValue;
|
||||
|
||||
if (rowType === "placeholder") {
|
||||
if (rowType === "downstreamBoardLoad" && downstreamBoardLoad) {
|
||||
value = column.key === "displayName"
|
||||
? `Unterverteilung ${downstreamBoardLoad.boardNumber ?? downstreamBoardLoad.name}`
|
||||
: column.key === "rowTotalPower"
|
||||
? downstreamBoardLoad.activePowerKw
|
||||
: column.key === "cosPhi"
|
||||
? downstreamBoardLoad.cosPhi
|
||||
: column.key === "remark"
|
||||
? "Automatisch aus der verknüpften Verteilung"
|
||||
: undefined;
|
||||
} else if (rowType === "placeholder") {
|
||||
value = column.key === "equipmentIdentifier" ? "-frei-" : undefined;
|
||||
} else if (kind === "deviceField" && device) {
|
||||
value = getDeviceValue(device, column.key);
|
||||
|
|
@ -166,7 +181,15 @@ function makeVisibleGridRow(
|
|||
return { cellKey: column.key, editable, kind, value };
|
||||
});
|
||||
|
||||
return { rowKey, rowType, sectionId, circuit, device, cells };
|
||||
return {
|
||||
rowKey,
|
||||
rowType,
|
||||
sectionId,
|
||||
circuit,
|
||||
device,
|
||||
downstreamBoardLoad,
|
||||
cells,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildVisibleGridRowsWithStructure(
|
||||
|
|
@ -221,15 +244,27 @@ export function buildVisibleGridRowsWithStructure(
|
|||
}
|
||||
if (row.rowType === "circuitBlock") {
|
||||
const circuit = row.circuit;
|
||||
if (circuit.deviceRows.length === 0) {
|
||||
const downstreamLoads = circuit.downstreamBoardLoads ?? [];
|
||||
if (circuit.deviceRows.length === 0 && downstreamLoads.length === 0) {
|
||||
rows.push(makeVisibleGridRow("reserveCircuit", section.id, circuit));
|
||||
} else if (circuit.deviceRows.length === 1) {
|
||||
} else if (circuit.deviceRows.length === 1 && downstreamLoads.length === 0) {
|
||||
rows.push(makeVisibleGridRow("circuitCompact", section.id, circuit, circuit.deviceRows[0]));
|
||||
} else {
|
||||
rows.push(makeVisibleGridRow("circuitSummary", section.id, circuit));
|
||||
for (const device of circuit.deviceRows) {
|
||||
rows.push(makeVisibleGridRow("deviceRow", section.id, circuit, device));
|
||||
}
|
||||
for (const load of downstreamLoads) {
|
||||
rows.push(
|
||||
makeVisibleGridRow(
|
||||
"downstreamBoardLoad",
|
||||
section.id,
|
||||
circuit,
|
||||
undefined,
|
||||
load
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue