import { useState, useEffect } from 'react' import { useNavigate, useLocation } from 'react-router-dom' import { Brain } from 'lucide-react' import { api } from '../utils/api' import { useProfile } from '../context/ProfileContext' import { getBfCategory } from '../utils/calc' import TrialBanner from '../components/TrialBanner' import EmailVerificationBanner from '../components/EmailVerificationBanner' import TrainingTypeDistribution from '../components/TrainingTypeDistribution' import SleepWidget from '../components/SleepWidget' import RestDaysWidget from '../components/RestDaysWidget' import Markdown from '../utils/Markdown' import QuickWeightEntry from '../components/QuickWeightEntry' import TrendKcalWeightChart from '../components/TrendKcalWeightChart' import { Pill, StatCard } from '../components/DashboardStatKit' import dayjs from 'dayjs' import 'dayjs/locale/de' import DashboardSection from '../components/DashboardSection' import DashboardTile from '../components/DashboardTile' import { DASHBOARD_TILE_GRID_COLS, dashboardStatGridClassName, dashboardTileGridClassName } from '../utils/dashboardLayout' dayjs.locale('de') // ── Main Dashboard ──────────────────────────────────────────────────────────── export default function Dashboard() { const nav = useNavigate() const location = useLocation() const { activeProfile } = useProfile() const [adminDeniedHint, setAdminDeniedHint] = useState(false) const [goalsCount, setGoalsCount] = useState(null) const [stats, setStats] = useState(null) const [weights, setWeights] = useState([]) const [calipers, setCalipers] = useState([]) const [circs, setCircs] = useState([]) const [nutrition, setNutrition] = useState([]) const [activities,setActivities]= useState([]) const [insights, setInsights] = useState([]) const [loading, setLoading] = useState(true) const [showInsight, setShowInsight] = useState(false) const [pipelineLoading, setPipelineLoading] = useState(false) const [pipelineError, setPipelineError] = useState(null) const load = () => Promise.all([ api.getStats(), api.listWeight(60), api.listCaliper(3), api.listCirc(2), api.listNutrition(30), api.listActivity(800, 30), api.latestInsights(), ]).then(([s,w,ca,ci,n,a,ins])=>{ setStats(s); setWeights(w); setCalipers(ca); setCircs(ci) setNutrition(n); setActivities(a) setInsights(Array.isArray(ins)?ins:[]) setLoading(false) }).catch(err => { console.error('Dashboard load failed:', err) // Set empty data on error so UI can still render setStats(null); setWeights([]); setCalipers([]); setCircs([]) setNutrition([]); setActivities([]); setInsights([]) setLoading(false) }) const runPipeline = async () => { setPipelineLoading(true); setPipelineError(null) try { await api.insightPipeline() await load() } catch(e) { setPipelineError('Fehler: '+e.message) } finally { setPipelineLoading(false) } } useEffect(()=>{ load() },[]) useEffect(() => { if (!location.state?.adminDenied) return setAdminDeniedHint(true) nav('.', { replace: true, state: null }) const clear = window.setTimeout(() => setAdminDeniedHint(false), 8000) return () => window.clearTimeout(clear) }, [location.state, nav]) useEffect(() => { if (!activeProfile?.id) return api.listGoals() .then((list) => setGoalsCount(Array.isArray(list) ? list.length : 0)) .catch(() => setGoalsCount(null)) }, [activeProfile?.id]) if (loading) return
const latestCal = calipers[0] const latestCir = circs[0] const latestW = weights[0] const prevW = weights[1] const sex = activeProfile?.sex||'m' const height = activeProfile?.height||178 // Deltas const wDelta = latestW&&prevW ? Math.round((latestW.weight-prevW.weight)*10)/10 : null const bfCat = latestCal?.body_fat_pct ? getBfCategory(latestCal.body_fat_pct,sex) : null const bfPrev = calipers[1]?.body_fat_pct const bfDelta = latestCal?.body_fat_pct&&bfPrev ? Math.round((latestCal.body_fat_pct-bfPrev)*10)/10 : null // WHR / WHtR const whr = latestCir?.c_waist&&latestCir?.c_hip ? Math.round(latestCir.c_waist/latestCir.c_hip*100)/100 : null const whtr = latestCir?.c_waist&&height ? Math.round(latestCir.c_waist/height*100)/100 : null // Nutrition averages (last 7 days) const recentNutr = nutrition.filter(n=>n.date>=dayjs().subtract(7,'day').format('YYYY-MM-DD')) const avgKcal = recentNutr.length ? Math.round(recentNutr.reduce((s,n)=>s+(n.kcal||0),0)/recentNutr.length) : null const avgProtein = recentNutr.length ? Math.round(recentNutr.reduce((s,n)=>s+(n.protein_g||0),0)/recentNutr.length*10)/10 : null const ptLow = Math.round((latestW?.weight||80)*1.6) const proteinOk = avgProtein && avgProtein >= ptLow // Activity (last 7 days) const recentAct = activities.filter(a=>a.date>=dayjs().subtract(7,'day').format('YYYY-MM-DD')) const actKcal = recentAct.length ? Math.round(recentAct.reduce((s,a)=>s+(a.kcal_active||0),0)) : null // Status pills const pills = [] if (whr) pills.push({label:'WHR', value:whr, status:whr<(sex==='m'?0.90:0.85)?'good':'warn', sub:`<${sex==='m'?'0,90':'0,85'}`}) if (whtr) pills.push({label:'WHtR', value:whtr, status:whtr<0.5?'good':'warn', sub:'<0,50'}) if (avgProtein) pills.push({label:'Protein Ø7T', value:avgProtein+'g', status:proteinOk?'good':'warn', sub:`Ziel ${ptLow}g`}) if (bfCat) pills.push({label:'KF', value:latestCal.body_fat_pct+'%', status:latestCal.body_fat_pct<(sex==='m'?18:25)?'good':'warn', sub:bfCat.label}) // Latest overall insight const latestInsight = insights.find(i=>i.scope==='gesamt')||insights[0] const hasAnyData = latestW||latestCal||nutrition.length>0 const showNutrSummary = !!(avgKcal || avgProtein) const showActSummary = actKcal != null const summaryBoth = showNutrSummary && showActSummary const summarySpanM = summaryBoth ? 1 : 2 const summarySpanD = summaryBoth ? 2 : 4 return (
{adminDeniedHint && (
Kein Admin-Zugriff. Dieser Bereich ist nur für Konten mit Administrator-Rolle. Du wurdest zur Übersicht weitergeleitet.
)} {/* Header greeting */}

