Files
homepage/src/routes/fitness/[history=fitnessHistory]/+page.svelte
Alexander Bocken f5420badc1
All checks were successful
CI / update (push) Successful in 2m4s
fitness: add bilingual EN/DE support for all fitness routes and components
Use SvelteKit param matchers for bilingual URL routing (e.g. /fitness/stats
and /fitness/statistik). Add centralized i18n module with translation
dictionary, language detection from URL, and path conversion utilities.
Translate all UI text across pages, components, and navigation.
2026-03-22 21:25:03 +01:00

118 lines
2.9 KiB
Svelte

<script>
import { page as appPage } from '$app/stores';
import SessionCard from '$lib/components/fitness/SessionCard.svelte';
import { detectFitnessLang, t } from '$lib/js/fitnessI18n';
const lang = $derived(detectFitnessLang($appPage.url.pathname));
let { data } = $props();
let sessions = $state(data.sessions?.sessions ? [...data.sessions.sessions] : []);
let total = $state(data.sessions?.total ? data.sessions.total : 0);
let loading = $state(false);
let page = $state(1);
/** @type {Record<string, typeof sessions>} */
const grouped = $derived.by(() => {
/** @type {Record<string, typeof sessions>} */
const groups = {};
for (const s of sessions) {
const d = new Date(s.startTime);
const key = `${d.toLocaleString('default', { month: 'long' })} ${d.getFullYear()}`;
if (!groups[key]) groups[key] = [];
groups[key].push(s);
}
return groups;
});
async function loadMore() {
if (loading || sessions.length >= total) return;
loading = true;
page++;
try {
const res = await fetch(`/api/fitness/sessions?limit=50&skip=${sessions.length}`);
const data = await res.json();
sessions = [...sessions, ...(data.sessions ?? [])];
total = data.total ?? total;
} catch {}
loading = false;
}
</script>
<svelte:head><title>{t('history_title', lang)} - Fitness</title></svelte:head>
<div class="history-page">
<h1>{t('history_title', lang)}</h1>
{#if sessions.length === 0}
<p class="empty">{t('no_workouts_yet', lang)}</p>
{:else}
{#each Object.entries(grouped) as [month, monthSessions] (month)}
<section class="month-group">
<h2 class="month-header">{month}{monthSessions.length} {monthSessions.length !== 1 ? t('workouts_plural', lang) : t('workout_singular', lang)}</h2>
<div class="session-list">
{#each monthSessions as session (session._id)}
<SessionCard {session} />
{/each}
</div>
</section>
{/each}
{#if sessions.length < total}
<button class="load-more" onclick={loadMore} disabled={loading}>
{loading ? t('loading', lang) : t('load_more', lang)}
</button>
{/if}
{/if}
</div>
<style>
.history-page {
display: flex;
flex-direction: column;
gap: 1rem;
}
h1 {
margin: 0;
font-size: 1.4rem;
}
.empty {
text-align: center;
color: var(--color-text-secondary);
padding: 3rem 0;
}
.month-group {
margin-bottom: 0.5rem;
}
.month-header {
font-size: 0.85rem;
font-weight: 600;
color: var(--color-text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
margin: 0 0 0.5rem;
}
.session-list {
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.load-more {
align-self: center;
padding: 0.6rem 2rem;
background: transparent;
border: 1px solid var(--color-border);
border-radius: 8px;
color: var(--color-primary);
font-weight: 600;
cursor: pointer;
}
.load-more:hover {
border-color: var(--color-accent);
}
.load-more:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>