Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
400 changes: 56 additions & 344 deletions src/App.tsx

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions src/components/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
@@ -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<ConfirmDialogProps> = ({
isOpen,
title,
message,
onConfirm,
onCancel,
confirmText = 'Reset',
cancelText = 'Cancel'
}) => {
if (!isOpen) return null;

return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-sm mx-4">
<h3 className="text-lg font-semibold text-gray-900 mb-3">{title}</h3>
<p className="text-gray-600 mb-6">{message}</p>
<div className="flex gap-3">
<button
onClick={onCancel}
className="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 font-semibold py-2 px-4 rounded-lg transition-colors"
>
{cancelText}
</button>
<button
onClick={onConfirm}
className="flex-1 bg-red-500 hover:bg-red-600 text-white font-semibold py-2 px-4 rounded-lg transition-colors"
>
{confirmText}
</button>
</div>
</div>
</div>
);
};
100 changes: 100 additions & 0 deletions src/components/ExerciseSet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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<string, boolean>;
weightUnit: string;
onToggleSet: (exerciseIndex: number | string, setIndex: number) => void;
}

export const ExerciseSet: React.FC<ExerciseSetProps> = ({
exercise,
exerciseIndex,
setScheme,
schemeIndex,
intensity,
weight,
completedSets,
weightUnit,
onToggleSet
}) => {
const warmupSets = calculateWarmupSets(weight, weightUnit);
const [sets, reps] = setScheme.split('x');

return (
<div className="bg-gray-50 rounded-xl p-5 border border-gray-200">
<h4 className="font-bold text-gray-900 mb-2 text-lg">{exercise}</h4>
<div className="text-sm text-gray-600 mb-4 font-medium">
{setScheme} @ {intensity}%{weight > 0 && ` (${weight} ${weightUnit})`}
</div>

{warmupSets.length > 0 && (
<div className="mb-4">
<h5 className="text-sm font-semibold text-gray-700 mb-2">Warm-up Sets</h5>
<div className="space-y-2">
{warmupSets.map((warmupSet, warmupIndex) => (
<div key={`warmup-${warmupIndex}`} className="flex items-center justify-between bg-blue-50 p-3 rounded-lg border border-blue-200">
<div className="flex items-center gap-4">
<span className="text-xs font-medium text-blue-700 w-16">Warm-up {warmupIndex + 1}</span>
<span className="text-sm text-blue-800">{warmupSet.reps} reps</span>
<span className="text-sm font-semibold text-blue-900">{warmupSet.weight} {weightUnit}</span>
</div>
<button
onClick={() => onToggleSet(`warmup-${exerciseIndex}-${schemeIndex}-${warmupIndex}`, 0)}
className={`w-6 h-6 rounded-full border-2 flex items-center justify-center transition-all ${
completedSets[`warmup-${exerciseIndex}-${schemeIndex}-${warmupIndex}-0`]
? 'bg-blue-500 border-blue-500 text-white' : 'border-blue-300 hover:border-blue-400 bg-white'
}`}
>
{completedSets[`warmup-${exerciseIndex}-${schemeIndex}-${warmupIndex}-0`] && <CheckCircle className="w-4 h-4" />}
</button>
</div>
))}
</div>
</div>
)}

<div className="space-y-3">
<h5 className="text-sm font-semibold text-gray-700">Working Sets</h5>
{Array.from({ length: parseInt(sets) }).map((_, setIndex) => (
<div key={setIndex} className="flex items-center justify-between gap-4 bg-white p-4 rounded-lg border border-gray-200">
<div className="flex items-center gap-4 flex-1">
<span className="text-sm font-semibold text-gray-700 w-14">Set {setIndex + 1}</span>
<span className="text-sm font-medium text-gray-600">{reps} reps</span>
</div>
<div className="flex items-center gap-4">
{weight > 0 && (
<div className="flex items-center gap-2">
<input
type="number"
className="w-20 px-3 py-2 border-2 border-gray-300 rounded-lg text-sm font-medium focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all"
defaultValue={weight}
placeholder="Weight"
step={weightUnit === 'kg' ? '2.5' : '5'}
/>
<span className="text-xs text-gray-500 font-medium">{weightUnit}</span>
</div>
)}
<button
onClick={() => onToggleSet(`${exerciseIndex}-${schemeIndex}`, setIndex)}
className={`w-8 h-8 rounded-full border-2 flex items-center justify-center transition-all ${
completedSets[`${exerciseIndex}-${schemeIndex}-${setIndex}`]
? 'bg-green-500 border-green-500 text-white' : 'border-gray-300 hover:border-green-400 bg-white'
}`}
>
{completedSets[`${exerciseIndex}-${schemeIndex}-${setIndex}`] && <CheckCircle className="w-5 h-5" />}
</button>
</div>
</div>
))}
</div>
</div>
);
};
35 changes: 35 additions & 0 deletions src/components/SimpleWorkout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import { Workout } from '../types';
import { WORKOUT_CONFIGS } from '../config';

interface SimpleWorkoutProps {
workoutType: string;
workout: Workout;
onComplete: () => void;
}

export const SimpleWorkout: React.FC<SimpleWorkoutProps> = ({ workoutType, workout, onComplete }) => {
const config = WORKOUT_CONFIGS[workoutType as keyof typeof WORKOUT_CONFIGS];

return (
<div>
<div className={`bg-gradient-to-r ${config.bg} text-white p-6 rounded-lg text-center`}>
<h3 className="text-2xl font-bold mb-2">
{'title' in config ? config.title :
'activity' in workout ? workout.activity :
'Workout'}
</h3>
{'desc' in config && <p className="opacity-90">{config.desc}</p>}
{'duration' in workout && workout.duration && (
<>
<div className="text-4xl font-bold">{workout.duration}</div>
{typeof workout.duration === 'number' && <div className="text-sm opacity-90 mt-1">minutes</div>}
</>
)}
</div>
<button onClick={onComplete} className="w-full bg-green-500 hover:bg-green-600 text-white font-semibold py-3 rounded-lg transition-colors mt-4">
{config.button}
</button>
</div>
);
};
53 changes: 53 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -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<string, number>;
60 changes: 60 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -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<string, number>;
tenRMs: Record<string, number>;
weightUnit: string;
completedSets: Record<string, boolean>;
draggedIndex: number | null;
dragOverIndex: number | null;
showResetConfirm: boolean;
}
Loading