New features
This commit is contained in:
parent
55a112415d
commit
8981dc9615
17 changed files with 880 additions and 99 deletions
119
src/routes/summary/ExerciseStatistics.svelte
Normal file
119
src/routes/summary/ExerciseStatistics.svelte
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<script lang="ts">
|
||||
import type { WorkoutDataSummary } from '$lib/workout/types';
|
||||
|
||||
import { exercises } from '$lib/workout';
|
||||
|
||||
import {
|
||||
type TimePeriod,
|
||||
getAveragePerDay,
|
||||
getGoalProgress,
|
||||
getProgressGradient
|
||||
} from './summaryUtils';
|
||||
import ExerciseGraph from './ExerciseGraph.svelte';
|
||||
|
||||
interface Props {
|
||||
selectedPeriod: TimePeriod;
|
||||
workoutSummary: WorkoutDataSummary;
|
||||
}
|
||||
|
||||
let { selectedPeriod, workoutSummary }: Props = $props();
|
||||
|
||||
// Track when component is mounted for initial animation
|
||||
let animated = $state(false);
|
||||
|
||||
// Track selected exercise for graph
|
||||
let selectedExercise = $state<string | null>(null);
|
||||
|
||||
// Set mounted flag after component loads
|
||||
setTimeout(() => {
|
||||
animated = true;
|
||||
}, 300);
|
||||
</script>
|
||||
|
||||
<div class="overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm">
|
||||
<div class="border-b border-gray-200 bg-gray-50 px-6 py-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">
|
||||
Detailed Statistics for {selectedPeriod.label.toLowerCase()}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="divide-y divide-gray-200">
|
||||
{#each exercises as { key, config }}
|
||||
{@const totalValue = workoutSummary[key]}
|
||||
{@const dailyAverage = getAveragePerDay(totalValue, workoutSummary)}
|
||||
{@const goalProgress = getGoalProgress(totalValue, config.dailyGoal, workoutSummary)}
|
||||
|
||||
<div
|
||||
class="cursor-pointer px-6 py-4 transition-colors duration-200 {selectedExercise === key
|
||||
? 'bg-blue-50'
|
||||
: 'hover:bg-gray-50'}"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => (selectedExercise = selectedExercise === key ? null : key)}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
selectedExercise = selectedExercise === key ? null : key;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
<span class="mr-3 text-2xl">{config.icon}</span>
|
||||
<h4 class="font-medium text-gray-900">{config.name}</h4>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-lg font-semibold {config.textColorClass}">
|
||||
{config.formatter(totalValue)}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">Total</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between text-sm text-gray-600">
|
||||
<div>
|
||||
<span class="font-medium">Daily Average:</span>
|
||||
{config.formatter(Number(dailyAverage.toFixed(2)))}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="mr-2 h-4 w-48 overflow-hidden rounded-full bg-gray-200">
|
||||
<div
|
||||
class="h-4 rounded-full bg-gradient-to-r {getProgressGradient(
|
||||
config.color
|
||||
)} transition-all duration-1000 ease-out"
|
||||
style="width: {animated ? goalProgress : 0}%"
|
||||
></div>
|
||||
</div>
|
||||
<span class="text-sm font-medium">{goalProgress.toFixed(0)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Goal Information -->
|
||||
<div class="mt-2 flex items-center justify-between text-xs text-gray-500">
|
||||
<div>
|
||||
Goal: {config.formatter(config.dailyGoal)}/day
|
||||
</div>
|
||||
{#if goalProgress >= 100}
|
||||
<div class="font-medium text-green-600">✅ Goal achieved!</div>
|
||||
{:else if goalProgress >= 80}
|
||||
<div class="font-medium text-yellow-600">🎯 Close to goal</div>
|
||||
{:else if totalValue > 0}
|
||||
<div class="text-gray-400">Keep going!</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if selectedExercise === key}
|
||||
<div class="mt-4 rounded-lg border bg-gray-50 p-4">
|
||||
<ExerciseGraph
|
||||
exerciseKey={key}
|
||||
{selectedPeriod}
|
||||
dailyGoal={config.dailyGoal}
|
||||
color={config.color}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue