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:
242
maternal-web/components/analytics/AnomalyAlertsPanel.tsx
Normal file
242
maternal-web/components/analytics/AnomalyAlertsPanel.tsx
Normal file
@@ -0,0 +1,242 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import {
|
||||
AlertTriangle,
|
||||
AlertCircle,
|
||||
Info,
|
||||
TrendingUp,
|
||||
Activity,
|
||||
Clock,
|
||||
ChevronRight,
|
||||
} from 'lucide-react';
|
||||
import { AnomalyDetection } from '@/lib/api/analytics';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
|
||||
interface AnomalyAlertsPanelProps {
|
||||
data: AnomalyDetection | null;
|
||||
loading?: boolean;
|
||||
error?: Error | null;
|
||||
}
|
||||
|
||||
export function AnomalyAlertsPanel({ data, loading, error }: AnomalyAlertsPanelProps) {
|
||||
const [expandedAnomaly, setExpandedAnomaly] = useState<string | null>(null);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent className="flex items-center justify-center h-64">
|
||||
<div className="animate-pulse">Analyzing patterns for anomalies...</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Card className="border-red-200">
|
||||
<CardContent className="flex items-center justify-center h-64 text-red-500">
|
||||
<AlertCircle className="h-5 w-5 mr-2" />
|
||||
Error loading anomaly detection data
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getSeverityColor = (severity: string) => {
|
||||
switch (severity) {
|
||||
case 'high':
|
||||
case 'critical':
|
||||
return 'bg-red-100 text-red-800 border-red-300';
|
||||
case 'medium':
|
||||
case 'warning':
|
||||
return 'bg-yellow-100 text-yellow-800 border-yellow-300';
|
||||
case 'low':
|
||||
case 'info':
|
||||
return 'bg-blue-100 text-blue-800 border-blue-300';
|
||||
default:
|
||||
return 'bg-gray-100 text-gray-800 border-gray-300';
|
||||
}
|
||||
};
|
||||
|
||||
const getSeverityIcon = (severity: string) => {
|
||||
switch (severity) {
|
||||
case 'high':
|
||||
case 'critical':
|
||||
return <AlertTriangle className="h-4 w-4" />;
|
||||
case 'medium':
|
||||
case 'warning':
|
||||
return <AlertCircle className="h-4 w-4" />;
|
||||
default:
|
||||
return <Info className="h-4 w-4" />;
|
||||
}
|
||||
};
|
||||
|
||||
const criticalAlerts = data.alerts.filter(a => a.severity === 'critical');
|
||||
const warningAlerts = data.alerts.filter(a => a.severity === 'warning');
|
||||
const infoAlerts = data.alerts.filter(a => a.severity === 'info');
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between">
|
||||
<span className="flex items-center gap-2">
|
||||
<Activity className="h-5 w-5" />
|
||||
Anomaly Detection
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-500">Confidence</span>
|
||||
<Badge variant="outline">
|
||||
{Math.round(data.confidenceScore * 100)}%
|
||||
</Badge>
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Confidence Score Bar */}
|
||||
<div className="space-y-2">
|
||||
<Progress value={data.confidenceScore * 100} className="h-2" />
|
||||
<p className="text-xs text-gray-500">
|
||||
Analysis based on {data.anomalies.length} detected patterns
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Alert Summary */}
|
||||
{(criticalAlerts.length > 0 || warningAlerts.length > 0 || infoAlerts.length > 0) && (
|
||||
<div className="grid grid-cols-3 gap-2 mb-4">
|
||||
{criticalAlerts.length > 0 && (
|
||||
<div className="flex items-center gap-2 p-2 bg-red-50 rounded-lg">
|
||||
<AlertTriangle className="h-4 w-4 text-red-600" />
|
||||
<span className="text-sm font-medium text-red-700">
|
||||
{criticalAlerts.length} Critical
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{warningAlerts.length > 0 && (
|
||||
<div className="flex items-center gap-2 p-2 bg-yellow-50 rounded-lg">
|
||||
<AlertCircle className="h-4 w-4 text-yellow-600" />
|
||||
<span className="text-sm font-medium text-yellow-700">
|
||||
{warningAlerts.length} Warning
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{infoAlerts.length > 0 && (
|
||||
<div className="flex items-center gap-2 p-2 bg-blue-50 rounded-lg">
|
||||
<Info className="h-4 w-4 text-blue-600" />
|
||||
<span className="text-sm font-medium text-blue-700">
|
||||
{infoAlerts.length} Info
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Tabs defaultValue="alerts" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="alerts">Alerts ({data.alerts.length})</TabsTrigger>
|
||||
<TabsTrigger value="anomalies">Anomalies ({data.anomalies.length})</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="alerts" className="space-y-3">
|
||||
{data.alerts.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
No alerts detected - everything looks normal!
|
||||
</div>
|
||||
) : (
|
||||
data.alerts.map((alert, index) => (
|
||||
<Alert
|
||||
key={index}
|
||||
className={`${getSeverityColor(alert.severity)} border`}
|
||||
>
|
||||
<div className="flex items-start gap-3">
|
||||
{getSeverityIcon(alert.severity)}
|
||||
<div className="flex-1">
|
||||
<AlertTitle className="mb-2">{alert.type}</AlertTitle>
|
||||
<AlertDescription>{alert.message}</AlertDescription>
|
||||
{alert.recommendations && alert.recommendations.length > 0 && (
|
||||
<div className="mt-3 space-y-1">
|
||||
<p className="text-xs font-medium">Recommendations:</p>
|
||||
<ul className="text-xs space-y-1">
|
||||
{alert.recommendations.map((rec, idx) => (
|
||||
<li key={idx} className="flex items-start gap-1">
|
||||
<ChevronRight className="h-3 w-3 mt-0.5 flex-shrink-0" />
|
||||
<span>{rec}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Alert>
|
||||
))
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="anomalies" className="space-y-3">
|
||||
{data.anomalies.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
No anomalies detected in recent activities
|
||||
</div>
|
||||
) : (
|
||||
data.anomalies.map((anomaly) => (
|
||||
<div
|
||||
key={anomaly.activityId}
|
||||
className="border rounded-lg p-3 space-y-2 hover:bg-gray-50 cursor-pointer transition-colors"
|
||||
onClick={() => setExpandedAnomaly(
|
||||
expandedAnomaly === anomaly.activityId ? null : anomaly.activityId
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start gap-2">
|
||||
<Badge className={getSeverityColor(anomaly.severity)}>
|
||||
{anomaly.severity}
|
||||
</Badge>
|
||||
<div>
|
||||
<p className="text-sm font-medium capitalize">
|
||||
{anomaly.type} Activity
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
<Clock className="h-3 w-3 inline mr-1" />
|
||||
{formatDistanceToNow(anomaly.timestamp, { addSuffix: true })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-xs text-gray-500">Deviation</p>
|
||||
<p className="text-sm font-medium">
|
||||
{anomaly.deviation.toFixed(1)}σ
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{expandedAnomaly === anomaly.activityId && (
|
||||
<div className="pt-2 border-t space-y-2">
|
||||
<p className="text-sm text-gray-600">{anomaly.description}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<TrendingUp className="h-4 w-4 text-gray-400" />
|
||||
<span className="text-xs text-gray-500">
|
||||
Statistical deviation: {anomaly.deviation.toFixed(2)} standard deviations from normal
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user