import { useState, useEffect, useCallback, useRef } from "react"; import { useNavigate } from "react-router-dom"; import api from "../../api/client"; const STATUS_STYLES = { draft: { bg: "var(--bg-card-hover)", color: "var(--text-secondary)" }, sent: { bg: "var(--badge-blue-bg)", color: "var(--badge-blue-text)" }, accepted: { bg: "var(--success-bg)", color: "var(--success-text)" }, rejected: { bg: "var(--danger-bg)", color: "var(--danger-text)" }, }; function fmt(n) { const f = parseFloat(n) || 0; return f.toLocaleString("el-GR", { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + " €"; } function fmtDate(iso) { if (!iso) return "—"; return iso.slice(0, 10); } // ── PDF thumbnail via PDF.js ────────────────────────────────────────────────── function loadPdfJs() { return new Promise((res, rej) => { if (window.pdfjsLib) { res(); return; } if (document.getElementById("__pdfjs2__")) { // Script already injected — wait for it const check = setInterval(() => { if (window.pdfjsLib) { clearInterval(check); res(); } }, 50); return; } const s = document.createElement("script"); s.id = "__pdfjs2__"; s.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"; s.onload = () => { window.pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js"; res(); }; s.onerror = rej; document.head.appendChild(s); }); } function PdfThumbnail({ quotationId, onClick }) { const canvasRef = useRef(null); const [failed, setFailed] = useState(false); useEffect(() => { let cancelled = false; (async () => { try { await loadPdfJs(); const token = localStorage.getItem("access_token"); const url = `/api/crm/quotations/${quotationId}/pdf`; const loadingTask = window.pdfjsLib.getDocument({ url, httpHeaders: token ? { Authorization: `Bearer ${token}` } : {}, }); const pdf = await loadingTask.promise; if (cancelled) return; const page = await pdf.getPage(1); if (cancelled) return; const canvas = canvasRef.current; if (!canvas) return; const viewport = page.getViewport({ scale: 1 }); const scale = Math.min(72 / viewport.width, 96 / viewport.height); const scaled = page.getViewport({ scale }); canvas.width = scaled.width; canvas.height = scaled.height; await page.render({ canvasContext: canvas.getContext("2d"), viewport: scaled }).promise; } catch { if (!cancelled) setFailed(true); } })(); return () => { cancelled = true; }; }, [quotationId]); const style = { width: 72, height: 96, borderRadius: 4, overflow: "hidden", flexShrink: 0, cursor: "pointer", border: "1px solid var(--border-primary)", display: "flex", alignItems: "center", justifyContent: "center", backgroundColor: "var(--bg-primary)", }; if (failed) { return (
📑
); } return (
); } function DraftThumbnail() { return (
📄 DRAFT
); } function PdfViewModal({ quotationId, quotationNumber, onClose }) { const [blobUrl, setBlobUrl] = useState(null); const [loadingPdf, setLoadingPdf] = useState(true); const [error, setError] = useState(false); useEffect(() => { let objectUrl = null; const token = localStorage.getItem("access_token"); fetch(`/api/crm/quotations/${quotationId}/pdf`, { headers: token ? { Authorization: `Bearer ${token}` } : {}, }) .then(r => { if (!r.ok) throw new Error("Failed to load PDF"); return r.blob(); }) .then(blob => { objectUrl = URL.createObjectURL(blob); setBlobUrl(objectUrl); }) .catch(() => setError(true)) .finally(() => setLoadingPdf(false)); return () => { if (objectUrl) URL.revokeObjectURL(objectUrl); }; }, [quotationId]); return (
e.stopPropagation()} style={{ backgroundColor: "var(--bg-card)", borderRadius: 10, overflow: "hidden", width: "80vw", height: "88vh", display: "flex", flexDirection: "column", boxShadow: "0 24px 64px rgba(0,0,0,0.5)", }} >
{quotationNumber}
{blobUrl && ( Download )}
{loadingPdf && Loading PDF...} {error && Failed to load PDF.} {blobUrl && (