import { useState, useEffect } from 'react' import { LineChart, Line, BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid, ReferenceLine } 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}
) } /** * Nutrition Charts Component (E1-E5) * * Displays 4 nutrition chart endpoints: * - Energy Balance Timeline (E1) * - Macro Distribution (E2) * - Protein Adequacy (E3) * - Nutrition Consistency (E5) */ export default function NutritionCharts({ days = 28 }) { const [energyData, setEnergyData] = useState(null) const [macroData, setMacroData] = useState(null) const [proteinData, setProteinData] = useState(null) const [consistencyData, setConsistencyData] = useState(null) const [loading, setLoading] = useState({}) const [errors, setErrors] = useState({}) useEffect(() => { loadCharts() }, [days]) const loadCharts = async () => { // Load all 4 charts in parallel await Promise.all([ loadEnergyBalance(), loadMacroDistribution(), loadProteinAdequacy(), loadConsistency() ]) } const loadEnergyBalance = async () => { setLoading(l => ({...l, energy: true})) setErrors(e => ({...e, energy: null})) try { const data = await api.getEnergyBalanceChart(days) setEnergyData(data) } catch (err) { setErrors(e => ({...e, energy: err.message})) } finally { setLoading(l => ({...l, energy: false})) } } const loadMacroDistribution = async () => { setLoading(l => ({...l, macro: true})) setErrors(e => ({...e, macro: null})) try { const data = await api.getMacroDistributionChart(days) setMacroData(data) } catch (err) { setErrors(e => ({...e, macro: err.message})) } finally { setLoading(l => ({...l, macro: false})) } } const loadProteinAdequacy = async () => { setLoading(l => ({...l, protein: true})) setErrors(e => ({...e, protein: null})) try { const data = await api.getProteinAdequacyChart(days) setProteinData(data) } catch (err) { setErrors(e => ({...e, protein: err.message})) } finally { setLoading(l => ({...l, protein: false})) } } const loadConsistency = async () => { setLoading(l => ({...l, consistency: true})) setErrors(e => ({...e, consistency: null})) try { const data = await api.getNutritionConsistencyChart(days) setConsistencyData(data) } catch (err) { setErrors(e => ({...e, consistency: err.message})) } finally { setLoading(l => ({...l, consistency: false})) } } // E1: Energy Balance Timeline const renderEnergyBalance = () => { if (!energyData || energyData.metadata?.confidence === 'insufficient') { return
Nicht genug Ernährungsdaten
} const chartData = energyData.data.labels.map((label, i) => ({ date: fmtDate(label), kcal: energyData.data.datasets[0]?.data[i], tdee: energyData.data.datasets[1]?.data[i] })) return ( <>
Ø {energyData.metadata.avg_kcal} kcal/Tag · {energyData.metadata.data_points} Einträge
) } // E2: Macro Distribution (Pie) const renderMacroDistribution = () => { if (!macroData || macroData.metadata?.confidence === 'insufficient') { return
Nicht genug Makronährstoff-Daten
} const chartData = macroData.data.labels.map((label, i) => ({ name: label, value: macroData.data.datasets[0]?.data[i], color: macroData.data.datasets[0]?.backgroundColor[i] })) return ( <> `${name}: ${value}%`} outerRadius={70} dataKey="value" > {chartData.map((entry, index) => ( ))}
P: {macroData.metadata.protein_g}g · C: {macroData.metadata.carbs_g}g · F: {macroData.metadata.fat_g}g
) } // E3: Protein Adequacy Timeline const renderProteinAdequacy = () => { if (!proteinData || proteinData.metadata?.confidence === 'insufficient') { return
Nicht genug Protein-Daten
} const chartData = proteinData.data.labels.map((label, i) => ({ date: fmtDate(label), protein: proteinData.data.datasets[0]?.data[i], targetLow: proteinData.data.datasets[1]?.data[i], targetHigh: proteinData.data.datasets[2]?.data[i] })) return ( <>
{proteinData.metadata.days_in_target}/{proteinData.metadata.data_points} Tage im Zielbereich ({proteinData.metadata.target_compliance_pct}%)
) } // E5: Nutrition Consistency (Bar) const renderConsistency = () => { if (!consistencyData || consistencyData.metadata?.confidence === 'insufficient') { return
Nicht genug Daten für Konsistenz-Analyse
} const chartData = consistencyData.data.labels.map((label, i) => ({ name: label, score: consistencyData.data.datasets[0]?.data[i], color: consistencyData.data.datasets[0]?.backgroundColor[i] })) return ( <> {chartData.map((entry, index) => ( ))}
Gesamt-Score: {consistencyData.metadata.consistency_score}/100
) } return (
{renderEnergyBalance()} {renderMacroDistribution()} {renderProteinAdequacy()} {renderConsistency()}
) }