From cada748f2d5bcd088c3572de75b814d61d7c314d Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Sun, 13 Apr 2025 20:00:33 +0100 Subject: [PATCH] feat: pagination --- src/app/api/mii/list/route.ts | 5 +- src/app/components/mii-list/index.tsx | 4 + src/app/components/mii-list/pagination.tsx | 92 ++++++++++++++++++++++ 3 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/app/components/mii-list/pagination.tsx diff --git a/src/app/api/mii/list/route.ts b/src/app/api/mii/list/route.ts index cbe477f..3b044c5 100644 --- a/src/app/api/mii/list/route.ts +++ b/src/app/api/mii/list/route.ts @@ -45,7 +45,7 @@ export async function GET(request: NextRequest) { const parsed = searchSchema.safeParse(Object.fromEntries(request.nextUrl.searchParams)); if (!parsed.success) return NextResponse.json({ error: parsed.error.errors[0].message }, { status: 400 }); - const { q: query, sort, tags, userId, page = 1, limit = 20 } = parsed.data; + const { q: query, sort, tags, userId, page = 1, limit = 24 } = parsed.data; const where: Prisma.MiiWhereInput = { // Searching @@ -92,7 +92,7 @@ export async function GET(request: NextRequest) { const skip = (page - 1) * limit; const [totalCount, filteredCount, list] = await Promise.all([ - prisma.mii.count({ where: userId ? { userId } : {} }), + prisma.mii.count({ where: { ...where, userId } }), prisma.mii.count({ where, skip, take: limit }), prisma.mii.findMany({ where, orderBy, select, skip: (page - 1) * limit, take: limit }), ]); @@ -100,6 +100,7 @@ export async function GET(request: NextRequest) { return NextResponse.json({ total: totalCount, filtered: filteredCount, + lastPage: Math.ceil(totalCount / limit), miis: list.map(({ _count, likedBy, ...rest }) => ({ ...rest, likes: _count.likedBy, diff --git a/src/app/components/mii-list/index.tsx b/src/app/components/mii-list/index.tsx index 13e8154..4fde6d1 100644 --- a/src/app/components/mii-list/index.tsx +++ b/src/app/components/mii-list/index.tsx @@ -8,6 +8,7 @@ import SortSelect from "./sort-select"; import Carousel from "../carousel"; import LikeButton from "../like-button"; import FilterSelect from "./filter-select"; +import Pagination from "./pagination"; interface Props { isLoggedIn: boolean; @@ -18,6 +19,7 @@ interface Props { interface ApiResponse { total: number; filtered: number; + lastPage: number; miis: { id: number; user?: { @@ -131,6 +133,8 @@ export default function MiiList({ isLoggedIn, userId }: Props) { ) : ( <>{error &&

Error: {error}

} )} + + {data && } ); } diff --git a/src/app/components/mii-list/pagination.tsx b/src/app/components/mii-list/pagination.tsx new file mode 100644 index 0000000..e0793a9 --- /dev/null +++ b/src/app/components/mii-list/pagination.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { redirect, useSearchParams } from "next/navigation"; +import Link from "next/link"; + +import { useEffect, useMemo, useState } from "react"; +import { Icon } from "@iconify/react"; + +interface Props { + lastPage: number; +} + +export default function Pagination({ lastPage }: Props) { + const searchParams = useSearchParams(); + const page = Number(searchParams.get("page") ?? 1); + + const numbers = useMemo(() => { + const result = []; + + // Always show 5 pages, centering around the current page when possible + const start = Math.max(1, Math.min(page - 2, lastPage - 4)); + const end = Math.min(lastPage, start + 4); + + for (let i = start; i <= end; i++) result.push(i); + + return result; + }, [page, lastPage]); + + return ( +
+ {/* Go to first page */} + + + + + {/* Previous page */} + + + + + {/* Page numbers */} +
+ {numbers.map((number) => ( + + {number} + + ))} +
+ + {/* Next page */} + + + + + {/* Go to last page */} + + + +
+ ); +}