diff --git a/docs/current-architecture.md b/docs/current-architecture.md index 0534219..f3956cd 100644 --- a/docs/current-architecture.md +++ b/docs/current-architecture.md @@ -355,6 +355,11 @@ rein und deterministisch in die drei Zonen Kopf, vollständige Gruppen und Fuß. Innerhalb einer Gruppe folgen auf die Überschrift optionale Schutzkomponenten, vollständige Stromkreisblöcke und der freie Einfügeplatz. Leere Gruppen bleiben sichtbar; Komponenten werden stabil nach `sortOrder` und ID sortiert. +`circuit-grid-projection.ts` normalisiert diese Struktur zusammen mit den +bisherigen kompakten und mehrzeiligen Stromkreisblöcken für den Editor. Kopf-, +Gruppen- und Fußkomponenten erscheinen dort zunächst als schreibgeschützte +Zeilen über die volle Tabellenbreite und nehmen nicht an Zellauswahl, +Tastatur-Einfügen oder Löschen teil. `distribution-board.update` versioniert Etage, Netzart und den verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und @@ -426,9 +431,10 @@ Kopieren in ein Projekt erzeugt ein eigenständiges Projektgerät. stromkreislistenweite BMK-Eindeutigkeit über Stromkreise und Verteilerkomponenten. Snapshot- und Transfer-Integration verwenden Snapshot-Schema 7. Persistente Insert/Delete/Update-Commands für - veränderliche Verteilerkomponenten sowie CRUD-Commands für leere Gruppen - sowie vollständige Gruppensortierung sind integriert; befüllte Unterbäume - und UI folgen in abgegrenzten Arbeitspaketen. + veränderliche Verteilerkomponenten, Gruppen einschließlich befüllter + Unterbäume sowie vollständige Gruppensortierung sind integriert. Der Editor + zeigt die geschützte Struktur bereits schreibgeschützt an; dedizierte + Bearbeitungsfunktionen folgen in einem abgegrenzten Arbeitspaket. PostgreSQL ist bewusst nicht implementiert. Die Domainregeln und Transaktionsgrenzen sollen portabel bleiben; Schema und Betriebsmodell benötigen diff --git a/docs/spec/08-current-product-backlog.md b/docs/spec/08-current-product-backlog.md index 4614858..33751af 100644 --- a/docs/spec/08-current-product-backlog.md +++ b/docs/spec/08-current-product-backlog.md @@ -33,7 +33,7 @@ requirements and intended sequencing, not proof of implementation. - [x] Phase D3b: persistent populated-group delete/restore command. - [x] Phase E1: tree read model for components, groups and protection devices. - [x] Phase E2a: pure header/group/footer structure projection. -- [ ] Phase E2b: render the structure projection in the editor grid. +- [x] Phase E2b: render the structure projection in the editor grid. - [ ] Phase E: editor projection and editing. - [ ] Phase F: documentation and full GUI verification. - [ ] Keep full electrical sizing and cable-dimensioning rules separate until diff --git a/docs/spec/09-distribution-board-components-and-protection-groups.md b/docs/spec/09-distribution-board-components-and-protection-groups.md index 5bdbd87..f1a8772 100644 --- a/docs/spec/09-distribution-board-components-and-protection-groups.md +++ b/docs/spec/09-distribution-board-components-and-protection-groups.md @@ -739,8 +739,9 @@ Acceptance: ### E. Editor Projection and Editing -Status: In progress. The complete tree read model E1 and pure structural -projection E2a are complete. Grid rendering and editing remain pending. +Status: In progress. The complete tree read model E1, pure structural +projection E2a and read-only grid rendering E2b are complete. Editing remains +pending. - render fixed header components - render group components and circuit blocks @@ -749,7 +750,7 @@ projection E2a are complete. Grid rendering and editing remain pending. - add type-dependent protection editing - keep spreadsheet selection, keyboard and drag behavior coherent -Implemented in E1/E2a: +Implemented in E1/E2a/E2b: - the tree response exposes fixed header, group and auxiliary footer components plus both one-to-one protection layers @@ -759,6 +760,10 @@ Implemented in E1/E2a: circuit blocks and free placeholder together - empty groups remain visible and circuit device rows remain owned by their circuit block +- the editor renders fixed header, group protection and auxiliary footer + components as read-only full-width rows +- structural component rows are excluded from spreadsheet cell navigation, + insertion and deletion commands until their dedicated editing controls exist Acceptance: diff --git a/src/app/globals.css b/src/app/globals.css index 351a429..9fe85c5 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -657,6 +657,40 @@ body { font-weight: 600; } +.tree-grid .structure-component-row td { + padding: 0.38rem 0.6rem; + border-bottom-color: #d8dee9; +} + +.tree-grid .structure-component-row.headerComponent td { + background: #e2e8f0; +} + +.tree-grid .structure-component-row.groupComponent td { + background: #f8fafc; +} + +.tree-grid .structure-component-row.footerComponent td { + background: #f1f5f9; +} + +.tree-grid .structure-component-content { + display: flex; + align-items: baseline; + gap: 0.75rem; + min-width: 0; +} + +.tree-grid .structure-component-content strong { + flex: 0 0 auto; + font-variant-numeric: tabular-nums; +} + +.tree-grid .structure-component-protection { + color: #475569; + font-size: 0.78rem; +} + .tree-grid .section-drop-cell { padding: 0.45rem 0.6rem; } diff --git a/src/frontend/components/circuit-tree-editor.tsx b/src/frontend/components/circuit-tree-editor.tsx index 469e4b2..2ed122a 100644 --- a/src/frontend/components/circuit-tree-editor.tsx +++ b/src/frontend/components/circuit-tree-editor.tsx @@ -11,6 +11,7 @@ import { } from "../../domain/services/project-voltage.service"; import { getInsertionSortOrder, + isGridInsertionRowType, resolveGridInsertionIntent, } from "../utils/circuit-grid-insertion"; import { @@ -53,7 +54,7 @@ import type { RowType, } from "../utils/circuit-grid-model"; import { - buildVisibleGridRows, + buildVisibleGridRowsWithStructure, filterAndSortCircuitSections, getDistinctFilterValues, } from "../utils/circuit-grid-projection"; @@ -386,8 +387,11 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str // visibleRows is the single normalized grid used by render + navigation + editability. // This avoids selected/rendered/editable state drifting apart after filters/sorts/reloads. const visibleRows = useMemo(() => { - return buildVisibleGridRows(filteredSortedSections); - }, [filteredSortedSections]); + return buildVisibleGridRowsWithStructure(filteredSortedSections, { + headerComponents: data?.headerComponents ?? [], + footerComponents: data?.footerComponents ?? [], + }); + }, [data, filteredSortedSections]); function clearColumnFilter(key: CellKey) { setColumnFilters((current) => { @@ -1660,7 +1664,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str const row = selectedCell ? findRow(selectedCell.rowKey) : null; const cell = row?.cells.find((entry) => entry.cellKey === selectedCell?.cellKey); const selection = - row && cell && row.rowType !== "section" + row && cell && isGridInsertionRowType(row.rowType) ? { rowType: row.rowType, cellKind: cell.kind, @@ -2351,7 +2355,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str const row = selectedCell ? findRow(selectedCell.rowKey) : null; const cell = row?.cells.find((entry) => entry.cellKey === selectedCell?.cellKey); const intent = resolveGridDeleteIntent( - row && cell && row.rowType !== "section" + row && cell && isGridInsertionRowType(row.rowType) ? { rowType: row.rowType, cellKind: cell.kind, @@ -2986,6 +2990,54 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
{visibleRows.map((row) => { + if ( + row.rowType === "headerComponent" || + row.rowType === "groupComponent" || + row.rowType === "footerComponent" + ) { + const component = row.component!; + const protection = component.protectionDevice; + const protectionSummary = protection + ? [ + protection.type, + `${formatValue( + protection.ratedCurrentA, + "protectionRatedCurrent" + )} A`, + protection.fuseUtilizationCategory, + protection.tripCharacteristic, + protection.rcdType + ? `Typ ${protection.rcdType}` + : undefined, + protection.ratedResidualCurrentMa !== undefined + ? `${formatValue( + protection.ratedResidualCurrentMa, + "protectionRatedCurrent" + )} mA` + : undefined, + ] + .filter(Boolean) + .join(" · ") + : null; + return ( +