import { fetchLibraries, fetchBooks, fetchSeriesMetadata, getBookCoverUrl, getMetadataLink, getMissingBooks, BookDto, SeriesMetadataDto, ExternalMetadataLinkDto, MissingBooksDto } from "../../../../../lib/api"; import { BooksGrid, EmptyState } from "../../../../components/BookCard"; import { MarkSeriesReadButton } from "../../../../components/MarkSeriesReadButton"; import { MarkBookReadButton } from "../../../../components/MarkBookReadButton"; import nextDynamic from "next/dynamic"; import { OffsetPagination } from "../../../../components/ui"; import { SafeHtml } from "../../../../components/SafeHtml"; import Image from "next/image"; import Link from "next/link"; const EditSeriesForm = nextDynamic( () => import("../../../../components/EditSeriesForm").then(m => m.EditSeriesForm) ); const MetadataSearchModal = nextDynamic( () => import("../../../../components/MetadataSearchModal").then(m => m.MetadataSearchModal) ); const ProwlarrSearchModal = nextDynamic( () => import("../../../../components/ProwlarrSearchModal").then(m => m.ProwlarrSearchModal) ); import { notFound } from "next/navigation"; import { getServerTranslations } from "../../../../../lib/i18n/server"; export const dynamic = "force-dynamic"; export default async function SeriesDetailPage({ params, searchParams, }: { params: Promise<{ id: string; name: string }>; searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }) { const { id, name } = await params; const { t } = await getServerTranslations(); const searchParamsAwaited = await searchParams; const page = typeof searchParamsAwaited.page === "string" ? parseInt(searchParamsAwaited.page) : 1; const limit = typeof searchParamsAwaited.limit === "string" ? parseInt(searchParamsAwaited.limit) : 50; const seriesName = decodeURIComponent(name); const [library, booksPage, seriesMeta, metadataLinks] = await Promise.all([ fetchLibraries().then((libs) => libs.find((l) => l.id === id)), fetchBooks(id, seriesName, page, limit).catch(() => ({ items: [] as BookDto[], total: 0, page: 1, limit, })), fetchSeriesMetadata(id, seriesName).catch(() => null as SeriesMetadataDto | null), getMetadataLink(id, seriesName).catch(() => [] as ExternalMetadataLinkDto[]), ]); const existingLink = metadataLinks.find((l) => l.status === "approved") ?? metadataLinks[0] ?? null; let missingData: MissingBooksDto | null = null; if (existingLink && existingLink.status === "approved") { missingData = await getMissingBooks(existingLink.id).catch(() => null); } if (!library) { notFound(); } const books = booksPage.items.map((book) => ({ ...book, coverUrl: getBookCoverUrl(book.id), })); const totalPages = Math.ceil(booksPage.total / limit); const booksReadCount = booksPage.items.filter((b) => b.reading_status === "read").length; const displayName = seriesName === "unclassified" ? t("books.unclassified") : seriesName; // Use first book cover as series cover const coverBookId = booksPage.items[0]?.id; return (
{/* Breadcrumb */}
{t("nav.libraries")} / {library.name} / {displayName}
{/* Series Header */}
{coverBookId && (
{t("books.coverOf",
)}

{displayName}

{seriesMeta && seriesMeta.authors.length > 0 && (

{seriesMeta.authors.join(", ")}

)} {seriesMeta?.status && ( {t(`seriesStatus.${seriesMeta.status}` as any) || seriesMeta.status} )}
{seriesMeta?.description && ( )}
{seriesMeta && seriesMeta.publishers.length > 0 && ( {seriesMeta.publishers.join(", ")} )} {seriesMeta?.start_year && ( {seriesMeta.start_year} )} {((seriesMeta && seriesMeta.publishers.length > 0) || seriesMeta?.start_year) && } {booksPage.total} {t("dashboard.books").toLowerCase()} {t("series.readCount", { read: String(booksReadCount), total: String(booksPage.total), plural: booksPage.total !== 1 ? "s" : "" })} {/* Reading progress bar */}
0 ? (booksReadCount / booksPage.total) * 100 : 0}%` }} />
{/* Collection progress bar (owned / expected) */} {missingData && missingData.total_external > 0 && ( <> {booksPage.total}/{missingData.total_external} — {t("series.missingCount", { count: missingData.missing_count, plural: missingData.missing_count !== 1 ? "s" : "" })}
)}
{/* Books Grid */} {books.length > 0 ? ( <> ) : ( )}
); }