/** * Template "Classique" — sobre, sérieux, header texte centré. * * Cible : cabinets, professions réglementées, structures traditionnelles. * Pas de bandeau coloré, pas d'ornementation. L'accent color est utilisé * uniquement pour les filets de séparation et le numéro de facture. */ import React from 'react' import { Document, Page, View, Text, Image, StyleSheet } from '@react-pdf/renderer' import { type InvoiceTemplateProps, PALETTE, formatCents, formatDate, formatQuantity, formatTvaRate, formatAddress, daysBetween, } from '#pdf-templates/common' const styles = StyleSheet.create({ page: { paddingTop: 56, paddingBottom: 56, paddingHorizontal: 56, fontSize: 10, color: PALETTE.ink, fontFamily: 'Helvetica', lineHeight: 1.5, }, // Header headerCenter: { textAlign: 'center', marginBottom: 24 }, logo: { width: 80, height: 32, objectFit: 'contain', marginBottom: 8, alignSelf: 'center' }, companyName: { fontSize: 16, fontWeight: 'bold', marginBottom: 4 }, companyMeta: { fontSize: 9, color: PALETTE.ink2 }, divider: { borderBottomWidth: 1, marginVertical: 16 }, // Titre facture invoiceBlock: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 20 }, invoiceTitle: { fontSize: 22, fontWeight: 'bold' }, invoiceMeta: { fontSize: 10 }, invoiceMetaRow: { flexDirection: 'row', justifyContent: 'space-between', gap: 12 }, invoiceMetaLabel: { color: PALETTE.ink2 }, // Client clientBlock: { borderWidth: 1, borderColor: PALETTE.line, padding: 12, marginBottom: 20, width: '50%', alignSelf: 'flex-end', }, clientLabel: { fontSize: 8, textTransform: 'uppercase', color: PALETTE.ink3, marginBottom: 4 }, clientName: { fontSize: 11, 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, fontWeight: 'bold' }, // Totals totalsBlock: { alignSelf: 'flex-end', width: '45%', marginBottom: 24 }, totalRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 3 }, totalRowGrand: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 8, marginTop: 4, borderTopWidth: 1, }, totalLabel: { color: PALETTE.ink2 }, grandLabel: { fontSize: 12, fontWeight: 'bold' }, grandAmount: { fontSize: 12, 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.4, }, }) export function ClassiqueTemplate(props: InvoiceTemplateProps) { const accent = { color: props.accentColor } const dividerAccent = { borderBottomColor: props.accentColor } const grandAccent = { borderTopColor: 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 centré : logo + identité émetteur */} {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} {/* Bloc facture (gauche) + meta (droite) */} FACTURE N° {props.numero} Date d'émission {formatDate(props.issueDate)} Date d'échéance {formatDate(props.dueDate)} Délai {props.paymentTermsDays} jour{props.paymentTermsDays > 1 ? 's' : ''} {/* 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} {/* Table des lignes */} 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)} ))} {/* Ventilation TVA — affichée seulement si plusieurs taux */} {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)} {/* Paiement / RIB */} {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} {/* Pied légal */} {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} Échéance : {formatDate(props.dueDate)} ( {daysBetween(props.issueDate, props.dueDate)} jours) ) }