feat(ui): persist page size preference across all list pages

Add pageSize to useUiStore (persisted in localStorage). Update
PAGE_SIZES default to [20, 50, 100, 200]. All 12 paginated pages
now read initial value from store and sync changes back globally.
This commit is contained in:
viettranx committed 2026-04-16 14:17:48 +07:00
1 parent f8a6f9363f
commit 207bc56dbd
9 files changed
+44 -10

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ import {
SelectValue,
} from "@/components/ui/select";
const PAGE_SIZES = [10, 20, 50, 100];
const PAGE_SIZES = [20, 50, 100, 200];
interface PaginationProps {
page: number;
+7 -2
View File
@@ -1,4 +1,5 @@
import { useState, useMemo, useCallback } from "react";
import { useUiStore } from "@/stores/use-ui-store";
export interface PaginationState {
page: number;
@@ -23,8 +24,11 @@ export function usePagination<T>(
items: T[],
options: UsePaginationOptions = {},
): UsePaginationReturn<T> {
const globalPageSize = useUiStore((s) => s.pageSize);
const setGlobalPageSize = useUiStore((s) => s.setPageSize);
const [page, setPageRaw] = useState(1);
const [pageSize, setPageSizeRaw] = useState(options.defaultPageSize ?? 20);
const [pageSize, setPageSizeRaw] = useState(options.defaultPageSize ?? globalPageSize);
const total = items.length;
const totalPages = Math.max(1, Math.ceil(total / pageSize));
@@ -45,7 +49,8 @@ export function usePagination<T>(
const setPageSize = useCallback((size: number) => {
setPageSizeRaw(size);
setPageRaw(1);
}, []);
setGlobalPageSize(size);
}, [setGlobalPageSize]);
const resetPage = useCallback(() => setPageRaw(1), []);
+5 -1
View File
@@ -1,5 +1,6 @@
import { useState, useEffect } from "react";
import { ClipboardList, RefreshCw } from "lucide-react";
import { useUiStore } from "@/stores/use-ui-store";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
@@ -30,10 +31,13 @@ export function ActivityPage() {
const { logs, total, loading, load } = useActivity();
const spinning = useMinLoading(loading);
const showSkeleton = useDeferredLoading(loading && logs.length === 0);
const globalPageSize = useUiStore((s) => s.pageSize);
const setGlobalPageSize = useUiStore((s) => s.setPageSize);
const [actionFilter, setActionFilter] = useState("all");
const [entityFilter, setEntityFilter] = useState("all");
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
const [pageSize, setPageSizeRaw] = useState(globalPageSize);
const setPageSize = (size: number) => { setPageSizeRaw(size); setPage(1); setGlobalPageSize(size); };
const totalPages = Math.max(1, Math.ceil(total / pageSize));
+5 -1
View File
@@ -1,6 +1,7 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { ChevronDown, Contact, Info, Merge, RefreshCw, Search, Unlink } from "lucide-react";
import { useUiStore } from "@/stores/use-ui-store";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
@@ -28,12 +29,15 @@ export function ContactsPage() {
const { t } = useTranslation("contacts");
const { t: tc } = useTranslation("common");
const globalPageSize = useUiStore((s) => s.pageSize);
const setGlobalPageSize = useUiStore((s) => s.setPageSize);
const [search, setSearch] = useState("");
const [appliedSearch, setAppliedSearch] = useState("");
const [channelType, setChannelType] = useState("");
const [contactType, setContactType] = useState("");
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
const [pageSize, setPageSizeRaw] = useState(globalPageSize);
const setPageSize = (size: number) => { setPageSizeRaw(size); setPage(1); setGlobalPageSize(size); };
// Selection state
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
+5 -1
View File
@@ -1,6 +1,7 @@
import { useState, useMemo, lazy, Suspense } from "react";
import { useTranslation } from "react-i18next";
import { Plus, RefreshCw, Search, Database } from "lucide-react";
import { useUiStore } from "@/stores/use-ui-store";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
@@ -38,9 +39,12 @@ export function MemoryPage() {
const [deleteLoading, setDeleteLoading] = useState(false);
const [searchOpen, setSearchOpen] = useState(false);
const [indexAllLoading, setIndexAllLoading] = useState(false);
const globalPageSize = useUiStore((s) => s.pageSize);
const setGlobalPageSize = useUiStore((s) => s.setPageSize);
const [activeTab, setActiveTab] = useState("documents");
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
const [pageSize, setPageSizeRaw] = useState(globalPageSize);
const setPageSize = (size: number) => { setPageSizeRaw(size); setPage(1); setGlobalPageSize(size); };
const {
documents,
+5 -1
View File
@@ -9,6 +9,7 @@ import { Pagination } from "@/components/shared/pagination";
import { Badge } from "@/components/ui/badge";
import { TableSkeleton } from "@/components/shared/loading-skeleton";
import { useDeferredLoading } from "@/hooks/use-deferred-loading";
import { useUiStore } from "@/stores/use-ui-store";
import { useSessions } from "./hooks/use-sessions";
import { SessionDetailPage } from "./session-detail-page";
import { parseSessionKey } from "@/lib/session-key";
@@ -19,9 +20,12 @@ export function SessionsPage() {
const { t } = useTranslation("sessions");
const { key: detailKey } = useParams<{ key: string }>();
const navigate = useNavigate();
const globalPageSize = useUiStore((s) => s.pageSize);
const setGlobalPageSize = useUiStore((s) => s.setPageSize);
const [search, setSearch] = useState("");
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
const [pageSize, setPageSizeRaw] = useState(globalPageSize);
const setPageSize = (size: number) => { setPageSizeRaw(size); setPage(1); setGlobalPageSize(size); };
const { sessions, total, loading, preview, deleteSession, resetSession, patchSession } = useSessions({
limit: pageSize,
+4 -1
View File
@@ -62,11 +62,14 @@ export function TracesPage() {
const { t } = useTranslation("traces");
const { t: tc } = useTranslation("common");
const tz = useUiStore((s) => s.timezone);
const globalPageSize = useUiStore((s) => s.pageSize);
const setGlobalPageSize = useUiStore((s) => s.setPageSize);
const [agentFilter, setAgentFilter] = useState<string>();
const [channelFilter, setChannelFilter] = useState<string>();
const [selectedTraceId, setSelectedTraceId] = useState<string | null>(null);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(20);
const [pageSize, setPageSizeRaw] = useState(globalPageSize);
const setPageSize = (size: number) => { setPageSizeRaw(size); setPage(1); setGlobalPageSize(size); };
const ws = useWs();
const queryClient = useQueryClient();
+6 -2
View File
@@ -1,6 +1,7 @@
import { useState, useEffect, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { BarChart3, RefreshCw } from "lucide-react";
import { useUiStore } from "@/stores/use-ui-store";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { PageHeader } from "@/components/shared/page-header";
@@ -31,9 +32,12 @@ function AnalyticsDashboard() {
useUsageAnalytics(filters);
// Legacy records table state
const globalPageSize = useUiStore((s) => s.pageSize);
const setGlobalPageSize = useUiStore((s) => s.setPageSize);
const { records, total, loading: recLoading, loadRecords } = useUsage();
const [page, setPage] = useState(1);
const [pageSize] = useState(20);
const [pageSize, setPageSizeRaw] = useState(globalPageSize);
const setPageSize = (size: number) => { setPageSizeRaw(size); setPage(1); setGlobalPageSize(size); };
const totalPages = Math.max(1, Math.ceil(total / pageSize));
useEffect(() => {
@@ -176,7 +180,7 @@ function AnalyticsDashboard() {
total={total}
totalPages={totalPages}
onPageChange={setPage}
onPageSizeChange={() => {}}
onPageSizeChange={setPageSize}
/>
</div>
)}
+6
View File
@@ -11,6 +11,7 @@ interface UiState {
timezone: string; // IANA timezone or "auto"
sidebarCollapsed: boolean;
mobileSidebarOpen: boolean;
pageSize: number; // global pagination page size preference
setTheme: (theme: Theme) => void;
setLanguage: (language: Language) => void;
@@ -18,6 +19,7 @@ interface UiState {
toggleSidebar: () => void;
setSidebarCollapsed: (collapsed: boolean) => void;
setMobileSidebarOpen: (open: boolean) => void;
setPageSize: (size: number) => void;
}
export const useUiStore = create<UiState>()(
@@ -28,6 +30,7 @@ export const useUiStore = create<UiState>()(
timezone: "auto",
sidebarCollapsed: false,
mobileSidebarOpen: false,
pageSize: 20,
setTheme: (theme) => {
set({ theme });
@@ -51,6 +54,8 @@ export const useUiStore = create<UiState>()(
},
setMobileSidebarOpen: (open) => set({ mobileSidebarOpen: open }),
setPageSize: (size) => set({ pageSize: size }),
}),
{
name: "goclaw:ui", // localStorage key
@@ -60,6 +65,7 @@ export const useUiStore = create<UiState>()(
language: state.language,
timezone: state.timezone,
sidebarCollapsed: state.sidebarCollapsed,
pageSize: state.pageSize,
}),
}
)