'use client'; import { useState, useRef } from 'react'; import { BookText, Upload, PenLine, ArrowRight, ArrowLeft, CheckCircle2, AlertCircle, Loader2, Scale, Cpu, TrendingUp, HeartPulse, Megaphone, Users, FlaskConical, ShoppingCart, X, Plus } from 'lucide-react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { cn } from '@/lib/utils'; import { useI18n } from '@/lib/i18n'; import { useGlossaries, useGlossaryTemplates } from '../useGlossaries'; import { SUPPORTED_LANGUAGES } from '../types'; import type { GlossaryTermInput } from '../types'; import { parseFileToTerms } from '../csvUtils'; import { useToast } from '@/components/ui/toast'; // ── Icônes par catégorie de modèle ───────────────────────────────────────── const TEMPLATE_ICONS: Record> = { legal: Scale, technology: Cpu, finance: TrendingUp, medical: HeartPulse, marketing: Megaphone, hr: Users, scientific: FlaskConical, ecommerce: ShoppingCart, }; // ── Types ────────────────────────────────────────────────────────────────── type CreationMethod = 'template' | 'file' | 'manual' | null; type Step = 1 | 2; export default function NewGlossaryPage() { const { t } = useI18n(); const router = useRouter(); const { toast } = useToast(); const { createGlossary, importTemplate, isCreating, isImportingTemplate } = useGlossaries(); const { templates, isLoading: isLoadingTemplates } = useGlossaryTemplates(); const [step, setStep] = useState(1); const [method, setMethod] = useState(null); // État pour modèle const [selectedTemplateId, setSelectedTemplateId] = useState(null); const [isImportingThis, setIsImportingThis] = useState(false); // État pour fichier const fileInputRef = useRef(null); const [fileStatus, setFileStatus] = useState<'idle' | 'parsing' | 'success' | 'error'>('idle'); const [fileError, setFileError] = useState(''); const [parsedTerms, setParsedTerms] = useState([]); const [parsedFileName, setParsedFileName] = useState(''); const [isDragging, setIsDragging] = useState(false); const [fileWizardStep, setFileWizardStep] = useState<1 | 2 | 3>(1); const [fileSrcLang, setFileSrcLang] = useState('fr'); const [fileTgtLang, setFileTgtLang] = useState('multi'); // État pour manuel const [manualName, setManualName] = useState(''); const [manualSrc, setManualSrc] = useState('fr'); const [manualTgt, setManualTgt] = useState('multi'); const isProcessing = isCreating || isImportingTemplate || isImportingThis; // ── Handlers ──────────────────────────────────────────────────────────── const handleSelectMethod = (m: CreationMethod) => { setMethod(m); setStep(2); // reset setSelectedTemplateId(null); setFileStatus('idle'); setParsedTerms([]); setParsedFileName(''); setManualName(''); setFileWizardStep(1); setFileSrcLang('fr'); setFileTgtLang('multi'); }; const handleBack = () => { setStep(1); setMethod(null); }; // Importer un modèle const handleImportTemplate = async () => { if (!selectedTemplateId) return; const tpl = templates.find(t => t.id === selectedTemplateId); setIsImportingThis(true); try { await importTemplate(selectedTemplateId, tpl?.name); toast({ title: 'Glossaire importé !', description: `Le modèle « ${tpl?.name} » a été ajouté à vos glossaires.` }); router.push('/dashboard/glossaries'); } catch { toast({ variant: 'destructive', title: 'Erreur', description: 'Impossible d\'importer ce modèle.' }); } finally { setIsImportingThis(false); } }; // Parser un fichier CSV const processFile = async (file: File) => { const ext = file.name.split('.').pop()?.toLowerCase(); if (!ext || !['csv', 'xlsx', 'xls', 'ods', 'txt', 'tsv'].includes(ext)) { setFileStatus('error'); setFileError('Format non supporté. Utilisez CSV, Excel (.xlsx), ODS, ou TSV.'); return; } if (file.size > 5 * 1024 * 1024) { setFileStatus('error'); setFileError('Fichier trop volumineux (maximum 5 MB).'); return; } setFileStatus('parsing'); setFileError(''); try { const terms = await parseFileToTerms(file); if (terms.length === 0) { setFileStatus('error'); setFileError('Le fichier est vide ou n\'a pas pu être lu.'); return; } setFileStatus('success'); setParsedTerms(terms); setParsedFileName(file.name.replace(/\.[^.]+$/, '').replace(/[_-]/g, ' ')); } catch { setFileStatus('error'); setFileError('Erreur lors de la lecture du fichier.'); } }; // Télécharger exemple CSV const handleDownloadSample = () => { const sample = `source,target\nbénéfice,profit\nflux de trésorerie,cash flow\nbilan,balance sheet\ncompte de résultat,income statement\nrésiliation,termination`; const blob = new Blob([sample], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'exemple_glossaire.csv'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; // Créer depuis fichier const handleCreateFromFile = async () => { if (!parsedTerms.length) return; try { await createGlossary({ name: parsedFileName || 'Mon glossaire', source_language: fileSrcLang, target_language: fileTgtLang, terms: parsedTerms, }); toast({ title: 'Glossaire créé !', description: `${parsedTerms.length} termes importés depuis votre fichier.` }); router.push('/dashboard/glossaries'); } catch { toast({ variant: 'destructive', title: 'Erreur', description: 'Impossible de créer le glossaire.' }); } }; // Créer manuellement const handleCreateManual = async () => { if (!manualName.trim()) return; try { await createGlossary({ name: manualName.trim(), source_language: manualSrc, target_language: manualTgt, terms: [] }); toast({ title: 'Glossaire créé !', description: `« ${manualName} » est prêt. Ajoutez vos premiers termes.` }); router.push('/dashboard/glossaries'); } catch { toast({ variant: 'destructive', title: 'Erreur', description: 'Impossible de créer le glossaire.' }); } }; // ── Rendu ─────────────────────────────────────────────────────────────── return (
{/* ── Fil d'Ariane ──────────────────────────────────────────── */}
Mes glossaires Nouveau glossaire
{/* ── Indicateur d'étapes ────────────────────────────────────── */}
{/* Étape 1 */}
= 1 ? 'bg-[#1A1A1A] text-white' : 'bg-[#EBEBEB] text-[#888888]' )}> {step > 1 ? : '1'}
Choisir le type
{/* Étape 2 */}
2
Configurer
{/* ══════════════ ÉTAPE 1 : Choisir le type ══════════════════ */} {step === 1 && (

Comment voulez-vous créer votre{' '} glossaire ?

Choisissez la méthode qui correspond à votre situation.

{/* Option 1 — Modèle professionnel */} {/* Option 2 — Fichier CSV */} {/* Option 3 — Manuel */}
)} {/* ══════════════ ÉTAPE 2 : Configurer ═══════════════════════ */} {step === 2 && (
{/* Bouton retour */} {/* ── CAS A : Depuis un modèle ─── */} {method === 'template' && (

Choisissez un modèle professionnel

Sélectionnez le modèle le plus adapté à vos documents. Vous pourrez modifier les termes après l'import.

{isLoadingTemplates ? (
) : templates.length === 0 ? (

Tous les modèles disponibles ont déjà été importés.

) : ( <>
{templates.map((tpl) => { const Icon = TEMPLATE_ICONS[tpl.id] || BookText; const isSelected = selectedTemplateId === tpl.id; return ( ); })}
{/* Bouton d'import */}
{selectedTemplateId && (

Modèle sélectionné : {templates.find(t => t.id === selectedTemplateId)?.name.split(' - ')[0]}

)}
)}
)} {/* ── CAS B : Depuis un fichier — Wizard 3 sous-étapes ─── */} {method === 'file' && (

Importez votre fichier de termes

{/* Indicateur sous-étapes */}
{([1, 2, 3] as const).map((s, i) => { const labels = ['Format', 'Fichier', 'Configurer']; const done = fileWizardStep > s; const active = fileWizardStep === s; return ( <>
{done ? : s}
{i < 2 &&
} ); })}
{/* ── Sous-étape 1 : Format ─── */} {fileWizardStep === 1 && (

Format attendu

Votre fichier doit avoir 2 colonnes : la première pour les termes source, la seconde pour les termes cibles. La première ligne peut être un en-tête (elle sera ignorée automatiquement).

{/* Tableau exemple */}
source
target
{[['bénéfice', 'profit'], ['flux de trésorerie', 'cash flow'], ['bilan', 'balance sheet'], ['résiliation', 'termination']].map(([s, t]) => (
{s}
{t}
))}
{/* Formats acceptés */}
{['CSV (.csv)', 'Excel (.xlsx)', 'Excel (.xls)', 'ODS (.ods)', 'TSV (.tsv)', 'Texte (.txt)'].map(f => ( {f} ))}
{/* Règles */}
{[ 'Séparateur : virgule (CSV) ou tabulation (TSV)', 'Encodage recommandé : UTF-8', 'Taille maximale : 5 MB', 'Limite : 500 termes par glossaire', 'Les champs contenant des virgules doivent être entre guillemets', ].map(rule => (
{rule}
))}
{/* Télécharger exemple */}
)} {/* ── Sous-étape 2 : Fichier ─── */} {fileWizardStep === 2 && (
{/* Zone de dépôt */}
{ e.preventDefault(); setIsDragging(true); }} onDragLeave={() => setIsDragging(false)} onDrop={e => { e.preventDefault(); setIsDragging(false); const f = e.dataTransfer.files[0]; if (f) processFile(f); }} onClick={() => fileInputRef.current?.click()} className={cn( 'flex flex-col items-center justify-center gap-4 p-12 rounded-2xl border-2 border-dashed cursor-pointer transition-all min-h-[220px]', isDragging ? 'border-[#C5A17A] bg-[#F5F0EA]' : 'border-[#D9D6D0] dark:border-white/10 hover:border-[#C5A17A] hover:bg-[#FAFAF8] dark:hover:bg-white/[0.02]', fileStatus === 'error' && 'border-red-400 bg-red-50 dark:bg-red-500/5' )} > { const f = e.target.files?.[0]; if (f) processFile(f); e.target.value = ''; }} /> {fileStatus === 'parsing' && ( <>

Lecture du fichier…

)} {fileStatus === 'success' && ( <>

{parsedTerms.length} termes détectés

Cliquez pour changer de fichier

)} {fileStatus === 'error' && ( <>

{fileError}

Cliquez pour réessayer

)} {fileStatus === 'idle' && ( <>

Glissez votre fichier ici

CSV, Excel, ODS, TSV — max 5 MB

ou cliquez pour parcourir )}
)} {/* ── Sous-étape 3 : Configurer ─── */} {fileWizardStep === 3 && (
{/* Résumé fichier */}

{parsedTerms.length} termes prêts à importer

{/* Nom */}
setParsedFileName(e.target.value)} placeholder="Ex : Termes RH internes, Glossaire juridique…" className="w-full px-4 py-3 rounded-xl border border-[#D9D6D0] dark:border-white/10 bg-[#FAFAF8] dark:bg-[#1A1A1A] text-[#1A1A1A] dark:text-white text-sm placeholder:text-[#AAAAAA] dark:placeholder:text-white/25 focus:outline-none focus:ring-2 focus:ring-brand-accent/30 focus:border-brand-accent/50" />
{/* Langues */}

Langue de la 1ère colonne de votre fichier

Langue de la 2ème colonne de votre fichier

{/* Info */}

Conseil :{' '} Choisissez Multilingue comme langue cible si vous prévoyez d'utiliser ce glossaire pour traduire vers plusieurs langues. Vous pourrez ajouter des traductions dans l'éditeur.

)}
)} {/* ── CAS C : Manuel ─── */} {method === 'manual' && (

Créez votre glossaire manuellement

Donnez un nom à votre glossaire et choisissez les langues. Vous ajouterez les termes dans l'éditeur.

{/* Nom */}
setManualName(e.target.value)} placeholder="Ex : Termes juridiques internes, Glossaire RH…" className="w-full px-4 py-3 rounded-xl border border-[#D9D6D0] dark:border-white/10 bg-[#FAFAF8] dark:bg-[#1A1A1A] text-[#1A1A1A] dark:text-white text-sm placeholder:text-[#AAAAAA] dark:placeholder:text-white/25 focus:outline-none focus:ring-2 focus:ring-brand-accent/30 focus:border-brand-accent/50" />
{/* Langues */}
{/* Info */}

Que se passe-t-il ensuite ?{' '} Un glossaire vide sera créé. Vous serez redirigé vers l'éditeur pour ajouter vos termes.

)}
)}
); }