'use client'; import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { BookOpen, Plus, Loader2, Check, ChevronDown, X, Globe, ChevronRight } from 'lucide-react'; import { API_BASE } from '@/lib/config'; import { useI18n } from '@/lib/i18n'; import { cn } from '@/lib/utils'; import { Switch } from '@/components/ui/switch'; import { SUPPORTED_LANGUAGES } from '../glossaries/types'; import { languages as API_LANGUAGES } from '@/lib/api'; interface GlossaryOption { id: string; name: string; source_language: string; target_language: string; terms_count: number; } interface TemplateOption { id: string; name: string; description: string; source_lang: string; target_lang: string; terms_count: number; } interface GlossarySelectorProps { sourceLang: string; targetLang: string; isPro: boolean; mode: string; glossaryId: string | null; onChange: (id: string | null) => void; disabled?: boolean; } export function GlossarySelector({ sourceLang, targetLang, isPro, mode, glossaryId, onChange, disabled }: GlossarySelectorProps) { const { t } = useI18n(); const [glossaries, setGlossaries] = useState([]); const [templates, setTemplates] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isGlossaryEnabled, setIsGlossaryEnabled] = useState(!!glossaryId); const [selectedGlossaryDetail, setSelectedGlossaryDetail] = useState(null); const [isLoadingDetail, setIsLoadingDetail] = useState(false); const [importingId, setImportingId] = useState(null); const [error, setError] = useState(null); const [isOpen, setIsOpen] = useState(false); const containerRef = useRef(null); const [filterByLang, setFilterByLang] = useState(sourceLang !== 'auto'); useEffect(() => { setFilterByLang(sourceLang !== 'auto'); }, [sourceLang]); // Form states for adding term const [newSource, setNewSource] = useState(''); const [newTarget, setNewTarget] = useState(''); const [isAddingTerm, setIsAddingTerm] = useState(false); const fetchData = useCallback(async () => { try { const token = localStorage.getItem('token'); const headers: Record = {}; if (token) headers['Authorization'] = `Bearer ${token}`; const [glossaryRes, templateRes] = await Promise.all([ fetch(`${API_BASE}/api/v1/glossaries?per_page=100`, { headers }), fetch(`${API_BASE}/api/v1/glossaries/templates/list`, { headers }), ]); if (glossaryRes.ok) { const data = await glossaryRes.json(); setGlossaries(data.data || []); } if (templateRes.ok) { const data = await templateRes.json(); setTemplates(data.data || []); } } catch { // ignore } finally { setIsLoading(false); } }, []); const fetchGlossaryDetail = useCallback(async (id: string) => { setIsLoadingDetail(true); try { const token = localStorage.getItem('token'); const headers: Record = {}; if (token) headers['Authorization'] = `Bearer ${token}`; const res = await fetch(`${API_BASE}/api/v1/glossaries/${id}`, { headers }); if (res.ok) { const data = await res.json(); setSelectedGlossaryDetail(data.data || null); } } catch { // ignore } finally { setIsLoadingDetail(false); } }, []); useEffect(() => { fetchData(); }, [fetchData]); // Synchronize glossary detail and enablement state with props useEffect(() => { if (glossaryId) { setIsGlossaryEnabled(true); fetchGlossaryDetail(glossaryId); } else { setSelectedGlossaryDetail(null); } }, [glossaryId, fetchGlossaryDetail]); // Close dropdown on outside click useEffect(() => { function handleClick(e: MouseEvent) { if (containerRef.current && !containerRef.current.contains(e.target as Node)) { setIsOpen(false); } } if (isOpen) document.addEventListener('mousedown', handleClick); return () => document.removeEventListener('mousedown', handleClick); }, [isOpen]); const handleImportTemplate = async (template: TemplateOption) => { const existing = glossaries.find( g => g.name.toLowerCase().includes(template.name.toLowerCase().split('/')[0].trim()) ); if (existing) { onChange(existing.id); setIsGlossaryEnabled(true); setIsOpen(false); return; } const token = localStorage.getItem('token'); const headers: Record = { 'Content-Type': 'application/json' }; if (token) headers['Authorization'] = `Bearer ${token}`; setImportingId(template.id); setError(null); try { const res = await fetch(`${API_BASE}/api/v1/glossaries/import?template_id=${encodeURIComponent(template.id)}`, { method: 'POST', headers, }); if (res.ok) { const data = await res.json(); const newId = data.data?.id; await fetchData(); if (newId) { onChange(newId); setIsGlossaryEnabled(true); } setIsOpen(false); } else { const errData = await res.json().catch(() => null); setError(errData?.message || t('translate.glossary.importFailed').replace('{status}', String(res.status))); } } catch { setError(t('translate.glossary.networkError')); } finally { setImportingId(null); } }; const handleAddTerm = async (e: React.FormEvent) => { e.preventDefault(); if (!glossaryId || !newSource.trim() || !newTarget.trim()) return; setIsAddingTerm(true); setError(null); try { const token = localStorage.getItem('token'); const headers: Record = { 'Content-Type': 'application/json', }; if (token) headers['Authorization'] = `Bearer ${token}`; // Fetch latest terms const detailRes = await fetch(`${API_BASE}/api/v1/glossaries/${glossaryId}`, { headers }); let currentTerms = []; if (detailRes.ok) { const detailData = await detailRes.json(); currentTerms = detailData.data?.terms || []; } const mappedTerms = currentTerms.map((t: any) => ({ source: t.source, target: t.target, translations: t.translations || {} })); // Append const updatedTerms = [...mappedTerms, { source: newSource.trim(), target: newTarget.trim(), translations: {} }]; const res = await fetch(`${API_BASE}/api/v1/glossaries/${glossaryId}`, { method: 'PATCH', headers, body: JSON.stringify({ terms: updatedTerms }) }); if (res.ok) { setNewSource(''); setNewTarget(''); // Refresh details fetchGlossaryDetail(glossaryId); fetchData(); } else { const errData = await res.json().catch(() => null); setError(errData?.message || t('translate.glossary.addTermError')); } } catch { setError(t('translate.glossary.networkError')); } finally { setIsAddingTerm(false); } }; const getFlag = useCallback((code: string) => { return API_LANGUAGES.find(l => l.code === code)?.flag ?? SUPPORTED_LANGUAGES.find(l => l.code === code)?.flag ?? code.toUpperCase(); }, []); const sourceFlag = useMemo(() => sourceLang === 'auto' ? '' : getFlag(sourceLang), [sourceLang, getFlag]); const targetFlag = useMemo(() => getFlag(targetLang), [targetLang, getFlag]); // A glossary is compatible with the target language if: // - its target_language exactly matches, OR // - it's a multilingual glossary (target_language === 'multi') const isCompatible = useCallback((gTargetLang: string) => { return gTargetLang === targetLang || gTargetLang === 'multi'; }, [targetLang]); const filteredGlossaries = useMemo(() => { if (!filterByLang || sourceLang === 'auto') { return glossaries; } return glossaries .filter(g => g.source_language === sourceLang) .sort((a, b) => { // Compatible glossaries first, then incompatible const aOk = isCompatible(a.target_language) ? 0 : 1; const bOk = isCompatible(b.target_language) ? 0 : 1; return aOk - bOk; }); }, [glossaries, filterByLang, sourceLang, isCompatible]); const filteredTemplates = useMemo(() => { if (!filterByLang || sourceLang === 'auto') { return templates; } return templates.filter(t => t.source_lang === sourceLang); }, [templates, filterByLang, sourceLang]); const selected = glossaries.find(g => g.id === glossaryId); return (
{t('translate.glossary.title') || 'Glossaire & Terminologie'} ({sourceFlag || 'AUTO'}➔{targetFlag})
{/* Active switch slider */} {isPro && mode === 'llm' && ( )}
{mode === 'classic' ? (
{t('translate.glossary.classicMode') || 'Moteur neutre sans glossaire (IA uniquement)'}
) : !isPro ? (

{t('translate.glossary.proOnly') || 'Passez Pro pour appliquer vos glossaires terminologiques.'}

) : isGlossaryEnabled ? (
{/* Help Info text */}

{t('translate.glossary.helpText') || 'Le glossaire force la traduction de termes précis. Choisissez un glossaire dont la langue source correspond à la langue d\'origine de votre document.'}

{/* Mismatch Warning — source language */} {selected && sourceLang !== 'auto' && selected.source_language !== sourceLang && (
⚠️ {t('translate.glossary.sourceWarning')} {getFlag(selected.source_language)} {selected.source_language.toUpperCase()}, {t('translate.glossary.sourceWarningBut')} {getFlag(sourceLang)} {sourceLang.toUpperCase()}.
)} {/* Mismatch Warning — target language (skip for multilingual glossaries) */} {selected && selected.target_language !== 'multi' && selected.target_language && selected.target_language !== targetLang && (
🎯 {t('translate.glossary.targetWarning') || 'Incompatibilité de cible :'} {getFlag(selected.target_language)} {selected.target_language.toUpperCase()}, {t('translate.glossary.targetWarningBut') || 'mais votre document cible'} {targetFlag} {targetLang.toUpperCase()}. {t('translate.glossary.targetWarningEnd') || 'Les termes risquent de ne pas être pertinents.'}
)} {/* Select Glossary Trigger button */}
{/* Error message */} {error && (

{error}

)} {/* Selector Dropdown list */} {isOpen && !disabled && (
{/* Filter toggle header */} {sourceLang !== 'auto' && (glossaries.length > 0 || templates.length > 0) && (
{t('translate.glossary.filterByLang') || 'Filtrer par langue'} ({sourceFlag})
)} {/* Custom Glossaries Section */} {filteredGlossaries.length > 0 && (
{t('translate.glossary.myGlossaries') || 'Mes Glossaires'}
{filteredGlossaries.map(g => { const flag = getFlag(g.source_language); const isSelected = g.id === glossaryId; return ( ); })}
)} {/* Templates Section */} {filteredTemplates.length > 0 && (
{t('translate.glossary.availableTemplates') || 'Modèles disponibles'}
{filteredTemplates.map(tmpl => { const isImporting = importingId === tmpl.id; const existingGlossary = glossaries.find( g => g.name.toLowerCase().includes(tmpl.name.toLowerCase().split('/')[0].trim()) ); const isAlreadySelected = existingGlossary?.id === glossaryId; const flag = getFlag(tmpl.source_lang); const tFlag = tmpl.target_lang === 'multi' ? `🌐 ${t('translate.glossary.multilingual') || 'MULTILINGUE'}` : getFlag(tmpl.target_lang); return ( ); })}
)} {/* Empty State */} {filteredGlossaries.length === 0 && filteredTemplates.length === 0 && (

{t('translate.glossary.noGlossaryForSource') || 'Aucun glossaire ni modèle pour la langue source'} {sourceFlag || sourceLang.toUpperCase()}.

{t('translate.glossary.createGlossary') || 'Créer un glossaire'} {sourceLang === 'auto' ? '' : sourceLang.toUpperCase()} ➔ {filterByLang && (glossaries.length > 0 || templates.length > 0) && ( )}
)}
)}
{/* Dynamic Terms Preview block */} {selected && (
{t('translate.glossary.activePreview') || 'Aperçu des correspondances actives :'} {selectedGlossaryDetail?.terms?.length || selected.terms_count} {t('translate.glossary.total') || 'au total'}
{isLoadingDetail ? (
{t('translate.glossary.loading') || 'Chargement...'}
) : selectedGlossaryDetail?.terms && selectedGlossaryDetail.terms.length > 0 ? (
{selectedGlossaryDetail.terms.slice(0, 4).map((t: any, i: number) => { const translations = t.translations || {}; const displayTarget = translations[targetLang] || t.target; return (
{t.source} ➔ {displayTarget}
); })} {selectedGlossaryDetail.terms.length > 4 && ( + {selectedGlossaryDetail.terms.length - 4} {t('translate.glossary.moreTerms') || 'autres termes'} )}
) : (

{t('translate.glossary.noTerms') || 'Aucun terme dans ce glossaire.'}

)}
)} {/* Ultra-neat Quick Term Adder */} {selected && (
setNewSource(e.target.value)} disabled={isAddingTerm || disabled} className="w-full bg-white dark:bg-[#1a1a1a] border border-black/5 dark:border-white/5 rounded-lg px-2.5 py-1.5 text-xs font-semibold text-brand-dark dark:text-white placeholder:text-brand-dark/30 outline-none focus:border-brand-accent min-w-0" />
setNewTarget(e.target.value)} disabled={isAddingTerm || disabled} className="flex-1 bg-white dark:bg-[#1a1a1a] border border-black/5 dark:border-white/5 rounded-lg px-2.5 py-1.5 text-xs font-semibold text-brand-dark dark:text-white placeholder:text-brand-dark/30 outline-none focus:border-brand-accent min-w-0" />
)}
) : (
{t('translate.glossary.disabledMode') || 'Moteur neutre sans glossaire appliqué'}
)}
); }