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 && }
)}
);
}