feat(frontend): library page, components, and schedule/add-to-block dialogs (tasks 11-14)
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"use client";
|
||||
|
||||
import { useCollections, useGenres } from "@/hooks/use-library";
|
||||
import type { LibrarySearchParams } from "@/hooks/use-library-search";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface Props {
|
||||
filter: LibrarySearchParams;
|
||||
onFilterChange: (next: Partial<LibrarySearchParams>) => void;
|
||||
}
|
||||
|
||||
const CONTENT_TYPES = [
|
||||
{ value: "", label: "All types" },
|
||||
{ value: "movie", label: "Movies" },
|
||||
{ value: "episode", label: "Episodes" },
|
||||
{ value: "short", label: "Shorts" },
|
||||
];
|
||||
|
||||
export function LibrarySidebar({ filter, onFilterChange }: Props) {
|
||||
const { data: collections } = useCollections(filter.provider);
|
||||
const { data: genres } = useGenres(filter.type, { provider: filter.provider });
|
||||
|
||||
return (
|
||||
<aside className="w-56 shrink-0 border-r border-zinc-800 bg-zinc-950 p-4 flex flex-col gap-4">
|
||||
<div>
|
||||
<p className="mb-1.5 text-xs font-medium uppercase tracking-wider text-zinc-500">Search</p>
|
||||
<Input
|
||||
placeholder="Search…"
|
||||
value={filter.q ?? ""}
|
||||
onChange={e => onFilterChange({ q: e.target.value || undefined })}
|
||||
className="h-8 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="mb-1.5 text-xs font-medium uppercase tracking-wider text-zinc-500">Type</p>
|
||||
<Select value={filter.type ?? ""} onValueChange={v => onFilterChange({ type: v || undefined })}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{CONTENT_TYPES.map(ct => (
|
||||
<SelectItem key={ct.value} value={ct.value}>{ct.label}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{collections && collections.length > 0 && (
|
||||
<div>
|
||||
<p className="mb-1.5 text-xs font-medium uppercase tracking-wider text-zinc-500">Collection</p>
|
||||
<Select value={filter.collection ?? ""} onValueChange={v => onFilterChange({ collection: v || undefined })}>
|
||||
<SelectTrigger className="h-8 text-xs"><SelectValue placeholder="All" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">All</SelectItem>
|
||||
{collections.map(c => (
|
||||
<SelectItem key={c.id} value={c.id}>{c.name}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{genres && genres.length > 0 && (
|
||||
<div>
|
||||
<p className="mb-1.5 text-xs font-medium uppercase tracking-wider text-zinc-500">Genre</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{genres.map(g => {
|
||||
const active = filter.genres?.includes(g) ?? false;
|
||||
return (
|
||||
<Badge
|
||||
key={g}
|
||||
variant={active ? "default" : "outline"}
|
||||
className="cursor-pointer text-xs"
|
||||
onClick={() => {
|
||||
const current = filter.genres ?? [];
|
||||
onFilterChange({
|
||||
genres: active ? current.filter(x => x !== g) : [...current, g],
|
||||
});
|
||||
}}
|
||||
>
|
||||
{g}
|
||||
</Badge>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user