wip
This commit is contained in:
437
lib/pdf-datasheet.tsx
Normal file
437
lib/pdf-datasheet.tsx
Normal file
@@ -0,0 +1,437 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user