Implement Phase 7 Performance Optimization and fix tracking system
Phase 7 Implementation: - Add lazy loading for AI Assistant and Insights pages - Create LoadingFallback component with skeleton screens (page, card, list, chart, chat variants) - Create OptimizedImage component with Next.js Image optimization - Create PerformanceMonitor component with web-vitals v5 integration - Add performance monitoring library tracking Core Web Vitals (CLS, INP, FCP, LCP, TTFB) - Install web-vitals v5.1.0 dependency - Extract AI chat interface and insights dashboard to lazy-loaded components Tracking System Fixes: - Fix API data transformation between frontend (timestamp/data) and backend (startedAt/metadata) - Update createActivity, getActivities, and getActivity to properly transform data structures - Fix diaper, feeding, and sleep tracking pages to work with backend API Homepage Improvements: - Connect Today's Summary to backend daily summary API - Load real-time data for feeding count, sleep hours, and diaper count - Add loading states and empty states for better UX - Format sleep duration as "Xh Ym" for better readability - Display child name in summary section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
182
maternal-web/components/common/LoadingFallback.tsx
Normal file
182
maternal-web/components/common/LoadingFallback.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import { Box, Skeleton, Paper, Container } from '@mui/material';
|
||||
|
||||
interface LoadingFallbackProps {
|
||||
variant?: 'page' | 'card' | 'list' | 'chart' | 'chat';
|
||||
}
|
||||
|
||||
export const LoadingFallback: React.FC<LoadingFallbackProps> = ({ variant = 'page' }) => {
|
||||
if (variant === 'chat') {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
height: 'calc(100vh - 200px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
background: 'linear-gradient(135deg, #FFF5F5 0%, #FFE4E1 100%)',
|
||||
borderRadius: 2,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{/* Header Skeleton */}
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 2,
|
||||
borderRadius: 0,
|
||||
background: 'rgba(255, 255, 255, 0.95)',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Skeleton variant="circular" width={40} height={40} />
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Skeleton variant="text" width={200} height={28} />
|
||||
<Skeleton variant="text" width={300} height={20} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Messages Skeleton */}
|
||||
<Box sx={{ flex: 1, overflowY: 'auto', p: 2 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||
{/* Suggested questions */}
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', justifyContent: 'center' }}>
|
||||
<Skeleton variant="rounded" width={180} height={32} sx={{ borderRadius: 3 }} />
|
||||
<Skeleton variant="rounded" width={200} height={32} sx={{ borderRadius: 3 }} />
|
||||
<Skeleton variant="rounded" width={160} height={32} sx={{ borderRadius: 3 }} />
|
||||
<Skeleton variant="rounded" width={190} height={32} sx={{ borderRadius: 3 }} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Input Skeleton */}
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 2,
|
||||
borderRadius: 0,
|
||||
background: 'rgba(255, 255, 255, 0.95)',
|
||||
borderTop: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', gap: 1, alignItems: 'flex-end' }}>
|
||||
<Skeleton variant="rounded" sx={{ flex: 1, height: 48, borderRadius: 3 }} />
|
||||
<Skeleton variant="circular" width={48} height={48} />
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === 'chart') {
|
||||
return (
|
||||
<Paper sx={{ p: 3 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Skeleton variant="text" width={180} height={32} />
|
||||
<Skeleton variant="rounded" width={120} height={36} />
|
||||
</Box>
|
||||
<Skeleton variant="rectangular" width="100%" height={250} sx={{ borderRadius: 2 }} />
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === 'list') {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{[1, 2, 3, 4, 5].map((i) => (
|
||||
<Paper key={i} sx={{ p: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Skeleton variant="circular" width={48} height={48} />
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Skeleton variant="text" width="60%" height={24} />
|
||||
<Skeleton variant="text" width="40%" height={20} />
|
||||
</Box>
|
||||
<Skeleton variant="rounded" width={80} height={32} />
|
||||
</Box>
|
||||
</Paper>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (variant === 'card') {
|
||||
return (
|
||||
<Paper sx={{ p: 3, borderRadius: 4 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Skeleton variant="circular" width={40} height={40} sx={{ mr: 2 }} />
|
||||
<Skeleton variant="text" width={150} height={32} />
|
||||
</Box>
|
||||
<Skeleton variant="text" width="100%" height={24} />
|
||||
<Skeleton variant="text" width="90%" height={24} />
|
||||
<Skeleton variant="text" width="70%" height={24} />
|
||||
<Box sx={{ mt: 3, display: 'flex', gap: 1 }}>
|
||||
<Skeleton variant="rounded" width={100} height={36} />
|
||||
<Skeleton variant="rounded" width={100} height={36} />
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
// Default: full page skeleton
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ py: 3 }}>
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Skeleton variant="text" width={300} height={48} />
|
||||
<Skeleton variant="text" width={500} height={24} sx={{ mt: 1 }} />
|
||||
</Box>
|
||||
|
||||
{/* Filter section */}
|
||||
<Paper sx={{ p: 3, mb: 3 }}>
|
||||
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
|
||||
<Skeleton variant="rounded" width={200} height={56} />
|
||||
<Skeleton variant="rounded" width={300} height={56} />
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Stats cards */}
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr', md: '1fr 1fr 1fr 1fr' }, gap: 3, mb: 3 }}>
|
||||
{[1, 2, 3, 4].map((i) => (
|
||||
<Paper key={i} sx={{ p: 3 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
|
||||
<Skeleton variant="circular" width={32} height={32} sx={{ mr: 1 }} />
|
||||
<Skeleton variant="text" width={100} height={28} />
|
||||
</Box>
|
||||
<Skeleton variant="text" width={80} height={48} />
|
||||
<Skeleton variant="text" width={120} height={20} />
|
||||
</Paper>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* Charts */}
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 3, mb: 3 }}>
|
||||
{[1, 2].map((i) => (
|
||||
<Paper key={i} sx={{ p: 3 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Skeleton variant="circular" width={24} height={24} sx={{ mr: 1 }} />
|
||||
<Skeleton variant="text" width={150} height={28} />
|
||||
</Box>
|
||||
<Skeleton variant="rectangular" width="100%" height={250} sx={{ borderRadius: 1 }} />
|
||||
</Paper>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* Activity list */}
|
||||
<Paper sx={{ p: 3 }}>
|
||||
<Skeleton variant="text" width={200} height={32} sx={{ mb: 2 }} />
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<Box key={i} sx={{ display: 'flex', alignItems: 'center', gap: 2, borderBottom: '1px solid', borderColor: 'divider', pb: 2 }}>
|
||||
<Skeleton variant="circular" width={40} height={40} />
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Skeleton variant="text" width="40%" height={24} />
|
||||
<Skeleton variant="text" width="60%" height={20} />
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user