'use client'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useTheme } from 'next-themes'; import { motion, AnimatePresence } from 'framer-motion'; import { Heart, Sun, Cloud, CloudRain, CloudLightning, Frown, Moon, Activity, Brain, Users, GraduationCap, Droplets, Utensils, Footprints, BookOpen, AlertTriangle, CheckCircle2, TrendingUp, TrendingDown, Minus, Calendar, Download, Sparkles, ChevronRight, Clock, Shield, Phone, Stethoscope, Syringe, FileText, RefreshCw, BarChart3, Zap, Bed, Coffee, Dumbbell, Bike, Waves, PersonStanding, Music, Eye, Star, ArrowUpRight, ArrowDownRight, } from 'lucide-react'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Progress } from '@/components/ui/progress'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Separator } from '@/components/ui/separator'; import { useAppStore } from '@/lib/store'; import { t } from '@/lib/i18n'; import { apiGet } from '@/lib/api'; import { toast } from 'sonner'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, BarChart, Bar, Legend, } from 'recharts'; /* ─── Types ──────────────────────────────────────────────────────── */ interface WellnessCheckin { id: string; date: string; mood: number; sleepHours: number | null; sleepQuality: number | null; stressLevel: number | null; activityType: string | null; activityMinutes: number | null; mealsCount: number | null; waterGlasses: number | null; gratitudeEntry: string | null; notes: string | null; } interface WellnessScoreData { id: string; date: string; overallScore: number; physicalScore: number | null; mentalScore: number | null; socialScore: number | null; academicScore: number | null; } interface WellnessSummary { checkinCount: number; avgMood: number; avgSleepHours: number; avgSleepQuality: number; avgStressLevel: number; totalActivityMinutes: number; avgMealsCount: number; avgWaterGlasses: number; avgOverallScore: number; avgPhysicalScore: number; avgMentalScore: number; avgSocialScore: number; avgAcademicScore: number; moodDistribution: Record; stressDistribution: Record; gratitudeEntries: Array<{ date: string; entry: string }>; recommendations: string[]; } interface TrendDataPoint { date: string; mood: number | null; overallScore: number | null; physicalScore: number | null; mentalScore: number | null; socialScore: number | null; academicScore: number | null; sleepHours: number | null; stressLevel: number | null; } interface ClassAvgPoint { date: string; avgOverall: number; avgMood: number; avgStress: number; } interface HealthRecordData { student: { id: string; firstName: string; lastName: string; dateOfBirth: string | null }; healthRecord: { bloodType: string | null; allergies: string[]; medications: Array<{ name: string; dosage: string; frequency: string }>; conditions: string[]; doctorName: string | null; doctorPhone: string | null; insuranceNumber: string | null; insuranceProvider: string | null; lastCheckup: string | null; notes: string | null; } | null; emergencyContacts: Array<{ id: string; name: string; phone: string; relationship: string }>; healthAlerts: Array<{ type: string; label: string; severity: 'warning' | 'info' }>; } interface SchoolOverviewData { totalCheckins: number; totalStudents: number; recentCheckins: Array<{ id: string; mood: number; date: string; student: { id: string; firstName: string; lastName: string } }>; avgScores: { overall: number; physical: number; mental: number; social: number; academic: number }; lowWellnessAlerts: number; } /* ─── Helper Functions ───────────────────────────────────────────── */ function getMoodIcon(mood: number, size = 20) { switch (mood) { case 5: return ; case 4: return ; case 3: return ; case 2: return ; case 1: return ; default: return ; } } function getMoodLabel(mood: number): string { switch (mood) { case 5: return t('wellness.mood_excellent'); case 4: return t('wellness.mood_good'); case 3: return t('wellness.mood_okay'); case 2: return t('wellness.mood_low'); case 1: return t('wellness.mood_very_low'); default: return ''; } } function getScoreColor(score: number): string { if (score >= 80) return 'text-emerald-500'; if (score >= 60) return 'text-teal-500'; if (score >= 40) return 'text-amber-500'; if (score >= 20) return 'text-orange-500'; return 'text-rose-500'; } function getScoreLabel(score: number): string { if (score >= 80) return t('wellness.score_excellent'); if (score >= 60) return t('wellness.score_good'); if (score >= 40) return t('wellness.score_fair'); if (score >= 20) return t('wellness.score_poor'); return t('wellness.score_critical'); } function getStressColor(level: number): string { if (level <= 3) return 'bg-emerald-500'; if (level <= 5) return 'bg-teal-500'; if (level <= 7) return 'bg-amber-500'; if (level <= 9) return 'bg-orange-500'; return 'bg-rose-500'; } function getStressLabel(level: number): string { if (level <= 3) return t('wellness.stress_low'); if (level <= 5) return t('wellness.stress_moderate'); if (level <= 7) return t('wellness.stress_high'); return t('wellness.stress_very_high'); } function getSleepQualityLabel(quality: number): string { if (quality >= 4) return t('wellness.sleep_excellent'); if (quality >= 3) return t('wellness.sleep_good'); if (quality >= 2) return t('wellness.sleep_fair'); return t('wellness.sleep_poor'); } function getActivityIcon(type: string | null) { switch (type) { case 'running': return ; case 'walking': return ; case 'cycling': return ; case 'swimming': return ; case 'sports': return ; case 'dance': return ; case 'yoga': return ; default: return ; } } function formatDate(dateStr: string): string { const d = new Date(dateStr); return d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }); } /* ─── Animated Counter Component ─────────────────────────────────── */ function AnimatedCounter({ value, duration = 1000 }: { value: number; duration?: number }) { const [display, setDisplay] = useState(0); useEffect(() => { const start = display; const end = value; const startTime = Date.now(); const animate = () => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); const eased = 1 - Math.pow(1 - progress, 3); setDisplay(Math.round(start + (end - start) * eased)); if (progress < 1) requestAnimationFrame(animate); }; requestAnimationFrame(animate); }, [value, duration]); return <>{display}; } /* ─── Circular Wellness Gauge ────────────────────────────────────── */ function WellnessGauge({ score, size = 180 }: { score: number; size?: number }) { const strokeWidth = 12; const radius = (size - strokeWidth) / 2; const circumference = 2 * Math.PI * radius; const offset = circumference - (score / 100) * circumference; const getGaugeColor = (s: number) => { if (s >= 80) return '#10b981'; if (s >= 60) return '#14b8a6'; if (s >= 40) return '#f59e0b'; if (s >= 20) return '#f97316'; return '#ef4444'; }; const color = getGaugeColor(score); return (
{getScoreLabel(score)}
); } /* ─── Metric Card ────────────────────────────────────────────────── */ function MetricCard({ icon, label, value, unit, color, trend, delay = 0, }: { icon: React.ReactNode; label: string; value: number; unit?: string; color: string; trend?: 'up' | 'down' | 'neutral'; delay?: number; }) { return (
{icon}
{trend && (
{trend === 'up' ? : trend === 'down' ? : }
)}
{unit && {unit}}

{label}

); } /* ─── Mood Selector ──────────────────────────────────────────────── */ function MoodSelector({ value, onChange }: { value: number; onChange: (v: number) => void }) { const moods = [1, 2, 3, 4, 5]; return (
{moods.map((m) => ( onChange(m)} className={`p-3 rounded-xl border-2 transition-all ${ value === m ? 'border-emerald-500 bg-emerald-50 dark:bg-emerald-950/30 shadow-md' : 'border-transparent bg-muted/50 hover:border-muted-foreground/30' }`} > {getMoodIcon(m, 28)}

{getMoodLabel(m)}

))}
); } /* ─── Stress Level Indicator ─────────────────────────────────────── */ function StressLevelIndicator({ value, onChange }: { value: number; onChange: (v: number) => void }) { const levels = Array.from({ length: 10 }, (_, i) => i + 1); return (
{t('wellness.stress_label')} 7 ? 'destructive' : value > 4 ? 'secondary' : 'outline'}> {value}/10 - {getStressLabel(value)}
{levels.map((l) => ( onChange(l)} className={`flex-1 h-8 rounded-md transition-all text-xs font-medium ${ l <= value ? `${getStressColor(l)} text-white` : 'bg-muted text-muted-foreground' }`} > {l} ))}
); } /* ─── Main Component ─────────────────────────────────────────────── */ export default function StudentWellnessView() { const { theme } = useTheme(); const currentUser = useAppStore((s) => s.currentUser); const currentStudentId = useAppStore((s) => s.currentStudentId); const currentClassId = useAppStore((s) => s.currentClassId); const role = currentUser?.role || 'STUDENT'; const schoolId = currentUser?.schoolId || ''; const isStudent = role === 'STUDENT'; const isParent = role === 'PARENT'; const isAdmin = role === 'SCHOOL_ADMIN' || role === 'VICE_PRINCIPAL'; const isTeacher = role === 'TEACHER'; // State const [activeTab, setActiveTab] = useState('dashboard'); const [loading, setLoading] = useState(true); const [checkins, setCheckins] = useState([]); const [scores, setScores] = useState([]); const [latestScore, setLatestScore] = useState(null); const [summary, setSummary] = useState(null); const [trendData, setTrendData] = useState([]); const [classAvgData, setClassAvgData] = useState([]); const [healthRecord, setHealthRecord] = useState(null); const [schoolOverview, setSchoolOverview] = useState(null); const [summaryPeriod, setSummaryPeriod] = useState<'week' | 'month'>('week'); // Check-in form state const [checkinMood, setCheckinMood] = useState(3); const [checkinSleepHours, setCheckinSleepHours] = useState(7); const [checkinSleepQuality, setCheckinSleepQuality] = useState(3); const [checkinStressLevel, setCheckinStressLevel] = useState(5); const [checkinActivityType, setCheckinActivityType] = useState(''); const [checkinActivityMinutes, setCheckinActivityMinutes] = useState(0); const [checkinMealsCount, setCheckinMealsCount] = useState(3); const [checkinWaterGlasses, setCheckinWaterGlasses] = useState(6); const [checkinGratitude, setCheckinGratitude] = useState(''); const [checkinNotes, setCheckinNotes] = useState(''); const [submitting, setSubmitting] = useState(false); // Determine student ID based on role const studentId = useMemo(() => { if (isStudent) { // For students, find their student record return currentStudentId || ''; } return currentStudentId || ''; }, [isStudent, currentStudentId]); // Fetch wellness data const fetchWellnessData = useCallback(async () => { setLoading(true); try { if (isAdmin && !studentId) { // Admin: school-wide overview const data = await apiGet(`/api/wellness?schoolId=${schoolId}&role=${role}`); setSchoolOverview(data); // Also fetch school trends const trends = await apiGet<{ trendData: TrendDataPoint[] }>(`/api/wellness/trends?schoolId=${schoolId}&days=30`); setTrendData(trends.trendData); } else if (isTeacher && currentClassId && !studentId) { // Teacher: class overview const data = await apiGet<{ checkins: WellnessCheckin[]; scores: WellnessScoreData[]; studentCount: number }>( `/api/wellness?schoolId=${schoolId}&role=${role}&classGroupId=${currentClassId}` ); setCheckins(data.checkins); setScores(data.scores); } else if (studentId) { // Individual student data const data = await apiGet<{ checkins: WellnessCheckin[]; scores: WellnessScoreData[]; latestScore: WellnessScoreData | null }>( `/api/wellness?schoolId=${schoolId}&studentId=${studentId}` ); setCheckins(data.checkins); setScores(data.scores); setLatestScore(data.latestScore); // Fetch trends const trends = await apiGet<{ trendData: TrendDataPoint[]; classAvgData: ClassAvgPoint[] }>( `/api/wellness/trends?schoolId=${schoolId}&studentId=${studentId}&days=30&includeClassAvg=${isStudent || isParent ? 'true' : 'false'}${currentClassId ? `&classGroupId=${currentClassId}` : ''}` ); setTrendData(trends.trendData); setClassAvgData(trends.classAvgData || []); // Fetch summary const sum = await apiGet( `/api/wellness/summary?schoolId=${schoolId}&studentId=${studentId}&period=${summaryPeriod}` ); setSummary(sum); // Fetch health records const hr = await apiGet(`/api/health-records/${studentId}?schoolId=${schoolId}`); setHealthRecord(hr); } } catch (error) { console.error('Error fetching wellness data:', error); } finally { setLoading(false); } }, [schoolId, studentId, role, currentClassId, isStudent, isParent, isAdmin, summaryPeriod]); useEffect(() => { fetchWellnessData(); }, [fetchWellnessData]); // Submit check-in const handleSubmitCheckin = async () => { if (!studentId) { toast.error(t('wellness.checkin_error')); return; } setSubmitting(true); try { const res = await fetch('/api/wellness', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ schoolId, studentId, mood: checkinMood, sleepHours: checkinSleepHours, sleepQuality: checkinSleepQuality, stressLevel: checkinStressLevel, activityType: checkinActivityType || null, activityMinutes: checkinActivityMinutes || null, mealsCount: checkinMealsCount, waterGlasses: checkinWaterGlasses, gratitudeEntry: checkinGratitude || null, notes: checkinNotes || null, }), }); const data = await res.json(); if (data.success) { toast.success(t('wellness.checkin_success')); fetchWellnessData(); } else { toast.error(t('wellness.checkin_error')); } } catch { toast.error(t('wellness.checkin_error')); } finally { setSubmitting(false); } }; // Export report const handleExportReport = () => { if (!summary || !trendData.length) return; const lines = [ `${t('wellness.title')} - ${t('wellness.reports')}`, `${'='.repeat(50)}`, `${t('wellness.summary_period')}: ${summaryPeriod === 'week' ? t('wellness.summary_week') : t('wellness.summary_month')}`, '', `${t('wellness.overall_score')}: ${Math.round(summary.avgOverallScore)}`, `${t('wellness.average_mood')}: ${summary.avgMood}/5`, `${t('wellness.sleep_quality')}: ${summary.avgSleepQuality}/5`, `${t('wellness.stress_level')}: ${summary.avgStressLevel}/10`, `${t('wellness.physical_activity')}: ${summary.totalActivityMinutes} ${t('wellness.minutes')}`, `${t('wellness.sleep_hours')}: ${summary.avgSleepHours}${t('wellness.hours')}`, `${t('wellness.meals_count')}: ${summary.avgMealsCount}`, `${t('wellness.water_glasses')}: ${summary.avgWaterGlasses}`, '', `${t('wellness.recommendations')}:`, ...summary.recommendations.map((r, i) => ` ${i + 1}. ${r}`), ]; const blob = new Blob([lines.join('\n')], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `wellness-report-${new Date().toISOString().split('T')[0]}.txt`; a.click(); URL.revokeObjectURL(url); toast.success(t('wellness.export_success')); }; // Prepare radar chart data const radarData = useMemo(() => { if (!latestScore) return []; return [ { subject: t('wellness.physical'), score: latestScore.physicalScore ?? 0, fullMark: 100 }, { subject: t('wellness.mental'), score: latestScore.mentalScore ?? 0, fullMark: 100 }, { subject: t('wellness.social'), score: latestScore.socialScore ?? 0, fullMark: 100 }, { subject: t('wellness.academic'), score: latestScore.academicScore ?? 0, fullMark: 100 }, ]; }, [latestScore]); // Prepare trend chart data (filter out nulls for cleaner display) const trendChartData = useMemo(() => { return trendData.filter(d => d.overallScore !== null); }, [trendData]); // Merge class avg into trend data const mergedTrendData = useMemo(() => { if (classAvgData.length === 0) return trendChartData.map(d => ({ ...d, classAvg: null as number | null })); return trendChartData.map(d => { const classPt = classAvgData.find(c => c.date === d.date); return { ...d, classAvg: classPt?.avgOverall ?? null }; }); }, [trendChartData, classAvgData]); /* ─── Loading State ───────────────────────────────────────────── */ if (loading) { return (
{t('wellness.loading')}
); } /* ─── Admin / School Overview ─────────────────────────────────── */ if (isAdmin && !studentId && schoolOverview) { return (
{/* Header Banner */}

{t('wellness.school_overview')}

{t('wellness.subtitle')}

{/* Stats Cards */}
} label={t('wellness.students_checked_in')} value={schoolOverview.totalCheckins} color="from-emerald-500 to-teal-500" delay={0} /> } label={t('wellness.avg_wellness_score')} value={schoolOverview.avgScores.overall} color="from-teal-500 to-cyan-500" delay={0.1} /> } label={t('wellness.low_wellness_alerts')} value={schoolOverview.lowWellnessAlerts} color="from-amber-500 to-orange-500" delay={0.2} /> } label={t('wellness.physical_activity')} value={schoolOverview.avgScores.physical} color="from-cyan-500 to-blue-500" delay={0.3} />
{/* School Trend Chart */} {t('wellness.trend_chart')} {t('wellness.trend_chart_desc')}
{/* Recent Check-ins */} {t('wellness.recent_checkins')}
{schoolOverview.recentCheckins.slice(0, 20).map((c, idx) => ( {getMoodIcon(c.mood, 20)}

{c.student.firstName} {c.student.lastName}

{new Date(c.date).toLocaleDateString('de-DE')}

{getMoodLabel(c.mood)}
))}
); } /* ─── Student / Parent / Teacher-Individual View ──────────────── */ return (
{/* Header Banner */}

{t('wellness.title')}

{t('wellness.subtitle')}

{/* Tabs */} {t('wellness.dashboard')} {t('wellness.checkin')} {t('wellness.health_records')} {t('wellness.reports')} {/* ─── Dashboard Tab ──────────────────────────────────────── */} {/* Score + Radar */}
{/* Overall Score */} {t('wellness.overall_score')} {t('wellness.overall_score_desc')}
{[ { label: t('wellness.physical'), score: latestScore?.physicalScore ?? 0, color: 'text-teal-500' }, { label: t('wellness.mental'), score: latestScore?.mentalScore ?? 0, color: 'text-amber-500' }, { label: t('wellness.social'), score: latestScore?.socialScore ?? 0, color: 'text-violet-500' }, { label: t('wellness.academic'), score: latestScore?.academicScore ?? 0, color: 'text-cyan-500' }, ].map((item) => (

{Math.round(item.score)}

{item.label}

))}
{/* Radar Chart */} {t('wellness.wellness_radar')} {t('wellness.wellness_radar_desc')}
{/* Key Metrics */}
} label={t('wellness.average_mood')} value={summary?.avgMood ?? 0} unit="/5" color="from-amber-500 to-yellow-500" trend={summary && summary.avgMood >= 3 ? 'up' : 'down'} delay={0} /> } label={t('wellness.sleep_quality')} value={summary?.avgSleepQuality ?? 0} unit="/5" color="from-indigo-500 to-purple-500" trend={summary && summary.avgSleepQuality >= 3 ? 'up' : 'down'} delay={0.1} /> } label={t('wellness.stress_level')} value={summary?.avgStressLevel ?? 0} unit="/10" color="from-rose-500 to-pink-500" trend={summary && summary.avgStressLevel <= 5 ? 'up' : 'down'} delay={0.2} /> } label={t('wellness.physical_activity')} value={summary?.totalActivityMinutes ?? 0} unit={t('wellness.minutes')} color="from-emerald-500 to-teal-500" delay={0.3} />
{/* Trend Chart */} {t('wellness.trend_chart')} {t('wellness.trend_chart_desc')}
{classAvgData.length > 0 && ( )}
{/* Recent Check-ins */} {t('wellness.recent_checkins')} {checkins.length === 0 ? (

{t('wellness.no_checkins')}

{t('wellness.no_checkins_desc')}

) : (
{checkins.slice(0, 10).map((c, idx) => ( {getMoodIcon(c.mood, 24)}

{getMoodLabel(c.mood)}

{c.stressLevel && ( 7 ? 'destructive' : 'secondary'} className="text-xs"> {t('wellness.stress_level')}: {c.stressLevel}/10 )}
{new Date(c.date).toLocaleDateString('de-DE')} {c.sleepHours && {c.sleepHours}h} {c.activityMinutes && {getActivityIcon(c.activityType)}{c.activityMinutes}min} {c.waterGlasses && {c.waterGlasses}}
{c.gratitudeEntry && (

"{c.gratitudeEntry}"

)}
))}
)}
{/* ─── Daily Check-in Tab ──────────────────────────────────── */} {t('wellness.checkin')} {new Date().toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })} {/* Mood Selector */}
{/* Sleep Section */}
setCheckinSleepHours(parseFloat(e.target.value) || 0)} className="w-24" /> {t('wellness.hours')}

