From 8c07cf36eeb4cb67c3585b732148b003dc6e68a2 Mon Sep 17 00:00:00 2001 From: Lars Date: Sat, 16 May 2026 08:34:30 +0200 Subject: [PATCH] Enhance section movement functionality in TrainingUnitSectionsEditor - Updated the onMoveSectionsAcrossSlots function to support additional parameters for improved section movement across slots, including handling for parallel phase indices and insertion points. - Refined logic for moving sections between slots, ensuring proper handling of parallel streams and enhancing the overall section management experience. --- .../components/TrainingUnitSectionsEditor.jsx | 78 ++++++++++++++- .../TrainingFrameworkProgramEditPage.jsx | 94 ++++++++++++++++--- 2 files changed, 156 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/TrainingUnitSectionsEditor.jsx b/frontend/src/components/TrainingUnitSectionsEditor.jsx index 724545a..a58fe7f 100644 --- a/frontend/src/components/TrainingUnitSectionsEditor.jsx +++ b/frontend/src/components/TrainingUnitSectionsEditor.jsx @@ -245,7 +245,7 @@ function reorderBlocksImmutable(blocks, fromI, toBeforeIdx) { /** * @param {(updater: (prev: Array) => Array) => void} props.onSectionsChange — wie React setState - * @param {(p: { fromSlot: number, fromSectionIdx: number, toSlot: number, toSectionIdx: number, toParallelStream?: { po: number, so: number } }) => void} [props.onMoveSectionsAcrossSlots] — Rahmenprogramm: Abschnitt zwischen Slots verschieben + * @param {(p: { fromSlot: number, fromSectionIdx?: number, toSlot: number, toSectionIdx: number, toParallelStream?: { po: number, so: number }, parallelPhaseRunOrderIndex?: number, insertBeforeParallelInTarget?: number, firstInParallelStreamInTarget?: { po: number, so: number } }) => void} [props.onMoveSectionsAcrossSlots] — Rahmenprogramm: Abschnitt(e) zwischen Slots verschieben */ export default function TrainingUnitSectionsEditor({ sections, @@ -807,6 +807,22 @@ export default function TrainingUnitSectionsEditor({ if (parsed.kind === 'phaseRun') { const dragPo = Number(parsed.phaseRunMove.phaseOrderIndex) || 0 if (dragPo === targetPo) return + const fs = typeof parsed.fromSlot === 'number' ? parsed.fromSlot : -1 + if ( + typeof onMoveSectionsAcrossSlots === 'function' && + sectionToSlot >= 0 && + fs >= 0 && + fs !== sectionToSlot + ) { + const ins = indicesOfParallelPhase(list, targetPo)[0] ?? list.length + onMoveSectionsAcrossSlots({ + fromSlot: fs, + toSlot: sectionToSlot, + toSectionIdx: ins, + parallelPhaseRunOrderIndex: dragPo, + }) + return + } patch((prev) => { const idxs = indicesOfParallelPhase(prev, targetPo) const fg = idxs.length ? idxs[0] : -1 @@ -818,7 +834,17 @@ export default function TrainingUnitSectionsEditor({ return } - if (parsed.kind === 'crossSlot') return + if (parsed.kind === 'crossSlot') { + if (typeof onMoveSectionsAcrossSlots !== 'function') return + onMoveSectionsAcrossSlots({ + fromSlot: parsed.fromSlot, + fromSectionIdx: parsed.fromSi, + toSlot: sectionToSlot, + toSectionIdx: 0, + insertBeforeParallelInTarget: targetPo, + }) + return + } const { fromSi } = parsed patch((prev) => { @@ -854,6 +880,23 @@ export default function TrainingUnitSectionsEditor({ if (parsed.kind === 'phaseRun') { const dragPo = Number(parsed.phaseRunMove.phaseOrderIndex) || 0 if (dragPo === targetPo) return + const fs = typeof parsed.fromSlot === 'number' ? parsed.fromSlot : -1 + if ( + typeof onMoveSectionsAcrossSlots === 'function' && + sectionToSlot >= 0 && + fs >= 0 && + fs !== sectionToSlot + ) { + const si = sectionIndicesForParallelStream(list, targetPo, targetSo) + const ins = si.length ? si[0] : indicesOfParallelPhase(list, targetPo)[0] ?? list.length + onMoveSectionsAcrossSlots({ + fromSlot: fs, + toSlot: sectionToSlot, + toSectionIdx: ins, + parallelPhaseRunOrderIndex: dragPo, + }) + return + } patch((prev) => { const idxs = indicesOfParallelPhase(prev, targetPo) const fg = idxs.length ? idxs[0] : -1 @@ -865,7 +908,17 @@ export default function TrainingUnitSectionsEditor({ return } - if (parsed.kind === 'crossSlot') return + if (parsed.kind === 'crossSlot') { + if (typeof onMoveSectionsAcrossSlots !== 'function') return + onMoveSectionsAcrossSlots({ + fromSlot: parsed.fromSlot, + fromSectionIdx: parsed.fromSi, + toSlot: sectionToSlot, + toSectionIdx: 0, + firstInParallelStreamInTarget: { po: targetPo, so: targetSo }, + }) + return + } const { fromSi } = parsed patch((prev) => { @@ -898,9 +951,24 @@ export default function TrainingUnitSectionsEditor({ const fromSlot = typeof data.fromSlot === 'number' ? data.fromSlot : -1 if (phaseRunMove != null && phaseRunMove.phaseOrderIndex != null) { + const po = Number(phaseRunMove.phaseOrderIndex) || 0 + if ( + typeof onMoveSectionsAcrossSlots === 'function' && + sectionToSlot >= 0 && + fromSlot >= 0 && + fromSlot !== sectionToSlot + ) { + onMoveSectionsAcrossSlots({ + fromSlot, + toSlot: sectionToSlot, + toSectionIdx: insertBeforeIdx, + parallelPhaseRunOrderIndex: po, + }) + return + } patch((prev) => { - const po = Number(phaseRunMove.phaseOrderIndex) || 0 - let next = moveParallelPhaseRunToInsertBefore(prev, po, insertBeforeIdx) + const poLocal = Number(phaseRunMove.phaseOrderIndex) || 0 + let next = moveParallelPhaseRunToInsertBefore(prev, poLocal, insertBeforeIdx) if (enableParallelPhaseControls) next = afterSectionReorderParallelGuard(prev, next) return next }) diff --git a/frontend/src/pages/TrainingFrameworkProgramEditPage.jsx b/frontend/src/pages/TrainingFrameworkProgramEditPage.jsx index 600b55a..3b226f9 100644 --- a/frontend/src/pages/TrainingFrameworkProgramEditPage.jsx +++ b/frontend/src/pages/TrainingFrameworkProgramEditPage.jsx @@ -15,6 +15,9 @@ import { buildPlanPayloadForSave, hydrateExercisePlanningRow, reorderBlockIntoParallelStreamEnd, + indicesOfParallelPhase, + reorderSectionBeforeParallelRunAsWholeGroup, + reorderSectionAsFirstInParallelStream, } from '../utils/trainingUnitSectionsForm' const DND_FW_SLOT = 'application/x-shinkan-framework-slot' @@ -561,7 +564,17 @@ export default function TrainingFrameworkProgramEditPage() { } const moveSectionsAcrossFrameworkSlots = useCallback( - ({ fromSlot, fromSectionIdx, toSlot, toSectionIdx, toParallelStream }) => { + (payload) => { + const { + fromSlot, + fromSectionIdx, + toSlot, + toSectionIdx, + toParallelStream, + parallelPhaseRunOrderIndex, + insertBeforeParallelInTarget, + firstInParallelStreamInTarget, + } = payload setForm((prev) => { const slots = prev.slots.map((sl) => ({ ...sl, @@ -579,15 +592,7 @@ export default function TrainingFrameworkProgramEditPage() { } const fromSecs = slots[fromSlot].sections - if ( - typeof fromSectionIdx !== 'number' || - fromSectionIdx < 0 || - fromSectionIdx >= fromSecs.length - ) { - return prev - } - - const [block] = fromSecs.splice(fromSectionIdx, 1) + const toSecs = slots[toSlot].sections const applyParallelStreamEnd = toParallelStream != null && toParallelStream.po != null && toParallelStream.so != null @@ -598,6 +603,43 @@ export default function TrainingFrameworkProgramEditPage() { } : null + /** Gesamten Parallel-Lauf aus fromSlot an toSectionIdx in toSlot legen */ + if (parallelPhaseRunOrderIndex != null && parallelPhaseRunOrderIndex !== '') { + const po = Number(parallelPhaseRunOrderIndex) || 0 + const idxs = indicesOfParallelPhase(fromSecs, po) + if (!idxs.length) { + return prev + } + const blocks = idxs.map((i) => fromSecs[i]) + for (const i of [...idxs].sort((a, b) => b - a)) { + fromSecs.splice(i, 1) + } + if (fromSlot === toSlot) { + let insertAt = Number(toSectionIdx) || 0 + for (const i of idxs) { + if (i < insertAt) insertAt -= 1 + } + insertAt = Math.max(0, Math.min(insertAt, fromSecs.length)) + fromSecs.splice(insertAt, 0, ...blocks) + slots[fromSlot].sections = fromSecs + return { ...prev, slots } + } + const insertAt = Math.max(0, Math.min(Number(toSectionIdx) || 0, toSecs.length)) + toSecs.splice(insertAt, 0, ...blocks) + slots[toSlot].sections = toSecs + return { ...prev, slots } + } + + if ( + typeof fromSectionIdx !== 'number' || + fromSectionIdx < 0 || + fromSectionIdx >= fromSecs.length + ) { + return prev + } + + const [block] = fromSecs.splice(fromSectionIdx, 1) + if (fromSlot === toSlot) { let insertAt = toSectionIdx if (fromSectionIdx < toSectionIdx) insertAt = toSectionIdx - 1 @@ -605,15 +647,45 @@ export default function TrainingFrameworkProgramEditPage() { fromSecs.splice(insertAt, 0, block) if (applyParallelStreamEnd) { slots[fromSlot].sections = applyParallelStreamEnd(fromSecs, insertAt) + } else { + slots[fromSlot].sections = fromSecs } return { ...prev, slots } } - const toSecs = slots[toSlot].sections + if (insertBeforeParallelInTarget != null && insertBeforeParallelInTarget !== '') { + const tpo = Number(insertBeforeParallelInTarget) || 0 + const pIdxs = indicesOfParallelPhase(toSecs, tpo) + const ins = pIdxs.length ? pIdxs[0] : toSecs.length + toSecs.splice(ins, 0, block) + slots[toSlot].sections = reorderSectionBeforeParallelRunAsWholeGroup(toSecs, ins, tpo) + return { ...prev, slots } + } + + if ( + firstInParallelStreamInTarget != null && + firstInParallelStreamInTarget.po != null && + firstInParallelStreamInTarget.so != null + ) { + const fpo = Number(firstInParallelStreamInTarget.po) || 0 + const fso = Number(firstInParallelStreamInTarget.so) || 0 + const nextSecs = [...toSecs, block] + const movedFromI = nextSecs.length - 1 + slots[toSlot].sections = reorderSectionAsFirstInParallelStream( + nextSecs, + movedFromI, + fpo, + fso + ) + return { ...prev, slots } + } + const ia = Math.max(0, Math.min(toSectionIdx, toSecs.length)) toSecs.splice(ia, 0, block) if (applyParallelStreamEnd) { slots[toSlot].sections = applyParallelStreamEnd(toSecs, ia) + } else { + slots[toSlot].sections = toSecs } return { ...prev, slots } })