feat: pagination
This commit is contained in:
parent
3f4a757bb1
commit
cada748f2d
3 changed files with 99 additions and 2 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 && <p className="text-xl text-red-400 font-semibold text-center mt-10">Error: {error}</p>}</>
|
||||
)}
|
||||
|
||||
{data && <Pagination lastPage={data.lastPage} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
92
src/app/components/mii-list/pagination.tsx
Normal file
92
src/app/components/mii-list/pagination.tsx
Normal file
|
|
@ -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 (
|
||||
<div className="flex justify-center items-center w-full mt-8">
|
||||
{/* Go to first page */}
|
||||
<Link
|
||||
href={page === 1 ? "#" : "/?page=1"}
|
||||
aria-disabled={page === 1}
|
||||
tabIndex={page === 1 ? -1 : undefined}
|
||||
className={`pill button !bg-orange-100 !p-0.5 aspect-square text-2xl ${
|
||||
page === 1 ? "pointer-events-none opacity-50" : "hover:!bg-orange-400"
|
||||
}`}
|
||||
>
|
||||
<Icon icon="stash:chevron-double-left" />
|
||||
</Link>
|
||||
|
||||
{/* Previous page */}
|
||||
<Link
|
||||
href={page === 1 ? "#" : `/?page=${page - 1}`}
|
||||
aria-disabled={page === 1}
|
||||
tabIndex={page === 1 ? -1 : undefined}
|
||||
className={`pill !bg-orange-100 !p-0.5 aspect-square text-2xl ${page === 1 ? "pointer-events-none opacity-50" : "hover:!bg-orange-400"}`}
|
||||
>
|
||||
<Icon icon="stash:chevron-left" />
|
||||
</Link>
|
||||
|
||||
{/* Page numbers */}
|
||||
<div className="flex mx-2">
|
||||
{numbers.map((number) => (
|
||||
<Link
|
||||
key={number}
|
||||
href={`/?page=${number}`}
|
||||
aria-current={number === page ? "page" : undefined}
|
||||
className={`pill !p-0 w-8 h-8 text-center !rounded-md ${number == page ? "!bg-orange-400" : "!bg-orange-100 hover:!bg-orange-400"}`}
|
||||
>
|
||||
{number}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Next page */}
|
||||
<Link
|
||||
href={page === lastPage ? "#" : `/?page=${page + 1}`}
|
||||
aria-disabled={page === lastPage}
|
||||
tabIndex={page === lastPage ? -1 : undefined}
|
||||
className={`pill button !bg-orange-100 !p-0.5 aspect-square text-2xl ${
|
||||
page === lastPage ? "pointer-events-none opacity-50" : "hover:!bg-orange-400"
|
||||
}`}
|
||||
>
|
||||
<Icon icon="stash:chevron-right" />
|
||||
</Link>
|
||||
|
||||
{/* Go to last page */}
|
||||
<Link
|
||||
href={page === lastPage ? "#" : `/?page=${lastPage}`}
|
||||
aria-disabled={page === lastPage}
|
||||
tabIndex={page === lastPage ? -1 : undefined}
|
||||
className={`pill button !bg-orange-100 !p-0.5 aspect-square text-2xl ${
|
||||
page === lastPage ? "pointer-events-none opacity-50" : "hover:!bg-orange-400"
|
||||
}`}
|
||||
>
|
||||
<Icon icon="stash:chevron-double-right" />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue