Initial commit: smartrun MVP
Garmin run classification and progression tracker. Go backend (MCP client to mcp-garmin, SQLite store, deterministic rule engine, REST API) and React/TS frontend (Dashboard, Review Queue, Workout Kinds). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
81
frontend/src/api/client.ts
Normal file
81
frontend/src/api/client.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import type {
|
||||
Activity,
|
||||
AuthResponse,
|
||||
KindAssignment,
|
||||
ProgressionMetric,
|
||||
ProgressionPoint,
|
||||
ReviewQueueItem,
|
||||
SyncRun,
|
||||
SyncStatus,
|
||||
WorkoutKind,
|
||||
} from "../types/api";
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080";
|
||||
|
||||
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(`${BASE_URL}${path}`, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
...init,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const body = await res.text();
|
||||
throw new Error(`${init?.method ?? "GET"} ${path} failed: ${res.status} ${body}`);
|
||||
}
|
||||
if (res.status === 204) return undefined as T;
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export const api = {
|
||||
// Auth
|
||||
login: () => request<AuthResponse>("/api/auth/login", { method: "POST" }),
|
||||
submitMFA: (code: string) =>
|
||||
request<AuthResponse>("/api/auth/mfa", { method: "POST", body: JSON.stringify({ code }) }),
|
||||
authStatus: () => request<AuthResponse>("/api/auth/status"),
|
||||
|
||||
// Sync
|
||||
syncRun: () => request<{ status: string }>("/api/sync/run", { method: "POST" }),
|
||||
syncBackfill: () => request<{ status: string }>("/api/sync/backfill", { method: "POST" }),
|
||||
syncRuns: () => request<SyncRun[]>("/api/sync/runs"),
|
||||
syncStatus: () => request<SyncStatus>("/api/sync/status"),
|
||||
|
||||
// Activities
|
||||
listActivities: (params?: { from?: string; to?: string; limit?: number }) => {
|
||||
const q = new URLSearchParams();
|
||||
if (params?.from) q.set("from", params.from);
|
||||
if (params?.to) q.set("to", params.to);
|
||||
if (params?.limit) q.set("limit", String(params.limit));
|
||||
const qs = q.toString();
|
||||
return request<Activity[]>(`/api/activities/${qs ? `?${qs}` : ""}`);
|
||||
},
|
||||
getActivity: (id: number) =>
|
||||
request<{ activity: Activity; laps: unknown[]; assignment?: KindAssignment }>(`/api/activities/${id}`),
|
||||
|
||||
// Workout kinds
|
||||
listWorkoutKinds: (includeInactive = false) =>
|
||||
request<WorkoutKind[]>(`/api/workout-kinds/${includeInactive ? "?include_inactive=true" : ""}`),
|
||||
createWorkoutKind: (body: { name: string; description?: string; color?: string; rule: unknown; priority?: number }) =>
|
||||
request<WorkoutKind>("/api/workout-kinds/", { method: "POST", body: JSON.stringify(body) }),
|
||||
updateWorkoutKind: (
|
||||
id: number,
|
||||
body: { name: string; description?: string; color?: string; rule: unknown; priority?: number; is_active?: boolean },
|
||||
) => request<WorkoutKind>(`/api/workout-kinds/${id}`, { method: "PUT", body: JSON.stringify(body) }),
|
||||
deleteWorkoutKind: (id: number) => request<void>(`/api/workout-kinds/${id}`, { method: "DELETE" }),
|
||||
reclassifyWorkoutKind: (id: number) =>
|
||||
request<{ reclassified: number }>(`/api/workout-kinds/${id}/reclassify`, { method: "POST" }),
|
||||
|
||||
// Review queue
|
||||
reviewQueue: () => request<ReviewQueueItem[]>("/api/review-queue/"),
|
||||
resolveReview: (activityId: number, workoutKindId: number) =>
|
||||
request<{ status: string }>(`/api/review-queue/${activityId}/resolve`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ workout_kind_id: workoutKindId }),
|
||||
}),
|
||||
|
||||
// Progression
|
||||
progression: (kindId: number, metric: ProgressionMetric = "pace", from?: string, to?: string) => {
|
||||
const q = new URLSearchParams({ metric });
|
||||
if (from) q.set("from", from);
|
||||
if (to) q.set("to", to);
|
||||
return request<ProgressionPoint[]>(`/api/progression/${kindId}?${q.toString()}`);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user