diff --git a/frontend/src/components/TrainingUnitSectionsEditor.jsx b/frontend/src/components/TrainingUnitSectionsEditor.jsx index a6d9e93..7342ed9 100644 --- a/frontend/src/components/TrainingUnitSectionsEditor.jsx +++ b/frontend/src/components/TrainingUnitSectionsEditor.jsx @@ -243,7 +243,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 }) => void} [props.onMoveSectionsAcrossSlots] — Rahmenprogramm: Abschnitt zwischen Slots verschieben + * @param {(p: { fromSlot: number, fromSectionIdx: number, toSlot: number, toSectionIdx: number, toParallelStream?: { po: number, so: number } }) => void} [props.onMoveSectionsAcrossSlots] — Rahmenprogramm: Abschnitt zwischen Slots verschieben */ export default function TrainingUnitSectionsEditor({ sections, @@ -463,8 +463,12 @@ export default function TrainingUnitSectionsEditor({ const removeSection = (sIdx) => { patch((prev) => { - const next = prev.filter((_, i) => i !== sIdx) - return next.length ? next : [defaultSection()] + let next = prev.filter((_, i) => i !== sIdx) + next = next.length ? next : [defaultSection()] + if (enableParallelPhaseControls) { + next = afterSectionReorderParallelGuard(prev, next) + } + return next }) } @@ -843,6 +847,7 @@ export default function TrainingUnitSectionsEditor({ fromSectionIdx: fromSi, toSlot: sectionToSlot, toSectionIdx: toIdx, + toParallelStream: { po, so }, }) return } @@ -1177,6 +1182,12 @@ export default function TrainingUnitSectionsEditor({ parallelPhaseOrder != null ? firstSectionIndexByParallelPhase.get(parallelPhaseOrder) : null + const phaseIndicesAll = + parallelPhaseOrder != null ? indicesOfParallelPhase(list, parallelPhaseOrder) : [] + const isLastGlobalRowOfParallelPhase = + pl?.phaseKind === 'parallel' && + phaseIndicesAll.length > 0 && + sIdx === phaseIndicesAll[phaseIndicesAll.length - 1] const firstVisibleIdxActiveStream = parallelPhaseOrder != null && streamOrdersForParallelPhase.length ? sectionIndicesForParallelStream( @@ -1193,7 +1204,9 @@ export default function TrainingUnitSectionsEditor({ sIdx !== firstGlobalIdxThisPhase const showSectionDropBandBefore = - (pl?.phaseKind !== 'parallel' || !hideParallelSection) && + (pl?.phaseKind !== 'parallel' || + !hideParallelSection || + isLastGlobalRowOfParallelPhase) && !hideDropBandBeforeOrphanFirstVisible const bandActiveBefore = (bx) => diff --git a/frontend/src/pages/TrainingFrameworkProgramEditPage.jsx b/frontend/src/pages/TrainingFrameworkProgramEditPage.jsx index f0a2a00..588a6cb 100644 --- a/frontend/src/pages/TrainingFrameworkProgramEditPage.jsx +++ b/frontend/src/pages/TrainingFrameworkProgramEditPage.jsx @@ -14,6 +14,7 @@ import { enrichSectionsWithVariants, buildSectionsPayload, hydrateExercisePlanningRow, + reorderBlockIntoParallelStreamEnd, } from '../utils/trainingUnitSectionsForm' const DND_FW_SLOT = 'application/x-shinkan-framework-slot' @@ -553,7 +554,7 @@ export default function TrainingFrameworkProgramEditPage() { } const moveSectionsAcrossFrameworkSlots = useCallback( - ({ fromSlot, fromSectionIdx, toSlot, toSectionIdx }) => { + ({ fromSlot, fromSectionIdx, toSlot, toSectionIdx, toParallelStream }) => { setForm((prev) => { const slots = prev.slots.map((sl) => ({ ...sl, @@ -581,17 +582,32 @@ export default function TrainingFrameworkProgramEditPage() { const [block] = fromSecs.splice(fromSectionIdx, 1) + const applyParallelStreamEnd = + toParallelStream != null && toParallelStream.po != null && toParallelStream.so != null + ? (secs, insertedAt) => { + const po = Number(toParallelStream.po) || 0 + const so = Number(toParallelStream.so) || 0 + return reorderBlockIntoParallelStreamEnd(secs, insertedAt, po, so) + } + : null + if (fromSlot === toSlot) { let insertAt = toSectionIdx if (fromSectionIdx < toSectionIdx) insertAt = toSectionIdx - 1 insertAt = Math.max(0, Math.min(insertAt, fromSecs.length)) fromSecs.splice(insertAt, 0, block) + if (applyParallelStreamEnd) { + slots[fromSlot].sections = applyParallelStreamEnd(fromSecs, insertAt) + } return { ...prev, slots } } const toSecs = slots[toSlot].sections const ia = Math.max(0, Math.min(toSectionIdx, toSecs.length)) toSecs.splice(ia, 0, block) + if (applyParallelStreamEnd) { + slots[toSlot].sections = applyParallelStreamEnd(toSecs, ia) + } return { ...prev, slots } }) }, diff --git a/frontend/src/utils/trainingUnitSectionsForm.js b/frontend/src/utils/trainingUnitSectionsForm.js index 4a8ed02..9d1ad80 100644 --- a/frontend/src/utils/trainingUnitSectionsForm.js +++ b/frontend/src/utils/trainingUnitSectionsForm.js @@ -816,19 +816,26 @@ export function reorderBlocksImmutableWithPlanLoc(prev, fromI, toBeforeIdx) { let planLocNext = null const belowKind = below?.planLoc?.phaseKind const aboveKind = above?.planLoc?.phaseKind + const movedKind = moved?.planLoc?.phaseKind + const belowPo = below?.planLoc?.phaseOrderIndex ?? 0 + const movedPo = moved?.planLoc?.phaseOrderIndex ?? 0 - if ( - belowKind === 'parallel' && - (!above || aboveKind === 'whole_group') - ) { - if (aboveKind === 'whole_group') { + if (belowKind === 'parallel' && aboveKind === 'whole_group') { + if (movedKind !== 'parallel') { planLocNext = { ...above.planLoc } - } else { + } else if (movedPo !== belowPo) { + planLocNext = { ...below.planLoc } + } + } else if (belowKind === 'parallel' && !above) { + if (movedKind !== 'parallel') { planLocNext = defaultPlanLocWholeGroup(0) } - } else if (belowKind) { + } + + if (!planLocNext && belowKind) { planLocNext = { ...below.planLoc } - } else if (insertAt === arr.length) { + } + if (!planLocNext && insertAt === arr.length) { if (!above) { planLocNext = defaultPlanLocWholeGroup(0) } else if (above.planLoc?.phaseKind === 'parallel') { @@ -837,9 +844,11 @@ export function reorderBlocksImmutableWithPlanLoc(prev, fromI, toBeforeIdx) { } else if (above.planLoc?.phaseKind === 'whole_group') { planLocNext = { ...above.planLoc } } - } else if (above?.planLoc?.phaseKind === 'whole_group') { + } + if (!planLocNext && above?.planLoc?.phaseKind === 'whole_group') { planLocNext = { ...above.planLoc } - } else if (above?.planLoc?.phaseKind === 'parallel') { + } + if (!planLocNext && above?.planLoc?.phaseKind === 'parallel') { const mx = maxPhaseOrderIndexFromSections(arr) planLocNext = defaultPlanLocWholeGroup(mx + 1) }