import React, { useEffect, useState } from 'react'
import { Link, useNavigate, useParams } from 'react-router-dom'
import api from '../utils/api'
import { sanitizeTrainerHtml } from '../utils/htmlUtils'
import { formatSkillLevelSlug } from '../constants/skillLevels'
const API_BASE = (import.meta.env.VITE_API_URL || '').replace(/\/$/, '')
function resolveMediaUrl(filePath) {
if (!filePath) return null
if (filePath.startsWith('http://') || filePath.startsWith('https://')) return filePath
const p = filePath.startsWith('/') ? filePath : `/${filePath}`
return `${API_BASE}${p}`
}
function HtmlBlock({ html, className = '' }) {
if (!html || !String(html).trim()) return null
const safe = sanitizeTrainerHtml(html)
return (
)
}
function MediaBlock({ media }) {
if (media.embed_url) {
return (
)
}
const src = resolveMediaUrl(media.file_path)
if (!src) return null
if (media.media_type === 'image' || (media.mime_type && media.mime_type.startsWith('image/'))) {
return (
)
}
if (media.media_type === 'video' || (media.mime_type && media.mime_type.startsWith('video/'))) {
return
}
return (
{media.title || media.original_filename || 'Datei öffnen'}
)
}
function TagRow({ exercise }) {
const tags = []
;(exercise.focus_areas || []).forEach((f) => {
tags.push({ key: `fa-${f.id}`, label: f.name, accent: !!f.is_primary })
})
;(exercise.training_styles || []).forEach((t) => {
tags.push({ key: `ts-${t.id}`, label: t.name, accent: false })
})
;(exercise.training_types || []).forEach((t) => {
tags.push({ key: `tt-${t.id}`, label: t.name, accent: false })
})
;(exercise.target_groups || []).forEach((g) => {
tags.push({ key: `tg-${g.id}`, label: g.name, accent: !!g.is_primary })
})
if (tags.length === 0) return null
return (
{tags.map((t) => (
{t.label}
))}
)
}
function metaParts(exercise) {
const parts = []
if (exercise.duration_min != null || exercise.duration_max != null) {
const a = exercise.duration_min
const b = exercise.duration_max
if (a != null && b != null && a !== b) parts.push(`${a}–${b} Min.`)
else if (a != null) parts.push(`ca. ${a} Min.`)
else if (b != null) parts.push(`ca. ${b} Min.`)
}
if (exercise.group_size_min != null || exercise.group_size_max != null) {
const a = exercise.group_size_min
const b = exercise.group_size_max
if (a != null && b != null && a !== b) parts.push(`Gruppe ${a}–${b}`)
else if (a != null) parts.push(`Gruppe ab ${a}`)
else if (b != null) parts.push(`Gruppe bis ${b}`)
}
return parts
}
function ExerciseDetailPage() {
const { id } = useParams()
const navigate = useNavigate()
const [exercise, setExercise] = useState(null)
const [error, setError] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
let cancelled = false
const load = async () => {
setLoading(true)
setError(null)
try {
const data = await api.getExercise(id)
if (!cancelled) setExercise(data)
} catch (err) {
if (!cancelled) setError(err)
} finally {
if (!cancelled) setLoading(false)
}
}
if (id) load()
return () => {
cancelled = true
}
}, [id])
if (loading) {
return (
)
}
if (error) {
const msg = error.message || String(error)
return (
Übung
{msg}
)
}
if (!exercise) return null
const meta = metaParts(exercise)
return (
Bearbeiten
{exercise.title}
{exercise.summary && (
)}
{exercise.visibility}
{exercise.status}
{exercise.club_name && {exercise.club_name}}
{meta.length > 0 &&
{meta.join(' · ')}
}
{exercise.goal && (
)}
{(exercise.equipment || []).length > 0 && (
Material & Aufbau
{exercise.equipment.map((x, i) => (
- {x}
))}
)}
{exercise.preparation && (
)}
{exercise.execution && (
)}
{(exercise.media || []).length > 0 && (
Medien
{exercise.media.map((m) => (
{m.title || m.original_filename || m.media_type}
{m.description &&
{m.description}
}
))}
)}
{exercise.trainer_notes && (
)}
{(exercise.skills || []).length > 0 && (
Fähigkeiten
{exercise.skills.map((s) => {
const rl = formatSkillLevelSlug(s.required_level)
const tl = formatSkillLevelSlug(s.target_level)
const lvl =
rl || tl ? ` (${[rl, tl].filter(Boolean).join(' → ')})` : ''
return (
{s.skill_name}
{s.intensity ? ` · ${s.intensity}` : ''}
{lvl}
)
})}
)}
{(exercise.variants || []).length > 0 && (
Varianten
{exercise.variants.map((v) => {
const dur =
v.duration_min != null || v.duration_max != null
? v.duration_min != null && v.duration_max != null && v.duration_min !== v.duration_max
? `${v.duration_min}–${v.duration_max} Min.`
: v.duration_min != null
? `ca. ${v.duration_min} Min.`
: `bis ca. ${v.duration_max} Min.`
: null
const diffLabel =
v.difficulty_adjustment === 'easier'
? 'einfacher'
: v.difficulty_adjustment === 'harder'
? 'schwerer'
: v.difficulty_adjustment === 'same'
? 'gleiche Schwierigkeit'
: v.difficulty_adjustment === 'adapted'
? 'angepasst'
: null
const equip =
Array.isArray(v.equipment_changes) && v.equipment_changes.length > 0
? v.equipment_changes.join(', ')
: null
return (
{v.variant_name}
{(dur || diffLabel || equip || v.progression_level != null) && (
{[dur, diffLabel, equip && `Material: ${equip}`, v.progression_level != null && `Progression ${v.progression_level}`]
.filter(Boolean)
.join(' · ')}
)}
{v.description &&
{v.description}
}
{v.execution_changes && (
)}
)
})}
)}
)
}
export default ExerciseDetailPage