feat: Bibliotheksseite, Papierkorb und manuelles Anlegen
Die Detailseite hängt jetzt an der internen Titel-ID statt an der TMDB-ID — nur so bekommen manuell angelegte Titel dieselbe Seite. Treffer aus der Suche laufen über /tmdb/<art>/<id>, das den Titel holt und weiterleitet. Der Fortschritt einer ganzen Liste wird in einer Abfrage gezählt statt je Eintrag; bei 300 Titeln wären das sonst 300 Rundreisen zur Datenbank. Entfernte Titel wandern für 30 Tage in den Papierkorb und behalten ihren Fortschritt.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import Link from "next/link";
|
||||
import type { Metadata } from "next";
|
||||
import { requireUser } from "@/lib/auth";
|
||||
import { getSettings } from "@/lib/settings";
|
||||
import { getLibrary, getLibraryFacets, type LibraryFilters as Filters, type LibrarySort } from "@/lib/queries";
|
||||
import { AppShell } from "@/components/AppShell";
|
||||
import { LibraryCard } from "@/components/library/LibraryCard";
|
||||
import { LibraryFilters } from "@/components/library/LibraryFilters";
|
||||
import type { EntryStatus, TitleType } from "@/generated/prisma/client";
|
||||
|
||||
export const metadata: Metadata = { title: "Bibliothek" };
|
||||
|
||||
const STATUSES = ["WATCHING", "PAUSED", "COMPLETED", "DROPPED", "WATCHLIST"];
|
||||
const SORTS = ["aktualisiert", "hinzugefuegt", "titel", "bewertung", "jahr"];
|
||||
|
||||
export default async function LibraryPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<Record<string, string | undefined>>;
|
||||
}) {
|
||||
const user = await requireUser("/bibliothek");
|
||||
const settings = await getSettings();
|
||||
const params = await searchParams;
|
||||
|
||||
const filters: Filters = {
|
||||
query: params.q?.trim() || undefined,
|
||||
status: STATUSES.includes(params.status ?? "") ? (params.status as EntryStatus) : undefined,
|
||||
type: params.typ === "SERIES" || params.typ === "MOVIE" ? (params.typ as TitleType) : undefined,
|
||||
genre: params.genre || undefined,
|
||||
tag: params.tag || undefined,
|
||||
provider: params.dienst || undefined,
|
||||
sort: SORTS.includes(params.sortierung ?? "") ? (params.sortierung as LibrarySort) : undefined,
|
||||
};
|
||||
|
||||
const [items, facets] = await Promise.all([
|
||||
getLibrary(user.id, filters),
|
||||
getLibraryFacets(user.id),
|
||||
]);
|
||||
|
||||
const hasFilters = Object.values(filters).some(Boolean);
|
||||
|
||||
return (
|
||||
<AppShell user={user} instanceName={settings.instanceName}>
|
||||
<header className="mb-5 flex flex-wrap items-end justify-between gap-3">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold tracking-tight sm:text-3xl">Bibliothek</h1>
|
||||
<p className="mt-1 text-sm text-[var(--fl-muted)]">
|
||||
{facets.counts.ALLE === 0
|
||||
? "Noch nichts aufgenommen."
|
||||
: `${facets.counts.ALLE} ${facets.counts.ALLE === 1 ? "Titel" : "Titel"} — davon ${facets.counts.WATCHING} gerade am Laufen.`}
|
||||
</p>
|
||||
</div>
|
||||
<Link href="/suche" className="fl-btn fl-btn-primary">
|
||||
Titel hinzufügen
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
{facets.counts.ALLE > 0 && <LibraryFilters facets={facets} />}
|
||||
|
||||
<div className="mt-6">
|
||||
{items.length === 0 ? (
|
||||
<div className="fl-card p-8 text-center">
|
||||
<p className="text-[var(--fl-muted)]">
|
||||
{facets.counts.ALLE === 0
|
||||
? "Deine Bibliothek ist noch leer."
|
||||
: hasFilters
|
||||
? "Zu dieser Auswahl passt nichts."
|
||||
: "Hier ist nichts."}
|
||||
</p>
|
||||
{facets.counts.ALLE === 0 && (
|
||||
<Link href="/suche" className="fl-btn fl-btn-primary mt-4">
|
||||
Ersten Titel suchen
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<p className="mb-4 text-sm text-[var(--fl-faint)]">
|
||||
{items.length} {items.length === 1 ? "Titel" : "Titel"}
|
||||
</p>
|
||||
<ul className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6">
|
||||
{items.map((item) => (
|
||||
<li key={item.entryId}>
|
||||
<LibraryCard item={item} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user