/** * Receipt PDF Template * React-PDF template for generating receipt PDFs */ import React from 'react'; import { Document, Page, Text, View, StyleSheet, Image, } from '@react-pdf/renderer'; import { formatDateForDisplay } from '../../../shared/lib/dates.js'; const LOCALE = 'fr-FR'; // Create styles - Simple black and white design const styles = StyleSheet.create({ page: { padding: 30, fontSize: 10, fontFamily: 'Helvetica', color: '#000', }, // Header section receiptTitle: { fontSize: 16, fontWeight: 'bold', marginBottom: 10, }, paidBadge: { fontSize: 14, fontWeight: 'bold', marginBottom: 10, color: '#059669', // Green color for PAID }, datesSection: { marginBottom: 25, fontSize: 9, }, dateItem: { marginBottom: 3, }, dateLabel: { fontWeight: 'normal', }, // Transaction section transactionSection: { marginBottom: 25, padding: 10, backgroundColor: '#f3f4f6', borderRadius: 4, }, transactionTitle: { fontSize: 10, fontWeight: 'bold', marginBottom: 6, }, transactionItem: { fontSize: 9, marginBottom: 3, }, // Company and Client Info infoSection: { flexDirection: 'row', marginBottom: 25, }, companySection: { width: '50%', paddingRight: 15, }, clientSection: { width: '50%', paddingLeft: 15, }, sectionTitle: { fontSize: 10, fontWeight: 'bold', marginBottom: 6, }, infoText: { fontSize: 9, marginBottom: 2, lineHeight: 1.3, }, // Price notice priceNotice: { fontSize: 15, fontWeight: 'bold', marginBottom: 20, textAlign: 'left', color: '#059669', // Green for paid amount }, // Table table: { marginTop: 10, marginBottom: 20, }, tableHeader: { flexDirection: 'row', paddingBottom: 10, borderBottomWidth: 1, borderBottomColor: '#E5E5E5', fontSize: 9, fontWeight: 'bold', }, tableRow: { flexDirection: 'row', paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#E5E5E5', fontSize: 9, }, tableColDescription: { width: '50%', }, tableColQuantity: { width: '15%', textAlign: 'center', }, tableColPrice: { width: '17.5%', textAlign: 'right', }, tableColTotal: { width: '17.5%', textAlign: 'right', }, itemDescription: { fontSize: 8, marginTop: 2, lineHeight: 1.2, }, // Totals totalsSection: { marginTop: 15, marginBottom: 30, alignItems: 'flex-end', }, totalRow: { flexDirection: 'row', paddingVertical: 5, width: '35%', fontSize: 10, }, totalLabel: { width: '60%', textAlign: 'right', paddingRight: 15, }, totalValue: { width: '40%', textAlign: 'right', }, // Notes section notesSection: { marginTop: 20, marginBottom: 20, }, notesTitle: { fontSize: 10, fontWeight: 'bold', marginBottom: 6, }, notesText: { fontSize: 9, lineHeight: 1.4, }, logoSection: { position: 'absolute', top: 30, right: 30, alignItems: 'flex-end', }, logoImage: { maxWidth: 120, maxHeight: 40, objectFit: 'contain', }, }); /** * Format currency */ const formatCurrency = (amount) => { const currency = process.env.ZEN_CURRENCY_SYMBOL || '$'; return `${currency}${parseFloat(amount).toFixed(2)}`; }; /** * Format date (using UTC date utilities) */ const formatDate = (dateString) => { return formatDateForDisplay(dateString, LOCALE, { year: 'numeric', month: 'long', day: 'numeric', }); }; const PAYMENT_METHOD_LABELS = { stripe: 'Carte de crédit (Stripe)', cash: 'Comptant', check: 'Chèque', wire: 'Virement bancaire', interac: 'Virement Interac', other: 'Autre', }; /** * Format payment method for display */ const formatPaymentMethod = (method) => { return PAYMENT_METHOD_LABELS[method] || method; }; /** * Receipt PDF Document Component */ const ReceiptPDFTemplate = ({ invoice, transaction, companyInfo = {} }) => { if (!invoice) { return null; } const clientName = invoice.company_name || `${invoice.first_name} ${invoice.last_name}`; const interestAmount = parseFloat(invoice.interest_amount || 0); const hasInterest = interestAmount > 0; const totalWithInterest = parseFloat(invoice.total_amount) + interestAmount; // Default company info const company = { name: companyInfo.name || process.env.ZEN_MODULE_INVOICE_COMPANY_NAME || 'Your Company', address: companyInfo.address || process.env.ZEN_MODULE_INVOICE_COMPANY_ADDRESS || '', city: companyInfo.city || process.env.ZEN_MODULE_INVOICE_COMPANY_CITY || '', province: companyInfo.province || process.env.ZEN_MODULE_INVOICE_COMPANY_PROVINCE || '', postalCode: companyInfo.postalCode || process.env.ZEN_MODULE_INVOICE_COMPANY_POSTAL_CODE || '', country: companyInfo.country || process.env.ZEN_MODULE_INVOICE_COMPANY_COUNTRY || '', phone: companyInfo.phone || process.env.ZEN_MODULE_INVOICE_COMPANY_PHONE || '', email: companyInfo.email || process.env.ZEN_MODULE_INVOICE_COMPANY_EMAIL || '', }; const logoBlackUrl = companyInfo.publicLogoBlack || ''; return ( {/* Logo top right */} {logoBlackUrl && ( )} {/* Header: Receipt Number and Status */} REÇU #{invoice.invoice_number} PAYÉ Date de facture : {formatDate(invoice.issue_date)} {transaction && ( Date de paiement : {formatDate(transaction.transaction_date)} )} {/* Transaction Information */} {transaction && ( Informations de paiement Numéro de transaction : {transaction.transaction_number} Mode de paiement : {formatPaymentMethod(transaction.payment_method)} Montant payé : {formatCurrency(transaction.amount)} )} {/* Company and Client Info - Side by Side */} {/* Company Info */} {company.name} {company.address && {company.address}} {(company.city || company.province || company.postalCode) && ( {[company.city, company.province, company.postalCode] .filter(Boolean) .join(', ')} )} {company.country && {company.country}} {company.phone && {company.phone}} {company.email && {company.email}} {/* Client Info */} Payé par : {clientName} {invoice.address && {invoice.address}} {(invoice.city || invoice.province || invoice.postal_code) && ( {[invoice.city, invoice.province, invoice.postal_code] .filter(Boolean) .join(', ')} )} {invoice.country && {invoice.country}} {invoice.client_email && {invoice.client_email}} {/* Amount paid notice */} {formatCurrency(transaction ? transaction.amount : invoice.total_amount)} payé en entier {/* Items Table */} Description Qté Prix unitaire Total {invoice.items && invoice.items.map((item, index) => ( {item.name} {item.description && ( {item.description} )} {item.quantity} {formatCurrency(item.unit_price)} {formatCurrency(item.total)} ))} {/* Totals - Simple Subtotal and Total */} Sous-total {formatCurrency(invoice.subtotal)} {hasInterest && ( Intérêts {formatCurrency(interestAmount)} )} Total payé {formatCurrency(totalWithInterest)} {/* Notes Section */} {invoice.notes && ( Notes {invoice.notes} )} ); }; export default ReceiptPDFTemplate;