Files
maternal-app/maternal-web/components/analytics/WeeklySleepChart.tsx
2025-10-01 19:01:52 +00:00

197 lines
5.7 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 { format, subDays } from 'date-fns';
import apiClient from '@/lib/api/client';
interface SleepData {
date: string;
totalHours: number;
nightSleep: number;
naps: number;
quality: number;
}
export default function WeeklySleepChart() {
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>
);
}