Add distribution power summaries
This commit is contained in:
@@ -27,7 +27,11 @@ import {
|
||||
deviceFieldKeys,
|
||||
formatPhaseTypeLabel,
|
||||
formatValue,
|
||||
getCircuitSectionLabel,
|
||||
getProjectColumnLayoutStorageKey,
|
||||
isGridEditorControlTarget,
|
||||
normalizeColumnOrder,
|
||||
parseStoredColumnLayout,
|
||||
} from "../utils/circuit-grid-model";
|
||||
import {
|
||||
buildCircuitDeviceRowInsertSnapshot,
|
||||
@@ -131,7 +135,8 @@ type CircuitReorderDropIntent =
|
||||
| { kind: "after-circuit"; sectionId: string; targetCircuitId: string; valid: boolean }
|
||||
| { kind: "section-end"; sectionId: string; valid: boolean };
|
||||
|
||||
const COLUMN_LAYOUT_STORAGE_KEY = "circuitTreeEditor.columnLayout.v1";
|
||||
const LEGACY_COLUMN_LAYOUT_STORAGE_KEY =
|
||||
"circuitTreeEditor.columnLayout.v1";
|
||||
|
||||
function normalizeUiError(err: unknown): string {
|
||||
const message = err instanceof Error ? err.message : "Der Vorgang ist fehlgeschlagen.";
|
||||
@@ -222,6 +227,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
const [filterValueSearch, setFilterValueSearch] = useState("");
|
||||
const [visibleColumnKeys, setVisibleColumnKeys] = useState<CellKey[]>(defaultVisibleColumnKeys);
|
||||
const [columnOrder, setColumnOrder] = useState<CellKey[]>(allColumns.map((column) => column.key));
|
||||
const [
|
||||
loadedColumnLayoutProjectId,
|
||||
setLoadedColumnLayoutProjectId,
|
||||
] = useState<string | null>(null);
|
||||
const [isColumnMenuOpen, setIsColumnMenuOpen] = useState(false);
|
||||
const [columnSearch, setColumnSearch] = useState("");
|
||||
const [draggingColumnKey, setDraggingColumnKey] = useState<CellKey | null>(null);
|
||||
@@ -267,13 +276,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeColumnOrder(keys: CellKey[]) {
|
||||
const unique = [...new Set(keys)];
|
||||
const allKeys = allColumns.map((column) => column.key);
|
||||
const merged = [...unique, ...allKeys.filter((key) => !unique.includes(key))];
|
||||
return ["equipmentIdentifier" as CellKey, ...merged.filter((key) => key !== "equipmentIdentifier")];
|
||||
}
|
||||
|
||||
// Initial/identity-change tree load for current route context.
|
||||
useEffect(() => {
|
||||
void loadTree({ showLoading: true });
|
||||
@@ -292,32 +294,48 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
}, [projectId]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoadedColumnLayoutProjectId(null);
|
||||
setColumnOrder(allColumns.map((column) => column.key));
|
||||
setVisibleColumnKeys(defaultVisibleColumnKeys);
|
||||
try {
|
||||
const raw = localStorage.getItem(COLUMN_LAYOUT_STORAGE_KEY);
|
||||
if (!raw) {
|
||||
const parsed = parseStoredColumnLayout(
|
||||
localStorage.getItem(
|
||||
getProjectColumnLayoutStorageKey(projectId)
|
||||
) ??
|
||||
localStorage.getItem(
|
||||
LEGACY_COLUMN_LAYOUT_STORAGE_KEY
|
||||
)
|
||||
);
|
||||
if (!parsed) {
|
||||
return;
|
||||
}
|
||||
const parsed = JSON.parse(raw) as { order?: string[]; visible?: string[] };
|
||||
const validKeys = new Set(allColumns.map((column) => column.key));
|
||||
const parsedOrder = (parsed.order ?? []).filter((key): key is CellKey => validKeys.has(key as CellKey));
|
||||
const parsedVisible = (parsed.visible ?? []).filter((key): key is CellKey => validKeys.has(key as CellKey));
|
||||
if (!parsedOrder.length || !parsedVisible.length || !parsedVisible.includes("equipmentIdentifier")) {
|
||||
return;
|
||||
}
|
||||
setColumnOrder(normalizeColumnOrder(parsedOrder));
|
||||
setVisibleColumnKeys(parsedVisible);
|
||||
setColumnOrder(parsed.order);
|
||||
setVisibleColumnKeys(parsed.visible);
|
||||
} catch {
|
||||
// ignore invalid local storage and use defaults
|
||||
} finally {
|
||||
setLoadedColumnLayoutProjectId(projectId);
|
||||
}
|
||||
}, []);
|
||||
}, [projectId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadedColumnLayoutProjectId !== projectId) {
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
order: columnOrder,
|
||||
visible: visibleColumnKeys,
|
||||
};
|
||||
localStorage.setItem(COLUMN_LAYOUT_STORAGE_KEY, JSON.stringify(payload));
|
||||
}, [columnOrder, visibleColumnKeys]);
|
||||
localStorage.setItem(
|
||||
getProjectColumnLayoutStorageKey(projectId),
|
||||
JSON.stringify(payload)
|
||||
);
|
||||
}, [
|
||||
columnOrder,
|
||||
loadedColumnLayoutProjectId,
|
||||
projectId,
|
||||
visibleColumnKeys,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const visible = new Set(visibleColumnKeys);
|
||||
@@ -474,6 +492,51 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
);
|
||||
}
|
||||
|
||||
function renderDistributionPowerSummary() {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<section
|
||||
className="distribution-power-summary"
|
||||
aria-label="Leistungszusammenfassung des Verteilers"
|
||||
>
|
||||
<div>
|
||||
<span>Gesamtleistung Verteiler</span>
|
||||
<strong>
|
||||
{formatValue(
|
||||
data.distributionBoardTotalPower,
|
||||
"rowTotalPower"
|
||||
)}{" "}
|
||||
kW
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>Verteilerweiter Gleichzeitigkeitsfaktor</span>
|
||||
<strong>
|
||||
{formatValue(
|
||||
data.distributionBoardSimultaneityFactor,
|
||||
"simultaneityFactor"
|
||||
)}
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
Gesamtleistung Verteiler unter Berücksichtigung des
|
||||
Gleichzeitigkeitsfaktors
|
||||
</span>
|
||||
<strong>
|
||||
{formatValue(
|
||||
data.distributionBoardTotalPowerWithSimultaneityFactor,
|
||||
"rowTotalPower"
|
||||
)}{" "}
|
||||
kW
|
||||
</strong>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function closeColumnSettingsMenu() {
|
||||
setIsColumnMenuOpen(false);
|
||||
setColumnSearch("");
|
||||
@@ -2490,6 +2553,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
{renderColumnSettingsMenu("col-empty")}
|
||||
</div>
|
||||
{renderActiveViewSummary()}
|
||||
{renderDistributionPowerSummary()}
|
||||
{error ? (
|
||||
<div className="notice error editor-error-notice" role="alert">
|
||||
<span>{error}</span>
|
||||
@@ -2598,6 +2662,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
{renderColumnSettingsMenu("col")}
|
||||
</div>
|
||||
{renderActiveViewSummary()}
|
||||
{renderDistributionPowerSummary()}
|
||||
{hasActiveSortOrFilter ? (
|
||||
<div className="notice muted">Sortierung und Filter verändern nur die Ansicht. Die Neunummerierung ist währenddessen deaktiviert.</div>
|
||||
) : null}
|
||||
@@ -2701,14 +2766,18 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
const valid = !selectedProjectDevice || isProjectDevicePlacementValid(selectedProjectDevice, section);
|
||||
return (
|
||||
<option key={section.id} value={section.id} disabled={!valid}>
|
||||
{section.displayName}{valid ? "" : " (nicht zulässig)"}
|
||||
{getCircuitSectionLabel(section)}
|
||||
{valid ? "" : " (nicht zulässig)"}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</label>
|
||||
{selectedProjectDevice && suggestedSection ? (
|
||||
<p className="notice muted">Vorgeschlagener Bereich: {suggestedSection.displayName}</p>
|
||||
<p className="notice muted">
|
||||
Vorgeschlagener Bereich:{" "}
|
||||
{getCircuitSectionLabel(suggestedSection)}
|
||||
</p>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
@@ -2980,7 +3049,19 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
>
|
||||
<td colSpan={visibleColumns.length + 1} className="section-drop-cell">
|
||||
<div className="section-content">
|
||||
<strong>{section.displayName}</strong>
|
||||
<div className="section-title">
|
||||
<strong>
|
||||
{getCircuitSectionLabel(section)}
|
||||
</strong>
|
||||
<span>
|
||||
Gesamtleistung Abschnitt:{" "}
|
||||
{formatValue(
|
||||
section.sectionTotalPower,
|
||||
"rowTotalPower"
|
||||
)}{" "}
|
||||
kW
|
||||
</span>
|
||||
</div>
|
||||
<div className="section-actions">
|
||||
<button type="button" tabIndex={-1} onClick={() => void handleAddReserveCircuit(section.id)}>
|
||||
Stromkreis hinzufügen
|
||||
|
||||
Reference in New Issue
Block a user