import React, { useState, useRef, useEffect } from "react"; // PoliceDossierEditor - Single-file React component (finished) // Usage: paste into a Create React App / Vite project (src/App.jsx) // Optional styling: Tailwind CSS. If you don't use Tailwind, the component still works — classes are conservative. // Required libs for PDF export: html2canvas, jspdf // Install: npm i html2canvas jspdf export default function App() { // Simple auth: demo credentials (you can replace with real backend) const DEMO_USER = { username: "admin", password: "demo123" }; const [user, setUser] = useState(() => { const saved = localStorage.getItem("pd_user"); return saved ? JSON.parse(saved) : null; }); const [cred, setCred] = useState({ username: "", password: "" }); // Document state - everything editable const defaultDoc = { title: "Dossier de Recrue", entryDate: "20/07/2025", rpName: "MAYET", rpFirst: "TOMMY", discord: "tommy.mayet2025", rpAge: "35 ans", birthDate: "10/02/1990", nationality: "Francais", gender: "Homme", parcours: [ "Fiche d'inscription reçue", "Acceptation officielle", "Présentation faite", "A attribué rôle Communication", "Sécurité Armés / Tir", "Ethique & Déontologie", ], progress: { Droit: "20/20", Procedure: "20/20", Technique: "20/20", Psychologie: "20/20", Securite: "20/20", }, comments: "", signature: "Mayet Tommy", logoDataUrl: null, }; const [doc, setDoc] = useState(() => { const saved = localStorage.getItem("pd_doc"); return saved ? JSON.parse(saved) : defaultDoc; }); // Edit mode toggles which UI is edit vs preview const [editMode, setEditMode] = useState(true); const previewRef = useRef(); useEffect(() => { localStorage.setItem("pd_doc", JSON.stringify(doc)); }, [doc]); useEffect(() => { localStorage.setItem("pd_user", JSON.stringify(user)); }, [user]); function login(e) { e.preventDefault(); if (cred.username === DEMO_USER.username && cred.password === DEMO_USER.password) { setUser({ username: cred.username }); setCred({ username: "", password: "" }); } else { alert("Identifiants incorrects — utilisez admin / demo123 pour la démo"); } } function logout() { setUser(null); } // Generic field updater (path supports nested keys with dot notation) function updateField(path, value) { setDoc((d) => { const copy = JSON.parse(JSON.stringify(d)); const keys = path.split("."); let cur = copy; for (let i = 0; i < keys.length - 1; i++) cur = cur[keys[i]]; cur[keys[keys.length - 1]] = value; return copy; }); } // Logo upload function handleLogo(e) { const f = e.target.files[0]; if (!f) return; const reader = new FileReader(); reader.onload = () => updateField("logoDataUrl", reader.result); reader.readAsDataURL(f); } // Download as PDF using html2canvas + jsPDF async function downloadPdf() { try { const html2canvas = (await import("html2canvas")).default; const { jsPDF } = await import("jspdf"); const node = previewRef.current; if (!node) return; // html2canvas renders the node to a canvas const canvas = await html2canvas(node, { scale: 2, useCORS: true }); const imgData = canvas.toDataURL("image/png"); const pdf = new jsPDF({ orientation: "portrait", unit: "pt", format: "a4" }); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = (canvas.height * pdfWidth) / canvas.width; pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight); const filename = (doc.rpName || 'dossier').split(' ').join('_') + '.pdf'; pdf.save(filename); } catch (err) { console.error(err); alert("Erreur lors de la génération PDF — ouvrez la console pour plus de détails."); } } // Export / Import JSON for full editability function exportJson() { const blob = new Blob([JSON.stringify(doc, null, 2)], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "dossier.json"; a.click(); URL.revokeObjectURL(url); } function importJson(e) { const f = e.target.files[0]; if (!f) return; const reader = new FileReader(); reader.onload = () => { try { const data = JSON.parse(reader.result); setDoc(data); alert("Fichier importé avec succès"); } catch (err) { alert("Fichier JSON invalide"); } }; reader.readAsText(f); } // Reset template function resetTemplate() { if (confirm("Remplacer le document par le modèle par défaut ?")) { setDoc(defaultDoc); } } // Helper: pretty button classes (works whether or not you have Tailwind) const btnClass = "inline-block px-3 py-1 rounded bg-sky-600 text-white hover:opacity-95 text-sm"; return (

Police Dossier Editor

{user ? ( <> Connecté: {user.username} ) : (
setCred((c) => ({ ...c, username: e.target.value }))} className="border px-2 py-1 rounded" /> setCred((c) => ({ ...c, password: e.target.value }))} className="border px-2 py-1 rounded" />
)}

Actions

Paramètres

updateField("title", e.target.value)} className="w-full border px-2 py-1 rounded" />
updateField("entryDate", e.target.value)} className="w-full border px-2 py-1 rounded" />
updateField("signature", e.target.value)} className="w-full border px-2 py-1 rounded" />