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 (
)
}
if (err) {
return (
)
}
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) => (
))}
) : 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.
)}
)
}