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));
const planList = await getPlans(userId);
setPlans(planList);
if (activeSession?.userBodyWeight) {
setUserBodyWeight(activeSession.userBodyWeight.toString());
} else if (userWeight) {
setUserBodyWeight(userWeight.toString());
}
};
loadData();
}, [activeSession, userId, userWeight]);
// 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]);
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);
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)}
{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('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;