438 lines
10 KiB
TypeScript
438 lines
10 KiB
TypeScript
import * as React from 'react';
|
|
import {
|
|
Document,
|
|
Page,
|
|
View,
|
|
Text,
|
|
Image,
|
|
StyleSheet,
|
|
Font,
|
|
} from '@react-pdf/renderer';
|
|
|
|
// Register fonts (using system fonts for now, can be customized)
|
|
Font.register({
|
|
family: 'Helvetica',
|
|
fonts: [
|
|
{ src: '/fonts/Helvetica.ttf', fontWeight: 400 },
|
|
{ src: '/fonts/Helvetica-Bold.ttf', fontWeight: 700 },
|
|
],
|
|
});
|
|
|
|
// Industrial/technical/restrained design - STYLEGUIDE.md compliant
|
|
const styles = StyleSheet.create({
|
|
page: {
|
|
padding: 72, // Large margins for engineering documentation feel
|
|
fontFamily: 'Helvetica',
|
|
fontSize: 10,
|
|
color: '#1F2933', // Dark gray text
|
|
lineHeight: 1.5, // Generous line height
|
|
backgroundColor: '#F8F9FA', // Almost white background
|
|
},
|
|
|
|
// Engineering documentation header
|
|
header: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'flex-start',
|
|
marginBottom: 48, // Large spacing
|
|
paddingBottom: 24,
|
|
borderBottom: '2px solid #E6E9ED', // Light gray separator
|
|
},
|
|
|
|
// Logo area - industrial style
|
|
logoArea: {
|
|
flexDirection: 'column',
|
|
alignItems: 'flex-start',
|
|
},
|
|
|
|
// Optional image logo container (keeps header height stable)
|
|
logoContainer: {
|
|
width: 120,
|
|
height: 32,
|
|
justifyContent: 'center',
|
|
},
|
|
|
|
// Image logo (preferred when available)
|
|
logo: {
|
|
width: 110,
|
|
height: 28,
|
|
objectFit: 'contain',
|
|
},
|
|
|
|
logoText: {
|
|
fontSize: 20,
|
|
fontWeight: 700,
|
|
color: '#0E2A47', // Dark navy
|
|
letterSpacing: 1,
|
|
textTransform: 'uppercase',
|
|
},
|
|
|
|
logoSubtext: {
|
|
fontSize: 10,
|
|
fontWeight: 400,
|
|
color: '#6B7280', // Medium gray
|
|
letterSpacing: 0.5,
|
|
marginTop: 2,
|
|
},
|
|
|
|
// Document info - technical style
|
|
docInfo: {
|
|
textAlign: 'right',
|
|
alignItems: 'flex-end',
|
|
},
|
|
|
|
docTitle: {
|
|
fontSize: 16,
|
|
fontWeight: 700,
|
|
color: '#0E2A47', // Dark navy
|
|
marginBottom: 8,
|
|
letterSpacing: 0.5,
|
|
textTransform: 'uppercase',
|
|
},
|
|
|
|
skuContainer: {
|
|
backgroundColor: '#E6E9ED', // Light gray background
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 8,
|
|
border: '1px solid #E6E9ED',
|
|
},
|
|
|
|
skuLabel: {
|
|
fontSize: 8,
|
|
color: '#6B7280', // Medium gray
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.5,
|
|
marginBottom: 4,
|
|
},
|
|
|
|
skuValue: {
|
|
fontSize: 14,
|
|
fontWeight: 700,
|
|
color: '#0E2A47', // Dark navy
|
|
},
|
|
|
|
// Product section - technical specification style
|
|
productSection: {
|
|
marginBottom: 40,
|
|
backgroundColor: '#FFFFFF', // White background for content blocks
|
|
padding: 24,
|
|
border: '1px solid #E6E9ED',
|
|
},
|
|
|
|
productName: {
|
|
fontSize: 24,
|
|
fontWeight: 700,
|
|
color: '#0E2A47', // Dark navy
|
|
marginBottom: 12,
|
|
lineHeight: 1.2,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.5,
|
|
},
|
|
|
|
productMeta: {
|
|
fontSize: 12,
|
|
color: '#6B7280', // Medium gray
|
|
fontWeight: 500,
|
|
},
|
|
|
|
// Content sections - rectangular blocks
|
|
section: {
|
|
marginBottom: 32,
|
|
backgroundColor: '#FFFFFF',
|
|
padding: 24,
|
|
border: '1px solid #E6E9ED',
|
|
},
|
|
|
|
sectionTitle: {
|
|
fontSize: 14,
|
|
fontWeight: 700,
|
|
color: '#0E2A47', // Dark navy
|
|
marginBottom: 16,
|
|
letterSpacing: 0.5,
|
|
textTransform: 'uppercase',
|
|
borderBottom: '1px solid #E6E9ED',
|
|
paddingBottom: 8,
|
|
},
|
|
|
|
// Description - technical documentation style
|
|
description: {
|
|
fontSize: 10,
|
|
lineHeight: 1.6,
|
|
color: '#1F2933', // Dark gray text
|
|
marginBottom: 0,
|
|
},
|
|
|
|
// Cross-section table - engineering specification style
|
|
table: {
|
|
marginTop: 16,
|
|
},
|
|
|
|
tableHeader: {
|
|
flexDirection: 'row',
|
|
backgroundColor: '#E6E9ED',
|
|
borderBottom: '1px solid #E6E9ED',
|
|
},
|
|
|
|
tableHeaderCell: {
|
|
flex: 1,
|
|
padding: 8,
|
|
fontSize: 10,
|
|
fontWeight: 700,
|
|
color: '#0E2A47',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.3,
|
|
},
|
|
|
|
tableRow: {
|
|
flexDirection: 'row',
|
|
borderBottom: '1px solid #F8F9FA',
|
|
},
|
|
|
|
tableCell: {
|
|
flex: 1,
|
|
padding: 8,
|
|
fontSize: 10,
|
|
color: '#1F2933',
|
|
},
|
|
|
|
tableRowAlt: {
|
|
backgroundColor: '#F8F9FA',
|
|
},
|
|
|
|
// Specifications - technical data style
|
|
specsContainer: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
},
|
|
|
|
// Backwards-compatible alias used by the component markup
|
|
specsGrid: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
justifyContent: 'space-between',
|
|
},
|
|
|
|
specColumn: {
|
|
width: '48%',
|
|
marginRight: '4%',
|
|
marginBottom: 16,
|
|
},
|
|
|
|
specItem: {
|
|
marginBottom: 12,
|
|
},
|
|
|
|
specLabel: {
|
|
fontSize: 9,
|
|
fontWeight: 700,
|
|
color: '#0E2A47',
|
|
marginBottom: 4,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.3,
|
|
},
|
|
|
|
specValue: {
|
|
fontSize: 10,
|
|
color: '#1F2933',
|
|
lineHeight: 1.4,
|
|
},
|
|
|
|
// Categories - technical classification
|
|
categories: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 8,
|
|
},
|
|
|
|
categoryTag: {
|
|
backgroundColor: '#E6E9ED',
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 6,
|
|
border: '1px solid #E6E9ED',
|
|
},
|
|
|
|
categoryText: {
|
|
fontSize: 9,
|
|
color: '#6B7280',
|
|
fontWeight: 500,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.3,
|
|
},
|
|
|
|
// Engineering documentation footer
|
|
footer: {
|
|
position: 'absolute',
|
|
bottom: 48,
|
|
left: 72,
|
|
right: 72,
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
paddingTop: 24,
|
|
borderTop: '2px solid #E6E9ED',
|
|
fontSize: 9,
|
|
color: '#6B7280',
|
|
},
|
|
|
|
footerLeft: {
|
|
fontWeight: 700,
|
|
color: '#0E2A47',
|
|
},
|
|
|
|
footerRight: {
|
|
color: '#6B7280',
|
|
},
|
|
});
|
|
|
|
interface ProductData {
|
|
id: number;
|
|
name: string;
|
|
shortDescriptionHtml: string;
|
|
descriptionHtml: string;
|
|
images: string[];
|
|
featuredImage: string | null;
|
|
sku: string;
|
|
categories: Array<{ name: string }>;
|
|
attributes: Array<{
|
|
name: string;
|
|
options: string[];
|
|
}>;
|
|
}
|
|
|
|
interface PDFDatasheetProps {
|
|
product: ProductData;
|
|
locale: 'en' | 'de';
|
|
logoUrl?: string;
|
|
}
|
|
|
|
// Helper to strip HTML tags
|
|
const stripHtml = (html: string): string => {
|
|
return html.replace(/<[^>]*>/g, '');
|
|
};
|
|
|
|
// Helper to get translated labels
|
|
const getLabels = (locale: 'en' | 'de') => {
|
|
const labels = {
|
|
en: {
|
|
productDatasheet: 'Product Datasheet',
|
|
description: 'Description',
|
|
specifications: 'Technical Specifications',
|
|
categories: 'Categories',
|
|
sku: 'SKU',
|
|
noImage: 'No image available',
|
|
},
|
|
de: {
|
|
productDatasheet: 'Produktdatenblatt',
|
|
description: 'Beschreibung',
|
|
specifications: 'Technische Spezifikationen',
|
|
categories: 'Kategorien',
|
|
sku: 'Artikelnummer',
|
|
noImage: 'Kein Bild verfügbar',
|
|
},
|
|
};
|
|
return labels[locale];
|
|
};
|
|
|
|
export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({
|
|
product,
|
|
locale,
|
|
logoUrl = '/media/logo.svg',
|
|
}) => {
|
|
const labels = getLabels(locale);
|
|
|
|
return (
|
|
<Document>
|
|
<Page size="A4" style={styles.page}>
|
|
{/* Clean, minimal header */}
|
|
<View style={styles.header}>
|
|
<View style={styles.logoArea}>
|
|
<View style={styles.logoContainer}>
|
|
{logoUrl ? (
|
|
<Image src={logoUrl} style={styles.logo} />
|
|
) : (
|
|
<View>
|
|
<Text style={styles.logoText}>KLZ</Text>
|
|
<Text style={styles.logoSubtext}>Cables</Text>
|
|
</View>
|
|
)}
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.docInfo}>
|
|
<Text style={styles.docTitle}>
|
|
{locale === 'en' ? 'Product Datasheet' : 'Produktdatenblatt'}
|
|
</Text>
|
|
<View style={styles.skuContainer}>
|
|
<Text style={styles.skuLabel}>{labels.sku}</Text>
|
|
<Text style={styles.skuValue}>{product.sku}</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Product section - clean and prominent */}
|
|
<View style={styles.productSection}>
|
|
<Text style={styles.productName}>{product.name}</Text>
|
|
<Text style={styles.productMeta}>
|
|
{product.categories.map(cat => cat.name).join(' • ')}
|
|
</Text>
|
|
</View>
|
|
|
|
{/* Description section */}
|
|
{(product.shortDescriptionHtml || product.descriptionHtml) && (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>{labels.description}</Text>
|
|
<Text style={styles.description}>
|
|
{stripHtml(product.shortDescriptionHtml || product.descriptionHtml)}
|
|
</Text>
|
|
</View>
|
|
)}
|
|
|
|
{/* Technical specifications */}
|
|
{product.attributes && product.attributes.length > 0 && (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>{labels.specifications}</Text>
|
|
<View style={styles.specsGrid}>
|
|
{product.attributes.map((attr, index) => (
|
|
<View key={index} style={styles.specItem}>
|
|
<Text style={styles.specLabel}>{attr.name}</Text>
|
|
<Text style={styles.specValue}>
|
|
{attr.options.join(', ')}
|
|
</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{/* Categories as clean tags */}
|
|
{product.categories && product.categories.length > 0 && (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>{labels.categories}</Text>
|
|
<View style={styles.categories}>
|
|
{product.categories.map((cat, index) => (
|
|
<View key={index} style={styles.categoryTag}>
|
|
<Text style={styles.categoryText}>{cat.name}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</View>
|
|
)}
|
|
|
|
{/* Minimal footer */}
|
|
<View style={styles.footer}>
|
|
<Text style={styles.footerLeft}>
|
|
{labels.sku}: {product.sku}
|
|
</Text>
|
|
<Text style={styles.footerRight}>
|
|
{new Date().toLocaleDateString(locale === 'en' ? 'en-US' : 'de-DE', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
})}
|
|
</Text>
|
|
</View>
|
|
</Page>
|
|
</Document>
|
|
);
|
|
};
|