/** * Schnellansicht einer Übung aus dem Katalog (ohne die Planungsseite zu verlassen). * Unterstützt Drill-down zu Kandidaten-Übungen bei Kombinationen inkl. „Zurück“ (PWA-sicher). */ import React, { useEffect, useMemo, useRef, useState } from 'react' import { Link } from 'react-router-dom' import api from '../utils/api' import ExerciseRichTextBlock from './ExerciseRichTextBlock' import CombinationPlanBracket from './CombinationPlanBracket' import { effectiveComboMethodProfile } from '../utils/comboPlanningMethodProfile' function TagMini({ exercise }) { const parts = [] ;(exercise.focus_areas || []).slice(0, 5).forEach((f) => { parts.push(f.name) }) if (parts.length === 0) return null return (
{parts.map((p, i) => ( {p} ))}
) } /** @typedef {{ exerciseId: number, variantId?: number | null, peekExtras?: object | null }} PeekStackEntry */ export default function ExercisePeekModal({ open, exerciseId, variantId, onClose, titleFallback, /** Nur Planung: effektives method_profile aus Zeilen-Katalog + Planungs-Override */ peekExtras, }) { const [loading, setLoading] = useState(false) const [err, setErr] = useState(null) const [exercise, setExercise] = useState(null) /** @type {[PeekStackEntry[], React.Dispatch>]} */ const [stack, setStack] = useState([]) /** @type {React.MutableRefObject} */ const wasOpenRef = useRef(false) useEffect(() => { if (!open) { setStack([]) wasOpenRef.current = false return } if (exerciseId == null || exerciseId === '') return if (!wasOpenRef.current) { wasOpenRef.current = true setStack([ { exerciseId: Number(exerciseId), variantId: variantId ?? null, peekExtras: peekExtras ?? null, }, ]) } }, [open, exerciseId, variantId, peekExtras]) const top = stack.length ? stack[stack.length - 1] : null useEffect(() => { if (!open || !top?.exerciseId) { setExercise(null) setErr(null) return } let cancelled = false ;(async () => { setLoading(true) setErr(null) try { const data = await api.getExercise(top.exerciseId) if (!cancelled) setExercise(data) } catch (e) { if (!cancelled) setErr(e.message || 'Laden fehlgeschlagen') } finally { if (!cancelled) setLoading(false) } })() return () => { cancelled = true } }, [open, top?.exerciseId]) const variant = top?.variantId != null && top.variantId !== '' && exercise?.variants?.length ? exercise.variants.find((v) => String(v.id) === String(top.variantId)) || null : null const isCombination = exercise && String(exercise.exercise_kind || 'simple').toLowerCase().trim() === 'combination' const comboMethodProfileEffective = useMemo(() => { if (!exercise || !isCombination) return {} const fromPeek = top?.peekExtras?.catalog_method_profile && typeof top.peekExtras.catalog_method_profile === 'object' && !Array.isArray(top.peekExtras.catalog_method_profile) && Object.keys(top.peekExtras.catalog_method_profile).length > 0 ? top.peekExtras.catalog_method_profile : exercise.method_profile || {} return effectiveComboMethodProfile(fromPeek, top?.peekExtras?.planning_method_profile ?? null) }, [exercise, isCombination, top?.peekExtras]) const planningAdjustedBadge = top?.peekExtras?.planning_method_profile != null && typeof top.peekExtras.planning_method_profile === 'object' && !Array.isArray(top.peekExtras.planning_method_profile) const pushCandidatePeek = (id) => { const n = Number(id) if (!Number.isFinite(n)) return setStack((s) => [...s, { exerciseId: n, variantId: null, peekExtras: null }]) } if (!open) return null const sheetWide = Boolean(isCombination && exercise && !loading) return (
e.target === e.currentTarget && onClose()}>
e.stopPropagation()} >
{stack.length > 1 ? ( ) : null}

{loading ? '…' : exercise?.title || titleFallback || (top?.exerciseId != null ? `Übung #${top.exerciseId}` : 'Übung')}

{loading && (

Laden…

)} {!loading && err &&

{err}

} {!loading && exercise && ( <> {isCombination ? ( <>
) : null} {variant ? (
Variante
{variant.variant_name || `Variante #${variant.id}`}
{variant.description ? (
) : null} {variant.execution_changes ? (

Durchführung (Variante)

) : null}
) : null} {exercise.summary && (
)} {(exercise.goal || exercise.preparation || exercise.execution || exercise.trainer_notes) && (
)} {exercise.goal && ( <>

Ziel

)} {exercise.preparation && ( <>

Vorbereitung

)} {exercise.execution && ( <>

Ablauf

)} {exercise.trainer_notes && ( <>

Trainer-Hinweise

)} )}
{top?.exerciseId != null ? (
Vollständige Übungsseite öffnen
) : null}
) }