From b2f3b313bb6d17d5809778cc2228a043dc9941a3 Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 9 Apr 2026 16:55:51 +0200 Subject: [PATCH] feat(mana/web/body): exercise picker, routines, phases, progression chart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five quick-win UI upgrades that take the body module from "skeleton ListView" to "actually usable for daily training": 1. ExercisePicker modal (replaces the previous bare + +
+ + {#each MUSCLE_GROUPS as g (g)} + + {/each} +
+ +
+ {#if filtered.length === 0} +

+ {$_('body.exercisePicker.empty', { default: 'Nichts gefunden' })} +

+ {:else} + + {/if} +
+ + + + + + diff --git a/apps/mana/apps/web/src/lib/modules/body/components/ExerciseProgressionChart.svelte b/apps/mana/apps/web/src/lib/modules/body/components/ExerciseProgressionChart.svelte new file mode 100644 index 000000000..c8e370b5e --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/body/components/ExerciseProgressionChart.svelte @@ -0,0 +1,158 @@ + + + +
+ {#if !resolvedExercise || timeline.length === 0} +

Noch keine Sets geloggt

+ {:else} +
+
{resolvedExercise.name}
+
+ e1RM {latest.value} + kg + {#if timeline.length > 1} + 0} class:negative={delta < 0}> + {delta > 0 ? '+' : ''}{delta.toFixed(0)} + + {/if} +
+
+ + + + {#each timeline as p, i (p.date)} + {@const range = extent.max - extent.min || 1} + {@const stepX = (width - padX * 2) / Math.max(timeline.length - 1, 1)} + {@const x = padX + i * stepX} + {@const y = padY + (height - padY * 2) * (1 - (p.value - extent.min) / range)} + + {/each} + + {/if} +
+ + diff --git a/apps/mana/apps/web/src/lib/modules/body/components/PhaseManager.svelte b/apps/mana/apps/web/src/lib/modules/body/components/PhaseManager.svelte new file mode 100644 index 000000000..4303287c2 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/body/components/PhaseManager.svelte @@ -0,0 +1,243 @@ + + + +
+ {#if activePhase} +
+
+
+ {$_(`body.phase.${activePhase.kind}`, { default: activePhase.kind })} +
+
+ seit {activePhase.startDate} + {#if activePhase.targetWeight} + · Ziel: {activePhase.targetWeight}kg + {/if} +
+
+ +
+ {:else if opening} +
(e.preventDefault(), start())}> +
+ {#each KINDS as k (k)} + + {/each} +
+
+ + +
+
+ + +
+
+ {:else} + + {/if} +
+ + diff --git a/apps/mana/apps/web/src/lib/modules/body/components/RoutineManager.svelte b/apps/mana/apps/web/src/lib/modules/body/components/RoutineManager.svelte new file mode 100644 index 000000000..84c1ecee3 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/body/components/RoutineManager.svelte @@ -0,0 +1,253 @@ + + + +
+
+

{$_('body.routines.title', { default: 'Routinen' })}

+ +
+ + {#if creating} +
+ +
+ {#each exercises.filter((e) => !e.isArchived) as ex (ex.id)} + + {/each} +
+ +
+ {/if} + + {#if activeRoutines.length === 0} +

{$_('body.routines.empty', { default: 'Noch keine Routinen' })}

+ {:else} + + {/if} +
+ + diff --git a/apps/mana/apps/web/src/lib/modules/body/components/WorkoutLogger.svelte b/apps/mana/apps/web/src/lib/modules/body/components/WorkoutLogger.svelte index f39fe8408..523d274e7 100644 --- a/apps/mana/apps/web/src/lib/modules/body/components/WorkoutLogger.svelte +++ b/apps/mana/apps/web/src/lib/modules/body/components/WorkoutLogger.svelte @@ -13,7 +13,9 @@ import { _ } from 'svelte-i18n'; import type { BodyExercise, BodySet, BodyWorkout } from '../types'; import { bodyStore } from '../stores/body.svelte'; + import { getLastSetByExercise, relativeDays } from '../queries'; import SetRow from './SetRow.svelte'; + import ExercisePicker from './ExercisePicker.svelte'; interface Props { workout: BodyWorkout; @@ -26,6 +28,11 @@ let weight = $state(0); let reps = $state(8); let isWarmup = $state(false); + let pickerOpen = $state(false); + + let lastSets = $derived(getLastSetByExercise(sets)); + let selectedExercise = $derived(exercises.find((e) => e.id === selectedExerciseId) ?? null); + let lastForSelected = $derived(selectedExerciseId ? lastSets.get(selectedExerciseId) : null); // Pre-fill from the most recent set of the selected exercise so the // "next set" form starts at last week's working weight, not zero. @@ -108,11 +115,17 @@ addSet(); }} > - +