Updated workout forms for compatability with database

This commit is contained in:
Rory Healy 2022-10-24 23:48:14 +11:00
parent cef2d9fd84
commit 6b748f75fe
11 changed files with 493 additions and 380 deletions

View File

@ -20,7 +20,7 @@ import { useAuth } from '../../context/authUserContext';
// Get reference to users collection
const usersCollectionRef = collection(db, 'users');
export default function Element({ element, type, onDelete }) {
export default function Element({ element, type, onDelete, allowEditing }) {
/* Paths of the images of the favourite button */
const star = '/images/star.png';
const starFilled = '/images/starFilled.png';
@ -120,8 +120,7 @@ export default function Element({ element, type, onDelete }) {
const makeButton = () => {
if (authUser) {
/* Crude check for checking if the element is an exercise or workout */
if (element.instructions !== undefined) {
if (type === 'exercises') {
return (
<EditButton
type="exercise"
@ -142,33 +141,37 @@ export default function Element({ element, type, onDelete }) {
}
return null;
};
return (
<div className={styles.element}>
<Image
src={element.imgSrc}
alt={element.imgAlt}
height={84}
width={120}
width={100}
height={100}
/>
<div className={styles.txt}>
<h1>{element.name}</h1>
</div>
<div className={styles.star}>
<form>
<input
type="image"
src={imgPath}
height={38}
width={38}
alt="star"
onClick={toggleStar}
/>
</form>
</div>
<div className={styles.buttons}>
<div className={styles.star}>
<form>
<Image
src={imgPath}
alt="star"
width={50}
height={50}
onClick={toggleStar}
/>
</form>
</div>
<div className={styles.star}>{makeButton()}</div>
{allowEditing !== undefined && (
<div className={styles.star}>{makeButton()}</div>
)}
</div>
</div>
);
}

View File

@ -19,6 +19,7 @@ import SelectedElement from './SelectedElement';
* @param {*} setSelected The function that sets the state of selected
* @param {*} type Either "exercises" or "workouts"
* @param {*} onDelete The callback function to handle an element being deleted from the list.
* @param {*} allowEditing True if the edit button should appear on the elements.
* @returns
*/
export default function List({
@ -28,6 +29,7 @@ export default function List({
setSelected,
type,
onDelete,
allowEditing,
}) {
// A function to handle when a new element is selected
const handleChange = (e) => {
@ -58,21 +60,30 @@ export default function List({
vertical
name="button-list"
>
{filteredList.map((element) => (
<ToggleButton
key={element.id}
id={`${listType}-${element.id}`}
variant="light"
name={listType}
value={element}
>
{selected === element.name ? (
<SelectedElement element={element} type={type} />
) : (
<Element element={element} type={type} onDelete={onDelete} />
)}
</ToggleButton>
))}
{filteredList.length === 0 ? (
<h3>No {type} available</h3>
) : (
filteredList.map((element) => (
<ToggleButton
key={element.id}
id={`${listType}-${element.id}`}
variant="light"
name={listType}
value={element}
>
{selected === element.name ? (
<SelectedElement element={element} type={type} />
) : (
<Element
element={element}
type={type}
onDelete={onDelete}
allowEditing={allowEditing}
/>
)}
</ToggleButton>
))
)}
</ToggleButtonGroup>
</div>
);

View File

@ -30,12 +30,12 @@ export default function WorkoutElement({ element }) {
<Col xs={6}>
<Row>
<div className={styles.sr}>
<p>{element.sets} sets</p>
<p>{element.reps} reps</p>
</div>
</Row>
<Row>
<div className={styles.sr}>
<p>{element.reps} reps</p>
<p>{element.sets} sets</p>
</div>
</Row>
</Col>

View File

@ -10,8 +10,17 @@ export default function handler(req, res) {
body.imgAlt = `Diagram for how to perform a ${body.name}`;
}
if (!body.name || !body.muscleGroups || !body.exercises) {
return res.status(400).json({ data: 'Required fields not found.' });
if (!body.muscleGroups) {
body.muscleGroups = [];
}
/* Cannot have workouts without a name or exercises */
if (!body.name) {
return res.status(400).json({ error: 'Workout name required.' });
}
if (body.exercises.length < 1) {
return res.status(400).json({ error: 'Workout contains no exercises.' });
}
return res.status(200).json({ data: req.body });

View File

@ -2,14 +2,18 @@
import React, { useEffect, useRef, useState } from 'react';
// Next components
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
// Bootstrap components
import Button from 'react-bootstrap/Button';
import Container from 'react-bootstrap/Container';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import Modal from 'react-bootstrap/Modal';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Row from 'react-bootstrap/Row';
import Tooltip from 'react-bootstrap/Tooltip';
// Firebase
import {
@ -18,35 +22,27 @@ import {
query,
orderBy,
getDocs,
getDoc,
doc,
} from 'firebase/firestore';
import { db } from '../../../firebase-config';
// Custom components
import CustomAlert from '../../../components/EditButton/CustomAlert';
import List from '../../../components/List/List';
import TopNavbar from '../../../components/Navbar/Navbar';
// Styles
import styles from '../../../styles/EditButton.module.css';
// Get muscle list
import muscles from '../../../public/muscles.json' assert { type: 'json' };
// Get reference to exercises and workouts collections
const workoutsCollectionRef = collection(db, 'workouts');
const exercisesCollectionRef = collection(db, 'exercises');
function WorkoutForm() {
const router = useRouter();
const { id } = router.query;
/* Ensures that the database is only queried once for data */
const isFirstLoad = useRef(false);
/* Handles state for the checkboxes */
const [checkboxes, setCheckboxes] = useState([]);
/* Handles state for validation of form */
// TODO: Form validation
// const [validated, setValidated] = useState(false);
@ -60,151 +56,85 @@ function WorkoutForm() {
setAlertActive({});
};
/* Used to manage the list of chosen exercises in the form */
const chosenExercises = useRef([]);
/* Used to manage the list of chosen muscle groups in the form */
const chosenMuscleGroups = useRef([]);
/* Used to get a list of checkboxes for exercises to choose for a workout
* TODO: Replace this with just a List component.
*/
const [exerciseOptions, setExerciseOptions] = useState([]);
/* Used to store the ids of exercises that have been added to the workout. */
const [exerciseGroups, setExerciseGroups] = useState([]);
/* Used to store a list of the exercises that can be included in the workout */
const [exercises, setExercises] = useState(undefined);
/* Used to store the selected exercise in the 'Add new exercise' modal */
const [selectedExercise, setSelectedExercise] = useState({});
useEffect(() => {
const getExercises = async () => {
const q = query(exercisesCollectionRef, orderBy('name'));
const data = await getDocs(q);
setExercises(data);
};
const updateChosenMuscles = (ex) => {
if (chosenMuscleGroups.current.includes(ex.target.value)) {
const filtered = chosenMuscleGroups.current.filter(
(i) => i !== ex.target.value
);
chosenMuscleGroups.current = filtered;
} else {
chosenMuscleGroups.current.push(ex.target.value);
}
};
const updateChosenExercises = (ex) => {
if (chosenExercises.current.includes(ex.target.value)) {
const filtered = chosenExercises.current.filter(
(i) => i !== ex.target.value
);
chosenExercises.current = filtered;
} else {
chosenExercises.current.push(ex.target.value);
}
};
const makeCheckboxes = () => {
const checkboxColumns = [];
for (let i = 0; i < muscles.length; i += 1) {
const { group, musclesList } = muscles[i];
const boxes = [];
for (let j = 0; j < musclesList.length; j += 1) {
const { mId, name } = musclesList[j];
boxes.push(
<div className="mb-3" key={mId}>
<Form.Check
type="checkbox"
id="workoutMuscleGroups"
value={name}
label={name}
key={name}
onChange={updateChosenMuscles}
/>
</div>
);
}
/* TODO: This is still giving unique key errors, not sure why. */
checkboxColumns.push(
<Col key={group}>
<b>{group}</b>
{boxes}
</Col>
);
}
return checkboxColumns;
};
const makeExerciseOptions = () => {
if (exercises !== undefined) {
return exercises.docs.map((document) => (
<div className="mb-3" key={document.id}>
<Row>
<Col xs={4}>
<Form.Control placeholder={document.data().name} readOnly />
</Col>
<Col xs={2}>
<Form.Control
placeholder="Reps"
onChange={updateChosenExercises}
/>
</Col>
<Col xs={2}>
<Form.Control
placeholder="Sets"
onChange={updateChosenExercises}
/>
</Col>
</Row>
{/* <Form.Check
type="checkbox"
id="chosenOptions"
value={document.id}
label={document.data().name}
key={document.data().name}
onChange={updateChosenExercises}
/> */}
</div>
));
}
return null;
setExercises(data.docs.map((d) => ({ ...d.data(), id: d.id })));
};
if (!isFirstLoad.current) {
getExercises();
isFirstLoad.current = true;
}
}, []);
if (isFirstLoad.current) {
setCheckboxes(makeCheckboxes());
setExerciseOptions(makeExerciseOptions());
/* Keeps track of which index the exercise is in the workout */
const index = useRef(0);
const updateExercises = (ex) => {
setExerciseGroups(exerciseGroups.concat(ex));
setSelectedExercise({});
};
const deleteExercise = (toDelete) => {
const newGroups = exerciseGroups.filter((ex) => ex.index !== toDelete);
for (let i = 0; i < exerciseGroups.length - 1; i += 1) {
newGroups[i].index = i;
}
}, [exercises]);
setExerciseGroups(newGroups);
index.current -= 1;
};
/* Handles state for the add exercise modal */
const [isModalOpen, setModalOpen] = useState(false);
const handleModalOpen = () => setModalOpen(true);
const handleModalClose = () => {
/* This check is to differentiate between cancelling or actually adding an exercise */
if (selectedExercise.id) {
updateExercises({ ...selectedExercise, index: index.current });
index.current += 1;
}
setModalOpen(false);
};
/* Transforms an exercise into the correct data structure for the form submission */
const getExercisesFromId = async () => {
const promiseList = [];
for (let i = 0; i < chosenExercises.current.length; i += 1) {
promiseList.push(
getDoc(doc(db, 'exercises', chosenExercises.current[i]))
);
}
const exerciseList = await Promise.all(promiseList);
const list = [];
const getRepsSetsMuscles = (values) => {
const exercisesList = [];
const muscleGroupsList = [];
for (let i = 0; i < exerciseList.length; i += 1) {
for (let i = 0; i < exerciseGroups.length; i += 1) {
/* Not sure why, but I can't seem to access the data directly here.
* So this is a workaround.
*/
const obj = { ...exerciseList[i].data() };
const obj = { ...exerciseGroups[i] };
delete obj.instructions;
delete obj.equipment;
delete obj.muscleGroups;
delete obj.videoURL;
list.push(obj);
// Add sets and reps to this
/* Get the muscle groups from the exercise */
for (let j = 0; j < obj.muscleGroups.length; j += 1) {
if (!muscleGroupsList.includes(obj.muscleGroups[j])) {
muscleGroupsList.push(obj.muscleGroups[j]);
}
}
delete obj.muscleGroups;
obj.reps = Number(values[`${obj.id}-reps`].value);
obj.sets = Number(values[`${obj.id}-sets`].value);
exercisesList.push(obj);
}
return list;
return [exercisesList, muscleGroupsList.sort()];
};
/* Handles the submission of forms. */
@ -212,16 +142,15 @@ function WorkoutForm() {
/* Prevent automatic submission and refreshing of the page. */
event.preventDefault();
const exercisesList = await getExercisesFromId();
const [exercisesList, muscleGroups] = getRepsSetsMuscles(event.target);
/* TODO: Implement muscleGroups and image uploading */
const data = {
name: event.target.workoutName.value,
imgSrc: '/images/push-ups.png',
imgAlt: `Picture of ${event.target.workoutName.value}`,
muscleGroups: chosenMuscleGroups.current,
muscleGroups,
exercises: exercisesList,
id,
};
/* Send the form data to the API and get a response */
@ -235,6 +164,15 @@ function WorkoutForm() {
/* Get the response, add the document, create an alert, then redirect. */
const result = await response.json();
if (result.error) {
handleAlertOpen({
heading: 'Error',
body: result.error,
variant: 'danger',
});
return;
}
addDoc(workoutsCollectionRef, result.data)
.then(() => {
handleAlertOpen({
@ -272,24 +210,24 @@ function WorkoutForm() {
return (
<div className={styles.form}>
<h2>Creating new workout</h2>
<div style={{ padding: '0 10vw' }}>
<h2>Creating new workout</h2>
</div>
<Form onSubmit={handleSubmit} action="/api/workout" method="post">
<Form.Group>
<Form.Label>Workout name</Form.Label>
<Form
onSubmit={handleSubmit}
action="/api/workout"
method="post"
className={styles.form}
>
<div className={styles.formname}>
<Form.Label>Enter workout name:</Form.Label>
<Form.Control
id="workoutName"
type="text"
placeholder="Enter workout name"
/>
</Form.Group>
<Form.Group>
<Form.Label>Select targeted areas</Form.Label>
<Container fluid>
<Row>{checkboxes}</Row>
</Container>
</Form.Group>
</div>
{/* Not going to work just yet */}
{/* <Form.Group controlId="formThumbnail">
@ -303,16 +241,101 @@ function WorkoutForm() {
</Form.Group> */}
<Form.Group>
<Form.Label>Select exercises to include</Form.Label>
{exerciseOptions}
<Form.Label>Exercises in this workout:</Form.Label>
<div className={styles.formexercises}>
{exerciseGroups.length === 0 ? (
<p>None</p>
) : (
exerciseGroups.map((ex) => (
<Row className="mb-3" key={ex.index}>
<Col xs={5} className="mt-2">
{ex.name}
</Col>
<Col xs={1} className="mt-2">
Reps
</Col>
<Col xs={2}>
<Form.Control id={`${ex.id}-reps`} />
</Col>
<Col xs={1} className="mt-2">
Sets
</Col>
<Col xs={2}>
<Form.Control id={`${ex.id}-sets`} />
</Col>
<Col xs={1} className="mt-2">
{/* TODO: Make this tooltip appear next to the image */}
<OverlayTrigger
overlay={<Tooltip>Delete {ex.name}</Tooltip>}
>
{({ ref }) => (
<Image
src="/images/delete.svg"
alt={`Delete ${ex.name}`}
height={20}
width={20}
onClick={() => deleteExercise(ex.index)}
lazyRoot={ref}
/>
)}
</OverlayTrigger>
</Col>
</Row>
))
)}
</div>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
<div className={styles.formbuttons}>
<Button variant="primary" onClick={handleModalOpen}>
Add an exercise
</Button>
<div>
<Link href="/workouts" passHref>
<Button variant="secondary">Cancel</Button>
</Link>{' '}
<Button variant="primary" type="submit">
Submit
</Button>
</div>
</div>
</Form>
{displayAlert(isAlertActive)}
<Modal show={isModalOpen} onHide={handleModalClose} centered size="lg">
<Modal.Header>
<Modal.Title>
<p>Add an exercise</p>
</Modal.Title>
</Modal.Header>
<Modal.Body>
{exercises && (
<List
list={exercises}
listType="radio"
type="exercises"
setSelected={setSelectedExercise}
/>
)}
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleModalClose}>
Cancel
</Button>
<Button variant="primary" onClick={handleModalClose}>
Add
</Button>
</Modal.Footer>
</Modal>
</div>
);
}

View File

@ -2,37 +2,39 @@
import React, { useEffect, useRef, useState } from 'react';
// Next components
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
// Bootstrap components
import Button from 'react-bootstrap/Button';
import Container from 'react-bootstrap/Container';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import Modal from 'react-bootstrap/Modal';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Row from 'react-bootstrap/Row';
import Tooltip from 'react-bootstrap/Tooltip';
// Firebase
import {
getDoc,
collection,
updateDoc,
doc,
query,
orderBy,
getDocs,
getDoc,
doc,
} from 'firebase/firestore';
import { db } from '../../../firebase-config';
// Custom components
import CustomAlert from '../../../components/EditButton/CustomAlert';
import List from '../../../components/List/List';
import TopNavbar from '../../../components/Navbar/Navbar';
// Styles
import styles from '../../../styles/EditButton.module.css';
// Get muscle list
import muscles from '../../../public/muscles.json' assert { type: 'json' };
// Get reference to exercises and workouts collections
const exercisesCollectionRef = collection(db, 'exercises');
@ -40,15 +42,9 @@ function WorkoutForm() {
const router = useRouter();
const { id } = router.query;
/* Ensures that the Firestore is only contacted once for data */
/* Ensures that the database is only queried once for data */
const isFirstLoad = useRef(false);
/* Handles state for the workout */
const [workout, setWorkout] = useState({});
/* Handles state for the checkboxes */
const [checkboxes, setCheckboxes] = useState([]);
/* Handles state for validation of form */
// TODO: Form validation
// const [validated, setValidated] = useState(false);
@ -62,134 +58,38 @@ function WorkoutForm() {
setAlertActive({});
};
/* Used to manage the list of chosen exercises in the form */
const chosenExercises = useRef([]);
/* Used to store the ids of exercises that have been added to the workout. */
const [exerciseGroups, setExerciseGroups] = useState([]);
/* Used to manage the list of chosen muscle groups in the form */
const chosenMuscleGroups = useRef([]);
/* Used to get a list of checkboxes for exercises to choose for a workout */
const [exerciseOptions, setExerciseOptions] = useState([]);
/* Used to ensure that the exercises collection is queried only once */
/* Used to store a list of the exercises that can be included in the workout */
const [exercises, setExercises] = useState(undefined);
useEffect(() => {
const getWorkout = async () => {
const workoutDoc = await getDoc(doc(db, 'workouts', id));
chosenExercises.current = workoutDoc.data().exercises;
chosenMuscleGroups.current = workoutDoc.data().muscleGroups;
setWorkout(workoutDoc.data());
};
/* Stores the current workout being edited. */
const [workout, setWorkout] = useState({});
/* Used to store the selected exercise in the 'Add new exercise' modal */
const [selectedExercise, setSelectedExercise] = useState({});
useEffect(() => {
const getExercises = async () => {
const q = query(exercisesCollectionRef, orderBy('name'));
const data = await getDocs(q);
setExercises(data);
setExercises(data.docs.map((d) => ({ ...d.data(), id: d.id })));
};
const updateChosenMuscles = (ex) => {
if (chosenMuscleGroups.current.includes(ex.target.value)) {
const filtered = chosenMuscleGroups.current.filter(
(i) => i !== ex.target.value
);
chosenMuscleGroups.current = filtered;
} else {
chosenMuscleGroups.current.push(ex.target.value);
}
const getWorkout = async () => {
const workoutDoc = await getDoc(doc(db, 'workouts', id));
setWorkout(workoutDoc.data());
};
const updateChosenExercises = (ex) => {
if (chosenExercises.current.includes(ex.target.value)) {
const filtered = chosenExercises.current.filter(
(i) => i !== ex.target.value
);
chosenExercises.current = filtered;
} else {
chosenExercises.current.push(ex.target.value);
}
};
const makeCheckboxes = () => {
const checkboxColumns = [];
// Create checkboxes for all muscles, and pre-check boxes
const preChecked =
workout.muscleGroups !== undefined ? workout.muscleGroups : [];
for (let i = 0; i < muscles.length; i += 1) {
const { group, musclesList } = muscles[i];
const boxes = [];
for (let j = 0; j < musclesList.length; j += 1) {
const { mId, name } = musclesList[j];
if (preChecked.includes(name)) {
boxes.push(
<div className="mb-3" key={mId}>
<Form.Check
type="checkbox"
id="workoutMuscleGroups"
value={name}
label={name}
key={name}
defaultChecked
onChange={updateChosenMuscles}
/>
</div>
);
} else {
boxes.push(
<div className="mb-3" key={mId}>
<Form.Check
type="checkbox"
id="workoutMuscleGroups"
value={name}
label={name}
key={name}
onChange={updateChosenMuscles}
/>
</div>
);
}
const loadExerciseGroups = () => {
const newGroups = [];
if (workout.exercises) {
for (let i = 0; i < workout.exercises.length; i += 1) {
newGroups.push({ ...workout.exercises[i], index: i });
}
/* TODO: This is still giving unique key errors, not sure why. */
checkboxColumns.push(
<Col key={group}>
<b>{group}</b>
{boxes}
</Col>
);
}
return checkboxColumns;
};
const makeExerciseOptions = () => {
if (exercises !== undefined) {
const preChecked =
workout.exercises !== undefined ? workout.exercises : [];
return exercises.docs.map((document) => (
<div className="mb-3" key={document.id}>
{preChecked.includes(document.id) ? (
<Form.Check
type="checkbox"
id="chosenOptions"
value={document.id}
label={document.data().name}
key={document.data().name}
onChange={updateChosenExercises}
defaultChecked
/>
) : (
<Form.Check
type="checkbox"
id="chosenOptions"
value={document.id}
label={document.data().name}
key={document.data().name}
onChange={updateChosenExercises}
/>
)}
</div>
));
}
return null;
setExerciseGroups(newGroups);
};
if (!isFirstLoad.current) {
@ -199,23 +99,82 @@ function WorkoutForm() {
}
if (isFirstLoad.current) {
setCheckboxes(makeCheckboxes());
setExerciseOptions(makeExerciseOptions());
loadExerciseGroups();
}
}, [exercises, id, workout.exercises, workout.muscleGroups]);
}, [id, workout.exercises]);
/* Keeps track of which index the exercise is in the workout */
const index = useRef(0);
const updateExercises = (ex) => {
setExerciseGroups(exerciseGroups.concat(ex));
setSelectedExercise({});
};
const deleteExercise = (toDelete) => {
const newGroups = exerciseGroups.filter((ex) => ex.index !== toDelete);
for (let i = 0; i < exerciseGroups.length - 1; i += 1) {
newGroups[i].index = i;
}
setExerciseGroups(newGroups);
index.current -= 1;
};
/* Handles state for the add exercise modal */
const [isModalOpen, setModalOpen] = useState(false);
const handleModalOpen = () => setModalOpen(true);
const handleModalClose = () => {
/* This check is to differentiate between cancelling or actually adding an exercise */
if (selectedExercise.id) {
updateExercises({ ...selectedExercise, index: index.current });
index.current += 1;
}
setModalOpen(false);
};
/* Transforms an exercise into the correct data structure for the form submission */
const getRepsSetsMuscles = (values) => {
const exercisesList = [];
const muscleGroupsList = [];
for (let i = 0; i < exerciseGroups.length; i += 1) {
/* Not sure why, but I can't seem to access the data directly here.
* So this is a workaround.
*/
const obj = { ...exerciseGroups[i] };
delete obj.instructions;
delete obj.equipment;
delete obj.videoURL;
/* Get the muscle groups from the exercise */
for (let j = 0; j < obj.muscleGroups.length; j += 1) {
if (!muscleGroupsList.includes(obj.muscleGroups[j])) {
muscleGroupsList.push(obj.muscleGroups[j]);
}
}
obj.reps = Number(values[`${obj.id}-reps`].value);
obj.sets = Number(values[`${obj.id}-sets`].value);
exercisesList.push(obj);
}
return [exercisesList, muscleGroupsList.sort()];
};
/* Handles the submission of forms. */
const handleSubmit = async (event) => {
/* Prevent automatic submission and refreshing of the page. */
event.preventDefault();
const [exercisesList, muscleGroups] = getRepsSetsMuscles(event.target);
/* TODO: Implement muscleGroups and image uploading */
const data = {
name: event.target.workoutName.value,
imgSrc: '/images/push-ups.png',
imgAlt: `Picture of ${event.target.workoutName.value}`,
muscleGroups: chosenMuscleGroups.current,
exercises: chosenExercises.current,
muscleGroups,
exercises: exercisesList,
id,
};
@ -228,13 +187,22 @@ function WorkoutForm() {
method: 'POST',
});
/* Get the response, update the document, create an alert, then redirect. */
/* Get the response, add the document, create an alert, then redirect. */
const result = await response.json();
updateDoc(doc(db, 'workouts', id), result.data)
if (result.error) {
handleAlertOpen({
heading: 'Error',
body: result.error,
variant: 'danger',
});
return;
}
updateDoc(doc(db, 'exercises', id), result.data)
.then(() => {
handleAlertOpen({
heading: 'Success!',
body: `${result.data.name} was updated in the workout list. Redirecting...`,
body: `${result.data.name} was added to the workout list. Redirecting...`,
variant: 'success',
});
setTimeout(() => {
@ -267,24 +235,24 @@ function WorkoutForm() {
return (
<div className={styles.form}>
<h2>{`Editing '${workout.name}'`}</h2>
<div style={{ padding: '0 10vw' }}>
<h2>Editing {workout.name}</h2>
</div>
<Form onSubmit={handleSubmit} action="/api/workout" method="post">
<Form.Group>
<Form.Label>Workout name</Form.Label>
<Form
onSubmit={handleSubmit}
action="/api/workout"
method="post"
className={styles.form}
>
<div className={styles.formname}>
<Form.Label>Enter workout name:</Form.Label>
<Form.Control
id="workoutName"
type="text"
defaultValue={workout.name}
/>
</Form.Group>
<Form.Group>
<Form.Label>Select targeted areas</Form.Label>
<Container fluid>
<Row>{checkboxes}</Row>
</Container>
</Form.Group>
</div>
{/* Not going to work just yet */}
{/* <Form.Group controlId="formThumbnail">
@ -292,27 +260,112 @@ function WorkoutForm() {
<Form.Control type="file" />
</Form.Group>
<Form.Group controlId="formimgAlt">
<Form.Group controlId="formImgAlt">
<Form.Label>Enter text to show if image doesn't load</Form.Label>
<Form.Control type="imgAlt" defaultValue={workout.imgAlt} />
<Form.Control type="imgAlt" placeholder="Enter image alt" />
</Form.Group> */}
<Form.Group>
<Form.Label>Select exercises to include</Form.Label>
{exerciseOptions}
<Form.Label>Exercises in this workout:</Form.Label>
<div className={styles.formexercises}>
{exerciseGroups.length === 0 ? (
<p>None</p>
) : (
exerciseGroups.map((ex) => (
<Row className="mb-3" key={ex.index}>
<Col xs={5} className="mt-2">
{ex.name}
</Col>
<Col xs={1} className="mt-2">
Reps
</Col>
<Col xs={2}>
<Form.Control id={`${ex.id}-reps`} defaultValue={ex.reps} />
</Col>
<Col xs={1} className="mt-2">
Sets
</Col>
<Col xs={2}>
<Form.Control id={`${ex.id}-sets`} defaultValue={ex.sets} />
</Col>
<Col xs={1} className="mt-2">
{/* TODO: Make this tooltip appear next to the image */}
<OverlayTrigger
overlay={<Tooltip>Delete {ex.name}</Tooltip>}
>
{({ ref }) => (
<Image
src="/images/delete.svg"
alt={`Delete ${ex.name}`}
height={20}
width={20}
onClick={() => deleteExercise(ex.index)}
lazyRoot={ref}
/>
)}
</OverlayTrigger>
</Col>
</Row>
))
)}
</div>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
<div className={styles.formbuttons}>
<Button variant="primary" onClick={handleModalOpen}>
Add an exercise
</Button>
<div>
<Link href="/workouts" passHref>
<Button variant="secondary">Cancel</Button>
</Link>{' '}
<Button variant="primary" type="submit">
Submit
</Button>
</div>
</div>
</Form>
{displayAlert(isAlertActive)}
<Modal show={isModalOpen} onHide={handleModalClose} centered size="lg">
<Modal.Header>
<Modal.Title>
<p>Add an exercise</p>
</Modal.Title>
</Modal.Header>
<Modal.Body>
{exercises && (
<List
list={exercises}
listType="radio"
type="exercises"
setSelected={setSelectedExercise}
/>
)}
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleModalClose}>
Cancel
</Button>
<Button variant="primary" onClick={handleModalClose}>
Add
</Button>
</Modal.Footer>
</Modal>
</div>
);
}
export default function EditWorkout() {
export default function CreateWorkout() {
return (
<>
<TopNavbar />

View File

@ -134,44 +134,35 @@ export default function WorkoutsPage() {
)}
<Col>
<div>
<main className={styles.main}>
<List
list={workoutList}
listType="radio"
selected={selectedWorkout}
setSelected={onClick}
type="workouts"
onDelete={onDelete}
/>
</main>
</div>
<List
list={workoutList}
listType="radio"
selected={selectedWorkout}
setSelected={onClick}
type="workouts"
onDelete={onDelete}
allowEditing={authUser !== undefined}
/>
</Col>
</Row>
{selectedWorkout !== undefined && (
<Modal
show={isOpen}
onHide={handleClose}
centered
scrollable
size="lg"
>
<Modal.Header closeButton>
<Modal.Title>{selectedWorkout.name}</Modal.Title>
</Modal.Header>
{/* TODO: Mobile view for workouts. */}
<Modal.Body>.</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={handleClose}>
Close
</Button>
</Modal.Footer>
</Modal>
)}
</Container>
{selectedWorkout !== undefined && (
<Modal show={isOpen} onHide={handleClose} centered scrollable size="lg">
<Modal.Header closeButton>
<Modal.Title>{selectedWorkout.name}</Modal.Title>
</Modal.Header>
{/* TODO: Mobile view for workouts. */}
<Modal.Body>.</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={handleClose}>
Close
</Button>
</Modal.Footer>
</Modal>
)}
</>
);
}

1
public/images/delete.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M13.05 42q-1.25 0-2.125-.875T10.05 39V10.5H8v-3h9.4V6h13.2v1.5H40v3h-2.05V39q0 1.2-.9 2.1-.9.9-2.1.9Zm21.9-31.5h-21.9V39h21.9Zm-16.6 24.2h3V14.75h-3Zm8.3 0h3V14.75h-3Zm-13.6-24.2V39Z"/></svg>

After

Width:  |  Height:  |  Size: 263 B

View File

@ -7,6 +7,23 @@
margin-bottom: 5vh;
}
.formname {
padding: 20px 0;
}
.formexercises {
border: 1px solid black;
border-radius: 10px;
padding: 2vh 2vh;
margin-bottom: 1vh;
}
.formbuttons {
display: flex;
justify-content: space-between;
padding: 10px;
}
.toast {
position: fixed;
top: 10vh;

View File

@ -1,6 +1,7 @@
/* Contains the image and the text */
.element {
display: flex;
justify-content: space-between;
width: 25vw + 120px + 60px;
border: 1px solid black;
border-radius: 10px;
@ -14,7 +15,6 @@
margin: 10px 0 0 5px;
text-align: left;
text-overflow: ellipsis;
width: 20vw;
overflow: hidden;
white-space: nowrap;
}
@ -32,10 +32,14 @@
white-space: nowrap;
}
.buttons {
display: inline-flex;
position: relative;
}
.star {
margin-top: 21px;
margin-left: 4px;
padding-inline: 10px;
align-self: center;
padding: 5px;
}
@media only screen and (max-width: 576px) {

View File

@ -5,6 +5,7 @@
margin: 0 auto;
align-content: center;
}
.main {
flex: 1;
display: flex;