feat: Add advanced analytics UI components in frontend
- 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.
This commit is contained in:
365
maternal-web/app/analytics/advanced/page.tsx
Normal file
365
maternal-web/app/analytics/advanced/page.tsx
Normal file
@@ -0,0 +1,365 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user