InvoicePDF.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import { Document, Image, Page, Text, View } from '@react-pdf/renderer'
  2. import { buildInvoicePrintSpec } from '@/features/invoice-designer/Pdf/buildInvoicePrint'
  3. import type { VisitConditionMap } from '@/features/condition-map/Lib/print'
  4. import type { InspectionResults } from '@/features/inspections/Lib/inspectionResults'
  5. import { pdfFamily } from '@/features/invoice-designer/Pdf/renderPdf'
  6. import { SpecPdfPage } from '@/features/invoice-designer/Pdf/SpecPdf'
  7. import type {
  8. ImageAttachment,
  9. InvoiceData,
  10. InvoiceSettingsProps,
  11. OtherAttachment,
  12. PaymentSummary,
  13. TemplateConfig,
  14. WorkshopInfo,
  15. } from './types'
  16. /**
  17. * The printed invoice.
  18. *
  19. * The sheet itself is the document the designer edits: the same generator
  20. * builds the same blocks, laid out by the same engine, so an invoice prints
  21. * the way the workshop arranged it — hand-placed sections, narrowed widths,
  22. * spacing and all. This file only maps the job onto that document and
  23. * appends the attachment pages.
  24. */
  25. export function InvoicePDF({
  26. data,
  27. workshop,
  28. invoiceSettings,
  29. paymentSummary,
  30. imageAttachments = [],
  31. otherAttachments = [],
  32. pdfAttachmentNames = [],
  33. logoDataUri,
  34. signer,
  35. template,
  36. torqvoiceLogoDataUri,
  37. portalUrl,
  38. telegramQrDataUri,
  39. telegramLabel,
  40. labels = {},
  41. locale,
  42. conditionMap,
  43. inspectionResults,
  44. }: {
  45. data: InvoiceData
  46. workshop?: WorkshopInfo
  47. invoiceSettings?: InvoiceSettingsProps
  48. paymentSummary?: PaymentSummary
  49. imageAttachments?: ImageAttachment[]
  50. otherAttachments?: OtherAttachment[]
  51. pdfAttachmentNames?: string[]
  52. logoDataUri?: string
  53. signer?: { name: string; dataUri?: string }
  54. template?: TemplateConfig
  55. torqvoiceLogoDataUri?: string
  56. portalUrl?: string
  57. telegramQrDataUri?: string
  58. telegramLabel?: string
  59. labels?: Record<string, string>
  60. /** The document's language; numbers are written its way. */
  61. locale?: string
  62. conditionMap?: VisitConditionMap
  63. inspectionResults?: InspectionResults | null
  64. }) {
  65. const spec = buildInvoicePrintSpec({
  66. data,
  67. workshop,
  68. invoiceSettings,
  69. paymentSummary,
  70. pdfAttachmentNames,
  71. otherAttachmentNames: otherAttachments.map((att) => att.fileName),
  72. logoDataUri,
  73. signer,
  74. template,
  75. torqvoiceLogoDataUri,
  76. portalUrl,
  77. telegramQrDataUri,
  78. telegramLabel,
  79. labels,
  80. locale,
  81. conditionMap,
  82. inspectionResults,
  83. })
  84. const shopDisplayName = workshop?.name || data.shopName || 'Torqvoice'
  85. const invoiceNum = data.invoiceNumber || `INV-${data.id.slice(-8).toUpperCase()}`
  86. const fontFamily = pdfFamily(spec.page.fontFamily)
  87. return (
  88. <Document>
  89. <SpecPdfPage spec={spec} />
  90. {imageAttachments.length > 0 && (
  91. <Page size="A4" style={{ padding: 40, fontFamily, fontSize: 9, color: '#111827' }}>
  92. <Text style={{ fontSize: 12, fontWeight: 700, marginBottom: 10 }}>
  93. {labels.serviceImages || 'Service Images'}
  94. </Text>
  95. <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 12 }}>
  96. {imageAttachments.map((img, i) => (
  97. <View key={i} style={{ width: '48%', marginBottom: 8 }}>
  98. <Image
  99. src={img.dataUri}
  100. style={{
  101. maxHeight: 250,
  102. borderRadius: 4,
  103. objectFit: 'contain',
  104. objectPosition: 'left',
  105. }}
  106. />
  107. <Text style={{ fontSize: 8, color: '#6b7280', marginTop: 2 }}>
  108. {img.description || img.fileName}
  109. </Text>
  110. </View>
  111. ))}
  112. </View>
  113. <Text
  114. fixed
  115. style={{
  116. position: 'absolute',
  117. bottom: 16,
  118. left: 40,
  119. right: 40,
  120. fontSize: 8,
  121. color: '#6b7280',
  122. textAlign: 'center',
  123. }}
  124. >
  125. {shopDisplayName} · {invoiceNum}
  126. </Text>
  127. </Page>
  128. )}
  129. </Document>
  130. )
  131. }