- Added a new `headingAccessory` prop to TrainingUnitSectionsEditor for customizable header content. - Updated the component to conditionally render the heading and accessory in a flexible toolbar layout. - Refactored TrainingFrameworkProgramEditPage and TrainingPlanningPage to utilize the new heading accessory feature, improving user interaction and layout consistency.
192 lines
5.7 KiB
JavaScript
192 lines
5.7 KiB
JavaScript
import api from './api'
|
|
|
|
export function defaultSection(title = 'Hauptteil') {
|
|
return { title, guidance_notes: '', items: [] }
|
|
}
|
|
|
|
export function exerciseRow() {
|
|
return {
|
|
item_type: 'exercise',
|
|
exercise_id: '',
|
|
exercise_variant_id: '',
|
|
exercise_title: '',
|
|
variants: [],
|
|
planned_duration_min: '',
|
|
actual_duration_min: '',
|
|
notes: '',
|
|
modifications: '',
|
|
}
|
|
}
|
|
|
|
export async function hydrateExercisePlanningRow(exercise) {
|
|
let variants = Array.isArray(exercise?.variants) ? exercise.variants : []
|
|
let title = exercise?.title || ''
|
|
const id = exercise?.id
|
|
if (!id) return null
|
|
if (!variants.length) {
|
|
try {
|
|
const full = await api.getExercise(id)
|
|
variants = Array.isArray(full?.variants) ? full.variants : []
|
|
title = full?.title || title
|
|
} catch {
|
|
variants = []
|
|
}
|
|
}
|
|
const row = exerciseRow()
|
|
row.exercise_id = id
|
|
row.exercise_variant_id = ''
|
|
row.exercise_title = title
|
|
row.variants = variants
|
|
return row
|
|
}
|
|
|
|
export function noteRow() {
|
|
return { item_type: 'note', note_body: '' }
|
|
}
|
|
|
|
export function normalizeUnitToForm(fullUnit) {
|
|
if (fullUnit.sections && fullUnit.sections.length) {
|
|
return fullUnit.sections.map((sec) => ({
|
|
title: sec.title,
|
|
guidance_notes: sec.guidance_notes || '',
|
|
items: (sec.items || []).map((it) => {
|
|
if (it.item_type === 'note') {
|
|
return { item_type: 'note', note_body: it.note_body || '' }
|
|
}
|
|
return {
|
|
item_type: 'exercise',
|
|
exercise_id: it.exercise_id,
|
|
exercise_variant_id: it.exercise_variant_id ?? '',
|
|
exercise_title: it.exercise_title || '',
|
|
variants: [],
|
|
planned_duration_min:
|
|
it.planned_duration_min !== null && it.planned_duration_min !== undefined
|
|
? String(it.planned_duration_min)
|
|
: '',
|
|
actual_duration_min:
|
|
it.actual_duration_min !== null && it.actual_duration_min !== undefined
|
|
? String(it.actual_duration_min)
|
|
: '',
|
|
notes: it.notes ?? '',
|
|
modifications: it.modifications ?? '',
|
|
}
|
|
}),
|
|
}))
|
|
}
|
|
if (fullUnit.exercises && fullUnit.exercises.length) {
|
|
return [
|
|
{
|
|
title: 'Übungen',
|
|
guidance_notes: '',
|
|
items: fullUnit.exercises.map((ex) => ({
|
|
item_type: 'exercise',
|
|
exercise_id: ex.exercise_id,
|
|
exercise_variant_id: ex.exercise_variant_id ?? '',
|
|
exercise_title: ex.exercise_title || '',
|
|
variants: [],
|
|
planned_duration_min:
|
|
ex.planned_duration_min !== null && ex.planned_duration_min !== undefined
|
|
? String(ex.planned_duration_min)
|
|
: '',
|
|
actual_duration_min:
|
|
ex.actual_duration_min !== null && ex.actual_duration_min !== undefined
|
|
? String(ex.actual_duration_min)
|
|
: '',
|
|
notes: ex.notes ?? '',
|
|
modifications: ex.modifications ?? '',
|
|
})),
|
|
},
|
|
]
|
|
}
|
|
return [defaultSection()]
|
|
}
|
|
|
|
export async function enrichSectionsWithVariants(sections) {
|
|
if (!sections?.length) return sections
|
|
const ids = []
|
|
for (const sec of sections) {
|
|
for (const it of sec.items || []) {
|
|
if (it.item_type === 'note') continue
|
|
if (it.exercise_id) ids.push(it.exercise_id)
|
|
}
|
|
}
|
|
const unique = [...new Set(ids)]
|
|
const cache = new Map()
|
|
await Promise.all(
|
|
unique.map(async (id) => {
|
|
try {
|
|
const ex = await api.getExercise(id)
|
|
cache.set(id, {
|
|
title: ex.title || '',
|
|
variants: Array.isArray(ex.variants) ? ex.variants : [],
|
|
})
|
|
} catch {
|
|
cache.set(id, { title: '', variants: [] })
|
|
}
|
|
})
|
|
)
|
|
return sections.map((sec) => ({
|
|
...sec,
|
|
items: (sec.items || []).map((it) => {
|
|
if (it.item_type === 'note') return it
|
|
if (!it.exercise_id) return it
|
|
const c = cache.get(it.exercise_id)
|
|
if (!c) return it
|
|
return {
|
|
...it,
|
|
exercise_title: it.exercise_title || c.title,
|
|
variants:
|
|
Array.isArray(it.variants) && it.variants.length > 0 ? it.variants : c.variants,
|
|
}
|
|
}),
|
|
}))
|
|
}
|
|
|
|
export function parseMin(v) {
|
|
if (v === '' || v === null || v === undefined) return null
|
|
const n = parseInt(String(v), 10)
|
|
return Number.isFinite(n) ? n : null
|
|
}
|
|
|
|
export function buildSectionsPayload(sections) {
|
|
return sections.map((sec, si) => ({
|
|
order_index: si,
|
|
title: (sec.title || '').trim() || 'Abschnitt',
|
|
guidance_notes: sec.guidance_notes?.trim() ? sec.guidance_notes.trim() : null,
|
|
items: (sec.items || [])
|
|
.map((it, ii) => {
|
|
if (it.item_type === 'note') {
|
|
return {
|
|
item_type: 'note',
|
|
order_index: ii,
|
|
note_body: it.note_body ?? '',
|
|
}
|
|
}
|
|
if (it.exercise_id === '' || it.exercise_id == null || Number.isNaN(Number(it.exercise_id))) {
|
|
return null
|
|
}
|
|
const vid = it.exercise_variant_id
|
|
return {
|
|
item_type: 'exercise',
|
|
order_index: ii,
|
|
exercise_id: parseInt(it.exercise_id, 10),
|
|
exercise_variant_id:
|
|
vid !== '' && vid != null && !Number.isNaN(Number(vid)) ? parseInt(vid, 10) : null,
|
|
planned_duration_min: parseMin(it.planned_duration_min),
|
|
actual_duration_min: parseMin(it.actual_duration_min),
|
|
notes: it.notes?.trim() ? it.notes.trim() : null,
|
|
modifications: it.modifications?.trim() ? it.modifications.trim() : null,
|
|
}
|
|
})
|
|
.filter(Boolean),
|
|
}))
|
|
}
|
|
|
|
export function sectionPlannedMinutes(sec) {
|
|
return (sec.items || []).reduce((sum, it) => {
|
|
if (it.item_type !== 'exercise') return sum
|
|
const m = parseMin(it.planned_duration_min)
|
|
return sum + (m || 0)
|
|
}, 0)
|
|
}
|