shinkan-jinkendo/frontend/src/components/CombinationPlanBracket.jsx

129 lines
5.2 KiB
React
Raw Normal View History

/**
* Kombination: konsolidierte Darstellung globales Profil + Stationen mit Zeiten (Vorschau, Plan-Ansicht, Druck).
*/
import React, { useMemo } from 'react'
import {
archetypeCoachHint,
combinationArchetypeLabel,
sortCombinationSlotsForDisplay,
} from '../constants/combinationArchetypes'
import { describeGlobalComboProfile, readSlotProfilesV1, summarizeSlotProfileBrief } from '../utils/combinationMethodProfileUi'
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
const stationTitle = ((slot.title || '').trim() || `Station ${stationIx}`).trim()
const names = candidateLine(slot)
const timing = summarizeSlotProfileBrief(timingByIx.get(stationIx))
return (
<li key={`slot-${stationIx}-${si}`} className="combo-plan-bracket__station">
<div className="combo-plan-bracket__station-index" title={`slot_index ${stationIx}`}>
S{stationIx}
</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>
)
}