import React, { useState, useEffect } from 'react'; import { Plus, Activity, ChevronDown, ChevronUp, Dumbbell, PlayCircle, CheckCircle, User, Scale, X, Flame, Timer as TimerIcon, ArrowUp, ArrowRight, Footprints, Ruler, CheckSquare, Trash2, Percent } from 'lucide-react'; import { WorkoutSession, WorkoutSet, ExerciseDef, ExerciseType, WorkoutPlan, Language } from '../types'; import { getExercises, getLastSetForExercise, saveExercise, getPlans } from '../services/storage'; import { getCurrentUserProfile } from '../services/auth'; import { t } from '../services/i18n'; interface TrackerProps { userId: string; userWeight?: number; activeSession: WorkoutSession | null; activePlan: WorkoutPlan | null; onSessionStart: (plan?: WorkoutPlan, startWeight?: number) => void; onSessionEnd: () => void; onSetAdded: (set: WorkoutSet) => void; onRemoveSet: (setId: string) => void; lang: Language; } const FilledInput = ({ label, value, onChange, type = "number", icon, autoFocus, step }: any) => (
); const Tracker: React.FC = ({ userId, userWeight, activeSession, activePlan, onSessionStart, onSessionEnd, onSetAdded, onRemoveSet, lang }) => { const [exercises, setExercises] = useState([]); const [plans, setPlans] = useState([]); const [selectedExercise, setSelectedExercise] = useState(null); const [lastSet, setLastSet] = useState(undefined); // Timer State const [elapsedTime, setElapsedTime] = useState('00:00:00'); // Form State const [weight, setWeight] = useState(''); const [reps, setReps] = useState(''); const [duration, setDuration] = useState(''); const [distance, setDistance] = useState(''); const [height, setHeight] = useState(''); const [bwPercentage, setBwPercentage] = useState('100'); // User Weight State const [userBodyWeight, setUserBodyWeight] = useState(userWeight ? userWeight.toString() : '70'); // Create Exercise State const [isCreating, setIsCreating] = useState(false); const [newName, setNewName] = useState(''); const [newType, setNewType] = useState(ExerciseType.STRENGTH); const [newBwPercentage, setNewBwPercentage] = useState('100'); // Plan Execution State const [currentStepIndex, setCurrentStepIndex] = useState(0); const [showPlanPrep, setShowPlanPrep] = useState(null); const [showPlanList, setShowPlanList] = useState(false); useEffect(() => { const loadData = async () => { const exList = await getExercises(userId); setExercises(exList.filter(e => !e.isArchived || (activePlan && activePlan.steps.some(s => s.exerciseId === e.id)))); const planList = await getPlans(userId); setPlans(planList); if (activeSession?.userBodyWeight) { setUserBodyWeight(activeSession.userBodyWeight.toString()); } else if (userWeight) { setUserBodyWeight(userWeight.toString()); } }; loadData(); }, [activeSession, userId, userWeight, activePlan]); // Timer Logic useEffect(() => { let interval: number; if (activeSession) { const updateTimer = () => { const diff = Math.floor((Date.now() - activeSession.startTime) / 1000); const h = Math.floor(diff / 3600); const m = Math.floor((diff % 3600) / 60); const s = diff % 60; setElapsedTime(`${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`); }; updateTimer(); interval = window.setInterval(updateTimer, 1000); } return () => clearInterval(interval); }, [activeSession]); useEffect(() => { if (activeSession && activePlan && exercises.length > 0 && activePlan.steps.length > 0) { if (currentStepIndex < activePlan.steps.length) { const step = activePlan.steps[currentStepIndex]; if (step) { const exDef = exercises.find(e => e.id === step.exerciseId); if (exDef) { if (!selectedExercise || selectedExercise.id !== exDef.id) { setSelectedExercise(exDef); } } } } } }, [activeSession, activePlan, currentStepIndex, exercises, selectedExercise]); useEffect(() => { const updateSelection = async () => { if (selectedExercise) { setBwPercentage(selectedExercise.bodyWeightPercentage ? selectedExercise.bodyWeightPercentage.toString() : '100'); const set = await getLastSetForExercise(userId, selectedExercise.id); setLastSet(set); if (set) { if (set.weight != null) setWeight(set.weight.toString()); if (set.reps != null) setReps(set.reps.toString()); if (set.durationSeconds != null) setDuration(set.durationSeconds.toString()); if (set.distanceMeters != null) setDistance(set.distanceMeters.toString()); if (set.height != null) setHeight(set.height.toString()); } else { setWeight(''); setReps(''); setDuration(''); setDistance(''); setHeight(''); } } }; updateSelection(); }, [selectedExercise, userId]); const handleStart = (plan?: WorkoutPlan) => { if (plan && plan.description) { setShowPlanPrep(plan); } else { onSessionStart(plan, parseFloat(userBodyWeight)); } }; const confirmPlanStart = () => { if (showPlanPrep) { onSessionStart(showPlanPrep, parseFloat(userBodyWeight)); setShowPlanPrep(null); } } const handleAddSet = () => { if (!activeSession || !selectedExercise) return; const newSet: WorkoutSet = { id: crypto.randomUUID(), exerciseId: selectedExercise.id, exerciseName: selectedExercise.name, type: selectedExercise.type, timestamp: Date.now(), ...(weight && { weight: parseFloat(weight) }), ...(reps && { reps: parseInt(reps) }), ...(duration && { durationSeconds: parseInt(duration) }), ...(distance && { distanceMeters: parseFloat(distance) }), ...(height && { height: parseFloat(height) }), ...((selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STATIC) && { bodyWeightPercentage: parseFloat(bwPercentage) || 100 }) }; onSetAdded(newSet); if (activePlan) { const currentStep = activePlan.steps[currentStepIndex]; if (currentStep && currentStep.exerciseId === selectedExercise.id) { const nextIndex = currentStepIndex + 1; setCurrentStepIndex(nextIndex); } } }; const handleCreateExercise = async () => { if (!newName.trim()) return; const newEx: ExerciseDef = { id: crypto.randomUUID(), name: newName.trim(), type: newType, ...(newType === ExerciseType.BODYWEIGHT && { bodyWeightPercentage: parseFloat(newBwPercentage) || 100 }) }; await saveExercise(userId, newEx); const exList = await getExercises(userId); setExercises(exList.filter(e => !e.isArchived)); setSelectedExercise(newEx); setNewName(''); setNewType(ExerciseType.STRENGTH); setNewBwPercentage('100'); setIsCreating(false); }; const jumpToStep = (index: number) => { if (!activePlan) return; setCurrentStepIndex(index); setShowPlanList(false); }; const isPlanFinished = activePlan && currentStepIndex >= activePlan.steps.length; const exerciseTypeLabels: Record = { [ExerciseType.STRENGTH]: t('type_strength', lang), [ExerciseType.BODYWEIGHT]: t('type_bodyweight', lang), [ExerciseType.CARDIO]: t('type_cardio', lang), [ExerciseType.STATIC]: t('type_static', lang), [ExerciseType.HIGH_JUMP]: t('type_height', lang), [ExerciseType.LONG_JUMP]: t('type_dist', lang), [ExerciseType.PLYOMETRIC]: t('type_jump', lang), }; if (!activeSession) { return (

{t('ready_title', lang)}

{t('ready_subtitle', lang)}

setUserBodyWeight(e.target.value)} />

