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:
235
maternal-web/components/analytics/CorrelationInsights.tsx
Normal file
235
maternal-web/components/analytics/CorrelationInsights.tsx
Normal file
@@ -0,0 +1,235 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import {
|
||||
Link,
|
||||
Link2Off,
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
Moon,
|
||||
Utensils,
|
||||
Baby,
|
||||
Activity,
|
||||
Info,
|
||||
CheckCircle,
|
||||
} from 'lucide-react';
|
||||
import { CorrelationAnalysis } from '@/lib/api/analytics';
|
||||
|
||||
interface CorrelationInsightsProps {
|
||||
data: CorrelationAnalysis | null;
|
||||
loading?: boolean;
|
||||
error?: Error | null;
|
||||
}
|
||||
|
||||
export function CorrelationInsights({ data, loading, error }: CorrelationInsightsProps) {
|
||||
if (loading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="flex items-center justify-center h-64">
|
||||
<div className="animate-pulse">Analyzing activity correlations...</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Card className="border-red-200">
|
||||
<CardContent className="flex items-center justify-center h-64 text-red-500">
|
||||
<Link2Off className="h-5 w-5 mr-2" />
|
||||
Error loading correlation data
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getCorrelationStrength = (value: number) => {
|
||||
const absValue = Math.abs(value);
|
||||
if (absValue > 0.7) return 'Strong';
|
||||
if (absValue > 0.4) return 'Moderate';
|
||||
if (absValue > 0.2) return 'Weak';
|
||||
return 'Negligible';
|
||||
};
|
||||
|
||||
const getCorrelationColor = (value: number) => {
|
||||
const absValue = Math.abs(value);
|
||||
if (absValue > 0.7) return 'bg-purple-100 text-purple-800';
|
||||
if (absValue > 0.4) return 'bg-blue-100 text-blue-800';
|
||||
if (absValue > 0.2) return 'bg-gray-100 text-gray-800';
|
||||
return 'bg-gray-50 text-gray-600';
|
||||
};
|
||||
|
||||
const getCorrelationIcon = (value: number) => {
|
||||
if (value > 0.3) return <TrendingUp className="h-4 w-4 text-green-500" />;
|
||||
if (value < -0.3) return <TrendingDown className="h-4 w-4 text-red-500" />;
|
||||
return <Activity className="h-4 w-4 text-gray-400" />;
|
||||
};
|
||||
|
||||
const formatCorrelation = (value: number) => {
|
||||
return (value * 100).toFixed(0) + '%';
|
||||
};
|
||||
|
||||
const correlations = [
|
||||
{
|
||||
name: 'Feeding & Sleep',
|
||||
value: data.feedingSleepCorrelation,
|
||||
icon1: <Utensils className="h-4 w-4" />,
|
||||
icon2: <Moon className="h-4 w-4" />,
|
||||
description: data.feedingSleepCorrelation > 0
|
||||
? 'Better feeding patterns correlate with better sleep'
|
||||
: data.feedingSleepCorrelation < 0
|
||||
? 'More feedings may be disrupting sleep patterns'
|
||||
: 'No significant relationship detected',
|
||||
},
|
||||
{
|
||||
name: 'Activity & Diapers',
|
||||
value: data.activityDiaperCorrelation,
|
||||
icon1: <Activity className="h-4 w-4" />,
|
||||
icon2: <Baby className="h-4 w-4" />,
|
||||
description: data.activityDiaperCorrelation > 0
|
||||
? 'More activity correlates with more diaper changes'
|
||||
: data.activityDiaperCorrelation < 0
|
||||
? 'Less active periods show more diaper changes'
|
||||
: 'No clear pattern between activity and diapers',
|
||||
},
|
||||
...(data.sleepMoodCorrelation !== undefined ? [{
|
||||
name: 'Sleep & Mood',
|
||||
value: data.sleepMoodCorrelation,
|
||||
icon1: <Moon className="h-4 w-4" />,
|
||||
icon2: <Activity className="h-4 w-4" />,
|
||||
description: data.sleepMoodCorrelation > 0
|
||||
? 'Better sleep strongly correlates with better mood'
|
||||
: data.sleepMoodCorrelation < 0
|
||||
? 'Sleep patterns inversely affect mood'
|
||||
: 'Sleep and mood appear independent',
|
||||
}] : []),
|
||||
];
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Link className="h-5 w-5" />
|
||||
Activity Correlations
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-6">
|
||||
{/* Correlation Visualizations */}
|
||||
<div className="space-y-4">
|
||||
{correlations.map((correlation) => (
|
||||
<div key={correlation.name} className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1 text-gray-600">
|
||||
{correlation.icon1}
|
||||
<span className="text-sm font-medium">&</span>
|
||||
{correlation.icon2}
|
||||
</div>
|
||||
<span className="text-sm font-medium">{correlation.name}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{getCorrelationIcon(correlation.value)}
|
||||
<Badge className={getCorrelationColor(correlation.value)}>
|
||||
{getCorrelationStrength(correlation.value)}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Correlation Bar */}
|
||||
<div className="relative h-8 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="absolute top-0 h-full bg-gradient-to-r from-red-400 via-gray-300 to-green-400"
|
||||
style={{
|
||||
width: '100%',
|
||||
opacity: 0.3,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className="absolute top-0 h-full flex items-center justify-center"
|
||||
style={{
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
>
|
||||
<div className="w-0.5 h-full bg-gray-400" />
|
||||
</div>
|
||||
<div
|
||||
className="absolute top-0 h-full flex items-center"
|
||||
style={{
|
||||
left: correlation.value > 0 ? '50%' : `${50 + correlation.value * 50}%`,
|
||||
width: `${Math.abs(correlation.value) * 50}%`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`h-4 rounded-full ${
|
||||
correlation.value > 0 ? 'bg-green-500' : 'bg-red-500'
|
||||
}`}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="absolute top-0 h-full flex items-center justify-end pr-2"
|
||||
style={{
|
||||
left: correlation.value > 0 ? `${50 + correlation.value * 50}%` : '50%',
|
||||
transform: correlation.value > 0 ? 'translateX(-100%)' : 'none',
|
||||
}}
|
||||
>
|
||||
<span className="text-xs font-medium text-white drop-shadow">
|
||||
{formatCorrelation(correlation.value)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-gray-600">{correlation.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Correlation Scale Legend */}
|
||||
<div className="p-3 bg-gray-50 rounded-lg space-y-2">
|
||||
<h4 className="text-xs font-medium text-gray-700 uppercase tracking-wider">
|
||||
Correlation Scale
|
||||
</h4>
|
||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-green-500" />
|
||||
<span>Positive: Activities increase together</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500" />
|
||||
<span>Negative: One increases, other decreases</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Insights */}
|
||||
{data.insights.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-sm font-medium text-gray-700 flex items-center gap-2">
|
||||
<Info className="h-4 w-4" />
|
||||
Key Insights
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{data.insights.map((insight, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-start gap-2 p-3 bg-blue-50 rounded-lg"
|
||||
>
|
||||
<CheckCircle className="h-4 w-4 text-blue-500 mt-0.5 flex-shrink-0" />
|
||||
<p className="text-sm text-blue-900">{insight}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user