"use client"; import { useEffect, useRef, useState, useTransition } from "react"; import { useRouter } from "next/navigation"; import { ListVideo, Eye, Trash2, X, Loader2 } from "lucide-react"; import { useQueuePanel } from "./QueuePanelProvider"; import { useWatchQueue } from "./WatchQueueProvider"; import { fetchQueueCovers } from "@/app/actions/queue"; import { ImageCard, type CardImage } from "@/components/grid/ImageCard"; import { bulkSetWatched } from "@/app/actions/bulk"; import { useClickOutside } from "@/lib/hooks/useClickOutside"; /** * Watch-queue panel. Mirrors `TrashPanel` and `SettingsPanel`: a centered * modal capped at 1400×900, dimmed backdrop, click-outside / Escape to * close. Triggered by the topnav indicator (which now toggles instead of * navigating to /queue). */ export function QueuePanel() { const { open, close } = useQueuePanel(); const queue = useWatchQueue(); const router = useRouter(); const ref = useRef(null); useClickOutside(ref, close, open); const [covers, setCovers] = useState([]); const [loading, setLoading] = useState(true); const [pending, start] = useTransition(); // Re-fetch when the panel opens or the underlying queue changes. The // fetch is keyed by the comma-joined id list so order changes also // refresh the displayed grid. useEffect(() => { if (!open) return; let live = true; setLoading(true); fetchQueueCovers(queue.ids).then((c) => { if (!live) return; setCovers(c); setLoading(false); }); return () => { live = false; }; }, [open, queue.ids]); // Prune any queued ids that no longer resolve to a live cover. useEffect(() => { if (!open || loading) return; const present = new Set(covers.map((c) => c.id)); const stale = queue.ids.filter((id) => !present.has(id)); if (stale.length > 0) queue.removeMany(stale); }, [open, loading, covers, queue]); // Lock background scroll + Escape to close while open. useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") close(); }; window.addEventListener("keydown", onKey); document.body.style.overflow = "hidden"; return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; }; }, [open, close]); if (!open) return null; function markAllWatched() { if (covers.length === 0) return; if (!confirm(`Mark all ${covers.length} covers as watched and clear the queue?`)) return; const ids = covers.map((c) => c.id); start(async () => { await bulkSetWatched(ids, true); queue.removeMany(ids); router.refresh(); }); } function clearQueue() { if (queue.ids.length === 0) return; if (!confirm("Clear the watch queue? Covers themselves are not affected.")) return; queue.clear(); } return (

Watch Queue

{queue.ids.length === 0 ? "Empty — right-click any cover and choose “Add to queue”." : `${covers.length} cover${covers.length === 1 ? "" : "s"} queued · marking watched removes them automatically`}

{queue.ids.length > 0 && ( <> )}
{loading ? (
Loading queue…
) : covers.length === 0 ? (

{queue.ids.length > 0 ? "Queue items couldn't be resolved (covers may have been deleted)." : "Queue is empty."}

) : (
{covers.map((c) => (
))}
)}
); }