Plan state is persistent during session
This commit is contained in:
32
App.tsx
32
App.tsx
@@ -9,7 +9,7 @@ import Plans from './components/Plans';
|
||||
import Login from './components/Login';
|
||||
import Profile from './components/Profile';
|
||||
import { TabView, WorkoutSession, WorkoutSet, WorkoutPlan, User, Language } from './types';
|
||||
import { getSessions, saveSession, deleteSession, getPlans, getActiveSession, updateActiveSession, deleteActiveSession } from './services/storage';
|
||||
import { getSessions, saveSession, deleteSession, getPlans, getActiveSession, updateActiveSession, deleteActiveSession, updateSetInActiveSession, deleteSetFromActiveSession } from './services/storage';
|
||||
import { getCurrentUserProfile, getMe } from './services/auth';
|
||||
import { getSystemLanguage } from './services/i18n';
|
||||
import { generateId } from './utils/uuid';
|
||||
@@ -132,39 +132,35 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddSet = async (set: WorkoutSet) => {
|
||||
const handleAddSet = (set: WorkoutSet) => {
|
||||
if (activeSession && currentUser) {
|
||||
const updatedSession = {
|
||||
...activeSession,
|
||||
sets: [...activeSession.sets, set]
|
||||
};
|
||||
setActiveSession(updatedSession);
|
||||
// Save to database
|
||||
await updateActiveSession(currentUser.id, updatedSession);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveSetFromActive = async (setId: string) => {
|
||||
if (activeSession && currentUser) {
|
||||
const updatedSession = {
|
||||
...activeSession,
|
||||
sets: activeSession.sets.filter(s => s.id !== setId)
|
||||
};
|
||||
setActiveSession(updatedSession);
|
||||
// Save to database
|
||||
await updateActiveSession(currentUser.id, updatedSession);
|
||||
await deleteSetFromActiveSession(currentUser.id, setId);
|
||||
const updatedSession = {
|
||||
...activeSession,
|
||||
sets: activeSession.sets.filter(s => s.id !== setId)
|
||||
};
|
||||
setActiveSession(updatedSession);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateSetInActive = async (updatedSet: WorkoutSet) => {
|
||||
if (activeSession && currentUser) {
|
||||
const updatedSession = {
|
||||
...activeSession,
|
||||
sets: activeSession.sets.map(s => s.id === updatedSet.id ? updatedSet : s)
|
||||
};
|
||||
setActiveSession(updatedSession);
|
||||
// Save to database
|
||||
await updateActiveSession(currentUser.id, updatedSession);
|
||||
const response = await updateSetInActiveSession(currentUser.id, updatedSet.id, updatedSet);
|
||||
const updatedSession = {
|
||||
...activeSession,
|
||||
sets: activeSession.sets.map(s => s.id === updatedSet.id ? response : s)
|
||||
};
|
||||
setActiveSession(updatedSession);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getExercises, getLastSetForExercise, saveExercise, getPlans } from '../
|
||||
import { getCurrentUserProfile } from '../services/auth';
|
||||
import { t } from '../services/i18n';
|
||||
import { generateId } from '../utils/uuid';
|
||||
import { api } from '../services/api';
|
||||
|
||||
interface TrackerProps {
|
||||
userId: string;
|
||||
@@ -99,6 +100,32 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
||||
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) {
|
||||
@@ -166,54 +193,61 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddSet = () => {
|
||||
const handleAddSet = async () => {
|
||||
if (!activeSession || !selectedExercise) return;
|
||||
|
||||
const newSet: WorkoutSet = {
|
||||
id: generateId(),
|
||||
const setData: Partial<WorkoutSet> = {
|
||||
exerciseId: selectedExercise.id,
|
||||
exerciseName: selectedExercise.name,
|
||||
type: selectedExercise.type,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
switch (selectedExercise.type) {
|
||||
case ExerciseType.STRENGTH:
|
||||
if (weight) newSet.weight = parseFloat(weight);
|
||||
if (reps) newSet.reps = parseInt(reps);
|
||||
if (weight) setData.weight = parseFloat(weight);
|
||||
if (reps) setData.reps = parseInt(reps);
|
||||
break;
|
||||
case ExerciseType.BODYWEIGHT:
|
||||
if (weight) newSet.weight = parseFloat(weight);
|
||||
if (reps) newSet.reps = parseInt(reps);
|
||||
newSet.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
if (weight) setData.weight = parseFloat(weight);
|
||||
if (reps) setData.reps = parseInt(reps);
|
||||
setData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.CARDIO:
|
||||
if (duration) newSet.durationSeconds = parseInt(duration);
|
||||
if (distance) newSet.distanceMeters = parseFloat(distance);
|
||||
if (duration) setData.durationSeconds = parseInt(duration);
|
||||
if (distance) setData.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.STATIC:
|
||||
if (duration) newSet.durationSeconds = parseInt(duration);
|
||||
newSet.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
if (duration) setData.durationSeconds = parseInt(duration);
|
||||
setData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.HIGH_JUMP:
|
||||
if (height) newSet.height = parseFloat(height);
|
||||
if (height) setData.height = parseFloat(height);
|
||||
break;
|
||||
case ExerciseType.LONG_JUMP:
|
||||
if (distance) newSet.distanceMeters = parseFloat(distance);
|
||||
if (distance) setData.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.PLYOMETRIC:
|
||||
if (reps) newSet.reps = parseInt(reps);
|
||||
if (reps) setData.reps = parseInt(reps);
|
||||
break;
|
||||
}
|
||||
|
||||
onSetAdded(newSet);
|
||||
try {
|
||||
const response = await api.post('/sessions/active/log-set', setData);
|
||||
if (response.success) {
|
||||
const { newSet, activeExerciseId } = response;
|
||||
onSetAdded(newSet);
|
||||
|
||||
if (activePlan) {
|
||||
const currentStep = activePlan.steps[currentStepIndex];
|
||||
if (currentStep && currentStep.exerciseId === selectedExercise.id) {
|
||||
const nextIndex = currentStepIndex + 1;
|
||||
setCurrentStepIndex(nextIndex);
|
||||
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);
|
||||
// Optionally, show an error message to the user
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Binary file not shown.
@@ -219,6 +219,163 @@ router.put('/active', async (req: any, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Log a set to the active session
|
||||
router.post('/active/log-set', async (req: any, res) => {
|
||||
try {
|
||||
const userId = req.user.userId;
|
||||
const { exerciseId, reps, weight, distanceMeters, durationSeconds } = req.body;
|
||||
|
||||
// Find active session
|
||||
const activeSession = await prisma.workoutSession.findFirst({
|
||||
where: { userId, endTime: null },
|
||||
include: { sets: true }
|
||||
});
|
||||
|
||||
if (!activeSession) {
|
||||
return res.status(404).json({ error: 'No active session found' });
|
||||
}
|
||||
|
||||
// Get the highest order value from the existing sets
|
||||
const maxOrder = activeSession.sets.reduce((max, set) => Math.max(max, set.order), -1);
|
||||
|
||||
// Create the new set
|
||||
const newSet = await prisma.workoutSet.create({
|
||||
data: {
|
||||
sessionId: activeSession.id,
|
||||
exerciseId,
|
||||
order: maxOrder + 1,
|
||||
reps: reps ? parseInt(reps) : null,
|
||||
weight: weight ? parseFloat(weight) : null,
|
||||
distanceMeters: distanceMeters ? parseFloat(distanceMeters) : null,
|
||||
durationSeconds: durationSeconds ? parseInt(durationSeconds) : null,
|
||||
completed: true
|
||||
},
|
||||
include: { exercise: true }
|
||||
});
|
||||
|
||||
// Recalculate active step
|
||||
if (activeSession.planId) {
|
||||
const plan = await prisma.workoutPlan.findUnique({
|
||||
where: { id: activeSession.planId }
|
||||
});
|
||||
|
||||
if (plan) {
|
||||
const planExercises: { id: string }[] = JSON.parse(plan.exercises);
|
||||
const allPerformedSets = await prisma.workoutSet.findMany({
|
||||
where: { sessionId: activeSession.id }
|
||||
});
|
||||
|
||||
const performedCounts = new Map<string, number>();
|
||||
for (const set of allPerformedSets) {
|
||||
performedCounts.set(set.exerciseId, (performedCounts.get(set.exerciseId) || 0) + 1);
|
||||
}
|
||||
|
||||
let activeExerciseId = null;
|
||||
const plannedCounts = new Map<string, number>();
|
||||
for (const planExercise of planExercises) {
|
||||
const exerciseId = planExercise.id;
|
||||
plannedCounts.set(exerciseId, (plannedCounts.get(exerciseId) || 0) + 1);
|
||||
const performedCount = performedCounts.get(exerciseId) || 0;
|
||||
|
||||
if (performedCount < plannedCounts.get(exerciseId)!) {
|
||||
activeExerciseId = exerciseId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const mappedNewSet = {
|
||||
...newSet,
|
||||
exerciseName: newSet.exercise.name,
|
||||
type: newSet.exercise.type
|
||||
};
|
||||
|
||||
return res.json({ success: true, newSet: mappedNewSet, activeExerciseId });
|
||||
}
|
||||
}
|
||||
|
||||
// If no plan or plan not found, just return the new set
|
||||
const mappedNewSet = {
|
||||
...newSet,
|
||||
exerciseName: newSet.exercise.name,
|
||||
type: newSet.exercise.type
|
||||
};
|
||||
|
||||
res.json({ success: true, newSet: mappedNewSet, activeExerciseId: null });
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Update a set in the active session
|
||||
router.put('/active/set/:setId', async (req: any, res) => {
|
||||
try {
|
||||
const userId = req.user.userId;
|
||||
const { setId } = req.params;
|
||||
const { reps, weight, distanceMeters, durationSeconds } = req.body;
|
||||
|
||||
// Find active session
|
||||
const activeSession = await prisma.workoutSession.findFirst({
|
||||
where: { userId, endTime: null },
|
||||
});
|
||||
|
||||
if (!activeSession) {
|
||||
return res.status(404).json({ error: 'No active session found' });
|
||||
}
|
||||
|
||||
const updatedSet = await prisma.workoutSet.update({
|
||||
where: { id: setId },
|
||||
data: {
|
||||
reps: reps ? parseInt(reps) : null,
|
||||
weight: weight ? parseFloat(weight) : null,
|
||||
distanceMeters: distanceMeters ? parseFloat(distanceMeters) : null,
|
||||
durationSeconds: durationSeconds ? parseInt(durationSeconds) : null,
|
||||
},
|
||||
include: { exercise: true }
|
||||
});
|
||||
|
||||
const mappedUpdatedSet = {
|
||||
...updatedSet,
|
||||
exerciseName: updatedSet.exercise.name,
|
||||
type: updatedSet.exercise.type
|
||||
};
|
||||
|
||||
res.json({ success: true, updatedSet: mappedUpdatedSet });
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete a set from the active session
|
||||
router.delete('/active/set/:setId', async (req: any, res) => {
|
||||
try {
|
||||
const userId = req.user.userId;
|
||||
const { setId } = req.params;
|
||||
|
||||
// Find active session
|
||||
const activeSession = await prisma.workoutSession.findFirst({
|
||||
where: { userId, endTime: null },
|
||||
});
|
||||
|
||||
if (!activeSession) {
|
||||
return res.status(404).json({ error: 'No active session found' });
|
||||
}
|
||||
|
||||
await prisma.workoutSet.delete({
|
||||
where: { id: setId }
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete active session (quit without saving)
|
||||
router.delete('/active', async (req: any, res) => {
|
||||
try {
|
||||
|
||||
@@ -51,6 +51,15 @@ export const updateActiveSession = async (userId: string, session: WorkoutSessio
|
||||
await api.put('/sessions/active', session);
|
||||
};
|
||||
|
||||
export const deleteSetFromActiveSession = async (userId: string, setId: string): Promise<void> => {
|
||||
await api.delete(`/sessions/active/set/${setId}`);
|
||||
};
|
||||
|
||||
export const updateSetInActiveSession = async (userId: string, setId: string, setData: Partial<WorkoutSet>): Promise<WorkoutSet> => {
|
||||
const response = await api.put(`/sessions/active/set/${setId}`, setData);
|
||||
return response.updatedSet;
|
||||
};
|
||||
|
||||
export const deleteActiveSession = async (userId: string): Promise<void> => {
|
||||
await api.delete('/sessions/active');
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user