This commit implements comprehensive localization for high-priority components: ## Tracking Pages (4 files) - Localized feeding, sleep, diaper, and medicine tracking pages - Replaced hardcoded strings with translation keys from tracking namespace - Added useTranslation hook integration - All form labels, buttons, and messages now support multiple languages ## Child Dialog Components (2 files) - Localized ChildDialog (add/edit child form) - Localized DeleteConfirmDialog - Added new translation keys to children.json for dialog content - Includes validation messages and action buttons ## Date/Time Localization (14 files + new hook) - Created useLocalizedDate hook wrapping date-fns with locale support - Supports 5 languages: English, Spanish, French, Portuguese, Chinese - Updated all date formatting across: * Tracking pages (feeding, sleep, diaper, medicine) * Activity pages (activities, history, track activity) * Settings components (sessions, biometric, device trust) * Analytics components (insights, growth, sleep chart, feeding graph) - Date displays automatically adapt to user's language (e.g., "2 hours ago" → "hace 2 horas") ## Translation Updates - Enhanced children.json with dialog section containing: * Form field labels (name, birthDate, gender, photoUrl) * Action buttons (add, update, delete, cancel, saving, deleting) * Delete confirmation messages * Validation error messages Files changed: 17 files (+164, -113) Languages supported: en, es, fr, pt-BR, zh-CN 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
222 lines
7.2 KiB
TypeScript
222 lines
7.2 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import {
|
|
Box,
|
|
Typography,
|
|
Paper,
|
|
List,
|
|
ListItem,
|
|
ListItemAvatar,
|
|
ListItemText,
|
|
Avatar,
|
|
Chip,
|
|
IconButton,
|
|
Tabs,
|
|
Tab,
|
|
Button,
|
|
} from '@mui/material';
|
|
import {
|
|
Restaurant,
|
|
Hotel,
|
|
BabyChangingStation,
|
|
Delete,
|
|
Edit,
|
|
FilterList,
|
|
} from '@mui/icons-material';
|
|
import { AppShell } from '@/components/layouts/AppShell/AppShell';
|
|
import { ProtectedRoute } from '@/components/common/ProtectedRoute';
|
|
import { motion } from 'framer-motion';
|
|
import { useLocalizedDate } from '@/hooks/useLocalizedDate';
|
|
|
|
// Mock data - will be replaced with API calls
|
|
const mockActivities = [
|
|
{
|
|
id: '1',
|
|
type: 'feeding',
|
|
timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000),
|
|
details: 'Breast feeding - Left, 15 minutes',
|
|
icon: <Restaurant />,
|
|
color: '#FFB6C1',
|
|
},
|
|
{
|
|
id: '2',
|
|
type: 'diaper',
|
|
timestamp: new Date(Date.now() - 3 * 60 * 60 * 1000),
|
|
details: 'Diaper change - Wet',
|
|
icon: <BabyChangingStation />,
|
|
color: '#FFE4B5',
|
|
},
|
|
{
|
|
id: '3',
|
|
type: 'sleep',
|
|
timestamp: new Date(Date.now() - 5 * 60 * 60 * 1000),
|
|
details: 'Sleep - 2h 30m, Good quality',
|
|
icon: <Hotel />,
|
|
color: '#B6D7FF',
|
|
},
|
|
{
|
|
id: '4',
|
|
type: 'feeding',
|
|
timestamp: new Date(Date.now() - 6 * 60 * 60 * 1000),
|
|
details: 'Bottle - 120ml',
|
|
icon: <Restaurant />,
|
|
color: '#FFB6C1',
|
|
},
|
|
{
|
|
id: '5',
|
|
type: 'diaper',
|
|
timestamp: new Date(Date.now() - 7 * 60 * 60 * 1000),
|
|
details: 'Diaper change - Both',
|
|
icon: <BabyChangingStation />,
|
|
color: '#FFE4B5',
|
|
},
|
|
];
|
|
|
|
export default function HistoryPage() {
|
|
const { formatDistanceToNow } = useLocalizedDate();
|
|
const [filter, setFilter] = useState<string>('all');
|
|
const [activities, setActivities] = useState(mockActivities);
|
|
|
|
const filteredActivities =
|
|
filter === 'all'
|
|
? activities
|
|
: activities.filter((activity) => activity.type === filter);
|
|
|
|
const handleDelete = (id: string) => {
|
|
// TODO: Call API to delete activity
|
|
setActivities(activities.filter((activity) => activity.id !== id));
|
|
};
|
|
|
|
return (
|
|
<ProtectedRoute>
|
|
<AppShell>
|
|
<Box>
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
|
<Typography variant="h4" fontWeight="600">
|
|
Activity History
|
|
</Typography>
|
|
<IconButton>
|
|
<FilterList />
|
|
</IconButton>
|
|
</Box>
|
|
|
|
{/* Filter Tabs */}
|
|
<Paper sx={{ mb: 3 }}>
|
|
<Tabs
|
|
value={filter}
|
|
onChange={(_, newValue) => setFilter(newValue)}
|
|
variant="scrollable"
|
|
scrollButtons="auto"
|
|
>
|
|
<Tab label="All" value="all" />
|
|
<Tab label="Feeding" value="feeding" icon={<Restaurant />} iconPosition="start" />
|
|
<Tab label="Sleep" value="sleep" icon={<Hotel />} iconPosition="start" />
|
|
<Tab label="Diaper" value="diaper" icon={<BabyChangingStation />} iconPosition="start" />
|
|
</Tabs>
|
|
</Paper>
|
|
|
|
{/* Activity Timeline */}
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
transition={{ duration: 0.3 }}
|
|
>
|
|
<Paper>
|
|
<List>
|
|
{filteredActivities.length === 0 ? (
|
|
<Box sx={{ p: 4, textAlign: 'center' }}>
|
|
<Typography variant="body1" color="text.secondary">
|
|
No activities found
|
|
</Typography>
|
|
</Box>
|
|
) : (
|
|
filteredActivities.map((activity, index) => (
|
|
<motion.div
|
|
key={activity.id}
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.3, delay: index * 0.05 }}
|
|
>
|
|
<ListItem
|
|
sx={{
|
|
borderBottom: index < filteredActivities.length - 1 ? '1px solid' : 'none',
|
|
borderColor: 'divider',
|
|
py: 2,
|
|
}}
|
|
secondaryAction={
|
|
<Box>
|
|
<IconButton edge="end" aria-label="edit" sx={{ mr: 1 }}>
|
|
<Edit />
|
|
</IconButton>
|
|
<IconButton
|
|
edge="end"
|
|
aria-label="delete"
|
|
onClick={() => handleDelete(activity.id)}
|
|
>
|
|
<Delete />
|
|
</IconButton>
|
|
</Box>
|
|
}
|
|
>
|
|
<ListItemAvatar>
|
|
<Avatar sx={{ bgcolor: activity.color }}>
|
|
{activity.icon}
|
|
</Avatar>
|
|
</ListItemAvatar>
|
|
<ListItemText
|
|
primary={activity.details}
|
|
secondary={
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 0.5 }}>
|
|
<Typography variant="caption" color="text.secondary">
|
|
{formatDistanceToNow(activity.timestamp, { addSuffix: true })}
|
|
</Typography>
|
|
<Chip
|
|
label={activity.type}
|
|
size="small"
|
|
sx={{
|
|
height: 20,
|
|
fontSize: '0.7rem',
|
|
textTransform: 'capitalize',
|
|
}}
|
|
/>
|
|
</Box>
|
|
}
|
|
/>
|
|
</ListItem>
|
|
</motion.div>
|
|
))
|
|
)}
|
|
</List>
|
|
</Paper>
|
|
</motion.div>
|
|
|
|
{/* Daily Summary */}
|
|
<Paper sx={{ p: 3, mt: 3 }}>
|
|
<Typography variant="h6" fontWeight="600" gutterBottom>
|
|
Today's Summary
|
|
</Typography>
|
|
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap', mt: 2 }}>
|
|
<Chip
|
|
icon={<Restaurant />}
|
|
label={`${activities.filter((a) => a.type === 'feeding').length} Feedings`}
|
|
sx={{ bgcolor: '#FFB6C1', color: 'white' }}
|
|
/>
|
|
<Chip
|
|
icon={<Hotel />}
|
|
label={`${activities.filter((a) => a.type === 'sleep').length} Sleep Sessions`}
|
|
sx={{ bgcolor: '#B6D7FF', color: 'white' }}
|
|
/>
|
|
<Chip
|
|
icon={<BabyChangingStation />}
|
|
label={`${activities.filter((a) => a.type === 'diaper').length} Diaper Changes`}
|
|
sx={{ bgcolor: '#FFE4B5', color: 'white' }}
|
|
/>
|
|
</Box>
|
|
</Paper>
|
|
</Box>
|
|
</AppShell>
|
|
</ProtectedRoute>
|
|
);
|
|
}
|