// DEPLOY_CONFIG: {"cron": [{"name": "weekly_running_stats_email_summary", "schedule": "0 9 * * 1", "action": "event", "config": {"event_type": "weekly_running_stats_email_summary"}}], "triggers": []}
import { useEffect, useMemo, useState } from 'react';
import { useAuth, useCollection, renderChart } from '@deplixo/sdk';
import { RouteMap } from './components/RouteMap.jsx';
import { SavedRoutes } from './components/SavedRoutes.jsx';
function getDistanceKm(route) {
const raw = route?.distance ?? route?.distanceKm ?? route?.length ?? route?.totalDistance;
const num = Number(raw);
return Number.isFinite(num) ? num : 0;
}
function getDateValue(route) {
const raw = route?.date ?? route?.createdAt ?? route?.updatedAt ?? route?.savedAt;
const date = raw ? new Date(raw) : null;
return date && !Number.isNaN(date.getTime()) ? date : null;
}
function getPaceMinutesPerKm(route) {
if (route?.paceMinutesPerKm != null) {
const num = Number(route.paceMinutesPerKm);
return Number.isFinite(num) ? num : null;
}
const timeRaw = route?.estimatedTime ?? route?.time ?? route?.duration;
const distanceKm = getDistanceKm(route);
const timeMinutes = Number(timeRaw);
if (Number.isFinite(timeMinutes) && distanceKm > 0) {
return timeMinutes / distanceKm;
}
return null;
}
function formatWeekLabel(date) {
const d = new Date(date);
const day = d.getDay();
const diff = d.getDate() - day + (day === 0 ? -6 : 1);
const monday = new Date(d);
monday.setDate(diff);
monday.setHours(0, 0, 0, 0);
return monday.toISOString().slice(0, 10);
}
function formatDateLabel(date) {
return new Date(date).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
}
function AnalyticsCharts({ routes }) {
const chartIds = {
runsPerWeek: 'runs-per-week-chart',
distanceOverTime: 'distance-over-time-chart',
paceTrends: 'pace-trends-chart',
};
const chartData = useMemo(() => {
const sorted = [...routes]
.map((route) => ({
...route,
_date: getDateValue(route),
_distanceKm: getDistanceKm(route),
_pace: getPaceMinutesPerKm(route),
}))
.filter((route) => route._date)
.sort((a, b) => a._date - b._date);
const runsByWeek = new Map();
const distanceByDate = [];
const paceByDate = [];
sorted.forEach((route) => {
const weekKey = formatWeekLabel(route._date);
runsByWeek.set(weekKey, (runsByWeek.get(weekKey) || 0) + 1);
distanceByDate.push({ label: formatDateLabel(route._date), value: route._distanceKm });
if (route._pace != null) {
paceByDate.push({ label: formatDateLabel(route._date), value: Number(route._pace.toFixed(2)) });
}
});
const weekEntries = Array.from(runsByWeek.entries()).sort((a, b) => a[0].localeCompare(b[0]));
return {
runsPerWeek: {
labels: weekEntries.map(([week]) => week),
values: weekEntries.map(([, count]) => count),
},
distanceOverTime: {
labels: distanceByDate.map((item) => item.label),
values: distanceByDate.map((item) => item.value),
},
paceTrends: {
labels: paceByDate.map((item) => item.label),
values: paceByDate.map((item) => item.value),
},
};
}, [routes]);
useEffect(() => {
const renderIfReady = () => {
const runsCanvas = document.getElementById(chartIds.runsPerWeek);
const distanceCanvas = document.getElementById(chartIds.distanceOverTime);
const paceCanvas = document.getElementById(chartIds.paceTrends);
if (runsCanvas && chartData.runsPerWeek.labels.length > 0) {
renderChart(runsCanvas, {
type: 'bar',
data: {
labels: chartData.runsPerWeek.labels,
datasets: [
{
label: 'Runs',
data: chartData.runsPerWeek.values,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
},
});
}
if (distanceCanvas && chartData.distanceOverTime.labels.length > 0) {
renderChart(distanceCanvas, {
type: 'line',
data: {
labels: chartData.distanceOverTime.labels,
datasets: [
{
label: 'Distance (km)',
data: chartData.distanceOverTime.values,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
},
});
}
if (paceCanvas && chartData.paceTrends.labels.length > 0) {
renderChart(paceCanvas, {
type: 'line',
data: {
labels: chartData.paceTrends.labels,
datasets: [
{
label: 'Pace (min/km)',
data: chartData.paceTrends.values,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
},
});
}
};
renderIfReady();
}, [chartData, chartIds.distanceOverTime, chartIds.paceTrends, chartIds.runsPerWeek]);
const hasAnyData = routes.length > 0;
return (
Track your weekly activity, distance progression, and pace trends from your saved runs. Save a few routes to see analytics charts here.Run Analytics
No run data yet
Runs per week
Distance over time
Pace trends
Sign in with Google to save and manage your personal running routes.
Plan your perfect running route