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.
nav('/capture')}>
Erfassen starten
)}
{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 && (
)}
{(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! 🎉'}
)
})()}
)}
{(weights.length>2||nutrition.length>2) && (
nav('/history',{state:{tab:'body'}})}>
Details →
}
>
)}
{(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 →
}
>
{pipelineLoading
? <> Analyse läuft… (3 Stufen)>
: <>
🔬 Mehrstufige Analyse starten>}
{pipelineError && {pipelineError}
}
{latestInsight ? (
<>
Letzte Analyse: {dayjs(latestInsight.created).format('DD. MMMM YYYY, HH:mm')}
setShowInsight(s=>!s)}>
{showInsight?'▲ Weniger anzeigen':'▼ Vollständig anzeigen'}
>
) : (
Noch keine KI-Auswertung vorhanden.
nav('/analysis')}>
Erste Analyse erstellen
)}
>}
)
}