- Added `get_energy_availability_warning_payload` function to assess energy availability and provide contextual warnings based on multiple health indicators. - Integrated energy availability KPI tile into the nutrition history visualization, enhancing user insights on energy balance. - Updated frontend components to conditionally display the energy availability warning, improving user experience and data interpretation. - Refactored existing logic in `charts.py` to utilize the new energy availability functionality, streamlining data handling.
488 lines
19 KiB
JavaScript
488 lines
19 KiB
JavaScript
import { useState, useEffect } from 'react'
|
||
import {
|
||
LineChart, Line, BarChart, Bar,
|
||
XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid, Legend,
|
||
ComposedChart, ReferenceArea,
|
||
} from 'recharts'
|
||
import { api } from '../utils/api'
|
||
import { MACRO_CHART, NUTRITION_MACRO_CHART_BLOCK_PX } from '../utils/macroChartTheme'
|
||
import dayjs from 'dayjs'
|
||
|
||
const fmtDate = d => dayjs(d).format('DD.MM')
|
||
|
||
function ChartCard({ title, loading, error, children }) {
|
||
return (
|
||
<div className="card" style={{marginBottom:12}}>
|
||
<div style={{fontSize:12,fontWeight:600,color:'var(--text3)',marginBottom:8}}>
|
||
{title}
|
||
</div>
|
||
{loading && (
|
||
<div style={{display:'flex',justifyContent:'center',padding:40}}>
|
||
<div className="spinner" style={{width:32,height:32}}/>
|
||
</div>
|
||
)}
|
||
{error && (
|
||
<div style={{padding:20,textAlign:'center',color:'var(--text3)',fontSize:12}}>
|
||
{error}
|
||
</div>
|
||
)}
|
||
{!loading && !error && children}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function ScoreCard({ title, score, components, goal_mode, recommendation }) {
|
||
const scoreColor = score >= 80 ? '#1D9E75' : score >= 60 ? '#F59E0B' : '#EF4444'
|
||
|
||
return (
|
||
<div className="card" style={{marginBottom:12}}>
|
||
<div style={{fontSize:12,fontWeight:600,color:'var(--text3)',marginBottom:12}}>
|
||
{title}
|
||
</div>
|
||
|
||
{/* Score Circle */}
|
||
<div style={{display:'flex',alignItems:'center',justifyContent:'center',marginBottom:16}}>
|
||
<div style={{
|
||
width:120,height:120,borderRadius:'50%',
|
||
border:`8px solid ${scoreColor}`,
|
||
display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center'
|
||
}}>
|
||
<div style={{fontSize:32,fontWeight:700,color:scoreColor}}>{score}</div>
|
||
<div style={{fontSize:10,color:'var(--text3)'}}>/ 100</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Components Breakdown */}
|
||
<div style={{fontSize:11,marginBottom:12}}>
|
||
{Object.entries(components).map(([key, value]) => {
|
||
const barColor = value >= 80 ? '#1D9E75' : value >= 60 ? '#F59E0B' : '#EF4444'
|
||
const label = {
|
||
'calorie_adherence': 'Kalorien-Adhärenz',
|
||
'protein_adherence': 'Protein-Adhärenz',
|
||
'intake_consistency': 'Konsistenz',
|
||
'food_quality': 'Lebensmittelqualität'
|
||
}[key] || key
|
||
|
||
return (
|
||
<div key={key} style={{marginBottom:8}}>
|
||
<div style={{display:'flex',justifyContent:'space-between',marginBottom:2}}>
|
||
<span style={{color:'var(--text2)',fontSize:10}}>{label}</span>
|
||
<span style={{color:'var(--text1)',fontSize:10,fontWeight:600}}>{value}</span>
|
||
</div>
|
||
<div style={{height:4,background:'var(--surface2)',borderRadius:2,overflow:'hidden'}}>
|
||
<div style={{height:'100%',width:`${value}%`,background:barColor,transition:'width 0.3s'}}/>
|
||
</div>
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
|
||
{/* Recommendation */}
|
||
<div style={{
|
||
padding:8,background:'var(--surface2)',borderRadius:6,
|
||
fontSize:10,color:'var(--text2)',marginBottom:8
|
||
}}>
|
||
💡 {recommendation}
|
||
</div>
|
||
|
||
{/* Goal Mode */}
|
||
<div style={{fontSize:9,color:'var(--text3)',textAlign:'center'}}>
|
||
Optimiert für: {goal_mode || 'health'}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function WarningCard({ title, warning_level, triggers, message }) {
|
||
const levelConfig = {
|
||
'warning': { icon: '⚠️', color: '#EF4444', bg: 'rgba(239, 68, 68, 0.1)' },
|
||
'caution': { icon: '⚡', color: '#F59E0B', bg: 'rgba(245, 158, 11, 0.1)' },
|
||
'none': { icon: '✅', color: '#1D9E75', bg: 'rgba(29, 158, 117, 0.1)' }
|
||
}[warning_level] || levelConfig['none']
|
||
|
||
return (
|
||
<div className="card" style={{marginBottom:12}}>
|
||
<div style={{fontSize:12,fontWeight:600,color:'var(--text3)',marginBottom:12}}>
|
||
{title}
|
||
</div>
|
||
|
||
{/* Status Badge */}
|
||
<div style={{
|
||
padding:16,background:levelConfig.bg,borderRadius:8,
|
||
borderLeft:`4px solid ${levelConfig.color}`,marginBottom:12
|
||
}}>
|
||
<div style={{fontSize:14,fontWeight:600,color:levelConfig.color,marginBottom:4}}>
|
||
{levelConfig.icon} {message}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Triggers List */}
|
||
{triggers && triggers.length > 0 && (
|
||
<div style={{marginTop:12}}>
|
||
<div style={{fontSize:10,fontWeight:600,color:'var(--text3)',marginBottom:6}}>
|
||
Auffällige Indikatoren:
|
||
</div>
|
||
<ul style={{margin:0,paddingLeft:20,fontSize:10,color:'var(--text2)'}}>
|
||
{triggers.map((t, i) => (
|
||
<li key={i} style={{marginBottom:4}}>{t}</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
)}
|
||
|
||
<div style={{fontSize:9,color:'var(--text3)',marginTop:12,fontStyle:'italic'}}>
|
||
Heuristische Einschätzung, keine medizinische Diagnose
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
/** Wöchentliche Makro-Verteilung (E3) — für Verlauf neben Donut nutzbar. */
|
||
export function WeeklyMacroDistributionPanel({ macroWeeklyData, loading, error }) {
|
||
if (loading) {
|
||
return (
|
||
<div style={{ display: 'flex', justifyContent: 'center', padding: 40 }}>
|
||
<div className="spinner" style={{ width: 32, height: 32 }} />
|
||
</div>
|
||
)
|
||
}
|
||
if (error) {
|
||
return (
|
||
<div style={{ padding: 20, textAlign: 'center', color: 'var(--text3)', fontSize: 12 }}>{error}</div>
|
||
)
|
||
}
|
||
if (!macroWeeklyData || macroWeeklyData.metadata?.confidence === 'insufficient') {
|
||
const msg = macroWeeklyData?.metadata?.message || 'Nicht genug Daten für Wochen-Analyse (min. 7 Tage)'
|
||
return (
|
||
<div style={{ padding: 20, textAlign: 'center', color: 'var(--text3)', fontSize: 12 }}>{msg}</div>
|
||
)
|
||
}
|
||
|
||
const chartData = macroWeeklyData.data.labels.map((label, i) => ({
|
||
week: label,
|
||
protein: macroWeeklyData.data.datasets[0]?.data[i],
|
||
carbs: macroWeeklyData.data.datasets[1]?.data[i],
|
||
fat: macroWeeklyData.data.datasets[2]?.data[i],
|
||
}))
|
||
|
||
const meta = macroWeeklyData.metadata
|
||
|
||
return (
|
||
<>
|
||
<div style={{ fontSize: 11, color: 'var(--text3)', lineHeight: 1.45, marginBottom: 10 }}>
|
||
Anteil der Kalorien aus jedem Makronährstoff pro Kalenderwoche (100 % gestapelt). Gut vergleichbar mit der
|
||
Donut-Übersicht links.
|
||
</div>
|
||
<div className="nutrition-macro-pair__chart-wrap">
|
||
<ResponsiveContainer width="100%" height={NUTRITION_MACRO_CHART_BLOCK_PX}>
|
||
<BarChart data={chartData} margin={{ top: 8, right: 4, bottom: 4, left: -18 }} barCategoryGap="18%">
|
||
<CartesianGrid stroke="var(--border)" strokeDasharray="3 3" vertical={false} />
|
||
<XAxis
|
||
dataKey="week"
|
||
tick={{ fontSize: 9, fill: 'var(--text3)' }}
|
||
tickLine={false}
|
||
interval={Math.max(0, Math.floor(chartData.length / 8) - 1)}
|
||
/>
|
||
<YAxis tick={{ fontSize: 9, fill: 'var(--text3)' }} tickLine={false} domain={[0, 100]} ticks={[0, 25, 50, 75, 100]} />
|
||
<Tooltip
|
||
contentStyle={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8, fontSize: 11 }}
|
||
formatter={(v, name) => [`${v}%`, name]}
|
||
/>
|
||
<Legend wrapperStyle={{ fontSize: 10, paddingTop: 8 }} />
|
||
<Bar dataKey="protein" stackId="a" fill={MACRO_CHART.protein} name="Protein %" radius={[0, 0, 0, 0]} />
|
||
<Bar dataKey="fat" stackId="a" fill={MACRO_CHART.fat} name="Fett %" radius={[0, 0, 0, 0]} />
|
||
<Bar dataKey="carbs" stackId="a" fill={MACRO_CHART.carbs} name="KH %" radius={[6, 6, 0, 0]} />
|
||
</BarChart>
|
||
</ResponsiveContainer>
|
||
</div>
|
||
<div style={{ marginTop: 10, fontSize: 10, color: 'var(--text3)', textAlign: 'center', lineHeight: 1.5 }}>
|
||
Ø Verteilung: P {meta.avg_protein_pct}% · KH {meta.avg_carbs_pct}% · F {meta.avg_fat_pct}% · Variabilität (CV): P{' '}
|
||
{meta.protein_cv}% · KH {meta.carbs_cv}% · F {meta.fat_cv}%
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
|
||
/**
|
||
* Nutrition Charts (E1–E5). Verlauf: `showWeeklyMacroDistribution={false}` wenn E3 separat (z. B. neben Donut) gerendert wird.
|
||
*/
|
||
export default function NutritionCharts({
|
||
days = 28,
|
||
showWeeklyMacroDistribution = true,
|
||
/** Verlauf: E5-Kachel liegt in nutrition-history-viz KPIs — doppelte Karte ausblenden */
|
||
hideEnergyAvailabilityCard = false,
|
||
}) {
|
||
const [energyData, setEnergyData] = useState(null)
|
||
const [proteinData, setProteinData] = useState(null)
|
||
const [macroWeeklyData, setMacroWeeklyData] = useState(null)
|
||
const [adherenceData, setAdherenceData] = useState(null)
|
||
const [warningData, setWarningData] = useState(null)
|
||
|
||
const [loading, setLoading] = useState({})
|
||
const [errors, setErrors] = useState({})
|
||
|
||
// Weeks for macro distribution (proportional to days selected)
|
||
const weeks = Math.max(4, Math.min(52, Math.ceil(days / 7)))
|
||
|
||
useEffect(() => {
|
||
loadCharts()
|
||
}, [days, showWeeklyMacroDistribution, hideEnergyAvailabilityCard])
|
||
|
||
const loadCharts = async () => {
|
||
const tasks = [
|
||
loadEnergyBalance(),
|
||
loadProteinAdequacy(),
|
||
loadAdherence(),
|
||
]
|
||
if (!hideEnergyAvailabilityCard) {
|
||
tasks.push(loadWarning())
|
||
}
|
||
if (showWeeklyMacroDistribution) {
|
||
tasks.splice(2, 0, loadMacroWeekly())
|
||
}
|
||
await Promise.all(tasks)
|
||
}
|
||
|
||
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 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 loadMacroWeekly = async () => {
|
||
setLoading(l => ({...l, macro: true}))
|
||
setErrors(e => ({...e, macro: null}))
|
||
try {
|
||
const data = await api.getWeeklyMacroDistributionChart(weeks)
|
||
setMacroWeeklyData(data)
|
||
} catch (err) {
|
||
setErrors(e => ({...e, macro: err.message}))
|
||
} finally {
|
||
setLoading(l => ({...l, macro: false}))
|
||
}
|
||
}
|
||
|
||
const loadAdherence = async () => {
|
||
setLoading(l => ({...l, adherence: true}))
|
||
setErrors(e => ({...e, adherence: null}))
|
||
try {
|
||
const data = await api.getNutritionAdherenceScore(days)
|
||
setAdherenceData(data)
|
||
} catch (err) {
|
||
setErrors(e => ({...e, adherence: err.message}))
|
||
} finally {
|
||
setLoading(l => ({...l, adherence: false}))
|
||
}
|
||
}
|
||
|
||
const loadWarning = async () => {
|
||
setLoading(l => ({...l, warning: true}))
|
||
setErrors(e => ({...e, warning: null}))
|
||
try {
|
||
const data = await api.getEnergyAvailabilityWarning(Math.min(days, 28))
|
||
setWarningData(data)
|
||
} catch (err) {
|
||
setErrors(e => ({...e, warning: err.message}))
|
||
} finally {
|
||
setLoading(l => ({...l, warning: false}))
|
||
}
|
||
}
|
||
|
||
// E1: Energy Balance — klare Farben (kein hellgraues Gewirr)
|
||
const renderEnergyBalance = () => {
|
||
if (!energyData || energyData.metadata?.confidence === 'insufficient') {
|
||
const msg = energyData?.metadata?.message || 'Nicht genug Ernährungsdaten für die Energiebilanz.'
|
||
return (
|
||
<div style={{ padding: 20, textAlign: 'center', color: 'var(--text3)', fontSize: 12 }}>{msg}</div>
|
||
)
|
||
}
|
||
|
||
const chartData = energyData.data.labels.map((label, i) => ({
|
||
date: fmtDate(label),
|
||
täglich: energyData.data.datasets[0]?.data[i],
|
||
avg7d: energyData.data.datasets[1]?.data[i],
|
||
avg14d: energyData.data.datasets[2]?.data[i],
|
||
tdee: energyData.data.datasets[3]?.data[i],
|
||
}))
|
||
|
||
const balance = energyData.metadata?.energy_balance || 0
|
||
const balanceColor = balance < -200 ? '#EF4444' : balance > 200 ? '#F59E0B' : '#1D9E75'
|
||
|
||
return (
|
||
<>
|
||
<div style={{ fontSize: 11, color: 'var(--text3)', lineHeight: 1.45, marginBottom: 8 }}>
|
||
Tägliche Aufnahme, gleitende Mittel und geschätzter TDEE — Linien sind farblich getrennt (Legende unten).
|
||
</div>
|
||
<ResponsiveContainer width="100%" height={240}>
|
||
<LineChart data={chartData} margin={{ top: 6, right: 10, bottom: 4, left: -18 }}>
|
||
<CartesianGrid stroke="var(--border)" strokeDasharray="3 3" vertical={false} />
|
||
<XAxis
|
||
dataKey="date"
|
||
tick={{ fontSize: 9, fill: 'var(--text3)' }}
|
||
tickLine={false}
|
||
interval={Math.max(0, Math.floor(chartData.length / 6) - 1)}
|
||
/>
|
||
<YAxis tick={{ fontSize: 9, fill: 'var(--text3)' }} tickLine={false} />
|
||
<Tooltip
|
||
contentStyle={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8, fontSize: 11 }}
|
||
/>
|
||
<Legend wrapperStyle={{ fontSize: 10, paddingTop: 6 }} />
|
||
<Line type="monotone" dataKey="täglich" stroke="#64748B" strokeWidth={2} dot={{ r: 2 }} name="Täglich kcal" />
|
||
<Line type="monotone" dataKey="avg14d" stroke="#6366F1" strokeWidth={2.5} dot={false} name="Ø 14 Tage" />
|
||
<Line type="monotone" dataKey="avg7d" stroke="#10B981" strokeWidth={3} dot={false} name="Ø 7 Tage" />
|
||
<Line type="monotone" dataKey="tdee" stroke="#EA580C" strokeWidth={2.5} strokeDasharray="10 5" dot={false} name="TDEE (Referenz)" />
|
||
</LineChart>
|
||
</ResponsiveContainer>
|
||
<div style={{ marginTop: 8, fontSize: 10, textAlign: 'center', lineHeight: 1.5 }}>
|
||
<span style={{ color: 'var(--text3)' }}>Ø {energyData.metadata.avg_kcal} kcal/Tag ·</span>
|
||
<span style={{ color: balanceColor, fontWeight: 600, marginLeft: 4 }}>
|
||
Balance: {balance > 0 ? '+' : ''}
|
||
{balance} kcal/Tag
|
||
</span>
|
||
<span style={{ color: 'var(--text3)', marginLeft: 8 }}>· {energyData.metadata.data_points} Tage</span>
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
|
||
// E2: Protein — Zielzone als Fläche, Linien klar von E1 abgrenzbar
|
||
const renderProteinAdequacy = () => {
|
||
if (!proteinData || proteinData.metadata?.confidence === 'insufficient') {
|
||
const msg = proteinData?.metadata?.message || 'Nicht genug Protein-Daten für dieses Diagramm.'
|
||
return (
|
||
<div style={{ padding: 20, textAlign: 'center', color: 'var(--text3)', fontSize: 12 }}>{msg}</div>
|
||
)
|
||
}
|
||
|
||
const tl = proteinData.metadata.target_low
|
||
const th = proteinData.metadata.target_high
|
||
|
||
const chartData = proteinData.data.labels.map((label, i) => ({
|
||
date: fmtDate(label),
|
||
täglich: proteinData.data.datasets[0]?.data[i],
|
||
avg7d: proteinData.data.datasets[1]?.data[i],
|
||
avg28d: proteinData.data.datasets[2]?.data[i],
|
||
}))
|
||
|
||
return (
|
||
<>
|
||
<div style={{ fontSize: 11, color: 'var(--text3)', lineHeight: 1.45, marginBottom: 8 }}>
|
||
Grüne Zone = empfohlenes Protein-Ziel (g/Tag). Tägliche Werte und Mittel — andere Farben als Energiebilanz oben.
|
||
</div>
|
||
<ResponsiveContainer width="100%" height={250}>
|
||
<ComposedChart data={chartData} margin={{ top: 6, right: 10, bottom: 4, left: -18 }}>
|
||
<CartesianGrid stroke="var(--border)" strokeDasharray="3 3" vertical={false} />
|
||
<XAxis
|
||
dataKey="date"
|
||
tick={{ fontSize: 9, fill: 'var(--text3)' }}
|
||
tickLine={false}
|
||
interval={Math.max(0, Math.floor(chartData.length / 6) - 1)}
|
||
/>
|
||
<YAxis tick={{ fontSize: 9, fill: 'var(--text3)' }} tickLine={false} />
|
||
<Tooltip
|
||
contentStyle={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8, fontSize: 11 }}
|
||
/>
|
||
{tl != null && th != null && (
|
||
<ReferenceArea y1={tl} y2={th} fill="rgba(16, 185, 129, 0.14)" stroke="#10B981" strokeOpacity={0.35} />
|
||
)}
|
||
<Legend wrapperStyle={{ fontSize: 10, paddingTop: 6 }} />
|
||
<Line type="monotone" dataKey="avg28d" stroke="#7C3AED" strokeWidth={2.5} dot={false} name="Ø 28 Tage" />
|
||
<Line type="monotone" dataKey="avg7d" stroke="#059669" strokeWidth={3} dot={false} name="Ø 7 Tage" />
|
||
<Line type="monotone" dataKey="täglich" stroke="#0284C7" strokeWidth={2} dot={{ r: 2 }} name="Täglich g" />
|
||
</ComposedChart>
|
||
</ResponsiveContainer>
|
||
<div style={{ marginTop: 8, fontSize: 10, color: 'var(--text3)', textAlign: 'center', lineHeight: 1.5 }}>
|
||
Ziel {tl}–{th} g/Tag · {proteinData.metadata.days_in_target}/{proteinData.metadata.data_points} Tage im Zielbereich (
|
||
{proteinData.metadata.target_compliance_pct}%)
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
|
||
// E4: Nutrition Adherence Score
|
||
const renderAdherence = () => {
|
||
if (!adherenceData || adherenceData.metadata?.confidence === 'insufficient') {
|
||
return (
|
||
<ChartCard title="🎯 Ernährungs-Adhärenz Score" loading={loading.adherence} error={errors.adherence}>
|
||
<div style={{padding:20,textAlign:'center',color:'var(--text3)',fontSize:12}}>
|
||
Nicht genug Daten (min. 7 Tage)
|
||
</div>
|
||
</ChartCard>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<ScoreCard
|
||
title="🎯 Ernährungs-Adhärenz Score"
|
||
score={adherenceData.score}
|
||
components={adherenceData.components}
|
||
goal_mode={adherenceData.goal_mode}
|
||
recommendation={adherenceData.recommendation}
|
||
/>
|
||
)
|
||
}
|
||
|
||
// E5: Energy Availability Warning
|
||
const renderWarning = () => {
|
||
if (!warningData) {
|
||
return (
|
||
<ChartCard title="⚡ Energieverfügbarkeit" loading={loading.warning} error={errors.warning}>
|
||
<div style={{padding:20,textAlign:'center',color:'var(--text3)',fontSize:12}}>
|
||
Keine Daten verfügbar
|
||
</div>
|
||
</ChartCard>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<WarningCard
|
||
title="⚡ Energieverfügbarkeit"
|
||
warning_level={warningData.warning_level}
|
||
triggers={warningData.triggers}
|
||
message={warningData.message}
|
||
/>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
<ChartCard title="📊 Energiebilanz" loading={loading.energy} error={errors.energy}>
|
||
{renderEnergyBalance()}
|
||
</ChartCard>
|
||
|
||
<ChartCard title="📊 Protein (Adequacy)" loading={loading.protein} error={errors.protein}>
|
||
{renderProteinAdequacy()}
|
||
</ChartCard>
|
||
|
||
{showWeeklyMacroDistribution && (
|
||
<ChartCard title="📊 Wöchentliche Makro-Verteilung" loading={loading.macro} error={errors.macro}>
|
||
<WeeklyMacroDistributionPanel macroWeeklyData={macroWeeklyData} loading={false} error={null} />
|
||
</ChartCard>
|
||
)}
|
||
|
||
{!loading.adherence && !errors.adherence && renderAdherence()}
|
||
{!hideEnergyAvailabilityCard && !loading.warning && !errors.warning && renderWarning()}
|
||
</div>
|
||
)
|
||
}
|