UNPKG

@maistik/nuxt-pdf

Version:

A Nuxt 3 & 4 module for server-side PDF generation using Handlebars templates

195 lines (194 loc) 6.85 kB
import Handlebars from "handlebars"; export function compilePdfComponent(templateName, ctx, messages, templateSources, partialSources, customHelpers = {}) { const handlebars = Handlebars.create(); handlebars.registerHelper("t", function(key) { const keys = key.split("."); let value = messages; for (const k of keys) { if (value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, k)) { value = value[k]; } else { return key; } } return typeof value === "string" ? value : key; }); handlebars.registerHelper("formatCurrency", function(value, currencyOrOptions) { if (typeof value !== "number") { return value; } let currency = "USD"; if (typeof currencyOrOptions === "string") { currency = currencyOrOptions; } else { const hash = currencyOrOptions?.hash; if (typeof hash?.currency === "string") { currency = hash.currency; } } if (!/^[A-Z]{3}$/.test(currency)) { currency = "USD"; } return new Intl.NumberFormat(ctx.locale || "en-US", { style: "currency", currency }).format(value); }); handlebars.registerHelper("lineTotal", function(qty, price) { return (qty * price).toFixed(2); }); handlebars.registerHelper("eq", function(a, b, options) { const isEqual = a === b; if (options && typeof options.fn === "function") { return isEqual ? options.fn(this) : options.inverse(this); } return isEqual; }); handlebars.registerHelper("ne", function(a, b, options) { const result = a !== b; if (options && typeof options.fn === "function") { return result ? options.fn(this) : options.inverse(this); } return result; }); handlebars.registerHelper("gt", function(a, b, options) { const result = a > b; if (options && typeof options.fn === "function") { return result ? options.fn(this) : options.inverse(this); } return result; }); handlebars.registerHelper("formatDate", function(date, formatOrOptions) { let style = "short"; if (typeof formatOrOptions === "string") { style = formatOrOptions; } else { const hash = formatOrOptions?.hash; if (typeof hash?.format === "string") { style = hash.format; } } if (!["full", "long", "medium", "short"].includes(style)) { style = "short"; } let dateObj; if (typeof date === "string") { dateObj = new Date(date); } else if (date instanceof Date) { dateObj = date; } else { dateObj = /* @__PURE__ */ new Date(); } if (isNaN(dateObj.getTime())) { dateObj = /* @__PURE__ */ new Date(); } return new Intl.DateTimeFormat(ctx.locale || "en-US", { dateStyle: style }).format(dateObj); }); handlebars.registerHelper("formatNumber", function(value, options) { if (typeof value !== "number") return value; const intlOptions = options && typeof options.hash === "object" ? options.hash : {}; return new Intl.NumberFormat(ctx.locale || "en-US", intlOptions).format(value); }); if (customHelpers && typeof customHelpers === "object") { for (const [name, helper] of Object.entries(customHelpers)) { if (typeof helper === "function") { handlebars.registerHelper(name, helper); } } } handlebars.registerHelper("upper", function(str) { return String(str || "").toUpperCase(); }); handlebars.registerHelper("lower", function(str) { return String(str || "").toLowerCase(); }); handlebars.registerHelper("capitalize", function(str) { return String(str || "").charAt(0).toUpperCase() + String(str || "").slice(1).toLowerCase(); }); handlebars.registerHelper("truncate", function(str, length) { const text = String(str || ""); return text.length > length ? `${text.substring(0, length)}...` : text; }); handlebars.registerHelper("multiply", function(a, b) { return (a || 0) * (b || 0); }); handlebars.registerHelper("add", function(a, b) { return (a || 0) + (b || 0); }); handlebars.registerHelper("subtract", function(a, b) { return (a || 0) - (b || 0); }); handlebars.registerHelper("divide", function(a, b) { return b !== 0 ? (a || 0) / b : 0; }); handlebars.registerHelper("percentage", function(value, total) { return total !== 0 ? `${((value || 0) / total * 100).toFixed(2)}%` : "0%"; }); Object.entries(partialSources).forEach(([name, source]) => { handlebars.registerPartial(name, source); }); const templateSource = templateSources[templateName]; if (!templateSource) { throw new Error(`Template "${templateName}" not found`); } const template = handlebars.compile(templateSource); const enrichedData = enrichContextForTemplate(templateName, ctx.data); return template({ ...enrichedData, options: ctx.options, locale: ctx.locale }); } function enrichContextForTemplate(templateName, data) { const enriched = { ...data }; if (templateName.toLowerCase().includes("invoice")) { if (Array.isArray(enriched.items)) { const items = enriched.items; const subtotal = items.reduce((sum, item) => sum + item.quantity * item.price, 0); const taxRate = typeof enriched.taxRate === "number" ? enriched.taxRate : 0.1; const tax = subtotal * taxRate; enriched.subtotal = subtotal; enriched.tax = tax; enriched.total = subtotal + tax; if (enriched.issueDate && typeof enriched.paymentTerms === "number") { const issueDate = new Date(enriched.issueDate); const dueDate = new Date(issueDate); dueDate.setDate(dueDate.getDate() + enriched.paymentTerms); enriched.dueDate = dueDate; } } } if (templateName.toLowerCase().includes("salesreport")) { if (Array.isArray(enriched.salesData)) { const salesData = enriched.salesData; enriched.quarters = calculateQuarters(salesData); const totalSales = salesData.reduce((sum, item) => sum + item.amount, 0); enriched.rating = calculatePerformanceRating(totalSales); } } return enriched; } function calculateQuarters(salesData) { const quarters = [ { name: "Q1", months: [0, 1, 2], total: 0 }, { name: "Q2", months: [3, 4, 5], total: 0 }, { name: "Q3", months: [6, 7, 8], total: 0 }, { name: "Q4", months: [9, 10, 11], total: 0 } ]; salesData.forEach((item) => { const month = new Date(item.date).getMonth(); const quarter = quarters.find((q) => q.months.includes(month)); if (quarter) { quarter.total += item.amount; } }); return quarters; } function calculatePerformanceRating(totalSales) { if (totalSales >= 1e5) return "Excellent"; if (totalSales >= 75e3) return "Good"; if (totalSales >= 5e4) return "Average"; return "Needs Improvement"; }