2026-05-13 14:24:55 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Kombination: konsolidierte Darstellung globales Profil + Stationen mit Zeiten (Vorschau, Plan-Ansicht, Druck).
|
|
|
|
|
|
*/
|
|
|
|
|
|
import React, { useMemo } from 'react'
|
|
|
|
|
|
import {
|
|
|
|
|
|
archetypeCoachHint,
|
|
|
|
|
|
combinationArchetypeLabel,
|
|
|
|
|
|
sortCombinationSlotsForDisplay,
|
|
|
|
|
|
} from '../constants/combinationArchetypes'
|
2026-05-13 14:34:17 +02:00
|
|
|
|
import { describeGlobalComboProfile, effectiveStationTimingSummary, readSlotProfilesV1 } from '../utils/combinationMethodProfileUi'
|
2026-05-13 14:24:55 +02:00
|
|
|
|
|
|
|
|
|
|
function candidateLine(slot) {
|
|
|
|
|
|
const cands = slot.candidates
|
|
|
|
|
|
if (Array.isArray(cands) && cands.length > 0) {
|
|
|
|
|
|
return cands
|
|
|
|
|
|
.map((c) =>
|
|
|
|
|
|
((c.title || '').trim() || (c.exercise_id != null ? `Übung #${c.exercise_id}` : '')).trim(),
|
|
|
|
|
|
)
|
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
|
.join(' ↔ ')
|
|
|
|
|
|
}
|
|
|
|
|
|
const ids = slot.candidate_exercise_ids || []
|
|
|
|
|
|
return ids
|
|
|
|
|
|
.map((raw) => {
|
|
|
|
|
|
const n = typeof raw === 'number' ? raw : parseInt(String(raw), 10)
|
|
|
|
|
|
return Number.isFinite(n) ? `Übung #${n}` : ''
|
|
|
|
|
|
})
|
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
|
.join(' ↔ ')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function CombinationPlanBracket({
|
|
|
|
|
|
methodArchetype,
|
|
|
|
|
|
methodProfile,
|
|
|
|
|
|
combinationSlots,
|
|
|
|
|
|
planningAdjusted = false,
|
|
|
|
|
|
}) {
|
|
|
|
|
|
const arch = typeof methodArchetype === 'string' ? methodArchetype.trim() : ''
|
|
|
|
|
|
const archLabel = arch ? combinationArchetypeLabel(arch) : null
|
|
|
|
|
|
const globals = describeGlobalComboProfile(arch, methodProfile || {})
|
|
|
|
|
|
const slotsSorted = useMemo(() => sortCombinationSlotsForDisplay(combinationSlots || []), [combinationSlots])
|
|
|
|
|
|
const timingByIx = useMemo(() => {
|
|
|
|
|
|
const mp = methodProfile || {}
|
|
|
|
|
|
const rows = readSlotProfilesV1(mp)
|
|
|
|
|
|
const m = new Map()
|
|
|
|
|
|
for (const r of rows) {
|
|
|
|
|
|
m.set(Number(r.slot_index), r)
|
|
|
|
|
|
}
|
|
|
|
|
|
return m
|
|
|
|
|
|
}, [methodProfile])
|
|
|
|
|
|
|
|
|
|
|
|
const coachHint = arch ? archetypeCoachHint(arch) : ''
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="combo-plan-bracket">
|
|
|
|
|
|
<div className="combo-plan-bracket__accent" aria-hidden />
|
|
|
|
|
|
<div className="combo-plan-bracket__body">
|
|
|
|
|
|
<header className="combo-plan-bracket__head">
|
|
|
|
|
|
<div className="combo-plan-bracket__head-main">
|
|
|
|
|
|
<span className="combo-plan-bracket__kicker">Kombinations‑Plan</span>
|
|
|
|
|
|
<span className="combo-plan-bracket__archetype">
|
|
|
|
|
|
{archLabel || arch || 'Archetyp'}
|
|
|
|
|
|
{arch && archLabel && archLabel !== arch ? (
|
|
|
|
|
|
<span className="combo-plan-bracket__archetype-id"> ({arch})</span>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{planningAdjusted ? (
|
|
|
|
|
|
<span className="combo-plan-bracket__badge">Planung angepasst</span>
|
|
|
|
|
|
) : null}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{coachHint ? <p className="combo-plan-bracket__hint">{coachHint}</p> : null}
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
{globals.length > 0 ? (
|
|
|
|
|
|
<section className="combo-plan-bracket__globals" aria-label="Globale Eckdaten">
|
|
|
|
|
|
<div className="combo-plan-bracket__globals-title">Runden · Zeiten · Pausen (global)</div>
|
|
|
|
|
|
<ul className="combo-plan-bracket__chip-row">
|
|
|
|
|
|
{globals.map((g) => (
|
|
|
|
|
|
<li key={g.key} className="combo-plan-bracket__chip" title={g.detailLabel}>
|
|
|
|
|
|
<span className="combo-plan-bracket__chip-cap">{g.caption}</span>
|
|
|
|
|
|
<span className="combo-plan-bracket__chip-val">{g.value}</span>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<p className="combo-plan-bracket__globals-empty">
|
|
|
|
|
|
Keine globalen Zahlenfelder gesetzt — Steuerung erfolgt nur je Station oder über den Freitext der Kombination.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<ol className="combo-plan-bracket__stations">
|
|
|
|
|
|
{slotsSorted.map((slot, si) => {
|
|
|
|
|
|
const siRaw = slot.slot_index
|
|
|
|
|
|
const ixParsed =
|
|
|
|
|
|
siRaw === '' || siRaw == null ? si : typeof siRaw === 'number' ? siRaw : parseInt(String(siRaw), 10)
|
|
|
|
|
|
const stationIx = Number.isFinite(ixParsed) ? ixParsed : si
|
2026-05-13 14:34:17 +02:00
|
|
|
|
const displayStep = si + 1
|
|
|
|
|
|
const stationTitle = ((slot.title || '').trim() || `Station ${displayStep}`).trim()
|
2026-05-13 14:24:55 +02:00
|
|
|
|
const names = candidateLine(slot)
|
2026-05-13 14:34:17 +02:00
|
|
|
|
const timing = effectiveStationTimingSummary(arch, methodProfile || {}, timingByIx.get(stationIx))
|
2026-05-13 14:24:55 +02:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<li key={`slot-${stationIx}-${si}`} className="combo-plan-bracket__station">
|
2026-05-13 14:34:17 +02:00
|
|
|
|
<div
|
|
|
|
|
|
className="combo-plan-bracket__station-index"
|
|
|
|
|
|
title={`Technischer Slot-Index (slot_index): ${stationIx}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
S{displayStep}
|
2026-05-13 14:24:55 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="combo-plan-bracket__station-main">
|
|
|
|
|
|
<div className="combo-plan-bracket__station-title">{stationTitle}</div>
|
|
|
|
|
|
<div className="combo-plan-bracket__station-exercises">{names || '(keine Einzelübung)'}</div>
|
|
|
|
|
|
{timing ? (
|
|
|
|
|
|
<div className="combo-plan-bracket__station-timing">
|
|
|
|
|
|
<span className="combo-plan-bracket__timing-label">Zeit / Steuerung</span>
|
|
|
|
|
|
<span>{timing}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="combo-plan-bracket__station-timing combo-plan-bracket__station-timing--muted">
|
|
|
|
|
|
Keine eigene Stations‑Zeit im Profil — ggf. nur globale Vorgaben oder Freitext.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
|
|
|
|
|
</ol>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|