import { useState, useEffect } from 'react' import { LineChart, Line, BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid } from 'recharts' import { api } from '../utils/api' import dayjs from 'dayjs' const fmtDate = d => dayjs(d).format('DD.MM') function ChartCard({ title, loading, error, children }) { return (
{title}
{loading && (
)} {error && (
{error}
)} {!loading && !error && children}
) } /** * Recovery Charts Component (R1-R5) * * Displays 5 recovery chart endpoints: * - Recovery Score Timeline (R1) * - HRV/RHR vs Baseline (R2) * - Sleep Duration + Quality (R3) * - Sleep Debt (R4) * - Vital Signs Matrix (R5) */ export default function RecoveryCharts({ days = 28 }) { const [recoveryData, setRecoveryData] = useState(null) const [hrvRhrData, setHrvRhrData] = useState(null) const [sleepData, setSleepData] = useState(null) const [debtData, setDebtData] = useState(null) const [vitalsData, setVitalsData] = useState(null) const [loading, setLoading] = useState({}) const [errors, setErrors] = useState({}) useEffect(() => { loadCharts() }, [days]) const loadCharts = async () => { // Load all 5 charts in parallel await Promise.all([ loadRecoveryScore(), loadHrvRhr(), loadSleepQuality(), loadSleepDebt(), loadVitalSigns() ]) } const loadRecoveryScore = async () => { setLoading(l => ({...l, recovery: true})) setErrors(e => ({...e, recovery: null})) try { const data = await api.getRecoveryScoreChart(days) setRecoveryData(data) } catch (err) { setErrors(e => ({...e, recovery: err.message})) } finally { setLoading(l => ({...l, recovery: false})) } } const loadHrvRhr = async () => { setLoading(l => ({...l, hrvRhr: true})) setErrors(e => ({...e, hrvRhr: null})) try { const data = await api.getHrvRhrBaselineChart(days) setHrvRhrData(data) } catch (err) { setErrors(e => ({...e, hrvRhr: err.message})) } finally { setLoading(l => ({...l, hrvRhr: false})) } } const loadSleepQuality = async () => { setLoading(l => ({...l, sleep: true})) setErrors(e => ({...e, sleep: null})) try { const data = await api.getSleepDurationQualityChart(days) setSleepData(data) } catch (err) { setErrors(e => ({...e, sleep: err.message})) } finally { setLoading(l => ({...l, sleep: false})) } } const loadSleepDebt = async () => { setLoading(l => ({...l, debt: true})) setErrors(e => ({...e, debt: null})) try { const data = await api.getSleepDebtChart(days) setDebtData(data) } catch (err) { setErrors(e => ({...e, debt: err.message})) } finally { setLoading(l => ({...l, debt: false})) } } const loadVitalSigns = async () => { setLoading(l => ({...l, vitals: true})) setErrors(e => ({...e, vitals: null})) try { const data = await api.getVitalSignsMatrixChart(7) // Last 7 days setVitalsData(data) } catch (err) { setErrors(e => ({...e, vitals: err.message})) } finally { setLoading(l => ({...l, vitals: false})) } } // R1: Recovery Score Timeline const renderRecoveryScore = () => { if (!recoveryData || recoveryData.metadata?.confidence === 'insufficient') { return
Keine Recovery-Daten vorhanden
} const chartData = recoveryData.data.labels.map((label, i) => ({ date: fmtDate(label), score: recoveryData.data.datasets[0]?.data[i] })) return ( <>
Aktuell: {recoveryData.metadata.current_score}/100 · {recoveryData.metadata.data_points} Einträge
) } // R2: HRV/RHR vs Baseline const renderHrvRhr = () => { if (!hrvRhrData || hrvRhrData.metadata?.confidence === 'insufficient') { return
Keine Vitalwerte vorhanden
} const chartData = hrvRhrData.data.labels.map((label, i) => ({ date: fmtDate(label), hrv: hrvRhrData.data.datasets[0]?.data[i], rhr: hrvRhrData.data.datasets[1]?.data[i] })) return ( <>
HRV Ø {hrvRhrData.metadata.avg_hrv}ms · RHR Ø {hrvRhrData.metadata.avg_rhr}bpm
) } // R3: Sleep Duration + Quality const renderSleepQuality = () => { if (!sleepData || sleepData.metadata?.confidence === 'insufficient') { return
Keine Schlafdaten vorhanden
} const chartData = sleepData.data.labels.map((label, i) => ({ date: fmtDate(label), duration: sleepData.data.datasets[0]?.data[i], quality: sleepData.data.datasets[1]?.data[i] })) return ( <>
Ø {sleepData.metadata.avg_duration_hours}h Schlaf
) } // R4: Sleep Debt const renderSleepDebt = () => { if (!debtData || debtData.metadata?.confidence === 'insufficient') { return
Keine Schlafdaten für Schulden-Berechnung
} const chartData = debtData.data.labels.map((label, i) => ({ date: fmtDate(label), debt: debtData.data.datasets[0]?.data[i] })) return ( <>
Aktuelle Schuld: {debtData.metadata.current_debt_hours.toFixed(1)}h
) } // R5: Vital Signs Matrix (Bar) const renderVitalSigns = () => { if (!vitalsData || vitalsData.metadata?.confidence === 'insufficient') { return
Keine aktuellen Vitalwerte
} const chartData = vitalsData.data.labels.map((label, i) => ({ name: label, value: vitalsData.data.datasets[0]?.data[i] })) return ( <>
Letzte {vitalsData.metadata.data_points} Messwerte (7 Tage)
) } return (
{renderRecoveryScore()} {renderHrvRhr()} {renderSleepQuality()} {renderSleepDebt()} {renderVitalSigns()}
) }