{checkinSleepHours >= 7 && checkinSleepHours <= 9 ? t('wellness.sleep_excellent') : checkinSleepHours >= 6 ? t('wellness.sleep_fair') : t('wellness.sleep_poor')}

{[1, 2, 3, 4, 5].map((q) => ( setCheckinSleepQuality(q)} className={`w-10 h-10 rounded-lg border-2 transition-all text-sm font-medium ${ checkinSleepQuality === q ? 'border-violet-500 bg-violet-50 dark:bg-violet-950/30 text-violet-600' : 'border-transparent bg-muted/50 text-muted-foreground hover:border-muted-foreground/30' }`} > {q} ))} {getSleepQualityLabel(checkinSleepQuality)}
{/* Stress Level */} {/* Physical Activity */}
setCheckinActivityMinutes(parseInt(e.target.value) || 0)} className="w-24" /> {t('wellness.minutes')}
{/* Nutrition & Hydration */}
{[1, 2, 3, 4, 5].map((m) => ( setCheckinMealsCount(m)} className={`w-10 h-10 rounded-lg border-2 transition-all text-sm ${ checkinMealsCount === m ? 'border-orange-500 bg-orange-50 dark:bg-orange-950/30 text-orange-600' : 'border-transparent bg-muted/50 text-muted-foreground hover:border-muted-foreground/30' }`} > {m} ))}
setCheckinWaterGlasses(parseInt(e.target.value) || 0)} className="w-20" />

{checkinWaterGlasses >= 8 ? t('wellness.score_excellent') : checkinWaterGlasses >= 6 ? t('wellness.score_good') : t('wellness.score_fair')}

{/* Gratitude & Notes */}