leistungsbilanz-ts/src/frontend/utils/circuit-grid-projection.ts

239 lines
7.3 KiB
TypeScript

import type {
CircuitTreeCircuitDto,
CircuitTreeComponentDto,
CircuitTreeDeviceRowDto,
CircuitTreeSectionDto,
} from "../types.js";
import { buildCircuitStructureProjection } from "./circuit-structure-projection";
import {
allColumns,
circuitOnlyColumns,
compareSortValues,
deviceFieldKeys,
getBlockSortValue,
getCellKind,
getCircuitValue,
getDeviceValue,
normalizeFilterValue,
} from "./circuit-grid-model";
import type {
CellKey,
CellKind,
ColumnDef,
GridValue,
RowType,
} from "./circuit-grid-model";
export type ColumnFilters = Partial<Record<CellKey, string[]>>;
export interface GridSortState {
key: CellKey;
direction: "asc" | "desc";
}
export interface VisibleGridCell {
cellKey: CellKey;
editable: boolean;
kind: CellKind;
value: GridValue;
}
export interface VisibleGridRow {
rowKey: string;
rowType: RowType;
sectionId: string;
circuit?: CircuitTreeCircuitDto;
device?: CircuitTreeDeviceRowDto;
component?: CircuitTreeComponentDto;
cells: VisibleGridCell[];
}
function getCircuitBlockFilterValues(circuit: CircuitTreeCircuitDto, key: CellKey): Set<string> {
const values = new Set<string>();
if (key === "rowTotalPower") {
values.add(normalizeFilterValue(key, circuit.circuitTotalPower));
}
if (circuitOnlyColumns.has(key) || key === "displayName" || key === "remark") {
values.add(normalizeFilterValue(key, getCircuitValue(circuit, key)));
}
if (!circuitOnlyColumns.has(key)) {
for (const device of circuit.deviceRows) {
values.add(normalizeFilterValue(key, getDeviceValue(device, key)));
}
}
if (values.size === 0 && deviceFieldKeys.has(key)) {
values.add(normalizeFilterValue(key, undefined));
}
return values;
}
export function getDistinctFilterValues(
sections: readonly CircuitTreeSectionDto[],
columns: readonly ColumnDef[]
): Record<CellKey, string[]> {
const result = {} as Record<CellKey, string[]>;
for (const column of columns) {
const values = new Set<string>();
for (const section of sections) {
for (const circuit of section.circuits) {
for (const value of getCircuitBlockFilterValues(circuit, column.key)) {
values.add(value);
}
}
}
result[column.key] = [...values].sort((left, right) =>
left.localeCompare(right, undefined, { numeric: true })
);
}
return result;
}
export function filterAndSortCircuitSections(
sections: readonly CircuitTreeSectionDto[],
columnFilters: ColumnFilters,
sortState: GridSortState | null
): CircuitTreeSectionDto[] {
const activeFilters = Object.entries(columnFilters).filter(([, values]) => (values?.length ?? 0) > 0) as Array<
[CellKey, string[]]
>;
return sections
.map((section) => {
let circuits = section.circuits.filter((circuit) =>
activeFilters.every(([key, selectedValues]) => {
const blockValues = getCircuitBlockFilterValues(circuit, key);
return selectedValues.some((selectedValue) => blockValues.has(selectedValue));
})
);
if (sortState) {
// Circuit blocks are sorted as units; their device row membership/order is untouched.
circuits = [...circuits].sort((left, right) => {
const comparison = compareSortValues(
getBlockSortValue(left, sortState.key),
getBlockSortValue(right, sortState.key)
);
return sortState.direction === "asc" ? comparison : -comparison;
});
}
return { ...section, circuits };
})
.filter((section) => activeFilters.length === 0 || section.circuits.length > 0);
}
function makeVisibleGridRow(
rowType: RowType,
sectionId: string,
circuit?: CircuitTreeCircuitDto,
device?: CircuitTreeDeviceRowDto
): VisibleGridRow {
const rowKey =
rowType === "placeholder"
? `placeholder:${sectionId}`
: rowType === "deviceRow" && device
? `device:${device.id}`
: `${rowType}:${circuit?.id ?? sectionId}`;
const cells = allColumns.map((column): VisibleGridCell => {
const kind = getCellKind(rowType, column.key);
const editable = kind === "circuitField" || kind === "deviceField";
let value: GridValue;
if (rowType === "placeholder") {
value = column.key === "equipmentIdentifier" ? "-frei-" : undefined;
} else if (kind === "deviceField" && device) {
value = getDeviceValue(device, column.key);
} else if (kind === "circuitField" && circuit) {
value = getCircuitValue(circuit, column.key);
} else if (column.key === "rowTotalPower" && device) {
value = device.rowTotalPower;
} else if (column.key === "rowTotalPower" && circuit) {
value = circuit.circuitTotalPower;
} else if (column.key === "circuitTotalPower" && circuit) {
value = circuit.circuitTotalPower;
} else if (column.key === "voltage" && circuit) {
value = circuit.voltage;
}
return { cellKey: column.key, editable, kind, value };
});
return { rowKey, rowType, sectionId, circuit, device, cells };
}
export function buildVisibleGridRows(sections: readonly CircuitTreeSectionDto[]): VisibleGridRow[] {
return buildVisibleGridRowsWithStructure(sections, {
headerComponents: [],
footerComponents: [],
});
}
export function buildVisibleGridRowsWithStructure(
sections: readonly CircuitTreeSectionDto[],
structure: {
headerComponents: readonly CircuitTreeComponentDto[];
footerComponents: readonly CircuitTreeComponentDto[];
}
): VisibleGridRow[] {
const rows: VisibleGridRow[] = [];
const projected = buildCircuitStructureProjection({
headerComponents: [...structure.headerComponents],
sections: [...sections],
footerComponents: [...structure.footerComponents],
});
for (const row of projected) {
if ("component" in row) {
const component = row.component;
rows.push({
rowKey: row.rowKey,
rowType: row.rowType,
sectionId: component.sectionId ?? `__${row.zone}__`,
component,
cells: allColumns.map((column) => ({
cellKey: column.key,
editable: false,
kind: "readonly",
value:
column.key === "equipmentIdentifier"
? component.equipmentIdentifier
: column.key === "displayName"
? component.name
: undefined,
})),
});
continue;
}
const section = row.section;
if (row.rowType === "groupHeader") {
rows.push({
rowKey: `section:${section.id}`,
rowType: "section",
sectionId: section.id,
cells: allColumns.map((column) => ({
cellKey: column.key,
editable: false,
kind: "readonly",
value: undefined,
})),
});
continue;
}
if (row.rowType === "circuitBlock") {
const circuit = row.circuit;
if (circuit.deviceRows.length === 0) {
rows.push(makeVisibleGridRow("reserveCircuit", section.id, circuit));
} else if (circuit.deviceRows.length === 1) {
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));
}
}
continue;
}
rows.push(makeVisibleGridRow("placeholder", section.id));
}
return rows;
}