import { useState, useMemo, useEffect } from 'react'; import { useAnimeLibrary } from '../../hooks/useAnimeLibrary'; import { formatDuration } from '../../lib/formatters'; import { AnimeCard } from './AnimeCard'; import { AnimeDetailView } from './AnimeDetailView'; type SortKey = 'lastWatched' | 'watchTime' | 'cards' | 'episodes'; type CardSize = 'sm' | 'md' | 'lg'; const GRID_CLASSES: Record = { sm: 'grid-cols-5 sm:grid-cols-7 md:grid-cols-9 lg:grid-cols-11', md: 'grid-cols-4 sm:grid-cols-5 md:grid-cols-7 lg:grid-cols-9', lg: 'grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-7', }; const SORT_OPTIONS: { key: SortKey; label: string }[] = [ { key: 'lastWatched', label: 'Last Watched' }, { key: 'watchTime', label: 'Watch Time' }, { key: 'cards', label: 'Cards' }, { key: 'episodes', label: 'Episodes' }, ]; function sortAnime(list: ReturnType['anime'], key: SortKey) { return [...list].sort((a, b) => { switch (key) { case 'lastWatched': return b.lastWatchedMs - a.lastWatchedMs; case 'watchTime': return b.totalActiveMs - a.totalActiveMs; case 'cards': return b.totalCards - a.totalCards; case 'episodes': return b.episodeCount - a.episodeCount; } }); } interface AnimeTabProps { initialAnimeId?: number | null; onClearInitialAnime?: () => void; onNavigateToWord?: (wordId: number) => void; onOpenEpisodeDetail?: (animeId: number, videoId: number) => void; } export function AnimeTab({ initialAnimeId, onClearInitialAnime, onNavigateToWord, onOpenEpisodeDetail, }: AnimeTabProps) { const { anime, loading, error } = useAnimeLibrary(); const [search, setSearch] = useState(''); const [sortKey, setSortKey] = useState('lastWatched'); const [cardSize, setCardSize] = useState('md'); const [selectedAnimeId, setSelectedAnimeId] = useState(null); useEffect(() => { if (initialAnimeId != null) { setSelectedAnimeId(initialAnimeId); onClearInitialAnime?.(); } }, [initialAnimeId, onClearInitialAnime]); const filtered = useMemo(() => { const base = search.trim() ? anime.filter((a) => a.canonicalTitle.toLowerCase().includes(search.toLowerCase())) : anime; return sortAnime(base, sortKey); }, [anime, search, sortKey]); const totalMs = anime.reduce((sum, a) => sum + a.totalActiveMs, 0); if (selectedAnimeId !== null) { return ( setSelectedAnimeId(null)} onNavigateToWord={onNavigateToWord} onOpenEpisodeDetail={ onOpenEpisodeDetail ? (videoId) => onOpenEpisodeDetail(selectedAnimeId, videoId) : undefined } /> ); } if (loading) return
Loading...
; if (error) return
Error: {error}
; return (
setSearch(e.target.value)} className="flex-1 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" />
{(['sm', 'md', 'lg'] as const).map((size) => ( ))}
{filtered.length} titles · {formatDuration(totalMs)}
{filtered.length === 0 ? (
No titles found
) : (
{filtered.map((item) => ( setSelectedAnimeId(item.animeId)} /> ))}
)}
); }