- 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.
365 lines
12 KiB
TypeScript
365 lines
12 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
import { useAuth } from '@/lib/auth/AuthContext';
|
|
import { childrenApi, Child } from '@/lib/api/children';
|
|
import {
|
|
analyticsApi,
|
|
CircadianRhythm,
|
|
AnomalyDetection,
|
|
GrowthAnalysis,
|
|
CorrelationAnalysis,
|
|
TrendAnalysis,
|
|
} from '@/lib/api/analytics';
|
|
import { CircadianRhythmCard } from '@/components/analytics/CircadianRhythmCard';
|
|
import { AnomalyAlertsPanel } from '@/components/analytics/AnomalyAlertsPanel';
|
|
import { GrowthPercentileChart } from '@/components/analytics/GrowthPercentileChart';
|
|
import { CorrelationInsights } from '@/components/analytics/CorrelationInsights';
|
|
import { TrendAnalysisChart } from '@/components/analytics/TrendAnalysisChart';
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
Button,
|
|
Alert,
|
|
AlertTitle,
|
|
Tabs,
|
|
Tab,
|
|
Box,
|
|
Select,
|
|
MenuItem,
|
|
FormControl,
|
|
InputLabel,
|
|
} from '@mui/material';
|
|
import { Loader2, RefreshCw, Activity, Brain, TrendingUp, Baby, Link } from 'lucide-react';
|
|
import { AppShell } from '@/components/layouts/AppShell/AppShell';
|
|
import { ProtectedRoute } from '@/components/common/ProtectedRoute';
|
|
|
|
export default function AdvancedAnalyticsPage() {
|
|
const { user } = useAuth();
|
|
const [children, setChildren] = useState<Child[]>([]);
|
|
const [selectedChildId, setSelectedChildId] = useState<string>('');
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string>('');
|
|
|
|
// Analytics data states
|
|
const [circadianData, setCircadianData] = useState<CircadianRhythm | null>(null);
|
|
const [anomalyData, setAnomalyData] = useState<AnomalyDetection | null>(null);
|
|
const [growthData, setGrowthData] = useState<GrowthAnalysis | null>(null);
|
|
const [correlationData, setCorrelationData] = useState<CorrelationAnalysis | null>(null);
|
|
const [sleepTrendData, setSleepTrendData] = useState<TrendAnalysis | null>(null);
|
|
const [feedingTrendData, setFeedingTrendData] = useState<TrendAnalysis | null>(null);
|
|
|
|
// Loading states for each component
|
|
const [circadianLoading, setCircadianLoading] = useState(false);
|
|
const [anomalyLoading, setAnomalyLoading] = useState(false);
|
|
const [growthLoading, setGrowthLoading] = useState(false);
|
|
const [correlationLoading, setCorrelationLoading] = useState(false);
|
|
const [trendLoading, setTrendLoading] = useState(false);
|
|
|
|
// Error states for each component
|
|
const [circadianError, setCircadianError] = useState<Error | null>(null);
|
|
const [anomalyError, setAnomalyError] = useState<Error | null>(null);
|
|
const [growthError, setGrowthError] = useState<Error | null>(null);
|
|
const [correlationError, setCorrelationError] = useState<Error | null>(null);
|
|
const [trendError, setTrendError] = useState<Error | null>(null);
|
|
|
|
const familyId = user?.families?.[0]?.familyId;
|
|
|
|
useEffect(() => {
|
|
if (familyId) {
|
|
loadChildren();
|
|
}
|
|
}, [familyId]);
|
|
|
|
useEffect(() => {
|
|
if (selectedChildId && children.length > 0) {
|
|
const childExists = children.some(child => child.id === selectedChildId);
|
|
if (childExists) {
|
|
loadAllAnalytics();
|
|
} else {
|
|
console.warn('[AdvancedAnalytics] Selected child not found, resetting');
|
|
setSelectedChildId(children[0].id);
|
|
}
|
|
}
|
|
}, [selectedChildId, children]);
|
|
|
|
const loadChildren = async () => {
|
|
if (!familyId) {
|
|
setLoading(false);
|
|
setError('No family found');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log('[AdvancedAnalytics] Loading children for familyId:', familyId);
|
|
const data = await childrenApi.getChildren(familyId);
|
|
console.log('[AdvancedAnalytics] Loaded children:', data);
|
|
setChildren(data);
|
|
|
|
if (data.length > 0) {
|
|
const existingChildStillValid = data.some(child => child.id === selectedChildId);
|
|
if (!selectedChildId || !existingChildStillValid) {
|
|
setSelectedChildId(data[0].id);
|
|
}
|
|
}
|
|
setError('');
|
|
} catch (error) {
|
|
console.error('[AdvancedAnalytics] Failed to load children:', error);
|
|
setError('Failed to load children');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadAllAnalytics = () => {
|
|
loadCircadianRhythm();
|
|
loadAnomalies();
|
|
loadGrowthAnalysis();
|
|
loadCorrelations();
|
|
loadTrends();
|
|
};
|
|
|
|
const loadCircadianRhythm = async () => {
|
|
if (!selectedChildId) return;
|
|
|
|
setCircadianLoading(true);
|
|
setCircadianError(null);
|
|
try {
|
|
const data = await analyticsApi.getCircadianRhythm(selectedChildId, 14);
|
|
setCircadianData(data);
|
|
} catch (error) {
|
|
console.error('[AdvancedAnalytics] Failed to load circadian rhythm:', error);
|
|
setCircadianError(error as Error);
|
|
} finally {
|
|
setCircadianLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadAnomalies = async () => {
|
|
if (!selectedChildId) return;
|
|
|
|
setAnomalyLoading(true);
|
|
setAnomalyError(null);
|
|
try {
|
|
const data = await analyticsApi.getAnomalies(selectedChildId, 30);
|
|
setAnomalyData(data);
|
|
} catch (error) {
|
|
console.error('[AdvancedAnalytics] Failed to load anomalies:', error);
|
|
setAnomalyError(error as Error);
|
|
} finally {
|
|
setAnomalyLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadGrowthAnalysis = async () => {
|
|
if (!selectedChildId) return;
|
|
|
|
setGrowthLoading(true);
|
|
setGrowthError(null);
|
|
try {
|
|
const data = await analyticsApi.getGrowthAnalysis(selectedChildId);
|
|
setGrowthData(data);
|
|
} catch (error) {
|
|
console.error('[AdvancedAnalytics] Failed to load growth analysis:', error);
|
|
setGrowthError(error as Error);
|
|
} finally {
|
|
setGrowthLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadCorrelations = async () => {
|
|
if (!selectedChildId) return;
|
|
|
|
setCorrelationLoading(true);
|
|
setCorrelationError(null);
|
|
try {
|
|
const data = await analyticsApi.getCorrelations(selectedChildId, 14);
|
|
setCorrelationData(data);
|
|
} catch (error) {
|
|
console.error('[AdvancedAnalytics] Failed to load correlations:', error);
|
|
setCorrelationError(error as Error);
|
|
} finally {
|
|
setCorrelationLoading(false);
|
|
}
|
|
};
|
|
|
|
const loadTrends = async () => {
|
|
if (!selectedChildId) return;
|
|
|
|
setTrendLoading(true);
|
|
setTrendError(null);
|
|
try {
|
|
const [sleepTrend, feedingTrend] = await Promise.all([
|
|
analyticsApi.getTrends(selectedChildId, 'sleep'),
|
|
analyticsApi.getTrends(selectedChildId, 'feeding'),
|
|
]);
|
|
setSleepTrendData(sleepTrend);
|
|
setFeedingTrendData(feedingTrend);
|
|
} catch (error) {
|
|
console.error('[AdvancedAnalytics] Failed to load trends:', error);
|
|
setTrendError(error as Error);
|
|
} finally {
|
|
setTrendLoading(false);
|
|
}
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<ProtectedRoute>
|
|
<AppShell>
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
<Loader2 className="h-8 w-8 animate-spin" />
|
|
</div>
|
|
</AppShell>
|
|
</ProtectedRoute>
|
|
);
|
|
}
|
|
|
|
if (children.length === 0) {
|
|
return (
|
|
<ProtectedRoute>
|
|
<AppShell>
|
|
<div className="p-6">
|
|
<Alert>
|
|
<AlertTitle>No Children Found</AlertTitle>
|
|
<AlertDescription>
|
|
Add a child to your family to view advanced analytics.
|
|
</AlertDescription>
|
|
</Alert>
|
|
</div>
|
|
</AppShell>
|
|
</ProtectedRoute>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<ProtectedRoute>
|
|
<AppShell>
|
|
<div className="p-6 space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold">Advanced Analytics</h1>
|
|
<p className="text-gray-600 mt-1">
|
|
AI-powered insights and deep analysis of your child's patterns
|
|
</p>
|
|
</div>
|
|
<Button onClick={loadAllAnalytics} variant="outline">
|
|
<RefreshCw className="h-4 w-4 mr-2" />
|
|
Refresh
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Child Selector */}
|
|
<Card>
|
|
<CardContent className="py-4">
|
|
<div className="flex items-center gap-4">
|
|
<label htmlFor="child-select" className="text-sm font-medium">
|
|
Select Child:
|
|
</label>
|
|
<Select
|
|
value={selectedChildId}
|
|
onValueChange={setSelectedChildId}
|
|
>
|
|
<SelectTrigger className="w-[200px]">
|
|
<SelectValue placeholder="Select a child" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{children.map((child) => (
|
|
<SelectItem key={child.id} value={child.id}>
|
|
{child.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{error && (
|
|
<Alert variant="destructive">
|
|
<AlertTitle>Error</AlertTitle>
|
|
<AlertDescription>{error}</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
{/* Analytics Tabs */}
|
|
<Tabs defaultValue="circadian" className="w-full">
|
|
<TabsList className="grid w-full grid-cols-5">
|
|
<TabsTrigger value="circadian">
|
|
<Brain className="h-4 w-4 mr-2" />
|
|
Sleep Rhythm
|
|
</TabsTrigger>
|
|
<TabsTrigger value="anomalies">
|
|
<Activity className="h-4 w-4 mr-2" />
|
|
Anomalies
|
|
</TabsTrigger>
|
|
<TabsTrigger value="growth">
|
|
<Baby className="h-4 w-4 mr-2" />
|
|
Growth
|
|
</TabsTrigger>
|
|
<TabsTrigger value="correlations">
|
|
<Link className="h-4 w-4 mr-2" />
|
|
Correlations
|
|
</TabsTrigger>
|
|
<TabsTrigger value="trends">
|
|
<TrendingUp className="h-4 w-4 mr-2" />
|
|
Trends
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="circadian" className="space-y-4">
|
|
<CircadianRhythmCard
|
|
data={circadianData}
|
|
loading={circadianLoading}
|
|
error={circadianError}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="anomalies" className="space-y-4">
|
|
<AnomalyAlertsPanel
|
|
data={anomalyData}
|
|
loading={anomalyLoading}
|
|
error={anomalyError}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="growth" className="space-y-4">
|
|
<GrowthPercentileChart
|
|
data={growthData}
|
|
loading={growthLoading}
|
|
error={growthError}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="correlations" className="space-y-4">
|
|
<CorrelationInsights
|
|
data={correlationData}
|
|
loading={correlationLoading}
|
|
error={correlationError}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="trends" className="space-y-4">
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<TrendAnalysisChart
|
|
data={sleepTrendData}
|
|
activityType="Sleep"
|
|
loading={trendLoading}
|
|
error={trendError}
|
|
/>
|
|
<TrendAnalysisChart
|
|
data={feedingTrendData}
|
|
activityType="Feeding"
|
|
loading={trendLoading}
|
|
error={trendError}
|
|
/>
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
</AppShell>
|
|
</ProtectedRoute>
|
|
);
|
|
} |