diff --git a/backend/routers/training_planning.py b/backend/routers/training_planning.py
index 69cf66d..acd9449 100644
--- a/backend/routers/training_planning.py
+++ b/backend/routers/training_planning.py
@@ -1261,9 +1261,9 @@ def _replace_unit_phases(
status_code=400,
detail="phase_kind muss whole_group oder parallel sein",
)
- p_oix = ph.get("order_index")
- if p_oix is None:
- p_oix = pi
+ # Reihenfolge strikt aus der Liste (pi): vermeidet UNIQUE(tu, order_index)-Kollisionen,
+ # wenn der Client dieselbe phase_order_index mehrfach trägt (z. B. nach Zuordnungswechseln).
+ p_oix = int(pi)
cur.execute(
"""
INSERT INTO training_unit_phases (training_unit_id, order_index, phase_kind, title, guidance_notes)
@@ -1272,7 +1272,7 @@ def _replace_unit_phases(
""",
(
unit_id,
- int(p_oix),
+ p_oix,
kind,
ph.get("title"),
ph.get("guidance_notes"),
diff --git a/frontend/src/components/TrainingUnitSectionsEditor.jsx b/frontend/src/components/TrainingUnitSectionsEditor.jsx
index 8785f7f..6f45923 100644
--- a/frontend/src/components/TrainingUnitSectionsEditor.jsx
+++ b/frontend/src/components/TrainingUnitSectionsEditor.jsx
@@ -1,4 +1,4 @@
-import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react'
+import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { GripVertical, Pencil, X } from 'lucide-react'
import CombinationMethodProfileEditor from './CombinationMethodProfileEditor'
import CombinationPlanBracket from './CombinationPlanBracket'
@@ -532,6 +532,13 @@ export default function TrainingUnitSectionsEditor({
const [dropSectionBand, setDropSectionBand] = useState(null)
/** Aktiver Reiter pro paralleler Phase (phaseOrder → streamOrder). */
const [parallelStreamTabByPhase, setParallelStreamTabByPhase] = useState({})
+ /** `${phaseOrder}:${streamOrder}` während Stream-Name bearbeitet wird */
+ const [streamNameEditKey, setStreamNameEditKey] = useState(null)
+ const [streamNameDraft, setStreamNameDraft] = useState('')
+ const [phaseTitleEditPo, setPhaseTitleEditPo] = useState(null)
+ const [phaseTitleDraft, setPhaseTitleDraft] = useState('')
+ const skipStreamNameBlurSave = useRef(false)
+ const skipPhaseTitleBlurSave = useRef(false)
/** { slot: number, beforeIdx: number } */
useEffect(() => {
@@ -1054,19 +1061,69 @@ export default function TrainingUnitSectionsEditor({
>
Phase
- {
- const hi = firstSectionIndexByParallelPhase.get(parallelPhaseOrder)
- const t = hi != null ? list[hi]?.planLoc?.phaseTitle : null
- return t != null ? String(t) : ''
- })()
- }
- onChange={(e) => updateParallelPhaseTitleAll(parallelPhaseOrder, e.target.value)}
- placeholder={`Bezeichnung (z. B. Drill runden · Phase ${parallelPhaseOrder})`}
- />
+ {(() => {
+ const hi = firstSectionIndexByParallelPhase.get(parallelPhaseOrder)
+ const phaseTitleStr =
+ hi != null && list[hi]?.planLoc?.phaseTitle != null
+ ? String(list[hi].planLoc.phaseTitle)
+ : ''
+ const editingPhase = phaseTitleEditPo === parallelPhaseOrder
+ return editingPhase ? (
+ setPhaseTitleDraft(e.target.value)}
+ onBlur={() => {
+ if (!skipPhaseTitleBlurSave.current) {
+ updateParallelPhaseTitleAll(parallelPhaseOrder, phaseTitleDraft)
+ }
+ skipPhaseTitleBlurSave.current = false
+ setPhaseTitleEditPo(null)
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') e.currentTarget.blur()
+ if (e.key === 'Escape') {
+ skipPhaseTitleBlurSave.current = true
+ setPhaseTitleEditPo(null)
+ e.currentTarget.blur()
+ }
+ }}
+ placeholder="Bezeichnung der Phase (z. B. Drill-Runde)"
+ />
+ ) : (
+ <>
+
+ {(phaseTitleStr || '').trim() ||
+ `Phase ${parallelPhaseOrder} · Namen per Stift bearbeiten`}
+
+
+ >
+ )
+ })()}