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,263 @@
'use client';
import { useState, useEffect } from 'react';
import {
Box,
Typography,
Paper,
Grid,
Card,
CardContent,
Button,
CircularProgress,
Tabs,
Tab,
Alert,
} from '@mui/material';
import { AppShell } from '@/components/layouts/AppShell/AppShell';
import { ProtectedRoute } from '@/components/common/ProtectedRoute';
import {
TrendingUp,
Hotel,
Restaurant,
BabyChangingStation,
Download,
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import apiClient from '@/lib/api/client';
import WeeklySleepChart from '@/components/analytics/WeeklySleepChart';
import FeedingFrequencyGraph from '@/components/analytics/FeedingFrequencyGraph';
import GrowthCurve from '@/components/analytics/GrowthCurve';
import PatternInsights from '@/components/analytics/PatternInsights';
interface TabPanelProps {
children?: React.ReactNode;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`analytics-tabpanel-${index}`}
aria-labelledby={`analytics-tab-${index}`}
{...other}
>
{value === index && <Box sx={{ pt: 3 }}>{children}</Box>}
</div>
);
}
export default function AnalyticsPage() {
const [tabValue, setTabValue] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [insights, setInsights] = useState<any>(null);
useEffect(() => {
fetchAnalytics();
}, []);
const fetchAnalytics = async () => {
try {
setIsLoading(true);
const response = await apiClient.get('/api/v1/analytics/insights');
setInsights(response.data.data);
} catch (err: any) {
console.error('Failed to fetch analytics:', err);
setError(err.response?.data?.message || 'Failed to load analytics');
} finally {
setIsLoading(false);
}
};
const handleExportReport = async () => {
try {
const response = await apiClient.get('/api/v1/analytics/reports/weekly', {
responseType: 'blob',
});
const blob = new Blob([response.data], { type: 'application/pdf' });
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `weekly-report-${new Date().toISOString().split('T')[0]}.pdf`;
link.click();
window.URL.revokeObjectURL(url);
} catch (err) {
console.error('Failed to export report:', err);
}
};
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
if (isLoading) {
return (
<ProtectedRoute>
<AppShell>
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: '60vh',
}}
>
<CircularProgress />
</Box>
</AppShell>
</ProtectedRoute>
);
}
return (
<ProtectedRoute>
<AppShell>
<Box>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
{/* Header */}
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
mb: 3,
}}
>
<Box>
<Typography variant="h4" gutterBottom fontWeight="600">
Analytics & Insights 📊
</Typography>
<Typography variant="body1" color="text.secondary">
Track patterns and get personalized insights
</Typography>
</Box>
<Button
variant="contained"
startIcon={<Download />}
onClick={handleExportReport}
sx={{ borderRadius: 3 }}
>
Export Report
</Button>
</Box>
{error && (
<Alert severity="error" sx={{ mb: 3, borderRadius: 2 }}>
{error}
</Alert>
)}
{/* Summary Cards */}
<Grid container spacing={2} sx={{ mb: 4 }}>
<Grid item xs={12} sm={4}>
<Card
sx={{
background: 'linear-gradient(135deg, #B6D7FF 0%, #A5C9FF 100%)',
color: 'white',
}}
>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<Hotel sx={{ fontSize: 32 }} />
<Typography variant="h5" fontWeight="600">
{insights?.sleep?.averageHours || '0'}h
</Typography>
</Box>
<Typography variant="body2">Avg Sleep (7 days)</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} sm={4}>
<Card
sx={{
background: 'linear-gradient(135deg, #FFB6C1 0%, #FFA5B0 100%)',
color: 'white',
}}
>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<Restaurant sx={{ fontSize: 32 }} />
<Typography variant="h5" fontWeight="600">
{insights?.feeding?.averagePerDay || '0'}
</Typography>
</Box>
<Typography variant="body2">Avg Feedings (7 days)</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} sm={4}>
<Card
sx={{
background: 'linear-gradient(135deg, #FFE4B5 0%, #FFD9A0 100%)',
color: 'white',
}}
>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<BabyChangingStation sx={{ fontSize: 32 }} />
<Typography variant="h5" fontWeight="600">
{insights?.diaper?.averagePerDay || '0'}
</Typography>
</Box>
<Typography variant="body2">Avg Diapers (7 days)</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
{/* Tabs */}
<Paper sx={{ borderRadius: 3, overflow: 'hidden' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
value={tabValue}
onChange={handleTabChange}
aria-label="analytics tabs"
sx={{ px: 2 }}
>
<Tab label="Sleep Patterns" />
<Tab label="Feeding Patterns" />
<Tab label="Growth Curve" />
<Tab label="Insights" />
</Tabs>
</Box>
<TabPanel value={tabValue} index={0}>
<Box sx={{ p: 3 }}>
<WeeklySleepChart />
</Box>
</TabPanel>
<TabPanel value={tabValue} index={1}>
<Box sx={{ p: 3 }}>
<FeedingFrequencyGraph />
</Box>
</TabPanel>
<TabPanel value={tabValue} index={2}>
<Box sx={{ p: 3 }}>
<GrowthCurve />
</Box>
</TabPanel>
<TabPanel value={tabValue} index={3}>
<Box sx={{ p: 3 }}>
<PatternInsights insights={insights} />
</Box>
</TabPanel>
</Paper>
</motion.div>
</Box>
</AppShell>
</ProtectedRoute>
);
}