'use client'; import { useState, useEffect } from 'react'; import { Box, Typography, Grid, Card, CardContent, CircularProgress, Alert, Paper, Divider, List, ListItem, ListItemAvatar, ListItemText, Avatar, Chip, Button, IconButton, Menu, MenuItem as MUIMenuItem, Tooltip as MUITooltip, } from '@mui/material'; import { Restaurant, Hotel, BabyChangingStation, TrendingUp, Timeline, Assessment, ChildCare, Add, MoreVert, Download, Share, Refresh, ShowChart, BubbleChart, DonutLarge, WaterDrop, Favorite, LocalHospital, NightsStay, WbSunny, } from '@mui/icons-material'; import { useRouter } from 'next/navigation'; import { motion, AnimatePresence } from 'framer-motion'; import { trackingApi, Activity, ActivityType } from '@/lib/api/tracking'; import { subDays, startOfDay, endOfDay, parseISO, differenceInMinutes, format, startOfWeek, getHours } from 'date-fns'; import { useLocalizedDate } from '@/hooks/useLocalizedDate'; import { useTranslation } from '@/hooks/useTranslation'; import { useFormatting } from '@/hooks/useFormatting'; import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, AreaChart, Area, ComposedChart, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, Scatter, ScatterChart, ZAxis, Treemap, Sankey, RadialBarChart, RadialBar, ReferenceLine, ReferenceArea, Brush, LabelList, } from 'recharts'; import { useAuth } from '@/lib/auth/AuthContext'; import html2canvas from 'html2canvas'; import jsPDF from 'jspdf'; interface DayData { date: string; feedings: number; sleepHours: number; diapers: number; activities: number; mood?: number; energy?: number; } interface HourlyData { hour: number; feeding: number; sleep: number; diaper: number; activity: number; } interface WeekdayPattern { day: string; avgFeedings: number; avgSleep: number; avgDiapers: number; } interface TimeOfDayData { period: string; activities: number; type: string; } interface CorrelationData { x: number; y: number; z: number; name: string; } const COLORS = { feeding: '#E91E63', sleep: '#1976D2', diaper: '#F57C00', medication: '#C62828', milestone: '#558B2F', note: '#FFD3B6', wet: '#87CEEB', dirty: '#D2691E', both: '#FF8C00', dry: '#90EE90', morning: '#FFD700', afternoon: '#FFA500', evening: '#FF6347', night: '#4B0082', }; const GRADIENT_COLORS = [ { offset: '0%', color: '#FF6B6B', opacity: 0.8 }, { offset: '100%', color: '#4ECDC4', opacity: 0.3 }, ]; interface EnhancedInsightsDashboardProps { selectedChildId: string; days: number; } export const EnhancedInsightsDashboard: React.FC = ({ selectedChildId, days }) => { const router = useRouter(); const { user } = useAuth(); const { formatDate, formatTime } = useLocalizedDate(); const { t } = useTranslation('insights'); const { formatNumber } = useFormatting(); const [activities, setActivities] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [anchorEl, setAnchorEl] = useState(null); const [selectedChart, setSelectedChart] = useState(null); // Processed data states const [stats, setStats] = useState({ totalFeedings: 0, avgSleepHours: 0, totalDiapers: 0, mostCommonType: 'none' as ActivityType | 'none', }); const [dailyData, setDailyData] = useState([]); const [hourlyData, setHourlyData] = useState([]); const [weekdayPatterns, setWeekdayPatterns] = useState([]); const [timeOfDayData, setTimeOfDayData] = useState([]); const [correlationData, setCorrelationData] = useState([]); useEffect(() => { if (selectedChildId) { loadActivities(); } }, [selectedChildId, days]); const loadActivities = async () => { if (!selectedChildId) { setError('No child selected'); setLoading(false); return; } try { setLoading(true); setError(null); const endDate = endOfDay(new Date()); const startDate = startOfDay(subDays(endDate, days - 1)); const fetchedActivities = await trackingApi.getActivities( selectedChildId, undefined, // type startDate.toISOString(), endDate.toISOString() ); setActivities(fetchedActivities); processActivities(fetchedActivities); } catch (err) { console.error('Failed to load activities:', err); setError('Failed to load activities'); } finally { setLoading(false); } }; const processActivities = (activities: Activity[]) => { try { // Process daily data with enhanced metrics const dailyMap = new Map(); const hourlyMap = new Map(); const weekdayMap = new Map(); // Initialize hourly data for (let i = 0; i < 24; i++) { hourlyMap.set(i, { hour: i, feeding: 0, sleep: 0, diaper: 0, activity: 0 }); } // Initialize weekday data ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].forEach(day => { weekdayMap.set(day, { feedings: [], sleep: [], diapers: [] }); }); activities.forEach(activity => { // Skip activities without valid timestamps if (!activity.startTime && !activity.timestamp) { console.warn('Activity missing timestamp:', activity); return; } const timestamp = activity.startTime || activity.timestamp; let dateObj: Date; try { dateObj = parseISO(timestamp); // Validate the date if (isNaN(dateObj.getTime())) { console.warn('Invalid date for activity:', activity); return; } } catch (err) { console.warn('Failed to parse date for activity:', activity, err); return; } const date = format(dateObj, 'MMM dd'); const hour = getHours(dateObj); const weekday = format(dateObj, 'EEE'); // Daily aggregation if (!dailyMap.has(date)) { dailyMap.set(date, { date, feedings: 0, sleepHours: 0, diapers: 0, activities: 0, mood: Math.random() * 5, // Simulated mood score energy: Math.random() * 100, // Simulated energy level }); } const dayData = dailyMap.get(date)!; dayData.activities++; // Hourly aggregation const hourData = hourlyMap.get(hour)!; hourData.activity++; // Process by type switch (activity.type) { case 'feeding': dayData.feedings++; hourData.feeding++; weekdayMap.get(weekday)!.feedings.push(1); break; case 'sleep': if (activity.endTime) { try { const endTimeObj = parseISO(activity.endTime); const duration = differenceInMinutes(endTimeObj, dateObj) / 60; if (duration > 0 && duration < 24) { // Sanity check dayData.sleepHours += duration; hourData.sleep++; weekdayMap.get(weekday)!.sleep.push(duration); } } catch (err) { console.warn('Failed to calculate sleep duration:', activity, err); } } else { // Count sleep activity even without duration hourData.sleep++; } break; case 'diaper': dayData.diapers++; hourData.diaper++; weekdayMap.get(weekday)!.diapers.push(1); break; } }); // Convert maps to arrays const dailyArray = Array.from(dailyMap.values()).sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime() ); const hourlyArray = Array.from(hourlyMap.values()); // Calculate weekday patterns const weekdayArray: WeekdayPattern[] = Array.from(weekdayMap.entries()).map(([day, data]) => ({ day, avgFeedings: data.feedings.length > 0 ? data.feedings.reduce((a, b) => a + b, 0) / data.feedings.length : 0, avgSleep: data.sleep.length > 0 ? data.sleep.reduce((a, b) => a + b, 0) / data.sleep.length : 0, avgDiapers: data.diapers.length > 0 ? data.diapers.reduce((a, b) => a + b, 0) / data.diapers.length : 0, })); // Time of day analysis const timeOfDayMap = new Map(); activities.forEach(activity => { // Skip activities without valid timestamps if (!activity.startTime && !activity.timestamp) { return; } const timestamp = activity.startTime || activity.timestamp; try { const dateObj = parseISO(timestamp); if (isNaN(dateObj.getTime())) { return; } const hour = getHours(dateObj); let period = 'night'; if (hour >= 6 && hour < 12) period = 'morning'; else if (hour >= 12 && hour < 18) period = 'afternoon'; else if (hour >= 18 && hour < 22) period = 'evening'; const key = `${period}-${activity.type}`; timeOfDayMap.set(key, (timeOfDayMap.get(key) || 0) + 1); } catch (err) { console.warn('Failed to process activity for time of day analysis:', activity, err); } }); const timeOfDayArray: TimeOfDayData[] = Array.from(timeOfDayMap.entries()).map(([key, count]) => { const [period, type] = key.split('-'); return { period, activities: count, type }; }); // Generate correlation data (simulated for demonstration) const correlationArray: CorrelationData[] = dailyArray.map(day => ({ x: day.feedings, y: day.sleepHours, z: day.diapers * 10, // Scale for visibility name: day.date, })); // Calculate stats const totalFeedings = dailyArray.reduce((sum, day) => sum + day.feedings, 0); const avgSleepHours = dailyArray.reduce((sum, day) => sum + day.sleepHours, 0) / dailyArray.length; const totalDiapers = dailyArray.reduce((sum, day) => sum + day.diapers, 0); // Find most common activity type const typeCount = new Map(); activities.forEach(activity => { typeCount.set(activity.type, (typeCount.get(activity.type) || 0) + 1); }); const mostCommonType = Array.from(typeCount.entries()).sort((a, b) => b[1] - a[1])[0]?.[0] || 'none'; setStats({ totalFeedings, avgSleepHours, totalDiapers, mostCommonType: mostCommonType as ActivityType | 'none', }); setDailyData(dailyArray); setHourlyData(hourlyArray); setWeekdayPatterns(weekdayArray); setTimeOfDayData(timeOfDayArray); setCorrelationData(correlationArray); } catch (error) { console.error('Error processing activities:', error); setError('Failed to process activity data'); } }; const handleMenuClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; const handleMenuClose = () => { setAnchorEl(null); }; const exportChart = async (chartId: string, format: 'png' | 'pdf') => { const element = document.getElementById(chartId); if (!element) return; try { const canvas = await html2canvas(element); if (format === 'png') { const link = document.createElement('a'); link.download = `${chartId}-${Date.now()}.png`; link.href = canvas.toDataURL(); link.click(); } else if (format === 'pdf') { const pdf = new jsPDF(); const imgData = canvas.toDataURL('image/png'); pdf.addImage(imgData, 'PNG', 10, 10, 190, 0); pdf.save(`${chartId}-${Date.now()}.pdf`); } } catch (error) { console.error('Export failed:', error); } }; const CustomTooltip = ({ active, payload, label }: any) => { if (active && payload && payload.length) { return ( {label} {payload.map((entry: any, index: number) => ( {entry.name}: {formatNumber(entry.value)} ))} ); } return null; }; const chartVariants = { hidden: { opacity: 0, scale: 0.9 }, visible: { opacity: 1, scale: 1, transition: { duration: 0.5, ease: "easeOut" } }, }; if (loading) { return ( ); } if (error) { return ( {error} ); } return ( {/* Enhanced Stats Cards with Animations */} {[ { icon: , value: stats.totalFeedings, label: t('stats.feedings.subtitle'), color: COLORS.feeding, trend: '+12%', }, { icon: , value: `${formatNumber(stats.avgSleepHours, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}h`, label: t('stats.sleep.subtitle'), color: COLORS.sleep, trend: '+5%', }, { icon: , value: stats.totalDiapers, label: t('stats.diapers.subtitle'), color: COLORS.diaper, trend: '-8%', }, { icon: , value: t(`activityTypes.${stats.mostCommonType}`), label: t('stats.topActivity.subtitle'), color: COLORS.milestone, trend: 'Stable', }, ].map((stat, index) => ( {stat.icon} {stat.value} {stat.label} ))} {/* Enhanced Charts Grid */} {/* Area Chart with Gradient */} Activity Trends } /> {/* Radar Chart for Pattern Analysis */} Weekly Pattern Analysis {/* Heatmap for Hourly Activity */} 24-Hour Activity Pattern `${hour}:00`} /> } /> {/* Bubble Chart for Correlations */} Activity Correlations } /> {correlationData.map((entry, index) => ( ))} {/* Radial Bar Chart for Time of Day */} Time of Day Distribution } /> {timeOfDayData.map((entry, index) => ( ))} {/* Export Menu */} { exportChart('area-chart', 'png'); handleMenuClose(); }}> Export as PNG { exportChart('area-chart', 'pdf'); handleMenuClose(); }}> Export as PDF Share ); };