2026-07-17 18:33:06 +02:00
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
|
import { api } from "../api/client";
|
|
|
|
|
import { ProgressionChart } from "../components/charts/ProgressionChart";
|
|
|
|
|
import type { ProgressionMetric, ProgressionPoint, WorkoutKind } from "../types/api";
|
|
|
|
|
|
|
|
|
|
const METRICS: { key: ProgressionMetric; label: string }[] = [
|
|
|
|
|
{ key: "pace", label: "Pace" },
|
|
|
|
|
{ key: "hr", label: "Heart Rate" },
|
|
|
|
|
{ key: "vo2max", label: "VO2max" },
|
|
|
|
|
{ key: "aerobic_te", label: "Aerobic Training Effect" },
|
|
|
|
|
{ key: "anaerobic_te", label: "Anaerobic Training Effect" },
|
Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:33:47 +02:00
|
|
|
{ key: "efficiency_factor", label: "Efficiency Factor" },
|
2026-07-17 18:33:06 +02:00
|
|
|
];
|
|
|
|
|
|
2026-07-26 10:00:24 +02:00
|
|
|
export function Analysis() {
|
2026-07-17 18:33:06 +02:00
|
|
|
const [kinds, setKinds] = useState<WorkoutKind[]>([]);
|
|
|
|
|
const [selectedKindId, setSelectedKindId] = useState<number | null>(null);
|
|
|
|
|
const [metric, setMetric] = useState<ProgressionMetric>("pace");
|
|
|
|
|
const [points, setPoints] = useState<ProgressionPoint[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
api
|
|
|
|
|
.listWorkoutKinds()
|
|
|
|
|
.then((k) => {
|
|
|
|
|
setKinds(k);
|
|
|
|
|
if (k.length > 0) setSelectedKindId(k[0].ID);
|
|
|
|
|
})
|
|
|
|
|
.catch((e) => setError(String(e)));
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (selectedKindId == null) return;
|
|
|
|
|
setLoading(true);
|
|
|
|
|
setError(null);
|
|
|
|
|
api
|
|
|
|
|
.progression(selectedKindId, metric)
|
|
|
|
|
.then(setPoints)
|
|
|
|
|
.catch((e) => setError(String(e)))
|
|
|
|
|
.finally(() => setLoading(false));
|
|
|
|
|
}, [selectedKindId, metric]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="page">
|
|
|
|
|
{kinds.length === 0 ? (
|
|
|
|
|
<p className="empty-state">
|
Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:33:47 +02:00
|
|
|
No training types defined yet. See the Training types card on the Profile page to start seeing progression
|
|
|
|
|
here.
|
2026-07-17 18:33:06 +02:00
|
|
|
</p>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<div className="controls">
|
|
|
|
|
<label>
|
Dedup Garmin data storage, configurable chart colors, taxonomy fixes, sync/progression fixes
- Remove duplicated Garmin fields from storage; decode display-only fields
(activity name/type, lap duration/HR, structured workout raw JSON) from
RawJSON at API-response time instead of storing redundant columns
- Add a fully configurable chart color system (pace/HR main-line colors, 4
effort-kind colors, tint/darken/brighten intensity knobs) under Profile >
Chart colors
- Rename training types and fix their display order (Easy, Long, 60'/30'
Threshold, Tempo, Intervals, MAS Test, Race) everywhere they're listed
- Add an Efficiency Factor progression metric; fix Progression chart axes to
use tight non-zero-based domains, m:ss/km pace formatting, and rounded
ticks instead of raw floating-point labels
- Expose the raw get_workout_by_id() payload in the raw-data viewer
alongside activity/lap/detail JSON; enlarge the modal and shrink array
indentation for readability
- Fix "last sync" reporting a meaningless activity count: record one
combined sync run per manual "Sync now" and count genuinely new
activities instead of re-listing whatever Garmin returned for the queried
window
- Let a Review Queue activity be manually cleared back to Unclassified, and
make "Reset all" available even while disconnected from Garmin
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 06:33:47 +02:00
|
|
|
Training types
|
2026-07-17 18:33:06 +02:00
|
|
|
<select
|
|
|
|
|
value={selectedKindId ?? ""}
|
|
|
|
|
onChange={(e) => setSelectedKindId(Number(e.target.value))}
|
|
|
|
|
>
|
|
|
|
|
{kinds.map((k) => (
|
|
|
|
|
<option key={k.ID} value={k.ID}>
|
|
|
|
|
{k.Name}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<label>
|
|
|
|
|
Metric
|
|
|
|
|
<select value={metric} onChange={(e) => setMetric(e.target.value as ProgressionMetric)}>
|
|
|
|
|
{METRICS.map((m) => (
|
|
|
|
|
<option key={m.key} value={m.key}>
|
|
|
|
|
{m.label}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{error && <p className="error">{error}</p>}
|
|
|
|
|
{loading ? <p>Loading...</p> : <ProgressionChart points={points} metric={metric} />}
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|