import { useState, useEffect } from 'react'; import { WorkoutSession, WorkoutSet, ExerciseDef, ExerciseType, WorkoutPlan, Language } from '../../types'; import { getExercises, getLastSetForExercise, saveExercise, getPlans } from '../../services/storage'; import { api } from '../../services/api'; import { logSporadicSet } from '../../services/sporadicSets'; interface UseTrackerProps { userId: string; userWeight?: number; activeSession: WorkoutSession | null; activePlan: WorkoutPlan | null; onSessionStart: (plan?: WorkoutPlan, startWeight?: number) => void; onSessionEnd: () => void; onSessionQuit: () => void; onSetAdded: (set: WorkoutSet) => void; onRemoveSet: (setId: string) => void; onUpdateSet: (set: WorkoutSet) => void; onSporadicSetAdded?: () => void; } export const useTracker = ({ userId, userWeight, activeSession, activePlan, onSessionStart, onSessionEnd, onSessionQuit, onSetAdded, onRemoveSet, onUpdateSet, onSporadicSetAdded }: UseTrackerProps) => { const [exercises, setExercises] = useState([]); const [plans, setPlans] = useState([]); const [selectedExercise, setSelectedExercise] = useState(null); const [lastSet, setLastSet] = useState(undefined); const [searchQuery, setSearchQuery] = useState(''); const [showSuggestions, setShowSuggestions] = useState(false); // 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); // Plan Execution State const [currentStepIndex, setCurrentStepIndex] = useState(0); const [showPlanPrep, setShowPlanPrep] = useState(null); const [showPlanList, setShowPlanList] = useState(false); // Confirmation State const [showFinishConfirm, setShowFinishConfirm] = useState(false); const [showQuitConfirm, setShowQuitConfirm] = useState(false); const [showMenu, setShowMenu] = useState(false); // Edit Set State const [editingSetId, setEditingSetId] = useState(null); const [editWeight, setEditWeight] = useState(''); const [editReps, setEditReps] = useState(''); const [editDuration, setEditDuration] = useState(''); const [editDistance, setEditDistance] = useState(''); const [editHeight, setEditHeight] = useState(''); // Sporadic Set State const [isSporadicMode, setIsSporadicMode] = useState(false); const [sporadicSuccess, setSporadicSuccess] = useState(false); useEffect(() => { const loadData = async () => { const exList = await getExercises(userId); exList.sort((a, b) => a.name.localeCompare(b.name)); setExercises(exList); 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]); // Recalculate current step when sets change useEffect(() => { if (activeSession && activePlan) { const performedCounts = new Map(); for (const set of activeSession.sets) { performedCounts.set(set.exerciseId, (performedCounts.get(set.exerciseId) || 0) + 1); } let nextStepIndex = activePlan.steps.length; // Default to finished const plannedCounts = new Map(); for (let i = 0; i < activePlan.steps.length; i++) { const step = activePlan.steps[i]; const exerciseId = step.exerciseId; plannedCounts.set(exerciseId, (plannedCounts.get(exerciseId) || 0) + 1); const performedCount = performedCounts.get(exerciseId) || 0; if (performedCount < plannedCounts.get(exerciseId)!) { nextStepIndex = i; break; } } setCurrentStepIndex(nextStepIndex); } }, [activeSession, activePlan]); 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) { setSelectedExercise(exDef); } } } } }, [currentStepIndex, activePlan, 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) { setWeight(set.weight?.toString() || ''); setReps(set.reps?.toString() || ''); setDuration(set.durationSeconds?.toString() || ''); setDistance(set.distanceMeters?.toString() || ''); setHeight(set.height?.toString() || ''); } else { setWeight(''); setReps(''); setDuration(''); setDistance(''); setHeight(''); } // Clear fields not relevant to the selected exercise type if (selectedExercise.type !== ExerciseType.STRENGTH && selectedExercise.type !== ExerciseType.BODYWEIGHT) { setWeight(''); } if (selectedExercise.type !== ExerciseType.STRENGTH && selectedExercise.type !== ExerciseType.BODYWEIGHT && selectedExercise.type !== ExerciseType.PLYOMETRIC) { setReps(''); } if (selectedExercise.type !== ExerciseType.CARDIO && selectedExercise.type !== ExerciseType.STATIC) { setDuration(''); } if (selectedExercise.type !== ExerciseType.CARDIO && selectedExercise.type !== ExerciseType.LONG_JUMP) { setDistance(''); } if (selectedExercise.type !== ExerciseType.HIGH_JUMP) { setHeight(''); } } else { setSearchQuery(''); // Clear search query if no exercise is selected } }; updateSelection(); }, [selectedExercise, userId]); const filteredExercises = searchQuery === '' ? exercises : exercises.filter(ex => ex.name.toLowerCase().includes(searchQuery.toLowerCase()) ); 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 = async () => { if (!activeSession || !selectedExercise) return; const setData: Partial = { exerciseId: selectedExercise.id, }; switch (selectedExercise.type) { case ExerciseType.STRENGTH: if (weight) setData.weight = parseFloat(weight); if (reps) setData.reps = parseInt(reps); break; case ExerciseType.BODYWEIGHT: if (weight) setData.weight = parseFloat(weight); if (reps) setData.reps = parseInt(reps); setData.bodyWeightPercentage = parseFloat(bwPercentage) || 100; break; case ExerciseType.CARDIO: if (duration) setData.durationSeconds = parseInt(duration); if (distance) setData.distanceMeters = parseFloat(distance); break; case ExerciseType.STATIC: if (duration) setData.durationSeconds = parseInt(duration); setData.bodyWeightPercentage = parseFloat(bwPercentage) || 100; break; case ExerciseType.HIGH_JUMP: if (height) setData.height = parseFloat(height); break; case ExerciseType.LONG_JUMP: if (distance) setData.distanceMeters = parseFloat(distance); break; case ExerciseType.PLYOMETRIC: if (reps) setData.reps = parseInt(reps); break; } try { const response = await api.post('/sessions/active/log-set', setData); if (response.success) { const { newSet, activeExerciseId } = response; onSetAdded(newSet); if (activePlan && activeExerciseId) { const nextStepIndex = activePlan.steps.findIndex(step => step.exerciseId === activeExerciseId); if (nextStepIndex !== -1) { setCurrentStepIndex(nextStepIndex); } } else if (activePlan && !activeExerciseId) { // Plan is finished setCurrentStepIndex(activePlan.steps.length); } } } catch (error) { console.error("Failed to log set:", error); } }; const handleLogSporadicSet = async () => { if (!selectedExercise) return; const set: any = { exerciseId: selectedExercise.id, timestamp: Date.now(), }; switch (selectedExercise.type) { case ExerciseType.STRENGTH: if (weight) set.weight = parseFloat(weight); if (reps) set.reps = parseInt(reps); break; case ExerciseType.BODYWEIGHT: if (weight) set.weight = parseFloat(weight); if (reps) set.reps = parseInt(reps); set.bodyWeightPercentage = parseFloat(bwPercentage) || 100; break; case ExerciseType.CARDIO: if (duration) set.durationSeconds = parseInt(duration); if (distance) set.distanceMeters = parseFloat(distance); break; case ExerciseType.STATIC: if (duration) set.durationSeconds = parseInt(duration); set.bodyWeightPercentage = parseFloat(bwPercentage) || 100; break; case ExerciseType.HIGH_JUMP: if (height) set.height = parseFloat(height); break; case ExerciseType.LONG_JUMP: if (distance) set.distanceMeters = parseFloat(distance); break; case ExerciseType.PLYOMETRIC: if (reps) set.reps = parseInt(reps); break; } try { const result = await logSporadicSet(set); if (result) { setSporadicSuccess(true); setTimeout(() => setSporadicSuccess(false), 2000); // Reset form setWeight(''); setReps(''); setDuration(''); setDistance(''); setHeight(''); if (onSporadicSetAdded) onSporadicSetAdded(); } } catch (error) { console.error("Failed to log sporadic set:", error); } }; const handleCreateExercise = async (newEx: ExerciseDef) => { await saveExercise(userId, newEx); setExercises(prev => [...prev, newEx].sort((a, b) => a.name.localeCompare(b.name))); setSelectedExercise(newEx); setSearchQuery(newEx.name); setIsCreating(false); }; const handleEditSet = (set: WorkoutSet) => { setEditingSetId(set.id); setEditWeight(set.weight?.toString() || ''); setEditReps(set.reps?.toString() || ''); setEditDuration(set.durationSeconds?.toString() || ''); setEditDistance(set.distanceMeters?.toString() || ''); setEditHeight(set.height?.toString() || ''); }; const handleSaveEdit = (set: WorkoutSet) => { const updatedSet: WorkoutSet = { ...set, ...(editWeight && { weight: parseFloat(editWeight) }), ...(editReps && { reps: parseInt(editReps) }), ...(editDuration && { durationSeconds: parseInt(editDuration) }), ...(editDistance && { distanceMeters: parseFloat(editDistance) }), ...(editHeight && { height: parseFloat(editHeight) }) }; onUpdateSet(updatedSet); setEditingSetId(null); }; const handleCancelEdit = () => { setEditingSetId(null); }; const jumpToStep = (index: number) => { if (!activePlan) return; setCurrentStepIndex(index); setShowPlanList(false); }; const resetForm = () => { setWeight(''); setReps(''); setDuration(''); setDistance(''); setHeight(''); setSelectedExercise(null); setSearchQuery(''); setSporadicSuccess(false); }; return { exercises, plans, activePlan, selectedExercise, setSelectedExercise, lastSet, searchQuery, setSearchQuery, showSuggestions, setShowSuggestions, elapsedTime, weight, setWeight, reps, setReps, duration, setDuration, distance, setDistance, height, setHeight, bwPercentage, setBwPercentage, userBodyWeight, setUserBodyWeight, isCreating, setIsCreating, currentStepIndex, showPlanPrep, setShowPlanPrep, showPlanList, setShowPlanList, showFinishConfirm, setShowFinishConfirm, showQuitConfirm, setShowQuitConfirm, showMenu, setShowMenu, editingSetId, editWeight, setEditWeight, editReps, setEditReps, editDuration, setEditDuration, editDistance, setEditDistance, editHeight, setEditHeight, isSporadicMode, setIsSporadicMode, sporadicSuccess, filteredExercises, handleStart, confirmPlanStart, handleAddSet, handleLogSporadicSet, handleCreateExercise, handleEditSet, handleSaveEdit, handleCancelEdit, jumpToStep, resetForm, }; };