import React, { useEffect, useState } from 'react' import { useNavigate, useParams } from 'react-router-dom' import api, { buildExerciseApiPayload } from '../utils/api' const INTENSITY_OPTIONS = [ { value: '', label: '—' }, { value: 'niedrig', label: 'niedrig' }, { value: 'mittel', label: 'mittel' }, { value: 'hoch', label: 'hoch' }, ] const LEVEL_OPTIONS = [ { value: '', label: '—' }, { value: 'einsteiger', label: 'Einsteiger' }, { value: 'grundlagen', label: 'Grundlagen' }, { value: 'aufbau', label: 'Aufbau' }, { value: 'fortgeschritten', label: 'Fortgeschritten' }, { value: 'experte', label: 'Experte' }, ] function emptyForm() { return { title: '', summary: '', goal: '', execution: '', preparation: '', trainer_notes: '', equipment: [], duration_min: '', duration_max: '', group_size_min: '', group_size_max: '', age_groups: [], focus_area_id: null, training_style_id: null, visibility: 'private', status: 'draft', skills: [], } } function detailToForm(exercise) { const primaryFa = exercise.focus_areas?.find((f) => f.is_primary) || exercise.focus_areas?.[0] const primaryTs = exercise.training_styles?.find((t) => t.is_primary) || exercise.training_styles?.[0] return { title: exercise.title || '', summary: exercise.summary || '', goal: exercise.goal || '', execution: exercise.execution || '', preparation: exercise.preparation || '', trainer_notes: exercise.trainer_notes || '', equipment: exercise.equipment || [], duration_min: exercise.duration_min ?? '', duration_max: exercise.duration_max ?? '', group_size_min: exercise.group_size_min ?? '', group_size_max: exercise.group_size_max ?? '', age_groups: exercise.age_groups || [], focus_area_id: primaryFa?.focus_area_id ?? null, training_style_id: primaryTs?.training_style_id ?? null, visibility: exercise.visibility || 'private', status: exercise.status || 'draft', skills: exercise.skills?.map((s) => ({ skill_id: s.skill_id, is_primary: s.is_primary || false, intensity: s.intensity || '', required_level: s.required_level || '', target_level: s.target_level || '', })) || [], } } function ExerciseFormPage() { const { id: routeId } = useParams() const navigate = useNavigate() const exerciseId = routeId && !Number.isNaN(parseInt(routeId, 10)) ? parseInt(routeId, 10) : null const isEdit = exerciseId != null const [formData, setFormData] = useState(emptyForm) const [skillsCatalog, setSkillsCatalog] = useState([]) const [focusAreas, setFocusAreas] = useState([]) const [trainingStyles, setTrainingStyles] = useState([]) const [mediaList, setMediaList] = useState([]) const [loading, setLoading] = useState(!!isEdit) const [saving, setSaving] = useState(false) const [mediaFile, setMediaFile] = useState(null) const [mediaType, setMediaType] = useState('image') const [mediaTitle, setMediaTitle] = useState('') const [mediaContext, setMediaContext] = useState('ablauf') const [embedUrl, setEmbedUrl] = useState('') const [embedTitle, setEmbedTitle] = useState('') useEffect(() => { let cancelled = false const boot = async () => { try { const [skillsData, faData, tsData] = await Promise.all([ api.listSkills(), api.listFocusAreas(), api.listTrainingStyles(), ]) if (cancelled) return setSkillsCatalog(skillsData) setFocusAreas(faData) setTrainingStyles(tsData) } catch (e) { if (!cancelled) console.error(e) } } boot() return () => { cancelled = true } }, []) useEffect(() => { if (!isEdit) { setFormData(emptyForm()) setMediaList([]) setLoading(false) return } let cancelled = false const load = async () => { setLoading(true) try { const exercise = await api.getExercise(exerciseId) if (cancelled) return setFormData(detailToForm(exercise)) setMediaList(exercise.media || []) } catch (err) { if (!cancelled) { alert(err.message || 'Übung nicht ladbar') navigate('/exercises') } } finally { if (!cancelled) setLoading(false) } } load() return () => { cancelled = true } }, [isEdit, exerciseId, navigate]) const updateFormField = (field, value) => { setFormData((prev) => ({ ...prev, [field]: value })) } const toggleSkill = (skillId) => { const existing = formData.skills.find((s) => s.skill_id === skillId) if (existing) { updateFormField( 'skills', formData.skills.filter((s) => s.skill_id !== skillId), ) } else { updateFormField('skills', [ ...formData.skills, { skill_id: skillId, is_primary: false, intensity: '', required_level: '', target_level: '', }, ]) } } const updateSkillField = (skillId, field, value) => { updateFormField( 'skills', formData.skills.map((s) => (s.skill_id === skillId ? { ...s, [field]: value } : s)), ) } const handleSubmit = async (e) => { e.preventDefault() if (!formData.title || formData.title.trim().length < 3) { alert('Titel mindestens 3 Zeichen') return } let payload try { payload = buildExerciseApiPayload(formData) } catch (err) { alert(err.message) return } setSaving(true) try { if (isEdit) { await api.updateExercise(exerciseId, payload) const ex = await api.getExercise(exerciseId) setMediaList(ex.media || []) alert('Gespeichert.') } else { const created = await api.createExercise(payload) navigate(`/exercises/${created.id}/edit`, { replace: true }) } } catch (err) { alert('Fehler beim Speichern: ' + err.message) } finally { setSaving(false) } } const refreshMedia = async () => { if (!exerciseId) return const ex = await api.getExercise(exerciseId) setMediaList(ex.media || []) } const handleUploadFile = async () => { if (!exerciseId || !mediaFile) { alert('Datei wählen') return } const fd = new FormData() fd.append('file', mediaFile) fd.append('media_type', mediaType) fd.append('title', mediaTitle) fd.append('description', '') fd.append('context', mediaContext) fd.append('is_primary', 'false') try { await api.uploadExerciseMedia(exerciseId, fd) setMediaFile(null) setMediaTitle('') await refreshMedia() } catch (err) { alert('Upload: ' + err.message) } } const handleAddEmbed = async () => { if (!exerciseId || !embedUrl.trim()) { alert('Embed-URL eingeben') return } const fd = new FormData() fd.append('embed_url', embedUrl.trim()) fd.append('media_type', 'video') fd.append('title', embedTitle) fd.append('description', '') fd.append('context', mediaContext) fd.append('is_primary', 'false') try { await api.uploadExerciseMedia(exerciseId, fd) setEmbedUrl('') setEmbedTitle('') await refreshMedia() } catch (err) { alert('Embed: ' + err.message) } } const handleDeleteMedia = async (mid) => { if (!confirm('Medium löschen?')) return try { await api.deleteExerciseMedia(exerciseId, mid) await refreshMedia() } catch (err) { alert(err.message) } } if (loading) { return (
Laden...
Datei (JPEG/PNG/GIF/MP4/PDF) oder Embed-URL (YouTube, Vimeo, Instagram, TikTok). Max. 10 Medien pro Übung.