Spaces:
Sleeping
Sleeping
| // Invoice Generator Utility | |
| // Generates invoice data and creates downloadable PDF | |
| export const generateInvoiceNumber = (count) => { | |
| return `INV-2026-${String(count + 1).padStart(6, '0')}`; | |
| }; | |
| export const createInvoiceData = (booking, photographer, customer) => { | |
| const subtotal = booking.totalPrice || 0; | |
| const taxRate = 18; // 18% GST | |
| const tax = (subtotal * taxRate) / 100; | |
| const totalAmount = subtotal + tax; | |
| const items = [ | |
| { | |
| description: `${photographer.firstName} ${photographer.lastName} - Photography Session`, | |
| quantity: 1, | |
| unitPrice: subtotal, | |
| total: subtotal, | |
| }, | |
| ]; | |
| return { | |
| issueDate: new Date(), | |
| dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), | |
| items, | |
| subtotal, | |
| tax, | |
| taxRate, | |
| totalAmount, | |
| photographerId: photographer._id, | |
| customerId: customer._id, | |
| bookingId: booking._id, | |
| }; | |
| }; | |
| export const formatInvoiceForDisplay = (invoice) => { | |
| return { | |
| ...invoice, | |
| issueDate: new Date(invoice.issueDate).toLocaleDateString('en-IN', { | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric', | |
| }), | |
| dueDate: new Date(invoice.dueDate).toLocaleDateString('en-IN', { | |
| year: 'numeric', | |
| month: 'long', | |
| day: 'numeric', | |
| }), | |
| }; | |
| }; | |
| // Simple text-based invoice template (can be extended with jsPDF for actual PDF) | |
| export const generateInvoiceHTML = (invoice, photographer, customer) => { | |
| const formatted = formatInvoiceForDisplay(invoice); | |
| return ` | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <style> | |
| body { font-family: Arial, sans-serif; margin: 0; padding: 20px; } | |
| .container { max-width: 800px; margin: 0 auto; border: 1px solid #ddd; padding: 40px; } | |
| .header { text-align: center; margin-bottom: 40px; border-bottom: 2px solid #2563eb; padding-bottom: 20px; } | |
| .header h1 { margin: 0; color: #2563eb; } | |
| .header .tagline { color: #666; font-size: 14px; } | |
| .info-row { display: flex; justify-content: space-between; margin: 20px 0; } | |
| .info-section { flex: 1; } | |
| .info-section h3 { margin: 0 0 10px 0; color: #333; } | |
| .info-section p { margin: 5px 0; color: #666; } | |
| table { width: 100%; border-collapse: collapse; margin: 30px 0; } | |
| table thead { background: #f3f4f6; } | |
| table th, table td { padding: 12px; text-align: left; border-bottom: 1px solid #e5e7eb; } | |
| .total-row { background: #f3f4f6; font-weight: bold; } | |
| .tax-row { background: #fff; } | |
| .grand-total { background: #2563eb; color: white; font-weight: bold; } | |
| .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #e5e7eb; text-align: center; color: #666; font-size: 12px; } | |
| .payment-status { margin: 20px 0; padding: 15px; background: #dbeafe; border-left: 4px solid #2563eb; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>INVOICE</h1> | |
| <p class="tagline">SnapLocal - Professional Photography Services</p> | |
| </div> | |
| <div class="info-row"> | |
| <div class="info-section"> | |
| <h3>Invoice Details</h3> | |
| <p><strong>Invoice #:</strong> ${invoice.invoiceNumber}</p> | |
| <p><strong>Issue Date:</strong> ${formatted.issueDate}</p> | |
| <p><strong>Due Date:</strong> ${formatted.dueDate}</p> | |
| </div> | |
| <div class="info-section"> | |
| <h3>From</h3> | |
| <p><strong>${photographer.firstName} ${photographer.lastName}</strong></p> | |
| <p>${photographer.bio || 'Professional Photographer'}</p> | |
| <p>Email: ${photographer.email || 'N/A'}</p> | |
| </div> | |
| </div> | |
| <div class="info-row"> | |
| <div class="info-section"> | |
| <h3>Bill To</h3> | |
| <p><strong>${customer.firstName} ${customer.lastName}</strong></p> | |
| <p>Email: ${customer.email}</p> | |
| </div> | |
| </div> | |
| <table> | |
| <thead> | |
| <tr> | |
| <th>Description</th> | |
| <th>Quantity</th> | |
| <th>Unit Price</th> | |
| <th>Total</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| ${invoice.items | |
| .map( | |
| item => | |
| `<tr> | |
| <td>${item.description}</td> | |
| <td>${item.quantity}</td> | |
| <td>₹${item.unitPrice.toFixed(2)}</td> | |
| <td>₹${item.total.toFixed(2)}</td> | |
| </tr>` | |
| ) | |
| .join('')} | |
| <tr class="total-row"> | |
| <td colspan="3" style="text-align: right;">Subtotal:</td> | |
| <td>₹${invoice.subtotal.toFixed(2)}</td> | |
| </tr> | |
| <tr class="tax-row"> | |
| <td colspan="3" style="text-align: right;">Tax (${invoice.taxRate}%):</td> | |
| <td>₹${invoice.tax.toFixed(2)}</td> | |
| </tr> | |
| ${ | |
| invoice.discount > 0 | |
| ? `<tr class="tax-row"> | |
| <td colspan="3" style="text-align: right;">Discount:</td> | |
| <td>-₹${invoice.discount.toFixed(2)}</td> | |
| </tr>` | |
| : '' | |
| } | |
| <tr class="grand-total"> | |
| <td colspan="3" style="text-align: right;">TOTAL AMOUNT DUE:</td> | |
| <td>₹${invoice.totalAmount.toFixed(2)}</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| <div class="payment-status"> | |
| <strong>Payment Status:</strong> ${invoice.paymentStatus.toUpperCase()} | |
| ${ | |
| invoice.paidAt | |
| ? `<p>Paid on ${new Date(invoice.paidAt).toLocaleDateString()}</p>` | |
| : '' | |
| } | |
| </div> | |
| <div class="footer"> | |
| <p>Thank you for choosing SnapLocal! This is an automatically generated invoice.</p> | |
| <p>For support, contact us at support@snaplocal.com</p> | |
| </div> | |
| </div> | |
| </body> | |
| </html> | |
| `; | |
| }; | |
| export const downloadInvoiceAsPDF = async (invoice, photographer, customer) => { | |
| // Simple approach: Generate HTML and open in new window for manual PDF save | |
| const htmlContent = generateInvoiceHTML(invoice, photographer, customer); | |
| const newWindow = window.open('', '', 'width=900,height=600'); | |
| newWindow.document.write(htmlContent); | |
| newWindow.document.close(); | |
| newWindow.print(); | |
| }; | |
| export const downloadInvoiceAsJSON = (invoice) => { | |
| const dataStr = JSON.stringify(invoice, null, 2); | |
| const dataBlob = new Blob([dataStr], { type: 'application/json' }); | |
| const url = URL.createObjectURL(dataBlob); | |
| const link = document.createElement('a'); | |
| link.href = url; | |
| link.download = `${invoice.invoiceNumber}.json`; | |
| link.click(); | |
| }; | |