import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid, PieChart, Pie, LineChart, Line, Cell, } from 'recharts' import { api } from '../utils/api' import KpiTilesOverview from './KpiTilesOverview' import { getStatusColor } from '../utils/interpret' import dayjs from 'dayjs' const PERIODS = [ { v: 7, label: '7 Tage' }, { v: 28, label: '28 Tage' }, { v: 90, label: '90 Tage' }, { v: 9999, label: 'Gesamt' }, ] /** * Layer 2b: Kennzahlen und Charts nur aus GET /api/charts/fitness-dashboard-viz (activity_metrics). */ export default function FitnessDashboardOverview({ period: periodProp, onPeriodChange, hidePeriodSelector = false, }) { const nav = useNavigate() const [internalPeriod, setInternalPeriod] = useState(28) const controlled = periodProp !== undefined && typeof onPeriodChange === 'function' const period = controlled ? periodProp : internalPeriod const setPeriod = controlled ? onPeriodChange : setInternalPeriod const [viz, setViz] = useState(null) const [loading, setLoading] = useState(true) const [err, setErr] = useState(null) useEffect(() => { let cancelled = false setLoading(true) setErr(null) api .getFitnessDashboardViz(period) .then((v) => { if (!cancelled) setViz(v) }) .catch((e) => { if (!cancelled) setErr(e.message || 'Laden fehlgeschlagen') }) .finally(() => { if (!cancelled) setLoading(false) }) return () => { cancelled = true } }, [period]) if (loading) { return (
Fitness-Übersicht
) } if (err) { return (
Fitness-Übersicht
{err}
) } if (!viz?.has_activity_entries) { return (
Fitness-Übersicht

Noch keine Aktivitätsdaten. Sobald du Trainings erfasst oder importierst, erscheinen Auswertungen hier.

) } const vol = viz.charts?.training_volume const typ = viz.charts?.training_type_distribution const qual = viz.charts?.quality_sessions const loadCh = viz.charts?.load_monitoring const volRows = (vol?.data?.labels || []).map((name, i) => ({ name, min: vol?.data?.datasets?.[0]?.data?.[i] ?? 0, })) const pieLabels = typ?.data?.labels || [] const pieVals = typ?.data?.datasets?.[0]?.data || [] const pieColors = typ?.data?.datasets?.[0]?.backgroundColor || [] const pieData = pieLabels.map((name, i) => ({ name, value: pieVals[i], fill: pieColors[i] || '#888780', })) const qualLabels = qual?.data?.labels || [] const qualVals = qual?.data?.datasets?.[0]?.data || [] const qualBg = qual?.data?.datasets?.[0]?.backgroundColor || [] const qualBar = qualLabels.map((name, i) => ({ name, n: qualVals[i] ?? 0, fill: qualBg[i] || '#1D9E75', })) const loadLabels = loadCh?.data?.labels || [] const loadVals = loadCh?.data?.datasets?.[0]?.data || [] const loadRows = loadLabels.map((iso, i) => ({ t: dayjs(iso).format('DD.MM.'), load: loadVals[i] ?? 0, })) const loadMeta = loadCh?.metadata || {} const kpiTiles = (viz.kpi_tiles || []).map((t) => ({ ...t, sublabel: typeof t.sublabel === 'string' && t.sublabel.length > 42 ? `${t.sublabel.slice(0, 40)}…` : t.sublabel, })) const insights = viz.progress_insights || [] const eff = viz.effective_window_days const wUsed = viz.training_volume_weeks_used const dTyp = viz.training_type_dist_days_used const loadDays = viz.load_chart_days_used const showPeriodDropdown = !hidePeriodSelector && !controlled return (
Fitness-Übersicht {showPeriodDropdown ? ( ) : null}

Alles aus dem Aktivitäts-Data-Layer (Issue 53). Zusammenfassung ca. {eff} Tage · Volumen{' '} {wUsed} Wochen · Kategorien {dTyp} Tage · Load-Zeitreihe{' '} {loadDays ?? '—'} Tage {viz.last_updated ? ( <> {' '} · letzte Aktivität {viz.last_updated} ) : null} .

{insights.length > 0 ? (
Einschätzungen
{insights.map((ins) => (
{ins.title}
{ins.body}
))}
) : null}
Trainingsvolumen (Minuten / Woche)
{volRows.length >= 1 ? ( [`${Math.round(v)} min`, 'Volumen']} /> ) : (
Keine Wochendaten im gewählten Fenster.
)}
Training nach Kategorie
{pieData.length >= 1 ? ( `${name} ${(percent * 100).toFixed(0)}%`} /> ) : (
Keine kategorisierten Sessions im Fenster.
)}
Qualitäts-Sessions (Schätzung)
{qualBar.length >= 1 ? ( {qualBar.map((entry, i) => ( ))} ) : (
Keine Daten.
)}
Belastung (Proxy-Load · duration×RPE / Tag)
{loadRows.length >= 1 ? ( <>
ACWR {loadMeta.acwr != null ? Number(loadMeta.acwr).toFixed(2) : '—'} ( {loadMeta.acwr_status === 'optimal' ? 'oft als günstig beschrieben' : 'außerhalb 0,8–1,3'} · Proxy)
) : (
Keine Load-Daten im Fenster.
)}
) }