feat(exercise): add support for exercise variants in ExerciseFullContent and related components
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 55s
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 55s
- Updated ExerciseFullContent to accept a new `variantId` prop and display variant-specific information. - Enhanced TrainingCoachPage to pass `variantId` when rendering ExerciseFullContent. - Refactored TrainingUnitRunPage to manage exercise context with variant support, including updates to modal handling. - Improved UI to show variant details, including name, description, and execution changes where applicable.
This commit is contained in:
parent
1ce6d929ce
commit
fb8837574e
|
|
@ -1,5 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* Voller Katalog-Inhalt einer Übung (Lesemodus für Coach/Mobile).
|
* Voller Katalog-Inhalt einer Übung (Lesemodus für Coach/Mobile).
|
||||||
|
* Optional: geplante Variante (`variantId`) — Beschreibung und Durchführungsänderungen oben.
|
||||||
*/
|
*/
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
@ -51,9 +52,9 @@ function metaParts(exercise) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{ exercise?: object|null, loading?: boolean, error?: string|null, exerciseId?: number }} props
|
* @param {{ exercise?: object|null, loading?: boolean, error?: string|null, exerciseId?: number, variantId?: number|string|null }} props
|
||||||
*/
|
*/
|
||||||
export default function ExerciseFullContent({ exercise, loading, error, exerciseId }) {
|
export default function ExerciseFullContent({ exercise, loading, error, exerciseId, variantId }) {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'center', padding: '1rem' }}>
|
<div style={{ textAlign: 'center', padding: '1rem' }}>
|
||||||
|
|
@ -70,8 +71,41 @@ export default function ExerciseFullContent({ exercise, loading, error, exercise
|
||||||
const resolvedId = exercise.id ?? exerciseId
|
const resolvedId = exercise.id ?? exerciseId
|
||||||
const meta = metaParts(exercise)
|
const meta = metaParts(exercise)
|
||||||
|
|
||||||
|
const variant =
|
||||||
|
variantId != null && variantId !== '' && Array.isArray(exercise.variants) && exercise.variants.length
|
||||||
|
? exercise.variants.find((v) => String(v.id) === String(variantId)) || null
|
||||||
|
: null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="exercise-coach-catalog" style={{ fontSize: '0.93rem', lineHeight: 1.5 }}>
|
<div className="exercise-coach-catalog" style={{ fontSize: '0.93rem', lineHeight: 1.5 }}>
|
||||||
|
{variant ? (
|
||||||
|
<section
|
||||||
|
className="card"
|
||||||
|
style={{
|
||||||
|
marginBottom: '14px',
|
||||||
|
padding: '12px 14px',
|
||||||
|
borderLeft: '3px solid var(--accent)',
|
||||||
|
background: 'var(--surface2)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3 style={{ fontSize: '0.72rem', textTransform: 'uppercase', color: 'var(--text3)', margin: '0 0 6px', letterSpacing: '0.04em' }}>
|
||||||
|
Geplante Variante
|
||||||
|
</h3>
|
||||||
|
<p style={{ margin: '0 0 10px', fontSize: '1.05rem', fontWeight: 700 }}>{variant.variant_name || `Variante #${variant.id}`}</p>
|
||||||
|
{variant.description ? (
|
||||||
|
<div style={{ marginBottom: variant.execution_changes ? '12px' : 0 }}>
|
||||||
|
<h4 style={{ fontSize: '0.78rem', textTransform: 'uppercase', color: 'var(--text3)', marginBottom: '8px' }}>Zur Variante</h4>
|
||||||
|
<ExerciseRichTextBlock html={variant.description} exerciseId={resolvedId} media={exercise.media} />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{variant.execution_changes ? (
|
||||||
|
<div>
|
||||||
|
<h4 style={{ fontSize: '0.78rem', textTransform: 'uppercase', color: 'var(--text3)', marginBottom: '8px' }}>Durchführung (Variante)</h4>
|
||||||
|
<ExerciseRichTextBlock html={variant.execution_changes} exerciseId={resolvedId} media={exercise.media} />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
) : null}
|
||||||
<h2 style={{ margin: '0 0 8px', fontSize: '1.2rem', lineHeight: 1.35 }}>{exercise.title}</h2>
|
<h2 style={{ margin: '0 0 8px', fontSize: '1.2rem', lineHeight: 1.35 }}>{exercise.title}</h2>
|
||||||
{meta.length > 0 && (
|
{meta.length > 0 && (
|
||||||
<p className="exercise-meta-line" style={{ marginBottom: '10px', color: 'var(--text3)', fontSize: '0.86rem' }}>
|
<p className="exercise-meta-line" style={{ marginBottom: '10px', color: 'var(--text3)', fontSize: '0.86rem' }}>
|
||||||
|
|
|
||||||
|
|
@ -400,7 +400,7 @@ export default function TrainingCoachPage() {
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
}
|
}
|
||||||
}, [step, currentEntry?.item?.exercise_id, currentEntry?.item?.item_type])
|
}, [step, currentEntry?.item?.exercise_id, currentEntry?.item?.exercise_variant_id, currentEntry?.item?.item_type])
|
||||||
|
|
||||||
const handleSaveDebrief = async () => {
|
const handleSaveDebrief = async () => {
|
||||||
setSaveOk(null)
|
setSaveOk(null)
|
||||||
|
|
@ -739,6 +739,7 @@ export default function TrainingCoachPage() {
|
||||||
error={catalogError}
|
error={catalogError}
|
||||||
exercise={catalogExercise}
|
exercise={catalogExercise}
|
||||||
exerciseId={currentEntry?.item?.exercise_id ?? null}
|
exerciseId={currentEntry?.item?.exercise_id ?? null}
|
||||||
|
variantId={currentEntry?.item?.exercise_variant_id ?? null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export default function TrainingUnitRunPage() {
|
||||||
const [loadError, setLoadError] = useState(null)
|
const [loadError, setLoadError] = useState(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [checked, setChecked] = useState(() => new Set())
|
const [checked, setChecked] = useState(() => new Set())
|
||||||
const [peekExerciseId, setPeekExerciseId] = useState(null)
|
const [peekCtx, setPeekCtx] = useState(null)
|
||||||
|
|
||||||
const loadChecked = useCallback((uid) => {
|
const loadChecked = useCallback((uid) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -143,9 +143,10 @@ export default function TrainingUnitRunPage() {
|
||||||
return (
|
return (
|
||||||
<div className="training-run-page app-page" style={{ paddingBottom: '2rem' }}>
|
<div className="training-run-page app-page" style={{ paddingBottom: '2rem' }}>
|
||||||
<ExercisePeekModal
|
<ExercisePeekModal
|
||||||
open={peekExerciseId != null}
|
open={peekCtx != null}
|
||||||
exerciseId={peekExerciseId}
|
exerciseId={peekCtx?.exerciseId}
|
||||||
onClose={() => setPeekExerciseId(null)}
|
variantId={peekCtx?.variantId ?? undefined}
|
||||||
|
onClose={() => setPeekCtx(null)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<nav className="training-run-toolbar no-print" style={{ marginBottom: '1rem', display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'center' }}>
|
<nav className="training-run-toolbar no-print" style={{ marginBottom: '1rem', display: 'flex', gap: '0.5rem', flexWrap: 'wrap', alignItems: 'center' }}>
|
||||||
|
|
@ -314,7 +315,12 @@ export default function TrainingUnitRunPage() {
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-secondary"
|
className="btn btn-secondary"
|
||||||
style={{ fontSize: '0.82rem', margin: 0 }}
|
style={{ fontSize: '0.82rem', margin: 0 }}
|
||||||
onClick={() => setPeekExerciseId(it.exercise_id)}
|
onClick={() =>
|
||||||
|
setPeekCtx({
|
||||||
|
exerciseId: it.exercise_id,
|
||||||
|
variantId: it.exercise_variant_id != null ? Number(it.exercise_variant_id) : null,
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Katalog (Popup)
|
Katalog (Popup)
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user