| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { useEffect, useRef, useState } from "react"; |
|
|
| const btn = { |
| fontSize: 13, |
| fontWeight: 700, |
| color: "#111827", |
| border: "1px solid #d1d5db", |
| borderRadius: 8, |
| padding: "6px 12px", |
| background: "#fff", |
| cursor: "pointer", |
| }; |
|
|
| export default function PdfViewer({ pdfUrl, downloadUrl, filename = "ban-an-da-gan-nhan.pdf" }) { |
| const containerRef = useRef(null); |
| const [status, setStatus] = useState("loading"); |
| const [numPages, setNumPages] = useState(0); |
|
|
| useEffect(() => { |
| let cancelled = false; |
|
|
| if (!pdfUrl) { |
| setStatus("error"); |
| return; |
| } |
|
|
| async function render() { |
| setStatus("loading"); |
| try { |
| const pdfjsLib = await import("pdfjs-dist"); |
| const workerSrc = (await import("pdfjs-dist/build/pdf.worker.min.mjs?url")).default; |
| pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc; |
|
|
| const pdf = await pdfjsLib.getDocument(pdfUrl).promise; |
| if (cancelled) return; |
| setNumPages(pdf.numPages); |
|
|
| const container = containerRef.current; |
| if (!container) return; |
| container.innerHTML = ""; |
|
|
| const scale = 1.4; |
| for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) { |
| const page = await pdf.getPage(pageNum); |
| if (cancelled) return; |
|
|
| const viewport = page.getViewport({ scale }); |
| const canvas = document.createElement("canvas"); |
| canvas.width = viewport.width; |
| canvas.height = viewport.height; |
| canvas.style.width = "100%"; |
| canvas.style.height = "auto"; |
| canvas.style.display = "block"; |
| canvas.style.margin = "0 auto 14px"; |
| canvas.style.boxShadow = "0 4px 16px rgba(15,23,42,0.12)"; |
| canvas.style.borderRadius = "6px"; |
|
|
| const ctx = canvas.getContext("2d"); |
| container.appendChild(canvas); |
| await page.render({ canvasContext: ctx, viewport }).promise; |
| } |
|
|
| if (!cancelled) setStatus("done"); |
| } catch (e) { |
| console.error("PDF render error:", e); |
| if (!cancelled) setStatus("error"); |
| } |
| } |
|
|
| render(); |
| return () => { |
| cancelled = true; |
| }; |
| }, [pdfUrl]); |
|
|
| async function handleDownload() { |
| const url = downloadUrl || pdfUrl; |
| if (!url) return; |
| try { |
| const res = await fetch(url); |
| const blob = await res.blob(); |
| const objUrl = URL.createObjectURL(blob); |
| const a = document.createElement("a"); |
| a.href = objUrl; |
| a.download = filename; |
| document.body.appendChild(a); |
| a.click(); |
| a.remove(); |
| setTimeout(() => URL.revokeObjectURL(objUrl), 2000); |
| } catch (e) { |
| |
| window.open(url, "_blank", "noopener"); |
| } |
| } |
|
|
| return ( |
| <div style={{ background: "#fff", border: "1px solid #e5e7eb", borderRadius: 16, overflow: "hidden" }}> |
| <div |
| style={{ |
| display: "flex", |
| alignItems: "center", |
| gap: 10, |
| padding: "12px 16px", |
| borderBottom: "1px solid #eef2f7", |
| flexWrap: "wrap", |
| }} |
| > |
| <strong style={{ fontSize: 14 }}> |
| PDF đã gán nhãn{numPages ? ` · ${numPages} trang` : ""} |
| </strong> |
| <span style={{ marginLeft: "auto", display: "flex", gap: 8 }}> |
| <button type="button" onClick={handleDownload} style={btn}> |
| ⬇ Tải PDF |
| </button> |
| {(downloadUrl || pdfUrl) && ( |
| <a |
| href={downloadUrl || pdfUrl} |
| target="_blank" |
| rel="noreferrer" |
| style={{ ...btn, textDecoration: "none", display: "inline-block" }} |
| > |
| Mở tab mới |
| </a> |
| )} |
| </span> |
| </div> |
| |
| <div style={{ maxHeight: 760, overflowY: "auto", padding: 16, background: "#f8fafc" }}> |
| {status === "loading" && ( |
| <div style={{ textAlign: "center", color: "#64748b", padding: 40 }}>Đang tải PDF…</div> |
| )} |
| {status === "error" && ( |
| <div style={{ textAlign: "center", color: "#64748b", padding: 40 }}> |
| Không hiển thị được PDF tại đây.{" "} |
| {(downloadUrl || pdfUrl) && ( |
| <a href={downloadUrl || pdfUrl} target="_blank" rel="noreferrer"> |
| Mở PDF ở tab mới |
| </a> |
| )} |
| </div> |
| )} |
| <div ref={containerRef} /> |
| </div> |
| </div> |
| ); |
| } |
|
|