"use client"; import { useEffect, useState, useTransition } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { clsx } from "clsx"; import { reorderAction, removeFromUniverseAction } from "@/app/actions/universes"; import { useToasts } from "@/components/Toasts"; import { Poster } from "@/components/Poster"; import { ProgressBar } from "@/components/ProgressBar"; import { formatMinutes, STATUS_COLOR, STATUS_LABEL, type StatusKey } from "@/lib/labels"; import type { UniverseEntry } from "@/lib/universes"; /** * Nummerierte Liste eines Universums in der gewählten Reihenfolge. * * 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 (`sortable = false`) ergibt sich aus den Daten * und lässt sich deshalb gar nicht erst anfassen. */ export function OrderedList({ universeId, orderId, entries, sortable, canWrite, nextUpItemId, }: { universeId: string; orderId: string; entries: UniverseEntry[]; sortable: boolean; canWrite: boolean; nextUpItemId: string | null; }) { const router = useRouter(); const { report } = useToasts(); const [items, setItems] = useState(entries); const [dragging, setDragging] = useState(null); const [, startTransition] = useTransition(); useEffect(() => setItems(entries), [entries]); const interactive = sortable && canWrite; function persist(next: UniverseEntry[]) { setItems(next); startTransition(async () => { const result = await reorderAction( orderId, next.map((entry) => entry.itemId), ); if (!result.ok) { report(result); setItems(entries); } router.refresh(); }); } function move(index: number, delta: number) { const target = index + delta; if (target < 0 || target >= items.length) return; const next = [...items]; const [moved] = next.splice(index, 1); if (moved) next.splice(target, 0, moved); persist(next); } function dropOn(targetItemId: string) { if (!dragging || dragging === targetItemId) return; const from = items.findIndex((entry) => entry.itemId === dragging); const to = items.findIndex((entry) => entry.itemId === targetItemId); if (from < 0 || to < 0) return; const next = [...items]; const [moved] = next.splice(from, 1); if (moved) next.splice(to, 0, moved); setDragging(null); persist(next); } if (items.length === 0) { return (

Dieses Universum ist noch leer. Nimm Filme und Serien aus deiner Bibliothek auf.

); } return (
    {items.map((entry, index) => (
  1. setDragging(entry.itemId)} onDragEnd={() => setDragging(null)} onDragOver={(event) => { if (interactive) event.preventDefault(); }} onDrop={() => dropOn(entry.itemId)} className={clsx( "fl-card flex items-center gap-3 p-2.5 transition-opacity sm:gap-4 sm:p-3", dragging === entry.itemId && "opacity-40", entry.itemId === nextUpItemId && "ring-1", )} style={ entry.itemId === nextUpItemId ? { boxShadow: "var(--fl-glow)", borderColor: "color-mix(in oklab, var(--fl-magenta) 45%, transparent)", } : undefined } > {entry.completed ? "✓" : index + 1}
    {entry.title} {entry.itemId === nextUpItemId && ( Als Nächstes dran )} {entry.status && ( {STATUS_LABEL[entry.status as StatusKey]} )}

    {entry.type === "SERIES" ? "Serie" : "Film"} {entry.year ? ` · ${entry.year}` : ""} {entry.type === "SERIES" ? ` · ${entry.watchedEpisodes}/${entry.totalEpisodes} Episoden` : ""} {entry.remainingMinutes > 0 ? ` · noch ${formatMinutes(entry.remainingMinutes)}` : ""}

    {entry.type === "SERIES" && entry.totalEpisodes > 0 && (
    )}
    {canWrite && (
    {interactive && ( <> move(index, -1)} > move(index, 1)} > )}
    )}
  2. ))}
); } function IconButton({ label, disabled, onClick, children, }: { label: string; disabled?: boolean; onClick: () => void; children: React.ReactNode; }) { return ( ); } function RemoveButton({ universeId, itemId, title, }: { universeId: string; itemId: string; title: string; }) { const router = useRouter(); const { report } = useToasts(); const [, startTransition] = useTransition(); return ( startTransition(async () => { report(await removeFromUniverseAction(universeId, itemId)); router.refresh(); }) } > ); }