diff --git a/frontend/src/app.css b/frontend/src/app.css
index fb4d05e..ed100e8 100644
--- a/frontend/src/app.css
+++ b/frontend/src/app.css
@@ -5412,6 +5412,24 @@ a.analysis-split__nav-item {
0 2px 12px rgba(15, 23, 42, 0.05);
}
+/* Kombinations‑Strip: volle Breite unter der Zeile, begrenzte Textbreite — Hauptzeile (Name/Min.) nicht verdrängen */
+.training-unit-sections-editor .tu-combo-planning-strip {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 10px;
+}
+
+.training-unit-sections-editor .tu-combo-planning-strip__meta {
+ width: 100%;
+ max-width: min(100%, 42rem);
+ min-width: 0;
+}
+
+.training-unit-sections-editor .tu-combo-planning-strip > .btn {
+ align-self: flex-start;
+}
+
.tu-planning-mod-tag {
display: inline-flex;
align-items: center;
diff --git a/frontend/src/components/TrainingUnitSectionsEditor.jsx b/frontend/src/components/TrainingUnitSectionsEditor.jsx
index 6431ee1..f6fdea9 100644
--- a/frontend/src/components/TrainingUnitSectionsEditor.jsx
+++ b/frontend/src/components/TrainingUnitSectionsEditor.jsx
@@ -65,18 +65,13 @@ function gatherPlanningModuleOutline(items, startIdx, moduleId) {
const MODULE_OUTLINE_PREVIEW_MAX = 8
-/** Statuszeile: Planungs‑Override vs. Katalog, inkl. Archetyp‑Label wenn bekannt. */
+/** Statuszeile: nur Planungs‑Override vs. Katalog (Archetyp steht bereits links). */
function compactComboPlanningCaption(it) {
const overridden =
it.planning_method_profile != null &&
typeof it.planning_method_profile === 'object' &&
!Array.isArray(it.planning_method_profile)
- const archRaw = String(it.catalog_method_archetype || '').trim()
- const archLbl = archRaw ? combinationArchetypeLabel(archRaw) : null
- if (overridden) {
- return archLbl ? `${archLbl} · Planung angepasst` : 'Planung angepasst'
- }
- return archLbl ? `${archLbl} · wie Katalog` : 'wie im Katalog'
+ return overridden ? 'Planung angepasst' : 'wie Katalog'
}
/** Globale Eckdaten aus effective profile (optional unter Stationenliste). */
@@ -1223,10 +1218,6 @@ export default function TrainingUnitSectionsEditor({
{stripArchLbl || stripArchRaw || '—'}
- {compactComboPlanningCaption(it)}
+
+ {compactComboPlanningCaption(it)}
+
{stripGlobalRough ? (
{
if (!row || typeof row !== 'object') return null
const si = Number(row.slot_index)
- const mode = normalizeAdvanceMode(row.advance_mode)
+ const inferredMode = inferAdvanceModeFromStoredSlotRow(row)
const out = {
slot_index: Number.isFinite(si) ? si : 0,
- advance_mode: mode,
+ advance_mode: inferredMode,
load_sec: normalizeOptionalNonNegInt(row.load_sec),
consecutive_reps: normalizeOptionalPositiveInt(row.consecutive_reps),
rep_series_count: normalizeOptionalPositiveInt(row.rep_series_count),
@@ -289,6 +314,8 @@ export function summarizeSlotProfileBrief(r) {
if (adv === 'timed') {
bits.push('Zeit')
if (r.load_sec != null) bits.push(`${r.load_sec}s Arbeit`)
+ if (r.consecutive_reps != null)
+ bits.push(`${r.consecutive_reps}× Wdh. ohne Wechsel zur nächsten Station`)
} else if (adv === 'rep') {
bits.push('Ziel‑Wdh.')
const nSer = r.rep_series_count != null && r.rep_series_count >= 1 ? r.rep_series_count : 1
@@ -307,7 +334,8 @@ export function summarizeSlotProfileBrief(r) {
}
}
if (r.intra_rep_rest_sec != null) {
- if (adv === 'timed') bits.push(`Pause ${r.intra_rep_rest_sec}s`)
+ if (adv === 'timed')
+ bits.push(`Pause zw. Wdh. ${r.intra_rep_rest_sec}s (nicht Stationswechsel)`)
else if (adv === 'rep' && r.rep_series_count != null && r.rep_series_count >= 2)
bits.push(`Pause zw. Serien ${r.intra_rep_rest_sec}s`)
else if (
@@ -330,6 +358,7 @@ export function stationPrimaryLoadLabel(slotRow) {
const adv = slotRow.advance_mode || 'timed'
if (adv === 'timed') {
if (slotRow.load_sec != null) return `${slotRow.load_sec}s`
+ if (slotRow.consecutive_reps != null) return `${slotRow.consecutive_reps}×`
return null
}
if (adv === 'rep') {
diff --git a/frontend/src/utils/comboPlanningMethodProfile.js b/frontend/src/utils/comboPlanningMethodProfile.js
index e461284..24c272d 100644
--- a/frontend/src/utils/comboPlanningMethodProfile.js
+++ b/frontend/src/utils/comboPlanningMethodProfile.js
@@ -1,5 +1,7 @@
/** Effektives Ablaufprofil für Kombination im Coach/in der Planung */
+import { inferAdvanceModeFromStoredSlotRow } from './combinationMethodProfileUi'
+
/** Top-Level: null/undefined aus Planungs-Snapshot löschen keine Katalog-Felder (API liefert oft JSON-null). */
function omitNullUndefinedTop(planObj) {
const out = {}
@@ -11,16 +13,52 @@ function omitNullUndefinedTop(planObj) {
}
/** Je Slot-Zeile: null/undefined aus Planung überschreiben keine Katalog-Werte (z. B. consecutive_reps). */
+function patchHasExplicitAdvanceMode(patch) {
+ return (
+ Object.prototype.hasOwnProperty.call(patch, 'advance_mode') &&
+ patch.advance_mode !== null &&
+ patch.advance_mode !== undefined &&
+ String(patch.advance_mode).trim() !== ''
+ )
+}
+
+/** Nach Merge: Rep/Manual ohne Sekunden-Arbeit; Zeit ohne advance_mode-Schlüssel. */
+function coerceMergedSlotProfileRow(row) {
+ if (!row || typeof row !== 'object') return row
+ const inferred = inferAdvanceModeFromStoredSlotRow(row)
+ if (inferred === 'rep' || inferred === 'manual') {
+ delete row.load_sec
+ row.advance_mode = inferred
+ } else {
+ delete row.advance_mode
+ }
+ return row
+}
+
function mergeSlotProfileFields(prev, patch) {
const base = prev && typeof prev === 'object' ? { ...prev } : {}
- if (!patch || typeof patch !== 'object') return base
+ if (!patch || typeof patch !== 'object') return coerceMergedSlotProfileRow(base)
+
for (const [k, v] of Object.entries(patch)) {
if (v === null || v === undefined) continue
base[k] = v
}
+
+ const loadExplicit =
+ Object.prototype.hasOwnProperty.call(patch, 'load_sec') &&
+ patch.load_sec !== null &&
+ patch.load_sec !== undefined &&
+ String(patch.load_sec).trim() !== ''
+
+ // Nur Arbeitsssekunden aus Planung → Zeitmodus soll Katalog‑advance_mode rep nicht „festhalten“
+ if (loadExplicit && !patchHasExplicitAdvanceMode(patch)) {
+ delete base.advance_mode
+ }
+
const ix = Number(base.slot_index)
if (Number.isFinite(ix)) base.slot_index = ix
- return base
+
+ return coerceMergedSlotProfileRow(base)
}
/**