Skip to content

Commit

Permalink
feat: introduce dialog for goals, improve ux
Browse files Browse the repository at this point in the history
  • Loading branch information
thraizz committed Jun 8, 2024
1 parent 138d7c0 commit 3fb9138
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 207 deletions.
61 changes: 61 additions & 0 deletions src/hooks/useGoals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { atom, useAtom } from "jotai";
import { useEffect } from "react";
import { Goal, PartialGoal } from "../types/Goals";
import {
golfSwingDataKeysInDegrees,
golfSwingDataKeysInMeters,
} from "../types/GolfSwingData";
import { useAveragedSwings } from "../utils/calculateAverages";
import { useIsEnglish } from "./useIsEnglish";

export const goalAtom = atom<PartialGoal[]>([]);
export const useGoals: () => Goal[] = () => {
const isEnglish = useIsEnglish();
const [goals, setGoals] = useAtom(goalAtom);
useEffect(
() =>
setGoals(
isEnglish
? [
{
id: "1",
title: "Driving distance",
target: 200,
metric: "Carry Distance",
},
]
: [
{
id: "1",
title: "Driving distance",
target: 200,
metric: "Gesamtstrecke",
},
],
),
[isEnglish, setGoals],
);
const averages = useAveragedSwings();

const calculateGoalProgress = (partialGoal: PartialGoal) => {
const current =
averages.find((average) => average.name === "Driver")?.[
"Carry Distance"
] ||
averages.find((average) => average.name === "Driver")?.["Gesamtstrecke"];

const progress = current ? (current / partialGoal.target) * 100 : 0;
const progressText = `${(current ? (current / partialGoal.target) * 100 : 0).toFixed(2)}%`;
const unit = golfSwingDataKeysInMeters.includes(partialGoal.metric)
? "m"
: golfSwingDataKeysInDegrees.includes(partialGoal.metric)
? "°"
: "";
return { progress, progressText, current, unit };
};

return goals.map((goal) => ({
...goal,
...calculateGoalProgress(goal),
}));
};
8 changes: 8 additions & 0 deletions src/hooks/useIsEnglish.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useSelectedSessions } from "./useSelectedSessions";

export const useIsEnglish = () => {
const sessions = useSelectedSessions();
return Object.values(sessions).some(
(session) => session.results.length && !!session.results[0]?.["Ball Speed"],
);
};
12 changes: 6 additions & 6 deletions src/router.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Navigate, Outlet, createBrowserRouter } from "react-router-dom";
import { Layout } from "./views/Layout";
import { Visualization } from "./views/Visualization";
import { Authentication } from "./views/Authentication";
import { routes } from "./routes";
import {
RedirectIfNotLoggedIn,
RedirectIfSignedIn,
} from "./utils/AuthRedirects";
import { Authentication } from "./views/Authentication";
import { Dashboard } from "./views/Dashboard";
import { Goals } from "./views/Goals";
import { Layout } from "./views/Layout";
import { NewLayout } from "./views/NewLayout";
import { routes } from "./routes";
import { Sessions } from "./views/Sessions";
import { Reports } from "./views/Reports";
import { Sessions } from "./views/Sessions";
import { Settings } from "./views/Settings";
import { Dashboard } from "./views/Dashboard";
import { Visualization } from "./views/Visualization";

