From 3005f1cb3e324bf76550aac470a343af7d33d562 Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 15 May 2026 12:10:44 +0200 Subject: [PATCH] Refactor TrainingUnitSectionsEditor to support new parallel stream functionality - Renamed functions and parameters to clarify the handling of sections within parallel streams, enhancing code readability. - Updated drag-and-drop event handlers to accommodate additional parameters for managing sections in parallel streams. - Introduced a new utility function to reorder sections as the first entry in a parallel stream, improving section management. - Enhanced the visual representation of drop zones for sections, ensuring a better user experience during reordering operations. --- .../components/TrainingUnitSectionsEditor.jsx | 68 ++++++++++++------- .../src/utils/trainingUnitSectionsForm.js | 52 ++++++++++++++ 2 files changed, 97 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/TrainingUnitSectionsEditor.jsx b/frontend/src/components/TrainingUnitSectionsEditor.jsx index c11ae35..c0cf4c3 100644 --- a/frontend/src/components/TrainingUnitSectionsEditor.jsx +++ b/frontend/src/components/TrainingUnitSectionsEditor.jsx @@ -25,7 +25,7 @@ import { swapAdjacentPhaseRuns, reorderBlocksImmutableWithPlanLoc, reorderSectionBeforeParallelRunAsWholeGroup, - reorderSectionAsFirstInParallelPhase, + reorderSectionAsFirstInParallelStream, reorderBlockIntoParallelStreamEnd, globalInsertBeforeIndexForParallelStreamEnd, movePhaseRunUpByPhaseOrder, @@ -744,7 +744,7 @@ export default function TrainingUnitSectionsEditor({ setDropSectionBand({ slot: sectionToSlot, phaseAboveSplitPo: Number(po) || 0 }) } - const onPhaseBelowSplitDragOver = (e, po) => { + const onPhaseBelowSplitDragOver = (e, po, so) => { if (!enableSectionDragReorder || !enableParallelPhaseControls) return if (!dtHasType(e, DND_TU_SECTION)) return e.preventDefault() @@ -754,7 +754,10 @@ export default function TrainingUnitSectionsEditor({ } catch { /* ignore */ } - setDropSectionBand({ slot: sectionToSlot, phaseBelowSplitPo: Number(po) || 0 }) + setDropSectionBand({ + slot: sectionToSlot, + phaseBelowSplit: { po: Number(po) || 0, so: Number(so) || 0 }, + }) } const applyParsedSectionDrop = (data) => { @@ -824,7 +827,7 @@ export default function TrainingUnitSectionsEditor({ }) } - const onPhaseBelowSplitDrop = (e, po) => { + const onPhaseBelowSplitDrop = (e, po, so) => { if (!enableSectionDragReorder || !enableParallelPhaseControls) return e.preventDefault() e.stopPropagation() @@ -843,6 +846,7 @@ export default function TrainingUnitSectionsEditor({ return } const targetPo = Number(po) || 0 + const targetSo = Number(so) || 0 const parsed = applyParsedSectionDrop(data) if (!parsed) return @@ -864,7 +868,7 @@ export default function TrainingUnitSectionsEditor({ const { fromSi } = parsed patch((prev) => { - let next = reorderSectionAsFirstInParallelPhase(prev, fromSi, targetPo) + let next = reorderSectionAsFirstInParallelStream(prev, fromSi, targetPo, targetSo) if (enableParallelPhaseControls) next = afterSectionReorderParallelGuard(prev, next) return next }) @@ -1383,7 +1387,7 @@ export default function TrainingUnitSectionsEditor({ dropSectionBand.beforeIdx === bx && !dropSectionBand.streamDrop && dropSectionBand.phaseAboveSplitPo == null && - dropSectionBand.phaseBelowSplitPo == null + !dropSectionBand.phaseBelowSplit const streamChipDropActive = (po, so) => useStreamTagDropUx && @@ -1398,7 +1402,8 @@ export default function TrainingUnitSectionsEditor({ const phaseBelowSplitDnd = parallelPhaseOrder != null && dropSectionBand?.slot === sectionToSlot && - dropSectionBand?.phaseBelowSplitPo === parallelPhaseOrder + dropSectionBand?.phaseBelowSplit?.po === parallelPhaseOrder && + dropSectionBand?.phaseBelowSplit?.so === activeParallelStream const streamVisual = enableParallelPhaseControls && pl?.phaseKind === 'parallel' @@ -1622,22 +1627,6 @@ export default function TrainingUnitSectionsEditor({ + Abschnitt in diesem Stream - {enableSectionDragReorder ? ( -
onPhaseBelowSplitDragOver(e, parallelPhaseOrder)} - onDragLeave={(e) => { - if (e.currentTarget.contains(e.relatedTarget)) return - clearSectionDnD() - }} - onDrop={(e) => onPhaseBelowSplitDrop(e, parallelPhaseOrder)} - /> - ) : null}
+ {enableSectionDragReorder ? ( +
+ onPhaseBelowSplitDragOver( + e, + parallelPhaseOrder, + activeParallelStream ?? 0 + ) + } + onDragLeave={(e) => { + if (e.currentTarget.contains(e.relatedTarget)) return + clearSectionDnD() + }} + onDrop={(e) => + onPhaseBelowSplitDrop( + e, + parallelPhaseOrder, + activeParallelStream ?? 0 + ) + } + /> + ) : null} ) : null} {!hideParallelSection ? ( diff --git a/frontend/src/utils/trainingUnitSectionsForm.js b/frontend/src/utils/trainingUnitSectionsForm.js index a8865fc..f2ce62f 100644 --- a/frontend/src/utils/trainingUnitSectionsForm.js +++ b/frontend/src/utils/trainingUnitSectionsForm.js @@ -905,6 +905,58 @@ export function reorderSectionAsFirstInParallelPhase(prev, fromI, phaseOrderInde return arr } +/** Abschnitt als ersten Eintrag eines parallelen Streams setzen (planLoc wie erster Abschnitt dieses Streams, bzw. leerer Stream wie reorderBlockIntoParallelStreamEnd). */ +export function reorderSectionAsFirstInParallelStream(prev, fromI, phaseOrderIndex, streamOrderIndex) { + const po = Number(phaseOrderIndex) || 0 + const so = Number(streamOrderIndex) || 0 + const len = prev?.length ?? 0 + if (fromI < 0 || fromI >= len) return prev + + const arr = [...prev] + const [moved] = arr.splice(fromI, 1) + + const streamIdx = sectionIndicesForParallelStream(arr, po, so) + let insertAt + let headTpl + let skipFromIAdjust = false + + if (streamIdx.length) { + const first = Math.min(...streamIdx) + headTpl = { ...arr[first].planLoc } + insertAt = first + } else { + const phaseIdx = indicesOfParallelPhase(arr, po) + if (!phaseIdx.length) { + const ml = moved?.planLoc + if (ml?.phaseKind !== 'parallel' || (ml.phaseOrderIndex ?? 0) !== po) return prev + headTpl = { + ...ml, + parallelStreamOrderIndex: so, + streamTitle: null, + streamNotes: null, + streamAssignedTrainerProfileIds: null, + } + insertAt = Math.min(fromI, arr.length) + skipFromIAdjust = true + } else { + const ref = arr[phaseIdx[phaseIdx.length - 1]] + headTpl = { + ...ref.planLoc, + parallelStreamOrderIndex: so, + streamTitle: null, + streamNotes: null, + streamAssignedTrainerProfileIds: null, + } + insertAt = phaseIdx[phaseIdx.length - 1] + 1 + } + } + + if (!skipFromIAdjust && fromI < insertAt) insertAt -= 1 + insertAt = Math.max(0, Math.min(insertAt, arr.length)) + arr.splice(insertAt, 0, { ...moved, planLoc: { ...headTpl } }) + return arr +} + /** * Abschnitt ans Ende eines parallelen Streams setzen (planLoc wie dieser Stream). * Leerer Stream: Einfügen hinter den letzten Abschnitt der zugehörigen parallelen Phase, planLoc vom Referenz-Abschnitt mit angepasstem streamIndex.