Files
babymonitor/plan.md
T

14 KiB

Proposed Implementation Plan

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.

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.

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).

I will use multiple sub-agents to execute these tasks in parallel.


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

{
  "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<number | null>(null);
   const [editStart, setEditStart] = useState<string>('');
   const [editEnd, setEditEnd] = useState<string>('');
 
+  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 <div className="text-center p-4">Loading sessions...</div>;
   if (!sessions.length) return <div className="text-center p-4 text-gray-500">No sessions recorded.</div>;
 
@@ -55,7 +69,7 @@ export function SessionList() {
     <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">Sleep Sessions</h3>
       <div className="space-y-2">
-        {sessions.map((s) => (
+        {paginatedSessions.map((s) => (
           <div key={s.id} className="border-b border-gray-100 last:border-0 py-3 sm:py-2">
             {editingId === s.id ? (
               <div className="flex flex-col space-y-3 py-1">
@@ -140,6 +154,13 @@ export function SessionList() {
           </div>
         ))}
       </div>
+      {sessions.length > sessionsPerPage && (
+        <Pagination 
+          currentPage={clampedPage} 
+          totalPages={totalPages} 
+          onPageChange={setCurrentPage} 
+        />
+      )}
     </div>
   );
 }
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<HTMLDivElement>(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
           
           <div className="space-y-3">
             <div>
-<label htmlFor="day-start" className="block text-xs text-gray-500 mb-1">Day starts (HH:MM)</label>
-<input
-  id="day-start"
-  type="time"
-  value={localDayStart}
-  onChange={(e) => setLocalDayStart(e.target.value)}
-  className="w-full p-2 border rounded-md text-sm"
-/>
+              <label htmlFor="day-start" className="block text-xs text-gray-500 mb-1">Day starts (HH:MM)</label>
+              <input
+                id="day-start"
+                type="time"
+                value={localDayStart}
+                onChange={(e) => setLocalDayStart(e.target.value)}
+                className="w-full p-2 border rounded-md text-sm"
+              />
             </div>
             <div>
-<label htmlFor="day-end" className="block text-xs text-gray-500 mb-1">Day ends (HH:MM)</label>
-<input
-  id="day-end"
-  type="time"
-  value={localDayEnd}
-  onChange={(e) => setLocalDayEnd(e.target.value)}
-  className="w-full p-2 border rounded-md text-sm"
-/>
+              <label htmlFor="day-end" className="block text-xs text-gray-500 mb-1">Day ends (HH:MM)</label>
+              <input
+                id="day-end"
+                type="time"
+                value={localDayEnd}
+                onChange={(e) => setLocalDayEnd(e.target.value)}
+                className="w-full p-2 border rounded-md text-sm"
+              />
             </div>
             <div>
-<label htmlFor="wakeup-ref" className="block text-xs text-gray-500 mb-1">Wakeup ref (HH:MM)</label>
-<input
-  id="wakeup-ref"
-  type="time"
-  value={localWakeupRef}
-  onChange={(e) => setLocalWakeupRef(e.target.value)}
-  className="w-full p-2 border rounded-md text-sm"
-/>
+              <label htmlFor="wakeup-ref" className="block text-xs text-gray-500 mb-1">Wakeup ref (HH:MM)</label>
+              <input
+                id="wakeup-ref"
+                type="time"
+                value={localWakeupRef}
+                onChange={(e) => setLocalWakeupRef(e.target.value)}
+                className="w-full p-2 border rounded-md text-sm"
+              />
             </div>
             <div>
-<label htmlFor="bedtime-ref" className="block text-xs text-gray-500 mb-1">Bedtime ref (HH:MM)</label>
-<input
-  id="bedtime-ref"
-  type="time"
-  value={localBedtimeRef}
-  onChange={(e) => setLocalBedtimeRef(e.target.value)}
-  className="w-full p-2 border rounded-md text-sm"
-/>
+              <label htmlFor="bedtime-ref" className="block text-xs text-gray-500 mb-1">Bedtime ref (HH:MM)</label>
+              <input
+                id="bedtime-ref"
+                type="time"
+                value={localBedtimeRef}
+                onChange={(e) => setLocalBedtimeRef(e.target.value)}
+                className="w-full p-2 border rounded-md text-sm"
+              />
+            </div>
+            <div>
+              <label htmlFor="sessions-per-page" className="block text-xs text-gray-500 mb-1">Results per page</label>
+              <select
+                id="sessions-per-page"
+                value={localSessionsPerPage}
+                onChange={(e) => setLocalSessionsPerPage(Number(e.target.value))}
+                className="w-full p-2 border rounded-md text-sm"
+              >
+                <option value={5}>5</option>
+                <option value={10}>10</option>
+                <option value={25}>25</option>
+                <option value={50}>50</option>
+              </select>
             </div>
           </div>
 
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 = () => {
           <h2 className="text-3xl font-extrabold text-slate-900 tracking-tight">Statistics</h2>
           <p className="text-slate-500 text-sm mt-1">Overview of sleep patterns and trends</p>
         </div>
-        <SettingsPopover
-          dayStart={settings.dayStart}
-          dayEnd={settings.dayEnd}
-          wakeupRef={settings.wakeupRef}
-          bedtimeRef={settings.bedtimeRef}
-          onSave={(s) => {
-            update('dayStart', s.dayStart);
-            update('dayEnd', s.dayEnd);
-            update('wakeupRef', s.wakeupRef);
-            update('bedtimeRef', s.bedtimeRef);
-          }}
-        />
+         <SettingsPopover
+           dayStart={settings.dayStart}
+           dayEnd={settings.dayEnd}
+           wakeupRef={settings.wakeupRef}
+           bedtimeRef={settings.bedtimeRef}
+           sessionsPerPage={settings.sessionsPerPage}
+           onSave={(s) => {
+             update('dayStart', s.dayStart);
+             update('dayEnd', s.dayEnd);
+             update('wakeupRef', s.wakeupRef);
+             update('bedtimeRef', s.bedtimeRef);
+             update('sessionsPerPage', s.sessionsPerPage);
+           }}
+         />
       </div>
 
       <div className="bg-white p-6 rounded-[2rem] shadow-md border border-gray-100">
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.