feat(frontend): add viewMode/drilldown state to library page
This commit is contained in:
@@ -1,18 +1,25 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { LayoutGrid, FolderOpen } from "lucide-react";
|
||||||
import { useLibrarySearch, type LibrarySearchParams } from "@/hooks/use-library-search";
|
import { useLibrarySearch, type LibrarySearchParams } from "@/hooks/use-library-search";
|
||||||
import { LibrarySidebar } from "./components/library-sidebar";
|
import { LibrarySidebar } from "./components/library-sidebar";
|
||||||
import { LibraryGrid } from "./components/library-grid";
|
import { LibraryGrid } from "./components/library-grid";
|
||||||
import { SyncStatusBar } from "./components/sync-status-bar";
|
import { SyncStatusBar } from "./components/sync-status-bar";
|
||||||
import type { LibraryItemFull } from "@/lib/types";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import type { LibraryItemFull, ShowSummary } from "@/lib/types";
|
||||||
|
|
||||||
const PAGE_SIZE = 50;
|
const PAGE_SIZE = 50;
|
||||||
|
|
||||||
|
type Drilldown = null | { series: string } | { series: string; season: number };
|
||||||
|
|
||||||
export default function LibraryPage() {
|
export default function LibraryPage() {
|
||||||
const [filter, setFilter] = useState<LibrarySearchParams>({ limit: PAGE_SIZE, offset: 0 });
|
const [filter, setFilter] = useState<LibrarySearchParams>({ limit: PAGE_SIZE, offset: 0 });
|
||||||
const [selected, setSelected] = useState<Set<string>>(new Set());
|
const [selected, setSelected] = useState<Set<string>>(new Set());
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
|
const [viewMode, setViewMode] = useState<"grouped" | "flat">("grouped");
|
||||||
|
const [drilldown, setDrilldown] = useState<Drilldown>(null);
|
||||||
|
const [selectedShowMap, setSelectedShowMap] = useState<Map<string, ShowSummary>>(new Map());
|
||||||
|
|
||||||
const { data, isLoading } = useLibrarySearch({ ...filter, offset: page * PAGE_SIZE });
|
const { data, isLoading } = useLibrarySearch({ ...filter, offset: page * PAGE_SIZE });
|
||||||
|
|
||||||
@@ -31,13 +38,78 @@ export default function LibraryPage() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleSelectShow(show: ShowSummary) {
|
||||||
|
setSelectedShowMap(prev => {
|
||||||
|
const next = new Map(prev);
|
||||||
|
if (next.has(show.series_name)) next.delete(show.series_name);
|
||||||
|
else next.set(show.series_name, show);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDrilldown(next: Drilldown) {
|
||||||
|
setDrilldown(next);
|
||||||
|
setSelected(new Set());
|
||||||
|
setSelectedShowMap(new Map());
|
||||||
|
setPage(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBack() {
|
||||||
|
if (!drilldown) return;
|
||||||
|
if ("season" in drilldown) {
|
||||||
|
// From episode level → go back to season level
|
||||||
|
setDrilldown({ series: drilldown.series });
|
||||||
|
} else {
|
||||||
|
// From season level → go back to root
|
||||||
|
setDrilldown(null);
|
||||||
|
}
|
||||||
|
setSelected(new Set());
|
||||||
|
setPage(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleViewModeToggle() {
|
||||||
|
const next = viewMode === "grouped" ? "flat" : "grouped";
|
||||||
|
setViewMode(next);
|
||||||
|
if (next === "flat") setDrilldown(null);
|
||||||
|
setSelected(new Set());
|
||||||
|
setSelectedShowMap(new Map());
|
||||||
|
}
|
||||||
|
|
||||||
const selectedItems = data?.items.filter(i => selected.has(i.id)) ?? [];
|
const selectedItems = data?.items.filter(i => selected.has(i.id)) ?? [];
|
||||||
|
const selectedShows = Array.from(selectedShowMap.values());
|
||||||
|
const selectedShowNames = new Set(selectedShowMap.keys());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col">
|
<div className="flex flex-1 flex-col">
|
||||||
<SyncStatusBar />
|
<SyncStatusBar />
|
||||||
|
<div className="flex items-center justify-end gap-2 border-b border-zinc-800 bg-zinc-950 px-4 py-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="gap-1.5 text-xs"
|
||||||
|
onClick={handleViewModeToggle}
|
||||||
|
>
|
||||||
|
{viewMode === "grouped" ? (
|
||||||
|
<>
|
||||||
|
<LayoutGrid className="h-3.5 w-3.5" />
|
||||||
|
Flat view
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FolderOpen className="h-3.5 w-3.5" />
|
||||||
|
Grouped view
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<div className="flex flex-1">
|
<div className="flex flex-1">
|
||||||
<LibrarySidebar filter={filter} onFilterChange={handleFilterChange} />
|
<LibrarySidebar
|
||||||
|
filter={filter}
|
||||||
|
onFilterChange={handleFilterChange}
|
||||||
|
viewMode={viewMode}
|
||||||
|
drilldown={drilldown}
|
||||||
|
onBack={handleBack}
|
||||||
|
/>
|
||||||
<LibraryGrid
|
<LibraryGrid
|
||||||
items={data?.items ?? []}
|
items={data?.items ?? []}
|
||||||
total={data?.total ?? 0}
|
total={data?.total ?? 0}
|
||||||
@@ -48,6 +120,13 @@ export default function LibraryPage() {
|
|||||||
onToggleSelect={toggleSelect}
|
onToggleSelect={toggleSelect}
|
||||||
onPageChange={setPage}
|
onPageChange={setPage}
|
||||||
selectedItems={selectedItems}
|
selectedItems={selectedItems}
|
||||||
|
viewMode={viewMode}
|
||||||
|
drilldown={drilldown}
|
||||||
|
onDrilldown={handleDrilldown}
|
||||||
|
filter={filter}
|
||||||
|
selectedShows={selectedShows}
|
||||||
|
selectedShowNames={selectedShowNames}
|
||||||
|
onToggleSelectShow={toggleSelectShow}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user