diff --git a/fitness_tracker/package-lock.json b/fitness_tracker/package-lock.json index cbf84b9..1677918 100644 --- a/fitness_tracker/package-lock.json +++ b/fitness_tracker/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.13.0", "@material/tab": "^14.0.0", "@material/tabs": "^2.3.0", + "@mui/icons-material": "^6.1.2", "@mui/material": "^5.16.7", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", @@ -1963,9 +1964,10 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", - "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -3610,6 +3612,32 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.2.tgz", + "integrity": "sha512-7NNcjW5JoT9jHagrVbARA1o41vQY2xezDamtke+mEKKZmsJyejfRBOacSrPDfjZQ//lyhIjNKyzAwisxYJR47w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.6" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^6.1.2", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { "version": "5.16.7", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.7.tgz", @@ -4262,104 +4290,6 @@ "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@testing-library/dom": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", - "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@testing-library/dom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "peer": true, - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/@testing-library/dom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@testing-library/dom/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@testing-library/dom/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "peer": true - }, - "node_modules/@testing-library/dom/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/dom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@testing-library/jest-dom": { "version": "5.17.0", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", @@ -7752,15 +7682,6 @@ "node": ">= 0.8" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -18254,19 +18175,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/fitness_tracker/package.json b/fitness_tracker/package.json index 3e176a1..5eb2ae1 100644 --- a/fitness_tracker/package.json +++ b/fitness_tracker/package.json @@ -7,6 +7,7 @@ "@emotion/styled": "^11.13.0", "@material/tab": "^14.0.0", "@material/tabs": "^2.3.0", + "@mui/icons-material": "^6.1.2", "@mui/material": "^5.16.7", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", diff --git a/fitness_tracker/src/components/ExerciseAdder.jsx b/fitness_tracker/src/components/ExerciseAdder.jsx index 2698b70..ceb70e5 100644 --- a/fitness_tracker/src/components/ExerciseAdder.jsx +++ b/fitness_tracker/src/components/ExerciseAdder.jsx @@ -1,69 +1,112 @@ import React, { useState } from 'react'; -import buttons from '../module_CSS/buttons.module.css' -import styles from '../module_CSS/ExerciseLogger.module.css' -import { MenuItem, Select } from '@mui/material'; +import buttons from '../module_CSS/buttons.module.css'; +import styles from '../module_CSS/ExerciseLogger.module.css'; +import { MenuItem, Select, Tooltip } from '@mui/material'; -const ExerciseAdder = ({exerciseList, addExercise, cancelAddExercise }) => { - const [selectedExercise, setSelectedExercise] = useState(exerciseList[0]) +const ExerciseAdder = ({ exerciseList, addExercise, cancelAddExercise }) => { + const [selectedExercise, setSelectedExercise] = useState(exerciseList.length > 0 ? exerciseList[0] : { name: "No exercises added" }); - return ( - - {exercise.name} - - ))} - + + No Exercises Recorded + + + + ) : ( + + )} - {selectedExercise.weight} + {selectedExercise.weight || 0} - {selectedExercise.reps} + {selectedExercise.reps || 0} - {selectedExercise.setsGoal} + {selectedExercise.setsGoal || 0} 0 - - + + + + + + - ) -} + ); +}; export default ExerciseAdder; diff --git a/fitness_tracker/src/components/ExercisesDisplay.jsx b/fitness_tracker/src/components/ExercisesDisplay.jsx index f4797e1..4924523 100644 --- a/fitness_tracker/src/components/ExercisesDisplay.jsx +++ b/fitness_tracker/src/components/ExercisesDisplay.jsx @@ -51,16 +51,16 @@ const ExercisesDisplay = ({ exercises, setExercises }) => { const logWorkout = () => { // Calculate the score of the workout - let score = 0 + let volumeScore = 0 exercises.forEach((exercise, index )=> { - score += (( Number(setsLogged[index]) / Number(exercise.setsGoal)) * Number(exercise.weight)) + volumeScore += (( Number(setsLogged[index]) / Number(exercise.setsGoal)) * Number(exercise.weight) * Number(exercise.reps)) }) - score = Math.round(score) + volumeScore = Math.round(volumeScore) // Create workout in the database with only score and date - axios.post("http://localhost:4001/workout", { score: score, date: new Date() }, { withCredentials: true }) + axios.post("http://localhost:4001/workout", { score: volumeScore, date: new Date() }, { withCredentials: true }) .then((response) => { console.log("Workout created", response.data) diff --git a/fitness_tracker/src/components/RoutinesDisplay.jsx b/fitness_tracker/src/components/RoutinesDisplay.jsx index f5781a3..6000849 100644 --- a/fitness_tracker/src/components/RoutinesDisplay.jsx +++ b/fitness_tracker/src/components/RoutinesDisplay.jsx @@ -1,13 +1,13 @@ -import React, { useEffect, useState } from 'react'; -import Routine from './Routine'; -import NewRoutineModal from './NewRoutineModal'; -import styles from '../module_CSS/RoutinesDisplay.module.css' -import axios from 'axios'; +import React, { useEffect, useState } from "react"; +import Routine from "./Routine"; +import NewRoutineModal from "./NewRoutineModal"; +import styles from "../module_CSS/RoutinesDisplay.module.css"; +import axios from "axios"; const RoutinesDisplay = ({ onAddToTodayWorkout }) => { - const [routines, setRoutines] = useState([]); + const [routines, setRoutines] = useState([]); - /* + /* { name: "Leg Routine", date: "14th Aug", @@ -20,181 +20,225 @@ const RoutinesDisplay = ({ onAddToTodayWorkout }) => { } */ - const [isModalOpen, setIsModalOpen] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); - // Get routines from the backend - useEffect(() => { - axios.get("http://localhost:4001/routine", { withCredentials: true, }) + // Get routines from the backend + useEffect(() => { + axios + .get("http://localhost:4001/routine", { withCredentials: true }) .then((response) => { response.data.forEach((routine) => { - fetchExercises(routine); + fetchExercises(routine); }); // Routine exercises are added in the fetchExercises function, so we can just set the routines here setRoutines(response.data); - }) .catch((error) => { handleAuthError(error); console.error("An error occurred while fetching routines:", error); }); - } , []); - - // Get exercises for a routine, and add them to the routine object - const fetchExercises = (routine) => { - // Get the exercise IDs for the routine - axios.get(`http://localhost:4001/routine/${routine.id}/exercises`, { withCredentials: true, }) + }, []); + + // Get exercises for a routine, and add them to the routine object + const fetchExercises = (routine) => { + // Get the exercise IDs for the routine + axios + .get(`http://localhost:4001/routine/${routine.id}/exercises`, { + withCredentials: true, + }) .then((response) => { const exerciseIds = response.data.exerciseIds.join(","); // Make comma separated string of exercise IDs // Get the actual exercises info for the routine - axios.get(`http://localhost:4001/exercise/${exerciseIds}`, { withCredentials: true, }) + axios + .get(`http://localhost:4001/exercise/${exerciseIds}`, { + withCredentials: true, + }) .then((response) => { routine.exercises = response.data; }) .catch((error) => { handleAuthError(error); console.error("An error occurred while fetching exercises:", error); - } - ); + }); }) .catch((error) => { handleAuthError(error); console.error("An error occurred while fetching exercise ids:", error); }); - }; - - const addExerciseToRoutine = (routine, exercise) => { - // Add the exercise to the routine in the backend - axios.post("http://localhost:4001/exercise", { "routine_id": routine.id, ...exercise }, { withCredentials: true, }) - .then((response) => { - // Get the exercise ID - const exerciseId = response.data.id; - - // Add the exercise ID to the routine object - routine.exercises.forEach((exercise) => { - if (exercise.name === response.data.name) { - exercise.id = exerciseId; - } - }) - }) - .catch((error) => { - handleAuthError(error); - console.error("An error occurred while adding exercises:", error); - }); - }; + }; + + const addExerciseToRoutine = (routine, exercise) => { + // Add the exercise to the routine in the backend + axios + .post( + "http://localhost:4001/exercise", + { routine_id: routine.id, ...exercise }, + { withCredentials: true } + ) + .then((response) => { + // Get the exercise ID + const exerciseId = response.data.id.id; - const handleSaveRoutine = (newRoutine) => { - setIsModalOpen(false); + routine.exercises.forEach((e) => { + if (e.name === exercise.name) { + e.id = exerciseId; + } + }); + }) + .catch((error) => { + handleAuthError(error); + console.error("An error occurred while adding exercises:", error); + }); + }; - // Extract routine only data - const { name, muscles, date} = newRoutine; + const handleSaveRoutine = (newRoutine) => { + setIsModalOpen(false); - // Extract exercises only data - const exercises = newRoutine.exercises.map(({ name, setsGoal, reps, weight }) => ({ name, setsGoal, reps, weight })); + // Extract routine only data + const { name, muscles, date } = newRoutine; - // Create new routine object with no exercises - axios.post("http://localhost:4001/routine", {"name": name, "muscles": muscles, "date": date}, { withCredentials: true, }) - .then((response) => { - // Get the routine ID from the response and add it to the newRoutine object - const routineId = response.data.id.id; - newRoutine.id = routineId; + // Extract exercises only data + const exercises = newRoutine.exercises.map( + ({ name, setsGoal, reps, weight }) => ({ name, setsGoal, reps, weight }) + ); - // Add exercises to the routine in the backend - exercises.forEach((exercise) => { - addExerciseToRoutine(newRoutine, exercise); - }); - // Add the newRoutine object to the routines array in the frontend so that it is displayed immediately - setRoutines([...routines, newRoutine]); - } + // Create new routine object with no exercises + axios + .post( + "http://localhost:4001/routine", + { name: name, muscles: muscles, date: date }, + { withCredentials: true } ) + .then((response) => { + // Get the routine ID from the response and add it to the newRoutine object + const routineId = response.data.id.id; + newRoutine.id = routineId; + + // Add exercises to the routine in the backend + exercises.forEach((exercise) => { + addExerciseToRoutine(newRoutine, exercise); + }); + // Add the newRoutine object to the routines array in the frontend so that it is displayed immediately + setRoutines([...routines, newRoutine]); + }) .catch((error) => { handleAuthError(error); console.error("An error occurred while adding routine:", error); - }); - }; - - const handleDeleteRoutine = (routineToDelete) => { - // Delete the routine in the backend - axios.delete(`http://localhost:4001/routine/${routineToDelete.id}`, { withCredentials: true, }) - .then((response) => { - // Delete the routine in the frontend - setRoutines(routines.filter(routine => routine !== routineToDelete)); - }) - .catch((error) => { - handleAuthError(error); - console.error("An error occurred while deleting routine:", error); - }); - }; - - const handleEditRoutine = (updatedRoutine, index) => { - // Update the routine in the backend - const { id, name, muscles, date } = updatedRoutine; - // Update the routine information - axios.put(`http://localhost:4001/routine/${id}`, { "name": name, "muscles": muscles, "date": date }, { withCredentials: true, }) - .then((response) => { - // Update the exercises in the backend - updatedRoutine.exercises.forEach((exercise) => { - axios.patch(`http://localhost:4001/exercise/${exercise.id}`, { "name": exercise.name, "setsGoal": exercise.setsGoal, "reps": exercise.reps, "weight": exercise.weight, "routine_id": id }, { withCredentials: true, }) - .then((response) => { - // Exercise updated successfully, do nothing - }) - .catch((error) => { - handleAuthError(error); - if (error.response.status === 404) { - // Exercise not found, so we need to create a new exercise - addExerciseToRoutine(updatedRoutine, exercise); - } else { - console.error("An error occurred while updating exercises:", error); - } + }); + }; + + const handleDeleteRoutine = (routineToDelete) => { + // Delete the routine in the backend + axios + .delete(`http://localhost:4001/routine/${routineToDelete.id}`, { + withCredentials: true, + }) + .then((response) => { + // Delete the routine in the frontend + setRoutines(routines.filter((routine) => routine !== routineToDelete)); + }) + .catch((error) => { + handleAuthError(error); + console.error("An error occurred while deleting routine:", error); + }); + }; + + const handleEditRoutine = (updatedRoutine, index) => { + // Update the routine in the backend + const { id, name, muscles, date } = updatedRoutine; + // Update the routine information + axios + .put( + `http://localhost:4001/routine/${id}`, + { name: name, muscles: muscles, date: date }, + { withCredentials: true } + ) + .then((response) => { + // Update the exercises in the backend + updatedRoutine.exercises.forEach((exercise) => { + axios + .patch( + `http://localhost:4001/exercise/${exercise.id}`, + { + name: exercise.name, + setsGoal: exercise.setsGoal, + reps: exercise.reps, + weight: exercise.weight, + routine_id: id, + }, + { withCredentials: true } + ) + .then((response) => { + // Exercise updated successfully, do nothing + }) + .catch((error) => { + handleAuthError(error); + if (error.response.status === 404) { + // Exercise not found, so we need to create a new exercise + addExerciseToRoutine(updatedRoutine, exercise); + } else { + console.error( + "An error occurred while updating exercises:", + error + ); + } }); - }); - }) - .catch((error) => { - handleAuthError(error); - console.error("An error occurred while updating routine:", error); - } - ); + }); + }) + .catch((error) => { + handleAuthError(error); + console.error("An error occurred while updating routine:", error); + }); - // Update the routine in the frontend - setRoutines(routines.map((routine, i) => i === index ? updatedRoutine : routine)); - }; - - const handleAuthError = (error) => { - if (error.response && error.response.status === 401) { - alert("Not logged in (Or an error has occurred), cannot access dashboard"); - window.location.href = "/login"; - } - }; - - return ( -
-

