File size: 3,907 Bytes
4cc00df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
 * ReportDownloader — exports an inspection result as PNG or PDF.
 * Uses html2canvas to capture the DOM node passed via `targetRef`,
 * then jsPDF to wrap it into a letter-sized PDF if needed.
 */
import { useRef, useState } from "react";
import html2canvas from "html2canvas";
import { jsPDF } from "jspdf";
import { Download, Image as ImageIcon, FileText, Loader2 } from "lucide-react";

async function captureNode(node, scale = 2) {
  return html2canvas(node, {
    backgroundColor: "#0A0A0A",
    scale,
    useCORS: true,
    logging: false,
  });
}

export default function ReportDownloader({ targetRef, inspectionId, disabled }) {
  const [busy, setBusy] = useState(null); // "png" | "pdf" | null

  const filename = `forgesight-report-${(inspectionId || "inspection").slice(0, 8)}`;

  const downloadPNG = async () => {
    if (!targetRef?.current || busy) return;
    setBusy("png");
    try {
      const canvas = await captureNode(targetRef.current, 2);
      const link = document.createElement("a");
      link.download = `${filename}.png`;
      link.href = canvas.toDataURL("image/png");
      link.click();
    } finally {
      setBusy(null);
    }
  };

  const downloadPDF = async () => {
    if (!targetRef?.current || busy) return;
    setBusy("pdf");
    try {
      const canvas = await captureNode(targetRef.current, 2);
      const imgData = canvas.toDataURL("image/png");

      // A4 portrait in mm
      const pdf = new jsPDF({ orientation: "portrait", unit: "mm", format: "a4" });
      const pageW = pdf.internal.pageSize.getWidth();
      const pageH = pdf.internal.pageSize.getHeight();

      // Scale image to fit full page width, paginate if tall
      const imgW = canvas.width;
      const imgH = canvas.height;
      const ratio = pageW / (imgW / 2);           // /2 because scale=2
      const scaledH = (imgH / 2) * ratio;

      let yOffset = 0;
      let remaining = scaledH;
      let page = 0;

      while (remaining > 0) {
        if (page > 0) pdf.addPage();
        const sliceH = Math.min(remaining, pageH);
        // sourceY in original canvas pixels
        const srcY = page * pageH * (imgW / 2) / pageW;
        pdf.addImage(
          imgData,
          "PNG",
          0, 0,
          pageW, sliceH,
          undefined, "FAST",
          0,
        );
        remaining -= pageH;
        page++;
      }

      // Metadata
      pdf.setProperties({
        title: `ForgeSight Inspection Report`,
        subject: "Automated QC Report — AMD MI300X × Qwen2-VL",
        author: "ForgeSight",
        creator: "ForgeSight · AMD Developer Hackathon",
      });

      pdf.save(`${filename}.pdf`);
    } finally {
      setBusy(null);
    }
  };

  if (disabled) return null;

  return (
    <div className="flex items-center gap-2">
      <span className="fs-label hidden sm:inline">Export</span>

      {/* PNG */}
      <button
        onClick={downloadPNG}
        disabled={!!busy}
        title="Download as PNG"
        className="fs-chip inline-flex items-center gap-1.5 hover:border-white/40 hover:text-white disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
        data-testid="download-png-btn"
      >
        {busy === "png" ? (
          <Loader2 className="w-3 h-3 animate-spin" />
        ) : (
          <ImageIcon className="w-3 h-3" />
        )}
        PNG
      </button>

      {/* PDF */}
      <button
        onClick={downloadPDF}
        disabled={!!busy}
        title="Download as PDF"
        className="fs-chip inline-flex items-center gap-1.5 hover:border-white/40 hover:text-white disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
        data-testid="download-pdf-btn"
      >
        {busy === "pdf" ? (
          <Loader2 className="w-3 h-3 animate-spin" />
        ) : (
          <FileText className="w-3 h-3" />
        )}
        PDF
      </button>
    </div>
  );
}