import { useEffect, useMemo, useRef, useState } from 'react'; import { apiClient } from '../../lib/api-client'; import { formatDuration } from '../../lib/formatters'; import { AnimeCoverImage } from './AnimeCoverImage'; import type { AnimeLibraryItem } from '../../types/stats'; interface LibraryEntryPickerProps { heading: string; /** Entries that cannot be picked, typically the one being moved away from. */ excludeAnimeIds?: number[]; initialQuery?: string; busyAnimeId?: number | null; error?: string | null; onSelect: (entry: AnimeLibraryItem) => void; onClose: () => void; } export function LibraryEntryPicker({ heading, excludeAnimeIds = [], initialQuery = '', busyAnimeId = null, error = null, onSelect, onClose, }: LibraryEntryPickerProps) { const [entries, setEntries] = useState(null); const [query, setQuery] = useState(initialQuery); const inputRef = useRef(null); useEffect(() => { inputRef.current?.focus(); let cancelled = false; apiClient .getAnimeLibrary() .then((data) => { if (!cancelled) setEntries(data); }) .catch(() => { if (!cancelled) setEntries([]); }); return () => { cancelled = true; }; }, []); const excluded = useMemo(() => new Set(excludeAnimeIds), [excludeAnimeIds]); const visible = useMemo(() => { const term = query.trim().toLowerCase(); return (entries ?? []) .filter((entry) => !excluded.has(entry.animeId)) .filter((entry) => !term || entry.canonicalTitle.toLowerCase().includes(term)) .sort((a, b) => b.lastWatchedMs - a.lastWatchedMs); }, [entries, excluded, query]); return (
e.stopPropagation()} >

{heading}

setQuery(e.target.value)} placeholder="Search library..." className="w-full bg-ctp-surface0 border border-ctp-surface1 rounded-lg px-3 py-2 text-sm text-ctp-text placeholder:text-ctp-overlay2 focus:outline-none focus:border-ctp-blue" /> {error ?
{error}
: null}
{entries === null &&
Loading...
} {entries !== null && visible.length === 0 && (
No other titles
)} {visible.map((entry) => ( ))}
); }