From 33d734454d89341a47374829ed2348d16914f6d8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 21:24:44 +0000 Subject: [PATCH 1/4] Initial plan From a07542d35949a9bf914a2c00695cf35c8d63ab0c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 Aug 2025 21:43:13 +0000 Subject: [PATCH 2/4] Refactor codebase for improved maintainability and readability Co-authored-by: Bruno-366 <81762173+Bruno-366@users.noreply.github.com> --- src/App.tsx | 400 +++++-------------------------- src/components/ConfirmDialog.tsx | 46 ++++ src/components/ExerciseSet.tsx | 77 ++++++ src/components/SimpleWorkout.tsx | 61 +++++ src/config.ts | 53 ++++ src/types.ts | 60 +++++ src/utils.ts | 104 ++++++++ 7 files changed, 457 insertions(+), 344 deletions(-) create mode 100644 src/components/ConfirmDialog.tsx create mode 100644 src/components/ExerciseSet.tsx create mode 100644 src/components/SimpleWorkout.tsx create mode 100644 src/config.ts create mode 100644 src/types.ts create mode 100644 src/utils.ts diff --git a/src/App.tsx b/src/App.tsx index 36dfb1d..6b47d90 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,152 +1,39 @@ import { useState } from 'react'; -import { Activity, Clock, CheckCircle } from 'lucide-react'; -import { blockTemplates } from './blockTemplates'; - -// TypeScript interfaces for workouts -interface BaseWorkout { - type: string; -} - -interface StrengthWorkout extends BaseWorkout { - type: 'strength'; - exercises: string[]; - sets: string; - intensity: number; -} - -interface HypertrophyWorkout extends BaseWorkout { - type: 'hypertrophy'; - exercises: string[]; - sets?: string; - intensity?: number; -} - -interface CardioWorkout extends BaseWorkout { - type: 'liss' | 'hiit'; - activity: string; - duration: number | string; -} - -interface RestWorkout extends BaseWorkout { - type: 'rest' | 'deload'; -} - -type Workout = StrengthWorkout | HypertrophyWorkout | CardioWorkout | RestWorkout; - -interface CustomPlanBlock { - name: string; - weeks: number; - type: string; -} - -interface CompletedWorkout { - date: string; - block: number; - blockName: string; - week: number; - day: number; - details: Workout; -} - -interface AppState { - activeTab: string; - currentWeek: number; - currentDay: number; - completedWorkouts: CompletedWorkout[]; - customPlan: CustomPlanBlock[]; - maxes: Record; - tenRMs: Record; - weightUnit: string; - completedSets: Record; - draggedIndex: number | null; - dragOverIndex: number | null; - showResetConfirm: boolean; -} +import { Activity, Clock } from 'lucide-react'; +import { + AppState, + CompletedWorkout, + Workout, + StrengthWorkout, + HypertrophyWorkout, + CardioWorkout +} from './types'; +import { + HISTORY_CONFIGS, + AVAILABLE_BLOCKS, + DEFAULT_CUSTOM_PLAN, + DEFAULT_MAXES +} from './config'; +import { + getCurrentWorkout, + getCurrentBlockExercises, + getExerciseKey, + calculateWeight, + calculateHypertrophyWeight +} from './utils'; +import { ExerciseSet } from './components/ExerciseSet'; +import { SimpleWorkout } from './components/SimpleWorkout'; +import { ConfirmDialog } from './components/ConfirmDialog'; const App = () => { - // Lookup tables for workout types and configurations - const WORKOUT_CONFIGS = { - rest: { bg: 'from-purple-500 to-pink-500', title: 'Rest Day', desc: 'Take a day off to recover', button: 'Complete Rest Day' }, - deload: { bg: 'from-blue-500 to-teal-500', title: 'Deload', desc: 'Light activity or mobility work', button: 'Complete Deload Day' }, - liss: { bg: 'from-green-500 to-blue-500', button: 'Complete LISS Cardio' }, - hiit: { bg: 'from-orange-500 to-red-500', button: 'Complete HIIT Cardio' } - }; - - const HISTORY_CONFIGS = { - rest: { color: 'bg-slate-400', label: 'Rest', summary: 'Recovery day' }, - deload: { color: 'bg-slate-400', label: 'Deload', summary: 'Light activity' }, - liss: { color: 'bg-green-500', label: 'LISS', getSummary: (w: CardioWorkout) => `${w.activity} - ${w.duration}${typeof w.duration === 'number' ? ' min' : ''}` }, - hiit: { color: 'bg-yellow-500', label: 'HIIT', getSummary: (w: CardioWorkout) => `${w.activity} - ${w.duration}${typeof w.duration === 'number' ? ' min' : ''}` }, - strength: { color: 'bg-red-500', label: 'Strength', getSummary: (w: StrengthWorkout) => w.exercises?.join(', ') || 'Strength training' }, - hypertrophy: { color: 'bg-blue-500', label: 'Hypertrophy', getSummary: (w: HypertrophyWorkout) => w.exercises?.slice(0, 3).join(', ') + (w.exercises?.length > 3 ? '...' : '') || 'Accessory work' } - }; - - const AVAILABLE_BLOCKS = { - endurance1: { name: "Endurance Block 1", weeks: 8 }, - powerbuilding1: { name: "Powerbuilding Block 1", weeks: 3 }, - powerbuilding2: { name: "Powerbuilding Block 2", weeks: 3 }, - powerbuilding3: { name: "Powerbuilding Block 3", weeks: 3 }, - powerbuilding3bulgarian: { name: "Powerbuilding Block 3 - Bulgarian", weeks: 3 }, - bodybuilding: { name: "Bodybuilding Block", weeks: 3 }, - strength: { name: "Strength Block", weeks: 6 } - }; - - // Helper function to generate exercise key from name - const getExerciseKey = (exerciseName: string) => { - return exerciseName.toLowerCase().replace(/\s+/g, '').replace(/[^a-z0-9]/g, ''); - }; - - // Get exercises used in the current active block - const getCurrentBlockExercises = () => { - const currentBlock = state.customPlan[0]; - if (!currentBlock) return { strengthExercises: [], hypertrophyExercises: [] }; - - const blockTemplate = blockTemplates[currentBlock.type as keyof typeof blockTemplates]; - if (!blockTemplate) return { strengthExercises: [], hypertrophyExercises: [] }; - - const strengthExercises = new Set(); - const hypertrophyExercises = new Set(); - - blockTemplate.weeks.forEach((week: { days: unknown[] }) => { - week.days.forEach((day: unknown) => { - const dayObj = day as Record; - if ('exercises' in dayObj && Array.isArray(dayObj.exercises)) { - (dayObj.exercises as string[]).forEach((exercise: string) => { - if (dayObj.type === 'strength') { - strengthExercises.add(exercise); - } else if (dayObj.type === 'hypertrophy') { - hypertrophyExercises.add(exercise); - } - }); - } - }); - }); - - return { - strengthExercises: Array.from(strengthExercises), - hypertrophyExercises: Array.from(hypertrophyExercises) - }; - }; - // Consolidated state const [state, setState] = useState({ activeTab: 'overview', currentWeek: 1, currentDay: 1, completedWorkouts: [] as CompletedWorkout[], - customPlan: [ - { name: "Endurance Block 1", weeks: 8, type: "endurance1" }, - { name: "Powerbuilding Block 1", weeks: 3, type: "powerbuilding1" }, - { name: "Powerbuilding Block 2", weeks: 3, type: "powerbuilding2" }, - { name: "Powerbuilding Block 3", weeks: 3, type: "powerbuilding3" }, - { name: "Bodybuilding Block", weeks: 3, type: "bodybuilding" }, - { name: "Bodybuilding Block", weeks: 3, type: "bodybuilding" }, - { name: "Bodybuilding Block", weeks: 3, type: "bodybuilding" }, - { name: "Powerbuilding Block 3 - Bulgarian", weeks: 3, type: "powerbuilding3bulgarian" }, - { name: "Strength Block", weeks: 6, type: "strength" }, - { name: "Endurance Block 1", weeks: 8, type: "endurance1" } - ], - maxes: { benchpress: 100, squat: 120, deadlift: 140, trapbardeadlift: 130, overheadpress: 60, frontsquat: 90, weightedpullup: 20, powerclean: 80, romaniandeadlift: 120 } as Record, + customPlan: DEFAULT_CUSTOM_PLAN, + maxes: DEFAULT_MAXES, tenRMs: {} as Record, weightUnit: 'kg', completedSets: {} as Record, @@ -158,69 +45,7 @@ const App = () => { // Unified state update function const updateState = (updates: Partial) => setState((prev: AppState) => ({ ...prev, ...updates })); - // Consolidated utility functions - const calculateWeight = (exercise: string, percentage: number) => { - const exerciseKey = getExerciseKey(exercise); - if (!exerciseKey || !state.maxes[exerciseKey]) return 0; - - const calculatedWeight = state.maxes[exerciseKey] * (percentage / 100); - const barbellExercises = ['Bench Press', 'Squat', 'Deadlift', 'Overhead Press', 'Front Squat', 'Trap Bar Deadlift', 'Power Clean', 'Romanian Deadlift']; - - if (barbellExercises.includes(exercise)) { - const increment = state.weightUnit === 'kg' ? 2.5 : 5; - return Math.round(calculatedWeight / increment) * increment; - } - return Math.round(calculatedWeight); - }; - - const calculateHypertrophyWeight = (exercise: string, percentage: number) => { - const exerciseKey = getExerciseKey(exercise); - - // Convert 10RM input to estimated 1RM, then apply percentage - let estimatedOneRM; - if (state.tenRMs[exerciseKey]) { - // Convert 10RM to 1RM: 10RM / 0.75 = 1RM - estimatedOneRM = state.tenRMs[exerciseKey] / 0.75; - } else if (state.maxes[exerciseKey]) { - // Fallback to actual 1RM if available - estimatedOneRM = state.maxes[exerciseKey]; - } else { - return 0; - } - - // Apply the block's programmed percentage to the estimated 1RM - const calculatedWeight = estimatedOneRM * (percentage / 100); - return Math.round(calculatedWeight); - }; - - const calculateWarmupSets = (_exercise: string, workingWeight: number) => { - if (!workingWeight || workingWeight <= 0) return []; - - const barbellWeight = state.weightUnit === 'kg' ? 20 : 45; - const plateIncrement = state.weightUnit === 'kg' ? 2.5 : 5; - const roundToPlate = (weight: number) => Math.max(Math.round(weight / plateIncrement) * plateIncrement, barbellWeight); - - const warmupSets = [ - { weight: roundToPlate(workingWeight * 0.5), reps: 5, type: 'warmup' }, - { weight: roundToPlate(workingWeight * 0.65), reps: 3, type: 'warmup' }, - { weight: roundToPlate(workingWeight * 0.8), reps: 2, type: 'warmup' }, - { weight: roundToPlate(workingWeight * 0.9), reps: 1, type: 'warmup' } - ]; - - return warmupSets.filter(set => set.weight < workingWeight); - }; - - const getCurrentWorkout = () => { - const block = state.customPlan[0]; - const blockTemplate = blockTemplates[block.type as keyof typeof blockTemplates]; - if (!blockTemplate) return null; - - const weekIndex = Math.min(state.currentWeek - 1, blockTemplate.weeks.length - 1); - const dayIndex = state.currentDay - 1; - return blockTemplate.weeks[weekIndex].days[dayIndex]; - }; - - // Consolidated event handlers + // Event handlers const handleDrag = (action: string, data: { index?: number; e?: React.DragEvent }) => { switch(action) { case 'start': @@ -269,7 +94,7 @@ const App = () => { }; const completeWorkout = () => { - const workoutDetails = getCurrentWorkout(); + const workoutDetails = getCurrentWorkout(state.customPlan, state.currentWeek, state.currentDay); if (!workoutDetails) return; // Guard against null workout const block = state.customPlan[0]; @@ -348,129 +173,18 @@ const App = () => { currentDay: 1, completedWorkouts: [], completedSets: {}, - customPlan: [ - { name: "Endurance Block 1", weeks: 8, type: "endurance1" }, - { name: "Powerbuilding Block 1", weeks: 3, type: "powerbuilding1" }, - { name: "Powerbuilding Block 2", weeks: 3, type: "powerbuilding2" }, - { name: "Powerbuilding Block 3", weeks: 3, type: "powerbuilding3" }, - { name: "Bodybuilding Block", weeks: 3, type: "bodybuilding" }, - { name: "Bodybuilding Block", weeks: 3, type: "bodybuilding" }, - { name: "Bodybuilding Block", weeks: 3, type: "bodybuilding" }, - { name: "Powerbuilding Block 3 - Bulgarian", weeks: 3, type: "powerbuilding3bulgarian" }, - { name: "Strength Block", weeks: 6, type: "strength" }, - { name: "Endurance Block 1", weeks: 8, type: "endurance1" } - ], + customPlan: DEFAULT_CUSTOM_PLAN, showResetConfirm: false }); }; // Render functions using lookup tables - const renderSimpleWorkout = (type: string, workout: Workout) => { - const config = WORKOUT_CONFIGS[type as keyof typeof WORKOUT_CONFIGS]; - return ( -
-
-

