import { useState } from 'react'; import { useTrends, type TimeRange, type GroupBy } from '../../hooks/useTrends'; import { DateRangeSelector } from './DateRangeSelector'; import { TrendChart } from './TrendChart'; import { StackedTrendChart } from './StackedTrendChart'; import { MAX_TITLES_MODES, MAX_TITLES_OPTIONS, buildAnimeVisibilityOptions, filterHiddenAnimeData, loadHiddenTitles, loadMaxTitles, loadMaxTitlesMode, loadShowEmptyDays, pruneHiddenAnime, saveHiddenTitles, saveMaxTitles, saveMaxTitlesMode, saveShowEmptyDays, type MaxTitlesMode, } from './anime-visibility'; import { LibrarySummarySection } from './LibrarySummarySection'; const MAX_TITLES_MODE_LABELS: Record = { recent: 'most recent', total: 'top', }; function SectionHeader({ children }: { children: React.ReactNode }) { return (

{children}

); } interface AnimeVisibilityFilterProps { animeTitles: string[]; hiddenAnime: ReadonlySet; maxTitles: number | null; maxTitlesMode: MaxTitlesMode; onShowAll: () => void; onHideAll: () => void; onToggleAnime: (title: string) => void; onMaxTitlesChange: (value: number | null) => void; onMaxTitlesModeChange: (mode: MaxTitlesMode) => void; } export function AnimeVisibilityFilter({ animeTitles, hiddenAnime, maxTitles, maxTitlesMode, onShowAll, onHideAll, onToggleAnime, onMaxTitlesChange, onMaxTitlesModeChange, }: AnimeVisibilityFilterProps) { if (animeTitles.length === 0) { return null; } return (

Title Visibility

Shared across all anime trend charts. Default: show everything. Selections are saved.

{animeTitles.map((title) => { const isVisible = !hiddenAnime.has(title); return ( ); })}
); } export function TrendsTab() { const [range, setRange] = useState('30d'); const [groupBy, setGroupBy] = useState('day'); const [showEmptyDays, setShowEmptyDays] = useState(() => loadShowEmptyDays()); const [hiddenAnime, setHiddenAnime] = useState>(() => loadHiddenTitles()); const [maxTitles, setMaxTitles] = useState(() => loadMaxTitles()); const [maxTitlesMode, setMaxTitlesMode] = useState(() => loadMaxTitlesMode()); const { data, loading, error } = useTrends(range, groupBy, showEmptyDays); const updateHiddenAnime = (next: Set) => { setHiddenAnime(next); saveHiddenTitles(next); }; const updateMaxTitles = (value: number | null) => { setMaxTitles(value); saveMaxTitles(value); }; const updateMaxTitlesMode = (mode: MaxTitlesMode) => { setMaxTitlesMode(mode); saveMaxTitlesMode(mode); }; const updateShowEmptyDays = (show: boolean) => { setShowEmptyDays(show); saveShowEmptyDays(show); }; const cardsMinedColor = 'var(--color-ctp-cards-mined)'; const cardsMinedStackedColors = [ cardsMinedColor, '#8aadf4', '#c6a0f6', '#f5a97f', '#f5bde6', '#91d7e3', '#ee99a0', '#f4dbd6', ]; if (loading) return
Loading...
; if (error) return
Error: {error}
; if (!data) return null; const animeTitles = buildAnimeVisibilityOptions([ data.animeCumulative.episodes, data.animeCumulative.cards, data.animeCumulative.words, data.animeCumulative.watchTime, ]); const activeHiddenAnime = pruneHiddenAnime(hiddenAnime, animeTitles); const filteredAnimeProgress = filterHiddenAnimeData( data.animeCumulative.episodes, activeHiddenAnime, ); const filteredCardsProgress = filterHiddenAnimeData( data.animeCumulative.cards, activeHiddenAnime, ); const filteredWordsProgress = filterHiddenAnimeData( data.animeCumulative.words, activeHiddenAnime, ); const filteredWatchTimeProgress = filterHiddenAnimeData( data.animeCumulative.watchTime, activeHiddenAnime, ); return (
Activity (per {groupBy === 'month' ? 'month' : 'day'}) Cumulative Totals Efficiency Patterns Library — Cumulative updateHiddenAnime(new Set())} onHideAll={() => updateHiddenAnime(new Set(animeTitles))} onToggleAnime={(title) => { const next = new Set(hiddenAnime); if (next.has(title)) { next.delete(title); } else { next.add(title); } updateHiddenAnime(next); }} onMaxTitlesChange={updateMaxTitles} onMaxTitlesModeChange={updateMaxTitlesMode} /> Library — Summary
); }