UNPKG

invoice-craft

Version:

Customizable, browser-first invoice PDF generator library with modern TypeScript API

248 lines (247 loc) 8.56 kB
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]; }