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>; 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 { const values = new Set(); 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 { const result = {} as Record; for (const column of columns) { const values = new Set(); 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; }