- 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.
197 lines
7.6 KiB
TypeScript
197 lines
7.6 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Progress } from '@/components/ui/progress';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Moon, Sun, Clock, Brain, AlertCircle, CheckCircle2 } from 'lucide-react';
|
|
import { CircadianRhythm } from '@/lib/api/analytics';
|
|
|
|
interface CircadianRhythmCardProps {
|
|
data: CircadianRhythm | null;
|
|
loading?: boolean;
|
|
error?: Error | null;
|
|
}
|
|
|
|
export function CircadianRhythmCard({ data, loading, error }: CircadianRhythmCardProps) {
|
|
if (loading) {
|
|
return (
|
|
<Card>
|
|
<CardContent className="flex items-center justify-center h-64">
|
|
<div className="animate-pulse">Loading circadian rhythm analysis...</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 circadian rhythm data
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
if (!data) {
|
|
return null;
|
|
}
|
|
|
|
const getChronotypeIcon = () => {
|
|
switch (data.chronotype) {
|
|
case 'early_bird':
|
|
return <Sun className="h-5 w-5 text-yellow-500" />;
|
|
case 'night_owl':
|
|
return <Moon className="h-5 w-5 text-indigo-500" />;
|
|
default:
|
|
return <Clock className="h-5 w-5 text-gray-500" />;
|
|
}
|
|
};
|
|
|
|
const getChronotypeColor = () => {
|
|
switch (data.chronotype) {
|
|
case 'early_bird':
|
|
return 'bg-yellow-100 text-yellow-800';
|
|
case 'night_owl':
|
|
return 'bg-indigo-100 text-indigo-800';
|
|
default:
|
|
return 'bg-gray-100 text-gray-800';
|
|
}
|
|
};
|
|
|
|
const formatTime = (time: string) => {
|
|
// Convert HH:MM to 12-hour format
|
|
const [hours, minutes] = time.split(':');
|
|
const hour = parseInt(hours);
|
|
const ampm = hour >= 12 ? 'PM' : 'AM';
|
|
const displayHour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
|
|
return `${displayHour}:${minutes} ${ampm}`;
|
|
};
|
|
|
|
return (
|
|
<Card className="w-full">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center justify-between">
|
|
<span className="flex items-center gap-2">
|
|
<Brain className="h-5 w-5" />
|
|
Circadian Rhythm Analysis
|
|
</span>
|
|
<Badge className={getChronotypeColor()}>
|
|
<span className="flex items-center gap-1">
|
|
{getChronotypeIcon()}
|
|
{data.chronotype.replace('_', ' ')}
|
|
</span>
|
|
</Badge>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{/* Sleep Consistency Score */}
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between text-sm">
|
|
<span className="text-gray-600">Sleep Consistency</span>
|
|
<span className="font-medium">{Math.round(data.consistency * 100)}%</span>
|
|
</div>
|
|
<Progress value={data.consistency * 100} className="h-2" />
|
|
<p className="text-xs text-gray-500">
|
|
{data.consistency > 0.8
|
|
? 'Excellent - Very consistent sleep schedule'
|
|
: data.consistency > 0.6
|
|
? 'Good - Fairly consistent schedule'
|
|
: 'Needs improvement - Irregular sleep pattern'}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Optimal Times */}
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-2">
|
|
<Moon className="h-4 w-4 text-blue-500" />
|
|
<span className="text-sm text-gray-600">Optimal Bedtime</span>
|
|
</div>
|
|
<p className="text-lg font-semibold">{formatTime(data.optimalBedtime)}</p>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<div className="flex items-center gap-2">
|
|
<Sun className="h-4 w-4 text-yellow-500" />
|
|
<span className="text-sm text-gray-600">Optimal Wake Time</span>
|
|
</div>
|
|
<p className="text-lg font-semibold">{formatTime(data.optimalWakeTime)}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Sleep Phase Shift */}
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-sm text-gray-600">Sleep Phase Shift</span>
|
|
<span className="text-sm font-medium">
|
|
{data.sleepPhaseShift > 0 ? '+' : ''}{data.sleepPhaseShift.toFixed(1)} hours
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
{Math.abs(data.sleepPhaseShift) < 1 ? (
|
|
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
|
) : (
|
|
<AlertCircle className="h-4 w-4 text-yellow-500" />
|
|
)}
|
|
<p className="text-xs text-gray-500">
|
|
{Math.abs(data.sleepPhaseShift) < 1
|
|
? 'Sleep schedule aligned with typical patterns'
|
|
: data.sleepPhaseShift > 0
|
|
? `Bedtime is ${Math.abs(data.sleepPhaseShift).toFixed(1)} hours later than typical`
|
|
: `Bedtime is ${Math.abs(data.sleepPhaseShift).toFixed(1)} hours earlier than typical`}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Melatonin Onset */}
|
|
<div className="p-3 bg-purple-50 rounded-lg">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<Brain className="h-4 w-4 text-purple-600" />
|
|
<span className="text-sm font-medium text-purple-900">Estimated Melatonin Onset</span>
|
|
</div>
|
|
<p className="text-lg font-semibold text-purple-700">{formatTime(data.melatoninOnset)}</p>
|
|
<p className="text-xs text-purple-600 mt-1">
|
|
Natural sleepiness begins around this time
|
|
</p>
|
|
</div>
|
|
|
|
{/* Recommended Schedule */}
|
|
<div className="space-y-3">
|
|
<h4 className="text-sm font-medium text-gray-700">Recommended Daily Schedule</h4>
|
|
<div className="space-y-2 text-sm">
|
|
<div className="flex justify-between py-2 border-b">
|
|
<span className="text-gray-600">Wake Time</span>
|
|
<span className="font-medium">{formatTime(data.recommendedSchedule.wakeTime)}</span>
|
|
</div>
|
|
{data.recommendedSchedule.morningNap && (
|
|
<div className="flex justify-between py-2 border-b">
|
|
<span className="text-gray-600">Morning Nap</span>
|
|
<span className="font-medium">
|
|
{formatTime(data.recommendedSchedule.morningNap.start)} ({data.recommendedSchedule.morningNap.duration} min)
|
|
</span>
|
|
</div>
|
|
)}
|
|
{data.recommendedSchedule.afternoonNap && (
|
|
<div className="flex justify-between py-2 border-b">
|
|
<span className="text-gray-600">Afternoon Nap</span>
|
|
<span className="font-medium">
|
|
{formatTime(data.recommendedSchedule.afternoonNap.start)} ({data.recommendedSchedule.afternoonNap.duration} min)
|
|
</span>
|
|
</div>
|
|
)}
|
|
<div className="flex justify-between py-2 border-b">
|
|
<span className="text-gray-600">Bedtime</span>
|
|
<span className="font-medium">{formatTime(data.recommendedSchedule.bedtime)}</span>
|
|
</div>
|
|
<div className="flex justify-between py-2">
|
|
<span className="text-gray-600">Daily Sleep Target</span>
|
|
<span className="font-medium">{Math.round(data.recommendedSchedule.totalSleepTarget / 60)} hours</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
} |