Move circuits between groups

This commit is contained in:
2026-07-31 07:58:42 +02:00
parent be9c6f0d52
commit 93ec294759
6 changed files with 336 additions and 21 deletions
+108 -20
View File
@@ -52,9 +52,11 @@ import {
buildCircuitSectionRenumberAssignments,
} from "../utils/circuit-section-renumber-command";
import {
buildCircuitGroupMovePlan,
buildCircuitGroupRenumberPlan,
buildCircuitGroupReorderAssignments,
buildNewCircuitGroupSnapshot,
canMoveCircuitToGroup,
canRenumberCircuitGroups,
canDeleteCircuitGroup,
renameCircuitGroupSnapshot,
@@ -96,6 +98,7 @@ import {
listProjectDevices,
moveCircuitDeviceRowsCommand,
moveCircuitDeviceRowsToNewCircuitCommand,
moveCircuitToGroupCommand,
reorderCircuitSectionCommand,
reorderCircuitSectionsCommand,
reorderCircuitGroupsCommand,
@@ -2524,7 +2527,8 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
});
}
// Handles circuit drag intent and reorders whole circuit blocks within one section only.
// Reorders whole circuit blocks within a group or moves one complete circuit
// to another group of the same category.
async function handleCircuitReorderDrop(event: DragEvent<HTMLElement>, intent: CircuitReorderDropIntent) {
event.preventDefault();
event.stopPropagation();
@@ -2538,8 +2542,75 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
setError("Der gezogene Stromkreis fehlt.");
return;
}
const sourceSectionIds = sourceCircuitIds
.map((id) => findCircuitSectionId(id))
.filter((id): id is string => Boolean(id));
const isCrossGroupMove =
sourceSectionIds.length === 1 &&
sourceSectionIds[0] !== intent.sectionId;
if (isCrossGroupMove) {
if (
sourceCircuitIds.length !== 1 ||
!canMoveCircuitToGroup(
data?.sections ?? [],
sourceCircuitIds[0],
intent.sectionId
)
) {
setError(
"Gruppenübergreifend kann genau ein Stromkreis in eine Gruppe derselben Kategorie verschoben werden."
);
return;
}
let plan: ReturnType<typeof buildCircuitGroupMovePlan>;
try {
plan = buildCircuitGroupMovePlan({
sections: data?.sections ?? [],
circuitId: sourceCircuitIds[0],
targetSectionId: intent.sectionId,
placement:
intent.kind === "section-end"
? { kind: "end" }
: {
kind:
intent.kind === "before-circuit"
? "before"
: "after",
targetCircuitId: intent.targetCircuitId,
},
});
} catch (err) {
setError(normalizeUiError(err));
return;
}
const selectionIntent: SelectionIntent = {
rowKey: `circuitSummary:${plan.circuitId}`,
cellKey: "equipmentIdentifier",
rowType: "circuitSummary",
sectionId: plan.targetSectionId,
circuitId: plan.circuitId,
};
await runCommand({
label: "Stromkreis in andere Gruppe verschieben",
redo: async () => {
const result = await moveCircuitToGroupCommand(
projectId,
getExpectedProjectRevision(),
plan
);
applyProjectCommandResult(result);
pendingSelectedCircuitIdsAfterReload.current = [
plan.circuitId,
];
return selectionIntent;
},
});
return;
}
if (!intent.valid) {
setError("Stromkreise können derzeit nicht bereichsübergreifend verschoben werden.");
setError(
"Stromkreise können nur innerhalb einer Gruppe oder einzeln zwischen Gruppen derselben Kategorie verschoben werden."
);
return;
}
const section = data?.sections.find((entry) => entry.id === intent.sectionId);
@@ -2549,7 +2620,9 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
}
const sectionCircuitIds = new Set(section.circuits.map((circuit) => circuit.id));
if (sourceCircuitIds.some((id) => !sectionCircuitIds.has(id))) {
setError("Stromkreise können derzeit nicht bereichsübergreifend verschoben werden.");
setError(
"Die ausgewählten Stromkreise gehören nicht zur Zielgruppe."
);
return;
}
const primaryCircuitId = sourceCircuitIds[0];
@@ -2892,6 +2965,23 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
const activeDraggedCircuitIds =
draggingCircuitIds.length > 0 ? draggingCircuitIds : draggingCircuitId ? [draggingCircuitId] : [];
const draggingCircuitCount = activeDraggedCircuitIds.length;
const isCircuitDropTargetValid = (targetSectionId: string) => {
const sourceSectionIds = activeDraggedCircuitIds
.map((id) => findCircuitSectionId(id))
.filter((id): id is string => Boolean(id));
return (
(sourceSectionIds.length > 0 &&
sourceSectionIds.every(
(sectionId) => sectionId === targetSectionId
)) ||
(activeDraggedCircuitIds.length === 1 &&
canMoveCircuitToGroup(
data.sections,
activeDraggedCircuitIds[0],
targetSectionId
))
);
};
const selectedProjectDevice = resolveSelectedProjectDevice();
const suggestedSection = selectedProjectDevice
? data.sections.find((section) => section.key === inferProjectDeviceSectionKey(selectedProjectDevice))
@@ -3479,12 +3569,17 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
}`}
onDragOver={(event) => {
if (draggingCircuitCount > 0) {
const valid = isCircuitDropTargetValid(
section.id
);
event.preventDefault();
event.dataTransfer.dropEffect = "none";
event.dataTransfer.dropEffect = valid
? "move"
: "none";
setCircuitReorderIntent({
kind: "section-end",
sectionId: section.id,
valid: false,
valid,
});
return;
}
@@ -3519,7 +3614,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
void handleCircuitReorderDrop(event, {
kind: "section-end",
sectionId: section.id,
valid: false,
valid: isCircuitDropTargetValid(section.id),
});
}
}}
@@ -3803,10 +3898,9 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
onClick={() => setActiveSectionId(row.sectionId)}
onDragOver={(event) => {
if (draggingCircuitCount > 0) {
const sourceSectionIds = activeDraggedCircuitIds
.map((id) => findCircuitSectionId(id))
.filter((id): id is string => Boolean(id));
const valid = sourceSectionIds.length > 0 && sourceSectionIds.every((sectionId) => sectionId === row.sectionId);
const valid = isCircuitDropTargetValid(
row.sectionId
);
if (row.rowType === "placeholder") {
event.preventDefault();
event.dataTransfer.dropEffect = valid ? "move" : "none";
@@ -3929,13 +4023,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
onDrop={(event) => {
if (draggingCircuitCount > 0) {
if (row.rowType === "placeholder") {
const sourceSectionIds = activeDraggedCircuitIds
.map((id) => findCircuitSectionId(id))
.filter((id): id is string => Boolean(id));
void handleCircuitReorderDrop(event, {
kind: "section-end",
sectionId: row.sectionId,
valid: sourceSectionIds.length > 0 && sourceSectionIds.every((sectionId) => sectionId === row.sectionId),
valid: isCircuitDropTargetValid(row.sectionId),
});
return;
}
@@ -3943,16 +4034,13 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
if (activeDraggedCircuitIds.includes(row.circuit.id)) {
return;
}
const sourceSectionIds = activeDraggedCircuitIds
.map((id) => findCircuitSectionId(id))
.filter((id): id is string => Boolean(id));
const rect = (event.currentTarget as HTMLTableRowElement).getBoundingClientRect();
const isAfter = event.clientY > rect.top + rect.height / 2;
void handleCircuitReorderDrop(event, {
kind: isAfter ? "after-circuit" : "before-circuit",
sectionId: row.sectionId,
targetCircuitId: row.circuit.id,
valid: sourceSectionIds.length > 0 && sourceSectionIds.every((sectionId) => sectionId === row.sectionId),
valid: isCircuitDropTargetValid(row.sectionId),
});
}
return;
@@ -4283,7 +4371,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
<span className="drop-hint">
{circuitReorderIntent.valid
? `${draggingCircuitCount || 1} Stromkreis(e) ans Bereichsende verschieben`
: "Bereichsübergreifendes Verschieben nicht zulässig"}
: "Nur ein Stromkreis kann zwischen Gruppen derselben Kategorie verschoben werden"}
</span>
) : null}
{circuitReorderIntent &&
@@ -4294,7 +4382,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
? circuitReorderIntent.kind === "before-circuit"
? `${draggingCircuitCount || 1} Stromkreis(e) vor diesen Stromkreis verschieben`
: `${draggingCircuitCount || 1} Stromkreis(e) hinter diesen Stromkreis verschieben`
: "Bereichsübergreifendes Verschieben nicht zulässig"}
: "Nur ein Stromkreis kann zwischen Gruppen derselben Kategorie verschoben werden"}
</span>
) : null}
</td>