- Add comprehensive API client methods for all advanced analytics endpoints - Create CircadianRhythmCard component for sleep pattern visualization - Create AnomalyAlertsPanel for anomaly detection and alerts - Create GrowthPercentileChart with WHO/CDC percentiles - Create CorrelationInsights for activity correlations - Create TrendAnalysisChart with predictions - Add advanced analytics page with all new components - Add UI component library (shadcn/ui) setup - Add navigation link to advanced analytics from insights page All advanced analytics features are now accessible from the frontend UI.
262 lines
8.2 KiB
TypeScript
262 lines
8.2 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import {
|
|
Box,
|
|
Typography,
|
|
Tabs,
|
|
Tab,
|
|
Select,
|
|
MenuItem,
|
|
FormControl,
|
|
InputLabel,
|
|
CircularProgress,
|
|
Alert,
|
|
Grid,
|
|
IconButton,
|
|
Button,
|
|
} from '@mui/material';
|
|
import { Timeline, TrendingUp, Assessment, ArrowBack } from '@mui/icons-material';
|
|
import { useRouter } from 'next/navigation';
|
|
import { childrenApi, Child } from '@/lib/api/children';
|
|
import { analyticsApi, PatternInsights, PredictionInsights } from '@/lib/api/analytics';
|
|
import { InsightsDashboard } from './InsightsDashboard';
|
|
import PredictionsCard from './PredictionsCard';
|
|
import GrowthSpurtAlert from './GrowthSpurtAlert';
|
|
import ChildSelector from '@/components/common/ChildSelector';
|
|
import { motion } from 'framer-motion';
|
|
import { useAuth } from '@/lib/auth/AuthContext';
|
|
|
|
interface TabPanelProps {
|
|
children?: React.ReactNode;
|
|
index: number;
|
|
value: number;
|
|
}
|
|
|
|
function TabPanel(props: TabPanelProps) {
|
|
const { children, value, index, ...other } = props;
|
|
|
|
return (
|
|
<div
|
|
role="tabpanel"
|
|
hidden={value !== index}
|
|
id={`insights-tabpanel-${index}`}
|
|
aria-labelledby={`insights-tab-${index}`}
|
|
{...other}
|
|
>
|
|
{value === index && <Box sx={{ pt: 3 }}>{children}</Box>}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function UnifiedInsightsDashboard() {
|
|
const router = useRouter();
|
|
const { user } = useAuth();
|
|
const [children, setChildren] = useState<Child[]>([]);
|
|
const [selectedChildIds, setSelectedChildIds] = useState<string[]>([]);
|
|
const [tabValue, setTabValue] = useState(0);
|
|
const [loading, setLoading] = useState(true);
|
|
const [insights, setInsights] = useState<PatternInsights | null>(null);
|
|
const [predictions, setPredictions] = useState<PredictionInsights | null>(null);
|
|
const [insightsLoading, setInsightsLoading] = useState(false);
|
|
const [predictionsLoading, setPredictionsLoading] = useState(false);
|
|
const [days, setDays] = useState<number>(7);
|
|
const [error, setError] = useState<string>('');
|
|
|
|
// Get the selected child ID (first one from the array for single selection)
|
|
const selectedChildId = selectedChildIds[0] || '';
|
|
|
|
const familyId = user?.families?.[0]?.familyId;
|
|
|
|
useEffect(() => {
|
|
if (familyId) {
|
|
loadChildren();
|
|
}
|
|
}, [familyId]);
|
|
|
|
useEffect(() => {
|
|
if (selectedChildId && children.length > 0) {
|
|
// Validate that selectedChildId belongs to current user's children
|
|
const childExists = children.some(child => child.id === selectedChildId);
|
|
if (childExists) {
|
|
loadInsights();
|
|
loadPredictions();
|
|
} else {
|
|
// Invalid child ID - reset to first child
|
|
console.warn('[UnifiedInsightsDashboard] Selected child not found in user\'s children, resetting');
|
|
setSelectedChildIds([children[0].id]);
|
|
setError('Selected child not found. Showing data for your first child.');
|
|
}
|
|
}
|
|
}, [selectedChildId, days, children]);
|
|
|
|
const loadChildren = async () => {
|
|
if (!familyId) {
|
|
setLoading(false);
|
|
setError('No family found');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log('[UnifiedInsightsDashboard] Loading children for familyId:', familyId);
|
|
const data = await childrenApi.getChildren(familyId);
|
|
console.log('[UnifiedInsightsDashboard] Loaded children:', data);
|
|
setChildren(data);
|
|
|
|
// Only set selectedChildIds if we don't have one or if it's not in the new list
|
|
if (data.length > 0) {
|
|
const existingChildStillValid = data.some(child => child.id === selectedChildId);
|
|
if (!selectedChildId || !existingChildStillValid) {
|
|
setSelectedChildIds([data[0].id]);
|
|
}
|
|
}
|
|
setError('');
|
|
} catch (error) {
|
|
console.error('[UnifiedInsightsDashboard] Failed to load children:', error);
|
|
setError('Failed to load children');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadInsights = async () => {
|
|
if (!selectedChildId) return;
|
|
|
|
setInsightsLoading(true);
|
|
try {
|
|
const data = await analyticsApi.getInsights(selectedChildId, days);
|
|
setInsights(data);
|
|
} catch (error) {
|
|
console.error('Failed to load insights:', error);
|
|
} finally {
|
|
setInsightsLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadPredictions = async () => {
|
|
if (!selectedChildId) return;
|
|
|
|
setPredictionsLoading(true);
|
|
try {
|
|
const data = await analyticsApi.getPredictions(selectedChildId);
|
|
setPredictions(data);
|
|
} catch (error) {
|
|
console.error('Failed to load predictions:', error);
|
|
} finally {
|
|
setPredictionsLoading(false);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '60vh' }}>
|
|
<CircularProgress />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (children.length === 0) {
|
|
return (
|
|
<Box sx={{ p: 3 }}>
|
|
<Alert severity="info">Add a child to your family to view insights and predictions.</Alert>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5 }}
|
|
>
|
|
<Box sx={{ px: { xs: 2, sm: 3 }, py: 3 }}>
|
|
{/* Header with Back Button */}
|
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
|
<IconButton onClick={() => router.back()} sx={{ mr: 2 }}>
|
|
<ArrowBack />
|
|
</IconButton>
|
|
<Box>
|
|
<Typography variant="h4" fontWeight={600}>
|
|
Insights & Predictions
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
AI-powered insights, patterns, and predictions for your child
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
<Button
|
|
variant="outlined"
|
|
color="primary"
|
|
onClick={() => router.push('/analytics/advanced')}
|
|
startIcon={<TrendingUp />}
|
|
>
|
|
Advanced Analytics
|
|
</Button>
|
|
</Box>
|
|
|
|
{/* Error Alert */}
|
|
{error && (
|
|
<Alert severity="warning" sx={{ mb: 3 }} onClose={() => setError('')}>
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
|
|
{/* Shared Filters */}
|
|
<Box sx={{ mb: 3, display: 'flex', gap: 2, flexWrap: 'wrap', alignItems: 'flex-start' }}>
|
|
{children.length > 1 && (
|
|
<Box sx={{ minWidth: 250 }}>
|
|
<ChildSelector
|
|
children={children}
|
|
selectedChildIds={selectedChildIds}
|
|
onChange={(childIds) => setSelectedChildIds(childIds)}
|
|
mode="single"
|
|
label="Child"
|
|
compact={false}
|
|
/>
|
|
</Box>
|
|
)}
|
|
<FormControl sx={{ minWidth: 150 }}>
|
|
<InputLabel>Time Period</InputLabel>
|
|
<Select
|
|
value={days}
|
|
label="Time Period"
|
|
onChange={(e) => setDays(Number(e.target.value))}
|
|
>
|
|
<MenuItem value={7}>Last 7 days</MenuItem>
|
|
<MenuItem value={30}>Last 30 days</MenuItem>
|
|
<MenuItem value={90}>Last 3 months</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
</Box>
|
|
|
|
{/* Growth Spurt Alert */}
|
|
{insights?.growthSpurt && <GrowthSpurtAlert growthSpurt={insights.growthSpurt} />}
|
|
|
|
{/* Tabs */}
|
|
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 2 }}>
|
|
<Tabs value={tabValue} onChange={(e, newValue) => setTabValue(newValue)}>
|
|
<Tab label="Insights" icon={<Timeline />} iconPosition="start" />
|
|
<Tab label="Predictions" icon={<TrendingUp />} iconPosition="start" />
|
|
</Tabs>
|
|
</Box>
|
|
|
|
{/* Tab Panels */}
|
|
<TabPanel value={tabValue} index={0}>
|
|
{/* Insights tab shows the existing InsightsDashboard */}
|
|
<InsightsDashboard selectedChildId={selectedChildId} days={days} />
|
|
</TabPanel>
|
|
|
|
<TabPanel value={tabValue} index={1}>
|
|
{/* Predictions tab */}
|
|
<Grid container spacing={3}>
|
|
<Grid item xs={12}>
|
|
<PredictionsCard predictions={predictions} loading={predictionsLoading} />
|
|
</Grid>
|
|
</Grid>
|
|
</TabPanel>
|
|
</Box>
|
|
</motion.div>
|
|
);
|
|
}
|