export const router = createBrowserRouter([
{
Expand Down
19 changes: 19 additions & 0 deletions src/types/Goals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { GolfSwingDataDE, GolfSwingDataEN } from "./GolfSwingData";

export type Goal = {
id: string;
title: string;
current: string | number | null | undefined;
target: number;
progressText: string;
progress: number;
unit: string;
};

export type PartialGoal = {
id: string;
title: string;
target: number;
club?: string;
metric: keyof GolfSwingDataDE | keyof GolfSwingDataEN;
};
50 changes: 36 additions & 14 deletions src/types/GolfSwingData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,30 +68,52 @@ export type GolfSwingDataDE = {

export type GolfSwingData = GolfSwingDataEN & GolfSwingDataDE;

export const golfSwingDataKeysInMeters: Array<
keyof GolfSwingDataEN | keyof GolfSwingDataDE
> = [
export const englishMetersMetrics: (keyof GolfSwingDataEN)[] = [
"Carry Distance",
"Carry-Distanz",
"Carry Deviation Distance",
"Carry-Abweichungsdistanz",
"Total Distance",
"Gesamtstrecke",
"Total Deviation Distance",
"Gesamtabweichungsdistanz",
"Carry Deviation Distance",
"Apex Height",
];

export const germanMetersMetrics: (keyof GolfSwingDataDE)[] = [
"Carry-Distanz",
"Gesamtstrecke",
"Gesamtabweichungsdistanz",
"Carry-Abweichungsdistanz",
"Höhe des Scheitelpunkts",
];

export const golfSwingDataKeysInDegrees: Array<
export const golfSwingDataKeysInMeters: Array<
keyof GolfSwingDataEN | keyof GolfSwingDataDE
> = [
> = [...englishMetersMetrics, ...germanMetersMetrics];

export const englishDegreeMetrics = [
"Carry Deviation Angle",
"Carry-Abweichungswinkel",
"Total Deviation Angle",
"Gesamtabweichungswinkel",
"Launch Direction",
"Abflugrichtung",
"Club Face",
"Spin Axis",
"Launch Angle",
"Attack Angle",
"Face to Path",
"Club Path",
];

export const germanDegreeMetrics: (keyof GolfSwingDataDE)[] = [
"Carry-Abweichungswinkel",
"Gesamtabweichungswinkel",
"Abflugrichtung",
"Abflugwinkel",
] as unknown as Array<keyof GolfSwingData>;
"Schlagflächenstellung",
"Schlagfläche",
"Drehachse",
"Schwungbahn",
"Anstellwinkel",
];

export const golfSwingDataKeysInDegrees: Array<
keyof GolfSwingDataEN | keyof GolfSwingDataDE
> = [...englishDegreeMetrics, ...germanDegreeMetrics] as unknown as Array<
keyof GolfSwingData
>;
23 changes: 23 additions & 0 deletions src/views/Goal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Goal as GoalType } from "../types/Goals";
import { ProgressBar } from "./ProgressBar";

export const Goal = ({ goal }: { goal: GoalType }) => {
return (
<div className="mt-4 max-w-2xl rounded-md bg-white p-4">
<h3 className="text-lg font-semibold">{goal.title}</h3>
<hr />
<div className="flex flex-col justify-between text-lg lg:flex-row">
<p>
Current: <b>{goal.current + goal.unit}</b>
</p>
<p>
Target: <b>{goal.target + goal.unit}</b>
</p>
<p>
Progress: <b>{goal.progressText}</b>
</p>
</div>
<ProgressBar progress={goal.progress} />
</div>
);
};
95 changes: 95 additions & 0 deletions src/views/GoalForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { useAtom } from "jotai";
import { useForm } from "react-hook-form";
import { useClubsPerSession } from "../hooks/useClubsPerSesssion";
import { goalAtom } from "../hooks/useGoals";
import { useIsEnglish } from "../hooks/useIsEnglish";
import {
GolfSwingDataDE,
GolfSwingDataEN,
englishDegreeMetrics,
englishMetersMetrics,
germanDegreeMetrics,
germanMetersMetrics,
golfSwingDataKeysInMeters,
} from "../types/GolfSwingData";

export const GoalForm = ({ closeAction }: { closeAction: () => void }) => {
const formMethods = useForm<{
title: string;
target: number;
club?: string;
metric: keyof GolfSwingDataDE | keyof GolfSwingDataEN;
}>();

const isEnglish = useIsEnglish();
const metricOptions = isEnglish
? [...englishDegreeMetrics, ...englishMetersMetrics]
: [...germanDegreeMetrics, ...germanMetersMetrics];
const [, setGoals] = useAtom(goalAtom);
const clubs = useClubsPerSession();
return (
<div className="mt-4 rounded-md bg-white p-4">
<form
onSubmit={formMethods.handleSubmit((data) => {
setGoals((goals) => [
...goals,
{
id: (goals.length + 1).toString(),
title: data.title,
target: data.target,
metric: data.metric,
},
]);
closeAction();
})}
>
<div className="flex flex-wrap gap-4">
<input
type="text"
placeholder="Title"
{...formMethods.register("title", { required: true })}
className="input w-full"
/>

<select
{...formMethods.register("metric", { required: true })}
className="input w-full"
>
{metricOptions.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>

<select
{...formMethods.register("club")}
className="input w-full"
defaultValue=""
>
{Object.keys(clubs).map((club) => (
<option key={club} value={club}>
{club}
</option>
))}
<option value="">All clubs</option>
</select>

<input
type="number"
placeholder={`Target (${
golfSwingDataKeysInMeters.includes(formMethods.watch("metric"))
? "m"
: "°"
})`}
{...formMethods.register("target", { required: true })}
className="input w-full"
/>
</div>
<button type="submit" className="btn mt-4 w-full">
Add goal
</button>
</form>
</div>
);
};
15 changes: 15 additions & 0 deletions src/views/GoalList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useGoals } from "../hooks/useGoals";
import { Goal } from "./Goal";

export const GoalList = () => {
const goals = useGoals();

return (
<div className="mt-4">
<h2 className="text-xl font-semibold">Your goals</h2>
{goals.map((goal) => (
<Goal key={goal.id} goal={goal} />
))}
</div>
);
};
Loading

0 comments on commit 3fb9138

Please sign in to comment.