import { useEffect, useState } from "react"; import { api } from "../api/client"; import type { ActivityListItem } from "../types/api"; function formatPace(avgSpeedMps: number | null): string | null { if (avgSpeedMps == null || avgSpeedMps <= 0) return null; const secPerKm = 1000 / avgSpeedMps; const m = Math.floor(secPerKm / 60); const s = Math.round(secPerKm % 60); return `${m}:${s.toString().padStart(2, "0")}/km`; } function lockReason(item: ActivityListItem): string { if (item.assignment_source === "manual") return "Manually assigned -- kept as-is by reclassify"; if (item.workout_kind_name === "Race") return "Race, from Garmin metadata -- kept as-is by reclassify"; return ""; } export function Activities() { const [items, setItems] = useState([]); const [error, setError] = useState(null); useEffect(() => { api.listActivities().then(setItems).catch((e) => setError(String(e))); }, []); return (

Activities

{error &&

{error}

} {items.length === 0 ? (

No activities synced yet.

) : ( {items.map((item) => { const pace = formatPace(item.AvgSpeedMps); return ( ); })}
Date Name Distance Pace Kind Source
{item.StartTimeUTC} {item.ActivityName || item.ActivityType} {(item.DistanceMeters / 1000).toFixed(2)} km {pace ?? "—"} {item.workout_kind_name ?? "—"} {item.assignment_source ?? "—"} {item.locked && ( Locked )}
)}
); }