109 lines
6 KiB
Svelte
109 lines
6 KiB
Svelte
<script>
|
|
import Stat from './Stat.svelte';
|
|
|
|
export let today;
|
|
export let activeCalorieTarget;
|
|
export let targetPercent;
|
|
export let macroTotal;
|
|
export let produceToday;
|
|
export let todayEntries = [];
|
|
export let todayActivities = [];
|
|
export let activityToday;
|
|
export let weekAverage;
|
|
export let selectPage;
|
|
</script>
|
|
|
|
<section class="space-y-4">
|
|
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-5">
|
|
<article class="sm:col-span-2 rounded-2xl bg-gradient-to-br from-blue-600 to-indigo-700 p-5 text-white shadow-sm">
|
|
<div class="text-xs font-semibold uppercase tracking-widest text-blue-200">Today</div>
|
|
<div id="todayCalories" class="mt-1 text-4xl font-black tracking-tight">{today.calories} kcal</div>
|
|
{#if activeCalorieTarget}
|
|
<div class="mt-2 h-2 overflow-hidden rounded-full bg-white/20"><div class="h-full rounded-full bg-white" style="width:{targetPercent}%"></div></div>
|
|
<div class="mt-1 text-xs font-semibold text-blue-100">{Math.max(0, activeCalorieTarget - today.calories)} kcal left of {activeCalorieTarget}</div>
|
|
{/if}
|
|
<div id="todayMacroText" class="mt-2 text-sm font-medium text-blue-100">P {today.protein}g · C {today.carbs}g · F {today.fat}g</div>
|
|
<div id="todayProduceText" class="text-sm text-blue-200">Fruit {today.fruit.toFixed(1)} · Veg {today.vegetables.toFixed(1)}</div>
|
|
</article>
|
|
<Stat label="Meals today" value={today.meals} note={today.meals === 1 ? 'meal logged' : 'meals logged'} />
|
|
<Stat label="7-day average" value={`${weekAverage} kcal`} note="Calories per day" />
|
|
<Stat label="Activity today" value={`${Math.round(activityToday.calories)} kcal`} note={`${activityToday.steps} steps`} />
|
|
</div>
|
|
|
|
<div class="grid gap-4 lg:grid-cols-2">
|
|
<section class="card">
|
|
<h3 class="card-title">Macros today</h3>
|
|
<div class="space-y-3 p-4">
|
|
{#each [['Protein', today.protein, 'bg-blue-500'], ['Carbs', today.carbs, 'bg-amber-400'], ['Fat', today.fat, 'bg-violet-500']] as [label, val, cls]}
|
|
<div>
|
|
<div class="mb-1.5 flex justify-between text-sm">
|
|
<span class="font-semibold text-slate-700">{label}</span>
|
|
<span class="text-slate-500">{val}g · {Math.round(val / macroTotal * 100)}%</span>
|
|
</div>
|
|
<div class="h-2.5 overflow-hidden rounded-full bg-slate-100">
|
|
<div class="h-full rounded-full transition-all {cls}" style="width:{Math.round(val / macroTotal * 100)}%"></div>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
<div class="border-t border-slate-100 pt-2 text-xs text-slate-400">{today.calories} kcal total · produce {produceToday.toFixed(1)} / 5</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card">
|
|
<h3 class="card-title">Today's meals</h3>
|
|
<div class="divide-y divide-slate-100">
|
|
{#each todayEntries as entry}
|
|
<div class="flex items-center justify-between px-4 py-3">
|
|
<div class="min-w-0">
|
|
<div class="truncate text-sm font-semibold text-slate-800">{entry.mealName || entry.description}</div>
|
|
<div class="text-xs text-slate-400">{entry.mealType} · {entry.time}</div>
|
|
</div>
|
|
<div class="ml-3 shrink-0 text-sm font-bold text-slate-600">{entry.calories} kcal</div>
|
|
</div>
|
|
{:else}
|
|
<div class="px-4 py-8 text-center text-sm text-slate-400">No meals logged today</div>
|
|
{/each}
|
|
</div>
|
|
{#if todayEntries.length}
|
|
<div class="border-t border-slate-100 px-4 py-2">
|
|
<button class="text-xs font-semibold text-blue-600 hover:text-blue-700" type="button" on:click={() => selectPage('log')}>+ Log another meal</button>
|
|
</div>
|
|
{:else}
|
|
<div class="border-t border-slate-100 px-4 py-2">
|
|
<button class="text-xs font-semibold text-blue-600 hover:text-blue-700" type="button" on:click={() => selectPage('log')}>Log your first meal today</button>
|
|
</div>
|
|
{/if}
|
|
</section>
|
|
|
|
<section class="card lg:col-span-2">
|
|
<h3 class="card-title">Synced activity</h3>
|
|
<div class="divide-y divide-slate-100">
|
|
{#each todayActivities.slice(0, 5) as activity}
|
|
<div class="flex items-center justify-between px-4 py-3">
|
|
<div class="min-w-0">
|
|
<div class="truncate text-sm font-semibold text-slate-800">{activity.title || activity.type || 'Activity'}</div>
|
|
<div class="text-xs text-slate-400">{activity.sourceName || activity.source || 'External'} · {String(activity.startAt || '').slice(11, 16) || 'All day'}</div>
|
|
</div>
|
|
<div class="ml-3 shrink-0 text-right text-sm font-bold text-slate-600">
|
|
{Math.round(Number(activity.calories || 0))} kcal
|
|
{#if activity.steps}<div class="text-xs font-medium text-slate-400">{activity.steps} steps</div>{/if}
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
<div class="grid gap-4 p-4 md:grid-cols-[1fr_1.1fr] md:items-center">
|
|
<div class="rounded-2xl bg-blue-50 p-4 text-left">
|
|
<div class="text-xs font-black uppercase tracking-widest text-blue-700">Connect Garmin</div>
|
|
<h4 class="mt-1 text-lg font-black text-slate-950">Use the Android app to sync your watch.</h4>
|
|
<p class="mt-2 text-sm leading-6 text-slate-600">The web dashboard shows synced activity, but Garmin pairing runs through Garmin Connect and Android Health Connect.</p>
|
|
</div>
|
|
<ol class="grid gap-2 text-sm leading-6 text-slate-600">
|
|
<li><strong class="text-slate-900">1.</strong> Sync your watch in Garmin Connect on Android.</li>
|
|
<li><strong class="text-slate-900">2.</strong> In Garmin Connect, enable sharing to Health Connect.</li>
|
|
<li><strong class="text-slate-900">3.</strong> Open Calorie AI on Android and tap Settings -> Sync Health Connect.</li>
|
|
</ol>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</section>
|