) : null}
-
+
{enableItemDragReorder ? (
-
Zwischen-Anmerkung
-
- {noteHasText ? notePv : '—'}
-
+
+ {isSepLine ? 'Trennung' : 'Zwischen-Anmerkung'}
+
+ {isSepLine ? (
+
+ ) : (
+
+ {noteHasText ? notePv : '—'}
+
+ )}
setTextEdit({
kind: 'zwischen-note',
@@ -610,12 +690,13 @@ export default function TrainingUnitSectionsEditor({
type="button"
className="tu-item-row__remove"
title="Entfernen"
- aria-label="Zwischen-Anmerkung entfernen"
+ aria-label={isSepLine ? 'Trennung entfernen' : 'Zwischen-Anmerkung entfernen'}
onClick={() => removeItem(sIdx, iIdx)}
>
✗
+ {betweenInsertMenus ? renderBetweenInsertBand(sIdx, iIdx + 1, itemCount) : null}
)
}
@@ -632,7 +713,7 @@ export default function TrainingUnitSectionsEditor({
: Number(it.exercise_variant_id)
return (
-
+
{showModuleBand ? (
) : null}
+ {betweenInsertMenus ? renderBetweenInsertBand(sIdx, iIdx + 1, itemCount) : null}
)
})}
@@ -837,21 +919,30 @@ export default function TrainingUnitSectionsEditor({
/>
) : null}
-
-
onRequestExercisePick?.({ sectionIndex: sIdx })}
- >
- + Übung
-
-
addItem(sIdx, 'note')}
- >
- + Anmerkung
-
+
+ {betweenInsertMenus ? (
+
+ Über die +-Zeilen zwischen den Einträgen fügst du an der gewünschten Stelle Inhalte ein. Reihenfolge
+ weiter per Ziehen oder den Pfeiltasten ändern.
+
+ ) : (
+
+ onRequestExercisePick?.({ sectionIndex: sIdx })}
+ >
+ + Übung
+
+ addItem(sIdx, 'note')}
+ >
+ + Anmerkung
+
+
+ )}
@@ -889,6 +980,91 @@ export default function TrainingUnitSectionsEditor({
+ Abschnitt hinzufügen
+ {insertChooser ? (
+
{
+ if (e.target === e.currentTarget) closeInsertChooser()
+ }}
+ >
+
e.stopPropagation()}
+ >
+
+ An dieser Stelle einfügen
+
+
+ Die neue Zeile erscheint genau hier; Reihenfolge kannst du wie gewohnt per Ziehen oder Pfeilen
+ ändern.
+
+
+ {
+ const { sIdx, beforeIx } = insertChooser
+ closeInsertChooser()
+ onRequestExercisePick?.({
+ sectionIndex: sIdx,
+ insertBeforeIndex: beforeIx,
+ })
+ }}
+ >
+ Übung auswählen …
+
+ {onRequestTrainingModulePick ? (
+ {
+ const ctx = { ...insertChooser }
+ closeInsertChooser()
+ onRequestTrainingModulePick({
+ sectionIndex: ctx.sIdx,
+ insertBeforeIndex: ctx.beforeIx,
+ })
+ }}
+ >
+ Trainingsmodul …
+
+ ) : null}
+ {
+ const { sIdx, beforeIx } = insertChooser
+ insertItemAt(sIdx, beforeIx, noteRow())
+ closeInsertChooser()
+ }}
+ >
+ Zwischen-Anmerkung
+
+ {
+ const { sIdx, beforeIx } = insertChooser
+ const r = noteRow()
+ r.note_body = SECTION_INSERT_SEPARATOR_BODY
+ insertItemAt(sIdx, beforeIx, r)
+ closeInsertChooser()
+ }}
+ >
+ Trennlinie
+
+
+ Abbrechen
+
+
+
+
+ ) : null}
+
{textEdit ? (
+ onRequestExercisePick={({ sectionIndex, itemIndex, insertBeforeIndex }) =>
setSectionPickerCtx({
slotIdx: si,
sectionIndex,
itemIndex: typeof itemIndex === 'number' ? itemIndex : undefined,
+ insertBeforeIndex:
+ typeof insertBeforeIndex === 'number' && Number.isFinite(insertBeforeIndex)
+ ? insertBeforeIndex
+ : undefined,
})
}
onPeekExercise={(id, variantId) =>
@@ -1096,7 +1101,7 @@ export default function TrainingFrameworkProgramEditPage() {
if (row) rows.push(row)
}
if (!rows.length) return
- const { slotIdx, sectionIndex: sIdx, itemIndex: iIdx } = sectionPickerCtx
+ const { slotIdx, sectionIndex: sIdx, itemIndex: iIdx, insertBeforeIndex } = sectionPickerCtx
setForm((prev) => ({
...prev,
slots: prev.slots.map((sl, ii) => {
@@ -1121,7 +1126,13 @@ export default function TrainingFrameworkProgramEditPage() {
if (tail.length) items.splice(iIdx + 1, 0, ...tail)
return { ...sec, items }
}
- return { ...sec, items: [...items, ...rows] }
+ const rawAt =
+ typeof insertBeforeIndex === 'number' && Number.isFinite(insertBeforeIndex)
+ ? insertBeforeIndex
+ : items.length
+ const at = Math.max(0, Math.min(rawAt, items.length))
+ items.splice(at, 0, ...rows)
+ return { ...sec, items }
}),
}
}),
diff --git a/frontend/src/pages/TrainingPlanningPage.jsx b/frontend/src/pages/TrainingPlanningPage.jsx
index a4e97a5..5263c2b 100644
--- a/frontend/src/pages/TrainingPlanningPage.jsx
+++ b/frontend/src/pages/TrainingPlanningPage.jsx
@@ -150,7 +150,7 @@ function TrainingPlanningPage() {
const [moduleApplyList, setModuleApplyList] = useState([])
const [moduleApplyModuleId, setModuleApplyModuleId] = useState('')
const [moduleApplySectionIx, setModuleApplySectionIx] = useState(0)
- const [moduleApplyInsertSlot, setModuleApplyInsertSlot] = useState('__end__')
+ const [moduleApplyInsertSlot, setModuleApplyInsertSlot] = useState('before:0')
const [moduleApplyErr, setModuleApplyErr] = useState('')
const [startDate, setStartDate] = useState(today)
@@ -684,10 +684,27 @@ function TrainingPlanningPage() {
}
}
- const openModuleApplyModal = useCallback(async () => {
+ const openModuleApplyModal = useCallback(async (placement) => {
setModuleApplyErr('')
- setModuleApplySectionIx(0)
- setModuleApplyInsertSlot('__end__')
+ const secs = planningFormRef.current?.sections ?? []
+ let secIx = 0
+ let before = 0
+ if (secs.length) {
+ if (placement && typeof placement.sectionIndex === 'number') {
+ secIx = Math.min(Math.max(0, placement.sectionIndex), secs.length - 1)
+ const items = Array.isArray(secs[secIx]?.items) ? secs[secIx].items : []
+ const cap = items.length
+ if (typeof placement.insertBeforeIndex === 'number' && Number.isFinite(placement.insertBeforeIndex)) {
+ before = Math.min(Math.max(0, placement.insertBeforeIndex), cap)
+ } else before = cap
+ } else {
+ const items = Array.isArray(secs[0]?.items) ? secs[0].items : []
+ before = items.length
+ secIx = 0
+ }
+ }
+ setModuleApplySectionIx(secIx)
+ setModuleApplyInsertSlot(`before:${before}`)
setModuleApplyOpen(true)
try {
const list = await api.listTrainingModules()
@@ -716,13 +733,13 @@ function TrainingPlanningPage() {
}
if (secIx < 0 || secIx >= baseSections.length) secIx = 0
- let insertBefore = null
- if (moduleApplyInsertSlot === '__end__') insertBefore = 'end'
- else if (moduleApplyInsertSlot === '__start__') insertBefore = 'start'
- else if (typeof moduleApplyInsertSlot === 'string' && moduleApplyInsertSlot.startsWith('before:')) {
+ const secItems = Array.isArray(baseSections[secIx]?.items) ? baseSections[secIx].items : []
+ const itemCap = secItems.length
+ let insertBefore = itemCap
+ if (typeof moduleApplyInsertSlot === 'string' && moduleApplyInsertSlot.startsWith('before:')) {
const zi = parseInt(moduleApplyInsertSlot.slice('before:'.length), 10)
- insertBefore = Number.isFinite(zi) ? zi : 'end'
- } else insertBefore = 'end'
+ if (Number.isFinite(zi)) insertBefore = Math.min(Math.max(0, zi), itemCap)
+ }
setModuleApplyBusy(true)
setModuleApplyErr('')
@@ -742,7 +759,7 @@ function TrainingPlanningPage() {
} finally {
setModuleApplyBusy(false)
}
- }, [moduleApplyModuleId, moduleApplySectionIx, moduleApplyInsertSlot, formData.sections])
+ }, [moduleApplyModuleId, moduleApplySectionIx, moduleApplyInsertSlot])
const handleTakeLead = async (unit) => {
if (!user?.id) return
@@ -1955,8 +1972,11 @@ function TrainingPlanningPage() {
className="form-input"
value={String(moduleApplySectionIx)}
onChange={(e) => {
- setModuleApplySectionIx(parseInt(e.target.value, 10))
- setModuleApplyInsertSlot('__end__')
+ const newIx = parseInt(e.target.value, 10)
+ setModuleApplySectionIx(newIx)
+ const secsNow = planningFormRef.current?.sections ?? []
+ const len = Array.isArray(secsNow[newIx]?.items) ? secsNow[newIx].items.length : 0
+ setModuleApplyInsertSlot(`before:${len}`)
}}
disabled={moduleApplyBusy || !formData.sections?.length}
>
@@ -1976,8 +1996,10 @@ function TrainingPlanningPage() {
onChange={(e) => setModuleApplyInsertSlot(e.target.value)}
disabled={moduleApplyBusy || !(formData.sections?.length > 0)}
>
- Ans Ende einfügen (nach allen Einträgen)
- An den Anfang (vor dem ersten Eintrag)
+
+ Ans Ende einfügen (nach allen Einträgen)
+
+ An den Anfang (vor dem ersten Eintrag)
{moduleApplyTargetItems.map((row, xi) => {
const labelPart =
row.item_type === 'note'
@@ -2526,14 +2548,6 @@ function TrainingPlanningPage() {
Vorlage aus Aufbau speichern
-
- Modul einfügen…
-
>
}
sections={formData.sections}
@@ -2544,10 +2558,17 @@ function TrainingPlanningPage() {
sections: updater(prev.sections),
}))
}
- onRequestExercisePick={({ sectionIndex, itemIndex }) => {
+ onRequestTrainingModulePick={(ctx) => {
+ void openModuleApplyModal(ctx)
+ }}
+ onRequestExercisePick={({ sectionIndex, itemIndex, insertBeforeIndex }) => {
setExercisePickerTarget({
sIdx: sectionIndex,
iIdx: typeof itemIndex === 'number' ? itemIndex : undefined,
+ insertBeforeIndex:
+ typeof insertBeforeIndex === 'number' && Number.isFinite(insertBeforeIndex)
+ ? insertBeforeIndex
+ : undefined,
})
setExercisePickerOpen(true)
}}
@@ -2700,7 +2721,7 @@ function TrainingPlanningPage() {
if (row) rows.push(row)
}
if (!rows.length) return
- const { sIdx, iIdx } = exercisePickerTarget
+ const { sIdx, iIdx, insertBeforeIndex } = exercisePickerTarget
setFormData((prev) => ({
...prev,
sections: prev.sections.map((s, si) => {
@@ -2720,7 +2741,13 @@ function TrainingPlanningPage() {
if (tail.length) items.splice(iIdx + 1, 0, ...tail)
return { ...s, items }
}
- return { ...s, items: [...items, ...rows] }
+ const rawAt =
+ typeof insertBeforeIndex === 'number' && Number.isFinite(insertBeforeIndex)
+ ? insertBeforeIndex
+ : items.length
+ const at = Math.max(0, Math.min(rawAt, items.length))
+ items.splice(at, 0, ...rows)
+ return { ...s, items }
}),
}))
setExercisePickerOpen(false)