'use client' import React, { useState, useEffect, useCallback } from 'react' import { useRouter } from 'next/navigation' import { useTheme } from '@/lib/ThemeContext' import { Sidebar } from '@/components/Sidebar' import { AudioButton } from '@/components/learn/AudioButton' import UnitBuilder, { type UnitWord } from './components/UnitBuilder' interface VocabWord { id: string english: string german: string word?: string lang?: string ipa_en: string ipa_de: string part_of_speech: string syllables_en: string[] syllables_de: string[] example_en: string example_de: string image_url: string difficulty: number tags: string[] translations?: Record } function vocabToUnit(w: VocabWord, searchLang: string): UnitWord { // Source = the word in the language we searched, Target = the translation const src = w.word || w.english || '' const tgt = searchLang === 'en' ? (w.german || '') : searchLang === 'de' ? (w.english || '') : (w.english || w.german || '') return { id: w.id, source_text: src, target_text: tgt, pos: w.part_of_speech } } export default function VocabularyPage() { const { isDark } = useTheme() const router = useRouter() const [query, setQuery] = useState('') const [results, setResults] = useState([]) const [isSearching, setIsSearching] = useState(false) const [filters, setFilters] = useState<{ tags: string[]; parts_of_speech: string[]; total_words: number }>({ tags: [], parts_of_speech: [], total_words: 0 }) const [posFilter, setPosFilter] = useState('') const [diffFilter, setDiffFilter] = useState(0) const [searchLang, setSearchLang] = useState('en') const [topics, setTopics] = useState<{ topic: string; words: string[]; display_words?: string[]; word_count: number }[]>([]) // Unit builder state (UnitWord format) const [unitWords, setUnitWords] = useState([]) const [isCreating, setIsCreating] = useState(false) const glassCard = isDark ? 'bg-white/10 backdrop-blur-xl border border-white/10' : 'bg-white/80 backdrop-blur-xl border border-black/5' const glassInput = isDark ? 'bg-white/10 border-white/20 text-white placeholder-white/40' : 'bg-white border-slate-200 text-slate-900 placeholder-slate-400' useEffect(() => { fetch('/api/vocabulary/filters') .then(r => r.ok ? r.json() : null) .then(d => { if (d) setFilters(d) }) .catch(() => {}) }, []) // Search with debounce useEffect(() => { if (!query.trim() && !posFilter && !diffFilter) { setResults([]) setTopics([]) return } const timer = setTimeout(async () => { setIsSearching(true) try { let url: string if (query.trim()) { url = `/api/vocabulary/search?q=${encodeURIComponent(query)}&lang=${searchLang}&limit=30&source=kaikki` } else { const params = new URLSearchParams({ limit: '30' }) if (posFilter) params.set('pos', posFilter) if (diffFilter) params.set('difficulty', String(diffFilter)) url = `/api/vocabulary/browse?${params}` } const resp = await fetch(url) if (resp.ok) { const data = await resp.json() setResults(data.words || []) } if (query.trim()) { const topicResp = await fetch(`/api/vocabulary/topics?q=${encodeURIComponent(query)}&lang=${searchLang}`) if (topicResp.ok) { const topicData = await topicResp.json() setTopics(topicData.topics || []) } } } catch (err) { console.error('Search failed:', err) } finally { setIsSearching(false) } }, 300) return () => clearTimeout(timer) }, [query, posFilter, diffFilter, searchLang]) const toggleWord = useCallback((word: VocabWord) => { setUnitWords(prev => { const exists = prev.find(w => w.id === word.id) if (exists) return prev.filter(w => w.id !== word.id) return [...prev, vocabToUnit(word, searchLang)] }) }, [searchLang]) const isSelected = (wordId: string) => unitWords.some(w => w.id === wordId) const createUnit = useCallback(async (title: string, sourceLang: string, targetLang: string) => { if (!title.trim() || unitWords.length === 0) return setIsCreating(true) try { const dictWords = unitWords.filter(w => !w.is_custom) const customWords = unitWords.filter(w => w.is_custom) const resp = await fetch('/api/vocabulary/units', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, word_ids: dictWords.map(w => w.id), custom_words: customWords.map(w => ({ source_text: w.source_text, target_text: w.target_text, })), source_lang: sourceLang, target_lang: targetLang, }), }) if (resp.ok) { const data = await resp.json() router.push(`/learn/${data.unit_id}/flashcards`) } } catch (err) { console.error('Create unit failed:', err) } finally { setIsCreating(false) } }, [unitWords, router]) const addTopicWords = useCallback(async (topic: { words: string[] }, showOnly: boolean) => { setIsSearching(true) const topicWords: VocabWord[] = [] for (const w of topic.words) { const r = await fetch(`/api/vocabulary/search?q=${encodeURIComponent(w)}&lang=en&limit=1&source=kaikki`) if (r.ok) { const d = await r.json() if (d.words?.[0]) topicWords.push(d.words[0]) } } if (!showOnly) { const newUnitWords = topicWords .filter(tw => !unitWords.find(uw => uw.id === tw.id)) .map(tw => vocabToUnit(tw, 'en')) setUnitWords(prev => [...prev, ...newUnitWords]) } setResults(topicWords) setIsSearching(false) }, [unitWords]) const noSearchResults = !isSearching && results.length === 0 && !!query.trim() && topics.length === 0 return (
{/* Header */}

Woerterbuch

{(filters as any).kaikki_total > 0 ? `${((filters as any).kaikki_total as number).toLocaleString()} Woerter in ${(filters as any).kaikki_languages} Sprachen` : 'Woerter suchen und Lernunits erstellen'}

{/* Left: Search + Results */}
{/* Search Bar */}
setQuery(e.target.value)} placeholder={searchLang === 'de' ? 'Deutsches Wort suchen...' : searchLang === 'en' ? 'English word search...' : 'Wort suchen...'} className={`flex-1 px-4 py-3 rounded-xl border outline-none text-lg ${glassInput}`} autoFocus />
{isSearching && (
)} {/* Topic suggestions */} {topics.length > 0 && (
{topics.map(topic => (
{topic.topic} ({topic.word_count})
{(topic.display_words || topic.words).slice(0, 15).map((w: string, i: number) => ( {w} ))} {topic.words.length > 15 && ( +{topic.words.length - 15} )}
))}
)} {/* No results message */} {noSearchResults && (

Keine Ergebnisse fuer "{query}"

Du kannst das Wort rechts manuell hinzufuegen →

)} {/* Result list */}
{results.map(word => (
toggleWord(word)} >
{word.image_url ? ( {word.english} ) : ( 📝 )}
{word.word || word.english} {word.ipa_en && {word.ipa_en}}
{word.german || word.english} {word.german && }
{word.part_of_speech && ( {word.part_of_speech} )}
))}
{/* Right: Unit Builder */}
) }