Skip to content

Commit 6fb5211

Browse files
committed
feat(pagination): persist page size + add 200/500 options
Add usePersistedPageSize hook (localStorage-backed, allowlist-validated) and apply it across the paginated tables: Accounts, OperationsErrors, PromptFilter logs. Usage logs were already updated in the previous commit's Usage.tsx changes. The default options list now includes 200 and 500 alongside the existing 10/20/50/100, addressing the need to scan more rows at a time. Per-page choice now survives reloads and reopen (keyed per page name, e.g. codex2api_page_size_accounts). SchedulerBoard (card grid, base size 12) and PromptFilter's built-in rule table (fixed small set) are intentionally untouched. Closes #147
1 parent 71219c1 commit 6fb5211

4 files changed

Lines changed: 80 additions & 6 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { useCallback, useEffect, useState } from 'react'
2+
3+
const STORAGE_PREFIX = 'codex2api_page_size_'
4+
5+
/**
6+
* usePersistedPageSize 提供按 key 持久化的分页大小状态。
7+
*
8+
* - 初值优先使用 localStorage 中保存的值;若不在 allowed 中或非法则回落 fallback。
9+
* - 调用 setPageSize 时同步写回 localStorage。
10+
*
11+
* @param key 页面/表格的标识(写入 localStorage 时会加 codex2api_page_size_ 前缀)
12+
* @param fallback 默认值
13+
* @param allowed 合法 pageSize 集合;不在其中的持久化值会被忽略
14+
*/
15+
export function usePersistedPageSize(
16+
key: string,
17+
fallback: number,
18+
allowed: number[],
19+
): [number, (next: number) => void] {
20+
const storageKey = `${STORAGE_PREFIX}${key}`
21+
22+
const readInitial = (): number => {
23+
if (typeof window === 'undefined') return fallback
24+
try {
25+
const raw = window.localStorage.getItem(storageKey)
26+
if (!raw) return fallback
27+
const parsed = Number.parseInt(raw, 10)
28+
if (Number.isFinite(parsed) && allowed.includes(parsed)) {
29+
return parsed
30+
}
31+
} catch {
32+
/* localStorage 不可用时静默回落 */
33+
}
34+
return fallback
35+
}
36+
37+
const [pageSize, setPageSizeState] = useState<number>(readInitial)
38+
39+
// allowed 变化时,如果当前值不再合法则回落到 fallback
40+
useEffect(() => {
41+
if (!allowed.includes(pageSize)) {
42+
setPageSizeState(fallback)
43+
}
44+
// eslint-disable-next-line react-hooks/exhaustive-deps
45+
}, [allowed.join(',')])
46+
47+
const setPageSize = useCallback(
48+
(next: number) => {
49+
if (!allowed.includes(next)) return
50+
setPageSizeState(next)
51+
try {
52+
window.localStorage.setItem(storageKey, String(next))
53+
} catch {
54+
/* 静默 */
55+
}
56+
},
57+
[storageKey, allowed],
58+
)
59+
60+
return [pageSize, setPageSize]
61+
}
62+
63+
/** 全站统一的可选分页大小,可在调用方按需裁剪。 */
64+
export const DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 50, 100, 200, 500]

