Files
babymonitor/frontend/src/components/stats/DailySummary.tsx
T
2026-05-28 09:31:20 +02:00

130 lines
4.1 KiB
TypeScript

import { useMemo } from 'react';
import { useStats } from '../../hooks/useStats';
import { formatMinutes } from '../../utils/time';
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from 'recharts';
interface DailySummaryProps {
from_date: string;
to_date: string;
day_start: string;
day_end: string;
}
interface ChartDataItem {
date: string;
daySleep: number;
nightSleep: number;
total: number;
}
interface AvgStats {
avgTotal: number;
avgDay: number;
avgNight: number;
count: number;
}
const DailySummary: React.FC<DailySummaryProps> = ({ from_date, to_date, day_start, day_end }) => {
const { dailySummary, isLoading } = useStats(from_date, to_date, day_start, day_end);
const { chartData, avgStats } = useMemo<{ chartData: ChartDataItem[]; avgStats: AvgStats }>(() => {
if (!dailySummary?.days) {
return { chartData: [] as ChartDataItem[], avgStats: { avgTotal: 0, avgDay: 0, avgNight: 0, count: 0 } };
}
const data = dailySummary.days.map((day) => ({
date: day.date,
daySleep: day.day_minutes / 60,
nightSleep: day.night_minutes / 60,
total: day.total_minutes / 60,
})).reverse();
let averages = { avgTotal: 0, avgDay: 0, avgNight: 0, count: 0 };
if (data.length) {
const totals = data.reduce(
(acc, d) => ({
total: acc.total + d.total,
day: acc.day + d.daySleep,
night: acc.night + d.nightSleep,
}),
{ total: 0, day: 0, night: 0 }
);
const count = data.length;
averages = {
avgTotal: totals.total / count,
avgDay: totals.day / count,
avgNight: totals.night / count,
count,
};
}
return { chartData: data, avgStats: averages };
}, [dailySummary]);
if (isLoading) return <div className="text-center p-4">Loading summary...</div>;
if (!chartData.length) {
return <div className="text-center p-4 text-gray-500">No data available for this period.</div>;
}
return (
<div className="bg-white p-4 rounded-xl shadow-sm border border-gray-200">
<h3 className="text-lg font-semibold mb-4 text-gray-800">Daily Summary</h3>
<ResponsiveContainer width="100%" height={Math.max(200, chartData.length * 30)}>
<BarChart
data={chartData}
layout="vertical"
margin={{ top: 5, right: 30, left: 60, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
<XAxis
type="number"
tickFormatter={(value) => `${Math.round(value)}h`}
tick={{ fontSize: 12 }}
tickLine={false}
/>
<YAxis
type="category"
dataKey="date"
tick={{ fontSize: 12 }}
tickLine={false}
width={60}
/>
<Tooltip
formatter={(value: number, name: string) => [
formatMinutes(Math.round((value ?? 0) * 60)),
name === 'daySleep' ? 'Day' : 'Night',
]}
/>
<Bar dataKey="nightSleep" stackId="a" fill="#1e3a5f" />
<Bar dataKey="daySleep" stackId="a" fill="#fbbf24" />
</BarChart>
</ResponsiveContainer>
<div className="mt-4 grid grid-cols-3 gap-4 text-center">
<div className="p-3 bg-gray-50 rounded-lg">
<div className="text-xs text-gray-500">Avg Total</div>
<div className="text-lg font-semibold text-gray-800">{formatMinutes(Math.round(avgStats.avgTotal * 60))}</div>
</div>
<div className="p-3 bg-gray-50 rounded-lg">
<div className="text-xs text-gray-500">Avg Day</div>
<div className="text-lg font-semibold text-gray-800">{formatMinutes(Math.round(avgStats.avgDay * 60))}</div>
</div>
<div className="p-3 bg-gray-50 rounded-lg">
<div className="text-xs text-gray-500">Avg Night</div>
<div className="text-lg font-semibold text-gray-800">{formatMinutes(Math.round(avgStats.avgNight * 60))}</div>
</div>
</div>
</div>
);
};
export default DailySummary;