'use client'; import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { X, Plus, Pin, PinOff, Trash2, ChevronUp, ChevronDown, Loader2 } from 'lucide-react'; import { fetchSavedViews, createSavedView, updateSavedView, deleteSavedView, type SavedView, type VideosQuery, } from '@/lib/api'; import styles from './SavedViewManager.module.css'; interface Props { onClose: () => void; currentFilters: Partial; currentColumnsJson: Record; currentSortJson: Record | null; } export default function SavedViewManager({ onClose, currentFilters, currentColumnsJson, currentSortJson }: Props) { const qc = useQueryClient(); const [newName, setNewName] = useState(''); const [newDesc, setNewDesc] = useState(''); const [pinNew, setPinNew] = useState(false); const [creating, setCreating] = useState(false); const { data: views = [], isLoading } = useQuery({ queryKey: ['savedViews'], queryFn: fetchSavedViews, staleTime: 30_000, }); const invalidate = () => { qc.invalidateQueries({ queryKey: ['savedViews'] }); qc.invalidateQueries({ queryKey: ['savedViewTabs'] }); }; const createMut = useMutation({ mutationFn: () => createSavedView({ name: newName.trim(), description: newDesc.trim() || undefined, queryJson: currentFilters as Record, columnsJson: currentColumnsJson, sortJson: currentSortJson, pinnedAsTab: pinNew, tabOrder: pinNew ? (views.filter((v) => v.pinnedAsTab).length) : undefined, }), onSuccess: () => { invalidate(); setNewName(''); setNewDesc(''); setPinNew(false); setCreating(false); }, }); const patchMut = useMutation({ mutationFn: ({ id, data }: { id: string; data: Parameters[1] }) => updateSavedView(id, data), onSuccess: invalidate, }); const deleteMut = useMutation({ mutationFn: (id: string) => deleteSavedView(id), onSuccess: invalidate, }); const pinnedViews = views.filter((v) => v.pinnedAsTab).sort((a, b) => (a.tabOrder ?? 99) - (b.tabOrder ?? 99)); const unpinnedViews = views.filter((v) => !v.pinnedAsTab).sort((a, b) => a.name.localeCompare(b.name)); const moveTab = (view: SavedView, dir: -1 | 1) => { const idx = pinnedViews.findIndex((v) => v.id === view.id); const swapIdx = idx + dir; if (swapIdx < 0 || swapIdx >= pinnedViews.length) return; const swap = pinnedViews[swapIdx]; Promise.all([ patchMut.mutateAsync({ id: view.id, data: { tabOrder: swapIdx } }), patchMut.mutateAsync({ id: swap.id, data: { tabOrder: idx } }), ]); }; return (
e.target === e.currentTarget && onClose()}>

Saved Views

{/* Create new view */} {creating ? (
setNewName(e.target.value)} autoFocus /> setNewDesc(e.target.value)} />
) : ( )} {isLoading &&
Loading…
} {/* Pinned as tabs */} {pinnedViews.length > 0 && (
Pinned Tabs
{pinnedViews.map((view, idx) => (
{view.name} {view.description && {view.description}}
))}
)} {/* All other views */} {unpinnedViews.length > 0 && (
All Views
{unpinnedViews.map((view) => (
{view.name} {view.description && {view.description}}
))}
)} {!isLoading && views.length === 0 && !creating && (

No saved views yet. Save your current filters to create one.

)}
); }