Persist circuit reorders
This commit is contained in:
@@ -30,6 +30,9 @@ import {
|
||||
import {
|
||||
buildCircuitDeviceRowMoveAssignments,
|
||||
} from "../utils/circuit-device-row-move-command";
|
||||
import {
|
||||
buildCircuitSectionReorderAssignments,
|
||||
} from "../utils/circuit-section-reorder-command";
|
||||
import type {
|
||||
CellKey,
|
||||
CellKind,
|
||||
@@ -52,7 +55,8 @@ import {
|
||||
listProjectDevices,
|
||||
moveCircuitDeviceRowsCommand,
|
||||
moveCircuitDeviceRowsToNewCircuitCommand,
|
||||
reorderSectionCircuits,
|
||||
reorderCircuitSectionCommand,
|
||||
reorderCircuitSectionsCommand,
|
||||
renumberCircuitSection,
|
||||
redoProjectCommand,
|
||||
updateSectionEquipmentIdentifiers,
|
||||
@@ -991,22 +995,42 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
return;
|
||||
}
|
||||
|
||||
// Sorting is view-only until explicitly applied; this avoids accidental persisted reorders.
|
||||
const reorderedSections = changedSections.map((changedSection) => {
|
||||
const currentSection = data.sections.find(
|
||||
(section) => section.id === changedSection.sectionId
|
||||
);
|
||||
if (!currentSection) {
|
||||
throw new Error("Ein sortierter Bereich wurde nicht gefunden.");
|
||||
}
|
||||
return {
|
||||
sectionId: currentSection.id,
|
||||
assignments: buildCircuitSectionReorderAssignments(
|
||||
currentSection.circuits,
|
||||
changedSection.order
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
// Sorting is view-only until explicitly applied. All affected sections are
|
||||
// then committed as one atomic project-history entry.
|
||||
await runCommand({
|
||||
label: "Sortierte Reihenfolge übernehmen",
|
||||
redo: async () => {
|
||||
for (const section of changedSections) {
|
||||
await reorderSectionCircuits(section.sectionId, section.order);
|
||||
}
|
||||
const result = await reorderCircuitSectionsCommand(
|
||||
projectId,
|
||||
getExpectedProjectRevision(),
|
||||
reorderedSections,
|
||||
"Sortierte Reihenfolge übernehmen"
|
||||
);
|
||||
applyProjectCommandResult(result);
|
||||
setSortState(null);
|
||||
setOpenFilterColumn(null);
|
||||
return null;
|
||||
},
|
||||
undo: async () => {
|
||||
for (const section of beforeBySection) {
|
||||
await reorderSectionCircuits(section.sectionId, section.order);
|
||||
}
|
||||
return null;
|
||||
undo: async () => null,
|
||||
persistent: {
|
||||
undoIntent: () => null,
|
||||
redoIntent: () => null,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -2112,7 +2136,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
|
||||
// Applies same-section circuit reorder as explicit id ordering.
|
||||
// No implicit renumbering is performed here.
|
||||
async function applyCircuitReorder(intent: CircuitReorderDropIntent, sourceCircuitIds: string[]) {
|
||||
function resolveCircuitReorderOrder(intent: CircuitReorderDropIntent, sourceCircuitIds: string[]) {
|
||||
const section = data?.sections.find((entry) => entry.id === intent.sectionId);
|
||||
if (!section) {
|
||||
throw new Error("Der Bereich ist ungültig.");
|
||||
@@ -2135,7 +2159,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
const insertIndex = intent.kind === "after-circuit" ? targetIndex + 1 : targetIndex;
|
||||
nextIds.splice(insertIndex, 0, ...block);
|
||||
}
|
||||
await reorderSectionCircuits(section.id, nextIds);
|
||||
return nextIds;
|
||||
}
|
||||
|
||||
// Handles project-device drag intent. This path creates linked rows/circuits and
|
||||
@@ -2414,31 +2438,55 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
setError("Stromkreise können derzeit nicht bereichsübergreifend verschoben werden.");
|
||||
return;
|
||||
}
|
||||
const beforeOrder = section.circuits.map((circuit) => circuit.id);
|
||||
const primaryCircuitId = sourceCircuitIds[0];
|
||||
const targetOrder = resolveCircuitReorderOrder(
|
||||
intent,
|
||||
sourceCircuitIds
|
||||
);
|
||||
const assignments = buildCircuitSectionReorderAssignments(
|
||||
section.circuits,
|
||||
targetOrder
|
||||
);
|
||||
if (assignments.length === 0) {
|
||||
return;
|
||||
}
|
||||
const label =
|
||||
sourceCircuitIds.length > 1
|
||||
? `${sourceCircuitIds.length} Stromkreise neu anordnen`
|
||||
: "Stromkreise neu anordnen";
|
||||
const selectionIntent: SelectionIntent = {
|
||||
rowKey: `circuitSummary:${primaryCircuitId}`,
|
||||
cellKey: "equipmentIdentifier",
|
||||
rowType: "circuitSummary",
|
||||
sectionId: intent.sectionId,
|
||||
circuitId: primaryCircuitId,
|
||||
};
|
||||
await runCommand({
|
||||
label: sourceCircuitIds.length > 1 ? `${sourceCircuitIds.length} Stromkreise neu anordnen` : "Stromkreise neu anordnen",
|
||||
label,
|
||||
redo: async () => {
|
||||
await applyCircuitReorder(intent, sourceCircuitIds);
|
||||
const result = await reorderCircuitSectionCommand(
|
||||
projectId,
|
||||
getExpectedProjectRevision(),
|
||||
intent.sectionId,
|
||||
assignments,
|
||||
label
|
||||
);
|
||||
applyProjectCommandResult(result);
|
||||
pendingSelectedCircuitIdsAfterReload.current = sourceCircuitIds;
|
||||
return {
|
||||
rowKey: `circuitSummary:${primaryCircuitId}`,
|
||||
cellKey: "equipmentIdentifier",
|
||||
rowType: "circuitSummary",
|
||||
sectionId: intent.sectionId,
|
||||
circuitId: primaryCircuitId,
|
||||
};
|
||||
return selectionIntent;
|
||||
},
|
||||
undo: async () => {
|
||||
await reorderSectionCircuits(intent.sectionId, beforeOrder);
|
||||
pendingSelectedCircuitIdsAfterReload.current = sourceCircuitIds;
|
||||
return {
|
||||
rowKey: `circuitSummary:${primaryCircuitId}`,
|
||||
cellKey: "equipmentIdentifier",
|
||||
rowType: "circuitSummary",
|
||||
sectionId: intent.sectionId,
|
||||
circuitId: primaryCircuitId,
|
||||
};
|
||||
undo: async () => null,
|
||||
persistent: {
|
||||
undoIntent: () => {
|
||||
pendingSelectedCircuitIdsAfterReload.current =
|
||||
sourceCircuitIds;
|
||||
return selectionIntent;
|
||||
},
|
||||
redoIntent: () => {
|
||||
pendingSelectedCircuitIdsAfterReload.current =
|
||||
sourceCircuitIds;
|
||||
return selectionIntent;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,6 +36,12 @@ import type {
|
||||
import type {
|
||||
CircuitDeviceRowMoveAssignment,
|
||||
} from "../../domain/models/circuit-device-row-move-project-command.model";
|
||||
import type {
|
||||
CircuitSectionReorderAssignment,
|
||||
} from "../../domain/models/circuit-section-reorder-project-command.model";
|
||||
import type {
|
||||
CircuitSectionsReorderSection,
|
||||
} from "../../domain/models/circuit-sections-reorder-project-command.model";
|
||||
|
||||
async function request<T>(url: string, init?: RequestInit): Promise<T> {
|
||||
const response = await fetch(url, {
|
||||
@@ -319,11 +325,41 @@ export function renumberCircuitSection(sectionId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function reorderSectionCircuits(sectionId: string, orderedCircuitIds: string[]) {
|
||||
return request(`/api/circuit-sections/${sectionId}/circuits/reorder`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ orderedCircuitIds }),
|
||||
});
|
||||
export function reorderCircuitSectionCommand(
|
||||
projectId: string,
|
||||
expectedRevision: number,
|
||||
sectionId: string,
|
||||
assignments: CircuitSectionReorderAssignment[],
|
||||
description: string
|
||||
) {
|
||||
return executeProjectCommand(
|
||||
projectId,
|
||||
expectedRevision,
|
||||
{
|
||||
schemaVersion: 1,
|
||||
type: "circuit.reorder-section",
|
||||
payload: { sectionId, assignments },
|
||||
},
|
||||
description
|
||||
);
|
||||
}
|
||||
|
||||
export function reorderCircuitSectionsCommand(
|
||||
projectId: string,
|
||||
expectedRevision: number,
|
||||
sections: CircuitSectionsReorderSection[],
|
||||
description: string
|
||||
) {
|
||||
return executeProjectCommand(
|
||||
projectId,
|
||||
expectedRevision,
|
||||
{
|
||||
schemaVersion: 1,
|
||||
type: "circuit.reorder-sections",
|
||||
payload: { sections },
|
||||
},
|
||||
description
|
||||
);
|
||||
}
|
||||
|
||||
export function updateSectionEquipmentIdentifiers(
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import type {
|
||||
CircuitSectionReorderAssignment,
|
||||
} from "../../domain/models/circuit-section-reorder-project-command.model.js";
|
||||
import type { CircuitTreeCircuitDto } from "../types.js";
|
||||
|
||||
export function buildCircuitSectionReorderAssignments(
|
||||
circuits: CircuitTreeCircuitDto[],
|
||||
orderedCircuitIds: string[]
|
||||
): CircuitSectionReorderAssignment[] {
|
||||
if (
|
||||
circuits.length !== orderedCircuitIds.length ||
|
||||
new Set(orderedCircuitIds).size !== orderedCircuitIds.length
|
||||
) {
|
||||
throw new Error(
|
||||
"Die Reihenfolge muss jeden Stromkreis des Bereichs genau einmal enthalten."
|
||||
);
|
||||
}
|
||||
|
||||
const circuitsById = new Map(
|
||||
circuits.map((circuit) => [circuit.id, circuit])
|
||||
);
|
||||
const assignments = orderedCircuitIds.map((circuitId, index) => {
|
||||
const circuit = circuitsById.get(circuitId);
|
||||
if (!circuit) {
|
||||
throw new Error(
|
||||
"Die Reihenfolge enthält einen ungültigen Stromkreis."
|
||||
);
|
||||
}
|
||||
return {
|
||||
circuitId,
|
||||
expectedSortOrder: circuit.sortOrder,
|
||||
targetSortOrder: (index + 1) * 10,
|
||||
};
|
||||
});
|
||||
if (
|
||||
assignments.every(
|
||||
(assignment) =>
|
||||
assignment.expectedSortOrder === assignment.targetSortOrder
|
||||
)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
return assignments;
|
||||
}
|
||||
Reference in New Issue
Block a user