n8n anonyme
This commit is contained in:
58
app/page.tsx
58
app/page.tsx
@@ -8,14 +8,19 @@ import { Input } from "@/components/ui/input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Upload, FileText, ShieldCheck, Download, Trash2, AlertCircle, X, Zap, Lock, Shield, Clock,
|
||||
User, AtSign, MapPin, Cake, Home as HomeIcon, Venus, Phone, Building, Fingerprint, CreditCard, Check
|
||||
User, AtSign, MapPin, Cake, Home as HomeIcon, Venus, Phone, Building, Fingerprint, CreditCard, Check,
|
||||
Eye // <-- Icône ajoutée
|
||||
} from "lucide-react";
|
||||
import MarkdownModal from "./components/MarkdownModal"; // <-- Composant de la modale ajouté
|
||||
|
||||
// Interfaces et Données
|
||||
// === Interfaces et Données ===
|
||||
|
||||
// L'interface est mise à jour pour inclure le contenu texte pour l'aperçu
|
||||
interface ProcessedFile {
|
||||
id: string; name: string; status: 'processing' | 'completed' | 'error';
|
||||
timestamp: Date; originalSize?: string; processedSize?: string;
|
||||
piiCount?: number; errorMessage?: string; processedBlob?: Blob;
|
||||
textContent?: string; // <-- Ajouté pour stocker le contenu du fichier
|
||||
}
|
||||
|
||||
interface AnonymizationOptions { [key: string]: boolean; }
|
||||
@@ -44,6 +49,8 @@ export default function Home() {
|
||||
const [anonymizationOptions, setAnonymizationOptions] = useState<AnonymizationOptions>(
|
||||
piiOptions.reduce((acc, option) => ({ ...acc, [option.id]: true }), {})
|
||||
);
|
||||
// Nouvel état pour gérer la modale
|
||||
const [modalContent, setModalContent] = useState<string | null>(null);
|
||||
|
||||
// === Fonctions utilitaires et Handlers ===
|
||||
const handleOptionChange = (id: string) => setAnonymizationOptions(prev => ({ ...prev, [id]: !prev[id] }));
|
||||
@@ -55,7 +62,27 @@ export default function Home() {
|
||||
const clearFile = () => { setFile(null); setError(null); };
|
||||
const clearHistory = () => setHistory([]);
|
||||
const removeFromHistory = (id: string) => setHistory(prev => prev.filter(item => item.id !== id));
|
||||
const handleDownload = (id: string) => { const fileToDownload = history.find(item => item.id === id); if (!fileToDownload?.processedBlob) return; const url = URL.createObjectURL(fileToDownload.processedBlob); const a = document.createElement('a'); a.href = url; a.download = `anonymized_${fileToDownload.name.split('.')[0] || 'file'}.txt`; a.click(); URL.revokeObjectURL(url); a.remove(); };
|
||||
|
||||
const handleDownload = (id: string) => {
|
||||
const fileToDownload = history.find(item => item.id === id);
|
||||
if (!fileToDownload?.processedBlob) return;
|
||||
const url = URL.createObjectURL(fileToDownload.processedBlob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `anonymized_${fileToDownload.name.split('.')[0] || 'file'}.txt`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
};
|
||||
|
||||
// Nouvelle fonction pour gérer l'ouverture de l'aperçu
|
||||
const handlePreview = (id: string) => {
|
||||
const fileToPreview = history.find((item) => item.id === id);
|
||||
if (fileToPreview?.textContent) {
|
||||
setModalContent(fileToPreview.textContent);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusInfo = (item: ProcessedFile) => { switch (item.status) { case 'completed': return { icon: <ShieldCheck className="h-4 w-4 text-white" />, color: 'bg-[#061717]' }; case 'error': return { icon: <AlertCircle className="h-4 w-4 text-white" />, color: 'bg-[#F7AB6E]' }; default: return { icon: <div className="h-2 w-2 rounded-full bg-[#F7AB6E] animate-pulse" />, color: 'bg-[#061717]' }; } };
|
||||
|
||||
// === Fonction principale pour l'appel à n8n ===
|
||||
@@ -75,9 +102,7 @@ export default function Home() {
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
// On attache le fichier binaire
|
||||
formData.append('file', file);
|
||||
// On attache les options cochées sous forme de chaîne JSON
|
||||
formData.append('options', JSON.stringify(anonymizationOptions));
|
||||
|
||||
setProgress(30);
|
||||
@@ -91,9 +116,13 @@ export default function Home() {
|
||||
|
||||
const processedBlob = await response.blob();
|
||||
const piiCount = parseInt(response.headers.get('X-Pii-Count') || '0', 10);
|
||||
|
||||
// On lit le contenu du blob en texte pour pouvoir l'afficher
|
||||
const textContent = await processedBlob.text();
|
||||
setProgress(90);
|
||||
|
||||
setHistory(prev => prev.map(item => item.id === fileId ? { ...item, status: 'completed', processedSize: formatFileSize(processedBlob.size), piiCount, processedBlob } : item));
|
||||
// On met à jour l'historique avec toutes les informations, y compris le contenu texte
|
||||
setHistory(prev => prev.map(item => item.id === fileId ? { ...item, status: 'completed', processedSize: formatFileSize(processedBlob.size), piiCount, processedBlob, textContent } : item));
|
||||
setProgress(100);
|
||||
|
||||
} catch (err) {
|
||||
@@ -127,7 +156,16 @@ export default function Home() {
|
||||
) : (
|
||||
history.map((item) => {
|
||||
const status = getStatusInfo(item);
|
||||
return (<div key={item.id} className="bg-[#061717] border-2 border-white shadow-[4px_4px_0_0_black] p-3 transition-all"><div className="flex items-start justify-between"><div className="flex items-center gap-3 min-w-0"><div className={`flex-shrink-0 w-7 h-7 border-2 border-white shadow-[2px_2px_0_0_black] flex items-center justify-center ${status.color}`}>{status.icon}</div><div className="min-w-0"><p className="text-sm font-black text-white uppercase truncate" title={item.name}>{item.name}</p>{item.status === 'completed' && (<div className="flex items-center gap-2 mt-1"><span className="text-xs font-bold text-white/70">{item.originalSize} → {item.processedSize}</span><Badge className="bg-[#F7AB6E] text-white border-2 border-white shadow-[2px_2px_0_0_black] font-black uppercase text-[10px] px-1.5 py-0">{item.piiCount} PII</Badge></div>)}{item.status === 'error' && <p className="text-xs font-bold text-[#F7AB6E] mt-1 uppercase">Erreur</p>}{item.status === 'processing' && <p className="text-xs font-bold text-[#F7AB6E] mt-1 uppercase">Traitement...</p>}</div></div><div className="flex items-center gap-1.5 ml-2">{item.status === 'completed' && (<Button onClick={() => handleDownload(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#061717] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"><Download className="h-3 w-3" /></Button>)}<Button onClick={() => removeFromHistory(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#F7AB6E] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"><X className="h-3 w-3" /></Button></div></div></div>);
|
||||
return (<div key={item.id} className="bg-[#061717] border-2 border-white shadow-[4px_4px_0_0_black] p-3 transition-all"><div className="flex items-start justify-between"><div className="flex items-center gap-3 min-w-0"><div className={`flex-shrink-0 w-7 h-7 border-2 border-white shadow-[2px_2px_0_0_black] flex items-center justify-center ${status.color}`}>{status.icon}</div><div className="min-w-0"><p className="text-sm font-black text-white uppercase truncate" title={item.name}>{item.name}</p>{item.status === 'completed' && (<div className="flex items-center gap-2 mt-1"><span className="text-xs font-bold text-white/70">{item.originalSize} → {item.processedSize}</span><Badge className="bg-[#F7AB6E] text-white border-2 border-white shadow-[2px_2px_0_0_black] font-black uppercase text-[10px] px-1.5 py-0">{item.piiCount} PII</Badge></div>)}{item.status === 'error' && <p className="text-xs font-bold text-[#F7AB6E] mt-1 uppercase">Erreur</p>}{item.status === 'processing' && <p className="text-xs font-bold text-[#F7AB6E] mt-1 uppercase">Traitement...</p>}</div></div><div className="flex items-center gap-1.5 ml-2">
|
||||
{/* Le nouveau bouton "Aperçu" est ajouté ici */}
|
||||
{item.status === 'completed' && item.textContent && (
|
||||
<Button onClick={() => handlePreview(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#061717] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5">
|
||||
<Eye className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
{item.status === 'completed' && (<Button onClick={() => handleDownload(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#061717] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"><Download className="h-3 w-3" /></Button>)}
|
||||
<Button onClick={() => removeFromHistory(item.id)} variant="ghost" size="icon" className="h-7 w-7 bg-[#F7AB6E] text-white border-2 border-white shadow-[2px_2px_0_0_black] hover:shadow-[1px_1px_0_0_black] active:shadow-none active:translate-x-0.5 active:translate-y-0.5"><X className="h-3 w-3" /></Button>
|
||||
</div></div></div>);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
@@ -169,6 +207,12 @@ export default function Home() {
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Le composant de la modale est rendu ici, en dehors du flux principal */}
|
||||
<MarkdownModal
|
||||
content={modalContent}
|
||||
onClose={() => setModalContent(null)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user