2026-05-05 13:39:30 +02:00
|
|
|
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: '',
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 14:47:38 +02:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-05 13:39:30 +02:00
|
|
|
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)
|
|
|
|
|
}
|