Skip to content

Commit 639b50a

Browse files
committed
Added Frotend Logic for Completing Workouts, PBs are good now
1 parent d79040b commit 639b50a

File tree

12 files changed

+318
-62
lines changed

12 files changed

+318
-62
lines changed

KonditionExpo/app/(tabs)/progress.tsx

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
Dimensions
1212
} from 'react-native';
1313
import { LineChart } from 'react-native-chart-kit';
14-
import { useWorkout, Workout } from '@/contexts/WorkoutContext';
14+
import { useWorkout, Workout, Exercise } from '@/contexts/WorkoutContext';
1515
import { Button } from '@/components/ui/Button';
1616
import { Input } from '@/components/ui/Input';
1717
import { router } from 'expo-router';
@@ -24,6 +24,7 @@ interface WorkoutItemProps {
2424
}
2525

2626
const WorkoutItem = ({ workout, onPress }: WorkoutItemProps) => {
27+
const { workouts, currentWorkout, exerSets, exerReps, exerWeights, startWorkout, getWorkouts, getExercises, getExercises_2} = useWorkout();
2728
const formatDate = (date: Date) => {
2829
return date.toLocaleDateString('en-US', {
2930
month: 'short',
@@ -32,7 +33,19 @@ const WorkoutItem = ({ workout, onPress }: WorkoutItemProps) => {
3233
});
3334
};
3435

35-
console.log("Workout Item: ", workout);
36+
//getWorkouts();
37+
//console.log(workout.id);
38+
39+
//getExercises(workout.id); // Set the exercises
40+
//console.log(getExercises_2()); // Get the exercises
41+
42+
//const exercises = getExercises_2();
43+
44+
//for (const exercise of exercises) {
45+
// exerSets += exercise.sets || 0;
46+
// exerReps += exercise.reps || 0;
47+
// exerWeight += exercise.weight || 0;
48+
//}
3649

3750
return (
3851
<TouchableOpacity style={styles.workoutItem} onPress={onPress}>
@@ -41,9 +54,9 @@ const WorkoutItem = ({ workout, onPress }: WorkoutItemProps) => {
4154
<Text style={styles.workoutDate}>{formatDate(workout.date)}</Text>
4255
</View>
4356
<View style={styles.workoutStats}>
44-
<Text style={styles.workoutStat}>{workout.exercises.sets} sets</Text>
45-
<Text style={styles.workoutStat}>{workout.exercises.reps} reps</Text>
46-
<Text style={styles.workoutStat}>{workout.exercises.weight} weight</Text>
57+
<Text style={styles.workoutStat}>{exerSets} sets</Text>
58+
<Text style={styles.workoutStat}>{exerReps} reps</Text>
59+
<Text style={styles.workoutStat}>{exerWeights} weights</Text>
4760
</View>
4861
</TouchableOpacity>
4962
);
@@ -52,10 +65,11 @@ const WorkoutItem = ({ workout, onPress }: WorkoutItemProps) => {
5265

5366

5467
const ProgressScreen = () => {
55-
const { workouts, currentWorkout, startWorkout, getWorkouts } = useWorkout();
68+
const { workouts, currentWorkout, startWorkout, getWorkouts, getExercises} = useWorkout();
5669
const { isAuthenticated, isLoading } = useAuth();
5770
const [showNewWorkoutModal, setShowNewWorkoutModal] = useState(false);
5871
const [newWorkoutName, setNewWorkoutName] = useState('');
72+
const [showFinishModal, setShowFinishModal] = useState(false);
5973
const backgroundColor = '#FFFFFF';
6074
const textColor = '#333';
6175
const tintColor = '#70A1FF';
@@ -211,6 +225,17 @@ const ProgressScreen = () => {
211225
/>
212226
</View>
213227

228+
{/*Finish Old Workout Button*/}
229+
<View style={styles.actionContainer}>
230+
<Button
231+
title="Finish Old Workout"
232+
onPress={() => setShowFinishModal(true)}
233+
size="lg"
234+
fullWidth
235+
style={{ backgroundColor: '#FFA07A' }}
236+
/>
237+
</View>
238+
214239
{/* Recent Workouts */}
215240
<View style={styles.workoutHistoryContainer}>
216241
<Text style={[styles.sectionTitle, { color: textColor }]}>
@@ -275,6 +300,58 @@ const ProgressScreen = () => {
275300
</View>
276301
</View>
277302
</Modal>
303+
304+
{/* Finish Workout Modal*/}
305+
<Modal
306+
visible={showFinishModal}
307+
transparent
308+
animationType="slide"
309+
onRequestClose={() => setShowFinishModal(false)}
310+
>
311+
<View style={styles.modalOverlay}>
312+
<View style={[styles.modalContent, { backgroundColor }]}>
313+
<Text style={[styles.modalTitle, { color: textColor }]}>
314+
Unfinished Workouts
315+
</Text>
316+
317+
{workouts.filter(w => !w.is_completed).length === 0 ? (
318+
<Text style={{ textAlign: 'center', color: textColor }}>
319+
No incomplete workouts.
320+
</Text>
321+
) : (
322+
workouts
323+
.filter(w => !w.is_completed)
324+
.map((workout) => (
325+
<TouchableOpacity
326+
key={workout.id}
327+
onPress={() => {
328+
setShowFinishModal(false);
329+
router.push({
330+
pathname: '/finish',
331+
params: { workoutId: workout.id },
332+
});
333+
}}
334+
style={{
335+
padding: 12,
336+
backgroundColor: '#F5F8FF',
337+
borderRadius: 10,
338+
marginBottom: 10,
339+
}}
340+
>
341+
<Text style={{ color: textColor }}>{workout.name}</Text>
342+
</TouchableOpacity>
343+
))
344+
)}
345+
346+
<Button
347+
title="Close"
348+
onPress={() => setShowFinishModal(false)}
349+
variant="outline"
350+
style={{ marginTop: 10 }}
351+
/>
352+
</View>
353+
</View>
354+
</Modal>
278355
</SafeAreaView>
279356
);
280357
};

KonditionExpo/app/finish.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useLocalSearchParams, router } from 'expo-router';
2+
import { View, Text, Button } from 'react-native';
3+
4+
export default function FinishWorkoutScreen() {
5+
const { workoutId } = useLocalSearchParams();
6+
7+
return (
8+
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
9+
<Text>Finish Workout Screen</Text>
10+
<Text>Workout ID: {workoutId}</Text>
11+
12+
<Button title="Back" onPress={() => router.back()} />
13+
</View>
14+
);
15+
}

KonditionExpo/app/home.tsx

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ const HomeScreen = () => {
146146
</View>
147147

148148
{/* Personal Bests */}
149-
150149
<View style={[styles.latestSection, { paddingBottom: 16 }]}>
151150
<View style={styles.sectionHeader}>
152151
<Text style={styles.sectionTitle}>Personal Bests</Text>
@@ -156,23 +155,27 @@ const HomeScreen = () => {
156155
</View>
157156

158157
{loading ? (
159-
<Text style={{ textAlign: 'center', color: '#666', marginBottom: 12 }}>Loading personal bests...</Text>
158+
<Text style={styles.pbLoading}>Loading personal bests...</Text>
160159
) : pbs.length === 0 ? (
161-
<Text style={{ textAlign: 'center', color: '#999', marginBottom: 12 }}>No personal bests yet. Start training to set new records!</Text>
160+
<Text style={styles.pbEmpty}>No personal bests yet. Start training to set new records!</Text>
162161
) : (
163162
testPbs.map((pb) => (
164-
<View key={pb.metric} style={styles.workoutItem}>
165-
<Image source={require('../assets/images/trophy.png')} style={styles.workoutIcon} />
166-
<View style={styles.workoutInfo}>
167-
<Text style={styles.workoutType}>{pb.metric}</Text>
168-
<Text style={styles.workoutMeta}>{pb.value}{pb.date}</Text>
163+
<View key={pb.exercise_name + pb.date_achieved} style={styles.pbCard}>
164+
<Image source={require('../assets/images/trophy.png')} style={styles.pbIcon} />
165+
<View style={styles.pbInfo}>
166+
<Text style={styles.pbTitle}>{pb.exercise_name}</Text>
167+
<View style={styles.pbDetails}>
168+
<Text style={styles.pbValue}>{pb.metric_value} pts</Text>
169+
<Text style={styles.pbDate}>{new Date(pb.date_achieved).toLocaleDateString()}</Text>
170+
</View>
169171
</View>
170172
</View>
171173
))
172174
)}
173175
</View>
174176

175177

178+
176179
</ScrollView>
177180
{/* ─── BMI Disclaimer Dialog ─── */}
178181
<Dialog
@@ -201,6 +204,52 @@ const HomeScreen = () => {
201204
};
202205

203206
const styles = StyleSheet.create({
207+
pbCard: {
208+
flexDirection: 'row',
209+
alignItems: 'center',
210+
backgroundColor: '#FFFFFF',
211+
borderRadius: 16,
212+
padding: 16,
213+
marginBottom: 12,
214+
shadowColor: '#000',
215+
shadowOpacity: 0.06,
216+
shadowRadius: 6,
217+
elevation: 2,
218+
},
219+
220+
pbIcon: {
221+
width: 40,
222+
height: 40,
223+
marginRight: 12,
224+
},
225+
226+
pbInfo: {
227+
flex: 1,
228+
justifyContent: 'center',
229+
},
230+
231+
pbTitle: {
232+
fontSize: 16,
233+
fontWeight: '600',
234+
color: '#333',
235+
marginBottom: 4,
236+
},
237+
238+
pbDetails: {
239+
flexDirection: 'row',
240+
justifyContent: 'space-between',
241+
},
242+
243+
pbValue: {
244+
fontSize: 14,
245+
color: '#70A1FF',
246+
fontWeight: '500',
247+
},
248+
249+
pbDate: {
250+
fontSize: 12,
251+
color: '#888',
252+
},
204253
container: { flex: 1, backgroundColor: '#FFFFFF' },
205254
content: { padding: 16, paddingBottom: 80 },
206255
header: { flexDirection: 'row', alignItems: 'center', marginBottom: 24 },

KonditionExpo/app/login.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ export default function LoginScreen() {
1919
const textColor = useThemeColor({}, 'text');
2020
const tintColor = useThemeColor({}, 'tint');
2121

22-
// Redirect if already authenticated
23-
useEffect(() => {
22+
// Redirect if already authenticated -- Might want to remove since i think we don't have logout
23+
/*useEffect(() => {
2424
if (isAuthenticated) {
2525
router.replace('/(tabs)');
2626
}
27-
}, [isAuthenticated]);
27+
}, [isAuthenticated]);*/
2828

2929
const handleLogin = async () => {
3030
setErrors({});

KonditionExpo/contexts/WorkoutContext.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ import { useAuth } from '@/contexts/AuthContext'; // adjust if needed
44

55
export interface Exercise {
66
id: string;
7+
workout_id: string;
78
name: string;
89
description?: string;
910
muscle_group: string;
10-
type: 'strength' | 'cardio' | 'flexibility';
11+
category: string;
12+
sets?: number;
13+
reps?: number;
14+
weight?: number;
15+
created_at: Date;
16+
updated_at?: Date;
1117
}
1218

1319
export interface WorkoutSet {
@@ -29,14 +35,18 @@ export interface Workout {
2935
id: string;
3036
date: Date;
3137
name: string;
32-
exercises: WorkoutExercise[];
38+
exercises: Exercise[]; //This won't be updated, instead use id to retrieve all exercises for a related workout
3339
duration: number; // in minutes
3440
notes?: string;
3541
}
3642

3743
interface WorkoutContextType {
3844
workouts: Workout[];
45+
exercises: Exercise[];
3946
currentWorkout: Workout | null;
47+
exerSets: number;
48+
exerReps: number;
49+
exerWeights: number;
4050
addWorkout: (workout: Workout) => Promise<void>; // <-- async now
4151
updateWorkout: (workoutId: string, workout: Workout) => void;
4252
getWorkouts: () => void;
@@ -47,14 +57,21 @@ interface WorkoutContextType {
4757
addSetToExercise: (exerciseId: string, set: Omit<WorkoutSet, 'id'>) => void;
4858
removeSetFromExercise: (exerciseId: string, setId: string) => void;
4959
updateSet: (exerciseId: string, setId: string, updatedSet: Partial<WorkoutSet>) => void;
60+
getExercises: (workoutID: string) => void;
61+
getExercises_2: () => Exercise[];
5062
}
5163

5264
const WorkoutContext = createContext<WorkoutContextType | undefined>(undefined);
5365

5466
export const WorkoutProvider = ({ children }: { children: ReactNode }) => {
5567
const [workouts, setWorkouts] = useState<Workout[]>([]);
68+
const [exercises, setExercises] = useState<Exercise[]>([]);
5669
const [currentWorkout, setCurrentWorkout] = useState<Workout | null>(null);
5770
const { token, isAuthenticated, isLoading } = useAuth(); //token of user
71+
const [exerSets, setExerSets] = useState(0);
72+
const [exerReps, setExerReps] = useState(0);
73+
const [exerWeights, setExerWeights] = useState(0);
74+
5875

5976
//NO WAIT FOR TOKEN, IS GIVING NULL IN WORKOUT CONTEXT
6077

@@ -128,6 +145,7 @@ export const WorkoutProvider = ({ children }: { children: ReactNode }) => {
128145
}));
129146

130147
setWorkouts(parsed);
148+
//await getExercises(.id);
131149
} catch (error) {
132150
console.error('Failed to load workouts:', error.response?.data || error.message);
133151
}
@@ -137,6 +155,29 @@ export const WorkoutProvider = ({ children }: { children: ReactNode }) => {
137155
setCurrentWorkout(null);
138156
};
139157

158+
const getExercises = async (workoutID: string) => {
159+
try{
160+
const exerReq = 'http://localhost:8000/api/v1/workouts/' + workoutID + '/exercises';
161+
const response = await axios.get(exerReq, {
162+
headers: {
163+
Authorization: `Bearer ${token}`,
164+
},
165+
});
166+
167+
//console.log("getExercises response: ", response);
168+
setExercises(response.data);
169+
for (const ex of exercises) {
170+
setExerSets(exerSets + ex.sets);
171+
setExerReps(exerReps + ex.reps);
172+
setExerWeights(exerWeights + ex.weight);
173+
}
174+
console.log(exercises);
175+
console.log(exerSets);
176+
} catch (error){
177+
console.log("Failed to get exercises for associated ID ", workoutID, ". ", error.response?.data || error.message)
178+
}
179+
};
180+
140181
//Everything below this is old - only updates local state rather than send backend requests
141182

142183
const updateWorkout = (workoutId: string, updatedWorkout: Workout) => { //Old
@@ -226,6 +267,9 @@ export const WorkoutProvider = ({ children }: { children: ReactNode }) => {
226267
<WorkoutContext.Provider value={{
227268
workouts,
228269
currentWorkout,
270+
exerSets,
271+
exerReps,
272+
exerWeights,
229273
addWorkout,
230274
updateWorkout,
231275
deleteWorkout,
@@ -236,6 +280,7 @@ export const WorkoutProvider = ({ children }: { children: ReactNode }) => {
236280
removeSetFromExercise,
237281
updateSet,
238282
getWorkouts,
283+
getExercises,
239284
}}>
240285
{children}
241286
</WorkoutContext.Provider>

0 commit comments

Comments
 (0)