const { useState, useRef, useCallback, useEffect, useMemo } = window.React; // ── PDF.js chargé dynamiquement depuis cdnjs ─────────────────────────────────── const PDFJS_URL="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.min.js"; const PDFJS_WORKER="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.4.120/pdf.worker.min.js"; let _pdfjsLib=null; const loadPdfJs=()=>new Promise((resolve,reject)=>{ if(_pdfjsLib){resolve(_pdfjsLib);return;} if(document.querySelector(`script[src="${PDFJS_URL}"]`)){ const wait=setInterval(()=>{if(window.pdfjsLib){clearInterval(wait);_pdfjsLib=window.pdfjsLib;_pdfjsLib.GlobalWorkerOptions.workerSrc=PDFJS_WORKER;resolve(_pdfjsLib);}},100); return; } const s=document.createElement("script"); s.src=PDFJS_URL; s.onload=()=>{_pdfjsLib=window.pdfjsLib;_pdfjsLib.GlobalWorkerOptions.workerSrc=PDFJS_WORKER;resolve(_pdfjsLib);}; s.onerror=reject; document.head.appendChild(s); }); // ── Visionneuse PDF avec PDF.js ──────────────────────────────────────────────── const PdfViewer=({dataUrl})=>{ const [pages,setPages]=useState([]); const [loading,setLoading]=useState(true); const [error,setError]=useState(null); const [scale,setScale]=useState(1.2); useEffect(()=>{ let cancelled=false; setLoading(true);setError(null);setPages([]); (async()=>{ try{ const lib=await loadPdfJs(); // Convertir dataUrl en ArrayBuffer const base64=dataUrl.split(",")[1]; const binary=atob(base64); const bytes=new Uint8Array(binary.length); for(let i=0;i{cancelled=true;}; },[dataUrl,scale]); if(loading)return(
Chargement du PDF…
); if(error)return(
⚠️
{error}
Utilisez le bouton ⬇ Télécharger pour ouvrir le fichier.
); return(
{/* Contrôle zoom */}
{Math.round(scale*100)}% {pages.length} page{pages.length>1?"s":""}
{pages.map(p=>(
{`Page {pages.length>1&&
Page {p.num}
}
))}
); }; // ── Fonts & Reset ───────────────────────────────────────────────────────────── const FontLink = () => ( ); // ── Theme ───────────────────────────────────────────────────────────────────── const T = { bg:"#0d1117", sidebar:"#0f172a", card:"#1a2235", cardHover:"#1e2840", border:"#2a3550", accent:"#f0a500", accentDim:"rgba(240,165,0,0.1)", text:"#e2ddd5", muted:"#6b7a99", success:"#22c55e", danger:"#ef4444", info:"#38bdf8", warning:"#fb923c", purple:"#a78bfa", pink:"#f472b6", font:"'DM Sans',sans-serif", fontD:"'Playfair Display',serif", }; // ── Helpers ─────────────────────────────────────────────────────────────────── const uid = () => Math.random().toString(36).slice(2,10); const fmt = d => d ? new Date(d+"T12:00:00").toLocaleDateString("fr-FR") : "—"; const fmtAmt = v => v!=null ? new Intl.NumberFormat("fr-FR",{style:"currency",currency:"EUR"}).format(v) : "—"; const today = () => new Date().toISOString().slice(0,10); const thisMonth = () => new Date().toISOString().slice(0,7); const daysFrom = d => d ? Math.round((new Date(d)-new Date())/86400000) : null; const fmtMois = d => d ? new Date(d+"T12:00:00").toLocaleDateString("fr-FR",{month:"long",year:"numeric"}) : "—"; // ── UI Atoms ────────────────────────────────────────────────────────────────── const Badge = ({c=T.accent,children}) => ( {children} ); const Card = ({children,style={},onClick}) => (
onClick&&(e.currentTarget.style.borderColor=T.accent)} onMouseLeave={e=>onClick&&(e.currentTarget.style.borderColor=T.border)}> {children}
); const Btn = ({children,onClick,v="primary",sm,disabled,style={}}) => { const s = v==="primary"?{background:T.accent,color:"#000",fontWeight:700,border:"none"} :v==="ghost"?{background:"transparent",color:T.muted,border:`1px solid ${T.border}`} :v==="danger"?{background:"rgba(239,68,68,0.1)",color:T.danger,border:`1px solid rgba(239,68,68,0.3)`} :v==="success"?{background:"rgba(34,197,94,0.1)",color:T.success,border:`1px solid rgba(34,197,94,0.3)`} :{background:T.accentDim,color:T.accent,border:`1px solid rgba(240,165,0,0.3)`}; return ; }; const Inp = ({value,onChange,placeholder,type="text",style={}}) => ( onChange(e.target.value)} placeholder={placeholder} style={{background:"#0a0e17",border:`1px solid ${T.border}`,borderRadius:8,padding:"8px 12px", color:T.text,fontSize:13,fontFamily:T.font,outline:"none",width:"100%",...style}}/> ); const Sel = ({value,onChange,children,style={}}) => ( ); const Txt = ({value,onChange,placeholder,rows=3}) => (