import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { ArrowLeft, Download, FileText } from 'lucide-react'; import html2pdf from 'html2pdf.js'; import MinimalTemplate from '../components/invoice-templates/MinimalTemplate'; // Available templates const templates = { minimal: { name: 'Minimal', description: 'Simple, professional layout', component: MinimalTemplate } }; const InvoicePreview = () => { const navigate = useNavigate(); const [invoiceData, setInvoiceData] = useState(null); const [pdfPageSize, setPdfPageSize] = useState('A4'); const [isGenerating, setIsGenerating] = useState(false); const [selectedTemplate] = useState('minimal'); // Calculate totals (same logic as InvoiceEditor) const calculateTotals = (items, discount = 0, fees = [], discounts = []) => { const subtotal = items.reduce((sum, item) => sum + (item.amount || 0), 0); // Calculate total fees const totalFees = fees.reduce((sum, fee) => { const feeAmount = fee.type === 'percentage' ? (subtotal * fee.value) / 100 : fee.value; return sum + feeAmount; }, 0); // Calculate total discounts const totalDiscounts = discounts.reduce((sum, discountItem) => { const discountAmount = discountItem.type === 'percentage' ? (subtotal * discountItem.value) / 100 : discountItem.value; return sum + discountAmount; }, 0); const total = subtotal + totalFees - discount - totalDiscounts; return { subtotal, total }; }; // Load invoice data from localStorage useEffect(() => { try { const savedInvoice = localStorage.getItem('currentInvoice'); const savedPageSize = localStorage.getItem('pdfPageSize'); if (savedInvoice) { const parsedInvoice = JSON.parse(savedInvoice); // Recalculate totals and installments to ensure accuracy const { subtotal, total } = calculateTotals( parsedInvoice.items || [], parsedInvoice.discount || 0, parsedInvoice.fees || [], parsedInvoice.discounts || [] ); // Update totals parsedInvoice.subtotal = subtotal; parsedInvoice.total = total; // Recalculate installments if they exist if (parsedInvoice.paymentTerms?.installments?.length > 0) { const updatedInstallments = parsedInvoice.paymentTerms.installments.map(installment => { if (installment.type === 'percentage') { const baseAmount = parsedInvoice.paymentTerms?.type === 'downpayment' ? total - (parsedInvoice.paymentTerms?.downPayment?.amount || 0) : total; const amount = (baseAmount * (installment.percentage || 0)) / 100; return { ...installment, amount }; } return installment; }); parsedInvoice.paymentTerms = { ...parsedInvoice.paymentTerms, installments: updatedInstallments }; } setInvoiceData(parsedInvoice); } else { // No invoice data found, redirect to editor navigate('/invoice-editor'); } if (savedPageSize) { setPdfPageSize(savedPageSize); } } catch (error) { navigate('/invoice-editor'); } }, [navigate]); // Format number with thousand separator const formatNumber = (num) => { const decimalDigits = invoiceData?.settings?.decimalDigits ?? 2; if (!invoiceData?.settings?.thousandSeparator) return num.toFixed(decimalDigits); return num.toLocaleString('en-US', { minimumFractionDigits: decimalDigits, maximumFractionDigits: decimalDigits }); }; // Format currency const formatCurrency = (amount, useThousandSeparator = false) => { const symbol = invoiceData?.settings?.currency?.symbol || '$'; const decimalDigits = invoiceData?.settings?.decimalDigits ?? 2; const formattedAmount = useThousandSeparator ? formatNumber(amount) : amount.toFixed(decimalDigits); return `${symbol} ${formattedAmount}`; }; // Utility function to temporarily apply print-optimized styles const applyPrintStyles = () => { const element = document.getElementById('invoice-content'); if (!element) return null; // Store original styles const originalStyles = new Map(); // Find all table elements and store their original styles const tables = element.querySelectorAll('table'); tables.forEach((table, index) => { originalStyles.set(`table-${index}`, table.style.cssText); table.style.borderCollapse = 'collapse'; }); // Find all th and td elements and apply print styles const cells = element.querySelectorAll('th, td'); cells.forEach((cell, index) => { originalStyles.set(`cell-${index}`, cell.style.cssText); cell.style.verticalAlign = 'middle'; cell.style.padding = '4px 12px 20px'; cell.style.lineHeight = '1.4'; }); // Find all totals rows and apply print styles const totalsRows = element.querySelectorAll('.invoice-totals-row, .invoice-total-final'); totalsRows.forEach((row, index) => { originalStyles.set(`totals-${index}`, row.style.cssText); row.style.display = 'flex'; row.style.alignItems = 'center'; row.style.justifyContent = 'space-between'; row.style.padding = '4px 12px 20px'; row.style.minHeight = '44px'; }); // Find all stamp cards and apply print styles const stampCards = element.querySelectorAll('.invoice-payment-status-stamp'); stampCards.forEach((stampCard, index) => { originalStyles.set(`stamp-${index}`, stampCard.style.cssText); stampCard.style.padding = '4px 12px 20px'; }); // Find all cards and apply print styles const fromToCards = element.querySelectorAll('.invoice-from-to-card'); fromToCards.forEach((fromToCard, index) => { originalStyles.set(`fromTo-${index}`, fromToCard.style.cssText); fromToCard.style.padding = '4px 12px 20px'; }); return originalStyles; }; // Utility function to restore original styles const restoreOriginalStyles = (originalStyles) => { if (!originalStyles) return; const element = document.getElementById('invoice-content'); if (!element) return; // Restore table styles const tables = element.querySelectorAll('table'); tables.forEach((table, index) => { const originalStyle = originalStyles.get(`table-${index}`); if (originalStyle !== undefined) { table.style.cssText = originalStyle; } }); // Restore cell styles const cells = element.querySelectorAll('th, td'); cells.forEach((cell, index) => { const originalStyle = originalStyles.get(`cell-${index}`); if (originalStyle !== undefined) { cell.style.cssText = originalStyle; } }); // Restore totals row styles const totalsRows = element.querySelectorAll('.invoice-totals-row, .invoice-total-final'); totalsRows.forEach((row, index) => { const originalStyle = originalStyles.get(`totals-${index}`); if (originalStyle !== undefined) { row.style.cssText = originalStyle; } }); // Restore stamp card styles const stampCards = element.querySelectorAll('.invoice-payment-status-stamp'); stampCards.forEach((stampCard, index) => { const originalStyle = originalStyles.get(`stamp-${index}`); if (originalStyle !== undefined) { stampCard.style.cssText = originalStyle; } }); // Restore fromTo card styles const fromToCards = element.querySelectorAll('.invoice-from-to-card'); fromToCards.forEach((fromToCard, index) => { const originalStyle = originalStyles.get(`fromTo-${index}`); if (originalStyle !== undefined) { fromToCard.style.cssText = originalStyle; } }); }; // Generate PDF from the visible invoice const handleDownloadPDF = async () => { if (!invoiceData) return; setIsGenerating(true); let originalStyles = null; try { const element = document.getElementById('invoice-content'); if (!element) { throw new Error('Invoice content not found'); } // Apply print-optimized styles temporarily originalStyles = applyPrintStyles(); // Small delay to ensure styles are applied await new Promise(resolve => setTimeout(resolve, 100)); const opt = { margin: [0.2, 0.4, 0.8, 0.4], // top, left, bottom, right margins in inches - increased bottom margin to prevent elements dropping filename: `invoice-${invoiceData.invoiceNumber || 'draft'}.pdf`, image: { type: 'png', quality: 0.98 }, html2canvas: { useCORS: true, backgroundColor: '#ffffff', letterRendering: true }, jsPDF: { unit: 'in', format: pdfPageSize === 'F4' ? [8.27, 13] : pdfPageSize.toLowerCase(), // F4 dimensions in inches orientation: 'portrait' }, pagebreak: { mode: ['avoid-all', 'css', 'legacy'], before: '.page-break-before', after: '.page-break-after', avoid: '.page-break-avoid' } }; await html2pdf().set(opt).from(element).save(); } catch (error) { alert('Failed to generate PDF. Please try again.'); } finally { // Restore original styles after a short delay setTimeout(() => { restoreOriginalStyles(originalStyles); }, 500); setIsGenerating(false); } }; // Navigate back to editor const handleEditInvoice = () => { // Ensure current invoice data is saved before navigating if (invoiceData) { try { localStorage.setItem('currentInvoice', JSON.stringify(invoiceData)); } catch (error) { // Failed to save invoice data before edit } } // Add a parameter to indicate we're editing existing data navigate('/invoice-editor?mode=edit'); }; if (!invoiceData) { return (
Loading invoice...
For the best preview experience and accurate PDF generation, please use desktop mode or a larger screen. The invoice preview is optimized for desktop viewing.