Refactor TrainingUnitRunPage and trainingPlanUtils for improved section handling
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m26s
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m26s
- Updated TrainingUnitRunPage to utilize new utility functions for managing sections with plan locations, enhancing data representation. - Refactored trainingPlanUtils to introduce functions for building view models from sections and for displaying sections with plan locations, streamlining the data flow. - Improved logic for handling phase runs and section indices, ensuring accurate representation of training units during rendering.
This commit is contained in:
parent
5338871f36
commit
c182ced7cd
|
|
@ -8,9 +8,9 @@ import api from '../utils/api'
|
|||
import ExercisePeekModal from '../components/ExercisePeekModal'
|
||||
import CombinationPlanBracket from '../components/CombinationPlanBracket'
|
||||
import {
|
||||
buildPlanRunViewModel,
|
||||
buildPlanRunViewModelFromSections,
|
||||
itemStableKey,
|
||||
sortedSections,
|
||||
sectionsWithPlanLocForDisplay,
|
||||
sortedItems,
|
||||
} from '../utils/trainingPlanUtils'
|
||||
import { effectiveComboMethodProfile } from '../utils/comboPlanningMethodProfile'
|
||||
|
|
@ -115,8 +115,8 @@ export default function TrainingUnitRunPage() {
|
|||
}
|
||||
}, [idNum, persistChecked])
|
||||
|
||||
const sections = useMemo(() => sortedSections(unit), [unit])
|
||||
const planModel = useMemo(() => buildPlanRunViewModel(unit), [unit])
|
||||
const sections = useMemo(() => sectionsWithPlanLocForDisplay(unit), [unit])
|
||||
const planModel = useMemo(() => buildPlanRunViewModelFromSections(sections), [sections])
|
||||
|
||||
const printStreamOptions = useMemo(() => {
|
||||
const opts = []
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import {
|
||||
cloneJsonSerializablePlanningProfile,
|
||||
inheritPlanLocForPhasedSave,
|
||||
phaseRunsFromSections,
|
||||
sectionIndicesForParallelStream,
|
||||
streamsForParallelPhaseOrders,
|
||||
|
|
@ -37,11 +38,79 @@ export function sumExerciseMinutesInSection(sec) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Lesemodell für Plan & Ablauf / Druck / Coach: Phasenläufe mit Ganzgruppe vs. Split.
|
||||
* Legacy ohne planLoc: ein Block.
|
||||
* GET liefert `planLoc` oft nicht auf flachen `sections`, aber `unit.phases` (verschachtelt).
|
||||
* Baut pro Abschnitt `planLoc` für phaseRuns / Darstellung (camelCase wie im Editor).
|
||||
*/
|
||||
export function buildPlanRunViewModel(unit) {
|
||||
const sections = sortedSections(unit)
|
||||
function planLocBySectionIdFromPhases(phases) {
|
||||
const byId = new Map()
|
||||
if (!Array.isArray(phases)) return byId
|
||||
for (const ph of phases) {
|
||||
const po = Number(ph.order_index ?? ph.orderIndex ?? 0) || 0
|
||||
const pk = String(ph.phase_kind ?? ph.phaseKind ?? '')
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
const phaseTitle = ph.title ?? ph.phaseTitle ?? null
|
||||
const phaseGuidanceNotes = ph.guidance_notes ?? ph.guidanceNotes ?? null
|
||||
if (pk === 'whole_group') {
|
||||
for (const sec of ph.sections || []) {
|
||||
const sid = sec.id != null ? Number(sec.id) : NaN
|
||||
if (!Number.isFinite(sid)) continue
|
||||
byId.set(sid, {
|
||||
phaseKind: 'whole_group',
|
||||
phaseOrderIndex: po,
|
||||
parallelStreamOrderIndex: null,
|
||||
phaseTitle,
|
||||
phaseGuidanceNotes,
|
||||
streamTitle: null,
|
||||
streamNotes: null,
|
||||
streamAssignedTrainerProfileIds: null,
|
||||
})
|
||||
}
|
||||
} else if (pk === 'parallel') {
|
||||
for (const st of ph.streams || []) {
|
||||
const so = Number(st.order_index ?? st.orderIndex ?? 0) || 0
|
||||
const streamTitle = st.title ?? st.streamTitle ?? null
|
||||
const streamNotes = st.notes ?? st.streamNotes ?? null
|
||||
const streamAssignedTrainerProfileIds =
|
||||
st.assigned_trainer_profile_ids ?? st.streamAssignedTrainerProfileIds ?? null
|
||||
for (const sec of st.sections || []) {
|
||||
const sid = sec.id != null ? Number(sec.id) : NaN
|
||||
if (!Number.isFinite(sid)) continue
|
||||
byId.set(sid, {
|
||||
phaseKind: 'parallel',
|
||||
phaseOrderIndex: po,
|
||||
parallelStreamOrderIndex: so,
|
||||
phaseTitle,
|
||||
phaseGuidanceNotes,
|
||||
streamTitle,
|
||||
streamNotes,
|
||||
streamAssignedTrainerProfileIds,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return byId
|
||||
}
|
||||
|
||||
export function sectionsWithPlanLocForDisplay(unit) {
|
||||
const sorted = sortedSections(unit)
|
||||
const byId = planLocBySectionIdFromPhases(unit?.phases)
|
||||
const merged = sorted.map((s) => {
|
||||
const sid = s.id != null ? Number(s.id) : NaN
|
||||
if (Number.isFinite(sid) && byId.has(sid)) {
|
||||
return { ...s, planLoc: { ...byId.get(sid) } }
|
||||
}
|
||||
if (s.planLoc && s.planLoc.phaseKind) return s
|
||||
return { ...s }
|
||||
})
|
||||
return inheritPlanLocForPhasedSave(merged)
|
||||
}
|
||||
|
||||
/**
|
||||
* Läuft auf bereits angereichter Abschnittsliste (gleiche Objektreferenzen wie in Slices).
|
||||
*/
|
||||
export function buildPlanRunViewModelFromSections(sections) {
|
||||
if (!sections.length) {
|
||||
return { mode: 'empty', runs: [], totalMin: 0, runCumulativeEnds: [] }
|
||||
}
|
||||
|
|
@ -88,7 +157,9 @@ export function buildPlanRunViewModel(unit) {
|
|||
const streamOrders = streamsForParallelPhaseOrders(slice, po)
|
||||
const streams = streamOrders.map((so) => {
|
||||
const idxs = sectionIndicesForParallelStream(slice, po, so)
|
||||
const streamSecs = idxs.map((i) => slice[i])
|
||||
const streamSecs = idxs
|
||||
.map((i) => slice[i])
|
||||
.sort((a, b) => (a.order_index ?? 0) - (b.order_index ?? 0))
|
||||
const first = streamSecs[0]
|
||||
const streamTitle = first?.planLoc?.streamTitle ?? null
|
||||
const minutes = streamSecs.reduce((s, sec) => s + sumExerciseMinutesInSection(sec), 0)
|
||||
|
|
@ -119,6 +190,11 @@ export function buildPlanRunViewModel(unit) {
|
|||
return { mode: 'phased', runs, totalMin: cum, runCumulativeEnds }
|
||||
}
|
||||
|
||||
/** @param {object} unit Trainingseinheit inkl. `sections`, optional `phases` (GET) */
|
||||
export function buildPlanRunViewModel(unit) {
|
||||
return buildPlanRunViewModelFromSections(sectionsWithPlanLocForDisplay(unit))
|
||||
}
|
||||
|
||||
function coachContextLabelForSection(sec, sectionsList) {
|
||||
const pl = sec?.planLoc
|
||||
if (!pl?.phaseKind) return 'Ablauf'
|
||||
|
|
@ -134,10 +210,10 @@ function coachContextLabelForSection(sec, sectionsList) {
|
|||
|
||||
/** Flache Reihenfolge für Coach-Timeline (global wie im Editor, inkl. gemischter Split-Abschnitte). */
|
||||
export function flattenPlanTimeline(unit) {
|
||||
const model = buildPlanRunViewModel(unit)
|
||||
const sections = sectionsWithPlanLocForDisplay(unit)
|
||||
const model = buildPlanRunViewModelFromSections(sections)
|
||||
if (model.mode === 'empty') return []
|
||||
|
||||
const sections = sortedSections(unit)
|
||||
const list = []
|
||||
|
||||
const pushSectionItems = (sec, coachCtx) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user