diff --git a/frontend/src/app.css b/frontend/src/app.css index fdb4013..52ecbf4 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -2740,3 +2740,27 @@ a.analysis-split__nav-item { page-break-inside: avoid; } } + +/* Coach — volle Übung, Nur-Mittelbereich scrollt; Steuerung oben/unten sichtbar */ +.training-coach-layout { + display: flex; + flex-direction: column; + width: 100%; + max-width: 720px; + margin: 0 auto; + min-height: calc(100dvh - var(--header-h) - var(--nav-h) - env(safe-area-inset-bottom, 0px) - 48px); +} + +@media (min-width: 1024px) { + .training-coach-layout { + min-height: calc(100dvh - env(safe-area-inset-bottom, 0px) - 24px); + } +} + +.training-coach-scroll { + flex: 1; + min-height: 0; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + padding-bottom: 4px; +} diff --git a/frontend/src/components/ExerciseFullContent.jsx b/frontend/src/components/ExerciseFullContent.jsx new file mode 100644 index 0000000..76c441c --- /dev/null +++ b/frontend/src/components/ExerciseFullContent.jsx @@ -0,0 +1,203 @@ +/** + * Voller Katalog-Inhalt einer Übung (Lesemodus für Coach/Mobile). + */ +import React from 'react' +import { Link } from 'react-router-dom' +import { sanitizeTrainerHtml } from '../utils/htmlUtils' + +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 ( +Übung aus Katalog laden…
+{error}
+ } + if (!exercise) return null + + const meta = metaParts(exercise) + + return ( ++ {meta.join(' · ')} +
+ )} +{m.description}
} ++ + Volle Übungsseite im Browser + +
+ )} +- Dieser Plan ist leer.{' '} - Unter Planung ergänzen. +
+ Dieser Plan ist leer. Unter Planung ergänzen.
) : ( <> -+
Nächste: {summarizeTimelineEntry(nextEntry)}
{next2Entry && ( -+
Daraufhin: {summarizeTimelineEntry(next2Entry)}
)} > ) : ( -
- Dies war der letzte Eintrag.
-
- Gute Arbeit — unten kannst du notieren und Ist-Zeiten speichern.
+
+ Letzter Punkt — unten Zeit speichern / Nachbereitung öffnen.
)}{currentEntry.item.note_body || ''}
+{currentEntry.item.note_body || ''}
++ {currentEntry.item.exercise_title || + (currentEntry.item.exercise_id ? `Übung #${currentEntry.item.exercise_id}` : 'Übung')} + {currentEntry.item.exercise_variant_name ? ( + ({currentEntry.item.exercise_variant_name}) + ) : null} +
++ geplant {currentEntry.item.planned_duration_min ?? '—'} Min · Ist (Plan):{' '} + + {durationOverridesForApi[String(currentEntry.item.id)] != null + ? durationOverridesForApi[String(currentEntry.item.id)].actual_duration_min + : currentEntry.item.actual_duration_min ?? '—'}{' '} + Min + {' '} + {durationOverridesForApi[String(currentEntry.item.id)] != null ? '(Entwurf)' : ''} +
+ {currentEntry.item.exercise_focus_area ? ( +{currentEntry.item.exercise_focus_area}
+ ) : null} + {currentEntry.item.notes ? ( +{currentEntry.item.notes}
+{currentEntry.item.modifications}
+{unit.trainer_notes}
+- geplant {currentEntry.item.planned_duration_min ?? '—'} Min · Ist (Plan/Bearbeitung):{' '} - - {durationOverridesForApi[String(currentEntry.item.id)] != null - ? durationOverridesForApi[String(currentEntry.item.id)].actual_duration_min - : currentEntry.item.actual_duration_min ?? '—'}{' '} - Min - - {durationOverridesForApi[String(currentEntry.item.id)] != null ? ' (Entwurf)' : ''} +
+ Ist‑Minuten anpassen und Einheit speichern (inkl. Trainer-Ergänzung).
- {currentEntry.item.exercise_focus_area && ( -- Bereich: {currentEntry.item.exercise_focus_area} +
+ {saveOk}
)} - {currentEntry.item.notes ? ( -{currentEntry.item.notes}
-{currentEntry.item.modifications}
-- Übernimmt die gemessene Zeit (auf volle Minuten gerundet) als Ist‑Minuten für dieses Element und kann später mit „Nachbereitung“ auf dem Server gespeichert werden. +
+ Übernimmt Ist‑Minuten für diesen Platz (gerundet).
-{unit.trainer_notes}
-- Übertragene Ist‑Minuten (Entwürfe aus dem Timer oder hier anpassen). Beim Speichern werden sie mit dem Plan an den Server geschickt. -
-- {saveOk} -
- )} -