Workout Routines

- {isModalOpen ? - : - - } - {isModalOpen && - setIsModalOpen(false)} - /> - } - {routines.map((routine, index) => ( - handleEditRoutine(updatedRoutine, index)} - onDelete={() => handleDeleteRoutine(routine)} - onAddToToday={() => onAddToTodayWorkout(routine)} - /> - ))} -
+ // Update the routine in the frontend + setRoutines( + routines.map((routine, i) => (i === index ? updatedRoutine : routine)) ); + }; + + const handleAuthError = (error) => { + if (error.response && error.response.status === 401) { + alert( + "Not logged in (Or an error has occurred), cannot access dashboard" + ); + window.location.href = "/login"; + } + }; + + return ( +
+

Workout Routines

+ {isModalOpen ? ( + + ) : ( + + )} + {isModalOpen && ( + setIsModalOpen(false)} + /> + )} + {routines.map((routine, index) => ( + handleEditRoutine(updatedRoutine, index)} + onDelete={() => handleDeleteRoutine(routine)} + onAddToToday={() => onAddToTodayWorkout(routine)} + /> + ))} +
+ ); }; export default RoutinesDisplay; diff --git a/fitness_tracker/src/components/TabDisplay.jsx b/fitness_tracker/src/components/TabDisplay.jsx index 521cf28..82628be 100644 --- a/fitness_tracker/src/components/TabDisplay.jsx +++ b/fitness_tracker/src/components/TabDisplay.jsx @@ -6,6 +6,7 @@ import Tab from '@mui/material/Tab'; import Box from '@mui/material/Box'; import RoutinesDisplay from './RoutinesDisplay'; import ExercisesDisplay from './ExercisesDisplay'; +import WorkoutHistoryDisplay from './WorkoutHistoryDisplay'; import styles from '../module_CSS/TabDisplay.module.css'; function CustomTabPanel(props) { @@ -73,6 +74,11 @@ function TabDisplay() { label="Today's Workout" {...a11yProps(1)} /> + @@ -83,6 +89,10 @@ function TabDisplay() { + + + + ); diff --git a/fitness_tracker/src/components/WorkoutHistoryDisplay.jsx b/fitness_tracker/src/components/WorkoutHistoryDisplay.jsx new file mode 100644 index 0000000..de460de --- /dev/null +++ b/fitness_tracker/src/components/WorkoutHistoryDisplay.jsx @@ -0,0 +1,91 @@ +import React, { useState, useEffect } from "react"; +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, +} from "@mui/material"; +import axios from "axios"; +import WorkoutHistoryRow from "./WorkoutHistoryRow"; +import styles from "../module_CSS/WorkoutHistory.module.css"; + +function WorkoutHistoryDisplay() { + const [workouts, setWorkouts] = useState([]); + const [exerciseList, setExerciseList] = useState({}); + + // Fetch workouts and its associated exercises on component mount + useEffect(() => { + fetchWorkouts(); + }, []); + + const fetchExercises = async (workoutId) => { + try { + const response = await axios.get( + `http://localhost:4001/workout/${workoutId}/exercises`, + { withCredentials: true } + ); + const exercisesData = response.data; + setExerciseList((prevState) => ({ + ...prevState, + [workoutId]: exercisesData, // Map exercises by workout ID + })); + } catch (error) { + console.error( + `Error fetching exercises for workout ${workoutId}:`, + error + ); + } + }; + + const fetchWorkouts = async () => { + try { + const response = await axios.get("http://localhost:4001/workout", { + withCredentials: true, + }); + const workoutsData = response.data; + setWorkouts(workoutsData); + + // Fetch exercises for each workout + workoutsData.forEach((workout) => { + fetchExercises(workout.id); + }); + } catch (error) { + console.error("Error fetching workouts:", error); + } + }; + + return ( + + + + + + Workout Date + + + + + + {workouts.map((workout) => ( + + ))} + +
+
+ ); +} + +export default WorkoutHistoryDisplay; diff --git a/fitness_tracker/src/components/WorkoutHistoryRow.jsx b/fitness_tracker/src/components/WorkoutHistoryRow.jsx new file mode 100644 index 0000000..292b479 --- /dev/null +++ b/fitness_tracker/src/components/WorkoutHistoryRow.jsx @@ -0,0 +1,169 @@ +import React, { useState } from "react"; +import { + TableRow, + TableCell, + IconButton, + Box, + Table, + TableBody, + TableContainer, + TableHead, + Collapse, + Typography, +} from "@mui/material"; +import { KeyboardArrowDown, KeyboardArrowUp } from "@mui/icons-material"; +import styles from "../module_CSS/WorkoutHistory.module.css"; + +const WorkoutHistoryRow = ({ workout, exercises }) => { + const [showDetails, setShowDetails] = useState(false); + + // Toggle expanded view containing exercise details + const handleRowClick = () => { + setShowDetails(!showDetails); + }; + + const getExerciseScore = (exercise) => { + return ( + (exercise.sets_completed / exercise.setsGoal) * + exercise.reps * + exercise.weight + ).toFixed(2); + }; + + return ( + <> + {/* Workout Date Row (Collapsed View) */} + + + {new Date(workout.date).toLocaleString()} + + + + {showDetails ? : } + + + + + {/* Expanded View */} + + + + + {/* Total Score Display */} + + TOTAL SCORE: {workout.score} + + + {/* Exercise Breakdown */} + + + + + + Exercise + + + Sets Completed + + + Reps + + + Weight (kg) + + + Score + + + + + {exercises && exercises.length > 0 ? ( + exercises.map((exercise, index) => ( + + + {exercise.name} + + + {exercise.sets_completed} / {exercise.setsGoal} + + + {exercise.reps} + + + {exercise.weight} + + + {getExerciseScore(exercise)} + + + )) + ) : ( + + + No exercises found for this workout. + + + )} + +
+
+
+
+
+
+ + ); +}; + +export default WorkoutHistoryRow; diff --git a/fitness_tracker/src/module_CSS/WorkoutHistory.module.css b/fitness_tracker/src/module_CSS/WorkoutHistory.module.css new file mode 100644 index 0000000..8cc8b0c --- /dev/null +++ b/fitness_tracker/src/module_CSS/WorkoutHistory.module.css @@ -0,0 +1,87 @@ +@import './colours.module.css'; /* Import colors from the same file */ + +:root { + --fs-xl: clamp(2rem, 2vw + 1rem, 4rem); + --fs-600: clamp(1.5rem, 1.8vw + 1rem, 3rem); + --fs-400: clamp(1rem, 1.5vw + 0.8rem, 2rem); + --fs-300: clamp(0.8rem, 1.2vw + 0.6rem, 1.4rem); + --fs-200: clamp(0.7rem, 1vw + 0.5rem, 1.2rem); + --fs-100: clamp(0.6rem, 0.8vw + 0.4rem, 1rem); +} + +/* Container for the table */ +.container { + padding: 12px; + border-radius: 4px; + background-color: var(--dark-bg-color) !important; +} + +/* Styles for the table itself */ +.table { + width: 100%; + border-collapse: collapse; + font-size: 1em; + text-align: left; + background-color: var(--table-dark-bg-color) !important; +} + +/* Styles for the table header */ +.tableHeaderBgColor { + background-color: var(--table-header-bg-color) !important; +} + +/* Styles for table rows */ +.tableOddRowsColor { + background-color: var(--table-odd-rows-color) !important; +} + +.tableEvenRowsColor { + background-color: var(--table-even-rows-color) !important; +} + +/* Text colors */ +.primaryTextColor { + color: var(--primary-text-color) !important; +} + +/* Borders for the table */ +.table td, .table th { + border-bottom: 1px solid var(--table-border-color) !important; +} + +.table tbody tr:last-child { + border-bottom: 2px solid var(--table-border-color) !important; +} + +/* Hover effect */ +.table tbody tr:hover { + background-color: var(--table-row-hover-bg-color) !important; +} + +/* Icon buttons */ +.iconButton { + color: var(--primary-text-color) !important; +} + +/* Ensure icon buttons inherit colors properly */ +.MuiIconButton-root { + color: var(--primary-text-color) !important; +} + +/* MUI Components Typography*/ +.tableDate { + font-size: var(--fs-300) !important; + color: var(--primary-text-color) !important; +} + +.tableTotalScore { + font-size: var(--fs-300) !important; + color: var(--primary-text-color) !important; + margin-bottom: 0.8rem !important; + font-weight: 600 !important; +} + +.tableExerciseData { + font-size: var(--fs-200); + color: var(--primary-text-color) !important; +} diff --git a/server/.gitignore b/server/.gitignore index 5b450e0..7fd633b 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -4,3 +4,6 @@ /node_modules /db/database.sqlite /db/test_database.sqlite + +#Environment Configuration Files +.env \ No newline at end of file diff --git a/server/controllers/workouts-controller.js b/server/controllers/workouts-controller.js index 1d1a40c..bbc6edf 100644 --- a/server/controllers/workouts-controller.js +++ b/server/controllers/workouts-controller.js @@ -116,7 +116,6 @@ const addExercises = async (req, res) => { const { id } = req.params; // Expecting workoutId in the URL parameters const { exercises } = req.body; // Expecting workoutId and list of exerciseIds (with sets completed) in the request body - if (!id || !Array.isArray(exercises) || exercises.length === 0) { return res.status(400).json({ error: 'Workout ID and a list of exercise IDs (with sets completed) are required.' }); } @@ -153,7 +152,7 @@ const getExercises = async (req, res) => { .join('exercises as e', 'e.id', 'we.exercise_id') .where('we.workout_id', id) .andWhere('we.user_id', req.session.user.user_id) - .select('e.id', 'we.id', 'e.name', 'e.muscle_group', 'e.sets', 'e.weight', 'we.sets_completed'); + .select('e.id', 'we.id', 'e.name', 'e.reps', 'e.setsGoal', 'e.weight', 'we.sets_completed'); if (exercises.length === 0) { return res.status(404).json({ message: 'No exercises found for this workout.' });