/** * Chart data transformation utilities for baby sleep tracker */ /** * Build timetable slots for visualization * @param slots - raw timetable slots from API * @returns grouped slots by date */ export function buildTimetableSlots(slots) { return slots.reduce((acc, slot) => { if (!acc[slot.date]) { acc[slot.date] = []; } acc[slot.date].push(slot); return acc; }, {}); } /** * Transform wakeup minutes for Recharts LineChart * @param days - wakeup/bedtime data from API * @returns array formatted for LineChart */ export function computeWakeupMinutes(days, wakeupRef = "04:00") { return days.map((day) => ({ date: day.date, minutes: day.wakeup_minutes ?? 0, time: day.wakeup_minutes !== null ? formatMinutesAfter(day.wakeup_minutes, wakeupRef) : "N/A", })); } /** * Transform bedtime minutes for Recharts LineChart * @param days - wakeup/bedtime data from API * @returns array formatted for LineChart */ export function computeBedtimeMinutes(days, bedtimeRef = "19:00") { return days.map((day) => ({ date: day.date, minutes: day.bedtime_minutes ?? 0, time: day.bedtime_minutes !== null ? formatMinutesAfter(day.bedtime_minutes, bedtimeRef) : "N/A", })); } /** * Transform daily summary for Recharts BarChart (vertical layout) * @param days - daily summary data from API * @returns array formatted for BarChart */ export function computeDailySummaryData(days) { return days.map((day) => ({ date: day.date, daySleep: day.day_minutes, nightSleep: day.night_minutes, total: day.total_minutes, })); } /** * Calculate average summary stats from daily data * @param days - daily summary data from API * @returns averages for total, day, and night sleep */ export function computeAverageStats(days) { if (!days.length) { return { avgTotal: 0, avgDay: 0, avgNight: 0, count: 0 }; } const totals = days.reduce((acc, day) => ({ total: acc.total + day.total_minutes, day: acc.day + day.day_minutes, night: acc.night + day.night_minutes, }), { total: 0, day: 0, night: 0 }); const count = days.length; return { avgTotal: Math.round(totals.total / count), avgDay: Math.round(totals.day / count), avgNight: Math.round(totals.night / count), count, }; } /** * Convert minutes after reference to clock time string * Internal helper - kept for chartHelpers module */ function formatMinutesAfter(minutes, reference) { const [refHours, refMinutes] = reference.split(":").map(Number); const totalRefMinutes = refHours * 60 + refMinutes; const targetMinutes = totalRefMinutes + minutes; const normalizedMinutes = ((targetMinutes % 1440) + 1440) % 1440; const hours = Math.floor(normalizedMinutes / 60); const mins = normalizedMinutes % 60; return `${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}`; } /** * Reverse date order (newest first for charts) * @param days - array of day data * @returns reversed array */ export function reverseDateOrder(days) { return [...days].reverse(); } /** * Filter days to only include those with data * @param days - array of day data * @returns filtered array */ export function filterDaysWithData(days) { return days.filter((day) => (day.total_minutes ?? 0) > 0); }