- {'title' in config ? config.title : - 'activity' in workout ? workout.activity : - 'Workout'} -

- {'desc' in config &&

{config.desc}

} - {'duration' in workout && workout.duration && ( - <> -
{workout.duration}
- {typeof workout.duration === 'number' &&
minutes
} - - )} -
- -
- ); - }; - - const renderExerciseSet = (exercise: string, exerciseIndex: number, setScheme: string, schemeIndex: number, intensity: number, weight: number, workout: StrengthWorkout | HypertrophyWorkout) => { - const [sets, reps] = setScheme.split('x'); - const warmupSets = workout.type === 'strength' && weight > 0 ? calculateWarmupSets(exercise, weight) : []; - - return ( -
-

{exercise}

-
- {setScheme} @ {intensity}%{weight > 0 && ` (${weight} ${state.weightUnit})`} -
- - {warmupSets.length > 0 && ( -
-
Warm-up Sets
-
- {warmupSets.map((warmupSet, warmupIndex) => ( -
-
- Warm-up {warmupIndex + 1} - {warmupSet.reps} reps - {warmupSet.weight} {state.weightUnit} -
- -
- ))} -
-
- )} - -
-
Working Sets
- {Array.from({ length: parseInt(sets) }).map((_, setIndex) => ( -
-
- Set {setIndex + 1} - {reps} reps -
-
- {weight > 0 && ( -
- - {state.weightUnit} -
- )} - -
-
- ))} -
-
- ); - }; - const renderWorkout = () => { - const workout = getCurrentWorkout(); + const workout = getCurrentWorkout(state.customPlan, state.currentWeek, state.currentDay); if (!workout) return

Workout template not yet implemented

; if (['rest', 'deload', 'liss', 'hiit'].includes(workout.type)) { - return renderSimpleWorkout(workout.type, workout as Workout); + return ; } if (workout.type === 'strength' || workout.type === 'hypertrophy') { @@ -487,10 +201,23 @@ const App = () => { const intensityIndex = shouldMapByIndex ? exerciseIndex : schemeIndex; const intensity = parseInt(intensities[intensityIndex] || intensities[0]); const weight = strengthWorkout.type === 'strength' ? - calculateWeight(exercise, intensity) : - calculateHypertrophyWeight(exercise, intensity); + calculateWeight(exercise, intensity, state.maxes, state.weightUnit) : + calculateHypertrophyWeight(exercise, intensity, state.tenRMs, state.maxes); - return renderExerciseSet(exercise, exerciseIndex, setScheme, schemeIndex, intensity, weight, strengthWorkout); + return ( + + ); }); })} - {state.showResetConfirm && ( -
-
-

Reset All Progress?

-

Are you sure you want to reset all progress? This cannot be undone.

-
- - -
-
-
- )} + updateState({ showResetConfirm: false })} + /> )} diff --git a/src/components/ConfirmDialog.tsx b/src/components/ConfirmDialog.tsx new file mode 100644 index 0000000..06d6c46 --- /dev/null +++ b/src/components/ConfirmDialog.tsx @@ -0,0 +1,46 @@ +import React from 'react'; + +interface ConfirmDialogProps { + isOpen: boolean; + title: string; + message: string; + onConfirm: () => void; + onCancel: () => void; + confirmText?: string; + cancelText?: string; +} + +export const ConfirmDialog: React.FC = ({ + isOpen, + title, + message, + onConfirm, + onCancel, + confirmText = 'Reset', + cancelText = 'Cancel' +}) => { + if (!isOpen) return null; + + return ( +
+
+

{title}

+

{message}

+
+ + +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/ExerciseSet.tsx b/src/components/ExerciseSet.tsx new file mode 100644 index 0000000..1c1afa9 --- /dev/null +++ b/src/components/ExerciseSet.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { CheckCircle } from 'lucide-react'; +import { calculateWarmupSets } from '../utils'; + +interface ExerciseSetProps { + exercise: string; + exerciseIndex: number; + setScheme: string; + schemeIndex: number; + intensity: number; + weight: number; + completedSets: Record; + weightUnit: string; + onToggleSet: (exerciseIndex: number | string, setIndex: number) => void; +} + +export const ExerciseSet: React.FC = ({ + exercise, + exerciseIndex, + setScheme, + schemeIndex, + intensity, + weight, + completedSets, + weightUnit, + onToggleSet +}) => { + const warmupSets = calculateWarmupSets(weight, weightUnit); + const [sets, reps] = setScheme.includes('x') ? setScheme.split('x').map(Number) : [1, parseInt(setScheme)]; + + return ( +
+
+

{exercise}

+
+ {setScheme} @ {intensity}% = {weight}{weightUnit} +
+
+ + {warmupSets.length > 0 && ( +
+
Warmup Sets
+
+ {warmupSets.map((warmupSet, idx) => ( +
+ {warmupSet.reps} reps + {warmupSet.weight}{weightUnit} +
+ ))} +
+
+ )} + +
+
Working Sets
+
+ {Array.from({ length: sets }, (_, setIndex) => ( +
+
+ {reps} reps +
+ +
+ ))} +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/SimpleWorkout.tsx b/src/components/SimpleWorkout.tsx new file mode 100644 index 0000000..fc804c1 --- /dev/null +++ b/src/components/SimpleWorkout.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Activity, Clock } from 'lucide-react'; +import { Workout } from '../types'; +import { WORKOUT_CONFIGS } from '../config'; + +interface SimpleWorkoutProps { + workoutType: string; + workout: Workout; + onComplete: () => void; +} + +export const SimpleWorkout: React.FC = ({ workoutType, workout, onComplete }) => { + const config = WORKOUT_CONFIGS[workoutType as keyof typeof WORKOUT_CONFIGS]; + + const renderContent = () => { + if (workoutType === 'rest' || workoutType === 'deload') { + const restConfig = config as { bg: string; title: string; desc: string; button: string }; + return ( +
+
🛌
+

{restConfig.title}

+

{restConfig.desc}

+ +
+ ); + } + + if (workoutType === 'liss' || workoutType === 'hiit') { + const cardioWorkout = workout as { activity: string; duration: number | string }; + return ( +
+
+ {workoutType === 'liss' ? : } +
+

{cardioWorkout.activity}

+
+
{cardioWorkout.duration}
+
+ {typeof cardioWorkout.duration === 'number' ? 'minutes' : ''} +
+
+ +
+ ); + } + + return null; + }; + + return renderContent(); +}; \ No newline at end of file diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..6402df9 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,53 @@ +import { CardioWorkout, StrengthWorkout, HypertrophyWorkout } from './types'; + +// Lookup tables for workout types and configurations +export const WORKOUT_CONFIGS = { + rest: { bg: 'from-purple-500 to-pink-500', title: 'Rest Day', desc: 'Take a day off to recover', button: 'Complete Rest Day' }, + deload: { bg: 'from-blue-500 to-teal-500', title: 'Deload', desc: 'Light activity or mobility work', button: 'Complete Deload Day' }, + liss: { bg: 'from-green-500 to-blue-500', button: 'Complete LISS Cardio' }, + hiit: { bg: 'from-orange-500 to-red-500', button: 'Complete HIIT Cardio' } +}; + +export const HISTORY_CONFIGS = { + rest: { color: 'bg-slate-400', label: 'Rest', summary: 'Recovery day' }, + deload: { color: 'bg-slate-400', label: 'Deload', summary: 'Light activity' }, + liss: { color: 'bg-green-500', label: 'LISS', getSummary: (w: CardioWorkout) => `${w.activity} - ${w.duration}${typeof w.duration === 'number' ? ' min' : ''}` }, + hiit: { color: 'bg-yellow-500', label: 'HIIT', getSummary: (w: CardioWorkout) => `${w.activity} - ${w.duration}${typeof w.duration === 'number' ? ' min' : ''}` }, + strength: { color: 'bg-red-500', label: 'Strength', getSummary: (w: StrengthWorkout) => w.exercises?.join(', ') || 'Strength training' }, + hypertrophy: { color: 'bg-blue-500', label: 'Hypertrophy', getSummary: (w: HypertrophyWorkout) => w.exercises?.slice(0, 3).join(', ') + (w.exercises?.length > 3 ? '...' : '') || 'Accessory work' } +}; + +export const AVAILABLE_BLOCKS = { + endurance1: { name: "Endurance Block 1", weeks: 8 }, + powerbuilding1: { name: "Powerbuilding Block 1", weeks: 3 }, + powerbuilding2: { name: "Powerbuilding Block 2", weeks: 3 }, + powerbuilding3: { name: "Powerbuilding Block 3", weeks: 3 }, + powerbuilding3bulgarian: { name: "Powerbuilding Block 3 - Bulgarian", weeks: 3 }, + bodybuilding: { name: "Bodybuilding Block", weeks: 3 }, + strength: { name: "Strength Block", weeks: 6 } +}; + +export const DEFAULT_CUSTOM_PLAN = [ + { name: "Endurance Block 1", weeks: 8, type: "endurance1" }, + { name: "Powerbuilding Block 1", weeks: 3, type: "powerbuilding1" }, + { name: "Powerbuilding Block 2", weeks: 3, type: "powerbuilding2" }, + { name: "Powerbuilding Block 3", weeks: 3, type: "powerbuilding3" }, + { name: "Bodybuilding Block", weeks: 3, type: "bodybuilding" }, + { name: "Bodybuilding Block", weeks: 3, type: "bodybuilding" }, + { name: "Bodybuilding Block", weeks: 3, type: "bodybuilding" }, + { name: "Powerbuilding Block 3 - Bulgarian", weeks: 3, type: "powerbuilding3bulgarian" }, + { name: "Strength Block", weeks: 6, type: "strength" }, + { name: "Endurance Block 1", weeks: 8, type: "endurance1" } +]; + +export const DEFAULT_MAXES = { + benchpress: 100, + squat: 120, + deadlift: 140, + trapbardeadlift: 130, + overheadpress: 60, + frontsquat: 90, + weightedpullup: 20, + powerclean: 80, + romaniandeadlift: 120 +} as Record; \ No newline at end of file diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..76cc2b8 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,60 @@ +// TypeScript interfaces for workouts +export interface BaseWorkout { + type: string; +} + +export interface StrengthWorkout extends BaseWorkout { + type: 'strength'; + exercises: string[]; + sets: string; + intensity: number; +} + +export interface HypertrophyWorkout extends BaseWorkout { + type: 'hypertrophy'; + exercises: string[]; + sets?: string; + intensity?: number; +} + +export interface CardioWorkout extends BaseWorkout { + type: 'liss' | 'hiit'; + activity: string; + duration: number | string; +} + +export interface RestWorkout extends BaseWorkout { + type: 'rest' | 'deload'; +} + +export type Workout = StrengthWorkout | HypertrophyWorkout | CardioWorkout | RestWorkout; + +export interface CustomPlanBlock { + name: string; + weeks: number; + type: string; +} + +export interface CompletedWorkout { + date: string; + block: number; + blockName: string; + week: number; + day: number; + details: Workout; +} + +export interface AppState { + activeTab: string; + currentWeek: number; + currentDay: number; + completedWorkouts: CompletedWorkout[]; + customPlan: CustomPlanBlock[]; + maxes: Record; + tenRMs: Record; + weightUnit: string; + completedSets: Record; + draggedIndex: number | null; + dragOverIndex: number | null; + showResetConfirm: boolean; +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..49021c1 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,104 @@ +import { blockTemplates } from './blockTemplates'; +import { CustomPlanBlock } from './types'; + +// Helper function to generate exercise key from name +export const getExerciseKey = (exerciseName: string) => { + return exerciseName.toLowerCase().replace(/\s+/g, '').replace(/[^a-z0-9]/g, ''); +}; + +// Get exercises used in the current active block +export const getCurrentBlockExercises = (customPlan: CustomPlanBlock[]) => { + const currentBlock = customPlan[0]; + if (!currentBlock) return { strengthExercises: [], hypertrophyExercises: [] }; + + const blockTemplate = blockTemplates[currentBlock.type as keyof typeof blockTemplates]; + if (!blockTemplate) return { strengthExercises: [], hypertrophyExercises: [] }; + + const strengthExercises = new Set(); + const hypertrophyExercises = new Set(); + + blockTemplate.weeks.forEach((week: { days: unknown[] }) => { + week.days.forEach((day: unknown) => { + const dayObj = day as Record; + if ('exercises' in dayObj && Array.isArray(dayObj.exercises)) { + (dayObj.exercises as string[]).forEach((exercise: string) => { + if (dayObj.type === 'strength') { + strengthExercises.add(exercise); + } else if (dayObj.type === 'hypertrophy') { + hypertrophyExercises.add(exercise); + } + }); + } + }); + }); + + return { + strengthExercises: Array.from(strengthExercises), + hypertrophyExercises: Array.from(hypertrophyExercises) + }; +}; + +// Get current workout from block template +export const getCurrentWorkout = (customPlan: CustomPlanBlock[], currentWeek: number, currentDay: number) => { + const block = customPlan[0]; + const blockTemplate = blockTemplates[block.type as keyof typeof blockTemplates]; + if (!blockTemplate) return null; + + const weekIndex = Math.min(currentWeek - 1, blockTemplate.weeks.length - 1); + const dayIndex = currentDay - 1; + return blockTemplate.weeks[weekIndex].days[dayIndex]; +}; + +// Weight calculation utilities +export const calculateWeight = (exercise: string, percentage: number, maxes: Record, weightUnit: string) => { + const exerciseKey = getExerciseKey(exercise); + if (!exerciseKey || !maxes[exerciseKey]) return 0; + + const calculatedWeight = maxes[exerciseKey] * (percentage / 100); + + // Round to appropriate plate weight for barbell exercises + const barbellExercises = ['Bench Press', 'Squat', 'Deadlift', 'Overhead Press', 'Front Squat', 'Trap Bar Deadlift', 'Power Clean', 'Romanian Deadlift']; + + if (barbellExercises.includes(exercise)) { + const increment = weightUnit === 'kg' ? 2.5 : 5; + return Math.round(calculatedWeight / increment) * increment; + } + return Math.round(calculatedWeight); +}; + +export const calculateHypertrophyWeight = (exercise: string, percentage: number, tenRMs: Record, maxes: Record) => { + const exerciseKey = getExerciseKey(exercise); + + // Convert 10RM input to estimated 1RM, then apply percentage + let estimatedOneRM; + if (tenRMs[exerciseKey]) { + // Convert 10RM to 1RM: 10RM / 0.75 = 1RM + estimatedOneRM = tenRMs[exerciseKey] / 0.75; + } else if (maxes[exerciseKey]) { + // Fallback to actual 1RM if available + estimatedOneRM = maxes[exerciseKey]; + } else { + return 0; + } + + // Apply the block's programmed percentage to the estimated 1RM + const calculatedWeight = estimatedOneRM * (percentage / 100); + return Math.round(calculatedWeight); +}; + +export const calculateWarmupSets = (workingWeight: number, weightUnit: string) => { + if (!workingWeight || workingWeight <= 0) return []; + + const barbellWeight = weightUnit === 'kg' ? 20 : 45; + const plateIncrement = weightUnit === 'kg' ? 2.5 : 5; + const roundToPlate = (weight: number) => Math.max(Math.round(weight / plateIncrement) * plateIncrement, barbellWeight); + + const warmupSets = [ + { weight: roundToPlate(workingWeight * 0.5), reps: 5, type: 'warmup' }, + { weight: roundToPlate(workingWeight * 0.65), reps: 3, type: 'warmup' }, + { weight: roundToPlate(workingWeight * 0.8), reps: 2, type: 'warmup' }, + { weight: roundToPlate(workingWeight * 0.9), reps: 1, type: 'warmup' } + ]; + + return warmupSets.filter(set => set.weight < workingWeight); +}; \ No newline at end of file From 1ad3defcb417cf3032b5a6c1e19697fa579824a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 Aug 2025 05:35:57 +0000 Subject: [PATCH 3/4] Fix workout tab UI to restore original design for strength sessions Co-authored-by: Bruno-366 <81762173+Bruno-366@users.noreply.github.com> --- src/components/ExerciseSet.tsx | 71 ++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/src/components/ExerciseSet.tsx b/src/components/ExerciseSet.tsx index 1c1afa9..13b536c 100644 --- a/src/components/ExerciseSet.tsx +++ b/src/components/ExerciseSet.tsx @@ -26,39 +26,62 @@ export const ExerciseSet: React.FC = ({ onToggleSet }) => { const warmupSets = calculateWarmupSets(weight, weightUnit); - const [sets, reps] = setScheme.includes('x') ? setScheme.split('x').map(Number) : [1, parseInt(setScheme)]; + const [sets, reps] = setScheme.split('x'); return ( -
-
-

{exercise}

-
- {setScheme} @ {intensity}% = {weight}{weightUnit} -
+
+

{exercise}

+
+ {setScheme} @ {intensity}%{weight > 0 && ` (${weight} ${weightUnit})`}
{warmupSets.length > 0 && ( -
-
Warmup Sets
-
- {warmupSets.map((warmupSet, idx) => ( -
- {warmupSet.reps} reps - {warmupSet.weight}{weightUnit} +
+
Warm-up Sets
+
+ {warmupSets.map((warmupSet, warmupIndex) => ( +
+
+ Warm-up {warmupIndex + 1} + {warmupSet.reps} reps + {warmupSet.weight} {weightUnit} +
+
))}
)} -
-
Working Sets
-
- {Array.from({ length: sets }, (_, setIndex) => ( -
-
- {reps} reps -
+
+
Working Sets
+ {Array.from({ length: parseInt(sets) }).map((_, setIndex) => ( +
+
+ Set {setIndex + 1} + {reps} reps +
+
+ {weight > 0 && ( +
+ + {weightUnit} +
+ )}
- ))} -
+
+ ))}
); From 2fb7c76046fb8a87802f879c60bc951e998ec6e5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 16 Aug 2025 05:54:37 +0000 Subject: [PATCH 4/4] Restore original SimpleWorkout UI for cardio workouts Co-authored-by: Bruno-366 <81762173+Bruno-366@users.noreply.github.com> --- src/components/SimpleWorkout.tsx | 68 ++++++++++---------------------- 1 file changed, 21 insertions(+), 47 deletions(-) diff --git a/src/components/SimpleWorkout.tsx b/src/components/SimpleWorkout.tsx index fc804c1..4e252e0 100644 --- a/src/components/SimpleWorkout.tsx +++ b/src/components/SimpleWorkout.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Activity, Clock } from 'lucide-react'; import { Workout } from '../types'; import { WORKOUT_CONFIGS } from '../config'; @@ -12,50 +11,25 @@ interface SimpleWorkoutProps { export const SimpleWorkout: React.FC = ({ workoutType, workout, onComplete }) => { const config = WORKOUT_CONFIGS[workoutType as keyof typeof WORKOUT_CONFIGS]; - const renderContent = () => { - if (workoutType === 'rest' || workoutType === 'deload') { - const restConfig = config as { bg: string; title: string; desc: string; button: string }; - return ( -
-
🛌
-

{restConfig.title}

-

{restConfig.desc}

- -
- ); - } - - if (workoutType === 'liss' || workoutType === 'hiit') { - const cardioWorkout = workout as { activity: string; duration: number | string }; - return ( -
-
- {workoutType === 'liss' ? : } -
-

{cardioWorkout.activity}

-
-
{cardioWorkout.duration}
-
- {typeof cardioWorkout.duration === 'number' ? 'minutes' : ''} -
-
- -
- ); - } - - return null; - }; - - return renderContent(); + return ( +
+
+

+ {'title' in config ? config.title : + 'activity' in workout ? workout.activity : + 'Workout'} +

+ {'desc' in config &&

{config.desc}

} + {'duration' in workout && workout.duration && ( + <> +
{workout.duration}
+ {typeof workout.duration === 'number' &&
minutes
} + + )} +
+ +
+ ); }; \ No newline at end of file