import React, { useEffect, useState, useRef, useMemo } from 'react' import { useNavigate, useParams, Link } from 'react-router-dom' import api, { buildExerciseApiPayload } from '../utils/api' import { resolveExerciseMediaFileUrl, resolveMediaAssetFileUrl } from '../utils/exerciseMediaUrl' import RichTextEditor from '../components/RichTextEditor' import ExerciseProgressionGraphPanel from '../components/ExerciseProgressionGraphPanel' import { SKILL_LEVEL_OPTIONS, normalizeSkillLevelSlug } from '../constants/skillLevels' import { useAuth } from '../context/AuthContext' /** MIME/Dateiname → Übungs-media_type; null → Dropdown-Fallback. */ function inferExerciseMediaType(file) { if (!file) return null const mime = (file.type || '').toLowerCase() if (mime.startsWith('image/')) return 'image' if (mime.startsWith('video/')) return 'video' if (mime === 'application/pdf' || mime.includes('pdf')) return 'document' const name = (file.name || '').toLowerCase() if (/\.(mp4|webm|mov|mkv|avi|m4v|mpeg|mpg)$/.test(name)) return 'video' if (/\.(jpg|jpeg|png|gif|webp|bmp|svg)$/.test(name)) return 'image' if (/\.pdf$/.test(name)) return 'document' return null } /** Kachelvorschau: Video nutzt ersten Frame (metadata), Bild = img. */ function ExerciseMediaThumbTile({ exerciseId, media, onOpenPreview }) { const src = !media.embed_url ? resolveExerciseMediaFileUrl(exerciseId, media) : null const commonStyle = { width: '100%', height: '100%', objectFit: 'cover', } return (
onOpenPreview(media)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault() onOpenPreview(media) } }} style={{ width: 72, height: 72, flexShrink: 0, borderRadius: '8px', overflow: 'hidden', background: 'var(--surface2, rgba(127,127,127,0.12))', border: '1px solid var(--border)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', }} > {media.embed_url ? ( {media.embed_platform || 'Embed'} ) : (media.mime_type?.startsWith('image/') || media.media_type === 'image') && src ? ( ) : (media.mime_type?.startsWith('video/') || media.media_type === 'video') && src ? (
) } const INTENSITY_OPTIONS = [ { value: '', label: '—' }, { value: 'niedrig', label: 'niedrig' }, { value: 'mittel', label: 'mittel' }, { value: 'hoch', label: 'hoch' }, ] const VARIANT_DIFFICULTY = [ { value: '', label: '—' }, { value: 'easier', label: 'Einfacher' }, { value: 'same', label: 'Gleich' }, { value: 'harder', label: 'Schwerer' }, { value: 'adapted', label: 'Angepasst' }, ] function emptyVariantDraft() { return { variant_name: '', description: '', execution_changes: '', duration_min: '', duration_max: '', equipment_lines: '', difficulty_adjustment: '', progression_level: 1, prerequisite_variant_id: '', } } function apiVariantToRow(v) { let lines = '' const eq = v.equipment_changes if (Array.isArray(eq)) { lines = eq.join('\n') } else if (typeof eq === 'string' && eq.trim()) { try { const p = JSON.parse(eq) lines = Array.isArray(p) ? p.join('\n') : eq } catch { lines = eq } } return { ...v, duration_min: v.duration_min ?? '', duration_max: v.duration_max ?? '', equipment_lines: lines, progression_level: v.progression_level ?? 1, prerequisite_variant_id: v.prerequisite_variant_id ?? '', difficulty_adjustment: v.difficulty_adjustment ?? '', } } function buildVariantPayloadFromRow(row) { const lines = (row.equipment_lines || '') .split(/[\n,]+/) .map((s) => s.trim()) .filter(Boolean) const pl = row.progression_level === '' || row.progression_level == null ? 1 : parseInt(row.progression_level, 10) const so = row.sequence_order === '' || row.sequence_order == null ? null : parseInt(row.sequence_order, 10) return { variant_name: (row.variant_name || '').trim(), description: (row.description || '').trim() || null, execution_changes: (row.execution_changes || '').trim() || null, duration_min: row.duration_min === '' || row.duration_min == null ? null : parseInt(row.duration_min, 10), duration_max: row.duration_max === '' || row.duration_max == null ? null : parseInt(row.duration_max, 10), equipment_changes: lines, difficulty_adjustment: row.difficulty_adjustment || null, progression_level: Number.isNaN(pl) ? 1 : pl, sequence_order: so !== null && Number.isNaN(so) ? null : so, prerequisite_variant_id: row.prerequisite_variant_id === '' || row.prerequisite_variant_id == null ? null : parseInt(row.prerequisite_variant_id, 10), } } /** Gemeinsame Felder für „Variante bearbeiten“ und „Neue Variante“. */ function ExerciseVariantFields({ row, onPatch, prerequisiteOthers, rteMinHeight = '110px', exerciseMediaInsertSlots }) { return ( <>
onPatch({ variant_name: e.target.value })} minLength={3} />