full pages

This commit is contained in:
nBiqoz
2025-06-14 21:08:43 +02:00
parent 3560f45c48
commit 4c0622e47e
2 changed files with 57 additions and 47 deletions

View File

@@ -1,14 +1,16 @@
// components/MarkdownModal.tsx // app/components/MarkdownModal.tsx
import { X, Download } from "lucide-react"; import { X, Download } from "lucide-react";
import { jsPDF } from "jspdf"; import { jsPDF } from "jspdf";
import { useState } from "react"; import { useState } from "react";
import { type PageObject } from "../page"; // Importer le type depuis la page principale
interface HtmlModalProps { interface HtmlModalProps {
content: string[] | null; content: PageObject[] | null; // Utilise le type PageObject importé
onClose: () => void; onClose: () => void;
} }
// Moteur de rendu HTML vers PDF, robuste et récursif
const renderHtmlOnPdfPage = (pdf: jsPDF, htmlContent: string) => { const renderHtmlOnPdfPage = (pdf: jsPDF, htmlContent: string) => {
const pageHeight = pdf.internal.pageSize.getHeight(); const pageHeight = pdf.internal.pageSize.getHeight();
const pageWidth = pdf.internal.pageSize.getWidth(); const pageWidth = pdf.internal.pageSize.getWidth();
@@ -105,10 +107,13 @@ export default function MarkdownModal({ content, onClose }: HtmlModalProps) {
try { try {
const pdf = new jsPDF({ orientation: "p", unit: "mm", format: "a4" }); const pdf = new jsPDF({ orientation: "p", unit: "mm", format: "a4" });
pdf.setFont("Helvetica", "normal"); pdf.setFont("Helvetica", "normal");
content.forEach((pageHtml, index) => {
// Mis à jour pour boucler sur les objets et passer le contenu HTML
content.forEach((page, index) => {
if (index > 0) pdf.addPage(); if (index > 0) pdf.addPage();
renderHtmlOnPdfPage(pdf, pageHtml); renderHtmlOnPdfPage(pdf, page.htmlContent);
}); });
pdf.save("document_anonymise.pdf"); pdf.save("document_anonymise.pdf");
} catch (error) { } catch (error) {
console.error("Erreur lors de la génération du PDF:", error); console.error("Erreur lors de la génération du PDF:", error);
@@ -117,9 +122,12 @@ export default function MarkdownModal({ content, onClose }: HtmlModalProps) {
} }
}; };
const previewHtml = content.join( // Mis à jour pour extraire le contenu HTML de chaque page avant de l'afficher
'<hr style="margin: 2.5rem 0; border-style: dashed; border-color: rgba(255,255,255,0.2);">' const previewHtml = content
); .map((page) => page.htmlContent)
.join(
'<hr style="margin: 2.5rem 0; border-style: dashed; border-color: rgba(255,255,255,0.2);">'
);
return ( return (
<div <div

View File

@@ -33,7 +33,13 @@ import {
} from "lucide-react"; } from "lucide-react";
import MarkdownModal from "./components/MarkdownModal"; import MarkdownModal from "./components/MarkdownModal";
// === Interfaces et Données === // Définition de la structure d'un objet page, exporté pour être réutilisé
export type PageObject = {
pageNumber: number;
htmlContent: string;
};
// Interface mise à jour pour utiliser la nouvelle structure PageObject
interface ProcessedFile { interface ProcessedFile {
id: string; id: string;
name: string; name: string;
@@ -44,8 +50,7 @@ interface ProcessedFile {
piiCount?: number; piiCount?: number;
errorMessage?: string; errorMessage?: string;
processedBlob?: Blob; processedBlob?: Blob;
// textContent est maintenant un tableau de strings, une par page. textContent?: PageObject[];
textContent?: string[];
} }
interface AnonymizationOptions { interface AnonymizationOptions {
@@ -76,14 +81,14 @@ export default function Home() {
useState<AnonymizationOptions>( useState<AnonymizationOptions>(
piiOptions.reduce((acc, option) => ({ ...acc, [option.id]: true }), {}) piiOptions.reduce((acc, option) => ({ ...acc, [option.id]: true }), {})
); );
// L'état de la modale attend maintenant un tableau de strings ou null // CORRECTION : L'état de la modale attend maintenant la nouvelle structure de données
const [modalContent, setModalContent] = useState<string[] | null>(null); const [modalContent, setModalContent] = useState<PageObject[] | null>(null);
const handleOptionChange = (id: string) => const handleOptionChange = (id: string) =>
setAnonymizationOptions((prev) => ({ ...prev, [id]: !prev[id] })); setAnonymizationOptions((prev) => ({ ...prev, [id]: !prev[id] }));
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files?.length) { if (e.target.files?.length) {
setFile(event.target.files[0]); setFile(e.target.files[0]);
setError(null); setError(null);
} }
}; };
@@ -99,8 +104,7 @@ export default function Home() {
e.preventDefault(); e.preventDefault();
setIsDragOver(true); setIsDragOver(true);
}, []); }, []);
const handleDragLeave = useCallback((e: React.DragEvent) => { const handleDragLeave = useCallback(() => {
e.preventDefault();
setIsDragOver(false); setIsDragOver(false);
}, []); }, []);
const formatFileSize = (bytes: number): string => { const formatFileSize = (bytes: number): string => {
@@ -118,7 +122,7 @@ export default function Home() {
const removeFromHistory = (id: string) => const removeFromHistory = (id: string) =>
setHistory((prev) => prev.filter((item) => item.id !== id)); setHistory((prev) => prev.filter((item) => item.id !== id));
const handleDownload = (id: string) => { const handleDownloadTxt = (id: string) => {
const fileToDownload = history.find((item) => item.id === id); const fileToDownload = history.find((item) => item.id === id);
if (!fileToDownload?.processedBlob) return; if (!fileToDownload?.processedBlob) return;
const url = URL.createObjectURL(fileToDownload.processedBlob); const url = URL.createObjectURL(fileToDownload.processedBlob);
@@ -132,7 +136,6 @@ export default function Home() {
a.remove(); a.remove();
}; };
// Met à jour le contenu de la modale avec le tableau de pages
const handlePreview = (id: string) => { const handlePreview = (id: string) => {
const fileToPreview = history.find((item) => item.id === id); const fileToPreview = history.find((item) => item.id === id);
if (fileToPreview?.textContent) { if (fileToPreview?.textContent) {
@@ -162,7 +165,6 @@ export default function Home() {
} }
}; };
// === Fonction principale mise à jour pour gérer le format JSON ===
const processFile = async () => { const processFile = async () => {
if (!file) return; if (!file) return;
const n8nWebhookUrl = process.env.NEXT_PUBLIC_N8N_WEBHOOK_URL; const n8nWebhookUrl = process.env.NEXT_PUBLIC_N8N_WEBHOOK_URL;
@@ -207,30 +209,31 @@ export default function Home() {
throw new Error(errorResult.error || `Échec du traitement.`); throw new Error(errorResult.error || `Échec du traitement.`);
} }
// On parse la réponse JSON au lieu de lire un blob
const result = await response.json(); const result = await response.json();
const docData = result.anonymizedDocument;
// Validation de la structure de la réponse if (!docData || !Array.isArray(docData.pages)) {
if (
!result.anonymizedDocument ||
!Array.isArray(result.anonymizedDocument.pages)
) {
throw new Error( throw new Error(
"Format de réponse invalide du service d'anonymisation." "Format de réponse invalide du service d'anonymisation."
); );
} }
const textContent: string[] = result.anonymizedDocument.pages; const textContent: PageObject[] = docData.pages;
const piiCount: number = result.anonymizedDocument.piiCount || 0; const piiCount: number = docData.piiCount || 0;
// On crée un blob pour le téléchargement .txt en joignant les pages const fullText = textContent
const fullText = textContent.join("\n\n--- Page Suivante ---\n\n"); .map(
(page) =>
`--- Page ${page.pageNumber} ---\n${page.htmlContent.replace(
/<[^>]*>/g,
"\n"
)}`
)
.join("\n\n");
const processedBlob = new Blob([fullText], { const processedBlob = new Blob([fullText], {
type: "text/plain;charset=utf-8", type: "text/plain;charset=utf-8",
}); });
setProgress(90); setProgress(90);
// On met à jour l'historique avec le tableau de pages
setHistory((prev) => setHistory((prev) =>
prev.map((item) => prev.map((item) =>
item.id === fileId item.id === fileId
@@ -263,7 +266,6 @@ export default function Home() {
} }
}; };
// === Rendu du composant (JSX - inchangé) ===
return ( return (
<div className="h-screen w-screen bg-[#061717] flex flex-col md:flex-row overflow-hidden"> <div className="h-screen w-screen bg-[#061717] flex flex-col md:flex-row overflow-hidden">
<aside className="w-full md:w-80 md:flex-shrink-0 bg-[#061717] border-b-4 md:border-b-0 md:border-r-4 border-white flex flex-col shadow-[4px_0_0_0_black]"> <aside className="w-full md:w-80 md:flex-shrink-0 bg-[#061717] border-b-4 md:border-b-0 md:border-r-4 border-white flex flex-col shadow-[4px_0_0_0_black]">
@@ -291,19 +293,7 @@ export default function Home() {
)} )}
</div> </div>
<div className="flex-1 overflow-y-auto p-3 space-y-3"> <div className="flex-1 overflow-y-auto p-3 space-y-3">
{history.length === 0 ? ( {history.length > 0 ? (
<div className="text-center py-10 px-4 flex flex-col justify-center h-full">
<div className="w-14 h-14 bg-[#F7AB6E] border-4 border-white shadow-[6px_6px_0_0_black] mx-auto mb-4 flex items-center justify-center">
<FileText className="h-7 w-7 text-white" />
</div>
<p className="text-white font-black text-base uppercase">
Aucun Document
</p>
<p className="text-white/70 font-bold mt-1 text-xs">
Vos fichiers apparaîtront ici.
</p>
</div>
) : (
history.map((item) => { history.map((item) => {
const status = getStatusInfo(item); const status = getStatusInfo(item);
return ( return (
@@ -360,7 +350,7 @@ export default function Home() {
)} )}
{item.status === "completed" && ( {item.status === "completed" && (
<Button <Button
onClick={() => handleDownload(item.id)} onClick={() => handleDownloadTxt(item.id)}
variant="ghost" variant="ghost"
size="icon" 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" 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"
@@ -381,6 +371,18 @@ export default function Home() {
</div> </div>
); );
}) })
) : (
<div className="text-center py-10 px-4 flex flex-col justify-center h-full">
<div className="w-14 h-14 bg-[#F7AB6E] border-4 border-white shadow-[6px_6px_0_0_black] mx-auto mb-4 flex items-center justify-center">
<FileText className="h-7 w-7 text-white" />
</div>
<p className="text-white font-black text-base uppercase">
Aucun Document
</p>
<p className="text-white/70 font-bold mt-1 text-xs">
Vos fichiers apparaîtront ici.
</p>
</div>
)} )}
</div> </div>
</aside> </aside>