import { useState, useMemo } from 'react'; import { useVocabulary } from '../../hooks/useVocabulary'; import { StatCard } from '../layout/StatCard'; import { WordList } from './WordList'; import { KanjiBreakdown } from './KanjiBreakdown'; import { KanjiDetailPanel } from './KanjiDetailPanel'; import { ExclusionManager } from './ExclusionManager'; import { formatNumber } from '../../lib/formatters'; import { TrendChart } from '../trends/TrendChart'; import { FrequencyRankTable } from './FrequencyRankTable'; import { CrossAnimeWordsTable } from './CrossAnimeWordsTable'; import { buildVocabularySummary } from '../../lib/dashboard-data'; import type { ExcludedWord } from '../../hooks/useExcludedWords'; import type { KanjiEntry, VocabularyEntry } from '../../types/stats'; interface VocabularyTabProps { onNavigateToAnime?: (animeId: number) => void; onOpenWordDetail?: (wordId: number) => void; excluded: ExcludedWord[]; isExcluded: (w: { headword: string; word: string; reading: string }) => boolean; onRemoveExclusion: (w: ExcludedWord) => void; onClearExclusions: () => void; } function isProperNoun(w: VocabularyEntry): boolean { return w.pos2 === 'ε›Ίζœ‰εθ©ž'; } export function VocabularyTab({ onNavigateToAnime, onOpenWordDetail, excluded, isExcluded, onRemoveExclusion, onClearExclusions, }: VocabularyTabProps) { const { words, kanji, knownWords, loading, error } = useVocabulary(); const [selectedKanjiId, setSelectedKanjiId] = useState(null); const [hideNames, setHideNames] = useState(false); const [showExclusionManager, setShowExclusionManager] = useState(false); const hasNames = useMemo(() => words.some(isProperNoun), [words]); const filteredWords = useMemo(() => { let result = words; if (hideNames) result = result.filter((w) => !isProperNoun(w)); if (excluded.length > 0) result = result.filter((w) => !isExcluded(w)); return result; }, [words, hideNames, excluded, isExcluded]); const summary = useMemo( () => buildVocabularySummary(filteredWords, kanji), [filteredWords, kanji], ); const knownWordCount = useMemo(() => { if (knownWords.size === 0) return 0; let count = 0; for (const w of filteredWords) { if (knownWords.has(w.headword)) count += 1; } return count; }, [filteredWords, knownWords]); if (loading) { return (
Loading...
); } if (error) { return (
Error: {error}
); } const handleSelectWord = (entry: VocabularyEntry): void => { onOpenWordDetail?.(entry.wordId); }; const handleBarClick = (headword: string): void => { const match = filteredWords.find((w) => w.headword === headword); if (match) onOpenWordDetail?.(match.wordId); }; const openKanjiDetail = (entry: KanjiEntry): void => { setSelectedKanjiId(entry.kanjiId); }; return (
{knownWords.size > 0 && ( 0 ? Math.round((knownWordCount / summary.uniqueWords) * 100) : 0}%)`} color="text-ctp-green" /> )}
{hasNames && ( )}
setSelectedKanjiId(null)} onSelectWord={onOpenWordDetail} onNavigateToAnime={onNavigateToAnime} /> {showExclusionManager && ( setShowExclusionManager(false)} /> )}
); }