{t('change_in_profile', lang)}

{plans.length > 0 && (

{t('or_choose_plan', lang)}

{plans.map(plan => ( ))}
)}
{showPlanPrep && (

{showPlanPrep.name}

{t('prep_title', lang)}
{showPlanPrep.description || t('prep_no_instructions', lang)}
)}
); } return (

{activePlan ? activePlan.name : t('free_workout', lang)}

{elapsedTime} {activeSession.userBodyWeight ? ` • ${activeSession.userBodyWeight}kg` : ''}
{activePlan && (
{showPlanList && (
{activePlan.steps.map((step, idx) => ( ))}
)}
)}
{selectedExercise && (
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STATIC) && ( setWeight(e.target.value)} icon={} autoFocus={activePlan && !isPlanFinished && activePlan.steps[currentStepIndex]?.isWeighted && (selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STRENGTH)} /> )} {(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.PLYOMETRIC) && ( setReps(e.target.value)} icon={} type="number" /> )} {(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.STATIC) && ( setDuration(e.target.value)} icon={} /> )} {(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.LONG_JUMP) && ( setDistance(e.target.value)} icon={} /> )} {(selectedExercise.type === ExerciseType.HIGH_JUMP) && ( setHeight(e.target.value)} icon={} /> )}
{(selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STATIC) && (
{t('body_weight_percent', lang)}
setBwPercentage(e.target.value)} /> %
)}
{t('prev', lang)}: {lastSet ? ( <> {lastSet?.weight ? `${lastSet?.weight}kg × ` : ''} {lastSet?.reps ? `${lastSet?.reps}` : ''} {lastSet?.distanceMeters ? `${lastSet?.distanceMeters}m` : ''} {lastSet?.height ? `${lastSet?.height}cm` : ''} {lastSet?.durationSeconds ? `${lastSet?.durationSeconds}s` : ''} ) : '—'}
)} {activeSession.sets.length > 0 && (

{t('history_section', lang)}

{[...activeSession.sets].reverse().map((set, idx) => { const setNumber = activeSession.sets.length - idx; return (
{setNumber}
{set.exerciseName}
{set.weight !== undefined && `${set.weight}kg `} {set.reps !== undefined && `x ${set.reps}`} {set.distanceMeters !== undefined && `${set.distanceMeters}m`} {set.durationSeconds !== undefined && `${set.durationSeconds}s`} {set.height !== undefined && `${set.height}cm`}
); })}
)}
{isCreating && (

{t('create_exercise', lang)}

setNewName(e.target.value)} type="text" autoFocus />
{[ { id: ExerciseType.STRENGTH, label: exerciseTypeLabels[ExerciseType.STRENGTH], icon: Dumbbell }, { id: ExerciseType.BODYWEIGHT, label: exerciseTypeLabels[ExerciseType.BODYWEIGHT], icon: User }, { id: ExerciseType.CARDIO, label: exerciseTypeLabels[ExerciseType.CARDIO], icon: Flame }, { id: ExerciseType.STATIC, label: exerciseTypeLabels[ExerciseType.STATIC], icon: TimerIcon }, { id: ExerciseType.HIGH_JUMP, label: exerciseTypeLabels[ExerciseType.HIGH_JUMP], icon: ArrowUp }, { id: ExerciseType.LONG_JUMP, label: exerciseTypeLabels[ExerciseType.LONG_JUMP], icon: Ruler }, { id: ExerciseType.PLYOMETRIC, label: exerciseTypeLabels[ExerciseType.PLYOMETRIC], icon: Footprints }, ].map((type) => ( ))}
{newType === ExerciseType.BODYWEIGHT && ( setNewBwPercentage(e.target.value)} icon={} /> )}
)}
); }; export default Tracker;