352 lines
14 KiB
Markdown
352 lines
14 KiB
Markdown
# 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
|
|
|
|
```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<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.
|