diff --git a/src/lib/components/fitness/SessionCard.svelte b/src/lib/components/fitness/SessionCard.svelte index 7437076..69902a4 100644 --- a/src/lib/components/fitness/SessionCard.svelte +++ b/src/lib/components/fitness/SessionCard.svelte @@ -1,5 +1,5 @@ @@ -63,11 +82,11 @@
{#each session.exercises.slice(0, 4) as ex (ex.exerciseId)} {@const exercise = getExerciseById(ex.exerciseId)} - {@const best = bestSet(ex.sets)} + {@const label = bestSetLabel(ex.sets, ex.exerciseId)}
{ex.sets.length} × {exercise?.name ?? ex.exerciseId} - {#if best} - {best.weight} kg × {best.reps}{#if best.rpe} @ {best.rpe}{/if} + {#if label} + {label} {/if}
{/each} diff --git a/src/lib/components/fitness/SetTable.svelte b/src/lib/components/fitness/SetTable.svelte index 9607be6..0f20821 100644 --- a/src/lib/components/fitness/SetTable.svelte +++ b/src/lib/components/fitness/SetTable.svelte @@ -1,12 +1,14 @@ @@ -39,10 +60,13 @@ {#if previousSets} {/if} - - - {#if editable} + {#each mainMetrics as metric (metric)} + + {/each} + {#if editable && hasRpe} + {/if} + {#if editable} {/if} @@ -54,39 +78,28 @@ {#if previousSets} {/if} - - - {#if editable} + {#each mainMetrics as metric (metric)} + + {/each} + {#if editable && hasRpe} + {/if} + {#if editable} - - + {#each mainMetrics as metric (metric)} + + {/each} - + {#if showEst1rm} + + {/if} {#each ex.sets as set, i (i)} - - + {#each mainMetrics as metric (metric)} + + {/each} - + {#if showEst1rm} + + {/if} {/each} diff --git a/src/routes/fitness/profile/+page.server.ts b/src/routes/fitness/profile/+page.server.ts deleted file mode 100644 index acd6a36..0000000 --- a/src/routes/fitness/profile/+page.server.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { PageServerLoad } from './$types'; - -export const load: PageServerLoad = async ({ fetch, locals }) => { - console.time('[profile] total load'); - - console.time('[profile] auth'); - const session = await locals.auth(); - console.timeEnd('[profile] auth'); - - console.time('[profile] fetch /api/fitness/stats/profile'); - const res = await fetch('/api/fitness/stats/profile'); - console.timeEnd('[profile] fetch /api/fitness/stats/profile'); - - console.time('[profile] parse json'); - const stats = await res.json(); - console.timeEnd('[profile] parse json'); - - console.timeEnd('[profile] total load'); - return { session, stats }; -}; diff --git a/src/routes/fitness/profile/+page.svelte b/src/routes/fitness/profile/+page.svelte deleted file mode 100644 index c6a3862..0000000 --- a/src/routes/fitness/profile/+page.svelte +++ /dev/null @@ -1,151 +0,0 @@ - - -
-

Profile

- -
- {#if user} - {user.name} - - {/if} -
- -

Dashboard

- - {#if (stats.workoutsChart?.data?.length ?? 0) > 0} - - {:else} -

No workout data to display yet.

- {/if} - - {#if (stats.weightChart?.data?.length ?? 0) > 1} - - {/if} -
- - diff --git a/src/routes/fitness/stats/+page.server.ts b/src/routes/fitness/stats/+page.server.ts new file mode 100644 index 0000000..862596c --- /dev/null +++ b/src/routes/fitness/stats/+page.server.ts @@ -0,0 +1,8 @@ +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async ({ fetch, locals }) => { + const session = await locals.auth(); + const res = await fetch('/api/fitness/stats/overview'); + const stats = await res.json(); + return { session, stats }; +}; diff --git a/src/routes/fitness/stats/+page.svelte b/src/routes/fitness/stats/+page.svelte new file mode 100644 index 0000000..74ebb4a --- /dev/null +++ b/src/routes/fitness/stats/+page.svelte @@ -0,0 +1,218 @@ + + +
+

Stats

+ +
+
+
+
{stats.totalWorkouts ?? 0}
+
Workouts
+
+
+
+
{stats.totalTonnage ?? 0}t
+
Tonnage Lifted
+
+
+
+
{stats.totalCardioKm ?? 0}km
+
Cardio Distance
+
+
+ + {#if (stats.workoutsChart?.data?.length ?? 0) > 0} + + {:else} +

No workout data to display yet.

+ {/if} + + {#if (stats.weightChart?.data?.length ?? 0) > 1} + + {/if} +
+ + diff --git a/src/routes/fitness/workout/+page.svelte b/src/routes/fitness/workout/+page.svelte index eb299e5..4c20a58 100644 --- a/src/routes/fitness/workout/+page.svelte +++ b/src/routes/fitness/workout/+page.svelte @@ -4,7 +4,7 @@ import { Plus, Trash2, Play, Pencil, X, Save } from 'lucide-svelte'; import { getWorkout } from '$lib/js/workout.svelte'; import { getWorkoutSync } from '$lib/js/workoutSync.svelte'; - import { getExerciseById } from '$lib/data/exercises'; + import { getExerciseById, getExerciseMetrics, METRIC_LABELS } from '$lib/data/exercises'; import TemplateCard from '$lib/components/fitness/TemplateCard.svelte'; import ExercisePicker from '$lib/components/fitness/ExercisePicker.svelte'; import AddActionButton from '$lib/components/AddActionButton.svelte'; @@ -25,7 +25,7 @@ /** @type {any} */ let editingTemplate = $state(null); let editorName = $state(''); - /** @type {Array<{ exerciseId: string, sets: Array<{ reps: number | null, weight: number | null }>, restTime: number }>} */ + /** @type {Array<{ exerciseId: string, sets: Array>, restTime: number }>} */ let editorExercises = $state([]); let editorPicker = $state(false); let editorSaving = $state(false); @@ -88,7 +88,7 @@ editorName = template.name; editorExercises = template.exercises.map((/** @type {any} */ ex) => ({ exerciseId: ex.exerciseId, - sets: ex.sets.map((/** @type {any} */ s) => ({ reps: s.reps ?? null, weight: s.weight ?? null })), + sets: ex.sets.map((/** @type {any} */ s) => ({ ...s })), restTime: ex.restTime ?? 120 })); showTemplateEditor = true; @@ -101,9 +101,13 @@ /** @param {string} exerciseId */ function editorAddExercise(exerciseId) { + const metrics = getExerciseMetrics(getExerciseById(exerciseId)); + /** @type {Record} */ + const emptySet = {}; + for (const m of metrics) emptySet[m] = null; editorExercises = [...editorExercises, { exerciseId, - sets: [{ reps: null, weight: null }], + sets: [emptySet], restTime: 120 }]; } @@ -115,7 +119,11 @@ /** @param {number} exIdx */ function editorAddSet(exIdx) { - editorExercises[exIdx].sets = [...editorExercises[exIdx].sets, { reps: null, weight: null }]; + const metrics = getExerciseMetrics(getExerciseById(editorExercises[exIdx].exerciseId)); + /** @type {Record} */ + const emptySet = {}; + for (const m of metrics) emptySet[m] = null; + editorExercises[exIdx].sets = [...editorExercises[exIdx].sets, emptySet]; } /** @param {number} exIdx @param {number} setIdx */ @@ -131,14 +139,21 @@ const body = { name: editorName.trim(), - exercises: editorExercises.map((ex) => ({ - exerciseId: ex.exerciseId, - sets: ex.sets.map((s) => ({ - reps: s.reps ?? 1, - weight: s.weight ?? undefined - })), - restTime: ex.restTime - })) + exercises: editorExercises.map((ex) => { + const metrics = getExerciseMetrics(getExerciseById(ex.exerciseId)); + return { + exerciseId: ex.exerciseId, + sets: ex.sets.map((s) => { + /** @type {Record} */ + const set = {}; + for (const m of metrics) { + if (s[m] != null) set[m] = s[m]; + } + return set; + }), + restTime: ex.restTime + }; + }) }; try { @@ -267,6 +282,7 @@ {#each editorExercises as ex, exIdx (exIdx)} {@const exercise = getExerciseById(ex.exerciseId)} + {@const exMetrics = getExerciseMetrics(exercise).filter((/** @type {string} */ m) => m !== 'rpe')}
{exercise?.name ?? ex.exerciseId} @@ -278,9 +294,10 @@ {#each ex.sets as set, setIdx (setIdx)}
{setIdx + 1} - - × - + {#each exMetrics as metric, mIdx (metric)} + {#if mIdx > 0}×{/if} + + {/each} {#if ex.sets.length > 1} {/if} diff --git a/src/routes/fitness/workout/active/+page.svelte b/src/routes/fitness/workout/active/+page.svelte index 030897d..9b64a30 100644 --- a/src/routes/fitness/workout/active/+page.svelte +++ b/src/routes/fitness/workout/active/+page.svelte @@ -3,6 +3,7 @@ import { Plus, Trash2, Play, Pause } from 'lucide-svelte'; import { getWorkout } from '$lib/js/workout.svelte'; import { getWorkoutSync } from '$lib/js/workoutSync.svelte'; + import { getExerciseById, getExerciseMetrics } from '$lib/data/exercises'; import ExerciseName from '$lib/components/fitness/ExerciseName.svelte'; import SetTable from '$lib/components/fitness/SetTable.svelte'; import RestTimer from '$lib/components/fitness/RestTimer.svelte'; @@ -14,7 +15,7 @@ const sync = getWorkoutSync(); let showPicker = $state(false); - /** @type {Record>} */ + /** @type {Record>>} */ let previousData = $state({}); onMount(() => { @@ -137,6 +138,7 @@ workout.updateSet(exIdx, setIdx, d)} onToggleComplete={(setIdx) => {
PREVIOUSKGREPS{METRIC_LABELS[metric]}RPE
{#if previousSets[i]} - {previousSets[i].weight} × {previousSets[i].reps} + {formatPrev(previousSets[i])} {:else} — {/if} - {#if editable} - handleInput(i, 'weight', e)} - /> - {:else} - {set.weight ?? '—'} - {/if} - - {#if editable} - handleInput(i, 'reps', e)} - /> - {:else} - {set.reps ?? '—'} - {/if} - + {#if editable} + handleInput(i, metric, e)} + /> + {:else} + {set[metric] ?? '—'} + {/if} + handleInput(i, 'rpe', e)} />
SETKGREPS{METRIC_LABELS[metric]}RPEEST. 1RMEST. 1RM
{i + 1}{set.weight ?? '—'}{set.reps ?? '—'}{set[metric] ?? '—'}{set.rpe ?? '—'}{set.weight && set.reps ? epley1rm(set.weight, set.reps) : '—'}{set.weight && set.reps ? epley1rm(set.weight, set.reps) : '—'}