invoice-craft
Version:
Customizable, browser-first invoice PDF generator library with modern TypeScript API
248 lines (247 loc) • 8.56 kB
JavaScript
import { calculateTotals } from "../core/calculations";
import { getLabels } from "../utils/localization";
import { generatePreviewHTML } from "../preview/htmlGenerator";
import { buildPdf } from "../pdf/pdfmake";
export async function exportInvoice(invoice, options = {}) {
const format = options.format || 'pdf';
switch (format) {
case 'pdf':
return await exportToPdf(invoice, options);
case 'html':
return exportToHtml(invoice, options);
case 'json':
return exportToJson(invoice, options);
case 'csv':
return exportToCsv(invoice, options);
default:
throw new Error(`Unsupported export format: ${format}`);
}
}
async function exportToPdf(invoice, options) {
const blob = await buildPdf(invoice, {
brandColor: options.brandColor,
logoUrl: options.logoUrl,
layoutStyle: options.layoutStyle
});
// Apply compression if requested
let finalBlob = blob;
if (options.compression) {
finalBlob = await compressPdf(blob, options.quality || 'medium');
}
const filename = `invoice-${invoice.invoiceNumber}.pdf`;
return {
data: finalBlob,
filename,
mimeType: 'application/pdf'
};
}
function exportToHtml(invoice, options) {
const html = generatePreviewHTML(invoice, {
includeStyles: options.includeStyles !== false,
responsive: true,
theme: 'light'
});
const filename = `invoice-${invoice.invoiceNumber}.html`;
return {
data: html,
filename,
mimeType: 'text/html'
};
}
function exportToJson(invoice, options) {
const totals = calculateTotals(invoice);
const labels = getLabels();
const exportData = {
invoice,
totals,
metadata: {
exportedAt: new Date().toISOString(),
exportFormat: 'json',
version: '1.0.0'
}
};
const jsonString = JSON.stringify(exportData, null, 2);
const filename = `invoice-${invoice.invoiceNumber}.json`;
return {
data: jsonString,
filename,
mimeType: 'application/json'
};
}
function exportToCsv(invoice, options) {
var _a, _b;
const totals = calculateTotals(invoice);
const labels = getLabels();
// Create CSV content
let csv = '';
// Header information
csv += 'Invoice Information\n';
csv += `Invoice Number,${invoice.invoiceNumber}\n`;
csv += `Invoice Date,${invoice.invoiceDate}\n`;
csv += `Due Date,${invoice.dueDate || 'N/A'}\n`;
csv += `Currency,${invoice.currency || 'USD'}\n`;
csv += '\n';
// From/To information
csv += 'From Information\n';
csv += `Name,${invoice.from.name}\n`;
csv += `Address,"${((_a = invoice.from.address) === null || _a === void 0 ? void 0 : _a.replace(/"/g, '""')) || 'N/A'}"\n`;
csv += `Email,${invoice.from.email || 'N/A'}\n`;
csv += `Phone,${invoice.from.phone || 'N/A'}\n`;
csv += '\n';
csv += 'To Information\n';
csv += `Name,${invoice.to.name}\n`;
csv += `Address,"${((_b = invoice.to.address) === null || _b === void 0 ? void 0 : _b.replace(/"/g, '""')) || 'N/A'}"\n`;
csv += `Email,${invoice.to.email || 'N/A'}\n`;
csv += `Phone,${invoice.to.phone || 'N/A'}\n`;
csv += '\n';
// Items
csv += 'Items\n';
csv += 'Description,Quantity,Unit Price,Tax Rate,Line Total\n';
invoice.items.forEach(item => {
const lineTotal = item.quantity * item.unitPrice;
const taxAmount = lineTotal * (item.taxRate || 0);
const totalWithTax = lineTotal + taxAmount;
csv += `"${item.description.replace(/"/g, '""')}",${item.quantity},${item.unitPrice},${(item.taxRate || 0) * 100}%,${totalWithTax.toFixed(2)}\n`;
});
csv += '\n';
// Totals
csv += 'Totals\n';
csv += `Subtotal,${totals.subtotal.toFixed(2)}\n`;
csv += `Tax,${totals.totalTax.toFixed(2)}\n`;
if (invoice.discount) {
csv += `Discount,${invoice.discount.toFixed(2)}\n`;
}
csv += `Total,${totals.total.toFixed(2)}\n`;
// Notes and terms
if (invoice.notes || invoice.terms) {
csv += '\n';
csv += 'Additional Information\n';
if (invoice.notes) {
csv += `Notes,"${invoice.notes.replace(/"/g, '""')}"\n`;
}
if (invoice.terms) {
csv += `Terms,"${invoice.terms.replace(/"/g, '""')}"\n`;
}
}
const filename = `invoice-${invoice.invoiceNumber}.csv`;
return {
data: csv,
filename,
mimeType: 'text/csv'
};
}
async function compressPdf(blob, quality) {
// Note: This is a placeholder for PDF compression
// In a real implementation, you might use a library like pdf-lib
// or send to a server-side compression service
const compressionRatios = {
low: 0.3,
medium: 0.6,
high: 0.8
};
const ratio = compressionRatios[quality];
// Simulate compression by returning a smaller blob
// In reality, you'd implement actual PDF compression
const arrayBuffer = await blob.arrayBuffer();
const compressedSize = Math.floor(arrayBuffer.byteLength * ratio);
const compressedBuffer = arrayBuffer.slice(0, compressedSize);
return new Blob([compressedBuffer], { type: 'application/pdf' });
}
// Utility functions for downloading exports
export function downloadExport(result) {
let url;
if (result.data instanceof Blob) {
url = URL.createObjectURL(result.data);
}
else {
const blob = new Blob([result.data], { type: result.mimeType });
url = URL.createObjectURL(blob);
}
const a = document.createElement('a');
a.href = url;
a.download = result.filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
export function previewExport(result) {
if (result.mimeType === 'application/pdf' || result.mimeType === 'text/html') {
let url;
if (result.data instanceof Blob) {
url = URL.createObjectURL(result.data);
}
else {
const blob = new Blob([result.data], { type: result.mimeType });
url = URL.createObjectURL(blob);
}
window.open(url, '_blank');
// Clean up URL after a delay
setTimeout(() => URL.revokeObjectURL(url), 1000);
}
else {
console.warn('Preview not supported for this format');
}
}
// Batch export functionality
export async function exportMultipleInvoices(invoices, options = {}) {
const results = [];
for (const invoice of invoices) {
try {
const result = await exportInvoice(invoice, options);
results.push(result);
}
catch (error) {
console.error(`Failed to export invoice ${invoice.invoiceNumber}:`, error);
// Continue with other invoices
}
}
return results;
}
// Create a ZIP file containing multiple exports
export async function exportToZip(invoices, options = {}) {
// Note: This would require a ZIP library like JSZip
// For now, we'll return a placeholder
const results = await exportMultipleInvoices(invoices, options);
// In a real implementation, you'd create a ZIP file here
const zipContent = `ZIP file would contain ${results.length} files`;
const filename = `invoices-batch-${new Date().toISOString().split('T')[0]}.zip`;
return {
data: zipContent,
filename,
mimeType: 'application/zip'
};
}
// Export templates for different formats
export const exportTemplates = {
pdf: {
name: 'PDF Document',
description: 'Professional PDF format suitable for printing and sharing',
extension: '.pdf',
mimeType: 'application/pdf'
},
html: {
name: 'HTML Document',
description: 'Web-friendly format that can be viewed in any browser',
extension: '.html',
mimeType: 'text/html'
},
json: {
name: 'JSON Data',
description: 'Structured data format for integration with other systems',
extension: '.json',
mimeType: 'application/json'
},
csv: {
name: 'CSV Spreadsheet',
description: 'Comma-separated values format for spreadsheet applications',
extension: '.csv',
mimeType: 'text/csv'
}
};
export function getSupportedFormats() {
return Object.keys(exportTemplates);
}
export function getFormatInfo(format) {
return exportTemplates[format];
}