feat: Redesign UI with consistent card styling and mobile header
- Updated track page cards to match home page styling with vibrant colors - Applied consistent 140px height cards across track and insights pages - Added mobile header bar with connection status and user menu - Moved user menu from floating top-left to fixed header top-right - Updated insights dashboard with home page color palette (#E91E63, #1976D2, etc.) - Centered cards with minWidth constraints (200px for stats, 400px for charts) - Fixed hydration mismatch by replacing JS media queries with CSS breakpoints - Improved accessibility with viewport settings (removed zoom restrictions) - Added UI improvements documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -68,11 +68,11 @@ interface ActivityTypeData {
|
||||
}
|
||||
|
||||
const COLORS = {
|
||||
feeding: '#FFB6C1',
|
||||
sleep: '#B6D7FF',
|
||||
diaper: '#FFE4B5',
|
||||
medication: '#D4B5FF',
|
||||
milestone: '#B5FFD4',
|
||||
feeding: '#E91E63',
|
||||
sleep: '#1976D2',
|
||||
diaper: '#F57C00',
|
||||
medication: '#C62828',
|
||||
milestone: '#558B2F',
|
||||
note: '#FFD3B6',
|
||||
wet: '#87CEEB',
|
||||
dirty: '#D2691E',
|
||||
@@ -269,50 +269,49 @@ export const InsightsDashboard: React.FC = () => {
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<Box>
|
||||
<Typography variant="h4" fontWeight="600" gutterBottom>
|
||||
<Box sx={{ px: { xs: 2, sm: 3 }, py: 3 }}>
|
||||
<Typography variant="h4" sx={{ mb: 1, fontWeight: 600 }}>
|
||||
{t('title')}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 4 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 4 }}>
|
||||
{t('subtitle')}
|
||||
</Typography>
|
||||
|
||||
{/* Filters */}
|
||||
<Paper sx={{ p: 3, mb: 3 }}>
|
||||
<Grid container spacing={2} alignItems="center">
|
||||
{children.length > 1 && (
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>{t('filters.child')}</InputLabel>
|
||||
<Select
|
||||
value={selectedChild}
|
||||
onChange={(e) => setSelectedChild(e.target.value)}
|
||||
label={t('filters.child')}
|
||||
>
|
||||
{children.map((child) => (
|
||||
<MenuItem key={child.id} value={child.id}>
|
||||
{child.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} sm={6} md={4}>
|
||||
<ToggleButtonGroup
|
||||
value={dateRange}
|
||||
exclusive
|
||||
onChange={(_, newValue) => newValue && setDateRange(newValue)}
|
||||
fullWidth
|
||||
size="large"
|
||||
{/* Time period selector */}
|
||||
<Box sx={{ mb: 4, display: 'flex', gap: 2, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
{children.length > 1 && (
|
||||
<FormControl sx={{ minWidth: 200 }}>
|
||||
<InputLabel>{t('filters.child')}</InputLabel>
|
||||
<Select
|
||||
value={selectedChild}
|
||||
onChange={(e) => setSelectedChild(e.target.value)}
|
||||
label={t('filters.child')}
|
||||
>
|
||||
<ToggleButton value="7days">{t('filters.dateRange.7days')}</ToggleButton>
|
||||
<ToggleButton value="30days">{t('filters.dateRange.30days')}</ToggleButton>
|
||||
<ToggleButton value="3months">{t('filters.dateRange.3months')}</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
{children.map((child) => (
|
||||
<MenuItem key={child.id} value={child.id}>
|
||||
{child.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
<ToggleButtonGroup
|
||||
value={dateRange}
|
||||
exclusive
|
||||
onChange={(_, newValue) => newValue && setDateRange(newValue)}
|
||||
sx={{
|
||||
'& .MuiToggleButton-root': {
|
||||
textTransform: 'none',
|
||||
fontWeight: 500,
|
||||
minWidth: { xs: 80, sm: 120 }
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="7days">{t('filters.dateRange.7days')}</ToggleButton>
|
||||
<ToggleButton value="30days">{t('filters.dateRange.30days')}</ToggleButton>
|
||||
<ToggleButton value="3months">{t('filters.dateRange.3months')}</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Box>
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 3 }}>
|
||||
@@ -355,148 +354,167 @@ export const InsightsDashboard: React.FC = () => {
|
||||
|
||||
{!loading && !noChildren && !noActivities && (
|
||||
<>
|
||||
{/* Summary Statistics */}
|
||||
<Grid container spacing={3} sx={{ mb: 3 }}>
|
||||
<Grid item xs={12} sm={6} md={3}>
|
||||
{/* Stats cards */}
|
||||
<Grid container spacing={2} sx={{ mb: 4 }} justifyContent="center">
|
||||
<Grid item xs={6} sm={3} sx={{ minWidth: 200 }}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, delay: 0 }}
|
||||
>
|
||||
<Card sx={{ bgcolor: COLORS.feeding, color: 'white', height: '160px', minHeight: '160px', width: '100%' }}>
|
||||
<Box
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '160px',
|
||||
minHeight: '160px',
|
||||
width: '100%',
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<Restaurant sx={{ fontSize: 32, mb: 1 }} />
|
||||
<Typography variant="h3" fontWeight="700">
|
||||
{stats.totalFeedings}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ opacity: 0.9, mt: 1 }}>
|
||||
{t('stats.feedings.subtitle')}
|
||||
</Typography>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 3,
|
||||
height: '140px',
|
||||
minHeight: '140px',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
bgcolor: COLORS.feeding,
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ fontSize: 48, mb: 1 }} aria-hidden="true">
|
||||
<Restaurant sx={{ fontSize: 48 }} />
|
||||
</Box>
|
||||
</Card>
|
||||
<Typography variant="h3" fontWeight={600}>
|
||||
{stats.totalFeedings}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ opacity: 0.9 }}>
|
||||
{t('stats.feedings.subtitle')}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6} md={3}>
|
||||
<Grid item xs={6} sm={3} sx={{ minWidth: 200 }}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, delay: 0.1 }}
|
||||
>
|
||||
<Card sx={{ bgcolor: COLORS.sleep, color: 'white', height: '160px', minHeight: '160px', width: '100%' }}>
|
||||
<Box
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '160px',
|
||||
minHeight: '160px',
|
||||
width: '100%',
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<Hotel sx={{ fontSize: 32, mb: 1 }} />
|
||||
<Typography variant="h3" fontWeight="700">
|
||||
{stats.avgSleepHours}h
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ opacity: 0.9, mt: 1 }}>
|
||||
{t('stats.sleep.subtitle')}
|
||||
</Typography>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 3,
|
||||
height: '140px',
|
||||
minHeight: '140px',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
bgcolor: COLORS.sleep,
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ fontSize: 48, mb: 1 }} aria-hidden="true">
|
||||
<Hotel sx={{ fontSize: 48 }} />
|
||||
</Box>
|
||||
</Card>
|
||||
<Typography variant="h3" fontWeight={600}>
|
||||
{stats.avgSleepHours}h
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ opacity: 0.9 }}>
|
||||
{t('stats.sleep.subtitle')}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6} md={3}>
|
||||
<Grid item xs={6} sm={3} sx={{ minWidth: 200 }}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, delay: 0.2 }}
|
||||
>
|
||||
<Card sx={{ bgcolor: COLORS.diaper, color: 'white', height: '160px', minHeight: '160px', width: '100%' }}>
|
||||
<Box
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '160px',
|
||||
minHeight: '160px',
|
||||
width: '100%',
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<BabyChangingStation sx={{ fontSize: 32, mb: 1 }} />
|
||||
<Typography variant="h3" fontWeight="700">
|
||||
{stats.totalDiapers}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ opacity: 0.9, mt: 1 }}>
|
||||
{t('stats.diapers.subtitle')}
|
||||
</Typography>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 3,
|
||||
height: '140px',
|
||||
minHeight: '140px',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
bgcolor: COLORS.diaper,
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ fontSize: 48, mb: 1 }} aria-hidden="true">
|
||||
<BabyChangingStation sx={{ fontSize: 48 }} />
|
||||
</Box>
|
||||
</Card>
|
||||
<Typography variant="h3" fontWeight={600}>
|
||||
{stats.totalDiapers}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ opacity: 0.9 }}>
|
||||
{t('stats.diapers.subtitle')}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6} md={3}>
|
||||
<Grid item xs={6} sm={3} sx={{ minWidth: 200 }}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, delay: 0.3 }}
|
||||
>
|
||||
<Card sx={{ bgcolor: COLORS.milestone, color: 'white', height: '160px', minHeight: '160px', width: '100%' }}>
|
||||
<Box
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '160px',
|
||||
minHeight: '160px',
|
||||
width: '100%',
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<TrendingUp sx={{ fontSize: 32, mb: 1 }} />
|
||||
<Typography variant="h3" fontWeight="700" sx={{ textTransform: 'capitalize' }}>
|
||||
{t(`activityTypes.${stats.mostCommonType}`)}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ opacity: 0.9, mt: 1 }}>
|
||||
{t('stats.topActivity.subtitle')}
|
||||
</Typography>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 3,
|
||||
height: '140px',
|
||||
minHeight: '140px',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
textAlign: 'center',
|
||||
bgcolor: COLORS.milestone,
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ fontSize: 48, mb: 1 }} aria-hidden="true">
|
||||
<TrendingUp sx={{ fontSize: 48 }} />
|
||||
</Box>
|
||||
</Card>
|
||||
<Typography variant="h3" fontWeight={600} sx={{ textTransform: 'capitalize' }}>
|
||||
{t(`activityTypes.${stats.mostCommonType}`)}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ opacity: 0.9 }}>
|
||||
{t('stats.topActivity.subtitle')}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</motion.div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Charts */}
|
||||
<Grid container spacing={3} sx={{ mb: 3 }}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card sx={{ height: '350px', minHeight: '350px', width: '100%' }}>
|
||||
<CardContent sx={{ height: '100%', display: 'flex', flexDirection: 'column', p: 2, '&:last-child': { pb: 2 } }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Restaurant sx={{ mr: 1, color: COLORS.feeding }} />
|
||||
<Typography variant="h6" fontWeight="600">
|
||||
{t('charts.feedingFrequency')}
|
||||
</Typography>
|
||||
</Box>
|
||||
{/* Charts grid */}
|
||||
<Grid container spacing={3} justifyContent="center" sx={{ mb: 3 }}>
|
||||
<Grid item xs={12} md={6} sx={{ minWidth: 400 }}>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 3,
|
||||
bgcolor: 'background.paper',
|
||||
height: 400,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 1, fontWeight: 600 }}>
|
||||
<Restaurant sx={{ color: COLORS.feeding }} /> {t('charts.feedingFrequency')}
|
||||
</Typography>
|
||||
<Box sx={{ flexGrow: 1, position: 'relative' }}>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<BarChart data={dailyData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
@@ -506,19 +524,26 @@ export const InsightsDashboard: React.FC = () => {
|
||||
<Bar dataKey="feedings" fill={COLORS.feeding} name={t('charts.chartLabels.feedings')} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card sx={{ height: '350px', minHeight: '350px', width: '100%' }}>
|
||||
<CardContent sx={{ height: '100%', display: 'flex', flexDirection: 'column', p: 2, '&:last-child': { pb: 2 } }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Hotel sx={{ mr: 1, color: COLORS.sleep }} />
|
||||
<Typography variant="h6" fontWeight="600">
|
||||
{t('charts.sleepDuration')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Grid item xs={12} md={6} sx={{ minWidth: 400 }}>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 3,
|
||||
bgcolor: 'background.paper',
|
||||
height: 400,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 1, fontWeight: 600 }}>
|
||||
<Hotel sx={{ color: COLORS.sleep }} /> {t('charts.sleepDuration')}
|
||||
</Typography>
|
||||
<Box sx={{ flexGrow: 1, position: 'relative' }}>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<LineChart data={dailyData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
@@ -535,20 +560,27 @@ export const InsightsDashboard: React.FC = () => {
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
{diaperData.length > 0 && (
|
||||
<Grid item xs={12} md={6}>
|
||||
<Card sx={{ height: '350px', minHeight: '350px', width: '100%' }}>
|
||||
<CardContent sx={{ height: '100%', display: 'flex', flexDirection: 'column', p: 2, '&:last-child': { pb: 2 } }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<BabyChangingStation sx={{ mr: 1, color: COLORS.diaper }} />
|
||||
<Typography variant="h6" fontWeight="600">
|
||||
{t('charts.diaperChangesByType')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Grid item xs={12} md={6} sx={{ minWidth: 400 }}>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 3,
|
||||
bgcolor: 'background.paper',
|
||||
height: 400,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 1, fontWeight: 600 }}>
|
||||
<BabyChangingStation sx={{ color: COLORS.diaper }} /> {t('charts.diaperChangesByType')}
|
||||
</Typography>
|
||||
<Box sx={{ flexGrow: 1, position: 'relative' }}>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
@@ -568,20 +600,27 @@ export const InsightsDashboard: React.FC = () => {
|
||||
<Tooltip />
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<Grid item xs={12} md={diaperData.length > 0 ? 6 : 12}>
|
||||
<Card sx={{ height: '350px', minHeight: '350px', width: '100%' }}>
|
||||
<CardContent sx={{ height: '100%', display: 'flex', flexDirection: 'column', p: 2, '&:last-child': { pb: 2 } }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Assessment sx={{ mr: 1, color: 'primary.main' }} />
|
||||
<Typography variant="h6" fontWeight="600">
|
||||
{t('charts.activityTimeline')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Grid item xs={12} md={diaperData.length > 0 ? 6 : 12} sx={{ minWidth: 400 }}>
|
||||
<Paper
|
||||
elevation={0}
|
||||
sx={{
|
||||
p: 3,
|
||||
bgcolor: 'background.paper',
|
||||
height: 400,
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 1, fontWeight: 600 }}>
|
||||
<Assessment sx={{ color: 'primary.main' }} /> {t('charts.activityTimeline')}
|
||||
</Typography>
|
||||
<Box sx={{ flexGrow: 1, position: 'relative' }}>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<BarChart data={dailyData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
@@ -594,8 +633,8 @@ export const InsightsDashboard: React.FC = () => {
|
||||
<Bar dataKey="sleepHours" fill={COLORS.sleep} name={t('charts.chartLabels.sleepHours')} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user