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()}
)
}