|
| 1 | +import { createWriteStream } from "fs"; |
| 2 | +import PDFDocument from "pdfkit"; |
| 3 | +import XLSX from "xlsx"; |
| 4 | +import { logInfo } from "./logger.js"; |
| 5 | + |
| 6 | +export function exportToExcel(rows, outputFile) { |
| 7 | + const worksheet = XLSX.utils.json_to_sheet(rows); |
| 8 | + const workbook = XLSX.utils.book_new(); |
| 9 | + XLSX.utils.book_append_sheet(workbook, worksheet, "Vagas"); |
| 10 | + XLSX.writeFile(workbook, outputFile); |
| 11 | + |
| 12 | + logInfo(`Arquivo gerado: ${outputFile}`); |
| 13 | +} |
| 14 | + |
| 15 | +function cleanJobUrl(url) { |
| 16 | + try { |
| 17 | + const match = url.match(/\/jobs\/view\/[^?#]*-(\d{6,})/); |
| 18 | + if (match) return `https://www.linkedin.com/jobs/view/${match[1]}`; |
| 19 | + } catch {} |
| 20 | + return url; |
| 21 | +} |
| 22 | + |
| 23 | +export function exportToPDF(rows, outputFile) { |
| 24 | + return new Promise((resolve, reject) => { |
| 25 | + const doc = new PDFDocument({ margin: 30, size: "A4", layout: "landscape" }); |
| 26 | + const stream = createWriteStream(outputFile); |
| 27 | + |
| 28 | + doc.pipe(stream); |
| 29 | + stream.on("error", reject); |
| 30 | + stream.on("finish", resolve); |
| 31 | + |
| 32 | + // Título |
| 33 | + doc |
| 34 | + .fontSize(14) |
| 35 | + .font("Helvetica-Bold") |
| 36 | + .text("Vagas LinkedIn – Brasil (Remoto)", { align: "center" }); |
| 37 | + doc.moveDown(0.5); |
| 38 | + doc.fontSize(9).font("Helvetica").text(`Total: ${rows.length} vagas`, { align: "center" }); |
| 39 | + doc.moveDown(1); |
| 40 | + |
| 41 | + const cols = [ |
| 42 | + { key: "palavra", label: "Palavra-chave", width: 110 }, |
| 43 | + { key: "titulo", label: "Título", width: 160 }, |
| 44 | + { key: "empresa", label: "Empresa", width: 130 }, |
| 45 | + { key: "local", label: "Local", width: 100 }, |
| 46 | + { key: "link", label: "Link", width: 280 } |
| 47 | + ]; |
| 48 | + |
| 49 | + const rowH = 18; |
| 50 | + const startX = doc.page.margins.left; |
| 51 | + |
| 52 | + function drawRow(rowData, isHeader) { |
| 53 | + let x = startX; |
| 54 | + const y = doc.y; |
| 55 | + |
| 56 | + if (isHeader) { |
| 57 | + doc.rect(x, y, cols.reduce((s, c) => s + c.width, 0), rowH).fill("#2c3e50"); |
| 58 | + } else { |
| 59 | + doc.rect(x, y, cols.reduce((s, c) => s + c.width, 0), rowH).fillAndStroke("#f9f9f9", "#dddddd"); |
| 60 | + } |
| 61 | + |
| 62 | + doc.font(isHeader ? "Helvetica-Bold" : "Helvetica").fontSize(8); |
| 63 | + |
| 64 | + for (const col of cols) { |
| 65 | + const raw = String(rowData[col.key] ?? ""); |
| 66 | + const isLink = col.key === "link" && !isHeader; |
| 67 | + const text = isLink ? cleanJobUrl(raw) : raw; |
| 68 | + |
| 69 | + if (isLink) { |
| 70 | + doc |
| 71 | + .fillColor("#0563C1") |
| 72 | + .text(text, x + 3, y + 4, { width: col.width - 6, lineBreak: false }); |
| 73 | + doc.link(x + 3, y + 4, col.width - 6, 10, text); |
| 74 | + } else { |
| 75 | + doc |
| 76 | + .fillColor(isHeader ? "white" : "#222222") |
| 77 | + .text(text, x + 3, y + 4, { width: col.width - 6, lineBreak: false, ellipsis: true }); |
| 78 | + } |
| 79 | + x += col.width; |
| 80 | + } |
| 81 | + |
| 82 | + doc.y = y + rowH; |
| 83 | + } |
| 84 | + |
| 85 | + drawRow({ palavra: "Palavra-chave", titulo: "Título", empresa: "Empresa", local: "Local", link: "Link" }, true); |
| 86 | + |
| 87 | + for (let i = 0; i < rows.length; i++) { |
| 88 | + if (doc.y + rowH > doc.page.height - doc.page.margins.bottom) { |
| 89 | + doc.addPage(); |
| 90 | + drawRow({ palavra: "Palavra-chave", titulo: "Título", empresa: "Empresa", local: "Local", link: "Link" }, true); |
| 91 | + } |
| 92 | + drawRow(rows[i], false); |
| 93 | + } |
| 94 | + |
| 95 | + doc.end(); |
| 96 | + logInfo(`PDF gerado: ${outputFile}`); |
| 97 | + }); |
| 98 | +} |
0 commit comments