Files
knur-app/app/page.tsx
Dominik Klarkowski d908965c95 init
2026-06-22 11:32:27 +02:00

119 lines
4.0 KiB
TypeScript

import { startOfWeek } from "date-fns";
import { DashboardAnalysisCard } from "@/components/dashboard-analysis-card";
import { RunningStatsChart } from "@/components/running-stats-chart";
import { StatCard } from "@/components/stat-card";
import { VolumeTrendChart } from "@/components/volume-trend-chart";
import { formatDateShort } from "@/lib/format";
import { listAnalysesForCalendar, serializeAnalysis } from "@/lib/models/analysis";
import { listRunningActivities } from "@/lib/models/running";
import { listStrengthWorkouts } from "@/lib/models/strength";
import { workoutVolumeKg } from "@/lib/strength/stats";
import { getCurrentUserId } from "@/lib/session";
export const dynamic = "force-dynamic";
const TREND_LIMIT = 20;
export default async function Home() {
const userId = await getCurrentUserId();
const [runs, strengthWorkouts, allAnalyses] = await Promise.all([
listRunningActivities(userId),
listStrengthWorkouts(userId),
listAnalysesForCalendar(userId),
]);
const weekStart = startOfWeek(new Date(), { weekStartsOn: 1 });
const weeklyKm =
runs.filter((run) => run.startTime >= weekStart).reduce((sum, run) => sum + run.distanceM, 0) /
1000;
const weeklyStrengthSessions = strengthWorkouts.filter(
(workout) => workout.date >= weekStart
).length;
const totalRuns = runs.length;
const totalDistanceKm = runs.reduce((sum, r) => sum + r.distanceM, 0) / 1000;
const totalStrengthWorkouts = strengthWorkouts.length;
const volumeTrendData = strengthWorkouts
.slice(0, TREND_LIMIT)
.map((w) => ({
label: formatDateShort(w.date),
date: w.date.toISOString().slice(0, 10),
name: w.name,
volumeKg: workoutVolumeKg(w),
}))
.reverse();
// Aggregate running distance per ISO week (Mon-based).
const weeklyDistanceM = new Map<number, number>();
for (const r of runs) {
const weekStartTs = startOfWeek(r.startTime, { weekStartsOn: 1 }).getTime();
weeklyDistanceM.set(weekStartTs, (weeklyDistanceM.get(weekStartTs) ?? 0) + r.distanceM);
}
const runTrendData = [...weeklyDistanceM.entries()]
.sort((a, b) => a[0] - b[0])
.slice(-TREND_LIMIT)
.map(([ts, meters]) => ({
label: formatDateShort(new Date(ts)),
distanceKm: Math.round((meters / 1000) * 100) / 100,
}));
const serializedAnalyses = allAnalyses.map(serializeAnalysis);
const dayRuns = runs.map((r) => ({
id: r._id.toString(),
name: r.name,
startTime: r.startTime.toISOString(),
distanceM: r.distanceM,
durationSec: r.durationSec,
avgPaceSecPerKm: r.avgPaceSecPerKm,
}));
const dayWorkouts = strengthWorkouts.map((w) => ({
id: w._id.toString(),
name: w.name,
date: w.date.toISOString(),
exerciseCount: w.exercises.length,
}));
return (
<div className="flex flex-col gap-8">
<section className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<StatCard label="Km w tym tygodniu" value={`${weeklyKm.toFixed(1)} km`} hint="Bieganie" />
<StatCard
label="Siłownia w tym tygodniu"
value={weeklyStrengthSessions}
hint="Treningi"
/>
<StatCard
label="Łączny dystans"
value={`${totalDistanceKm.toFixed(0)} km`}
hint={`${totalRuns} biegów`}
/>
<StatCard
label="Treningi siłowe"
value={totalStrengthWorkouts}
hint="Łącznie"
/>
</section>
{(volumeTrendData.length > 1 || runTrendData.length > 1) && (
<section className="flex flex-col gap-4">
<h2 className="text-lg font-semibold text-fg">Globalne statystyki</h2>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
{volumeTrendData.length > 1 && <VolumeTrendChart data={volumeTrendData} />}
{runTrendData.length > 1 && <RunningStatsChart data={runTrendData} />}
</div>
</section>
)}
<DashboardAnalysisCard
analyses={serializedAnalyses}
runs={dayRuns}
workouts={dayWorkouts}
/>
</div>
);
}