import { notFound } from "next/navigation"; import { AiAnalysisCard } from "@/components/ai-analysis-card"; import { ElevationChart } from "@/components/elevation-chart"; import { RouteMapSection } from "@/components/route-map-section"; import { GcbChart } from "@/components/gcb-chart"; import { RunMetricChart } from "@/components/run-metric-chart"; import { StatCard } from "@/components/stat-card"; import { formatDate, formatDistance, formatDuration, formatPace } from "@/lib/format"; import { getLatestAnalysisForTarget, serializeAnalysis } from "@/lib/models/analysis"; import { getRunningActivity, type RunningActivity, } from "@/lib/models/running"; import { getCurrentUserId } from "@/lib/session"; export const dynamic = "force-dynamic"; const CHART_SAMPLES = 200; function interpolate(sorted: { dist: number; value: number }[], target: number): number | null { if (sorted.length === 0) return null; if (target <= sorted[0].dist) return sorted[0].value; if (target >= sorted[sorted.length - 1].dist) return sorted[sorted.length - 1].value; let lo = 0; let hi = sorted.length - 1; while (lo + 1 < hi) { const mid = (lo + hi) >> 1; if (sorted[mid].dist <= target) lo = mid; else hi = mid; } const t = (target - sorted[lo].dist) / (sorted[hi].dist - sorted[lo].dist); return sorted[lo].value + t * (sorted[hi].value - sorted[lo].value); } function buildGrid(maxDistKm: number): number[] { return Array.from({ length: CHART_SAMPLES }, (_, i) => Math.round((i / (CHART_SAMPLES - 1)) * maxDistKm * 100) / 100 ); } function RouteMap({ activity }: { activity: RunningActivity }) { const routePoints = activity.routePoints; return (
{routePoints && routePoints.length > 0 ? ( ) : (
Brak danych GPS — zsynchronizuj ponownie
)}
); } function buildChartData(activity: RunningActivity) { const maxDistKm = Math.round(activity.distanceM / 10) / 100; const grid = buildGrid(maxDistKm); const elevProfile = activity.elevationProfile; const metrics = activity.runMetrics; const elevSorted = elevProfile ? elevProfile .map((altM, i) => ({ dist: (i / elevProfile.length) * maxDistKm, value: altM })) .filter((p) => p.value > 0) : []; const metricsDistKm = metrics ? (() => { const raw = metrics.distanceKm; const max = Math.max(...raw); return max > 0 ? raw : raw.map((_, i) => Math.round(((i / (raw.length - 1)) * maxDistKm) * 100) / 100); })() : []; const hrSorted = metrics?.hrBpm ? metricsDistKm .map((d, i) => ({ dist: d, value: metrics.hrBpm![i] ?? 0 })) .filter((p) => p.value > 0) : []; const paceSorted = metrics?.paceSec ? metricsDistKm .map((d, i) => ({ dist: d, value: metrics.paceSec![i] ?? 0 })) .filter((p) => p.value > 0 && p.value < 1800) : []; const gcbSorted = metrics?.gcbLeftPct ? metricsDistKm .map((d, i) => ({ dist: d, value: metrics.gcbLeftPct![i] ?? 0 })) .filter((p) => p.value > 0) : []; const hasElev = elevSorted.length >= 2; const hasHr = hrSorted.length >= 2; const hasGcb = gcbSorted.length >= 2; const elevData = hasElev ? grid.map((distanceKm) => { const altM = interpolate(elevSorted, distanceKm) ?? 0; const paceSec = paceSorted.length >= 2 ? interpolate(paceSorted, distanceKm) ?? undefined : undefined; return { distanceKm, altM, paceSec }; }) : null; const hrData = hasHr ? grid.map((distanceKm) => ({ distanceKm, value: interpolate(hrSorted, distanceKm) ?? 0 })) : null; const gcbData = hasGcb ? grid.map((distanceKm) => { const left = Math.round((interpolate(gcbSorted, distanceKm) ?? 50) * 10) / 10; return { distanceKm, left, right: Math.round((100 - left) * 10) / 10 }; }) : null; return { elevData, hrData, gcbData }; } export default async function RunningActivityPage({ params, }: { params: Promise<{ id: string }>; }) { const { id } = await params; const userId = await getCurrentUserId(); const activity = await getRunningActivity(userId, id); if (!activity) { notFound(); } const analysis = await getLatestAnalysisForTarget(userId, "running", activity._id); const { elevData, hrData, gcbData } = buildChartData(activity); return (

{activity.name}

{formatDate(activity.startTime)}

{activity.maxHr ? : null} {activity.elevationGainM ? : null} {activity.vo2Max ? : null} {activity.avgGroundContactTimeMs ? ( ) : null} {activity.avgVerticalOscillationCm ? ( ) : null} {activity.avgVerticalRatioPct ? ( ) : null} {activity.avgStrideLengthCm ? ( ) : null} {activity.avgGroundContactBalanceLeftPct ? ( ) : null} {activity.avgPowerW ? : null} {activity.maxPowerW ? : null} {activity.avgRespirationRate ? ( ) : null} {activity.aerobicTrainingEffect ? ( ) : null} {activity.anaerobicTrainingEffect ? ( ) : null}
{elevData && } {(hrData || gcbData) && (
{hrData && ( )} {gcbData && }
)}
); }