frontend/src/pages/Accounts.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import StatusBadge from "../components/StatusBadge";
99
import ToastNotice from "../components/ToastNotice";
1010
import { useDataLoader, type LoadOptions } from "../hooks/useDataLoader";
1111
import { useConfirmDialog } from "../hooks/useConfirmDialog";
12+
import {
13+
DEFAULT_PAGE_SIZE_OPTIONS,
14+
usePersistedPageSize,
15+
} from "../hooks/usePersistedPageSize";
1216
import { useToast } from "../hooks/useToast";
1317
import type {
1418
AccountRow,
@@ -249,10 +253,14 @@ async function runAccountBatch(
249253

250254
export default function Accounts() {
251255
const { t } = useTranslation();
252-
const pageSizeOptions = [10, 20, 50, 100];
256+
const pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS;
253257
const [showAdd, setShowAdd] = useState(false);
254258
const [page, setPage] = useState(1);
255-
const [pageSize, setPageSize] = useState(20);
259+
const [pageSize, setPageSize] = usePersistedPageSize(
260+
"accounts",
261+
20,
262+
pageSizeOptions,
263+
);
256264
const [statusFilter, setStatusFilter] = useState<
257265
| "all"
258266
| "normal"

frontend/src/pages/OperationsErrors.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import StateShell from '../components/StateShell'
2121
import ToastNotice from '../components/ToastNotice'
2222
import { useDataLoader } from '../hooks/useDataLoader'
2323
import { useToast } from '../hooks/useToast'
24+
import { DEFAULT_PAGE_SIZE_OPTIONS, usePersistedPageSize } from '../hooks/usePersistedPageSize'
2425
import { getTimeRangeISO, type TimeRangeKey } from '../lib/timeRange'
2526
import { formatCompactEmail } from '../lib/utils'
2627
import { formatBeijingTime } from '../utils/time'
@@ -47,7 +48,7 @@ import {
4748
} from '@/components/ui/table'
4849

4950
const ERROR_TIME_RANGES: TimeRangeKey[] = ['1h', '6h', '24h', '7d', '30d']
50-
const pageSizeOptions = [10, 20, 50, 100]
51+
const pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS
5152

5253
const errorTableHeadClass = 'text-[12px] font-semibold'
5354
const errorTableTextClass = 'text-[14px]'
@@ -58,7 +59,7 @@ export default function OperationsErrors() {
5859
const { toast, showToast } = useToast()
5960
const [timeRange, setTimeRange] = useState<TimeRangeKey>('1h')
6061
const [page, setPage] = useState(1)
61-
const [pageSize, setPageSize] = useState(20)
62+
const [pageSize, setPageSize] = usePersistedPageSize('ops_errors', 20, pageSizeOptions)
6263
const [statusFilter, setStatusFilter] = useState('')
6364
const [errorKindFilter, setErrorKindFilter] = useState('')
6465
const [endpointFilter, setEndpointFilter] = useState('')

frontend/src/pages/PromptFilter.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import PageHeader from '../components/PageHeader'
88
import Pagination from '../components/Pagination'
99
import StateShell from '../components/StateShell'
1010
import ToastNotice from '../components/ToastNotice'
11+
import { DEFAULT_PAGE_SIZE_OPTIONS, usePersistedPageSize } from '../hooks/usePersistedPageSize'
1112
import { useDataLoader } from '../hooks/useDataLoader'
1213
import { useToast } from '../hooks/useToast'
1314
import { formatBeijingTime, formatRelativeTime } from '../utils/time'
@@ -525,7 +526,7 @@ function LogsView({ clearLogs, clearing }: { clearLogs: () => Promise<void>; cle
525526
const [draftFilters, setDraftFilters] = useState<LogFilters>(emptyFilters)
526527
const [filters, setFilters] = useState<LogFilters>(emptyFilters)
527528
const [page, setPage] = useState(1)
528-
const [pageSize, setPageSize] = useState(20)
529+
const [pageSize, setPageSize] = usePersistedPageSize('prompt_filter_logs', 20, DEFAULT_PAGE_SIZE_OPTIONS)
529530
const [logs, setLogs] = useState<PromptFilterLog[]>([])
530531
const [total, setTotal] = useState(0)
531532
const [loading, setLoading] = useState(false)
@@ -623,7 +624,7 @@ function LogsView({ clearLogs, clearing }: { clearLogs: () => Promise<void>; cle
623624

624625
<StateShell loading={loading} error={error} isEmpty={!loading && logs.length === 0} onRetry={() => void loadLogs()} emptyTitle={t('promptFilter.noLogs')}>
625626
<PromptFilterLogsTable logs={logs} />
626-
<Pagination page={page} totalPages={totalPages} totalItems={total} pageSize={pageSize} onPageChange={setPage} onPageSizeChange={(next) => { setPage(1); setPageSize(next) }} pageSizeOptions={[10, 20, 50, 100]} />
627+
<Pagination page={page} totalPages={totalPages} totalItems={total} pageSize={pageSize} onPageChange={setPage} onPageSizeChange={(next) => { setPage(1); setPageSize(next) }} pageSizeOptions={DEFAULT_PAGE_SIZE_OPTIONS} />
627628
</StateShell>
628629
</CardContent>
629630
</Card>

0 commit comments

Comments
 (0)