Enhance section movement functionality in TrainingUnitSectionsEditor
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
Test Suite / pytest-backend (pull_request) Successful in 34s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m8s

- 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.
This commit is contained in:
Lars 2026-05-16 08:34:30 +02:00
parent 7d2661a8e8
commit 8c07cf36ee
2 changed files with 156 additions and 16 deletions

View File

@ -245,7 +245,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, 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({ export default function TrainingUnitSectionsEditor({
sections, sections,
@ -807,6 +807,22 @@ export default function TrainingUnitSectionsEditor({
if (parsed.kind === 'phaseRun') { if (parsed.kind === 'phaseRun') {
const dragPo = Number(parsed.phaseRunMove.phaseOrderIndex) || 0 const dragPo = Number(parsed.phaseRunMove.phaseOrderIndex) || 0
if (dragPo === targetPo) return 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) => { patch((prev) => {
const idxs = indicesOfParallelPhase(prev, targetPo) const idxs = indicesOfParallelPhase(prev, targetPo)
const fg = idxs.length ? idxs[0] : -1 const fg = idxs.length ? idxs[0] : -1
@ -818,7 +834,17 @@ export default function TrainingUnitSectionsEditor({
return 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 const { fromSi } = parsed
patch((prev) => { patch((prev) => {
@ -854,6 +880,23 @@ export default function TrainingUnitSectionsEditor({
if (parsed.kind === 'phaseRun') { if (parsed.kind === 'phaseRun') {
const dragPo = Number(parsed.phaseRunMove.phaseOrderIndex) || 0 const dragPo = Number(parsed.phaseRunMove.phaseOrderIndex) || 0
if (dragPo === targetPo) return 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) => { patch((prev) => {
const idxs = indicesOfParallelPhase(prev, targetPo) const idxs = indicesOfParallelPhase(prev, targetPo)
const fg = idxs.length ? idxs[0] : -1 const fg = idxs.length ? idxs[0] : -1
@ -865,7 +908,17 @@ export default function TrainingUnitSectionsEditor({
return 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 const { fromSi } = parsed
patch((prev) => { patch((prev) => {
@ -898,9 +951,24 @@ export default function TrainingUnitSectionsEditor({
const fromSlot = typeof data.fromSlot === 'number' ? data.fromSlot : -1 const fromSlot = typeof data.fromSlot === 'number' ? data.fromSlot : -1
if (phaseRunMove != null && phaseRunMove.phaseOrderIndex != null) { 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) => { patch((prev) => {
const po = Number(phaseRunMove.phaseOrderIndex) || 0 const poLocal = Number(phaseRunMove.phaseOrderIndex) || 0
let next = moveParallelPhaseRunToInsertBefore(prev, po, insertBeforeIdx) let next = moveParallelPhaseRunToInsertBefore(prev, poLocal, insertBeforeIdx)
if (enableParallelPhaseControls) next = afterSectionReorderParallelGuard(prev, next) if (enableParallelPhaseControls) next = afterSectionReorderParallelGuard(prev, next)
return next return next
}) })

View File

@ -15,6 +15,9 @@ import {
buildPlanPayloadForSave, buildPlanPayloadForSave,
hydrateExercisePlanningRow, hydrateExercisePlanningRow,
reorderBlockIntoParallelStreamEnd, reorderBlockIntoParallelStreamEnd,
indicesOfParallelPhase,
reorderSectionBeforeParallelRunAsWholeGroup,
reorderSectionAsFirstInParallelStream,
} from '../utils/trainingUnitSectionsForm' } from '../utils/trainingUnitSectionsForm'
const DND_FW_SLOT = 'application/x-shinkan-framework-slot' const DND_FW_SLOT = 'application/x-shinkan-framework-slot'
@ -561,7 +564,17 @@ export default function TrainingFrameworkProgramEditPage() {
} }
const moveSectionsAcrossFrameworkSlots = useCallback( const moveSectionsAcrossFrameworkSlots = useCallback(
({ fromSlot, fromSectionIdx, toSlot, toSectionIdx, toParallelStream }) => { (payload) => {
const {
fromSlot,
fromSectionIdx,
toSlot,
toSectionIdx,
toParallelStream,
parallelPhaseRunOrderIndex,
insertBeforeParallelInTarget,
firstInParallelStreamInTarget,
} = payload
setForm((prev) => { setForm((prev) => {
const slots = prev.slots.map((sl) => ({ const slots = prev.slots.map((sl) => ({
...sl, ...sl,
@ -579,15 +592,7 @@ export default function TrainingFrameworkProgramEditPage() {
} }
const fromSecs = slots[fromSlot].sections const fromSecs = slots[fromSlot].sections
if ( const toSecs = slots[toSlot].sections
typeof fromSectionIdx !== 'number' ||
fromSectionIdx < 0 ||
fromSectionIdx >= fromSecs.length
) {
return prev
}
const [block] = fromSecs.splice(fromSectionIdx, 1)
const applyParallelStreamEnd = const applyParallelStreamEnd =
toParallelStream != null && toParallelStream.po != null && toParallelStream.so != null toParallelStream != null && toParallelStream.po != null && toParallelStream.so != null
@ -598,6 +603,43 @@ export default function TrainingFrameworkProgramEditPage() {
} }
: null : 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) { if (fromSlot === toSlot) {
let insertAt = toSectionIdx let insertAt = toSectionIdx
if (fromSectionIdx < toSectionIdx) insertAt = toSectionIdx - 1 if (fromSectionIdx < toSectionIdx) insertAt = toSectionIdx - 1
@ -605,15 +647,45 @@ export default function TrainingFrameworkProgramEditPage() {
fromSecs.splice(insertAt, 0, block) fromSecs.splice(insertAt, 0, block)
if (applyParallelStreamEnd) { if (applyParallelStreamEnd) {
slots[fromSlot].sections = applyParallelStreamEnd(fromSecs, insertAt) slots[fromSlot].sections = applyParallelStreamEnd(fromSecs, insertAt)
} else {
slots[fromSlot].sections = fromSecs
} }
return { ...prev, slots } 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)) const ia = Math.max(0, Math.min(toSectionIdx, toSecs.length))
toSecs.splice(ia, 0, block) toSecs.splice(ia, 0, block)
if (applyParallelStreamEnd) { if (applyParallelStreamEnd) {
slots[toSlot].sections = applyParallelStreamEnd(toSecs, ia) slots[toSlot].sections = applyParallelStreamEnd(toSecs, ia)
} else {
slots[toSlot].sections = toSecs
} }
return { ...prev, slots } return { ...prev, slots }
}) })