305 lines
10 KiB
TypeScript
305 lines
10 KiB
TypeScript
'use client';
|
|
|
|
import * as React from 'react';
|
|
import { View as PDFView, Text as PDFText, StyleSheet, Link as PDFLink, Image as PDFImage, Font } from '@react-pdf/renderer';
|
|
|
|
// INDUSTRIAL DESIGN SYSTEM TOKENS
|
|
export const COLORS = {
|
|
CHARCOAL: '#0f172a', // Slate 900
|
|
TEXT_MAIN: '#334155', // Slate 700
|
|
TEXT_DIM: '#64748b', // Slate 500
|
|
TEXT_LIGHT: '#94a3b8', // Slate 400
|
|
DIVIDER: '#cbd5e1', // Slate 300
|
|
GRID: '#f1f5f9', // Slate 100
|
|
BLUEPRINT: '#e2e8f0', // Slate 200
|
|
WHITE: '#ffffff'
|
|
};
|
|
|
|
export const FONT_SIZES = {
|
|
H1: 28,
|
|
H2: 20,
|
|
H3: 14,
|
|
BODY: 11,
|
|
TINY: 9,
|
|
SUB: 10,
|
|
BLUEPRINT: 8
|
|
};
|
|
|
|
// Register a more technical font if possible, or use Helvetica with varying weights
|
|
// Note: helvetica-bold is standard in react-pdf
|
|
|
|
export const pdfStyles = StyleSheet.create({
|
|
page: {
|
|
paddingTop: 45, // DIN 5008
|
|
paddingLeft: 70, // ~25mm
|
|
paddingRight: 57, // ~20mm
|
|
paddingBottom: 80, // Safe buffer for absolute footer
|
|
backgroundColor: COLORS.WHITE,
|
|
fontFamily: 'Helvetica',
|
|
fontSize: FONT_SIZES.BODY,
|
|
color: COLORS.CHARCOAL,
|
|
},
|
|
titlePage: {
|
|
width: '100%',
|
|
height: '100%',
|
|
backgroundColor: COLORS.WHITE,
|
|
fontFamily: 'Helvetica',
|
|
color: COLORS.CHARCOAL,
|
|
padding: 0, // NO PADDING to prevent inner overflow page breaks
|
|
},
|
|
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: FONT_SIZES.TINY,
|
|
textDecoration: 'underline',
|
|
color: COLORS.TEXT_DIM,
|
|
marginBottom: 8,
|
|
},
|
|
recipientAddress: {
|
|
fontSize: FONT_SIZES.BODY,
|
|
lineHeight: 1.4,
|
|
},
|
|
brandLogoContainer: {
|
|
width: '40%',
|
|
alignItems: 'flex-end',
|
|
},
|
|
brandIconContainer: {
|
|
width: 40,
|
|
height: 40,
|
|
backgroundColor: '#0f172a',
|
|
borderRadius: 8,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
marginBottom: 12,
|
|
},
|
|
brandIconText: {
|
|
color: COLORS.WHITE,
|
|
fontSize: 20,
|
|
fontWeight: 'bold',
|
|
},
|
|
titleInfo: {
|
|
marginBottom: 24,
|
|
},
|
|
mainTitle: {
|
|
fontSize: FONT_SIZES.H3,
|
|
fontWeight: 'bold',
|
|
marginBottom: 4,
|
|
color: COLORS.CHARCOAL,
|
|
letterSpacing: 0.5,
|
|
},
|
|
subTitle: {
|
|
fontSize: FONT_SIZES.BODY,
|
|
color: COLORS.TEXT_DIM,
|
|
marginTop: 2,
|
|
},
|
|
section: {
|
|
marginBottom: 32,
|
|
},
|
|
sectionTitle: {
|
|
fontSize: FONT_SIZES.SUB,
|
|
fontWeight: 'bold',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 1,
|
|
color: COLORS.TEXT_LIGHT,
|
|
marginBottom: 8,
|
|
},
|
|
footer: {
|
|
position: 'absolute',
|
|
bottom: 32,
|
|
left: 70,
|
|
right: 57,
|
|
borderTopWidth: 1,
|
|
borderTopColor: COLORS.GRID,
|
|
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: FONT_SIZES.TINY,
|
|
color: COLORS.TEXT_LIGHT,
|
|
lineHeight: 1.5,
|
|
},
|
|
footerLabel: {
|
|
fontWeight: 'bold',
|
|
color: COLORS.TEXT_DIM,
|
|
},
|
|
pageNumber: {
|
|
fontSize: FONT_SIZES.TINY,
|
|
color: COLORS.DIVIDER,
|
|
fontWeight: 'bold',
|
|
marginTop: 8,
|
|
textAlign: 'right',
|
|
},
|
|
foldingMark: {
|
|
position: 'absolute',
|
|
left: 20,
|
|
width: 10,
|
|
borderTopWidth: 0.5,
|
|
borderTopColor: COLORS.DIVIDER,
|
|
},
|
|
divider: {
|
|
width: '100%',
|
|
height: 1,
|
|
backgroundColor: COLORS.DIVIDER,
|
|
marginVertical: 12,
|
|
},
|
|
blueprintGrid: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
zIndex: -10,
|
|
},
|
|
gridLineH: {
|
|
width: '100%',
|
|
height: 0.5,
|
|
backgroundColor: COLORS.GRID,
|
|
position: 'absolute',
|
|
},
|
|
gridLineV: {
|
|
width: 0.5,
|
|
height: '100%',
|
|
backgroundColor: COLORS.GRID,
|
|
position: 'absolute',
|
|
},
|
|
technicalMarker: {
|
|
position: 'absolute',
|
|
fontSize: FONT_SIZES.BLUEPRINT,
|
|
color: COLORS.BLUEPRINT,
|
|
fontFamily: 'Helvetica',
|
|
letterSpacing: 1,
|
|
},
|
|
// Atoms
|
|
industrialListItem: {
|
|
flexDirection: 'row',
|
|
alignItems: 'flex-start',
|
|
marginBottom: 6,
|
|
},
|
|
industrialBulletBox: {
|
|
width: 6,
|
|
height: 6,
|
|
backgroundColor: COLORS.DIVIDER,
|
|
marginRight: 8,
|
|
marginTop: 5,
|
|
},
|
|
industrialTitle: {
|
|
fontSize: FONT_SIZES.H1,
|
|
fontWeight: 'bold',
|
|
color: COLORS.CHARCOAL,
|
|
marginBottom: 6,
|
|
letterSpacing: 0, // Reset for clarity
|
|
},
|
|
industrialSubtitle: {
|
|
fontSize: FONT_SIZES.SUB,
|
|
fontWeight: 'bold',
|
|
color: COLORS.TEXT_LIGHT,
|
|
textTransform: 'uppercase',
|
|
marginBottom: 16,
|
|
letterSpacing: 2,
|
|
},
|
|
industrialTextLead: {
|
|
fontSize: FONT_SIZES.BODY,
|
|
color: COLORS.TEXT_MAIN,
|
|
lineHeight: 1.6,
|
|
marginBottom: 12,
|
|
},
|
|
industrialText: {
|
|
fontSize: FONT_SIZES.BODY,
|
|
color: COLORS.TEXT_DIM,
|
|
lineHeight: 1.6,
|
|
marginBottom: 8,
|
|
},
|
|
industrialCard: {
|
|
padding: 16,
|
|
borderWidth: 1,
|
|
borderColor: COLORS.BLUEPRINT,
|
|
marginBottom: 12,
|
|
},
|
|
industrialCardTitle: {
|
|
fontSize: FONT_SIZES.BODY + 1, // 10
|
|
fontWeight: 'bold',
|
|
color: COLORS.CHARCOAL,
|
|
marginBottom: 4,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.5,
|
|
},
|
|
darkBox: {
|
|
marginTop: 32,
|
|
padding: 24,
|
|
backgroundColor: COLORS.CHARCOAL,
|
|
color: COLORS.WHITE,
|
|
},
|
|
darkTitle: {
|
|
fontSize: FONT_SIZES.H2,
|
|
fontWeight: 'bold',
|
|
color: COLORS.WHITE,
|
|
marginBottom: 8,
|
|
},
|
|
darkText: {
|
|
fontSize: FONT_SIZES.BODY,
|
|
color: COLORS.TEXT_LIGHT,
|
|
lineHeight: 1.6,
|
|
},
|
|
});
|
|
|
|
const styles = pdfStyles;
|
|
|
|
export const BlueprintBackground = () => (
|
|
<PDFView style={styles.blueprintGrid} fixed>
|
|
{/* Clean background - grid lines removed per user request */}
|
|
</PDFView>
|
|
);
|
|
|
|
export const IndustrialListItem = ({ children }: { children: React.ReactNode }) => (
|
|
<PDFView style={pdfStyles.industrialListItem}>
|
|
<PDFView style={pdfStyles.industrialBulletBox} />
|
|
{children}
|
|
</PDFView>
|
|
);
|
|
|
|
export const IndustrialCard = ({ title, children, style = {} }: { title: string; children: React.ReactNode; style?: any }) => (
|
|
<PDFView style={[pdfStyles.industrialCard, style]}>
|
|
<PDFText style={pdfStyles.industrialCardTitle}>{title}</PDFText>
|
|
{children}
|
|
</PDFView>
|
|
);
|
|
|
|
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 Divider = ({ style = {} }: { style?: any }) => (
|
|
<PDFView style={[pdfStyles.divider, style]} />
|
|
);
|
|
|
|
export const Footer = ({ logo, companyData, bankData, showDetails = true, showPageNumber = true }: { logo?: string; companyData: any; bankData: any; showDetails?: boolean; showPageNumber?: boolean }) => (
|
|
<PDFView style={pdfStyles.footer}><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' }]}>{showPageNumber && <PDFText style={pdfStyles.pageNumber} render={({ pageNumber, totalPages }) => `${pageNumber} / ${totalPages}`} fixed />}</PDFView></>)}{!showDetails && (<PDFView style={[pdfStyles.footerColumn, { alignItems: 'flex-end' }]}>{showPageNumber && <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; address?: string; phone?: string; taxId?: string }; icon?: string; showAddress?: boolean; }) => (
|
|
<PDFView style={[pdfStyles.header, showAddress ? {} : { minHeight: 40, marginBottom: 0 }]}><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.address && <PDFText>{recipient.address}</PDFText>}{recipient.phone && <PDFText>{recipient.phone}</PDFText>}{recipient.email && <PDFText>{recipient.email}</PDFText>}{recipient.taxId && <PDFText>USt-ID: {recipient.taxId}</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: '#0f172a' } : {}]}>{line}</PDFText>))}</PDFView>
|
|
);
|