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).
|
||||
* Optional: geplante Variante (`variantId`) — Beschreibung und Durchführungsänderungen oben.
|
||||
*/
|
||||
import React from 'react'
|
||||
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) {
|
||||
return (
|
||||
<div style={{ textAlign: 'center', padding: '1rem' }}>
|
||||
|
|
@ -70,8 +71,41 @@ export default function ExerciseFullContent({ exercise, loading, error, exercise
|
|||
const resolvedId = exercise.id ?? exerciseId
|
||||
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 (
|
||||
<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>
|
||||
{meta.length > 0 && (
|
||||
<p className="exercise-meta-line" style={{ marginBottom: '10px', color: 'var(--text3)', fontSize: '0.86rem' }}>
|
||||
|
|
|
|||
|
|
@ -400,7 +400,7 @@ export default function TrainingCoachPage() {
|
|||
return () => {
|
||||
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 () => {
|
||||
setSaveOk(null)
|
||||
|
|
@ -739,6 +739,7 @@ export default function TrainingCoachPage() {
|
|||
error={catalogError}
|
||||
exercise={catalogExercise}
|
||||
exerciseId={currentEntry?.item?.exercise_id ?? null}
|
||||
variantId={currentEntry?.item?.exercise_variant_id ?? null}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default function TrainingUnitRunPage() {
|
|||
const [loadError, setLoadError] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [checked, setChecked] = useState(() => new Set())
|
||||
const [peekExerciseId, setPeekExerciseId] = useState(null)
|
||||
const [peekCtx, setPeekCtx] = useState(null)
|
||||
|
||||
const loadChecked = useCallback((uid) => {
|
||||
try {
|
||||
|
|
@ -143,9 +143,10 @@ export default function TrainingUnitRunPage() {
|
|||
return (
|
||||
<div className="training-run-page app-page" style={{ paddingBottom: '2rem' }}>
|
||||
<ExercisePeekModal
|
||||
open={peekExerciseId != null}
|
||||
exerciseId={peekExerciseId}
|
||||
onClose={() => setPeekExerciseId(null)}
|
||||
open={peekCtx != null}
|
||||
exerciseId={peekCtx?.exerciseId}
|
||||
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' }}>
|
||||
|
|
@ -314,7 +315,12 @@ export default function TrainingUnitRunPage() {
|
|||
type="button"
|
||||
className="btn btn-secondary"
|
||||
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)
|
||||
</button>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user