diff --git a/frontend/src/app.css b/frontend/src/app.css
index a5ea375..64c3d7a 100644
--- a/frontend/src/app.css
+++ b/frontend/src/app.css
@@ -5414,22 +5414,80 @@ 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 */
+/* Kombinationszeile: immer unter Hauptzeile (Titel / Minuten / Aktionen), nicht daneben */
+.training-unit-sections-editor .tu-item-row--exercise.tu-item-row--combo {
+ flex-direction: column;
+ align-items: stretch;
+ flex-wrap: nowrap;
+ gap: 0;
+}
+
+.training-unit-sections-editor .tu-item-row--exercise.tu-item-row--combo .tu-item-row__mainline {
+ flex: none;
+ width: 100%;
+}
+
+/* Kombinations‑Strip: volle Breite; oben „Ablauf bearbeiten“, darunter Klammer‑Vorschau */
.training-unit-sections-editor .tu-combo-planning-strip {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 10px;
+ padding: 10px 12px 12px;
+ border-top: 1px solid color-mix(in srgb, var(--border2) 85%, var(--accent) 12%);
+ background: color-mix(in srgb, var(--surface2) 65%, var(--surface));
+ margin-top: 2px;
}
-.training-unit-sections-editor .tu-combo-planning-strip__meta {
- width: 100%;
- max-width: min(100%, 42rem);
+.training-unit-sections-editor--item-drag .tu-item-row--combo .tu-combo-planning-strip {
+ padding-left: 44px;
+}
+
+.training-unit-sections-editor .tu-combo-planning-strip__toolbar {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.training-unit-sections-editor .tu-combo-planning-strip__meta--fallback {
+ font-size: 0.78rem;
+ color: var(--text2);
+ line-height: 1.45;
+}
+
+.training-unit-sections-editor .tu-combo-planning-strip__bracket-wrap {
min-width: 0;
+ overflow-x: auto;
}
-.training-unit-sections-editor .tu-combo-planning-strip > .btn {
- align-self: flex-start;
+.training-unit-sections-editor .combo-plan-bracket--planning-embed {
+ font-size: 0.93rem;
+}
+
+.training-unit-sections-editor .combo-plan-bracket--planning-embed .combo-plan-bracket__station {
+ padding: 8px 9px;
+}
+
+.training-unit-sections-editor .combo-plan-bracket--planning-embed .combo-plan-bracket__chip {
+ padding: 5px 8px;
+}
+
+.training-unit-sections-editor .combo-plan-bracket--planning-embed .combo-plan-bracket__globals-title {
+ font-size: 0.72rem;
+}
+
+.training-unit-sections-editor .combo-plan-bracket--planning-embed .combo-plan-bracket__head-main {
+ flex-wrap: wrap;
+}
+
+.training-unit-sections-editor .combo-plan-bracket--planning-embed .combo-plan-bracket__kicker {
+ font-size: 0.62rem;
+}
+
+.training-unit-sections-editor .combo-plan-bracket--planning-embed .combo-plan-bracket__archetype {
+ font-size: 0.88rem;
}
.tu-planning-mod-tag {
diff --git a/frontend/src/components/CombinationPlanBracket.jsx b/frontend/src/components/CombinationPlanBracket.jsx
index 28438ee..cffe8bd 100644
--- a/frontend/src/components/CombinationPlanBracket.jsx
+++ b/frontend/src/components/CombinationPlanBracket.jsx
@@ -41,6 +41,7 @@ export default function CombinationPlanBracket({
/** 'none' | 'link' (Router) | 'button' (z. B. ExercisePeekModal / PWA-sicher) */
candidateInteraction = 'none',
onCandidatePeek,
+ className,
}) {
const arch = typeof methodArchetype === 'string' ? methodArchetype.trim() : ''
const archLabel = arch ? combinationArchetypeLabel(arch) : null
@@ -59,7 +60,7 @@ export default function CombinationPlanBracket({
const coachHint = arch ? archetypeCoachHint(arch) : ''
return (
-
+
diff --git a/frontend/src/components/TrainingUnitSectionsEditor.jsx b/frontend/src/components/TrainingUnitSectionsEditor.jsx
index 995802d..7ac7580 100644
--- a/frontend/src/components/TrainingUnitSectionsEditor.jsx
+++ b/frontend/src/components/TrainingUnitSectionsEditor.jsx
@@ -3,7 +3,7 @@ import { GripVertical, Pencil } from 'lucide-react'
import CombinationMethodProfileEditor from './CombinationMethodProfileEditor'
import CombinationPlanBracket from './CombinationPlanBracket'
import { comboPlanningProfileJsonForEditor, effectiveComboMethodProfile } from '../utils/comboPlanningMethodProfile'
-import { combinationArchetypeLabel, sortCombinationSlotsForDisplay } from '../constants/combinationArchetypes'
+import { sortCombinationSlotsForDisplay } from '../constants/combinationArchetypes'
import {
cloneJsonSerializablePlanningProfile,
comboSlotsOutlineForProfileEditor,
@@ -13,7 +13,6 @@ import {
sectionPlannedMinutes,
} from '../utils/trainingUnitSectionsForm'
import api from '../utils/api'
-import { effectiveStationTimingSummary, readSlotProfilesV1 } from '../utils/combinationMethodProfileUi'
import { isCompactTagLegendMode } from '../config/planningModuleUx'
import { useAuth } from '../context/AuthContext'
@@ -74,60 +73,6 @@ function compactComboPlanningCaption(it) {
return overridden ? 'Planung angepasst' : 'wie Katalog'
}
-/** Globale Eckdaten aus effective profile (optional unter Stationenliste). */
-function comboRoughGlobalTimingHint(profileObj, archetypeKey) {
- if (!profileObj || typeof profileObj !== 'object' || Array.isArray(profileObj)) return null
- const bits = []
- const rounds = profileObj.rounds
- const ws = profileObj.work_seconds
- const rb = profileObj.rest_between_rounds_sec
- const hint = profileObj.hint_step_duration_sec
- const globRest = profileObj.rest_between_sets_sec
- if (rounds != null && rounds !== '') bits.push(`${rounds} Runden`)
- if (ws != null && ws !== '') bits.push(`${ws}s Arbeit`)
- if (rb != null && rb !== '') bits.push(`Pause ${rb}s`)
- if (globRest != null && globRest !== '') bits.push(`Sets-Pause ${globRest}s`)
- if (hint != null && hint !== '') bits.push(`Orientierung ~${hint}s`)
- const arch = (archetypeKey || '').trim()
- if (arch === 'time_domain_interval') {
- const iw = profileObj.interval_work_sec
- const ir = profileObj.interval_rest_sec
- const ig = profileObj.interval_groups
- if (iw != null && iw !== '') bits.push(`${iw}s Intervall`)
- if (ir != null && ir !== '') bits.push(`${ir}s Erholung`)
- if (ig != null && ig !== '') bits.push(`${ig} Gruppen`)
- }
- return bits.length ? bits.join(' · ') : null
-}
-
-/** Pro Station eine kompakte Textzeile für die Planungsliste. */
-function comboPlanningStripBulletTexts(it) {
- const slots = sortCombinationSlotsForDisplay(it.combination_slots || [])
- if (!slots.length) return []
- const mp = effectiveComboMethodProfile(it.catalog_method_profile || {}, it.planning_method_profile)
- const archRaw = String(it.catalog_method_archetype || '').trim()
- const byIx = new Map(readSlotProfilesV1(mp).map((r) => [Number(r.slot_index), r]))
- const titles = it.combo_member_title_by_id || {}
- return slots.map((slot, idx) => {
- const siRaw = slot.slot_index
- const siParsed =
- siRaw === '' || siRaw == null ? idx : typeof siRaw === 'number' ? siRaw : parseInt(String(siRaw), 10)
- const ix = Number.isFinite(siParsed) ? siParsed : idx
- const stationLbl = ((slot.title || '').trim() || `Station ${idx + 1}`)
- const candIds = (slot.candidate_exercise_ids || [])
- .map((raw) => (typeof raw === 'number' ? raw : parseInt(String(raw), 10)))
- .filter((n) => Number.isFinite(n))
- const namesJoined =
- candIds.length === 0
- ? '(keine Übung)'
- : candIds.map((id) => titles[String(id)] || `Übung ${id}`).join(' ↔ ')
- const timing = effectiveStationTimingSummary(archRaw, mp, byIx.get(ix))
- let line = `${stationLbl}: ${namesJoined}`
- if (timing) line += ` · ${timing}`
- return line
- })
-}
-
/** Stabile Farbzurodnung aus Modul-ID (nur Darstellung). */
function planningModulePalette(moduleId) {
const id = normalizedPlanningModuleChainId(moduleId)
@@ -703,7 +648,8 @@ export default function TrainingUnitSectionsEditor({
{(!hideHeading || headingAccessory) ? (
@@ -1017,10 +963,6 @@ export default function TrainingUnitSectionsEditor({
const stripArchRaw =
isCombination && it.exercise_id ? String(it.catalog_method_archetype || '').trim() : ''
- const stripArchLbl =
- stripArchRaw && isCombination ? combinationArchetypeLabel(stripArchRaw) : null
- const stripBullets =
- isCombination && it.exercise_id ? comboPlanningStripBulletTexts(it) : []
const stripMpEff =
isCombination && it.exercise_id
? effectiveComboMethodProfile(
@@ -1028,17 +970,15 @@ export default function TrainingUnitSectionsEditor({
it.planning_method_profile,
)
: null
- const stripGlobalRough =
- isCombination && it.exercise_id && stripMpEff
- ? comboRoughGlobalTimingHint(stripMpEff, stripArchRaw)
- : null
return (
{!planningCompactLegend &&
renderModulePlanningHead(modBandTitle, modOutline, showModuleBand)}
@@ -1215,76 +1155,48 @@ export default function TrainingUnitSectionsEditor({
{isCombination && it.exercise_id ? (
-
-
-
- Archetyp:
-
- {stripArchLbl || stripArchRaw || '—'}
-
-
- {compactComboPlanningCaption(it)}
-
-
- {stripGlobalRough ? (
-
- Block:
- {stripGlobalRough}
-
- ) : null}
- {stripBullets.length > 0 ? (
-
- {stripBullets.map((line, bi) => (
- -
- {line}
-
- ))}
-
- ) : (
-
- Stationen laden oder noch keine Kombi-Stationen im Katalog …
-
- )}
+
+
+
-
+ {(it.combination_slots || []).length > 0 ? (
+
+ onPeekExercise(Number(exId), null, undefined)
+ : undefined
+ }
+ />
+
+ ) : (
+
+
+ Stationen werden geladen oder die Kombination hat im Katalog noch keine Stationsliste …
+
+
+ )}
) : null}
diff --git a/frontend/src/pages/ExerciseFormPage.jsx b/frontend/src/pages/ExerciseFormPage.jsx
index 975fa07..b82904a 100644
--- a/frontend/src/pages/ExerciseFormPage.jsx
+++ b/frontend/src/pages/ExerciseFormPage.jsx
@@ -2403,18 +2403,6 @@ function ExerciseFormPage() {
}
/>
)}
-
setComboStationPickerIx(null)}
- exerciseKindAny={['simple']}
- multiSelect
- enableQuickCreateDraft
- onSelectExercises={(picked) => {
- if (comboStationPickerIx === null) return
- mergePickedExercisesIntoSlot(comboStationPickerIx, picked)
- setComboStationPickerIx(null)
- }}
- />
{reportTarget && (
)}
+ setComboStationPickerIx(null)}
+ exerciseKindAny={['simple']}
+ multiSelect
+ enableQuickCreateDraft
+ onSelectExercises={(picked) => {
+ if (comboStationPickerIx === null) return
+ mergePickedExercisesIntoSlot(comboStationPickerIx, picked)
+ setComboStationPickerIx(null)
+ }}
+ />
+
KI-Ausbaustufe: Backend laut Spec{' '}
POST /api/exercises/ai/suggest und{' '}