refactor: streamline exercise selection process in training pages
Some checks failed
Deploy Development / deploy (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 43s

- Simplified the exercise selection logic in TrainingUnitSectionsEditor, TrainingFrameworkProgramEditPage, and TrainingPlanningPage by removing the multi-select option.
- Updated the ExercisePickerModal to always enable multi-select, enhancing user experience when selecting exercises.
- Refactored the handling of selected exercises to improve clarity and maintainability of the code.
This commit is contained in:
Lars 2026-05-05 14:52:19 +02:00
parent 4080088d42
commit 86192df508
3 changed files with 65 additions and 126 deletions

View File

@ -809,19 +809,10 @@ export default function TrainingUnitSectionsEditor({
<button <button
type="button" type="button"
className="btn btn-secondary framework-ctrl framework-ctrl--xs" className="btn btn-secondary framework-ctrl framework-ctrl--xs"
onClick={() => addItem(sIdx, 'exercise')} onClick={() => onRequestExercisePick?.({ sectionIndex: sIdx })}
> >
+ Übung + Übung
</button> </button>
<button
type="button"
className="btn btn-secondary framework-ctrl framework-ctrl--xs"
onClick={() =>
onRequestExercisePick?.({ sectionIndex: sIdx, multi: true })
}
>
+ mehrere Übungen
</button>
<button <button
type="button" type="button"
className="btn btn-secondary framework-ctrl framework-ctrl--xs" className="btn btn-secondary framework-ctrl framework-ctrl--xs"

View File

@ -627,12 +627,11 @@ export default function TrainingFrameworkProgramEditPage() {
), ),
})) }))
}} }}
onRequestExercisePick={({ sectionIndex, itemIndex, multi }) => onRequestExercisePick={({ sectionIndex, itemIndex }) =>
setSectionPickerCtx({ setSectionPickerCtx({
slotIdx: si, slotIdx: si,
sectionIndex, sectionIndex,
itemIndex: typeof itemIndex === 'number' ? itemIndex : undefined, itemIndex: typeof itemIndex === 'number' ? itemIndex : undefined,
multi: !!multi,
}) })
} }
onPeekExercise={(id, variantId) => onPeekExercise={(id, variantId) =>
@ -1108,75 +1107,45 @@ export default function TrainingFrameworkProgramEditPage() {
<ExercisePickerModal <ExercisePickerModal
open={sectionPickerCtx != null} open={sectionPickerCtx != null}
multiSelect={!!sectionPickerCtx?.multi} multiSelect
onClose={() => setSectionPickerCtx(null)} onClose={() => setSectionPickerCtx(null)}
onSelectExercises={ onSelectExercises={async (picked) => {
sectionPickerCtx?.multi if (!sectionPickerCtx || !picked?.length) return
? async (picked) => { const rows = []
if (!sectionPickerCtx || !picked?.length) return for (const ex of picked) {
const { slotIdx, sectionIndex: sIdx } = sectionPickerCtx const row = await hydrateExercisePlanningRow(ex)
const rows = [] if (row) rows.push(row)
for (const ex of picked) { }
const row = await hydrateExercisePlanningRow(ex) if (!rows.length) return
if (row) rows.push(row)
}
if (!rows.length) return
setForm((prev) => ({
...prev,
slots: prev.slots.map((sl, ii) =>
ii !== slotIdx
? sl
: {
...sl,
sections: (
sl.sections && sl.sections.length ? sl.sections : [defaultSection('Ablauf')]
).map((sec, si) =>
si !== sIdx ? sec : { ...sec, items: [...(sec.items || []), ...rows] }
),
}
),
}))
setSectionPickerCtx(null)
}
: undefined
}
onSelectExercise={async (exercise) => {
if (!sectionPickerCtx) return
if (sectionPickerCtx.multi) return
if (typeof sectionPickerCtx.itemIndex !== 'number') return
const { slotIdx, sectionIndex: sIdx, itemIndex: iIdx } = sectionPickerCtx const { slotIdx, sectionIndex: sIdx, itemIndex: iIdx } = sectionPickerCtx
const row = await hydrateExercisePlanningRow(exercise)
if (!row) return
setForm((prev) => ({ setForm((prev) => ({
...prev, ...prev,
slots: prev.slots.map((sl, ii) => slots: prev.slots.map((sl, ii) => {
ii !== slotIdx if (ii !== slotIdx) return sl
? sl const baseSecs = sl.sections && sl.sections.length ? sl.sections : [defaultSection('Ablauf')]
: { return {
...sl, ...sl,
sections: (sl.sections && sl.sections.length ? sl.sections : [defaultSection('Ablauf')]).map( sections: baseSecs.map((sec, si) => {
(sec, si) => if (si !== sIdx) return sec
si !== sIdx const items = [...(sec.items || [])]
? sec if (typeof iIdx === 'number') {
: { const cur = items[iIdx]
...sec, if (!cur || cur.item_type !== 'exercise') return sec
items: (sec.items || []).map((r2, ji) => const [first, ...tail] = rows
ji !== iIdx items[iIdx] = {
? r2 ...cur,
: r2.item_type !== 'exercise' exercise_id: first.exercise_id,
? r2 exercise_variant_id: first.exercise_variant_id,
: { exercise_title: first.exercise_title,
...r2, variants: first.variants,
exercise_id: row.exercise_id, }
exercise_variant_id: row.exercise_variant_id, if (tail.length) items.splice(iIdx + 1, 0, ...tail)
exercise_title: row.exercise_title, return { ...sec, items }
variants: row.variants,
}
),
}
),
} }
), return { ...sec, items: [...items, ...rows] }
}),
}
}),
})) }))
setSectionPickerCtx(null) setSectionPickerCtx(null)
}} }}

View File

@ -709,11 +709,10 @@ function TrainingPlanningPage() {
sections: updater(prev.sections), sections: updater(prev.sections),
})) }))
} }
onRequestExercisePick={({ sectionIndex, itemIndex, multi }) => { onRequestExercisePick={({ sectionIndex, itemIndex }) => {
setExercisePickerTarget({ setExercisePickerTarget({
sIdx: sectionIndex, sIdx: sectionIndex,
iIdx: typeof itemIndex === 'number' ? itemIndex : undefined, iIdx: typeof itemIndex === 'number' ? itemIndex : undefined,
multi: !!multi,
}) })
setExercisePickerOpen(true) setExercisePickerOpen(true)
}} }}
@ -824,61 +823,41 @@ function TrainingPlanningPage() {
)} )}
<ExercisePickerModal <ExercisePickerModal
open={exercisePickerOpen} open={exercisePickerOpen}
multiSelect={!!exercisePickerTarget?.multi} multiSelect
onClose={() => { onClose={() => {
setExercisePickerOpen(false) setExercisePickerOpen(false)
setExercisePickerTarget(null) setExercisePickerTarget(null)
}} }}
onSelectExercises={ onSelectExercises={async (picked) => {
exercisePickerTarget?.multi if (!exercisePickerTarget || !picked?.length) return
? async (picked) => { const rows = []
if (!exercisePickerTarget || !picked?.length) return for (const ex of picked) {
const { sIdx } = exercisePickerTarget const row = await hydrateExercisePlanningRow(ex)
const rows = [] if (row) rows.push(row)
for (const ex of picked) { }
const row = await hydrateExercisePlanningRow(ex) if (!rows.length) return
if (row) rows.push(row)
}
if (!rows.length) return
setFormData((prev) => ({
...prev,
sections: prev.sections.map((s, si) =>
si !== sIdx ? s : { ...s, items: [...(s.items || []), ...rows] }
),
}))
setExercisePickerOpen(false)
setExercisePickerTarget(null)
}
: undefined
}
onSelectExercise={async (ex) => {
if (!exercisePickerTarget || exercisePickerTarget.multi) return
const row = await hydrateExercisePlanningRow(ex)
if (!row) return
const { sIdx, iIdx } = exercisePickerTarget const { sIdx, iIdx } = exercisePickerTarget
if (typeof iIdx !== 'number') return
setFormData((prev) => ({ setFormData((prev) => ({
...prev, ...prev,
sections: prev.sections.map((s, si) => sections: prev.sections.map((s, si) => {
si !== sIdx if (si !== sIdx) return s
? s const items = [...(s.items || [])]
: { if (typeof iIdx === 'number') {
...s, const cur = items[iIdx]
items: s.items.map((r2, ii) => if (!cur || cur.item_type !== 'exercise') return s
ii !== iIdx const [first, ...tail] = rows
? r2 items[iIdx] = {
: r2.item_type !== 'exercise' ...cur,
? r2 exercise_id: first.exercise_id,
: { exercise_variant_id: first.exercise_variant_id,
...r2, exercise_title: first.exercise_title,
exercise_id: row.exercise_id, variants: first.variants,
exercise_variant_id: row.exercise_variant_id, }
exercise_title: row.exercise_title, if (tail.length) items.splice(iIdx + 1, 0, ...tail)
variants: row.variants, return { ...s, items }
} }
), return { ...s, items: [...items, ...rows] }
} }),
),
})) }))
setExercisePickerOpen(false) setExercisePickerOpen(false)
setExercisePickerTarget(null) setExercisePickerTarget(null)