750 lines
31 KiB
TypeScript
750 lines
31 KiB
TypeScript
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<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);
|
|
|
|
// Timer State
|
|
const [elapsedTime, setElapsedTime] = useState<string>('00:00:00');
|
|
|
|
// Form State
|
|
const [weight, setWeight] = useState<string>('');
|
|
const [reps, setReps] = useState<string>('');
|
|
const [duration, setDuration] = useState<string>('');
|
|
const [distance, setDistance] = useState<string>('');
|
|
const [height, setHeight] = useState<string>('');
|
|
const [bwPercentage, setBwPercentage] = useState<string>('100');
|
|
|
|
// User Weight State
|
|
const [userBodyWeight, setUserBodyWeight] = useState<string>(userWeight ? userWeight.toString() : '70');
|
|
|
|
// Create Exercise State
|
|
const [isCreating, setIsCreating] = useState(false);
|
|
|
|
// Plan Execution State
|
|
const [currentStepIndex, setCurrentStepIndex] = useState(0);
|
|
const [showPlanPrep, setShowPlanPrep] = useState<WorkoutPlan | null>(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<string | null>(null);
|
|
const [editWeight, setEditWeight] = useState<string>('');
|
|
const [editReps, setEditReps] = useState<string>('');
|
|
const [editDuration, setEditDuration] = useState<string>('');
|
|
const [editDistance, setEditDistance] = useState<string>('');
|
|
const [editHeight, setEditHeight] = useState<string>('');
|
|
|
|
// Sporadic Set State
|
|
const [isSporadicMode, setIsSporadicMode] = useState(false);
|
|
const [sporadicSuccess, setSporadicSuccess] = useState(false);
|
|
|
|
// Unilateral Exercise State
|
|
const [sameValuesBothSides, setSameValuesBothSides] = useState(true);
|
|
const [weightLeft, setWeightLeft] = useState<string>('');
|
|
const [weightRight, setWeightRight] = useState<string>('');
|
|
const [repsLeft, setRepsLeft] = useState<string>('');
|
|
const [repsRight, setRepsRight] = useState<string>('');
|
|
const [durationLeft, setDurationLeft] = useState<string>('');
|
|
const [durationRight, setDurationRight] = useState<string>('');
|
|
const [distanceLeft, setDistanceLeft] = useState<string>('');
|
|
const [distanceRight, setDistanceRight] = useState<string>('');
|
|
const [heightLeft, setHeightLeft] = useState<string>('');
|
|
const [heightRight, setHeightRight] = useState<string>('');
|
|
|
|
const handleToggleSameValues = (checked: boolean) => {
|
|
setSameValuesBothSides(checked);
|
|
if (!checked) {
|
|
// Propagate values from single fields to left/right fields
|
|
setWeightLeft(weight);
|
|
setWeightRight(weight);
|
|
setRepsLeft(reps);
|
|
setRepsRight(reps);
|
|
setDurationLeft(duration);
|
|
setDurationRight(duration);
|
|
setDistanceLeft(distance);
|
|
setDistanceRight(distance);
|
|
setHeightLeft(height);
|
|
setHeightRight(height);
|
|
}
|
|
};
|
|
|
|
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<string, number>();
|
|
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<string, number>();
|
|
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;
|
|
|
|
// For unilateral exercises, create two sets (LEFT and RIGHT)
|
|
if (selectedExercise.isUnilateral) {
|
|
const setsToCreate: Array<Partial<WorkoutSet> & { side: 'LEFT' | 'RIGHT' }> = [];
|
|
|
|
if (sameValuesBothSides) {
|
|
// Create two identical sets with LEFT and RIGHT sides
|
|
const setData: Partial<WorkoutSet> = {
|
|
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;
|
|
}
|
|
|
|
setsToCreate.push({ ...setData, side: 'LEFT' });
|
|
setsToCreate.push({ ...setData, side: 'RIGHT' });
|
|
} else {
|
|
// Create separate sets for LEFT and RIGHT with different values
|
|
const leftSetData: Partial<WorkoutSet> = {
|
|
exerciseId: selectedExercise.id,
|
|
};
|
|
const rightSetData: Partial<WorkoutSet> = {
|
|
exerciseId: selectedExercise.id,
|
|
};
|
|
|
|
switch (selectedExercise.type) {
|
|
case ExerciseType.STRENGTH:
|
|
if (weightLeft) leftSetData.weight = parseFloat(weightLeft);
|
|
if (repsLeft) leftSetData.reps = parseInt(repsLeft);
|
|
if (weightRight) rightSetData.weight = parseFloat(weightRight);
|
|
if (repsRight) rightSetData.reps = parseInt(repsRight);
|
|
break;
|
|
case ExerciseType.BODYWEIGHT:
|
|
if (weightLeft) leftSetData.weight = parseFloat(weightLeft);
|
|
if (repsLeft) leftSetData.reps = parseInt(repsLeft);
|
|
leftSetData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
|
if (weightRight) rightSetData.weight = parseFloat(weightRight);
|
|
if (repsRight) rightSetData.reps = parseInt(repsRight);
|
|
rightSetData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
|
break;
|
|
case ExerciseType.CARDIO:
|
|
if (durationLeft) leftSetData.durationSeconds = parseInt(durationLeft);
|
|
if (distanceLeft) leftSetData.distanceMeters = parseFloat(distanceLeft);
|
|
if (durationRight) rightSetData.durationSeconds = parseInt(durationRight);
|
|
if (distanceRight) rightSetData.distanceMeters = parseFloat(distanceRight);
|
|
break;
|
|
case ExerciseType.STATIC:
|
|
if (durationLeft) leftSetData.durationSeconds = parseInt(durationLeft);
|
|
leftSetData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
|
if (durationRight) rightSetData.durationSeconds = parseInt(durationRight);
|
|
rightSetData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
|
break;
|
|
case ExerciseType.HIGH_JUMP:
|
|
if (heightLeft) leftSetData.height = parseFloat(heightLeft);
|
|
if (heightRight) rightSetData.height = parseFloat(heightRight);
|
|
break;
|
|
case ExerciseType.LONG_JUMP:
|
|
if (distanceLeft) leftSetData.distanceMeters = parseFloat(distanceLeft);
|
|
if (distanceRight) rightSetData.distanceMeters = parseFloat(distanceRight);
|
|
break;
|
|
case ExerciseType.PLYOMETRIC:
|
|
if (repsLeft) leftSetData.reps = parseInt(repsLeft);
|
|
if (repsRight) rightSetData.reps = parseInt(repsRight);
|
|
break;
|
|
}
|
|
|
|
setsToCreate.push({ ...leftSetData, side: 'LEFT' });
|
|
setsToCreate.push({ ...rightSetData, side: 'RIGHT' });
|
|
}
|
|
|
|
// Log both sets
|
|
try {
|
|
for (const setData of setsToCreate) {
|
|
const response = await api.post('/sessions/active/log-set', setData);
|
|
if (response.success) {
|
|
const { newSet } = response;
|
|
onSetAdded(newSet);
|
|
}
|
|
}
|
|
|
|
// Update plan progress after logging both sets
|
|
if (activePlan) {
|
|
const response = await api.post('/sessions/active/log-set', { exerciseId: selectedExercise.id });
|
|
if (response.success && response.activeExerciseId) {
|
|
const nextStepIndex = activePlan.steps.findIndex(step => step.exerciseId === response.activeExerciseId);
|
|
if (nextStepIndex !== -1) {
|
|
setCurrentStepIndex(nextStepIndex);
|
|
}
|
|
} else if (response.success && !response.activeExerciseId) {
|
|
setCurrentStepIndex(activePlan.steps.length);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to log unilateral sets:", error);
|
|
}
|
|
} else {
|
|
// Regular bilateral exercise - single set
|
|
const setData: Partial<WorkoutSet> = {
|
|
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;
|
|
|
|
// For unilateral exercises, create two sets (LEFT and RIGHT)
|
|
if (selectedExercise.isUnilateral) {
|
|
const setsToCreate: any[] = [];
|
|
|
|
if (sameValuesBothSides) {
|
|
// Create two identical sets with LEFT and RIGHT sides
|
|
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;
|
|
}
|
|
|
|
setsToCreate.push({ ...set, side: 'LEFT' });
|
|
setsToCreate.push({ ...set, side: 'RIGHT' });
|
|
} else {
|
|
// Create separate sets for LEFT and RIGHT with different values
|
|
const leftSet: any = {
|
|
exerciseId: selectedExercise.id,
|
|
timestamp: Date.now(),
|
|
};
|
|
const rightSet: any = {
|
|
exerciseId: selectedExercise.id,
|
|
timestamp: Date.now(),
|
|
};
|
|
|
|
switch (selectedExercise.type) {
|
|
case ExerciseType.STRENGTH:
|
|
if (weightLeft) leftSet.weight = parseFloat(weightLeft);
|
|
if (repsLeft) leftSet.reps = parseInt(repsLeft);
|
|
if (weightRight) rightSet.weight = parseFloat(weightRight);
|
|
if (repsRight) rightSet.reps = parseInt(repsRight);
|
|
break;
|
|
case ExerciseType.BODYWEIGHT:
|
|
if (weightLeft) leftSet.weight = parseFloat(weightLeft);
|
|
if (repsLeft) leftSet.reps = parseInt(repsLeft);
|
|
leftSet.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
|
if (weightRight) rightSet.weight = parseFloat(weightRight);
|
|
if (repsRight) rightSet.reps = parseInt(repsRight);
|
|
rightSet.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
|
break;
|
|
case ExerciseType.CARDIO:
|
|
if (durationLeft) leftSet.durationSeconds = parseInt(durationLeft);
|
|
if (distanceLeft) leftSet.distanceMeters = parseFloat(distanceLeft);
|
|
if (durationRight) rightSet.durationSeconds = parseInt(durationRight);
|
|
if (distanceRight) rightSet.distanceMeters = parseFloat(distanceRight);
|
|
break;
|
|
case ExerciseType.STATIC:
|
|
if (durationLeft) leftSet.durationSeconds = parseInt(durationLeft);
|
|
leftSet.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
|
if (durationRight) rightSet.durationSeconds = parseInt(durationRight);
|
|
rightSet.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
|
break;
|
|
case ExerciseType.HIGH_JUMP:
|
|
if (heightLeft) leftSet.height = parseFloat(heightLeft);
|
|
if (heightRight) rightSet.height = parseFloat(heightRight);
|
|
break;
|
|
case ExerciseType.LONG_JUMP:
|
|
if (distanceLeft) leftSet.distanceMeters = parseFloat(distanceLeft);
|
|
if (distanceRight) rightSet.distanceMeters = parseFloat(distanceRight);
|
|
break;
|
|
case ExerciseType.PLYOMETRIC:
|
|
if (repsLeft) leftSet.reps = parseInt(repsLeft);
|
|
if (repsRight) rightSet.reps = parseInt(repsRight);
|
|
break;
|
|
}
|
|
|
|
setsToCreate.push({ ...leftSet, side: 'LEFT' });
|
|
setsToCreate.push({ ...rightSet, side: 'RIGHT' });
|
|
}
|
|
|
|
// Log both sets
|
|
try {
|
|
for (const set of setsToCreate) {
|
|
await logSporadicSet(set);
|
|
}
|
|
setSporadicSuccess(true);
|
|
setTimeout(() => setSporadicSuccess(false), 2000);
|
|
// Reset form
|
|
setWeight('');
|
|
setReps('');
|
|
setDuration('');
|
|
setDistance('');
|
|
setHeight('');
|
|
setWeightLeft('');
|
|
setWeightRight('');
|
|
setRepsLeft('');
|
|
setRepsRight('');
|
|
setDurationLeft('');
|
|
setDurationRight('');
|
|
setDistanceLeft('');
|
|
setDistanceRight('');
|
|
setHeightLeft('');
|
|
setHeightRight('');
|
|
if (onSporadicSetAdded) onSporadicSetAdded();
|
|
} catch (error) {
|
|
console.error("Failed to log unilateral sporadic sets:", error);
|
|
}
|
|
} else {
|
|
// Regular bilateral exercise - single set
|
|
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,
|
|
// Unilateral exercise state
|
|
sameValuesBothSides,
|
|
setSameValuesBothSides,
|
|
weightLeft,
|
|
setWeightLeft,
|
|
weightRight,
|
|
setWeightRight,
|
|
repsLeft,
|
|
setRepsLeft,
|
|
repsRight,
|
|
setRepsRight,
|
|
durationLeft,
|
|
setDurationLeft,
|
|
durationRight,
|
|
setDurationRight,
|
|
distanceLeft,
|
|
setDistanceLeft,
|
|
distanceRight,
|
|
setDistanceRight,
|
|
heightLeft,
|
|
setHeightLeft,
|
|
heightRight,
|
|
setHeightRight,
|
|
handleToggleSameValues,
|
|
};
|
|
};
|