fix: add missing Workout fields to program workouts and guard against undefined level

Program workouts built by buildProgramWorkouts() were missing level,
rounds, calories, and other Workout-interface fields, causing
workout.level.toLowerCase() to crash on the detail, collection, and
category screens. Added derived defaults (level from week number,
category from program id, standard Tabata timings) and defensive
fallbacks with ?? 'Beginner' at all call sites. Also fixed a potential
division-by-zero when exercises array is empty.
This commit is contained in:
Millian Lamiaux
2026-03-25 23:28:47 +01:00
parent 4fa8be600c
commit f11eb6b9ae
4 changed files with 78 additions and 59 deletions

View File

@@ -5,6 +5,7 @@
*/
import { Program, Assessment, ProgramId, WeekNumber, Week } from '../types/program'
import type { WorkoutLevel, WorkoutCategory, MusicVibe } from '../types/workout'
type ProgramWorkoutInput = {
id: string
@@ -18,6 +19,13 @@ type ProgramWorkoutInput = {
tips: string[]
}
/** Derive difficulty level from the week number in a progressive program */
function getLevelFromWeek(week: number): WorkoutLevel {
if (week <= 2) return 'Beginner'
if (week === 3) return 'Intermediate'
return 'Advanced'
}
// Helper to create exercises with consistent structure
const createExercise = (name: string, modification?: string, progression?: string) => ({
name,
@@ -1571,13 +1579,24 @@ export const ASSESSMENT_WORKOUT: Assessment = {
// PROGRAM BUILDER
// ═══════════════════════════════════════════════════════════════════════════
function buildProgramWorkouts(inputs: ProgramWorkoutInput[]): any[] {
function buildProgramWorkouts(inputs: ProgramWorkoutInput[], category: WorkoutCategory): any[] {
return inputs.map((input) => ({
...input,
exercises: input.exercises.map((name) =>
createExercise(name)
),
duration: 4,
duration: 4 as const,
// Workout-compatible fields so screens don't crash
level: getLevelFromWeek(input.week),
category,
trainerId: '',
calories: 50,
rounds: input.exercises.length,
prepTime: 10,
workTime: 20,
restTime: 10,
musicVibe: 'electronic' as MusicVibe,
isFeatured: false,
}))
}
@@ -1661,7 +1680,7 @@ export const PROGRAMS: Record<ProgramId, Program> = {
optional: ['Wall', 'Elevated surface'],
},
focusAreas: ['Shoulders', 'Chest', 'Back', 'Arms', 'Posture'],
weeks: buildWeeks(buildProgramWorkouts(upperBodyWorkouts)),
weeks: buildWeeks(buildProgramWorkouts(upperBodyWorkouts, 'upper-body')),
},
'lower-body': {
id: 'lower-body',
@@ -1675,7 +1694,7 @@ export const PROGRAMS: Record<ProgramId, Program> = {
optional: ['Step or bench', 'Wall'],
},
focusAreas: ['Legs', 'Glutes', 'Hips', 'Calves', 'Knee Health'],
weeks: buildWeeks(buildProgramWorkouts(lowerBodyWorkouts)),
weeks: buildWeeks(buildProgramWorkouts(lowerBodyWorkouts, 'lower-body')),
},
'full-body': {
id: 'full-body',
@@ -1689,14 +1708,14 @@ export const PROGRAMS: Record<ProgramId, Program> = {
optional: ['Wall', 'Elevated surface'],
},
focusAreas: ['Total Body', 'Core', 'Cardio', 'Functional Fitness'],
weeks: buildWeeks(buildProgramWorkouts(fullBodyWorkouts)),
weeks: buildWeeks(buildProgramWorkouts(fullBodyWorkouts, 'full-body')),
},
}
// Export individual arrays for convenience
export const UPPER_BODY_WORKOUTS = buildProgramWorkouts(upperBodyWorkouts)
export const LOWER_BODY_WORKOUTS = buildProgramWorkouts(lowerBodyWorkouts)
export const FULL_BODY_WORKOUTS = buildProgramWorkouts(fullBodyWorkouts)
export const UPPER_BODY_WORKOUTS = buildProgramWorkouts(upperBodyWorkouts, 'upper-body')
export const LOWER_BODY_WORKOUTS = buildProgramWorkouts(lowerBodyWorkouts, 'lower-body')
export const FULL_BODY_WORKOUTS = buildProgramWorkouts(fullBodyWorkouts, 'full-body')
// Export all workouts as flat array for player compatibility
export const ALL_PROGRAM_WORKOUTS = [