From 6fd316e985d1c6ee333f2414baa891fa7a8919bb Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 29 Apr 2026 07:58:28 +0200 Subject: [PATCH] feat: enhance TrainingCoachPage with exercise details and navigation - Replaced ExercisePeekModal with ExerciseFullContent for improved exercise visibility. - Added CoachStepNavBar component for better navigation between training steps. - Implemented loading and error handling for fetching exercise details based on the current step. - Updated UI elements for a more cohesive layout and improved user experience. --- frontend/src/app.css | 24 + .../src/components/ExerciseFullContent.jsx | 203 ++++++++ frontend/src/pages/TrainingCoachPage.jsx | 451 ++++++++++-------- 3 files changed, 487 insertions(+), 191 deletions(-) create mode 100644 frontend/src/components/ExerciseFullContent.jsx 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 ( +
+ + {media.embed_url} + + {media.embed_platform && ( + + ({media.embed_platform}) + + )} +
+ ) + } + const src = resolveMediaUrl(media.file_path) + if (!src) return null + if (media.media_type === 'image' || (media.mime_type && media.mime_type.startsWith('image/'))) { + return ( + {media.title + ) + } + if (media.media_type === 'video' || (media.mime_type && media.mime_type.startsWith('video/'))) { + return