Files
gymflow/src/components/Tracker/useTracker.ts
2025-12-13 19:40:40 +02:00

288 lines
10 KiB
TypeScript

import { useState, useEffect } from 'react';
import { WorkoutSession, WorkoutSet, ExerciseDef, WorkoutPlan } from '../../types';
import { getExercises, saveExercise, getPlans } from '../../services/storage';
import { api } from '../../services/api';
import { useSessionTimer } from '../../hooks/useSessionTimer';
import { useWorkoutForm } from '../../hooks/useWorkoutForm';
import { usePlanExecution } from '../../hooks/usePlanExecution';
import { useAuth } from '../../context/AuthContext';
import { useActiveWorkout } from '../../context/ActiveWorkoutContext';
import { useSession } from '../../context/SessionContext';
import { useRestTimer } from '../../hooks/useRestTimer';
export const useTracker = (props: any) => { // Props ignored/removed
const { currentUser } = useAuth();
const userId = currentUser?.id || '';
const userWeight = currentUser?.profile?.weight;
const {
activeSession,
activePlan,
startSession,
addSet,
updateSet,
quitSession,
endSession,
removeSet
} = useActiveWorkout();
const { refreshData: refreshHistory } = useSession();
const [exercises, setExercises] = useState<ExerciseDef[]>([]);
const [plans, setPlans] = useState<WorkoutPlan[]>([]);
const [selectedExercise, setSelectedExercise] = useState<ExerciseDef | null>(null);
const [lastSet, setLastSet] = useState<WorkoutSet | undefined>(undefined);
const [searchQuery, setSearchQuery] = useState<string>('');
const [showSuggestions, setShowSuggestions] = useState(false);
// User Weight State
const [userBodyWeight, setUserBodyWeight] = useState<string>(userWeight ? userWeight.toString() : '70');
// Create Exercise State
const [isCreating, setIsCreating] = useState(false);
// Confirmation State
const [showFinishConfirm, setShowFinishConfirm] = useState(false);
const [showQuitConfirm, setShowQuitConfirm] = useState(false);
const [showMenu, setShowMenu] = useState(false);
// Quick Log State
const [quickLogSession, setQuickLogSession] = useState<WorkoutSession | null>(null);
const [isSporadicMode, setIsSporadicMode] = useState(false);
const [sporadicSuccess, setSporadicSuccess] = useState(false);
// Last Workout State
const [lastWorkoutDate, setLastWorkoutDate] = useState<Date | null>(null);
// Hooks
const elapsedTime = useSessionTimer(activeSession);
// useWorkoutForm needs onUpdateSet. But context updateSet signature might be different?
// context: updateSet(setId, updates). useWorkoutForm expects onUpdateSet(set).
// We can adaptor.
const handleUpdateSetWrapper = (set: WorkoutSet) => {
updateSet(set.id, set);
};
const form = useWorkoutForm({ userId, onUpdateSet: handleUpdateSetWrapper });
const planExec = usePlanExecution({ activeSession, activePlan, exercises });
// Rest Timer Logic (Moved from ActiveSessionView to persist state)
const getTargetRestTime = () => {
if (activePlan) {
const currentStep = activePlan.steps[planExec.currentStepIndex];
if (currentStep && currentStep.restTimeSeconds) {
return currentStep.restTimeSeconds;
}
}
return currentUser?.profile?.restTimerDefault || 120;
};
const targetRestTime = getTargetRestTime();
const timer = useRestTimer({
defaultTime: targetRestTime
});
// Initial Data Load
useEffect(() => {
if (!userId) return;
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());
}
try {
const lastSessionRes = await api.get<any>('/sessions/active/last');
if (lastSessionRes.success && lastSessionRes.data?.session?.endTime) {
setLastWorkoutDate(new Date(lastSessionRes.data.session.endTime));
} else {
setLastWorkoutDate(null);
}
} catch (err) {
console.error("Failed to load last session", err);
}
loadQuickLogSession();
};
loadData();
}, [activeSession?.id, userId, userWeight, activePlan?.id, isSporadicMode]);
// Function to reload Quick Log session
const loadQuickLogSession = async () => {
try {
const response = await api.get<any>('/sessions/quick-log');
if (response.success && response.data?.session) {
setQuickLogSession(response.data.session);
}
} catch (error) {
console.error("Failed to load quick log session:", error);
}
};
// Auto-select exercise from plan step
useEffect(() => {
const step = planExec.getCurrentStep();
if (step) {
const exDef = exercises.find(e => e.id === step.exerciseId);
if (exDef && selectedExercise?.id !== exDef.id) {
setSelectedExercise(exDef);
}
}
}, [planExec.currentStepIndex, activePlan, exercises]);
// Update form when exercise changes
useEffect(() => {
const updateSelection = async () => {
if (selectedExercise) {
setSearchQuery(selectedExercise.name);
await form.updateFormFromLastSet(selectedExercise.id, selectedExercise.type, selectedExercise.bodyWeightPercentage);
} else {
setSearchQuery('');
form.resetForm();
}
};
updateSelection();
}, [selectedExercise, userId]);
const filteredExercises = searchQuery === ''
? exercises
: exercises.filter(ex =>
ex.name.toLowerCase().includes(searchQuery.toLowerCase())
);
const handleStart = (plan?: WorkoutPlan) => {
if (plan && plan.description) {
planExec.setShowPlanPrep(plan);
} else {
startSession(plan, parseFloat(userBodyWeight));
}
};
const confirmPlanStart = () => {
if (planExec.showPlanPrep) {
startSession(planExec.showPlanPrep, parseFloat(userBodyWeight));
planExec.setShowPlanPrep(null);
}
}
const handleAddSet = async () => {
if (!activeSession || !selectedExercise) return;
const setData = form.prepareSetData(selectedExercise);
await addSet(setData);
};
const handleLogSporadicSet = async () => {
if (!selectedExercise) return;
const setData = form.prepareSetData(selectedExercise);
try {
const response = await api.post('/sessions/quick-log/set', setData);
if (response.success) {
setSporadicSuccess(true);
setTimeout(() => setSporadicSuccess(false), 2000);
loadQuickLogSession();
// form.resetForm(); // Persist values
refreshHistory();
}
} catch (error) {
console.error("Failed to log quick log 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);
};
// Forwarding form handlers from hook
const handleEditSet = form.startEditing;
const handleSaveEdit = form.saveEdit;
const handleCancelEdit = form.cancelEdit;
// Reset override
const resetForm = () => {
form.resetForm();
setSelectedExercise(null);
setSearchQuery('');
setSporadicSuccess(false);
};
return {
exercises,
plans,
activePlan,
selectedExercise,
setSelectedExercise,
lastSet,
searchQuery,
setSearchQuery,
showSuggestions,
setShowSuggestions,
elapsedTime,
// Form Props
weight: form.weight, setWeight: form.setWeight,
reps: form.reps, setReps: form.setReps,
duration: form.duration, setDuration: form.setDuration,
distance: form.distance, setDistance: form.setDistance,
height: form.height, setHeight: form.setHeight,
bwPercentage: form.bwPercentage, setBwPercentage: form.setBwPercentage,
unilateralSide: form.unilateralSide, setUnilateralSide: form.setUnilateralSide,
userBodyWeight, setUserBodyWeight,
isCreating, setIsCreating,
// Plan Execution Props
currentStepIndex: planExec.currentStepIndex,
showPlanPrep: planExec.showPlanPrep, setShowPlanPrep: planExec.setShowPlanPrep,
showPlanList: planExec.showPlanList, setShowPlanList: planExec.setShowPlanList,
jumpToStep: planExec.jumpToStep,
showFinishConfirm, setShowFinishConfirm,
showQuitConfirm, setShowQuitConfirm,
showMenu, setShowMenu,
// Editing
editingSetId: form.editingSetId,
editWeight: form.editWeight, setEditWeight: form.setEditWeight,
editReps: form.editReps, setEditReps: form.setEditReps,
editDuration: form.editDuration, setEditDuration: form.setEditDuration,
editDistance: form.editDistance, setEditDistance: form.setEditDistance,
editHeight: form.editHeight, setEditHeight: form.setEditHeight,
editSide: form.editSide, setEditSide: form.setEditSide,
isSporadicMode, setIsSporadicMode,
sporadicSuccess,
filteredExercises,
handleStart,
confirmPlanStart,
handleAddSet,
handleLogSporadicSet,
handleCreateExercise,
handleEditSet,
handleSaveEdit,
handleCancelEdit,
resetForm,
quickLogSession,
loadQuickLogSession,
// Pass through context methods for UI to use
onSessionEnd: endSession,
onSessionQuit: quitSession,
onRemoveSet: removeSet,
updateSet: handleUpdateSetWrapper,
activeSession, // Need this in view
timer, // Expose timer to views
lastWorkoutDate
};
};