forked from jappel/leistungsbilanz-ts
223 lines
8.6 KiB
TypeScript
223 lines
8.6 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { Fragment } from "react";
|
|
import { getCircuitTree } from "../utils/api";
|
|
import type { CircuitTreeCircuitDto, CircuitTreeResponseDto } from "../types";
|
|
|
|
function formatNumber(value: number | undefined, digits = 2) {
|
|
if (value === undefined || Number.isNaN(value)) {
|
|
return "-";
|
|
}
|
|
return new Intl.NumberFormat("de-DE", {
|
|
minimumFractionDigits: digits,
|
|
maximumFractionDigits: digits,
|
|
}).format(value);
|
|
}
|
|
|
|
function formatPhaseType(value: string | undefined) {
|
|
if (value === "three_phase") {
|
|
return "Dreiphasig";
|
|
}
|
|
if (value === "single_phase") {
|
|
return "Einphasig";
|
|
}
|
|
return value ?? "-";
|
|
}
|
|
|
|
function renderCircuitSummaryLabel(circuit: CircuitTreeCircuitDto) {
|
|
if (circuit.displayName?.trim()) {
|
|
return circuit.displayName;
|
|
}
|
|
if (circuit.deviceRows.length > 1) {
|
|
return `${circuit.deviceRows.length} Geräte`;
|
|
}
|
|
return "Reserve";
|
|
}
|
|
|
|
export function CircuitTreePreview(props: { projectId: string; circuitListId: string }) {
|
|
const { projectId, circuitListId } = props;
|
|
const [data, setData] = useState<CircuitTreeResponseDto | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
getCircuitTree(projectId, circuitListId)
|
|
.then(setData)
|
|
.catch((err: unknown) =>
|
|
setError(err instanceof Error ? err.message : "Die Stromkreisstruktur konnte nicht geladen werden.")
|
|
)
|
|
.finally(() => setIsLoading(false));
|
|
}, [projectId, circuitListId]);
|
|
|
|
if (isLoading) {
|
|
return <div className="alert alert-info">Stromkreisstruktur wird geladen …</div>;
|
|
}
|
|
|
|
if (error) {
|
|
return <div className="alert alert-danger">{error}</div>;
|
|
}
|
|
|
|
if (!data || !data.sections.length) {
|
|
return <div className="alert alert-secondary">Keine Bereiche oder Stromkreise vorhanden.</div>;
|
|
}
|
|
|
|
const hasAnyCircuits = data.sections.some((section) => section.circuits.length > 0);
|
|
if (!hasAnyCircuits) {
|
|
return <div className="alert alert-secondary">Bereiche sind vorhanden, enthalten aber noch keine Stromkreise.</div>;
|
|
}
|
|
|
|
return (
|
|
<div className="card shadow-sm">
|
|
<div className="card-body">
|
|
<div className="table-responsive">
|
|
<table className="table table-sm align-middle circuit-tree-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Betriebsmittelkennzeichen</th>
|
|
<th>Anzeigename</th>
|
|
<th>Phasenart</th>
|
|
<th>Anschlussart</th>
|
|
<th>Kostengruppe</th>
|
|
<th>Kategorie</th>
|
|
<th>Ebene</th>
|
|
<th>Raumnummer</th>
|
|
<th>Raumname</th>
|
|
<th className="text-end">Anzahl</th>
|
|
<th className="text-end">Leistung / Gerät</th>
|
|
<th className="text-end">Gleichzeitigkeit</th>
|
|
<th className="text-end">cos φ</th>
|
|
<th className="text-end">Zeilensumme</th>
|
|
<th className="text-end">Stromkreissumme</th>
|
|
<th>Schutzart</th>
|
|
<th className="text-end">Bemessungsstrom</th>
|
|
<th>Charakteristik</th>
|
|
<th>Kabeltyp</th>
|
|
<th>Kabelquerschnitt</th>
|
|
<th className="text-end">Kabellänge</th>
|
|
<th>Bemerkung</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{data.sections.map((section) => (
|
|
<SectionRows key={section.id} section={section} />
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number] }) {
|
|
const { section } = props;
|
|
return (
|
|
<>
|
|
<tr className="section-row">
|
|
<td colSpan={22}>
|
|
<strong>{section.displayName}</strong>
|
|
</td>
|
|
</tr>
|
|
{section.circuits.map((circuit) => {
|
|
if (circuit.deviceRows.length === 0) {
|
|
return (
|
|
<tr key={circuit.id} className="reserve-row">
|
|
<td>{circuit.equipmentIdentifier}</td>
|
|
<td>{circuit.displayName?.trim() || "Reserve"}</td>
|
|
<td colSpan={12}>-</td>
|
|
<td className="text-end">{formatNumber(circuit.circuitTotalPower)}</td>
|
|
<td>{circuit.protectionType ?? "-"}</td>
|
|
<td className="text-end">{formatNumber(circuit.protectionRatedCurrent)}</td>
|
|
<td>{circuit.protectionCharacteristic ?? "-"}</td>
|
|
<td>{circuit.cableType ?? "-"}</td>
|
|
<td>{circuit.cableCrossSection ?? "-"}</td>
|
|
<td className="text-end">{formatNumber(circuit.cableLength)}</td>
|
|
<td>{circuit.remark ?? "-"}</td>
|
|
</tr>
|
|
);
|
|
}
|
|
|
|
if (circuit.deviceRows.length === 1) {
|
|
const row = circuit.deviceRows[0];
|
|
return (
|
|
<tr key={circuit.id} className={circuit.isReserve ? "reserve-row" : ""}>
|
|
<td>{circuit.equipmentIdentifier}</td>
|
|
<td>{row.displayName || row.name}</td>
|
|
<td>{formatPhaseType(row.phaseType)}</td>
|
|
<td>{row.connectionKind ?? "-"}</td>
|
|
<td>{row.costGroup ?? "-"}</td>
|
|
<td>{row.category ?? "-"}</td>
|
|
<td>{row.level ?? "-"}</td>
|
|
<td>{row.roomNumberSnapshot ?? "-"}</td>
|
|
<td>{row.roomNameSnapshot ?? "-"}</td>
|
|
<td className="text-end">{formatNumber(row.quantity, 0)}</td>
|
|
<td className="text-end">{formatNumber(row.powerPerUnit)}</td>
|
|
<td className="text-end">{formatNumber(row.simultaneityFactor)}</td>
|
|
<td className="text-end">{formatNumber(row.cosPhi)}</td>
|
|
<td className="text-end">{formatNumber(row.rowTotalPower)}</td>
|
|
<td className="text-end">{formatNumber(circuit.circuitTotalPower)}</td>
|
|
<td>{circuit.protectionType ?? "-"}</td>
|
|
<td className="text-end">{formatNumber(circuit.protectionRatedCurrent)}</td>
|
|
<td>{circuit.protectionCharacteristic ?? "-"}</td>
|
|
<td>{circuit.cableType ?? "-"}</td>
|
|
<td>{circuit.cableCrossSection ?? "-"}</td>
|
|
<td className="text-end">{formatNumber(circuit.cableLength)}</td>
|
|
<td>{row.remark ?? circuit.remark ?? "-"}</td>
|
|
</tr>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Fragment key={circuit.id}>
|
|
<tr key={`${circuit.id}-summary`} className="summary-row">
|
|
<td>{circuit.equipmentIdentifier}</td>
|
|
<td>{renderCircuitSummaryLabel(circuit)}</td>
|
|
<td colSpan={12}>-</td>
|
|
<td className="text-end">{formatNumber(circuit.circuitTotalPower)}</td>
|
|
<td>{circuit.protectionType ?? "-"}</td>
|
|
<td className="text-end">{formatNumber(circuit.protectionRatedCurrent)}</td>
|
|
<td>{circuit.protectionCharacteristic ?? "-"}</td>
|
|
<td>{circuit.cableType ?? "-"}</td>
|
|
<td>{circuit.cableCrossSection ?? "-"}</td>
|
|
<td className="text-end">{formatNumber(circuit.cableLength)}</td>
|
|
<td>{circuit.remark ?? "-"}</td>
|
|
</tr>
|
|
{circuit.deviceRows.map((row) => (
|
|
<tr key={row.id} className="device-row">
|
|
<td className="text-muted"> </td>
|
|
<td className="indented-cell">{row.displayName || row.name}</td>
|
|
<td>{formatPhaseType(row.phaseType)}</td>
|
|
<td>{row.connectionKind ?? "-"}</td>
|
|
<td>{row.costGroup ?? "-"}</td>
|
|
<td>{row.category ?? "-"}</td>
|
|
<td>{row.level ?? "-"}</td>
|
|
<td>{row.roomNumberSnapshot ?? "-"}</td>
|
|
<td>{row.roomNameSnapshot ?? "-"}</td>
|
|
<td className="text-end">{formatNumber(row.quantity, 0)}</td>
|
|
<td className="text-end">{formatNumber(row.powerPerUnit)}</td>
|
|
<td className="text-end">{formatNumber(row.simultaneityFactor)}</td>
|
|
<td className="text-end">{formatNumber(row.cosPhi)}</td>
|
|
<td className="text-end">{formatNumber(row.rowTotalPower)}</td>
|
|
<td className="text-end">-</td>
|
|
<td>-</td>
|
|
<td className="text-end">-</td>
|
|
<td>-</td>
|
|
<td>-</td>
|
|
<td>-</td>
|
|
<td className="text-end">-</td>
|
|
<td>{row.remark ?? "-"}</td>
|
|
</tr>
|
|
))}
|
|
</Fragment>
|
|
);
|
|
})}
|
|
<tr className="placeholder-row">
|
|
<td>-frei-</td>
|
|
<td colSpan={21}>Freie Zeile</td>
|
|
</tr>
|
|
</>
|
|
);
|
|
}
|