Skip to content

Commit

Permalink
Get exercise updates working.
Browse files Browse the repository at this point in the history
  • Loading branch information
eswan18 committed Aug 31, 2023
1 parent 8d1f4b5 commit 5356e83
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useState } from "react";
import ClientModal from "@/components/ClientModal";
import SolidButton from "@/components/buttons/SolidButton";
import GhostButton from "@/components/buttons/GhostButton";
import Input from "@/components/forms/Input";
import Form from "@/components/forms/Form";
import NumericInput from "@/components/forms/NumericInput";

type ExerciseInputModalProps = {
onSubmit: (e: ExerciseInputModalState) => void;
exerciseTypeName: string;
handleClose?: () => void
initalValues?: {
weight?: number;
reps?: number;
}
}

export type ExerciseInputModalState = {
weight: number
reps: number
}

export default function ExerciseInputModal({onSubmit, exerciseTypeName, handleClose, initalValues }: ExerciseInputModalProps) {
const [weight, setWeight] = useState<number | undefined>(initalValues?.weight);
const [reps, setReps] = useState<number | undefined>(initalValues?.reps);
const buttonEnabled = (weight != null) && (reps != null);

return (
<ClientModal handleClose={ handleClose }>
<Form title={`${exerciseTypeName}`} onSubmit={() => {weight && reps && onSubmit({weight, reps})}}>
<NumericInput label="Weight" htmlFor="weight" type="number" step="0.5" id="weight" name="Weight" placeholder="9000" value={weight} onValueUpdate={setWeight} />
<NumericInput label="Reps" htmlFor="reps" type="number" step="1" id="reps" name="Reps" placeholder="42" value={reps} onValueUpdate={setReps} />
<div className="flex flex-row justify-evenly items-center mt-4" >
<span className="text-sm">
<GhostButton type="button" onClick={handleClose}>Cancel</GhostButton>
</span>
<span className="text-xl">
<SolidButton type="submit" enabled={buttonEnabled}>Save</SolidButton>
</span>
</div>
</Form>
</ClientModal>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,7 @@ import CreateNewExerciseWidget from "./CreateNewExerciseWidget";
import { getAllExerciseTypes } from "@/lib/resources/exerciseTypes/getExerciseTypes";
import ExerciseWidget from "./ExerciseWidget";
import LoadingSpinner from "@/components/LoadingSpinner";
import ClientModal from "@/components/ClientModal";
import SolidButton from "@/components/buttons/SolidButton";
import Input from "@/components/forms/Input";
import Form from "@/components/forms/Form";
import GhostButton from "@/components/buttons/GhostButton";

import ExerciseInputModal, { ExerciseInputModalState } from "./ExerciseInputModal";

type ExerciseWithKey = {
exercise: Exercise;
Expand Down Expand Up @@ -78,40 +73,6 @@ export default function ExerciseSetWidget({ exerciseType, exercises, workoutId }
)
}

type ExerciseInputModalProps = {
onSubmit: (e: ExerciseInputModalState) => void;
exerciseTypeName: string;
handleClose?: () => void
}

type ExerciseInputModalState = {
weight: number
reps: number
}

function ExerciseInputModal({onSubmit, exerciseTypeName, handleClose }: ExerciseInputModalProps) {
const [weight, setWeight] = useState<number | undefined>(undefined);
const [reps, setReps] = useState<number | undefined>(undefined);
const buttonEnabled = (weight != null) && (reps != null);

return (
<ClientModal handleClose={ handleClose }>
<Form title="Record exercise" onSubmit={() => {weight && reps && onSubmit({weight, reps})}}>
<Input label="Weight" htmlFor="weight" type="number" step="0.5" id="weight" name="Weight" placeholder="9000" onValueUpdate={setWeight} />
<Input label="Reps" htmlFor="reps" type="number" step="1" id="reps" name="Reps" placeholder="42" onValueUpdate={setReps} />
<div className="flex flex-row justify-evenly items-center mt-4" >
<span className="text-sm">
<GhostButton type="button" onClick={handleClose}>Cancel</GhostButton>
</span>
<span className="text-xl">
<SolidButton type="submit" enabled={buttonEnabled}>Save</SolidButton>
</span>
</div>
</Form>
</ClientModal>
)
}

type ExercisePanelProps = {
type: ExerciseType;
exercisesWithKeys: ExerciseWithKey[];
Expand All @@ -123,7 +84,7 @@ function ExercisePanel({ type, exercisesWithKeys, appendNewExercise }: ExerciseP
<>
<h2 className="text-xl"><i className="fa-solid fa-dumbbell" /> {type.name}</h2>
<div className="flex flex-row justify-left overflow-x-scroll">
{exercisesWithKeys.map((ex) => <ExerciseWidget exercise={ex.exercise} key={ex.key} />)}
{exercisesWithKeys.map((ex) => <ExerciseWidget exercise={ex.exercise} exerciseType={type} key={ex.key} />)}
<CreateNewExerciseWidget addNewExercise={appendNewExercise} />
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,66 @@
'use client'

import { useState, useEffect, useRef } from "react";
import { Exercise } from "@/lib/resources/apiTypes";
import { Exercise, ExerciseType } from "@/lib/resources/apiTypes";
import { createExercise, overwriteExercise } from "@/lib/resources/exercises";
import SaveStatusIndicator, { SaveStatus } from "@/components/indicators/SaveStatusIndicator";
import { ReactElement } from "react";
import ExerciseInputModal, { ExerciseInputModalState } from "./ExerciseInputModal";


export async function saveExercise({ exercise, setExerciseSaveState, setExerciseId }: { exercise: Exercise, setExerciseSaveState: (saveState: SaveStatus) => void, setExerciseId: (id?: string) => void }) {
setExerciseSaveState("saving");
// If the exercise has an ID, we update it. Otherwise, we create a new one.
export async function saveNewExercise({ exercise, setExerciseSaveState, setExerciseId }: { exercise: Exercise, setExerciseSaveState: (saveState: SaveStatus) => void, setExerciseId: (id?: string) => void }) {
if (exercise.id) {
overwriteExercise({id: exercise.id, exercise}).then(() => {
setExerciseSaveState("saved")
});
} else {
createExercise(exercise).then((ex) => {
setExerciseSaveState("saved")
setExerciseId(ex.id)
});
alert("Exercises that have an ID shouldn't be saved")
return
}
setExerciseSaveState("saving");
createExercise(exercise).then((ex) => {
setExerciseSaveState("saved")
setExerciseId(ex.id)
});
}

export async function updateExistingExercise({ exercise, setExerciseSaveState }: { exercise: Exercise, setExerciseSaveState: (saveState: SaveStatus) => void }) {
if (!exercise.id) {
alert('exercises must have IDs to be updated')
return
}
setExerciseSaveState("saving");
overwriteExercise({id: exercise.id, exercise}).then(() => {
setExerciseSaveState("saved")
});
}

export default function ExerciseWidget({ exercise }: { exercise: Exercise }) {
export default function ExerciseWidget({ exercise, exerciseType }: { exercise: Exercise, exerciseType: ExerciseType }) {
const [saveState, setSaveState] = useState<SaveStatus>(exercise.id ? "saved" : "unsaved");
const [ex, setEx] = useState<Exercise>(exercise);
const [id, setId] = useState<string | undefined>(exercise.id);
const justLoadedFromServer = useRef<boolean>(!!ex.id);
const [modal, setModal] = useState<ReactElement | null>(null);

const showModal = () => {
const onSubmit = (modalState: ExerciseInputModalState) => {
setEx({...ex, ...modalState});
setModal(null);
}
setModal(<ExerciseInputModal
initalValues={{weight: ex.weight, reps: ex.reps}}
onSubmit={onSubmit}
exerciseTypeName={exerciseType.name}
handleClose={ () => setModal(null) }
/>)
}

useEffect(() => {
// This keeps us from saving data we already recieved from the server.
if (justLoadedFromServer.current) {
justLoadedFromServer.current = false;
} else {
saveExercise({ exercise: ex, setExerciseSaveState: setSaveState, setExerciseId: setId })
if (ex.id) {
updateExistingExercise({ exercise: ex, setExerciseSaveState: setSaveState })
} else {
saveNewExercise({ exercise: ex, setExerciseSaveState: setSaveState, setExerciseId: setId })
}
}
}, [ex]);

Expand All @@ -46,18 +74,24 @@ export default function ExerciseWidget({ exercise }: { exercise: Exercise }) {
<div className="text-xl">
<i className="fa-solid fa-xmark text-gray-400" /> {ex.reps}
</div>
<EditButtonContainer saveState={saveState} />
<EditButtonContainer saveState={saveState} onClick={showModal} />
</div>
{ modal }
</div>
)
}

function EditButtonContainer({saveState}: {saveState: SaveStatus}) {
type EditButtonContainerProps = {
saveState: SaveStatus;
onClick: () => void;
}

function EditButtonContainer({saveState, onClick}: EditButtonContainerProps) {
const disabled = !(saveState === "saved");
const color = disabled ? "text-gray-400" : "text-fuchsia-900";
return (
<div className={`mt-1 ${ color }`}>
<button disabled={disabled}><i className="text-lg fa-solid fa-pen-to-square" /></button>
<button disabled={disabled} onClick={ onClick }><i className="text-lg fa-solid fa-pen-to-square" /></button>
</div>
)
}
Expand Down Expand Up @@ -88,7 +122,7 @@ function SaveStatusOverlayContainer({saveState}: {saveState: SaveStatus}) {
prevState.current = saveState;
}, [saveState]);

const fadeOutClasses = "transition-opacity ease-out duration-500 opacity-0";
const fadeOutClasses = "transition-opacity ease-out duration-00 opacity-0";
const saveStateIndicatorOpacity = isSaveStateIndicatorOpaque ? "opacity-90" : fadeOutClasses;
return (
<div
Expand Down
36 changes: 36 additions & 0 deletions components/forms/NumericInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
type NumericInputProps = {
htmlFor: string;
type: string;
step?: string;
id: string;
name: string;
label: string;
value?: number;
placeholder: string;
onValueUpdate?: (value: number) => void;
required?: boolean;
className?: string;
}

export default function NumericInput({htmlFor, step, id, name, label, value, placeholder, onValueUpdate, required = false}: NumericInputProps) {
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = parseFloat(e.target.value);
onValueUpdate && onValueUpdate(value);
}
return (
<div className='flex flex-col mb-3'>
<label htmlFor={htmlFor} className='mb-1 text-gray-700 dark:text-gray-100'>{label}</label>
<input
type='number'
step={step}
id={id}
name={name}
value={value}
placeholder={placeholder}
className='dark:text-gray-900 rounded-md border border-gray-300'
required={required}
onChange={onChange}
/>
</div>
);
}

0 comments on commit 5356e83

Please sign in to comment.