/** * Template "Élégant" — filets fins, mise en page éditoriale. * * Cible : boutiques premium, artisanat haut de gamme, hôtellerie-restauration * qualitative. Filets de séparation horizontaux discrets, deux colonnes * pour l'identité, accent color sur les filets et le mot "Facture" centré. */ import React from 'react' import { Document, Page, View, Text, Image, StyleSheet } from '@react-pdf/renderer' import { type InvoiceTemplateProps, PALETTE, formatCents, formatDate, formatQuantity, formatTvaRate, formatAddress, } from '#pdf-templates/common' const styles = StyleSheet.create({ page: { paddingTop: 56, paddingBottom: 56, paddingHorizontal: 64, fontSize: 10, color: PALETTE.ink, fontFamily: 'Times-Roman', lineHeight: 1.55, }, // Header deux colonnes header: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 20 }, headerLeft: { flex: 1 }, headerRight: { flex: 1, alignItems: 'flex-end' }, logo: { width: 64, height: 26, objectFit: 'contain', marginBottom: 10 }, companyName: { fontSize: 12, fontWeight: 'bold', marginBottom: 2 }, small: { fontSize: 9, color: PALETTE.ink2 }, // Titre centré titleBlock: { alignItems: 'center', marginVertical: 16 }, titleAccent: { fontSize: 9, textTransform: 'uppercase', letterSpacing: 4, marginBottom: 6 }, titleText: { fontSize: 24, fontWeight: 'bold', fontFamily: 'Times-Bold' }, titleNumero: { fontSize: 11, marginTop: 4, color: PALETTE.ink2, fontStyle: 'italic' }, hairline: { borderBottomWidth: 0.5, marginVertical: 12 }, // Méta metaRow: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 24 }, metaBlock: { flex: 1 }, metaLabel: { fontSize: 8, textTransform: 'uppercase', color: PALETTE.ink3, marginBottom: 4 }, metaValue: { fontSize: 11 }, clientName: { fontSize: 12, fontWeight: 'bold' }, // Table table: { marginBottom: 16 }, tableHeader: { flexDirection: 'row', borderBottomWidth: 1, paddingBottom: 6, marginBottom: 4, }, tableRow: { flexDirection: 'row', paddingVertical: 6, borderBottomWidth: 0.5, borderBottomColor: PALETTE.line, }, cellDesc: { flex: 4 }, cellQty: { flex: 1, textAlign: 'right' }, cellPu: { flex: 1.5, textAlign: 'right' }, cellTva: { flex: 1, textAlign: 'right' }, cellTotal: { flex: 1.5, textAlign: 'right' }, cellHead: { fontSize: 8, textTransform: 'uppercase', color: PALETTE.ink3, letterSpacing: 1 }, // Totaux totalsBlock: { alignSelf: 'flex-end', width: '45%', marginBottom: 24 }, totalRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 3 }, totalRowGrand: { flexDirection: 'row', justifyContent: 'space-between', paddingTop: 10, paddingBottom: 4, marginTop: 6, borderTopWidth: 1, borderBottomWidth: 0.5, paddingHorizontal: 4, }, totalLabel: { color: PALETTE.ink2 }, grandLabel: { fontSize: 13, fontWeight: 'bold' }, grandAmount: { fontSize: 13, fontWeight: 'bold' }, // TVA breakdown tvaBlock: { marginBottom: 16, width: '60%' }, tvaHeader: { flexDirection: 'row', borderBottomWidth: 0.5, borderBottomColor: PALETTE.line, paddingBottom: 3, }, tvaRow: { flexDirection: 'row', paddingVertical: 2 }, tvaCell: { flex: 1, textAlign: 'right', fontSize: 9 }, tvaCellLeft: { flex: 1, textAlign: 'left', fontSize: 9, color: PALETTE.ink2 }, // Footer paymentBlock: { marginTop: 12, marginBottom: 12 }, paymentLabel: { fontSize: 8, textTransform: 'uppercase', color: PALETTE.ink3, marginBottom: 4, }, legalBlock: { marginTop: 'auto', paddingTop: 12, borderTopWidth: 0.5, borderTopColor: PALETTE.line, fontSize: 7, color: PALETTE.ink3, lineHeight: 1.5, fontStyle: 'italic', }, }) export function ElegantTemplate(props: InvoiceTemplateProps) { const accent = { color: props.accentColor } const hairlineAccent = { borderBottomColor: props.accentColor } const issuerAddress = formatAddress({ line1: props.issuer.addressLine1, line2: props.issuer.addressLine2, zip: props.issuer.addressZip, city: props.issuer.addressCity, country: props.issuer.addressCountry, }) const clientAddress = formatAddress({ line1: props.client.addressLine1, line2: props.client.addressLine2, zip: props.client.addressZip, city: props.client.addressCity, country: props.client.addressCountry, }) const showTvaBreakdown = props.tvaBreakdown.length > 1 return ( {/* Header : émetteur à gauche, dates à droite */} {props.logoUrl ? : null} {props.issuer.companyName ?? '—'} {issuerAddress.map((line, i) => ( {line} ))} {props.issuer.siret ? ( SIRET {props.issuer.siret} ) : null} {props.issuer.tvaIntra ? ( TVA {props.issuer.tvaIntra} ) : null} Émise le {formatDate(props.issueDate)} Échéance {formatDate(props.dueDate)} {/* Titre centré */} Facture N° {props.numero} Établie le {formatDate(props.issueDate)} {/* Méta client */} Adressée à {props.client.name} {clientAddress.map((line, i) => ( {line} ))} {props.client.siret ? ( SIRET {props.client.siret} ) : null} {props.client.tvaIntra ? ( TVA {props.client.tvaIntra} ) : null} Délai de paiement {props.paymentTermsDays} jour{props.paymentTermsDays > 1 ? 's' : ''} {/* Table */} Désignation Qté P.U. HT TVA Total HT {props.lines.map((l) => ( {l.description} {formatQuantity(l.quantity)} {formatCents(l.unitPriceCents)} {formatTvaRate(l.tvaRate)} {formatCents(l.totalHtCents)} ))} {showTvaBreakdown ? ( Taux Base HT Montant TVA {props.tvaBreakdown.map((b) => ( {formatTvaRate(b.rate)} {formatCents(b.htCents)} {formatCents(b.tvaCents)} ))} ) : null} {/* Totaux */} Total HT {formatCents(props.amountHtCents)} TVA {formatCents(props.amountTvaCents)} Total TTC {formatCents(props.amountTtcCents)} {props.rib.iban || props.rib.bic ? ( Coordonnées de paiement {props.rib.bankName ? {props.rib.bankName} : null} {props.rib.iban ? IBAN : {props.rib.iban} : null} {props.rib.bic ? BIC : {props.rib.bic} : null} ) : null} {props.footerNotes ? ( Notes {props.footerNotes} ) : null} {props.penaltyRateText} {props.escompteText} {props.footerLegalText ? {props.footerLegalText} : null} {props.issuer.rcs || props.issuer.capital ? ( {[props.issuer.formeJuridique, props.issuer.capital, props.issuer.rcs] .filter(Boolean) .join(' · ')} ) : null} ) }