forked from jappel/leistungsbilanz-ts
239 lines
7.3 KiB
TypeScript
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;
|
|
}
|