From 96fb3cb988b7c827d8ccc9c5ab583a7702f996b8 Mon Sep 17 00:00:00 2001 From: Jacob Hagstedt Date: Wed, 3 Jun 2026 13:40:37 +0200 Subject: [PATCH] docs: update plan with implementation details --- plan.md | 378 +++++--------------------------------------------------- 1 file changed, 32 insertions(+), 346 deletions(-) diff --git a/plan.md b/plan.md index 520bf59..c049c00 100644 --- a/plan.md +++ b/plan.md @@ -1,351 +1,37 @@ -# Proposed Implementation Plan +# Plan: Add Median Statistic to Wakeup and Bedtime Plots -## Phase 1: Settings Update -* **`frontend/src/hooks/useSettings.ts`**: Add `sessionsPerPage` to the settings state and define a default value (e.g., 10). -* **`frontend/src/components/stats/SettingsPopover.tsx`**: - * Update the props and local state to include `sessionsPerPage`. - * Add a dropdown in the settings UI for selecting "Results per page" (e.g., 5, 10, 25, 50). -* **`frontend/src/components/stats/StatsPage.tsx`**: Update the `onSave` handler to persist the new `sessionsPerPage` setting. +## Overview +The goal is to add a fourth statistic, "Median", to the summary section below the Wakeup and Bedtime trend graphs in `WakeupPlot.tsx` and `BedtimePlot.tsx`. Currently, these components display "Earliest", "Latest", and "Average". -## Phase 2: Pagination UI and Logic -* **`frontend/src/components/stats/Pagination.tsx` (New Component)**: Implement a "Google-style" pagination component with `Previous`, page numbers, and `Next` buttons. -* **`frontend/src/components/stats/SessionList.tsx`**: - * Integrate `useSettings` to fetch the selected page size. - * Implement local pagination logic (managing `currentPage`). - * Slice the session data to show only the current page's items. - * Render the new `Pagination` component at the bottom of the list. +## Detailed Tasks -## Phase 3: Verification -* Verify that changing "Results per page" in Settings correctly updates the list. -* Confirm pagination navigation (Prev/Next/Numbers) works as expected and handles edge cases (e.g., first/last page). +### 1. Update `frontend/src/components/stats/WakeupPlot.tsx` +- **Sub-task 1.1: Update Statistics Calculation** + - Locate the `stats` `useMemo` block. + - Calculate the median of `validMinutes`. + - *Note*: To calculate the median, sort the array and pick the middle element (or average of two middle elements if even). + - Include `median` in the returned object: `{ min, max, avg, median }`. +- **Sub-task 1.2: Update UI Layout** + - Find the statistics container div (currently `grid grid-cols-3`). + - Change the Tailwind class from `grid-cols-3` to `grid-cols-4`. +- **Sub-task 1.3: Add Median Display** + - Add a new `div` for the "Median" statistic. + - Use identical styling to the existing three blocks. + - Display the value using `formatMinutesAfter(Math.round(stats.median), wakeup_ref)`. -I will use multiple sub-agents to execute these tasks in parallel. +### 2. Update `frontend/src/components/stats/BedtimePlot.tsx` +- **Sub-task 2.1: Update Statistics Calculation** + - Locate the `stats` `useMemo` block. + - Calculate the median of `validMinutes`. + - Include `median` in the returned object: `{ min, max, avg, median }`. +- **Sub-task 2.2: Update UI Layout** + - Find the statistics container div (currently `grid grid-cols-3`). + - Change the Tailwind class from `grid-cols-3` to `grid-cols-4`. +- **Sub-task 2.3: Add Median Display** + - Add a new `div` for the "Median" statistic. + - Use identical styling to the existing three blocks. + - Display the value using `formatMinutesAfter(Math.round(stats.median), bedtime_ref)`. ---- - -The user wants to implement client-side pagination for "Sleep Sessions". -Plan: -1. Update `useSettings` to include `sessionsPerPage`. -2. Update `SettingsPopover` to allow users to change `sessionsPerPage`. -3. Update `StatsPage`'s `onSave` in `SettingsPopover` to include the new setting. -4. Create a new `Pagination` component. -5. Update `SessionList` to use pagination logic and the new component. -6. Verify with Playwright and screenshots. - -I'll start by using `todowrite` to track progress. -Then I will launch sub-agents. - -Sub-agents: -1. `explore` (or `general`) for Phase 1 (Settings). -2. `general` for Phase 2 (Pagination component and SessionList logic). -3. `general` for Phase 3 (Playwright verification). - -Wait, the user said "use as many sub-agents as possible". I'll split it. - -Task 1: Update settings (useSettings, SettingsPopover, StatsPage). -Task 2: Implement Pagination component and integrate it into SessionList. -Task 3: Write Playwright tests to verify functionality and take screenshots. - -Let's start with `todowrite`. - -## TODOS - -```json -{ - "todos": [ - { - "content": "Update useSettings to include sessionsPerPage", - "priority": "high", - "status": "pending" - }, - { - "content": "Update SettingsPopover to allow changing sessionsPerPage", - "priority": "high", - "status": "pending" - }, - { - "content": "Update StatsPage to persist sessionsPerPage in onSave", - "priority": "high", - "status": "pending" - }, - { - "content": "Create Pagination component in frontend/src/components/stats/Pagination.tsx", - "priority": "high", - "status": "pending" - }, - { - "content": "Update SessionList to implement client-side pagination logic and use Pagination component", - "priority": "high", - "status": "pending" - }, - { - "content": "Write Playwright tests to verify pagination and take screenshots", - "priority": "medium", - "status": "pending" - } - ] -} -``` - -## Current progress (git diff) - -``` -diff --git a/frontend/src/components/stats/SessionList.tsx b/frontend/src/components/stats/SessionList.tsx -index 374e41c..2bd6ea7 100644 ---- a/frontend/src/components/stats/SessionList.tsx -+++ b/frontend/src/components/stats/SessionList.tsx -@@ -1,15 +1,29 @@ - import React, { useState } from 'react'; - import { useSessions } from '../../hooks/useSleepSession'; -+import { useSettings } from '../../hooks/useSettings'; -+import { Pagination } from './Pagination'; - import { Edit2, Check, X, Trash2 } from 'lucide-react'; - import type { SleepSessionRead } from '../../types'; - import { formatDateTime, toLocalDatetimeInputValue, getDurationMinutes, formatMinutes } from '../../utils/time'; - - export function SessionList() { - const { sessions, isLoading, updateSession, deleteSession, isDeleting } = useSessions(); -+ const { settings } = useSettings(); -+ const sessionsPerPage = settings.sessionsPerPage; -+ -+ const [currentPage, setCurrentPage] = useState(1); - const [editingId, setEditingId] = useState(null); - const [editStart, setEditStart] = useState(''); - const [editEnd, setEditEnd] = useState(''); - -+ const totalPages = Math.ceil(sessions.length / sessionsPerPage); -+ const clampedPage = Math.max(1, Math.min(currentPage, totalPages || 1)); -+ -+ const paginatedSessions = sessions.slice( -+ (clampedPage - 1) * sessionsPerPage, -+ clampedPage * sessionsPerPage -+ ); -+ - if (isLoading) return
Loading sessions...
; - if (!sessions.length) return
No sessions recorded.
; - -@@ -55,7 +69,7 @@ export function SessionList() { -
-

Sleep Sessions

-
-- {sessions.map((s) => ( -+ {paginatedSessions.map((s) => ( -
- {editingId === s.id ? ( -
-@@ -140,6 +154,13 @@ export function SessionList() { -
- ))} -
-+ {sessions.length > sessionsPerPage && ( -+ -+ )} -
- ); - } -diff --git a/frontend/src/components/stats/SettingsPopover.tsx b/frontend/src/components/stats/SettingsPopover.tsx -index 005a302..8031392 100644 ---- a/frontend/src/components/stats/SettingsPopover.tsx -+++ b/frontend/src/components/stats/SettingsPopover.tsx -@@ -6,15 +6,17 @@ interface SettingsPopoverProps { - dayEnd: string; - wakeupRef: string; - bedtimeRef: string; -- onSave: (settings: { dayStart: string; dayEnd: string; wakeupRef: string; bedtimeRef: string }) => void; -+ sessionsPerPage: number; -+ onSave: (settings: { dayStart: string; dayEnd: string; wakeupRef: string; bedtimeRef: string; sessionsPerPage: number }) => void; - } - --export function SettingsPopover({ dayStart, dayEnd, wakeupRef, bedtimeRef, onSave }: SettingsPopoverProps) { -+export function SettingsPopover({ dayStart, dayEnd, wakeupRef, bedtimeRef, sessionsPerPage, onSave }: SettingsPopoverProps) { - const [isOpen, setIsOpen] = useState(false); - const [localDayStart, setLocalDayStart] = useState(dayStart); - const [localDayEnd, setLocalDayEnd] = useState(dayEnd); - const [localWakeupRef, setLocalWakeupRef] = useState(wakeupRef); - const [localBedtimeRef, setLocalBedtimeRef] = useState(bedtimeRef); -+ const [localSessionsPerPage, setLocalSessionsPerPage] = useState(sessionsPerPage); - const popoverRef = useRef(null); - - useEffect(() => { -@@ -22,7 +24,8 @@ export function SettingsPopover({ dayStart, dayEnd, wakeupRef, bedtimeRef, onSav - setLocalDayEnd(dayEnd); - setLocalWakeupRef(wakeupRef); - setLocalBedtimeRef(bedtimeRef); -- }, [dayStart, dayEnd, wakeupRef, bedtimeRef]); -+ setLocalSessionsPerPage(sessionsPerPage); -+ }, [dayStart, dayEnd, wakeupRef, bedtimeRef, sessionsPerPage]); - - useEffect(() => { - const handleClickOutside = (e: MouseEvent) => { -@@ -42,6 +45,7 @@ export function SettingsPopover({ dayStart, dayEnd, wakeupRef, bedtimeRef, onSav - dayEnd: localDayEnd, - wakeupRef: localWakeupRef, - bedtimeRef: localBedtimeRef, -+ sessionsPerPage: localSessionsPerPage, - }); - setIsOpen(false); - }; -@@ -65,44 +69,58 @@ export function SettingsPopover({ dayStart, dayEnd, wakeupRef, bedtimeRef, onSav - -
-
-- -- setLocalDayStart(e.target.value)} -- className="w-full p-2 border rounded-md text-sm" --/> -+ -+ setLocalDayStart(e.target.value)} -+ className="w-full p-2 border rounded-md text-sm" -+ /> -
-
-- -- setLocalDayEnd(e.target.value)} -- className="w-full p-2 border rounded-md text-sm" --/> -+ -+ setLocalDayEnd(e.target.value)} -+ className="w-full p-2 border rounded-md text-sm" -+ /> -
-
-- -- setLocalWakeupRef(e.target.value)} -- className="w-full p-2 border rounded-md text-sm" --/> -+ -+ setLocalWakeupRef(e.target.value)} -+ className="w-full p-2 border rounded-md text-sm" -+ /> -
-
-- -- setLocalBedtimeRef(e.target.value)} -- className="w-full p-2 border rounded-md text-sm" --/> -+ -+ setLocalBedtimeRef(e.target.value)} -+ className="w-full p-2 border rounded-md text-sm" -+ /> -+
-+
-+ -+ -
-
- -diff --git a/frontend/src/components/stats/StatsPage.tsx b/frontend/src/components/stats/StatsPage.tsx -index 2516d55..e3518c8 100644 ---- a/frontend/src/components/stats/StatsPage.tsx -+++ b/frontend/src/components/stats/StatsPage.tsx -@@ -27,18 +27,20 @@ const StatsPage: React.FC = () => { -

Statistics

-

Overview of sleep patterns and trends

-
-- { -- update('dayStart', s.dayStart); -- update('dayEnd', s.dayEnd); -- update('wakeupRef', s.wakeupRef); -- update('bedtimeRef', s.bedtimeRef); -- }} -- /> -+ { -+ update('dayStart', s.dayStart); -+ update('dayEnd', s.dayEnd); -+ update('wakeupRef', s.wakeupRef); -+ update('bedtimeRef', s.bedtimeRef); -+ update('sessionsPerPage', s.sessionsPerPage); -+ }} -+ /> - - -
-diff --git a/frontend/src/hooks/useSettings.ts b/frontend/src/hooks/useSettings.ts -index c587fb3..d95899b 100644 ---- a/frontend/src/hooks/useSettings.ts -+++ b/frontend/src/hooks/useSettings.ts -@@ -8,6 +8,7 @@ interface Settings { - wakeupRef: string; - bedtimeRef: string; - statsInterval: number; -+ sessionsPerPage: number; - } - - const DEFAULT_SETTINGS: Settings = { -@@ -16,6 +17,7 @@ const DEFAULT_SETTINGS: Settings = { - wakeupRef: '04:00', - bedtimeRef: '19:00', - statsInterval: 7, -+ sessionsPerPage: 10, - }; - - export function useSettings() { - -``` - -## Last command - -The last command you ran was this: - -``` -npx playwright test frontend/tests/e2e/pagination.spec.ts --reporter=list -``` - -so please run this again and see the output, as this might help us know where to pick things up. +## Verification Plan +1. **Visual Inspection**: Ensure the four statistics are evenly spaced and aligned in a single row below each graph. +2. **Logic Check**: Manually verify with a small set of known numbers (e.g., [10, 20, 30] -> median 20; [10, 20, 30, 40] -> median 25) that the calculation is correct.