Umsortieren geht per Ziehen und über Pfeilknöpfe: reines Drag & Drop wäre mit Tastatur nicht bedienbar und auf dem Handy fummelig — die Knöpfe sind der verlässliche Weg, das Ziehen der bequeme. Die Erscheinungsreihenfolge lässt sich gar nicht erst anfassen, weil sie sich aus den Daten ergibt.
91 lines
3.4 KiB
TypeScript
91 lines
3.4 KiB
TypeScript
import Link from "next/link";
|
|
import type { Metadata } from "next";
|
|
import { requireUser } from "@/lib/auth";
|
|
import { getSettings } from "@/lib/settings";
|
|
import { listUniverses } from "@/lib/universes";
|
|
import { AppShell } from "@/components/AppShell";
|
|
import { Poster } from "@/components/Poster";
|
|
import { ProgressBar } from "@/components/ProgressBar";
|
|
|
|
export const metadata: Metadata = { title: "Universen" };
|
|
|
|
export default async function UniversesPage() {
|
|
const user = await requireUser("/universen");
|
|
const settings = await getSettings();
|
|
const universes = await listUniverses(user.id);
|
|
|
|
return (
|
|
<AppShell user={user} instanceName={settings.instanceName}>
|
|
<header className="mb-6 flex flex-wrap items-end justify-between gap-3">
|
|
<div>
|
|
<h1 className="text-2xl font-semibold tracking-tight sm:text-3xl">Universen</h1>
|
|
<p className="mt-1 max-w-2xl text-sm text-[var(--fl-muted)]">
|
|
Filme und Serien, die zusammengehören — mit mehreren Reihenfolgen und einem
|
|
Gesamtfortschritt.
|
|
</p>
|
|
</div>
|
|
<Link href="/universen/neu" className="fl-btn fl-btn-primary">
|
|
Neues Universum
|
|
</Link>
|
|
</header>
|
|
|
|
{universes.length === 0 ? (
|
|
<div className="fl-card p-8 text-center">
|
|
<p className="mx-auto max-w-md text-[var(--fl-muted)]">
|
|
Noch kein Universum angelegt. Ein Universum bündelt zusammengehörige Titel — etwa alle
|
|
MCU-Filme samt Serien — und zeigt dir, was als Nächstes dran ist.
|
|
</p>
|
|
<Link href="/universen/neu" className="fl-btn fl-btn-primary mt-4">
|
|
Erstes Universum anlegen
|
|
</Link>
|
|
</div>
|
|
) : (
|
|
<ul className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
{universes.map((universe) => (
|
|
<li key={universe.id}>
|
|
<Link
|
|
href={`/universen/${universe.id}`}
|
|
className="fl-card fl-poster-card block overflow-hidden p-4"
|
|
>
|
|
<div className="mb-3 flex gap-1.5">
|
|
{universe.posters.length > 0 ? (
|
|
universe.posters.map((path, index) => (
|
|
<div key={`${path}-${index}`} className="w-1/5">
|
|
<Poster path={path} alt="" size="w154" />
|
|
</div>
|
|
))
|
|
) : (
|
|
<div className="w-1/5">
|
|
<Poster path={null} alt={universe.name} size="w154" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<h2 className="font-semibold">{universe.name}</h2>
|
|
{universe.description && (
|
|
<p className="mt-1 line-clamp-2 text-sm text-[var(--fl-muted)]">
|
|
{universe.description}
|
|
</p>
|
|
)}
|
|
|
|
<p className="mt-2 text-xs text-[var(--fl-faint)]">
|
|
{universe.itemCount} {universe.itemCount === 1 ? "Titel" : "Titel"}
|
|
{!universe.isOwner ? ` · geteilt von ${universe.ownerName}` : ""}
|
|
</p>
|
|
|
|
<div className="mt-2">
|
|
<ProgressBar
|
|
percent={universe.percent}
|
|
label={`${universe.completed} von ${universe.itemCount} abgeschlossen`}
|
|
showValue
|
|
/>
|
|
</div>
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</AppShell>
|
|
);
|
|
}
|