'use client'; import { useState, useEffect, useCallback } from 'react'; import { Box, Typography, Button, Paper, CircularProgress, Grid } from '@mui/material'; import { AppShell } from '@/components/layouts/AppShell/AppShell'; import { ProtectedRoute } from '@/components/common/ProtectedRoute'; import { EmailVerificationBanner } from '@/components/common/EmailVerificationBanner'; import { ErrorBoundary } from '@/components/common/ErrorBoundary'; import { DataErrorFallback } from '@/components/common/ErrorFallbacks'; import { NetworkStatusIndicator } from '@/components/common/NetworkStatusIndicator'; import { StatGridSkeleton } from '@/components/common/LoadingSkeletons'; import DynamicChildDashboard from '@/components/dashboard/DynamicChildDashboard'; import { Restaurant, Hotel, BabyChangingStation, Insights, SmartToy, Analytics, MedicalServices, } from '@mui/icons-material'; import { motion } from 'framer-motion'; import { useAuth } from '@/lib/auth/AuthContext'; import { useRouter } from 'next/navigation'; import { childrenApi, Child } from '@/lib/api/children'; import { trackingApi } from '@/lib/api/tracking'; import { format, formatDistanceToNow } from 'date-fns'; import { useRealTimeActivities } from '@/hooks/useWebSocket'; import { useTranslation } from '@/hooks/useTranslation'; import { analyticsApi } from '@/lib/api/analytics'; import { useTheme } from '@mui/material/styles'; import { useDispatch, useSelector } from 'react-redux'; import { fetchChildren, selectSelectedChild } from '@/store/slices/childrenSlice'; import { AppDispatch, RootState } from '@/store/store'; export default function HomePage() { const { t } = useTranslation('dashboard'); const theme = useTheme(); const { user, isLoading: authLoading } = useAuth(); const router = useRouter(); const dispatch = useDispatch(); const selectedChild = useSelector(selectSelectedChild); const familyId = user?.families?.[0]?.familyId; const [selectedChildId, setSelectedChildId] = useState(null); const [predictions, setPredictions] = useState(null); const [predictionsLoading, setPredictionsLoading] = useState(false); const [children, setChildren] = useState([]); const [recentActivities, setRecentActivities] = useState([]); const [todaySummary, setTodaySummary] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Load children useEffect(() => { if (familyId && !authLoading) { loadChildren(); } }, [familyId, authLoading]); const loadChildren = async () => { if (!familyId) return; try { const data = await childrenApi.getChildren(familyId); setChildren(data); if (data.length > 0 && !selectedChildId) { setSelectedChildId(data[0].id); } } catch (err) { console.error('Failed to load children:', err); setError(err); } }; // Load dashboard data when child selected useEffect(() => { if (selectedChildId) { loadDashboardData(); } }, [selectedChildId]); const loadDashboardData = async () => { if (!selectedChildId) return; setLoading(true); try { // Load recent activities const activities = await trackingApi.getActivities(selectedChildId); setRecentActivities(activities.slice(0, 10)); // Calculate today's summary from activities const today = new Date(); today.setHours(0, 0, 0, 0); const todayActivities = activities.filter((a: any) => { const activityDate = new Date(a.timestamp || a.startedAt); return activityDate >= today; }); const summary = { feedingCount: todayActivities.filter((a: any) => a.type === 'feeding').length, sleepCount: todayActivities.filter((a: any) => a.type === 'sleep').length, diaperCount: todayActivities.filter((a: any) => a.type === 'diaper').length, medicationCount: todayActivities.filter((a: any) => ['medication', 'medicine'].includes(a.type)).length, totalFeedingAmount: todayActivities .filter((a: any) => a.type === 'feeding') .reduce((sum: number, a: any) => sum + (a.data?.amount || 0), 0), totalSleepDuration: todayActivities .filter((a: any) => a.type === 'sleep') .reduce((sum: number, a: any) => { const start = new Date(a.startedAt); const end = a.endedAt ? new Date(a.endedAt) : new Date(); return sum + (end.getTime() - start.getTime()) / (1000 * 60); }, 0), }; setTodaySummary(summary); setError(null); } catch (err) { console.error('Failed to load dashboard data:', err); setError(err); } finally { setLoading(false); } }; const refetch = loadDashboardData; // Real-time activity handler const refreshDashboard = useCallback(async () => { console.log('[HomePage] Refreshing dashboard data...'); await refetch(); }, [refetch]); // Subscribe to real-time activity updates useRealTimeActivities( refreshDashboard, // On activity created refreshDashboard, // On activity updated refreshDashboard // On activity deleted ); // Sync selectedChildId with Redux selectedChild useEffect(() => { if (selectedChild?.id && selectedChild.id !== selectedChildId) { setSelectedChildId(selectedChild.id); } }, [selectedChild, selectedChildId]); // Fetch predictions when selectedChildId changes useEffect(() => { const fetchPredictions = async () => { if (!selectedChildId) return; setPredictionsLoading(true); try { const predictionData = await analyticsApi.getPredictions(selectedChildId); setPredictions(predictionData); } catch (err) { console.error('Failed to fetch predictions:', err); setPredictions(null); } finally { setPredictionsLoading(false); } }; fetchPredictions(); }, [selectedChildId]); const quickActions = [ { icon: , label: t('quickActions.feeding'), color: theme.palette.primary.main, path: '/track/feeding' }, { icon: , label: t('quickActions.sleep'), color: theme.palette.secondary.main, path: '/track/sleep' }, { icon: , label: t('quickActions.diaper'), color: theme.palette.warning.main, path: '/track/diaper' }, { icon: , label: t('quickActions.medical'), color: theme.palette.error.main, path: '/track/medicine' }, { icon: , label: t('quickActions.activities'), color: theme.palette.success.main, path: '/track/activity' }, { icon: , label: t('quickActions.aiAssistant'), color: theme.palette.info.main, path: '/ai-assistant' }, ]; const formatSleepHours = (minutes: number) => { const hours = Math.floor(minutes / 60); const mins = minutes % 60; if (hours > 0 && mins > 0) { return `${hours}h ${mins}m`; } else if (hours > 0) { return `${hours}h`; } else { return `${mins}m`; } }; const isLoading = authLoading || loading; // Build child metrics object for DynamicChildDashboard const childMetrics = children.reduce((acc: any, child: any) => { // Use todaySummary for selected child, or zero for others if (child.id === selectedChildId && todaySummary) { acc[child.id] = { feedingCount: todaySummary.feedingCount || 0, sleepDuration: todaySummary.totalSleepDuration || 0, diaperCount: todaySummary.diaperCount || 0, medicationCount: todaySummary.medicationCount || 0, }; } else { acc[child.id] = { feedingCount: 0, sleepDuration: 0, diaperCount: 0, medicationCount: 0, }; } return acc; }, {}); const handleChildSelect = (childId: string) => { setSelectedChildId(childId); }; return ( {user?.name ? t('welcomeBackWithName', { name: user.name }) : t('welcomeBack')} 👋 {t('subtitle')} {/* Quick Actions */} {t('quickActions.title')} {quickActions.map((action, index) => ( router.push(action.path)} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); router.push(action.path); } }} aria-label={t('quickActions.navigateTo', { action: action.label })} sx={{ p: 3, height: '140px', // Fixed height for consistency minHeight: '140px', // Ensure minimum height width: '100%', // Full width of grid container display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', cursor: 'pointer', bgcolor: action.color, color: 'white', border: 'none', transition: 'transform 0.2s', '&:hover': { transform: 'scale(1.05)', }, '&:focus-visible': { outline: '3px solid white', outlineOffset: '-3px', transform: 'scale(1.05)', }, }} > {action.label} ))} {/* Today's Summary - Dynamic Multi-Child Dashboard */} {t('summary.title')} } > {isLoading ? ( ) : children.length === 0 ? ( {t('summary.noChild')} ) : ( )} {/* Next Predicted Activity */} {t('predictions.title')} {predictionsLoading ? ( Loading predictions... ) : predictions?.sleep?.nextNapTime || predictions?.feeding?.nextFeedingTime ? ( <> {predictions.sleep?.nextNapTime && ( <> Nap time {formatDistanceToNow(new Date(predictions.sleep.nextNapTime), { addSuffix: true })} {predictions.sleep.reasoning || t('predictions.basedOnPatterns')} )} {!predictions.sleep?.nextNapTime && predictions.feeding?.nextFeedingTime && ( <> Feeding time {formatDistanceToNow(new Date(predictions.feeding.nextFeedingTime), { addSuffix: true })} {predictions.feeding.reasoning || t('predictions.basedOnPatterns')} )} ) : ( {t('predictions.notEnoughData')} )} ); }