diff --git a/frontend/src/app/dashboard/glossaries/new/page.tsx b/frontend/src/app/dashboard/glossaries/new/page.tsx index 45201eb..a6adb1d 100644 --- a/frontend/src/app/dashboard/glossaries/new/page.tsx +++ b/frontend/src/app/dashboard/glossaries/new/page.tsx @@ -53,6 +53,9 @@ export default function NewGlossaryPage() { 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(''); @@ -72,6 +75,9 @@ export default function NewGlossaryPage() { setParsedTerms([]); setParsedFileName(''); setManualName(''); + setFileWizardStep(1); + setFileSrcLang('fr'); + setFileTgtLang('multi'); }; const handleBack = () => { @@ -126,11 +132,30 @@ export default function NewGlossaryPage() { } }; + // 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: 'fr', target_language: 'multi', terms: parsedTerms }); + 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 { @@ -371,111 +396,270 @@ export default function NewGlossaryPage() { )} - {/* ── CAS B : Depuis un fichier ─── */} + {/* ── CAS B : Depuis un fichier — Wizard 3 sous-étapes ─── */} {method === 'file' && (

Importez votre fichier de termes

-

- Formats acceptés : CSV, Excel (.xlsx), ODS, TSV — maximum 5 MB. -

- {/* Format attendu */} -
-

Format CSV attendu :

- - terme_source,terme_cible
- contrat,contract
- résiliation,termination -
+ {/* 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 &&
} + + ); + })}
- {/* 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-[200px]', - isDragging ? 'border-[#C5A17A] bg-[#F5F0EA]' : 'border-[#D9D6D0] dark:border-white/10 hover:border-[#C5A17A] hover:bg-[#FAFAF8]', - 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 -

-

- ou cliquez pour parcourir -

-
- - )} -
+ {/* ── Sous-étape 1 : Format ─── */} + {fileWizardStep === 1 && ( +
+
- {/* Nom du glossaire (si fichier parsé) */} - {fileStatus === 'success' && ( -
- - setParsedFileName(e.target.value)} - placeholder="Ex : Termes RH internes" - className="w-full px-4 py-3 rounded-xl border border-[#D9D6D0] dark:border-white/10 bg-white dark:bg-[#141414] text-[#1A1A1A] dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-brand-accent/30 focus:border-brand-accent/50" - /> +
+

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. +

+
+
+ +
+ + +
+
+ )}
)}