/** * Template "Moderne" — bandeau coloré en header, mise en page contemporaine. * * Cible : agences, studios, freelances créatifs. L'accent color est dominant : * bandeau header, ligne de séparation, total TTC. Le logo se pose sur le * bandeau. */ 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: { fontSize: 10, color: PALETTE.ink, fontFamily: 'Helvetica', lineHeight: 1.5, }, // Bandeau coloré pleine largeur en haut banner: { paddingTop: 36, paddingBottom: 28, paddingHorizontal: 56, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', }, bannerLeft: { flex: 2 }, bannerRight: { flex: 1, alignItems: 'flex-end' }, invoiceTitle: { fontSize: 26, fontWeight: 'bold', color: PALETTE.paper, letterSpacing: 1 }, invoiceNumero: { fontSize: 11, color: PALETTE.paper, opacity: 0.85, marginTop: 4 }, bannerCompany: { fontSize: 13, color: PALETTE.paper, fontWeight: 'bold' }, bannerMeta: { fontSize: 8, color: PALETTE.paper, opacity: 0.85 }, logo: { width: 60, height: 24, objectFit: 'contain', marginBottom: 6 }, // Corps body: { paddingTop: 32, paddingHorizontal: 56, paddingBottom: 36, flexGrow: 1 }, metaRow: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 24 }, metaBlock: { flex: 1 }, metaLabel: { fontSize: 8, textTransform: 'uppercase', color: PALETTE.ink3, marginBottom: 4, }, clientName: { fontSize: 12, fontWeight: 'bold' }, // Table table: { marginBottom: 16 }, tableHeader: { flexDirection: 'row', paddingVertical: 8, paddingHorizontal: 6, }, tableHeaderText: { fontSize: 8, textTransform: 'uppercase', fontWeight: 'bold', color: PALETTE.paper }, tableRow: { flexDirection: 'row', paddingVertical: 8, paddingHorizontal: 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' }, // Totaux totalsBlock: { alignSelf: 'flex-end', width: '45%', marginBottom: 24 }, totalRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 3 }, totalRowGrand: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 10, paddingHorizontal: 12, marginTop: 6, }, totalLabel: { color: PALETTE.ink2 }, grandLabel: { fontSize: 12, fontWeight: 'bold', color: PALETTE.paper }, grandAmount: { fontSize: 14, fontWeight: 'bold', color: PALETTE.paper }, // 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 }, cellHead: { fontSize: 8, textTransform: 'uppercase', color: PALETTE.ink3, fontWeight: 'bold' }, // 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.4, }, }) export function ModerneTemplate(props: InvoiceTemplateProps) { const accentBg = { backgroundColor: 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 ( {/* Bandeau coloré */} FACTURE N° {props.numero} {props.logoUrl ? : null} {props.issuer.companyName ?? '—'} {issuerAddress.slice(0, 2).map((line, i) => ( {line} ))} {props.issuer.siret ? ( SIRET {props.issuer.siret} ) : null} {/* Méta : client + dates */} 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} Émise le {formatDate(props.issueDate)} Échéance {formatDate(props.dueDate)} {props.paymentTermsDays} jour{props.paymentTermsDays > 1 ? 's' : ''} de délai {/* 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} ) }