Added 1B, 2 and added bootstrap again for site
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
"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 renderCircuitSummaryLabel(circuit: CircuitTreeCircuitDto) {
|
||||
if (circuit.displayName?.trim()) {
|
||||
return circuit.displayName;
|
||||
}
|
||||
if (circuit.deviceRows.length > 1) {
|
||||
return `${circuit.deviceRows.length} devices`;
|
||||
}
|
||||
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 : "Circuit tree could not be loaded.")
|
||||
)
|
||||
.finally(() => setIsLoading(false));
|
||||
}, [projectId, circuitListId]);
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="alert alert-info">Loading circuit tree...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div className="alert alert-danger">{error}</div>;
|
||||
}
|
||||
|
||||
if (!data || !data.sections.length) {
|
||||
return <div className="alert alert-secondary">No sections or circuits available.</div>;
|
||||
}
|
||||
|
||||
const hasAnyCircuits = data.sections.some((section) => section.circuits.length > 0);
|
||||
if (!hasAnyCircuits) {
|
||||
return <div className="alert alert-secondary">Sections exist, but no circuits were found yet.</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>Equipment identifier</th>
|
||||
<th>Display name</th>
|
||||
<th>Phase type</th>
|
||||
<th>Connection kind</th>
|
||||
<th>Cost group</th>
|
||||
<th>Category</th>
|
||||
<th>Level</th>
|
||||
<th>Room number</th>
|
||||
<th>Room name</th>
|
||||
<th className="text-end">Quantity</th>
|
||||
<th className="text-end">Power / unit</th>
|
||||
<th className="text-end">Simultaneity</th>
|
||||
<th className="text-end">cosPhi</th>
|
||||
<th className="text-end">Row total</th>
|
||||
<th className="text-end">Circuit total</th>
|
||||
<th>Protection type</th>
|
||||
<th className="text-end">Protection current</th>
|
||||
<th>Protection characteristic</th>
|
||||
<th>Cable type</th>
|
||||
<th>Cable cross-section</th>
|
||||
<th className="text-end">Cable length</th>
|
||||
<th>Remark</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>{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>{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}>free placeholder</td>
|
||||
</tr>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -168,3 +168,76 @@ export interface CreateProjectDeviceInput {
|
||||
powerFactor?: number;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface CircuitTreeDeviceRowDto {
|
||||
id: string;
|
||||
linkedProjectDeviceId?: string;
|
||||
legacyConsumerId?: string;
|
||||
sortOrder: number;
|
||||
name: string;
|
||||
displayName: string;
|
||||
phaseType?: string;
|
||||
connectionKind?: string;
|
||||
costGroup?: string;
|
||||
category?: string;
|
||||
level?: string;
|
||||
roomId?: string;
|
||||
roomNumberSnapshot?: string;
|
||||
roomNameSnapshot?: string;
|
||||
quantity: number;
|
||||
powerPerUnit: number;
|
||||
simultaneityFactor: number;
|
||||
cosPhi?: number;
|
||||
remark?: string;
|
||||
overriddenFields?: string;
|
||||
rowTotalPower: number;
|
||||
}
|
||||
|
||||
export interface CircuitTreeCircuitDto {
|
||||
id: string;
|
||||
circuitListId: string;
|
||||
sectionId: string;
|
||||
equipmentIdentifier: string;
|
||||
displayName?: string;
|
||||
sortOrder: number;
|
||||
protectionType?: string;
|
||||
protectionRatedCurrent?: number;
|
||||
protectionCharacteristic?: string;
|
||||
cableType?: string;
|
||||
cableCrossSection?: string;
|
||||
cableLength?: number;
|
||||
rcdAssignment?: string;
|
||||
terminalDesignation?: string;
|
||||
voltage?: number;
|
||||
status?: string;
|
||||
isReserve: boolean;
|
||||
remark?: string;
|
||||
circuitTotalPower: number;
|
||||
deviceRows: CircuitTreeDeviceRowDto[];
|
||||
}
|
||||
|
||||
export interface CircuitTreeSectionDto {
|
||||
id: string;
|
||||
key: string;
|
||||
displayName: string;
|
||||
prefix: string;
|
||||
sortOrder: number;
|
||||
circuits: CircuitTreeCircuitDto[];
|
||||
}
|
||||
|
||||
export interface CircuitTreeMigrationReportDto {
|
||||
circuitListId: string;
|
||||
legacyConsumerCount: number;
|
||||
createdCircuitCount: number;
|
||||
createdDeviceRowCount: number;
|
||||
groupedDuplicateCircuitNumbers: Array<{ normalizedCircuitNumber: string; count: number }>;
|
||||
generatedIdentifiers: string[];
|
||||
unassignedRows: Array<{ consumerId: string; reason: string }>;
|
||||
warnings: string[];
|
||||
}
|
||||
|
||||
export interface CircuitTreeResponseDto {
|
||||
circuitListId: string;
|
||||
sections: CircuitTreeSectionDto[];
|
||||
migrationReport?: CircuitTreeMigrationReportDto;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
ProjectDto,
|
||||
RoomDto,
|
||||
UpdateConsumerInput,
|
||||
CircuitTreeResponseDto,
|
||||
} from "../types";
|
||||
|
||||
async function request<T>(url: string, init?: RequestInit): Promise<T> {
|
||||
@@ -77,6 +78,10 @@ export function listCircuitLists(projectId: string) {
|
||||
return request<CircuitListDto[]>(`/api/projects/${projectId}/circuit-lists`);
|
||||
}
|
||||
|
||||
export function getCircuitTree(projectId: string, circuitListId: string) {
|
||||
return request<CircuitTreeResponseDto>(`/api/projects/${projectId}/circuit-lists/${circuitListId}/tree`);
|
||||
}
|
||||
|
||||
export function listFloors(projectId: string) {
|
||||
return request<FloorDto[]>(`/api/projects/${projectId}/floors`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user