All checks were successful
Deploy Development / deploy (push) Successful in 33s
Test Suite / pytest-backend (push) Successful in 6s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 45s
- Added functionality to promote private exercises used in training units to club visibility, allowing better access for trainers and members. - Introduced helper functions to retrieve distinct exercise IDs and group club IDs for scheduled units. - Updated the create, update, and quick create training unit methods to include exercise promotion logic, enhancing exercise management within clubs.
235 lines
7.4 KiB
JavaScript
235 lines
7.4 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
|
|
let meta = {}
|
|
if (!variants.length) {
|
|
try {
|
|
const full = await api.getExercise(id)
|
|
variants = Array.isArray(full?.variants) ? full.variants : []
|
|
title = full?.title || title
|
|
meta = {
|
|
exercise_visibility: full?.visibility || 'private',
|
|
exercise_club_id: full?.club_id ?? null,
|
|
exercise_created_by: full?.created_by ?? null,
|
|
exercise_status: full?.status || 'draft',
|
|
}
|
|
} catch {
|
|
variants = []
|
|
}
|
|
} else {
|
|
meta = {
|
|
exercise_visibility: exercise?.visibility ?? null,
|
|
exercise_club_id: exercise?.club_id ?? null,
|
|
exercise_created_by: exercise?.created_by ?? null,
|
|
exercise_status: exercise?.status ?? null,
|
|
}
|
|
if (meta.exercise_visibility == null || meta.exercise_created_by == null) {
|
|
try {
|
|
const full = await api.getExercise(id)
|
|
if (meta.exercise_visibility == null) meta.exercise_visibility = full?.visibility || 'private'
|
|
if (meta.exercise_club_id == null) meta.exercise_club_id = full?.club_id ?? null
|
|
if (meta.exercise_created_by == null) meta.exercise_created_by = full?.created_by ?? null
|
|
if (meta.exercise_status == null) meta.exercise_status = full?.status || 'draft'
|
|
} catch {
|
|
/* keep partial meta */
|
|
}
|
|
}
|
|
meta.exercise_visibility = meta.exercise_visibility || 'private'
|
|
meta.exercise_status = meta.exercise_status || 'draft'
|
|
}
|
|
const row = exerciseRow()
|
|
row.exercise_id = id
|
|
row.exercise_variant_id = ''
|
|
row.exercise_title = title
|
|
row.variants = variants
|
|
Object.assign(row, meta)
|
|
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 : [],
|
|
visibility: ex.visibility || 'private',
|
|
club_id: ex.club_id ?? null,
|
|
created_by: ex.created_by ?? null,
|
|
status: ex.status || 'draft',
|
|
})
|
|
} catch {
|
|
cache.set(id, {
|
|
title: '',
|
|
variants: [],
|
|
visibility: 'private',
|
|
club_id: null,
|
|
created_by: null,
|
|
status: 'draft',
|
|
})
|
|
}
|
|
})
|
|
)
|
|
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,
|
|
exercise_visibility: c.visibility,
|
|
exercise_club_id: c.club_id,
|
|
exercise_created_by: c.created_by,
|
|
exercise_status: c.status,
|
|
}
|
|
}),
|
|
}))
|
|
}
|
|
|
|
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)
|
|
}
|