feat: Implement combined quote PDF with AGBs and recurring pricing, utilizing shared PDF UI components.
This commit is contained in:
231
src/components/pdf/SharedUI.tsx
Normal file
231
src/components/pdf/SharedUI.tsx
Normal file
@@ -0,0 +1,231 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Text as PDFText,
|
||||
View as PDFView,
|
||||
StyleSheet as PDFStyleSheet,
|
||||
Image as PDFImage
|
||||
} from '@react-pdf/renderer';
|
||||
|
||||
export const pdfStyles = PDFStyleSheet.create({
|
||||
page: {
|
||||
paddingTop: 45, // DIN 5008
|
||||
paddingLeft: 70, // ~25mm
|
||||
paddingRight: 57, // ~20mm
|
||||
paddingBottom: 48,
|
||||
backgroundColor: '#ffffff',
|
||||
fontFamily: 'Helvetica',
|
||||
fontSize: 10,
|
||||
color: '#000000',
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: 20,
|
||||
minHeight: 120,
|
||||
},
|
||||
addressBlock: {
|
||||
width: '55%',
|
||||
marginTop: 45, // DIN 5008 positioning for window
|
||||
},
|
||||
senderLine: {
|
||||
fontSize: 7,
|
||||
textDecoration: 'underline',
|
||||
color: '#666666',
|
||||
marginBottom: 8,
|
||||
},
|
||||
recipientAddress: {
|
||||
fontSize: 10,
|
||||
lineHeight: 1.4,
|
||||
},
|
||||
brandLogoContainer: {
|
||||
width: '40%',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
brandIconContainer: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
backgroundColor: '#000000',
|
||||
borderRadius: 8,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
brandIconText: {
|
||||
color: '#ffffff',
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
titleInfo: {
|
||||
marginBottom: 24,
|
||||
},
|
||||
mainTitle: {
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 4,
|
||||
color: '#000000',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 1,
|
||||
},
|
||||
subTitle: {
|
||||
fontSize: 9,
|
||||
color: '#666666',
|
||||
marginTop: 2,
|
||||
},
|
||||
section: {
|
||||
marginBottom: 32,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 8,
|
||||
fontWeight: 'bold',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 1,
|
||||
color: '#999999',
|
||||
marginBottom: 8,
|
||||
},
|
||||
footer: {
|
||||
position: 'absolute',
|
||||
bottom: 32,
|
||||
left: 70,
|
||||
right: 57,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#f1f5f9',
|
||||
paddingTop: 16,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
footerColumn: {
|
||||
flex: 1,
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
footerLogo: {
|
||||
height: 20,
|
||||
width: 'auto',
|
||||
objectFit: 'contain',
|
||||
marginBottom: 8,
|
||||
},
|
||||
footerText: {
|
||||
fontSize: 7,
|
||||
color: '#94a3b8',
|
||||
lineHeight: 1.5,
|
||||
},
|
||||
footerLabel: {
|
||||
fontWeight: 'bold',
|
||||
color: '#64748b',
|
||||
},
|
||||
pageNumber: {
|
||||
fontSize: 7,
|
||||
color: '#cbd5e1',
|
||||
fontWeight: 'bold',
|
||||
marginTop: 8,
|
||||
textAlign: 'right',
|
||||
},
|
||||
foldingMark: {
|
||||
position: 'absolute',
|
||||
left: 20,
|
||||
width: 10,
|
||||
borderTopWidth: 0.5,
|
||||
borderTopColor: '#cbd5e1',
|
||||
}
|
||||
});
|
||||
|
||||
export const FoldingMarks = () => (
|
||||
<>
|
||||
<PDFView style={[pdfStyles.foldingMark, { top: 297.6 }]} fixed />
|
||||
<PDFView style={[pdfStyles.foldingMark, { top: 420.9, width: 15 }]} fixed />
|
||||
<PDFView style={[pdfStyles.foldingMark, { top: 595.3 }]} fixed />
|
||||
</>
|
||||
);
|
||||
|
||||
export const Footer = ({ logo, companyData, bankData, showDetails = true }: { logo?: string; companyData: any; bankData: any; showDetails?: boolean }) => (
|
||||
<PDFView style={pdfStyles.footer} fixed>
|
||||
<PDFView style={pdfStyles.footerColumn}>
|
||||
{logo ? (
|
||||
<PDFImage src={logo} style={pdfStyles.footerLogo} />
|
||||
) : (
|
||||
<PDFText style={{ fontSize: 12, fontWeight: 'bold', marginBottom: 8 }}>marc mintel</PDFText>
|
||||
)}
|
||||
</PDFView>
|
||||
|
||||
{showDetails && (
|
||||
<>
|
||||
<PDFView style={pdfStyles.footerColumn}>
|
||||
<PDFText style={pdfStyles.footerText}>
|
||||
<PDFText style={pdfStyles.footerLabel}>{companyData.name}</PDFText>{"\n"}
|
||||
{companyData.address1}{"\n"}
|
||||
{companyData.address2}{"\n"}
|
||||
UST: {companyData.ustId}
|
||||
</PDFText>
|
||||
</PDFView>
|
||||
|
||||
<PDFView style={[pdfStyles.footerColumn, { alignItems: 'flex-end' }]}>
|
||||
<PDFText style={[pdfStyles.footerText, { textAlign: 'right' }]}>
|
||||
<PDFText style={pdfStyles.footerLabel}>{bankData.name}</PDFText>{"\n"}
|
||||
{bankData.bic}{"\n"}
|
||||
{bankData.iban}
|
||||
</PDFText>
|
||||
<PDFText style={pdfStyles.pageNumber} render={({ pageNumber, totalPages }) => `${pageNumber} / ${totalPages}`} fixed />
|
||||
</PDFView>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!showDetails && (
|
||||
<PDFView style={[pdfStyles.footerColumn, { alignItems: 'flex-end' }]}>
|
||||
<PDFText style={pdfStyles.pageNumber} render={({ pageNumber, totalPages }) => `${pageNumber} / ${totalPages}`} fixed />
|
||||
</PDFView>
|
||||
)}
|
||||
</PDFView>
|
||||
);
|
||||
|
||||
export const Header = ({
|
||||
sender,
|
||||
recipient,
|
||||
icon,
|
||||
showAddress = true
|
||||
}: {
|
||||
sender?: string;
|
||||
recipient?: { title: string; subtitle?: string; email?: string };
|
||||
icon?: string;
|
||||
showAddress?: boolean;
|
||||
}) => (
|
||||
<PDFView style={pdfStyles.header}>
|
||||
<PDFView style={pdfStyles.addressBlock}>
|
||||
{showAddress && sender && (
|
||||
<>
|
||||
<PDFText style={pdfStyles.senderLine}>{sender}</PDFText>
|
||||
{recipient && (
|
||||
<PDFView style={pdfStyles.recipientAddress}>
|
||||
<PDFText style={{ fontWeight: 'bold' }}>{recipient.title}</PDFText>
|
||||
{recipient.subtitle && <PDFText>{recipient.subtitle}</PDFText>}
|
||||
{recipient.email && <PDFText>{recipient.email}</PDFText>}
|
||||
</PDFView>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PDFView>
|
||||
|
||||
<PDFView style={pdfStyles.brandLogoContainer}>
|
||||
<PDFView style={pdfStyles.brandIconContainer}>
|
||||
{icon ? (
|
||||
<PDFImage src={icon} style={{ width: 24, height: 24 }} />
|
||||
) : (
|
||||
<PDFText style={pdfStyles.brandIconText}>M</PDFText>
|
||||
)}
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
);
|
||||
|
||||
export const DocumentTitle = ({ title, subLines }: { title: string; subLines?: string[] }) => (
|
||||
<PDFView style={pdfStyles.titleInfo}>
|
||||
<PDFText style={pdfStyles.mainTitle}>{title}</PDFText>
|
||||
{subLines?.map((line, i) => (
|
||||
<PDFText key={i} style={[pdfStyles.subTitle, i === 1 ? { fontWeight: 'bold', color: '#000000' } : {}]}>
|
||||
{line}
|
||||
</PDFText>
|
||||
))}
|
||||
</PDFView>
|
||||
);
|
||||
Reference in New Issue
Block a user