Merge pull request #122 from wen-templari/feat/server-side-pagination

feat: implement server-side pagination for repair admin
This commit is contained in:
clas 2025-11-12 21:46:45 +08:00 committed by GitHub
commit f767fce6ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 199 additions and 46 deletions

View file

@ -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<string[]>(
UserEventStatus.filter(v => v.status !== EventStatus.cancelled).map(v => v.status),
)
@ -230,16 +231,25 @@ export default function App() {
const list = useAsyncList<PublicEvent>({
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,6 +460,7 @@ export default function App() {
<CheckboxPopover value={statusFilter} onValueChange={setStatusFilter} />
</div>
<div className="min-h-[600px]">
{isLoading
? (
<div className="flex justify-center py-8">
@ -472,6 +479,7 @@ export default function App() {
)}
</div>
)}
</div>
{/* Mobile Pagination */}
<div className="mt-6 flex justify-center">

View file

@ -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: {