Files
flimmer/src/components/library/LibraryCard.tsx
T
Florian Wilde 9ee8664233
CI / pruefen (push) Canceled after 0s
Release / veroeffentlichen (push) Canceled after 0s
feat: eigene Sammlung — „hab ich" mit Quelle
Streaming-Verfügbarkeit beantwortet nur die halbe Frage: manche Filme laufen
nirgends mehr, die hat man gekauft und liegen zu Hause — in Jellyfin, Plex,
Emby, auf dem NAS oder als Scheibe.

Bewusst getrennt vom Feld „wo schaue ich das": das eine sagt, wo ein Titel
gerade läuft, und das ändert sich ständig; das andere sagt, was einem gehört,
und das bleibt. Auf der Detailseite steht die Sammlung deshalb vor der
Verfügbarkeit — was man hat, ist die verlässlichere Antwort.

Durchgezogen bis in Filter, Export, CSV-Import und REST-API.
2026-09-01 21:15:53 +02:00

92 lines
3.4 KiB
TypeScript

import Link from "next/link";
import { Poster } from "@/components/Poster";
import { ProgressBar } from "@/components/ProgressBar";
import { STATUS_COLOR, STATUS_LABEL, type StatusKey } from "@/lib/labels";
import type { LibraryItem } from "@/lib/queries";
/**
* Posterkachel der Bibliothek.
*
* Alles Wesentliche steht ohne Klick da: Status, Fortschritt, Bewertung.
* Die Karte ist ein Link — der ganze Bereich ist anklickbar, nicht nur der Titel.
*/
export function LibraryCard({ item }: { item: LibraryItem }) {
const status = item.status as StatusKey;
const showProgress = item.type === "SERIES" && item.total > 0 && item.status !== "WATCHLIST";
return (
<Link href={`/titel/${item.titleId}`} className="fl-poster-card group block focus:outline-none">
<div className="relative">
<Poster path={item.posterPath} alt={item.title} size="w342" />
{/* Status und Sammlung teilen sich die obere Zeile — unten sitzt bei
den Postern oft der Titel, dort würden sie ihn verdecken. */}
<span className="absolute left-2 top-2 flex flex-wrap gap-1">
<span
className="rounded-full px-2 py-0.5 text-[0.65rem] font-semibold backdrop-blur-md"
style={{
color: STATUS_COLOR[status],
backgroundColor: `color-mix(in oklab, var(--fl-bg) 72%, transparent)`,
boxShadow: `inset 0 0 0 1px color-mix(in oklab, ${STATUS_COLOR[status]} 45%, transparent)`,
}}
>
{STATUS_LABEL[status]}
</span>
{item.owned && (
<span
className="rounded-full px-2 py-0.5 text-[0.65rem] font-semibold text-[var(--fl-on-accent)]"
style={{ backgroundImage: "var(--fl-gradient)" }}
title={
item.ownedSource ? `In deiner Sammlung: ${item.ownedSource}` : "In deiner Sammlung"
}
>
Hab ich
</span>
)}
</span>
{item.rating != null && (
<span
className="absolute right-2 top-2 rounded-full px-2 py-0.5 text-[0.65rem] font-semibold text-[var(--fl-on-accent)]"
style={{ backgroundImage: "var(--fl-gradient)" }}
>
{item.rating}
</span>
)}
{item.currentRun > 0 && (
<span className="absolute bottom-2 right-2 rounded-full bg-[color-mix(in_oklab,var(--fl-bg)_78%,transparent)] px-2 py-0.5 text-[0.65rem] font-medium text-[var(--fl-muted)] backdrop-blur-md">
{item.currentRun + 1}. Durchlauf
</span>
)}
</div>
<div className="mt-2 space-y-1">
<p className="line-clamp-2 text-sm font-medium leading-snug">{item.title}</p>
<p className="text-xs text-[var(--fl-faint)]">
{item.type === "SERIES" ? "Serie" : "Film"}
{item.year ? ` · ${item.year}` : ""}
{item.owned && item.ownedSource
? ` · ${item.ownedSource}`
: item.whereToWatch
? ` · ${item.whereToWatch}`
: ""}
</p>
{showProgress && (
<div className="pt-0.5">
<ProgressBar
percent={item.percent}
label={`${item.watched} von ${item.total} Episoden gesehen`}
/>
<p className="mt-1 text-xs text-[var(--fl-faint)]">
{item.watched}/{item.total} Episoden
</p>
</div>
)}
</div>
</Link>
);
}