Files
office_translator/frontend/src/app/dashboard/glossaries/new/page.tsx
sepehr ebb2537fda
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m21s
feat(glossaries): implement a 3-step wizard for CSV/file imports with custom source/target language selection
2026-06-28 11:38:18 +02:00

750 lines
40 KiB
TypeScript

'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<string, React.ComponentType<{ size?: number; className?: string }>> = {
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<Step>(1);
const [method, setMethod] = useState<CreationMethod>(null);
// État pour modèle
const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(null);
const [isImportingThis, setIsImportingThis] = useState(false);
// État pour fichier
const fileInputRef = useRef<HTMLInputElement>(null);
const [fileStatus, setFileStatus] = useState<'idle' | 'parsing' | 'success' | 'error'>('idle');
const [fileError, setFileError] = useState('');
const [parsedTerms, setParsedTerms] = useState<GlossaryTermInput[]>([]);
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 (
<div className="max-w-4xl mx-auto w-full p-6 lg:p-8">
{/* ── Fil d'Ariane ──────────────────────────────────────────── */}
<div className="flex items-center gap-2 mb-8 text-[11px] font-medium text-[#555555] dark:text-white/40">
<Link href="/dashboard/glossaries" className="hover:text-[#1A1A1A] dark:hover:text-white transition-colors">
Mes glossaires
</Link>
<ArrowRight size={11} />
<span className="text-[#1A1A1A] dark:text-white font-bold">Nouveau glossaire</span>
</div>
{/* ── Indicateur d'étapes ────────────────────────────────────── */}
<div className="flex items-center gap-4 mb-10">
{/* Étape 1 */}
<div className="flex items-center gap-2">
<div className={cn(
'w-7 h-7 rounded-full flex items-center justify-center text-[11px] font-black transition-all',
step >= 1 ? 'bg-[#1A1A1A] text-white' : 'bg-[#EBEBEB] text-[#888888]'
)}>
{step > 1 ? <CheckCircle2 size={14} /> : '1'}
</div>
<span className={cn('text-xs font-bold uppercase tracking-wider', step === 1 ? 'text-[#1A1A1A] dark:text-white' : 'text-[#888888]')}>
Choisir le type
</span>
</div>
<div className="flex-1 h-px bg-[#E5E3DF] dark:bg-white/10" />
{/* Étape 2 */}
<div className="flex items-center gap-2">
<div className={cn(
'w-7 h-7 rounded-full flex items-center justify-center text-[11px] font-black transition-all',
step === 2 ? 'bg-[#1A1A1A] text-white' : 'bg-[#EBEBEB] text-[#888888]'
)}>
2
</div>
<span className={cn('text-xs font-bold uppercase tracking-wider', step === 2 ? 'text-[#1A1A1A] dark:text-white' : 'text-[#888888]')}>
Configurer
</span>
</div>
</div>
{/* ══════════════ ÉTAPE 1 : Choisir le type ══════════════════ */}
{step === 1 && (
<div className="animate-fade-in">
<div className="mb-8">
<h1 className="text-3xl font-serif font-medium text-[#1A1A1A] dark:text-white tracking-tight mb-2">
Comment voulez-vous créer votre{' '}
<span className="italic">glossaire ?</span>
</h1>
<p className="text-[#555555] dark:text-white/50 text-sm font-light">
Choisissez la méthode qui correspond à votre situation.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-5">
{/* Option 1 — Modèle professionnel */}
<button
onClick={() => handleSelectMethod('template')}
className="group text-left p-7 bg-white dark:bg-[#141414] border-2 border-[#E5E3DF] dark:border-white/10 rounded-2xl hover:border-[#C5A17A] dark:hover:border-brand-accent/50 hover:shadow-lg transition-all cursor-pointer"
>
<div className="w-12 h-12 rounded-xl bg-[#F0EDE8] dark:bg-brand-accent/10 flex items-center justify-center text-[#8B6F47] mb-5 group-hover:scale-110 transition-transform">
<BookText size={24} />
</div>
<h2 className="text-base font-serif font-bold text-[#1A1A1A] dark:text-white mb-2 tracking-tight">
Depuis un modèle
</h2>
<p className="text-[12px] text-[#555555] dark:text-white/50 font-light leading-relaxed">
Choisissez parmi nos modèles professionnels pré-remplis : RH, Médical, Juridique, Tech
</p>
<div className="mt-5 flex items-center gap-1.5 text-[11px] font-bold text-[#8B6F47] dark:text-brand-accent uppercase tracking-wider">
Choisir un modèle <ArrowRight size={12} />
</div>
</button>
{/* Option 2 — Fichier CSV */}
<button
onClick={() => handleSelectMethod('file')}
className="group text-left p-7 bg-white dark:bg-[#141414] border-2 border-[#E5E3DF] dark:border-white/10 rounded-2xl hover:border-[#C5A17A] dark:hover:border-brand-accent/50 hover:shadow-lg transition-all cursor-pointer"
>
<div className="w-12 h-12 rounded-xl bg-[#F0EDE8] dark:bg-brand-accent/10 flex items-center justify-center text-[#8B6F47] mb-5 group-hover:scale-110 transition-transform">
<Upload size={24} />
</div>
<h2 className="text-base font-serif font-bold text-[#1A1A1A] dark:text-white mb-2 tracking-tight">
Importer un fichier
</h2>
<p className="text-[12px] text-[#555555] dark:text-white/50 font-light leading-relaxed">
Importez vos propres termes depuis un fichier CSV, Excel (.xlsx) ou ODS existant.
</p>
<div className="mt-5 flex items-center gap-1.5 text-[11px] font-bold text-[#8B6F47] dark:text-brand-accent uppercase tracking-wider">
Importer un fichier <ArrowRight size={12} />
</div>
</button>
{/* Option 3 — Manuel */}
<button
onClick={() => handleSelectMethod('manual')}
className="group text-left p-7 bg-white dark:bg-[#141414] border-2 border-[#E5E3DF] dark:border-white/10 rounded-2xl hover:border-[#C5A17A] dark:hover:border-brand-accent/50 hover:shadow-lg transition-all cursor-pointer"
>
<div className="w-12 h-12 rounded-xl bg-[#F0EDE8] dark:bg-brand-accent/10 flex items-center justify-center text-[#8B6F47] mb-5 group-hover:scale-110 transition-transform">
<PenLine size={24} />
</div>
<h2 className="text-base font-serif font-bold text-[#1A1A1A] dark:text-white mb-2 tracking-tight">
Créer manuellement
</h2>
<p className="text-[12px] text-[#555555] dark:text-white/50 font-light leading-relaxed">
Partez de zéro et ajoutez vos termes un par un directement dans l'éditeur.
</p>
<div className="mt-5 flex items-center gap-1.5 text-[11px] font-bold text-[#8B6F47] dark:text-brand-accent uppercase tracking-wider">
Créer vide <ArrowRight size={12} />
</div>
</button>
</div>
</div>
)}
{/* ══════════════ ÉTAPE 2 : Configurer ═══════════════════════ */}
{step === 2 && (
<div className="animate-fade-in">
{/* Bouton retour */}
<button
onClick={handleBack}
className="flex items-center gap-2 text-[11px] font-bold uppercase tracking-wider text-[#555555] hover:text-[#1A1A1A] dark:hover:text-white transition-colors mb-8 cursor-pointer"
>
<ArrowLeft size={14} /> Retour
</button>
{/* ── CAS A : Depuis un modèle ─── */}
{method === 'template' && (
<div>
<h1 className="text-2xl font-serif font-medium text-[#1A1A1A] dark:text-white mb-2">
Choisissez un <span className="italic">modèle professionnel</span>
</h1>
<p className="text-[#555555] dark:text-white/50 text-sm font-light mb-8">
Sélectionnez le modèle le plus adapté à vos documents. Vous pourrez modifier les termes après l'import.
</p>
{isLoadingTemplates ? (
<div className="flex items-center justify-center py-20">
<Loader2 className="size-8 animate-spin text-[#C5A17A]" />
</div>
) : templates.length === 0 ? (
<div className="p-12 text-center bg-white dark:bg-[#141414] border border-[#E5E3DF] dark:border-white/5 rounded-2xl">
<p className="text-sm text-[#555555] dark:text-white/40">
Tous les modèles disponibles ont déjà é importés.
</p>
<button onClick={handleBack} className="mt-4 text-[11px] font-bold text-[#7A5C35] hover:underline cursor-pointer">
Choisir une autre méthode
</button>
</div>
) : (
<>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
{templates.map((tpl) => {
const Icon = TEMPLATE_ICONS[tpl.id] || BookText;
const isSelected = selectedTemplateId === tpl.id;
return (
<button
key={tpl.id}
onClick={() => setSelectedTemplateId(isSelected ? null : tpl.id)}
className={cn(
'text-left p-5 rounded-xl border-2 transition-all cursor-pointer',
isSelected
? 'border-[#1A1A1A] dark:border-white bg-[#F5F3EF] dark:bg-white/5 shadow-md'
: 'border-[#E5E3DF] dark:border-white/10 bg-white dark:bg-[#141414] hover:border-[#C5A17A] dark:hover:border-brand-accent/40'
)}
>
<div className="flex items-start gap-4">
<div className={cn(
'w-10 h-10 rounded-xl flex items-center justify-center shrink-0',
isSelected ? 'bg-[#1A1A1A] text-white' : 'bg-[#F0EDE8] dark:bg-brand-accent/10 text-[#8B6F47]'
)}>
<Icon size={18} />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2">
<p className="text-sm font-serif font-bold text-[#1A1A1A] dark:text-white leading-tight">
{tpl.name.split(' - ')[0]}
</p>
<span className="shrink-0 text-[9px] font-mono font-semibold text-[#666666] dark:text-white/40">
{tpl.terms_count} termes
</span>
</div>
<p className="text-[11px] text-[#555555] dark:text-white/40 font-light mt-1 line-clamp-2">
{tpl.description}
</p>
</div>
{isSelected && (
<CheckCircle2 size={18} className="text-[#1A1A1A] dark:text-white shrink-0 mt-0.5" />
)}
</div>
</button>
);
})}
</div>
{/* Bouton d'import */}
<div className="flex items-center justify-end gap-4">
{selectedTemplateId && (
<p className="text-[11px] text-[#555555] dark:text-white/40 font-light">
Modèle sélectionné : <strong className="text-[#1A1A1A] dark:text-white">{templates.find(t => t.id === selectedTemplateId)?.name.split(' - ')[0]}</strong>
</p>
)}
<button
onClick={handleImportTemplate}
disabled={!selectedTemplateId || isProcessing}
className="flex items-center gap-2 px-7 py-3 rounded-xl bg-[#1A1A1A] hover:bg-[#333333] text-white text-xs font-bold uppercase tracking-widest transition-all disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
>
{isImportingThis ? <><Loader2 size={14} className="animate-spin" /> Import en cours</> : <><CheckCircle2 size={14} /> Importer ce modèle</>}
</button>
</div>
</>
)}
</div>
)}
{/* ── CAS B : Depuis un fichier — Wizard 3 sous-étapes ─── */}
{method === 'file' && (
<div>
<h1 className="text-2xl font-serif font-medium text-[#1A1A1A] dark:text-white mb-2">
Importez votre <span className="italic">fichier de termes</span>
</h1>
{/* Indicateur sous-étapes */}
<div className="flex items-center gap-2 mb-8">
{([1, 2, 3] as const).map((s, i) => {
const labels = ['Format', 'Fichier', 'Configurer'];
const done = fileWizardStep > s;
const active = fileWizardStep === s;
return (
<>
<div key={s} className="flex items-center gap-1.5">
<div className={cn(
'w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-black transition-all',
done ? 'bg-emerald-600 text-white' : active ? 'bg-[#1A1A1A] text-white' : 'bg-[#EBEBEB] dark:bg-white/10 text-[#888]'
)}>
{done ? <CheckCircle2 size={12} /> : s}
</div>
<span className={cn('text-[10px] font-bold uppercase tracking-wider hidden sm:block',
active ? 'text-[#1A1A1A] dark:text-white' : 'text-[#AAAAAA]'
)}>{labels[i]}</span>
</div>
{i < 2 && <div className="flex-1 h-px bg-[#E5E3DF] dark:bg-white/10" />}
</>
);
})}
</div>
{/* ── Sous-étape 1 : Format ─── */}
{fileWizardStep === 1 && (
<div className="space-y-5">
<div className="bg-white dark:bg-[#141414] border border-[#E5E3DF] dark:border-white/5 rounded-2xl p-6 space-y-5">
<div>
<p className="text-xs font-bold text-[#333333] dark:text-white/70 uppercase tracking-wider mb-3">Format attendu</p>
<p className="text-[12px] text-[#555555] dark:text-white/50 font-light mb-4 leading-relaxed">
Votre fichier doit avoir <strong className="text-[#1A1A1A] dark:text-white">2 colonnes</strong> : 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).
</p>
{/* Tableau exemple */}
<div className="rounded-xl overflow-hidden border border-[#E5E3DF] dark:border-white/10 text-[11px] font-mono">
<div className="grid grid-cols-2 bg-[#F5F3EF] dark:bg-white/5 border-b border-[#E5E3DF] dark:border-white/10">
<div className="px-4 py-2 font-bold text-[#8B6F47] uppercase tracking-wider">source</div>
<div className="px-4 py-2 font-bold text-[#8B6F47] uppercase tracking-wider border-l border-[#E5E3DF] dark:border-white/10">target</div>
</div>
{[['bénéfice', 'profit'], ['flux de trésorerie', 'cash flow'], ['bilan', 'balance sheet'], ['résiliation', 'termination']].map(([s, t]) => (
<div key={s} className="grid grid-cols-2 border-b border-[#E5E3DF] dark:border-white/5 last:border-0 hover:bg-[#FAFAF8] dark:hover:bg-white/[0.02]">
<div className="px-4 py-2 text-[#1A1A1A] dark:text-white">{s}</div>
<div className="px-4 py-2 text-[#555555] dark:text-white/60 border-l border-[#E5E3DF] dark:border-white/10">{t}</div>
</div>
))}
</div>
</div>
{/* Formats acceptés */}
<div className="flex flex-wrap gap-2">
{['CSV (.csv)', 'Excel (.xlsx)', 'Excel (.xls)', 'ODS (.ods)', 'TSV (.tsv)', 'Texte (.txt)'].map(f => (
<span key={f} className="px-2.5 py-1 rounded-lg bg-[#F0EDE8] dark:bg-white/5 text-[10px] font-bold text-[#8B6F47] dark:text-brand-accent uppercase tracking-wider">{f}</span>
))}
</div>
{/* Règles */}
<div className="space-y-2">
{[
'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 => (
<div key={rule} className="flex items-start gap-2 text-[11px] text-[#555555] dark:text-white/50">
<CheckCircle2 size={12} className="text-emerald-500 mt-0.5 shrink-0" />
{rule}
</div>
))}
</div>
</div>
{/* Télécharger exemple */}
<div className="flex items-center justify-between">
<button
onClick={handleDownloadSample}
className="flex items-center gap-2 text-[11px] font-bold text-[#8B6F47] dark:text-brand-accent hover:underline cursor-pointer"
>
<Upload size={12} /> Télécharger un exemple CSV
</button>
<button
onClick={() => setFileWizardStep(2)}
className="flex items-center gap-2 px-6 py-2.5 rounded-xl bg-[#1A1A1A] hover:bg-[#333333] text-white text-xs font-bold uppercase tracking-widest transition-all cursor-pointer"
>
Suivant <ArrowRight size={13} />
</button>
</div>
</div>
)}
{/* ── Sous-étape 2 : Fichier ─── */}
{fileWizardStep === 2 && (
<div className="space-y-5">
{/* Zone de dépôt */}
<div
onDragOver={e => { 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'
)}
>
<input
ref={fileInputRef}
type="file"
accept=".csv,.xlsx,.xls,.ods,.txt,.tsv"
className="hidden"
onChange={e => { const f = e.target.files?.[0]; if (f) processFile(f); e.target.value = ''; }}
/>
{fileStatus === 'parsing' && (
<><Loader2 size={32} className="animate-spin text-[#C5A17A]" /><p className="text-sm font-medium text-[#555555] dark:text-white/50">Lecture du fichier</p></>
)}
{fileStatus === 'success' && (
<>
<CheckCircle2 size={36} className="text-emerald-600" />
<div className="text-center">
<p className="text-sm font-bold text-emerald-700 dark:text-emerald-400">{parsedTerms.length} termes détectés</p>
<p className="text-[11px] text-[#555555] dark:text-white/40 mt-1">Cliquez pour changer de fichier</p>
</div>
</>
)}
{fileStatus === 'error' && (
<>
<AlertCircle size={36} className="text-red-500" />
<div className="text-center">
<p className="text-sm font-medium text-red-600">{fileError}</p>
<p className="text-[11px] text-[#555555] dark:text-white/40 mt-1">Cliquez pour réessayer</p>
</div>
</>
)}
{fileStatus === 'idle' && (
<>
<div className="w-16 h-16 rounded-2xl bg-[#F0EDE8] dark:bg-white/5 flex items-center justify-center text-[#8B6F47]">
<Upload size={32} />
</div>
<div className="text-center">
<p className="text-sm font-serif font-bold text-[#1A1A1A] dark:text-white">Glissez votre fichier ici</p>
<p className="text-[11px] text-[#555555] dark:text-white/40 mt-1">CSV, Excel, ODS, TSV max 5 MB</p>
</div>
<span className="text-[10px] text-[#AAAAAA] font-light">ou cliquez pour parcourir</span>
</>
)}
</div>
<div className="flex items-center justify-between">
<button onClick={() => setFileWizardStep(1)} className="flex items-center gap-2 text-[11px] font-bold uppercase tracking-wider text-[#555555] hover:text-[#1A1A1A] dark:hover:text-white transition-colors cursor-pointer">
<ArrowLeft size={13} /> Retour
</button>
<button
onClick={() => setFileWizardStep(3)}
disabled={fileStatus !== 'success'}
className="flex items-center gap-2 px-6 py-2.5 rounded-xl bg-[#1A1A1A] hover:bg-[#333333] text-white text-xs font-bold uppercase tracking-widest transition-all disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
>
Suivant <ArrowRight size={13} />
</button>
</div>
</div>
)}
{/* ── Sous-étape 3 : Configurer ─── */}
{fileWizardStep === 3 && (
<div className="space-y-5">
<div className="bg-white dark:bg-[#141414] border border-[#E5E3DF] dark:border-white/5 rounded-2xl p-6 space-y-6">
{/* Résumé fichier */}
<div className="flex items-center gap-3 p-3 rounded-xl bg-emerald-50 dark:bg-emerald-500/5 border border-emerald-200 dark:border-emerald-500/20">
<CheckCircle2 size={16} className="text-emerald-600 shrink-0" />
<div>
<p className="text-[11px] font-bold text-emerald-800 dark:text-emerald-400">{parsedTerms.length} termes prêts à importer</p>
<button onClick={() => { setFileWizardStep(2); setFileStatus('idle'); setParsedTerms([]); }} className="text-[10px] text-emerald-700 dark:text-emerald-500 hover:underline cursor-pointer">
Changer de fichier
</button>
</div>
</div>
{/* Nom */}
<div>
<label className="block text-xs font-bold text-[#333333] dark:text-white/70 uppercase tracking-wider mb-2">
Nom du glossaire <span className="text-red-500">*</span>
</label>
<input
type="text"
value={parsedFileName}
onChange={e => 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"
/>
</div>
{/* Langues */}
<div className="grid grid-cols-2 gap-5">
<div>
<label className="block text-xs font-bold text-[#333333] dark:text-white/70 uppercase tracking-wider mb-2">
Langue source
</label>
<select
value={fileSrcLang}
onChange={e => setFileSrcLang(e.target.value)}
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 focus:outline-none focus:ring-2 focus:ring-brand-accent/30"
>
{SUPPORTED_LANGUAGES.filter(l => l.code !== 'multi').map(l => (
<option key={l.code} value={l.code}>{l.flag} {l.label}</option>
))}
</select>
<p className="text-[10px] text-[#AAAAAA] dark:text-white/30 mt-1.5 font-light">Langue de la 1ère colonne de votre fichier</p>
</div>
<div>
<label className="block text-xs font-bold text-[#333333] dark:text-white/70 uppercase tracking-wider mb-2">
Langue cible
</label>
<select
value={fileTgtLang}
onChange={e => setFileTgtLang(e.target.value)}
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 focus:outline-none focus:ring-2 focus:ring-brand-accent/30"
>
{SUPPORTED_LANGUAGES.map(l => (
<option key={l.code} value={l.code}>{l.flag} {l.label}</option>
))}
</select>
<p className="text-[10px] text-[#AAAAAA] dark:text-white/30 mt-1.5 font-light">Langue de la 2ème colonne de votre fichier</p>
</div>
</div>
{/* Info */}
<div className="p-3.5 rounded-xl bg-[#F5F3EF] dark:bg-white/[0.02] border border-[#D9D6D0] dark:border-white/5">
<p className="text-[11px] text-[#555555] dark:text-white/50 font-light leading-relaxed">
<strong className="font-bold text-[#1A1A1A] dark:text-white/80">Conseil :</strong>{' '}
Choisissez <strong>Multilingue</strong> comme langue cible si vous prévoyez d'utiliser ce glossaire pour traduire vers plusieurs langues. Vous pourrez ajouter des traductions dans l'éditeur.
</p>
</div>
</div>
<div className="flex items-center justify-between">
<button onClick={() => setFileWizardStep(2)} className="flex items-center gap-2 text-[11px] font-bold uppercase tracking-wider text-[#555555] hover:text-[#1A1A1A] dark:hover:text-white transition-colors cursor-pointer">
<ArrowLeft size={13} /> Retour
</button>
<button
onClick={handleCreateFromFile}
disabled={!parsedFileName.trim() || isProcessing}
className="flex items-center gap-2 px-7 py-3 rounded-xl bg-[#1A1A1A] hover:bg-[#333333] text-white text-xs font-bold uppercase tracking-widest transition-all disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
>
{isCreating ? <><Loader2 size={14} className="animate-spin" /> Création</> : <><CheckCircle2 size={14} /> Créer le glossaire</>}
</button>
</div>
</div>
)}
</div>
)}
{/* ── CAS C : Manuel ─── */}
{method === 'manual' && (
<div>
<h1 className="text-2xl font-serif font-medium text-[#1A1A1A] dark:text-white mb-2">
Créez votre glossaire <span className="italic">manuellement</span>
</h1>
<p className="text-[#555555] dark:text-white/50 text-sm font-light mb-8">
Donnez un nom à votre glossaire et choisissez les langues. Vous ajouterez les termes dans l'éditeur.
</p>
<div className="bg-white dark:bg-[#141414] border border-[#E5E3DF] dark:border-white/5 rounded-2xl p-8 space-y-6">
{/* Nom */}
<div>
<label className="block text-xs font-bold text-[#333333] dark:text-white/70 uppercase tracking-wider mb-2">
Nom du glossaire <span className="text-red-500">*</span>
</label>
<input
type="text"
value={manualName}
onChange={e => 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"
/>
</div>
{/* Langues */}
<div className="grid grid-cols-2 gap-5">
<div>
<label className="block text-xs font-bold text-[#333333] dark:text-white/70 uppercase tracking-wider mb-2">
Langue source
</label>
<select
value={manualSrc}
onChange={e => setManualSrc(e.target.value)}
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 focus:outline-none focus:ring-2 focus:ring-brand-accent/30"
>
{SUPPORTED_LANGUAGES.filter(l => l.code !== 'multi').map(l => (
<option key={l.code} value={l.code}>{l.flag} {l.label}</option>
))}
</select>
</div>
<div>
<label className="block text-xs font-bold text-[#333333] dark:text-white/70 uppercase tracking-wider mb-2">
Langue cible
</label>
<select
value={manualTgt}
onChange={e => setManualTgt(e.target.value)}
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 focus:outline-none focus:ring-2 focus:ring-brand-accent/30"
>
{SUPPORTED_LANGUAGES.map(l => (
<option key={l.code} value={l.code}>{l.flag} {l.label}</option>
))}
</select>
</div>
</div>
{/* Info */}
<div className="p-3.5 rounded-xl bg-[#F5F3EF] dark:bg-white/[0.02] border border-[#D9D6D0] dark:border-white/5">
<p className="text-[11px] text-[#555555] dark:text-white/50 font-light leading-relaxed">
<strong className="font-bold text-[#1A1A1A] dark:text-white/80">Que se passe-t-il ensuite ?</strong>{' '}
Un glossaire vide sera créé. Vous serez redirigé vers l'éditeur pour ajouter vos termes.
</p>
</div>
</div>
<div className="flex justify-end mt-6">
<button
onClick={handleCreateManual}
disabled={!manualName.trim() || isProcessing}
className="flex items-center gap-2 px-7 py-3 rounded-xl bg-[#1A1A1A] hover:bg-[#333333] text-white text-xs font-bold uppercase tracking-widest transition-all disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
>
{isCreating ? <><Loader2 size={14} className="animate-spin" /> Création</> : <><Plus size={14} /> Créer le glossaire</>}
</button>
</div>
</div>
)}
</div>
)}
</div>
);
}