Files
platform/services/fitness/frontend-legacy/src/routes/templates/+page.svelte
Yusuf Suleman d3e250e361 Initial commit: Second Brain Platform
Complete platform with unified design system and real API integration.

Apps: Dashboard, Fitness, Budget, Inventory, Trips, Reader, Media, Settings
Infrastructure: SvelteKit + Python gateway + Docker Compose
2026-03-28 23:20:40 -05:00

102 lines
4.3 KiB
Svelte

<script lang="ts">
import { get, post, del } from '$lib/api/client.ts';
import type { MealTemplate, MealType } from '$lib/api/types.ts';
import { MEAL_TYPES } from '$lib/api/types.ts';
import { today } from '$lib/api/client.ts';
let templates = $state<MealTemplate[]>([]);
let loading = $state(true);
let logDate = $state(today());
let logMeal = $state<MealType>('lunch');
let loggingId = $state('');
$effect(() => { loadTemplates(); });
async function loadTemplates() {
loading = true;
try { templates = await get<MealTemplate[]>('/api/templates'); }
catch {} finally { loading = false; }
}
async function logTemplate(id: string) {
loggingId = id;
try {
await post(`/api/templates/${id}/log`, { meal_type: logMeal, entry_date: logDate });
alert('Logged!');
} catch {} finally { loggingId = ''; }
}
async function deleteTemplate(id: string) {
if (confirm('Archive this template?')) {
await del(`/api/templates/${id}`);
loadTemplates();
}
}
</script>
<div class="max-w-3xl mx-auto px-4 py-6">
<div class="mb-6">
<h1 class="text-3xl font-bold">Meal Templates</h1>
<p class="text-base-content/50 text-sm mt-0.5">Save and reuse your favorite meals</p>
</div>
<div class="rounded-xl border border-info/20 bg-gradient-to-br from-info/10 to-info/5 p-4 mb-6 shadow-md flex items-center gap-4 flex-wrap">
<span class="text-xs text-base-content/50 font-medium uppercase tracking-wide">Log to:</span>
<input type="date" class="input input-bordered input-sm" bind:value={logDate} />
<select class="select select-bordered select-sm" bind:value={logMeal}>
{#each MEAL_TYPES as mt}
<option value={mt}>{mt}</option>
{/each}
</select>
</div>
{#if loading}
<div class="flex justify-center py-12"><span class="loading loading-spinner loading-lg text-primary"></span></div>
{:else if templates.length === 0}
<div class="text-center py-16">
<div class="p-4 rounded-2xl bg-primary/15 inline-block mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1" ry="1"/></svg>
</div>
<div class="text-base-content/50">No templates yet</div>
<div class="text-sm text-base-content/30 mt-1">Templates can be created from the daily log or via API</div>
</div>
{:else}
<div class="space-y-3">
{#each templates as t}
<div class="rounded-xl border border-base-300 bg-base-200/50 p-5 shadow-sm card-hover">
<div class="flex justify-between items-start mb-3">
<div class="flex items-center gap-2">
<span class="font-bold text-lg">{t.name}</span>
{#if t.meal_type}<span class="badge badge-sm badge-outline">{t.meal_type}</span>{/if}
{#if t.is_favorite}<span class="text-warning"></span>{/if}
</div>
<div class="flex gap-1">
<button class="btn btn-primary btn-sm gap-1" onclick={() => logTemplate(t.id)} disabled={loggingId === t.id}>
{#if loggingId === t.id}<span class="loading loading-spinner loading-xs"></span>{:else}
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd"/></svg>
{/if}
Log
</button>
<button class="btn btn-ghost btn-sm" onclick={() => deleteTemplate(t.id)}>Archive</button>
</div>
</div>
{#if t.items?.length > 0}
<div class="space-y-1.5">
{#each t.items as item}
<div class="flex justify-between text-sm text-base-content/70">
<span>{item.snapshot_food_name} <span class="text-base-content/30">x{item.quantity}</span></span>
<span class="font-medium">{Math.round(item.snapshot_calories * item.quantity)} cal</span>
</div>
{/each}
<div class="border-t border-base-300/50 pt-2 mt-2 flex justify-between font-bold text-sm">
<span>Total</span>
<span>{Math.round(t.items.reduce((s, i) => s + i.snapshot_calories * i.quantity, 0))} cal</span>
</div>
</div>
{/if}
</div>
{/each}
</div>
{/if}
</div>