Hallo, {activeProfile?.name||'Nutzer'} 👋

{dayjs().format('dddd, DD. MMMM YYYY')} {latestW && ` · Letztes Update ${dayjs(latestW.date).format('DD.MM.')}`}
{/* Email Verification Banner */} {activeProfile && } {/* Trial Banner */} {activeProfile && } {!hasAnyData && (

Willkommen bei Mitai Jinkendo!

Starte mit deiner ersten Messung.

)} {hasAnyData && <> nav('/weight')}> Alle Einträge → } >
nav('/history')} color="#378ADD"/> {latestCal?.body_fat_pct && nav('/history',{state:{tab:'body'}})} color={bfCat?.color}/>} {latestCal?.lean_mass && nav('/history',{state:{tab:'body'}})}/>} {avgKcal && nav('/history',{state:{tab:'nutrition'}})} color="#EF9F27"/>}
{pills.length > 0 && (
{pills.map((p,i)=>)}
)}
{(activeProfile?.goal_weight||activeProfile?.goal_bf_pct) && latestW && (
{activeProfile?.goal_weight && latestW && (()=>{ const start = Math.max(...weights.map(w=>w.weight)) const curr = latestW.weight const goal = activeProfile.goal_weight const total = start - goal const done = start - curr const pct = total > 0 ? Math.min(100, Math.round(done/total*100)) : 100 const remain = Math.round((curr-goal)*10)/10 return (
Gewicht: {curr} → {goal} kg {remain>0?`noch ${remain}kg`:'Ziel erreicht! 🎉'}
{pct}% des Weges
) })()} {activeProfile?.goal_bf_pct && latestCal?.body_fat_pct && (()=>{ const curr = latestCal.body_fat_pct const goal = activeProfile.goal_bf_pct const remain= Math.round((curr-goal)*10)/10 const pct = curr<=goal ? 100 : Math.min(100,Math.round((1-(curr-goal)/Math.max(curr-goal,5))*100)) return (
Körperfett: {curr}% → {goal}% {remain>0?`noch ${remain}%`:'Ziel erreicht! 🎉'}
Aktuell: {bfCat?.label}
) })()}
)} {(weights.length>2||nutrition.length>2) && ( nav('/history',{state:{tab:'body'}})}> Details → } >
Ø Kalorien Gewicht
)} {(showNutrSummary || showActSummary) && (
{showNutrSummary && (
nav('/history',{state:{tab:'nutrition'}})}>
🍽️ ERNÄHRUNG (Ø 7T)
{avgKcal &&
{avgKcal} kcal
} {avgProtein &&
{avgProtein}g Protein {proteinOk?'✓':'⚠️'}
}
→ Verlauf Ernährung
)} {showActSummary && (
nav('/history',{state:{tab:'activity'}})}>
🏋️ AKTIVITÄT (7T)
{actKcal} kcal
{recentAct.length} Trainings
→ Verlauf Aktivität
)}
)}
{activities.length > 0 && ( nav('/activity')}> Details → } >
)} { e.stopPropagation(); nav('/goals') }}> Ziele bearbeiten → } >
nav('/goals')}> {goalsCount != null && (
{goalsCount === 0 ? 'Noch keine Ziele angelegt.' : `${goalsCount} ${goalsCount === 1 ? 'Ziel' : 'Ziele'} im System.`}
)}
Hier pflegst du Focus Areas, Meilensteine und Fortschritt – unabhängig von der KI-Analyse-Seite. Tippen zum Öffnen oder unten in der Navigation Ziele wählen.
nav('/analysis')}> Analysen → } >
{pipelineError &&
{pipelineError}
} {latestInsight ? ( <>
Letzte Analyse: {dayjs(latestInsight.created).format('DD. MMMM YYYY, HH:mm')}
{!showInsight && (
)}
) : (
Noch keine KI-Auswertung vorhanden.
)}
}
) }