319 lines
10 KiB
TypeScript
319 lines
10 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import {
|
|
Box,
|
|
Typography,
|
|
Paper,
|
|
Grid,
|
|
Chip,
|
|
Alert,
|
|
CircularProgress,
|
|
LinearProgress,
|
|
Card,
|
|
CardContent,
|
|
} from '@mui/material';
|
|
import {
|
|
TrendingUp,
|
|
TrendingDown,
|
|
Schedule,
|
|
Lightbulb,
|
|
Warning,
|
|
CheckCircle,
|
|
} from '@mui/icons-material';
|
|
import { motion } from 'framer-motion';
|
|
import apiClient from '@/lib/api/client';
|
|
|
|
interface Pattern {
|
|
type: string;
|
|
description: string;
|
|
confidence: number;
|
|
trend: 'up' | 'down' | 'stable';
|
|
recommendations?: string[];
|
|
}
|
|
|
|
interface Insight {
|
|
category: string;
|
|
title: string;
|
|
description: string;
|
|
severity: 'info' | 'warning' | 'success';
|
|
patterns?: Pattern[];
|
|
}
|
|
|
|
interface Props {
|
|
insights?: any;
|
|
}
|
|
|
|
export default function PatternInsights({ insights: propInsights }: Props) {
|
|
const [insights, setInsights] = useState<Insight[]>([]);
|
|
const [patterns, setPatterns] = useState<Pattern[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (propInsights) {
|
|
processInsights(propInsights);
|
|
setIsLoading(false);
|
|
} else {
|
|
fetchPatterns();
|
|
}
|
|
}, [propInsights]);
|
|
|
|
const fetchPatterns = async () => {
|
|
try {
|
|
setIsLoading(true);
|
|
const response = await apiClient.get('/api/v1/insights/patterns');
|
|
const data = response.data.data;
|
|
|
|
processInsights(data);
|
|
} catch (err: any) {
|
|
console.error('Failed to fetch patterns:', err);
|
|
setError(err.response?.data?.message || 'Failed to load insights');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const processInsights = (data: any) => {
|
|
// Process sleep patterns
|
|
const sleepInsights: Insight[] = [];
|
|
if (data?.sleep) {
|
|
const avgHours = data.sleep.averageHours || 0;
|
|
if (avgHours < 10) {
|
|
sleepInsights.push({
|
|
category: 'Sleep',
|
|
title: 'Low Sleep Duration',
|
|
description: `Average sleep is ${avgHours}h/day. Recommended: 12-16h for infants, 10-13h for toddlers.`,
|
|
severity: 'warning',
|
|
});
|
|
} else {
|
|
sleepInsights.push({
|
|
category: 'Sleep',
|
|
title: 'Healthy Sleep Duration',
|
|
description: `Great! Your child is averaging ${avgHours}h of sleep per day.`,
|
|
severity: 'success',
|
|
});
|
|
}
|
|
|
|
if (data.sleep.patterns) {
|
|
sleepInsights[0].patterns = data.sleep.patterns.map((p: any) => ({
|
|
type: p.type || 'sleep',
|
|
description: p.description || 'Sleep pattern detected',
|
|
confidence: p.confidence || 0.8,
|
|
trend: p.trend || 'stable',
|
|
}));
|
|
}
|
|
}
|
|
|
|
// Process feeding patterns
|
|
const feedingInsights: Insight[] = [];
|
|
if (data?.feeding) {
|
|
const avgPerDay = data.feeding.averagePerDay || 0;
|
|
feedingInsights.push({
|
|
category: 'Feeding',
|
|
title: 'Feeding Frequency',
|
|
description: `Your child is feeding ${avgPerDay} times per day on average.`,
|
|
severity: 'info',
|
|
});
|
|
|
|
if (data.feeding.patterns) {
|
|
feedingInsights[0].patterns = data.feeding.patterns.map((p: any) => ({
|
|
type: p.type || 'feeding',
|
|
description: p.description || 'Feeding pattern detected',
|
|
confidence: p.confidence || 0.8,
|
|
trend: p.trend || 'stable',
|
|
recommendations: p.recommendations,
|
|
}));
|
|
}
|
|
}
|
|
|
|
// Process diaper patterns
|
|
const diaperInsights: Insight[] = [];
|
|
if (data?.diaper) {
|
|
const avgPerDay = data.diaper.averagePerDay || 0;
|
|
if (avgPerDay < 5) {
|
|
diaperInsights.push({
|
|
category: 'Diaper',
|
|
title: 'Low Diaper Changes',
|
|
description: `Average ${avgPerDay} diaper changes/day. Consider checking hydration if this continues.`,
|
|
severity: 'warning',
|
|
});
|
|
} else {
|
|
diaperInsights.push({
|
|
category: 'Diaper',
|
|
title: 'Normal Diaper Activity',
|
|
description: `Averaging ${avgPerDay} diaper changes per day - within normal range.`,
|
|
severity: 'success',
|
|
});
|
|
}
|
|
}
|
|
|
|
setInsights([...sleepInsights, ...feedingInsights, ...diaperInsights]);
|
|
|
|
// Extract all patterns
|
|
const allPatterns: Pattern[] = [];
|
|
[...sleepInsights, ...feedingInsights, ...diaperInsights].forEach((insight) => {
|
|
if (insight.patterns) {
|
|
allPatterns.push(...insight.patterns);
|
|
}
|
|
});
|
|
setPatterns(allPatterns);
|
|
};
|
|
|
|
const getSeverityIcon = (severity: string) => {
|
|
switch (severity) {
|
|
case 'warning':
|
|
return <Warning sx={{ color: 'warning.main' }} />;
|
|
case 'success':
|
|
return <CheckCircle sx={{ color: 'success.main' }} />;
|
|
default:
|
|
return <Lightbulb sx={{ color: 'info.main' }} />;
|
|
}
|
|
};
|
|
|
|
const getTrendIcon = (trend: string) => {
|
|
switch (trend) {
|
|
case 'up':
|
|
return <TrendingUp sx={{ color: 'success.main' }} />;
|
|
case 'down':
|
|
return <TrendingDown sx={{ color: 'warning.main' }} />;
|
|
default:
|
|
return <Schedule sx={{ color: 'info.main' }} />;
|
|
}
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
|
|
<CircularProgress />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<Alert severity="error" sx={{ borderRadius: 2 }}>
|
|
{error}
|
|
</Alert>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box>
|
|
<Typography variant="h6" gutterBottom fontWeight="600">
|
|
Pattern Insights & Recommendations
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
|
AI-powered insights based on your child's activity patterns
|
|
</Typography>
|
|
|
|
{/* Insights Cards */}
|
|
<Grid container spacing={2} sx={{ mb: 4 }}>
|
|
{insights.map((insight, index) => (
|
|
<Grid item xs={12} key={index}>
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.3, delay: index * 0.1 }}
|
|
>
|
|
<Card
|
|
sx={{
|
|
borderLeft: 4,
|
|
borderColor:
|
|
insight.severity === 'warning'
|
|
? 'warning.main'
|
|
: insight.severity === 'success'
|
|
? 'success.main'
|
|
: 'info.main',
|
|
}}
|
|
>
|
|
<CardContent>
|
|
<Box sx={{ display: 'flex', gap: 2, alignItems: 'flex-start' }}>
|
|
<Box sx={{ mt: 0.5 }}>{getSeverityIcon(insight.severity)}</Box>
|
|
<Box sx={{ flex: 1 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
|
|
<Typography variant="h6" fontWeight="600">
|
|
{insight.title}
|
|
</Typography>
|
|
<Chip label={insight.category} size="small" />
|
|
</Box>
|
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
|
{insight.description}
|
|
</Typography>
|
|
|
|
{/* Pattern Details */}
|
|
{insight.patterns && insight.patterns.length > 0 && (
|
|
<Box sx={{ mt: 2 }}>
|
|
{insight.patterns.map((pattern, pIndex) => (
|
|
<Paper
|
|
key={pIndex}
|
|
elevation={0}
|
|
sx={{
|
|
p: 2,
|
|
mb: 1,
|
|
bgcolor: 'background.default',
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
mb: 1,
|
|
}}
|
|
>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
{getTrendIcon(pattern.trend)}
|
|
<Typography variant="body2" fontWeight="600">
|
|
{pattern.description}
|
|
</Typography>
|
|
</Box>
|
|
<Chip
|
|
label={`${Math.round(pattern.confidence * 100)}% confidence`}
|
|
size="small"
|
|
color="primary"
|
|
variant="outlined"
|
|
/>
|
|
</Box>
|
|
<LinearProgress
|
|
variant="determinate"
|
|
value={pattern.confidence * 100}
|
|
sx={{ mb: 1, borderRadius: 1 }}
|
|
/>
|
|
{pattern.recommendations && pattern.recommendations.length > 0 && (
|
|
<Box sx={{ mt: 1 }}>
|
|
<Typography variant="caption" color="text.secondary">
|
|
Recommendations:
|
|
</Typography>
|
|
<ul style={{ margin: '4px 0', paddingLeft: '20px' }}>
|
|
{pattern.recommendations.map((rec, rIndex) => (
|
|
<li key={rIndex}>
|
|
<Typography variant="caption">{rec}</Typography>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</Box>
|
|
)}
|
|
</Paper>
|
|
))}
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
</Grid>
|
|
))}
|
|
</Grid>
|
|
|
|
{/* No Insights Message */}
|
|
{insights.length === 0 && (
|
|
<Alert severity="info" sx={{ borderRadius: 2 }}>
|
|
Keep tracking activities to see personalized insights and patterns!
|
|
</Alert>
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|