/** * Trainingsablauf anzeigen, drucken und lokal auf der Matte abhaken (Fortschritt im Browser gespeichert). */ import React, { useCallback, useEffect, useMemo, useState } from 'react' import { Link, useNavigate, useParams } from 'react-router-dom' import api from '../utils/api' import ExercisePeekModal from '../components/ExercisePeekModal' import { itemStableKey, sortedSections, sortedItems } from '../utils/trainingPlanUtils' function storageKey(unitId) { return `sj_training_run_checked_${unitId}` } function formatMin(m) { if (m === null || m === undefined || m === '') return null const n = Number(m) if (!Number.isFinite(n)) return null return `${n} Min.` } function statusLabel(s) { if (s === 'completed') return 'Durchgeführt' if (s === 'cancelled') return 'Abgesagt' return 'Geplant' } export default function TrainingUnitRunPage() { const { unitId } = useParams() const navigate = useNavigate() const idNum = unitId ? parseInt(unitId, 10) : NaN const [unit, setUnit] = useState(null) const [loadError, setLoadError] = useState(null) const [loading, setLoading] = useState(true) const [checked, setChecked] = useState(() => new Set()) const [peekExerciseId, setPeekExerciseId] = useState(null) const loadChecked = useCallback((uid) => { try { const raw = sessionStorage.getItem(storageKey(uid)) if (!raw) return new Set() const arr = JSON.parse(raw) if (!Array.isArray(arr)) return new Set() return new Set(arr.map(String)) } catch { return new Set() } }, []) useEffect(() => { if (!unitId || Number.isNaN(idNum)) { setLoadError('Ungültige Trainingseinheit') setLoading(false) return } let cancelled = false ;(async () => { setLoading(true) setLoadError(null) try { const u = await api.getTrainingUnit(idNum) if (!cancelled) { setUnit(u) setChecked(loadChecked(idNum)) } } catch (e) { if (!cancelled) setLoadError(e.message || 'Laden fehlgeschlagen') } finally { if (!cancelled) setLoading(false) } })() return () => { cancelled = true } }, [unitId, idNum, loadChecked]) const persistChecked = useCallback( (next) => { setChecked(next) try { sessionStorage.setItem(storageKey(idNum), JSON.stringify([...next])) } catch { /* ignore quota */ } }, [idNum] ) const toggle = useCallback( (key) => { const next = new Set(checked) if (next.has(key)) next.delete(key) else next.add(key) persistChecked(next) }, [checked, persistChecked] ) const clearProgress = useCallback(() => { persistChecked(new Set()) try { sessionStorage.removeItem(storageKey(idNum)) } catch { /* ignore */ } }, [idNum, persistChecked]) const sections = useMemo(() => sortedSections(unit), [unit]) const totalPlannedMin = useMemo(() => { let t = 0 for (const sec of sections) { for (const it of sortedItems(sec)) { if (it.item_type === 'exercise' && it.planned_duration_min != null) { const n = Number(it.planned_duration_min) if (Number.isFinite(n)) t += n } } } return t }, [sections]) if (loading) { return (
Plan wird geladen…
{loadError || 'Trainingseinheit nicht gefunden.'}
Noch keine Abschnitte in diesem Plan. Unter Planung bearbeiten.
) : ({sec.guidance_notes}
)}{unit.trainer_notes}