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>
199 lines
5.8 KiB
TypeScript
199 lines
5.8 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { Box, Typography, CircularProgress, Alert } from '@mui/material';
|
|
import {
|
|
LineChart,
|
|
Line,
|
|
BarChart,
|
|
Bar,
|
|
XAxis,
|
|
YAxis,
|
|
CartesianGrid,
|
|
Tooltip,
|
|
Legend,
|
|
ResponsiveContainer,
|
|
} from 'recharts';
|
|
import { subDays } from 'date-fns';
|
|
import { useLocalizedDate } from '@/hooks/useLocalizedDate';
|
|
import apiClient from '@/lib/api/client';
|
|
|
|
interface SleepData {
|
|
date: string;
|
|
totalHours: number;
|
|
nightSleep: number;
|
|
naps: number;
|
|
quality: number;
|
|
}
|
|
|
|
export default function WeeklySleepChart() {
|
|
const { format } = useLocalizedDate();
|
|
const [data, setData] = useState<SleepData[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
fetchSleepData();
|
|
}, []);
|
|
|
|
const fetchSleepData = async () => {
|
|
try {
|
|
setIsLoading(true);
|
|
const endDate = new Date();
|
|
const startDate = subDays(endDate, 6);
|
|
|
|
const response = await apiClient.get('/api/v1/activities/sleep', {
|
|
params: {
|
|
startDate: startDate.toISOString(),
|
|
endDate: endDate.toISOString(),
|
|
},
|
|
});
|
|
|
|
// Process the data to aggregate by day
|
|
const sleepActivities = response.data.data;
|
|
const dailyData: { [key: string]: SleepData } = {};
|
|
|
|
// Initialize 7 days of data
|
|
for (let i = 0; i < 7; i++) {
|
|
const date = subDays(endDate, 6 - i);
|
|
const dateStr = format(date, 'MMM dd');
|
|
dailyData[dateStr] = {
|
|
date: dateStr,
|
|
totalHours: 0,
|
|
nightSleep: 0,
|
|
naps: 0,
|
|
quality: 0,
|
|
};
|
|
}
|
|
|
|
// Aggregate sleep data by day
|
|
sleepActivities.forEach((activity: any) => {
|
|
const dateStr = format(new Date(activity.startTime), 'MMM dd');
|
|
if (dailyData[dateStr]) {
|
|
const duration = activity.duration || 0;
|
|
const hours = duration / 60; // Convert minutes to hours
|
|
|
|
dailyData[dateStr].totalHours += hours;
|
|
|
|
// Determine if it's night sleep or nap based on time
|
|
const hour = new Date(activity.startTime).getHours();
|
|
if (hour >= 18 || hour < 6) {
|
|
dailyData[dateStr].nightSleep += hours;
|
|
} else {
|
|
dailyData[dateStr].naps += hours;
|
|
}
|
|
|
|
// Average quality
|
|
if (activity.quality) {
|
|
dailyData[dateStr].quality =
|
|
(dailyData[dateStr].quality + activity.quality) / 2;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Round values for display
|
|
const chartData = Object.values(dailyData).map((day) => ({
|
|
...day,
|
|
totalHours: Math.round(day.totalHours * 10) / 10,
|
|
nightSleep: Math.round(day.nightSleep * 10) / 10,
|
|
naps: Math.round(day.naps * 10) / 10,
|
|
quality: Math.round(day.quality * 10) / 10,
|
|
}));
|
|
|
|
setData(chartData);
|
|
} catch (err: any) {
|
|
console.error('Failed to fetch sleep data:', err);
|
|
setError(err.response?.data?.message || 'Failed to load sleep data');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
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">
|
|
Weekly Sleep Patterns
|
|
</Typography>
|
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
|
Track your child's sleep duration and quality over the past 7 days
|
|
</Typography>
|
|
|
|
{/* Total Sleep Hours Chart */}
|
|
<Box sx={{ mb: 4 }}>
|
|
<Typography variant="subtitle1" gutterBottom fontWeight="600">
|
|
Total Sleep Hours
|
|
</Typography>
|
|
<ResponsiveContainer width="100%" height={300}>
|
|
<BarChart data={data}>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
|
<XAxis dataKey="date" stroke="#666" />
|
|
<YAxis stroke="#666" label={{ value: 'Hours', angle: -90, position: 'insideLeft' }} />
|
|
<Tooltip
|
|
contentStyle={{
|
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
border: 'none',
|
|
borderRadius: 8,
|
|
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
|
}}
|
|
/>
|
|
<Legend />
|
|
<Bar dataKey="nightSleep" stackId="a" fill="#B6D7FF" name="Night Sleep" />
|
|
<Bar dataKey="naps" stackId="a" fill="#A5C9FF" name="Naps" />
|
|
</BarChart>
|
|
</ResponsiveContainer>
|
|
</Box>
|
|
|
|
{/* Sleep Quality Trend */}
|
|
<Box>
|
|
<Typography variant="subtitle1" gutterBottom fontWeight="600">
|
|
Sleep Quality Trend
|
|
</Typography>
|
|
<ResponsiveContainer width="100%" height={250}>
|
|
<LineChart data={data}>
|
|
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
|
<XAxis dataKey="date" stroke="#666" />
|
|
<YAxis
|
|
stroke="#666"
|
|
domain={[0, 5]}
|
|
label={{ value: 'Quality (1-5)', angle: -90, position: 'insideLeft' }}
|
|
/>
|
|
<Tooltip
|
|
contentStyle={{
|
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
border: 'none',
|
|
borderRadius: 8,
|
|
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
|
}}
|
|
/>
|
|
<Legend />
|
|
<Line
|
|
type="monotone"
|
|
dataKey="quality"
|
|
stroke="#B6D7FF"
|
|
strokeWidth={3}
|
|
dot={{ fill: '#B6D7FF', r: 5 }}
|
|
name="Quality"
|
|
/>
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</Box>
|
|
</Box>
|
|
);
|
|
}
|