288 lines
10 KiB
TypeScript
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
|
|
};
|
|
};
|
|
|
|
|