import { fetchBooks, searchBooks, fetchLibraries, BookDto, LibraryDto, SeriesHitDto, getBookCoverUrl } from "@/lib/api"; import { BooksGrid, EmptyState } from "@/app/components/BookCard"; import { LiveSearchForm } from "@/app/components/LiveSearchForm"; import { Card, CardContent, OffsetPagination } from "@/app/components/ui"; import Link from "next/link"; import Image from "next/image"; import { getServerTranslations } from "@/lib/i18n/server"; import { paramString, paramStringOr, paramInt } from "@/lib/searchParams"; export const dynamic = "force-dynamic"; export default async function BooksPage({ searchParams }: { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }) { const { t } = await getServerTranslations(); const sp = await searchParams; const libraryId = paramString(sp, "library"); const searchQuery = paramStringOr(sp, "q", ""); const readingStatus = paramString(sp, "status"); const format = paramString(sp, "format"); const metadataProvider = paramString(sp, "metadata"); const sort = paramString(sp, "sort"); const page = paramInt(sp, "page", 1); const limit = paramInt(sp, "limit", 20); const [libraries] = await Promise.all([ fetchLibraries().catch(() => [] as LibraryDto[]) ]); let seriesHits: SeriesHitDto[] = []; const [booksPage, searchResponse] = await Promise.all([ fetchBooks(libraryId, undefined, page, limit, readingStatus, sort, undefined, format, metadataProvider, searchQuery || undefined).catch(() => ({ items: [] as BookDto[], total: 0, page: 1, limit, })), searchQuery ? searchBooks(searchQuery, libraryId, limit).catch(() => null) : Promise.resolve(null), ]); const books = booksPage.items; const total = booksPage.total; if (searchResponse) { seriesHits = searchResponse.series_hits ?? []; } const displayBooks = books.map(book => ({ ...book, coverUrl: getBookCoverUrl(book.id) })); const totalPages = Math.ceil(total / limit); const libraryOptions = [ { value: "", label: t("books.allLibraries") }, ...libraries.map((lib) => ({ value: lib.id, label: lib.name })), ]; const statusOptions = [ { value: "", label: t("common.all") }, { value: "unread", label: t("status.unread") }, { value: "reading", label: t("status.reading") }, { value: "read", label: t("status.read") }, ]; const formatOptions = [ { value: "", label: t("books.allFormats") }, { value: "cbz", label: "CBZ" }, { value: "cbr", label: "CBR" }, { value: "pdf", label: "PDF" }, { value: "epub", label: "EPUB" }, ]; const metadataOptions = [ { value: "", label: t("series.metadataAll") }, { value: "linked", label: t("series.metadataLinked") }, { value: "unlinked", label: t("series.metadataUnlinked") }, ]; const sortOptions = [ { value: "", label: t("books.sortTitle") }, { value: "latest", label: t("books.sortLatest") }, ]; const hasFilters = searchQuery || libraryId || readingStatus || format || metadataProvider || sort; return ( <>

{t("books.title")}

{/* Résultats */} {searchQuery ? (

{t("books.resultCountFor", { count: String(total), plural: total !== 1 ? "s" : "", query: searchQuery })}

) : (

{t("books.resultCount", { count: String(total), plural: total !== 1 ? "s" : "" })}

)} {/* Séries matchantes */} {seriesHits.length > 0 && (

{t("books.seriesHeading")}

{seriesHits.map((s) => (
{t("books.coverOf",

{s.name === "unclassified" ? t("books.unclassified") : s.name}

{t("books.bookCount", { count: String(s.book_count), plural: s.book_count !== 1 ? "s" : "" })}

))}
)} {/* Grille de livres */} {displayBooks.length > 0 ? ( <> {searchQuery &&

{t("books.title")}

} ) : ( )} ); }