feat(ui): Add pagination to books pages and improve spacing

- Added CursorPagination component with page size selector (20/50/100)
- Updated /books page with pagination support
- Updated /libraries/[id]/books with pagination
- Improved layout margins (added pb-16 and responsive px)
- Series page uses improved layout spacing
This commit is contained in:
2026-03-06 14:50:27 +01:00
parent c421f427b0
commit fa574586ed
9 changed files with 368 additions and 24 deletions

View File

@@ -1,6 +1,6 @@
import { fetchBooks, searchBooks, fetchLibraries, BookDto, LibraryDto, getBookCoverUrl } from "../../lib/api";
import { BooksGrid, EmptyState } from "../components/BookCard";
import { Card, Button, FormField, FormInput, FormSelect, FormRow } from "../components/ui";
import { Card, Button, FormField, FormInput, FormSelect, FormRow, CursorPagination } from "../components/ui";
import Link from "next/link";
export const dynamic = "force-dynamic";
@@ -13,6 +13,8 @@ export default async function BooksPage({
const searchParamsAwaited = await searchParams;
const libraryId = typeof searchParamsAwaited.library === "string" ? searchParamsAwaited.library : undefined;
const searchQuery = typeof searchParamsAwaited.q === "string" ? searchParamsAwaited.q : "";
const cursor = typeof searchParamsAwaited.cursor === "string" ? searchParamsAwaited.cursor : undefined;
const limit = typeof searchParamsAwaited.limit === "string" ? parseInt(searchParamsAwaited.limit) : 20;
const [libraries] = await Promise.all([
fetchLibraries().catch(() => [] as LibraryDto[])
@@ -25,7 +27,7 @@ export default async function BooksPage({
if (searchQuery) {
// Mode recherche
const searchResponse = await searchBooks(searchQuery, libraryId).catch(() => null);
const searchResponse = await searchBooks(searchQuery, libraryId, limit).catch(() => null);
if (searchResponse) {
searchResults = searchResponse.hits.map(hit => ({
id: hit.id,
@@ -45,10 +47,15 @@ export default async function BooksPage({
totalHits = searchResponse.estimated_total_hits;
}
} else {
// Mode liste
const booksPage = await fetchBooks(libraryId).catch(() => ({ items: [] as BookDto[], next_cursor: null }));
// Mode liste avec pagination
const booksPage = await fetchBooks(libraryId, undefined, cursor, limit).catch(() => ({
items: [] as BookDto[],
next_cursor: null,
prev_cursor: null
}));
books = booksPage.items;
nextCursor = booksPage.next_cursor;
// Note: L'API ne supporte pas encore prev_cursor, on gère ça côté UI
}
const displayBooks = (searchResults || books).map(book => ({
@@ -56,6 +63,9 @@ export default async function BooksPage({
coverUrl: getBookCoverUrl(book.id)
}));
const hasNextPage = !!nextCursor;
const hasPrevPage = !!cursor; // Si on a un cursor, on peut revenir en arrière (simplifié)
return (
<>
<h1 className="text-3xl font-bold text-foreground mb-6 flex items-center gap-3">
@@ -110,19 +120,14 @@ export default async function BooksPage({
<BooksGrid books={displayBooks} />
{/* Pagination */}
{!searchQuery && nextCursor && (
<div className="flex justify-center mt-8">
<form>
<input type="hidden" name="library" value={libraryId || ""} />
<input type="hidden" name="cursor" value={nextCursor} />
<button
type="submit"
className="px-6 py-3 bg-primary text-white font-medium rounded-lg hover:bg-primary/90 transition-colors"
>
📥 Load more
</button>
</form>
</div>
{!searchQuery && (
<CursorPagination
hasNextPage={hasNextPage}
hasPrevPage={hasPrevPage}
pageSize={limit}
currentCount={displayBooks.length}
nextCursor={nextCursor}
/>
)}
</>
) : (