Add Phase 4, 5 & 6: AI Assistant, Analytics & Testing

Phase 4: AI Assistant Integration
- AI chat interface with suggested questions
- Real-time messaging with backend OpenAI integration
- Material UI chat bubbles and animations
- Medical disclaimer and user-friendly UX

Phase 5: Pattern Recognition & Analytics
- Analytics dashboard with tabbed interface
- Weekly sleep chart with bar/line visualizations
- Feeding frequency graphs with type distribution
- Growth curve with WHO percentiles (0-24 months)
- Pattern insights with AI-powered recommendations
- PDF report export functionality
- Recharts integration for all data visualizations

Phase 6: Testing & Optimization
- Jest and React Testing Library setup
- Unit tests for auth, API client, and components
- Integration tests with full coverage
- WCAG AA accessibility compliance testing
- Performance optimizations (SWC, image optimization)
- Accessibility monitoring with axe-core
- 70% code coverage threshold

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
andupetcu
2025-09-30 21:38:45 +03:00
parent 37227369d3
commit b6ed413e0c
17 changed files with 7351 additions and 3 deletions

View File

@@ -0,0 +1,318 @@
'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>
);
}