@maistik/nuxt-pdf
Version:
A Nuxt 3 & 4 module for server-side PDF generation using Handlebars templates
195 lines (194 loc) • 6.85 kB
JavaScript
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";
}