74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
|
|
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<ActivityListItem[]>([]);
|
||
|
|
const [error, setError] = useState<string | null>(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
api.listActivities().then(setItems).catch((e) => setError(String(e)));
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="page">
|
||
|
|
<h2>Activities</h2>
|
||
|
|
{error && <p className="error">{error}</p>}
|
||
|
|
|
||
|
|
{items.length === 0 ? (
|
||
|
|
<p className="empty-state">No activities synced yet.</p>
|
||
|
|
) : (
|
||
|
|
<table className="kinds-table">
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Date</th>
|
||
|
|
<th>Name</th>
|
||
|
|
<th>Distance</th>
|
||
|
|
<th>Pace</th>
|
||
|
|
<th>Kind</th>
|
||
|
|
<th>Source</th>
|
||
|
|
<th></th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
{items.map((item) => {
|
||
|
|
const pace = formatPace(item.AvgSpeedMps);
|
||
|
|
return (
|
||
|
|
<tr key={item.ID}>
|
||
|
|
<td>{item.StartTimeUTC}</td>
|
||
|
|
<td>{item.ActivityName || item.ActivityType}</td>
|
||
|
|
<td>{(item.DistanceMeters / 1000).toFixed(2)} km</td>
|
||
|
|
<td>{pace ?? "—"}</td>
|
||
|
|
<td>{item.workout_kind_name ?? "—"}</td>
|
||
|
|
<td>{item.assignment_source ?? "—"}</td>
|
||
|
|
<td>
|
||
|
|
{item.locked && (
|
||
|
|
<span className="lock-badge" title={lockReason(item)}>
|
||
|
|
Locked
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
);
|
||
|
|
})}
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|