Enhance TrainingUnitSectionsEditor and TrainingFrameworkProgramEditPage with parallel stream support
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s

- Updated the TrainingUnitSectionsEditor to include additional parameters for moving sections across slots, allowing for better handling of parallel streams.
- Improved the removeSection function to ensure proper reordering and handling of sections in parallel phases.
- Enhanced the moveSectionsAcrossFrameworkSlots function to apply changes to sections when moved into parallel streams, ensuring accurate updates.
- Refactored the reorderBlocksImmutableWithPlanLoc function to accommodate new logic for managing phase orders and section types during reordering.
This commit is contained in:
Lars 2026-05-15 10:21:15 +02:00
parent 4902771772
commit 73975d3402
3 changed files with 53 additions and 15 deletions

View File

@ -243,7 +243,7 @@ function reorderBlocksImmutable(blocks, fromI, toBeforeIdx) {
/** /**
* @param {(updater: (prev: Array) => Array) => void} props.onSectionsChange wie React setState * @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({ export default function TrainingUnitSectionsEditor({
sections, sections,
@ -463,8 +463,12 @@ export default function TrainingUnitSectionsEditor({
const removeSection = (sIdx) => { const removeSection = (sIdx) => {
patch((prev) => { patch((prev) => {
const next = prev.filter((_, i) => i !== sIdx) let next = prev.filter((_, i) => i !== sIdx)
return next.length ? next : [defaultSection()] next = next.length ? next : [defaultSection()]
if (enableParallelPhaseControls) {
next = afterSectionReorderParallelGuard(prev, next)
}
return next
}) })
} }
@ -843,6 +847,7 @@ export default function TrainingUnitSectionsEditor({
fromSectionIdx: fromSi, fromSectionIdx: fromSi,
toSlot: sectionToSlot, toSlot: sectionToSlot,
toSectionIdx: toIdx, toSectionIdx: toIdx,
toParallelStream: { po, so },
}) })
return return
} }
@ -1177,6 +1182,12 @@ export default function TrainingUnitSectionsEditor({
parallelPhaseOrder != null parallelPhaseOrder != null
? firstSectionIndexByParallelPhase.get(parallelPhaseOrder) ? firstSectionIndexByParallelPhase.get(parallelPhaseOrder)
: null : null
const phaseIndicesAll =
parallelPhaseOrder != null ? indicesOfParallelPhase(list, parallelPhaseOrder) : []
const isLastGlobalRowOfParallelPhase =
pl?.phaseKind === 'parallel' &&
phaseIndicesAll.length > 0 &&
sIdx === phaseIndicesAll[phaseIndicesAll.length - 1]
const firstVisibleIdxActiveStream = const firstVisibleIdxActiveStream =
parallelPhaseOrder != null && streamOrdersForParallelPhase.length parallelPhaseOrder != null && streamOrdersForParallelPhase.length
? sectionIndicesForParallelStream( ? sectionIndicesForParallelStream(
@ -1193,7 +1204,9 @@ export default function TrainingUnitSectionsEditor({
sIdx !== firstGlobalIdxThisPhase sIdx !== firstGlobalIdxThisPhase
const showSectionDropBandBefore = const showSectionDropBandBefore =
(pl?.phaseKind !== 'parallel' || !hideParallelSection) && (pl?.phaseKind !== 'parallel' ||
!hideParallelSection ||
isLastGlobalRowOfParallelPhase) &&
!hideDropBandBeforeOrphanFirstVisible !hideDropBandBeforeOrphanFirstVisible
const bandActiveBefore = (bx) => const bandActiveBefore = (bx) =>

View File

@ -14,6 +14,7 @@ import {
enrichSectionsWithVariants, enrichSectionsWithVariants,
buildSectionsPayload, buildSectionsPayload,
hydrateExercisePlanningRow, hydrateExercisePlanningRow,
reorderBlockIntoParallelStreamEnd,
} from '../utils/trainingUnitSectionsForm' } from '../utils/trainingUnitSectionsForm'
const DND_FW_SLOT = 'application/x-shinkan-framework-slot' const DND_FW_SLOT = 'application/x-shinkan-framework-slot'
@ -553,7 +554,7 @@ export default function TrainingFrameworkProgramEditPage() {
} }
const moveSectionsAcrossFrameworkSlots = useCallback( const moveSectionsAcrossFrameworkSlots = useCallback(
({ fromSlot, fromSectionIdx, toSlot, toSectionIdx }) => { ({ fromSlot, fromSectionIdx, toSlot, toSectionIdx, toParallelStream }) => {
setForm((prev) => { setForm((prev) => {
const slots = prev.slots.map((sl) => ({ const slots = prev.slots.map((sl) => ({
...sl, ...sl,
@ -581,17 +582,32 @@ export default function TrainingFrameworkProgramEditPage() {
const [block] = fromSecs.splice(fromSectionIdx, 1) 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) { if (fromSlot === toSlot) {
let insertAt = toSectionIdx let insertAt = toSectionIdx
if (fromSectionIdx < toSectionIdx) insertAt = toSectionIdx - 1 if (fromSectionIdx < toSectionIdx) insertAt = toSectionIdx - 1
insertAt = Math.max(0, Math.min(insertAt, fromSecs.length)) insertAt = Math.max(0, Math.min(insertAt, fromSecs.length))
fromSecs.splice(insertAt, 0, block) fromSecs.splice(insertAt, 0, block)
if (applyParallelStreamEnd) {
slots[fromSlot].sections = applyParallelStreamEnd(fromSecs, insertAt)
}
return { ...prev, slots } return { ...prev, slots }
} }
const toSecs = slots[toSlot].sections const toSecs = slots[toSlot].sections
const ia = Math.max(0, Math.min(toSectionIdx, toSecs.length)) const ia = Math.max(0, Math.min(toSectionIdx, toSecs.length))
toSecs.splice(ia, 0, block) toSecs.splice(ia, 0, block)
if (applyParallelStreamEnd) {
slots[toSlot].sections = applyParallelStreamEnd(toSecs, ia)
}
return { ...prev, slots } return { ...prev, slots }
}) })
}, },

View File

@ -816,19 +816,26 @@ export function reorderBlocksImmutableWithPlanLoc(prev, fromI, toBeforeIdx) {
let planLocNext = null let planLocNext = null
const belowKind = below?.planLoc?.phaseKind const belowKind = below?.planLoc?.phaseKind
const aboveKind = above?.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 ( if (belowKind === 'parallel' && aboveKind === 'whole_group') {
belowKind === 'parallel' && if (movedKind !== 'parallel') {
(!above || aboveKind === 'whole_group')
) {
if (aboveKind === 'whole_group') {
planLocNext = { ...above.planLoc } planLocNext = { ...above.planLoc }
} else { } else if (movedPo !== belowPo) {
planLocNext = { ...below.planLoc }
}
} else if (belowKind === 'parallel' && !above) {
if (movedKind !== 'parallel') {
planLocNext = defaultPlanLocWholeGroup(0) planLocNext = defaultPlanLocWholeGroup(0)
} }
} else if (belowKind) { }
if (!planLocNext && belowKind) {
planLocNext = { ...below.planLoc } planLocNext = { ...below.planLoc }
} else if (insertAt === arr.length) { }
if (!planLocNext && insertAt === arr.length) {
if (!above) { if (!above) {
planLocNext = defaultPlanLocWholeGroup(0) planLocNext = defaultPlanLocWholeGroup(0)
} else if (above.planLoc?.phaseKind === 'parallel') { } else if (above.planLoc?.phaseKind === 'parallel') {
@ -837,9 +844,11 @@ export function reorderBlocksImmutableWithPlanLoc(prev, fromI, toBeforeIdx) {
} else if (above.planLoc?.phaseKind === 'whole_group') { } else if (above.planLoc?.phaseKind === 'whole_group') {
planLocNext = { ...above.planLoc } planLocNext = { ...above.planLoc }
} }
} else if (above?.planLoc?.phaseKind === 'whole_group') { }
if (!planLocNext && above?.planLoc?.phaseKind === 'whole_group') {
planLocNext = { ...above.planLoc } planLocNext = { ...above.planLoc }
} else if (above?.planLoc?.phaseKind === 'parallel') { }
if (!planLocNext && above?.planLoc?.phaseKind === 'parallel') {
const mx = maxPhaseOrderIndexFromSections(arr) const mx = maxPhaseOrderIndexFromSections(arr)
planLocNext = defaultPlanLocWholeGroup(mx + 1) planLocNext = defaultPlanLocWholeGroup(mx + 1)
} }