/** * Template "Minimal" — noir et blanc, aéré, aucun ornement. * * Cible : indépendants, designers, profils qui veulent un rendu Apple-clean. * L'accent color n'est utilisé que pour le numéro de facture (sobre). * Pas de filets, beaucoup d'espace blanc, typographie en valeur. */ 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: 72, paddingBottom: 60, paddingHorizontal: 72, fontSize: 10, color: PALETTE.ink, fontFamily: 'Helvetica', lineHeight: 1.6, }, // Header header: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 56 }, headerLeft: { flex: 1 }, headerRight: { flex: 1, alignItems: 'flex-end' }, logo: { width: 48, height: 18, objectFit: 'contain', marginBottom: 12 }, invoiceLabel: { fontSize: 9, textTransform: 'uppercase', color: PALETTE.ink3, letterSpacing: 2 }, invoiceNumero: { fontSize: 18, marginTop: 4 }, companyName: { fontSize: 11, fontWeight: 'bold' }, small: { fontSize: 9, color: PALETTE.ink2 }, // Méta metaRow: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 48 }, metaBlock: { flex: 1 }, metaLabel: { fontSize: 8, textTransform: 'uppercase', color: PALETTE.ink3, marginBottom: 6 }, metaValue: { fontSize: 11 }, // Table table: { marginBottom: 24 }, tableHeader: { flexDirection: 'row', paddingBottom: 8 }, tableRow: { flexDirection: 'row', paddingVertical: 8 }, 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: 32 }, totalRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 4 }, totalRowGrand: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 12, marginTop: 8, }, totalLabel: { color: PALETTE.ink2 }, grandLabel: { fontSize: 14, fontWeight: 'bold' }, grandAmount: { fontSize: 14, fontWeight: 'bold' }, // TVA breakdown tvaBlock: { marginBottom: 24, width: '60%' }, 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: 16, marginBottom: 16 }, paymentLabel: { fontSize: 8, textTransform: 'uppercase', color: PALETTE.ink3, marginBottom: 4, }, legalBlock: { marginTop: 'auto', paddingTop: 24, fontSize: 7, color: PALETTE.ink3, lineHeight: 1.5, }, }) export function MinimalTemplate(props: InvoiceTemplateProps) { const accent = { color: 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 sobre : logo + numéro grand */} {props.logoUrl ? : null} {props.issuer.companyName ?? '—'} {issuerAddress.map((line, i) => ( {line} ))} {props.issuer.siret ? ( SIRET {props.issuer.siret} ) : null} Facture {props.numero} {/* Méta : client + dates */} Client {props.client.name} {clientAddress.map((line, i) => ( {line} ))} {props.client.siret ? ( SIRET {props.client.siret} ) : null} Émise le {formatDate(props.issueDate)} Échéance {formatDate(props.dueDate)} {/* 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 ? ( {props.tvaBreakdown.map((b) => ( TVA {formatTvaRate(b.rate)} sur {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 ? ( 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 ? ( {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} ) }