130 lines
4.6 KiB
TypeScript
130 lines
4.6 KiB
TypeScript
"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";
|
|
import { ArrowLeft } from "lucide-react";
|
|
|
|
interface Props {
|
|
filter: LibrarySearchParams;
|
|
onFilterChange: (next: Partial<LibrarySearchParams>) => void;
|
|
viewMode: "grouped" | "flat";
|
|
drilldown: null | { series: string } | { series: string; season: number };
|
|
onBack: () => void;
|
|
}
|
|
|
|
const ALL = "";
|
|
|
|
const CONTENT_TYPES_ALL = [
|
|
{ value: ALL, label: "All types" },
|
|
{ value: "movie", label: "Movies" },
|
|
{ value: "episode", label: "Episodes" },
|
|
{ value: "short", label: "Shorts" },
|
|
];
|
|
|
|
const CONTENT_TYPES_GROUPED = [
|
|
{ value: ALL, label: "All types" },
|
|
{ value: "movie", label: "Movies" },
|
|
{ value: "short", label: "Shorts" },
|
|
];
|
|
|
|
export function LibrarySidebar({ filter, onFilterChange, viewMode, drilldown, onBack }: Props) {
|
|
const { data: collections } = useCollections(filter.provider);
|
|
const { data: genres } = useGenres(filter.type, { provider: filter.provider });
|
|
|
|
if (drilldown !== null) {
|
|
return (
|
|
<aside className="w-56 shrink-0 border-r border-zinc-800 bg-zinc-950 p-4 flex flex-col gap-4">
|
|
<button
|
|
type="button"
|
|
onClick={onBack}
|
|
className="flex items-center gap-1.5 text-xs text-zinc-400 hover:text-zinc-100 transition-colors"
|
|
>
|
|
<ArrowLeft className="h-3.5 w-3.5" />
|
|
Back
|
|
</button>
|
|
<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>
|
|
</aside>
|
|
);
|
|
}
|
|
|
|
const contentTypes = viewMode === "grouped" ? CONTENT_TYPES_GROUPED : CONTENT_TYPES_ALL;
|
|
|
|
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 ?? ALL} onValueChange={v => onFilterChange({ type: v === ALL ? undefined : v })}>
|
|
<SelectTrigger className="h-8 text-xs"><SelectValue /></SelectTrigger>
|
|
<SelectContent>
|
|
{contentTypes.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>
|
|
);
|
|
}
|