120 lines
3.5 KiB
Svelte
120 lines
3.5 KiB
Svelte
|
|
<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>
|