Catalyst / admin/Bid-Sentinel 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / Bid-Sentinel

public

Bid Scrape and Tracking Application with AI Capability

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
Bid-Sentinel / bid-sentinel-v2 / frontend / src / utils / export.js 2348 B · main
 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
import jsPDF from "jspdf";
import autoTable from "jspdf-autotable";
import { formatDate, formatValue } from "./format";

const COLUMNS = [
  "Opportunity Name",
  "Buyer",
  "Fit %",
  "Certifications",
  "Published",
  "Closing",
  "Value (£)",
  "Duration",
  "Bid Status",
  "Outcome",
  "URL",
];

function toRow(t) {
  return [
    t.title || "",
    t.buyer || "",
    t.fit_score == null ? "" : `${t.fit_score}%`,
    t.required_certs || "",
    formatDate(t.published_date),
    formatDate(t.closing_date),
    formatValue(t),
    t.duration || "",
    t.bid_status || "",
    t.outcome || "",
    t.url || "",
  ];
}

export function buildCsvBlob(tenders) {
  const escape = (v) => `"${String(v ?? "").replace(/"/g, '""')}"`;
  const lines = [
    COLUMNS.map(escape).join(","),
    ...tenders.map((t) => toRow(t).map(escape).join(",")),
  ];
  return new Blob([lines.join("\r\n")], { type: "text/csv;charset=utf-8;" });
}

export function exportCsv(tenders, filename = "bid-sentinel-tenders.csv") {
  triggerDownload(buildCsvBlob(tenders), filename);
}

function _buildPdfDoc(tenders) {
  const doc = new jsPDF({ orientation: "landscape", unit: "pt", format: "a4" });
  doc.setFontSize(16);
  doc.setTextColor(11, 61, 145);
  doc.text("BID SENTINEL", 40, 36);
  doc.setFontSize(11);
  doc.setTextColor(71, 85, 105);
  doc.text("UK Cyber Security Tender Opportunities", 40, 52);
  doc.setFontSize(9);
  doc.text(`Generated ${new Date().toLocaleString("en-GB")}`, 40, 66);
  doc.setTextColor(0, 0, 0);

  autoTable(doc, {
    startY: 80,
    head: [COLUMNS.slice(0, 10)], // omit raw URL column in PDF for readability
    body: tenders.map((t) => toRow(t).slice(0, 10)),
    styles: { fontSize: 6.5, cellPadding: 2.5, overflow: "linebreak" },
    headStyles: { fillColor: [11, 61, 145] },
    columnStyles: { 0: { cellWidth: 150 } },
  });
  return doc;
}

export function buildPdfBlob(tenders) {
  return _buildPdfDoc(tenders).output("blob");
}

export function exportPdf(tenders, filename = "bid-sentinel-tenders.pdf") {
  _buildPdfDoc(tenders).save(filename);
}

function triggerDownload(blob, filename) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}