Reorder circuit groups
This commit is contained in:
@@ -386,6 +386,10 @@ Gerätezeilen atomar. Delete/Undo erfasst denselben Datensatz vollständig.
|
|||||||
Im Editor sind die bisherigen Schutzspalten deshalb nur noch eine
|
Im Editor sind die bisherigen Schutzspalten deshalb nur noch eine
|
||||||
schreibgeschützte Projektion der 1:1-Daten; Änderungen erfolgen über ein
|
schreibgeschützte Projektion der 1:1-Daten; Änderungen erfolgen über ein
|
||||||
geräteabhängiges Schutzgeräte-Modal und `circuit-protection.update`.
|
geräteabhängiges Schutzgeräte-Modal und `circuit-protection.update`.
|
||||||
|
Gruppen lassen sich im Editor schrittweise nur gegenüber einer benachbarten
|
||||||
|
Gruppe derselben Kategorie verschieben. Der Client sendet dabei immer die
|
||||||
|
vollständige erwartete und gewünschte Sortierreihenfolge über
|
||||||
|
`circuit-group.reorder`; Gruppennummer, Präfix und Kind-BMK bleiben unverändert.
|
||||||
`distribution-board.update` versioniert Etage, Netzart und den
|
`distribution-board.update` versioniert Etage, Netzart und den
|
||||||
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
|
verteilerweiten Gleichzeitigkeitsfaktor gemeinsam und stellt alle Werte über
|
||||||
dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und
|
dauerhaftes Undo/Redo wieder her. Der Faktor liegt zwischen `0` und `1` und
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ requirements and intended sequencing, not proof of implementation.
|
|||||||
- [x] Phase E3b1: create, rename and safely delete empty circuit groups.
|
- [x] Phase E3b1: create, rename and safely delete empty circuit groups.
|
||||||
- [x] Phase E3b2a: persistent circuit-protection update command.
|
- [x] Phase E3b2a: persistent circuit-protection update command.
|
||||||
- [x] Phase E3b2b: circuit-protection defaults on insert and editor modal.
|
- [x] Phase E3b2b: circuit-protection defaults on insert and editor modal.
|
||||||
- [ ] Phase E4: group reorder, renumber, circuit moves and populated-delete warning.
|
- [x] Phase E4a: persistent same-category group reorder controls.
|
||||||
|
- [ ] Phase E4b: explicit group renumber, circuit moves and populated-delete warning.
|
||||||
- [ ] Phase E: editor projection and editing.
|
- [ ] Phase E: editor projection and editing.
|
||||||
- [ ] Phase F: documentation and full GUI verification.
|
- [ ] Phase F: documentation and full GUI verification.
|
||||||
- [ ] Keep full electrical sizing and cable-dimensioning rules separate until
|
- [ ] Keep full electrical sizing and cable-dimensioning rules separate until
|
||||||
|
|||||||
@@ -789,6 +789,8 @@ Implemented in E1/E2a/E2b/E3a:
|
|||||||
together with the circuit and all device rows
|
together with the circuit and all device rows
|
||||||
- the editor modal exposes only fields valid for the selected protection type;
|
- the editor modal exposes only fields valid for the selected protection type;
|
||||||
the old flat protection columns now project the new 1:1 data read-only
|
the old flat protection columns now project the new 1:1 data read-only
|
||||||
|
- group controls move a group only to the adjacent peer of the same category;
|
||||||
|
the complete list order is one persistent command and no number or BMK changes
|
||||||
|
|
||||||
Acceptance:
|
Acceptance:
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import {
|
|||||||
buildCircuitSectionRenumberAssignments,
|
buildCircuitSectionRenumberAssignments,
|
||||||
} from "../utils/circuit-section-renumber-command";
|
} from "../utils/circuit-section-renumber-command";
|
||||||
import {
|
import {
|
||||||
|
buildCircuitGroupReorderAssignments,
|
||||||
buildNewCircuitGroupSnapshot,
|
buildNewCircuitGroupSnapshot,
|
||||||
canDeleteCircuitGroup,
|
canDeleteCircuitGroup,
|
||||||
renameCircuitGroupSnapshot,
|
renameCircuitGroupSnapshot,
|
||||||
@@ -95,6 +96,7 @@ import {
|
|||||||
moveCircuitDeviceRowsToNewCircuitCommand,
|
moveCircuitDeviceRowsToNewCircuitCommand,
|
||||||
reorderCircuitSectionCommand,
|
reorderCircuitSectionCommand,
|
||||||
reorderCircuitSectionsCommand,
|
reorderCircuitSectionsCommand,
|
||||||
|
reorderCircuitGroupsCommand,
|
||||||
renumberCircuitSectionCommand,
|
renumberCircuitSectionCommand,
|
||||||
redoProjectCommand,
|
redoProjectCommand,
|
||||||
updateCircuitById,
|
updateCircuitById,
|
||||||
@@ -1235,6 +1237,36 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleMoveCircuitGroup(
|
||||||
|
groupId: string,
|
||||||
|
direction: -1 | 1
|
||||||
|
) {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const assignments = buildCircuitGroupReorderAssignments(
|
||||||
|
data.sections,
|
||||||
|
groupId,
|
||||||
|
direction
|
||||||
|
);
|
||||||
|
if (!assignments) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await runCommand({
|
||||||
|
label: "Stromkreisgruppen sortieren",
|
||||||
|
redo: async () => {
|
||||||
|
const result = await reorderCircuitGroupsCommand(
|
||||||
|
projectId,
|
||||||
|
getExpectedProjectRevision(),
|
||||||
|
circuitListId,
|
||||||
|
assignments
|
||||||
|
);
|
||||||
|
applyProjectCommandResult(result);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function handleSaveCircuitProtection(
|
async function handleSaveCircuitProtection(
|
||||||
protection: CircuitTreeProtectionDeviceDto
|
protection: CircuitTreeProtectionDeviceDto
|
||||||
) {
|
) {
|
||||||
@@ -3477,6 +3509,40 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
>
|
>
|
||||||
Gruppe bearbeiten
|
Gruppe bearbeiten
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
disabled={
|
||||||
|
!buildCircuitGroupReorderAssignments(
|
||||||
|
data.sections,
|
||||||
|
section.id,
|
||||||
|
-1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={() =>
|
||||||
|
void handleMoveCircuitGroup(section.id, -1)
|
||||||
|
}
|
||||||
|
title="Innerhalb derselben Kategorie nach oben verschieben"
|
||||||
|
>
|
||||||
|
Gruppe nach oben
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
tabIndex={-1}
|
||||||
|
disabled={
|
||||||
|
!buildCircuitGroupReorderAssignments(
|
||||||
|
data.sections,
|
||||||
|
section.id,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={() =>
|
||||||
|
void handleMoveCircuitGroup(section.id, 1)
|
||||||
|
}
|
||||||
|
title="Innerhalb derselben Kategorie nach unten verschieben"
|
||||||
|
>
|
||||||
|
Gruppe nach unten
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import type {
|
|||||||
} from "../../domain/models/distribution-board-component-structure-project-command.model";
|
} from "../../domain/models/distribution-board-component-structure-project-command.model";
|
||||||
import type {
|
import type {
|
||||||
CircuitGroupSnapshot,
|
CircuitGroupSnapshot,
|
||||||
|
CircuitGroupReorderAssignment,
|
||||||
} from "../../domain/models/circuit-group-structure-project-command.model";
|
} from "../../domain/models/circuit-group-structure-project-command.model";
|
||||||
import type {
|
import type {
|
||||||
CircuitProtectionSnapshot,
|
CircuitProtectionSnapshot,
|
||||||
@@ -433,6 +434,24 @@ export function deleteCircuitGroupCommand(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function reorderCircuitGroupsCommand(
|
||||||
|
projectId: string,
|
||||||
|
expectedRevision: number,
|
||||||
|
circuitListId: string,
|
||||||
|
assignments: CircuitGroupReorderAssignment[]
|
||||||
|
) {
|
||||||
|
return executeProjectCommand(
|
||||||
|
projectId,
|
||||||
|
expectedRevision,
|
||||||
|
{
|
||||||
|
schemaVersion: 1,
|
||||||
|
type: "circuit-group.reorder",
|
||||||
|
payload: { circuitListId, assignments },
|
||||||
|
},
|
||||||
|
"Stromkreisgruppen sortieren"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function updateCircuitProtectionCommand(
|
export function updateCircuitProtectionCommand(
|
||||||
projectId: string,
|
projectId: string,
|
||||||
expectedRevision: number,
|
expectedRevision: number,
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import {
|
|||||||
type CircuitGroupCategory,
|
type CircuitGroupCategory,
|
||||||
} from "../../shared/constants/circuit-group";
|
} from "../../shared/constants/circuit-group";
|
||||||
import type { CircuitTreeSectionDto } from "../types";
|
import type { CircuitTreeSectionDto } from "../types";
|
||||||
|
import type {
|
||||||
|
CircuitGroupReorderAssignment,
|
||||||
|
} from "../../domain/models/circuit-group-structure-project-command.model";
|
||||||
|
|
||||||
export function toCircuitGroupSnapshot(
|
export function toCircuitGroupSnapshot(
|
||||||
section: CircuitTreeSectionDto,
|
section: CircuitTreeSectionDto,
|
||||||
@@ -77,6 +80,41 @@ export function canDeleteCircuitGroup(
|
|||||||
return section.circuits.length === 0 && section.components.length === 0;
|
return section.circuits.length === 0 && section.components.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildCircuitGroupReorderAssignments(
|
||||||
|
sections: readonly CircuitTreeSectionDto[],
|
||||||
|
groupId: string,
|
||||||
|
direction: -1 | 1
|
||||||
|
): CircuitGroupReorderAssignment[] | null {
|
||||||
|
const current = sections.find((section) => section.id === groupId);
|
||||||
|
if (!current?.category) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const categoryGroups = sections
|
||||||
|
.filter((section) => section.category === current.category)
|
||||||
|
.sort(
|
||||||
|
(left, right) =>
|
||||||
|
left.sortOrder - right.sortOrder ||
|
||||||
|
left.id.localeCompare(right.id)
|
||||||
|
);
|
||||||
|
const currentIndex = categoryGroups.findIndex(
|
||||||
|
(section) => section.id === groupId
|
||||||
|
);
|
||||||
|
const target = categoryGroups[currentIndex + direction];
|
||||||
|
if (!target) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return sections.map((section) => ({
|
||||||
|
groupId: section.id,
|
||||||
|
expectedSortOrder: section.sortOrder,
|
||||||
|
targetSortOrder:
|
||||||
|
section.id === current.id
|
||||||
|
? target.sortOrder
|
||||||
|
: section.id === target.id
|
||||||
|
? current.sortOrder
|
||||||
|
: section.sortOrder,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
function getNewGroupSortOrder(
|
function getNewGroupSortOrder(
|
||||||
category: CircuitGroupCategory,
|
category: CircuitGroupCategory,
|
||||||
sections: readonly (CircuitTreeSectionDto & {
|
sections: readonly (CircuitTreeSectionDto & {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import "./circuit-group-move-project-command.repository.test.js";
|
|||||||
import "./circuit-group-subtree-snapshot.test.js";
|
import "./circuit-group-subtree-snapshot.test.js";
|
||||||
import "./circuit-group-subtree-project-command.repository.test.js";
|
import "./circuit-group-subtree-project-command.repository.test.js";
|
||||||
import {
|
import {
|
||||||
|
buildCircuitGroupReorderAssignments,
|
||||||
buildNewCircuitGroupSnapshot,
|
buildNewCircuitGroupSnapshot,
|
||||||
canDeleteCircuitGroup,
|
canDeleteCircuitGroup,
|
||||||
renameCircuitGroupSnapshot,
|
renameCircuitGroupSnapshot,
|
||||||
@@ -69,6 +70,54 @@ describe("circuit group numbering", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reorders complete group lists only between category peers", () => {
|
||||||
|
const sections = [
|
||||||
|
groupSection("lighting-1", "lighting", 1, 10),
|
||||||
|
groupSection("lighting-2", "lighting", 2, 15),
|
||||||
|
groupSection("single-1", "single_phase", 1, 20),
|
||||||
|
];
|
||||||
|
assert.deepEqual(
|
||||||
|
buildCircuitGroupReorderAssignments(
|
||||||
|
sections,
|
||||||
|
"lighting-1",
|
||||||
|
1
|
||||||
|
),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
groupId: "lighting-1",
|
||||||
|
expectedSortOrder: 10,
|
||||||
|
targetSortOrder: 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
groupId: "lighting-2",
|
||||||
|
expectedSortOrder: 15,
|
||||||
|
targetSortOrder: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
groupId: "single-1",
|
||||||
|
expectedSortOrder: 20,
|
||||||
|
targetSortOrder: 20,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
buildCircuitGroupReorderAssignments(
|
||||||
|
sections,
|
||||||
|
"lighting-1",
|
||||||
|
-1
|
||||||
|
),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
buildCircuitGroupReorderAssignments(
|
||||||
|
sections,
|
||||||
|
"single-1",
|
||||||
|
1
|
||||||
|
),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("formats the agreed identifiers including the leading hyphen", () => {
|
it("formats the agreed identifiers including the leading hyphen", () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
formatGroupUpstreamProtectionIdentifier("lighting", 1),
|
formatGroupUpstreamProtectionIdentifier("lighting", 1),
|
||||||
|
|||||||
Reference in New Issue
Block a user