Files
flimmer/src/app/bibliothek/page.tsx
T
Florian Wilde d04248bec9
CI / pruefen (pull_request) Canceled after 0s
Rueckmeldung beim Tippen in beiden Suchen
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
2026-09-02 00:23:04 +02:00

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>
);
}