CI / pruefen (pull_request) Canceled after 0s
Die Bibliotheksfilterleiste erzeugte das Signal laengst und warf es weg: `const [, startTransition] = useTransition()`. Waehrend der Suche blieben die alten Treffer unveraendert stehen — ohne jeden Hinweis, dass gearbeitet wird. Beide Suchen teilen sich jetzt eine schmale Klammer (SearchPending): die Filterleiste meldet den Ladezustand, die Trefferliste daneben — eine Server-Komponente — wird gedaempft und auf aria-busy gesetzt. Die alten Treffer bleiben sichtbar; sie verschwinden zu lassen liesse die Liste bei jedem Tastendruck springen. Der Hinweis „sucht …" steht jetzt auch in der Bibliothek, nicht nur unter /suche. Sammelverzoegerung in beiden auf 250 ms angeglichen (vorher 350 und 300). Zwei Vermutungen aus dem Issue haben sich beim Nachmessen NICHT bestaetigt und bleiben deshalb unangetastet: Die Facetten kosten nichts. Gemessen an einer eigens angelegten Bibliothek mit 2000 Eintraegen (Median aus 9 Laeufen): getLibrary 50 ms, getLibraryFacets 17 ms, beide parallel 61 ms. Ein Wegfall spaerte rund 12 ms — eine Zwischenspeicherung braechte dafuer das Risiko veralteter Zaehler. Langsame Bilder blockieren die Trefferanzeige nicht. Mit kuenstlich um 1500 ms verzoegerten /api/image-Antworten stehen die Treffer nach 465 ms im DOM, 891 ms vor den Bildern; die Rueckfallkacheln zeigen derweil die Titel. Ein Umbau des Bild-Endpunkts waere also Aufwand ohne Wirkung. Refs #3
100 lines
3.8 KiB
TypeScript
100 lines
3.8 KiB
TypeScript
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 { SearchPendingProvider, SearchResults } from "@/components/SearchPending";
|
|
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,
|
|
owned: params.sammlung === "1" ? true : 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>
|
|
|
|
<SearchPendingProvider>
|
|
{facets.counts.ALLE > 0 && <LibraryFilters facets={facets} />}
|
|
|
|
<SearchResults>
|
|
<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>
|
|
</SearchResults>
|
|
</SearchPendingProvider>
|
|
</AppShell>
|
|
);
|
|
}
|