From 8072dd5ce7427be3c0085f576d3d2ccce5a847a6 Mon Sep 17 00:00:00 2001 From: Clas Wen Date: Wed, 12 Nov 2025 21:45:28 +0800 Subject: [PATCH] feat: implement server-side pagination for repair admin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace client-side pagination (limit: 1000) with server-side pagination (limit: 10) - Add totalCount state to track pagination from API headers - Extract X-Total-Count from response headers for accurate page calculation - Pass status filter as array to API instead of comma-separated string - Add loading state during pagination changes - Add min-h-[600px] to prevent layout shifts during loading - Remove client-side filtering and slicing logic - Add useEffect to reload data on page/filter changes Performance improvements: - 99% reduction in data transfer (10 vs 1000 events per request) - Faster initial load and page transitions - Better scalability for growing datasets 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/pages/repair/RepairAdmin.tsx | 78 ++++++++------- src/types/saturday.d.ts | 167 +++++++++++++++++++++++++++++-- 2 files changed, 199 insertions(+), 46 deletions(-) diff --git a/src/pages/repair/RepairAdmin.tsx b/src/pages/repair/RepairAdmin.tsx index 4374ebe..56ea569 100644 --- a/src/pages/repair/RepairAdmin.tsx +++ b/src/pages/repair/RepairAdmin.tsx @@ -190,6 +190,7 @@ export default function App() { const [isLoading, setIsLoading] = useState(true) const [page, setPage] = useState(1) const rowsPerPage = 10 + const [totalCount, setTotalCount] = useState(0) const [statusFilter, setStatusFilter] = useState( UserEventStatus.filter(v => v.status !== EventStatus.cancelled).map(v => v.status), ) @@ -230,16 +231,25 @@ export default function App() { const list = useAsyncList({ async load() { - const { data } = await saturdayClient.GET("/events", { + setIsLoading(true) + const offset = (page - 1) * rowsPerPage + const { data, response } = await saturdayClient.GET("/events", { params: { query: { order: "DESC", - offset: 0, - limit: 1000, + offset: offset, + limit: rowsPerPage, + status: statusFilter.length > 0 ? statusFilter : null, }, }, }) + // Extract total count from response headers + const totalCountHeader = response.headers.get("X-Total-Count") + if (totalCountHeader) { + setTotalCount(parseInt(totalCountHeader, 10)) + } + setIsLoading(false) return { @@ -263,28 +273,24 @@ export default function App() { }, }) - const filteredList = useMemo(() => { - if (statusFilter.length > 0) { - return list.items.filter(item => statusFilter.includes(item.status)) - } - return list.items - }, [list, statusFilter]) - + // Items are now paginated and filtered by the server const items = useMemo(() => { - const start = (page - 1) * rowsPerPage - const end = start + rowsPerPage - - return filteredList.slice(start, end) - }, [filteredList, page, rowsPerPage]) + return list.items + }, [list.items]) const pages = useMemo(() => { - return Math.ceil(filteredList.length / rowsPerPage) - }, [filteredList, rowsPerPage]) + return Math.ceil(totalCount / rowsPerPage) + }, [totalCount, rowsPerPage]) useEffect(() => { setPage(1) + list.reload() }, [statusFilter]) + useEffect(() => { + list.reload() + }, [page]) + const columns: { key: string label: string @@ -454,24 +460,26 @@ export default function App() { - {isLoading - ? ( -
- -
- ) - : ( -
- {items.map(event => ( - - ))} - {items.length === 0 && ( -
- 暂无维修记录 -
- )} -
- )} +
+ {isLoading + ? ( +
+ +
+ ) + : ( +
+ {items.map(event => ( + + ))} + {items.length === 0 && ( +
+ 暂无维修记录 +
+ )} +
+ )} +
{/* Mobile Pagination */}
diff --git a/src/types/saturday.d.ts b/src/types/saturday.d.ts index 6292ea4..c831254 100644 --- a/src/types/saturday.d.ts +++ b/src/types/saturday.d.ts @@ -797,6 +797,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -842,6 +847,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -880,6 +890,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -918,6 +933,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -960,6 +980,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -992,6 +1017,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1025,6 +1055,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1055,7 +1090,7 @@ export interface operations { * @example 50 */ limit?: number - status?: string + status?: string[] | null order?: string } header?: never @@ -1067,6 +1102,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1087,16 +1127,6 @@ export interface operations { "export-events-xlsx": { parameters: { query: { - /** - * @description Offset - * @example 0 - */ - offset?: number - /** - * @description Limit - * @example 50 - */ - limit?: number status?: string order?: string start_time: string @@ -1143,6 +1173,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1181,6 +1216,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1219,6 +1259,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1251,6 +1296,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1287,6 +1337,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1323,6 +1378,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1359,6 +1419,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1404,6 +1469,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1442,6 +1512,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1480,6 +1555,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1518,6 +1598,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1560,6 +1645,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1602,6 +1692,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1633,6 +1728,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1673,6 +1773,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1709,6 +1814,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1752,6 +1862,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1787,6 +1902,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1829,6 +1949,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1871,6 +1996,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1912,6 +2042,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1951,6 +2086,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: { @@ -1980,6 +2120,11 @@ export interface operations { /** @description OK */ 200: { headers: { + "X-Limit"?: number | null + "X-Offset"?: number | null + "X-Page"?: number | null + "X-Total-Count"?: number | null + "X-Total-Pages"?: number | null [name: string]: unknown } content: {