UNPKG

@maistik/nuxt-pdf

Version:

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

186 lines (185 loc) 6.57 kB
import { useRuntimeConfig } from "#imports"; import Handlebars from "handlebars"; export function compilePdfComponent(templateName, ctx, messages, templateSources, partialSources) { 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" && k in value) { 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 if (currencyOrOptions && typeof currencyOrOptions.hash?.currency === "string") { currency = currencyOrOptions.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", (a, b, opts) => a !== b ? opts.fn(this) : opts.inverse(this) ); handlebars.registerHelper( "gt", (a, b, opts) => a > b ? opts.fn(this) : opts.inverse(this) ); handlebars.registerHelper("formatDate", function(date, formatOrOptions) { let style = "short"; if (typeof formatOrOptions === "string") { style = formatOrOptions; } else if (formatOrOptions && typeof formatOrOptions.hash?.format === "string") { style = formatOrOptions.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; return new Intl.NumberFormat(ctx.locale || "en-US", options).format(value); }); try { const config = useRuntimeConfig(); const customHelpers = config.pdf.customHelpers; if (customHelpers && typeof customHelpers === "object") { Object.entries(customHelpers).forEach(([name, helper]) => { if (typeof helper === "function") { handlebars.registerHelper(name, helper); } }); } } catch (error) { console.warn("Failed to load custom helpers:", error); } 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 (enriched.items && Array.isArray(enriched.items)) { enriched.subtotal = enriched.items.reduce((sum, item) => { return sum + item.quantity * item.price; }, 0); enriched.tax = enriched.subtotal * (enriched.taxRate || 0.1); enriched.total = enriched.subtotal + enriched.tax; if (enriched.issueDate && enriched.paymentTerms) { 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 (enriched.salesData && Array.isArray(enriched.salesData)) { enriched.quarters = calculateQuarters(enriched.salesData); const totalSales = enriched.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"; }