- 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.
242 lines
9.0 KiB
TypeScript
242 lines
9.0 KiB
TypeScript
'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>
|
||
);
|
||
} |