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.
92 lines
3.4 KiB
TypeScript
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>
|
|
);
|
|
}
|