feat(combo-planning): replace summarizeSlotProfileBrief with effectiveStationTimingSummary
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 57s
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 57s
- Updated CombinationCoachSlots, CombinationPlanBracket, and TrainingUnitSectionsEditor components to utilize effectiveStationTimingSummary for improved timing display. - Adjusted station title handling to enhance clarity and consistency across components. - Refactored utility functions to streamline slot timing summaries and improve overall user experience in combination planning.
This commit is contained in:
parent
ed15f73727
commit
79dabbca5a
|
|
@ -10,7 +10,7 @@ import {
|
||||||
combinationArchetypeLabel,
|
combinationArchetypeLabel,
|
||||||
sortCombinationSlotsForDisplay,
|
sortCombinationSlotsForDisplay,
|
||||||
} from '../constants/combinationArchetypes'
|
} from '../constants/combinationArchetypes'
|
||||||
import { readSlotProfilesV1, summarizeSlotProfileBrief } from '../utils/combinationMethodProfileUi'
|
import { effectiveStationTimingSummary, readSlotProfilesV1 } from '../utils/combinationMethodProfileUi'
|
||||||
|
|
||||||
export default function CombinationCoachSlots({
|
export default function CombinationCoachSlots({
|
||||||
combinationSlots,
|
combinationSlots,
|
||||||
|
|
@ -190,10 +190,10 @@ export default function CombinationCoachSlots({
|
||||||
const slotTitle =
|
const slotTitle =
|
||||||
(slot.title && String(slot.title).trim()) ||
|
(slot.title && String(slot.title).trim()) ||
|
||||||
(candIds.length <= 1 && slot.candidates?.[0]?.title) ||
|
(candIds.length <= 1 && slot.candidates?.[0]?.title) ||
|
||||||
`Station ${slot.slot_index != null ? Number(slot.slot_index) + 1 : si + 1}`
|
`Station ${si + 1}`
|
||||||
|
|
||||||
const ix = slot.slot_index != null ? Number(slot.slot_index) : si
|
const ix = slot.slot_index != null ? Number(slot.slot_index) : si
|
||||||
const timingSummary = summarizeSlotProfileBrief(slotTimingByIx.get(ix))
|
const timingSummary = effectiveStationTimingSummary(archeKey, methodProfile || {}, slotTimingByIx.get(ix))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={`${slot.slot_index ?? si}-${slotTitle}`} style={{ lineHeight: 1.45 }}>
|
<li key={`${slot.slot_index ?? si}-${slotTitle}`} style={{ lineHeight: 1.45 }}>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
combinationArchetypeLabel,
|
combinationArchetypeLabel,
|
||||||
sortCombinationSlotsForDisplay,
|
sortCombinationSlotsForDisplay,
|
||||||
} from '../constants/combinationArchetypes'
|
} from '../constants/combinationArchetypes'
|
||||||
import { describeGlobalComboProfile, readSlotProfilesV1, summarizeSlotProfileBrief } from '../utils/combinationMethodProfileUi'
|
import { describeGlobalComboProfile, effectiveStationTimingSummary, readSlotProfilesV1 } from '../utils/combinationMethodProfileUi'
|
||||||
|
|
||||||
function candidateLine(slot) {
|
function candidateLine(slot) {
|
||||||
const cands = slot.candidates
|
const cands = slot.candidates
|
||||||
|
|
@ -95,14 +95,18 @@ export default function CombinationPlanBracket({
|
||||||
const ixParsed =
|
const ixParsed =
|
||||||
siRaw === '' || siRaw == null ? si : typeof siRaw === 'number' ? siRaw : parseInt(String(siRaw), 10)
|
siRaw === '' || siRaw == null ? si : typeof siRaw === 'number' ? siRaw : parseInt(String(siRaw), 10)
|
||||||
const stationIx = Number.isFinite(ixParsed) ? ixParsed : si
|
const stationIx = Number.isFinite(ixParsed) ? ixParsed : si
|
||||||
const stationTitle = ((slot.title || '').trim() || `Station ${stationIx}`).trim()
|
const displayStep = si + 1
|
||||||
|
const stationTitle = ((slot.title || '').trim() || `Station ${displayStep}`).trim()
|
||||||
const names = candidateLine(slot)
|
const names = candidateLine(slot)
|
||||||
const timing = summarizeSlotProfileBrief(timingByIx.get(stationIx))
|
const timing = effectiveStationTimingSummary(arch, methodProfile || {}, timingByIx.get(stationIx))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={`slot-${stationIx}-${si}`} className="combo-plan-bracket__station">
|
<li key={`slot-${stationIx}-${si}`} className="combo-plan-bracket__station">
|
||||||
<div className="combo-plan-bracket__station-index" title={`slot_index ${stationIx}`}>
|
<div
|
||||||
S{stationIx}
|
className="combo-plan-bracket__station-index"
|
||||||
|
title={`Technischer Slot-Index (slot_index): ${stationIx}`}
|
||||||
|
>
|
||||||
|
S{displayStep}
|
||||||
</div>
|
</div>
|
||||||
<div className="combo-plan-bracket__station-main">
|
<div className="combo-plan-bracket__station-main">
|
||||||
<div className="combo-plan-bracket__station-title">{stationTitle}</div>
|
<div className="combo-plan-bracket__station-title">{stationTitle}</div>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
sectionPlannedMinutes,
|
sectionPlannedMinutes,
|
||||||
} from '../utils/trainingUnitSectionsForm'
|
} from '../utils/trainingUnitSectionsForm'
|
||||||
import api from '../utils/api'
|
import api from '../utils/api'
|
||||||
import { readSlotProfilesV1, summarizeSlotProfileBrief } from '../utils/combinationMethodProfileUi'
|
import { effectiveStationTimingSummary, readSlotProfilesV1 } from '../utils/combinationMethodProfileUi'
|
||||||
import { isCompactTagLegendMode } from '../config/planningModuleUx'
|
import { isCompactTagLegendMode } from '../config/planningModuleUx'
|
||||||
import { useAuth } from '../context/AuthContext'
|
import { useAuth } from '../context/AuthContext'
|
||||||
|
|
||||||
|
|
@ -109,6 +109,7 @@ function comboPlanningStripBulletTexts(it) {
|
||||||
const slots = sortCombinationSlotsForDisplay(it.combination_slots || [])
|
const slots = sortCombinationSlotsForDisplay(it.combination_slots || [])
|
||||||
if (!slots.length) return []
|
if (!slots.length) return []
|
||||||
const mp = effectiveComboMethodProfile(it.catalog_method_profile || {}, it.planning_method_profile)
|
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 byIx = new Map(readSlotProfilesV1(mp).map((r) => [Number(r.slot_index), r]))
|
||||||
const titles = it.combo_member_title_by_id || {}
|
const titles = it.combo_member_title_by_id || {}
|
||||||
return slots.map((slot, idx) => {
|
return slots.map((slot, idx) => {
|
||||||
|
|
@ -116,7 +117,7 @@ function comboPlanningStripBulletTexts(it) {
|
||||||
const siParsed =
|
const siParsed =
|
||||||
siRaw === '' || siRaw == null ? idx : typeof siRaw === 'number' ? siRaw : parseInt(String(siRaw), 10)
|
siRaw === '' || siRaw == null ? idx : typeof siRaw === 'number' ? siRaw : parseInt(String(siRaw), 10)
|
||||||
const ix = Number.isFinite(siParsed) ? siParsed : idx
|
const ix = Number.isFinite(siParsed) ? siParsed : idx
|
||||||
const stationLbl = ((slot.title || '').trim() || `Station ${ix}`)
|
const stationLbl = ((slot.title || '').trim() || `Station ${idx + 1}`)
|
||||||
const candIds = (slot.candidate_exercise_ids || [])
|
const candIds = (slot.candidate_exercise_ids || [])
|
||||||
.map((raw) => (typeof raw === 'number' ? raw : parseInt(String(raw), 10)))
|
.map((raw) => (typeof raw === 'number' ? raw : parseInt(String(raw), 10)))
|
||||||
.filter((n) => Number.isFinite(n))
|
.filter((n) => Number.isFinite(n))
|
||||||
|
|
@ -124,7 +125,7 @@ function comboPlanningStripBulletTexts(it) {
|
||||||
candIds.length === 0
|
candIds.length === 0
|
||||||
? '(keine Übung)'
|
? '(keine Übung)'
|
||||||
: candIds.map((id) => titles[String(id)] || `Übung ${id}`).join(' ↔ ')
|
: candIds.map((id) => titles[String(id)] || `Übung ${id}`).join(' ↔ ')
|
||||||
const timing = summarizeSlotProfileBrief(byIx.get(ix))
|
const timing = effectiveStationTimingSummary(archRaw, mp, byIx.get(ix))
|
||||||
let line = `${stationLbl}: ${namesJoined}`
|
let line = `${stationLbl}: ${namesJoined}`
|
||||||
if (timing) line += ` · ${timing}`
|
if (timing) line += ` · ${timing}`
|
||||||
return line
|
return line
|
||||||
|
|
|
||||||
|
|
@ -322,6 +322,82 @@ export function summarizeSlotProfileBrief(r) {
|
||||||
return bits.join(' · ')
|
return bits.join(' · ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function globalTimingHintsForArchetype(arch, mp) {
|
||||||
|
if (!mp || typeof mp !== 'object' || Array.isArray(mp)) return []
|
||||||
|
const bits = []
|
||||||
|
switch (arch) {
|
||||||
|
case 'circuit_rotate_time':
|
||||||
|
if (mp.work_seconds != null && mp.work_seconds !== '') bits.push(`${mp.work_seconds}s Arbeit je Station`)
|
||||||
|
if (mp.transition_seconds != null && mp.transition_seconds !== '')
|
||||||
|
bits.push(`Rotation ${mp.transition_seconds}s`)
|
||||||
|
if (mp.rest_seconds != null && mp.rest_seconds !== '') bits.push(`Pause ${mp.rest_seconds}s`)
|
||||||
|
if (mp.rounds != null && mp.rounds !== '') bits.push(`${mp.rounds} Umlauf‑Runden`)
|
||||||
|
break
|
||||||
|
case 'sequence_linear':
|
||||||
|
if (mp.hint_step_duration_sec != null && mp.hint_step_duration_sec !== '')
|
||||||
|
bits.push(`~${mp.hint_step_duration_sec}s je Station`)
|
||||||
|
if (mp.rounds != null && mp.rounds !== '') bits.push(`${mp.rounds} Sequenz‑Durchläufe`)
|
||||||
|
if (mp.block_intro_sec != null && mp.block_intro_sec !== '') bits.push(`Block‑Intro ${mp.block_intro_sec}s`)
|
||||||
|
break
|
||||||
|
case 'time_domain_interval':
|
||||||
|
if (mp.work_seconds != null && mp.work_seconds !== '') bits.push(`${mp.work_seconds}s Intervall‑Arbeit`)
|
||||||
|
if (mp.rest_seconds != null && mp.rest_seconds !== '') bits.push(`${mp.rest_seconds}s Erholung`)
|
||||||
|
if (mp.interval_rounds != null && mp.interval_rounds !== '') bits.push(`${mp.interval_rounds} Intervall‑Zyklen`)
|
||||||
|
break
|
||||||
|
case 'pair_superset':
|
||||||
|
if (mp.work_seconds_per_side != null && mp.work_seconds_per_side !== '')
|
||||||
|
bits.push(`${mp.work_seconds_per_side}s Arbeit`)
|
||||||
|
if (mp.switch_seconds != null && mp.switch_seconds !== '') bits.push(`Wechsel ${mp.switch_seconds}s`)
|
||||||
|
break
|
||||||
|
case 'station_parcour':
|
||||||
|
if (mp.rounds != null && mp.rounds !== '') bits.push(`${mp.rounds} Parcours‑Runden`)
|
||||||
|
break
|
||||||
|
case 'circuit_all_parallel':
|
||||||
|
if (mp.rounds != null && mp.rounds !== '') bits.push(`${mp.rounds} Runden`)
|
||||||
|
if (mp.explain_before_seconds != null && mp.explain_before_seconds !== '')
|
||||||
|
bits.push(`Erklärung ${mp.explain_before_seconds}s`)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return bits
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWeakSlotTimingSummary(txt) {
|
||||||
|
if (!txt || typeof txt !== 'string') return true
|
||||||
|
const t = txt.trim()
|
||||||
|
return t === 'Zeit' || t === 'Coach' || t === 'Ziel‑Wdh.'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stationszeile für Lesetext: Slot‑Zeiten + bei Bedarf globale Eckdaten (Zirkel‑Sekunden, Runden …).
|
||||||
|
*/
|
||||||
|
export function effectiveStationTimingSummary(archetypeKey, profileObj, slotRow) {
|
||||||
|
const arch = typeof archetypeKey === 'string' ? archetypeKey.trim() : ''
|
||||||
|
const mp = profileObj && typeof profileObj === 'object' && !Array.isArray(profileObj) ? profileObj : {}
|
||||||
|
const slotTxt = summarizeSlotProfileBrief(slotRow)
|
||||||
|
const hints = globalTimingHintsForArchetype(arch, mp)
|
||||||
|
const hintStr = hints.join(' · ')
|
||||||
|
|
||||||
|
if (!isWeakSlotTimingSummary(slotTxt)) {
|
||||||
|
const extras = []
|
||||||
|
for (const h of hints) {
|
||||||
|
if (
|
||||||
|
/Runden|Durchläufe|Zyklen|Umlauf/i.test(h) &&
|
||||||
|
slotTxt &&
|
||||||
|
!/Runden|Serien|×|\d+s Arbeit|\d+s Erholung|\d+s Intervall/i.test(slotTxt)
|
||||||
|
) {
|
||||||
|
extras.push(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extras.length ? `${slotTxt} · ${extras.join(' · ')}` : slotTxt
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hintStr) return hintStr
|
||||||
|
if (slotTxt) return slotTxt
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeOptionalNonNegInt(v) {
|
function normalizeOptionalNonNegInt(v) {
|
||||||
if (v === '' || v === undefined || v === null) return undefined
|
if (v === '' || v === undefined || v === null) return undefined
|
||||||
const n = typeof v === 'number' ? v : parseInt(String(v), 10)
|
const n = typeof v === 'number' ? v : parseInt(String(v), 10)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user