Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Successful in 56s
Build & Deploy / 🏗️ Build (push) Successful in 2m29s
Build & Deploy / 🚀 Deploy (push) Failing after 15s
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
403 lines
9.6 KiB
TypeScript
403 lines
9.6 KiB
TypeScript
import * as React from 'react';
|
|
import { Document, Page, View, Text, Image, StyleSheet, Font } from '@react-pdf/renderer';
|
|
|
|
// Standard fonts like Helvetica are built-in to PDF and don't require registration
|
|
// unless we want to use specific TTF files. Using built-in Helvetica for maximum stability.
|
|
|
|
// Industrial/technical/restrained design - STYLEGUIDE.md compliant
|
|
const styles = StyleSheet.create({
|
|
page: {
|
|
color: '#111827', // Text Primary
|
|
lineHeight: 1.5,
|
|
backgroundColor: '#FFFFFF',
|
|
paddingTop: 0,
|
|
paddingBottom: 100,
|
|
fontFamily: 'Helvetica',
|
|
},
|
|
|
|
// Hero-style header
|
|
hero: {
|
|
backgroundColor: '#FFFFFF',
|
|
paddingTop: 24,
|
|
paddingBottom: 0,
|
|
paddingHorizontal: 72,
|
|
marginBottom: 20,
|
|
position: 'relative',
|
|
borderBottomWidth: 0,
|
|
borderBottomColor: '#e5e7eb',
|
|
},
|
|
|
|
header: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 16,
|
|
},
|
|
|
|
logoText: {
|
|
fontSize: 24,
|
|
fontWeight: 700,
|
|
color: '#000d26',
|
|
letterSpacing: 1,
|
|
textTransform: 'uppercase',
|
|
},
|
|
|
|
docTitle: {
|
|
fontSize: 10,
|
|
fontWeight: 700,
|
|
color: '#001a4d',
|
|
letterSpacing: 2,
|
|
textTransform: 'uppercase',
|
|
},
|
|
|
|
productRow: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 20,
|
|
},
|
|
productInfoCol: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
},
|
|
productImageCol: {
|
|
flex: 1,
|
|
height: 120,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
borderRadius: 8,
|
|
borderWidth: 1,
|
|
borderColor: '#e5e7eb',
|
|
backgroundColor: '#FFFFFF',
|
|
overflow: 'hidden',
|
|
},
|
|
|
|
// Product Hero Info
|
|
productHero: {
|
|
marginTop: 0,
|
|
},
|
|
|
|
productName: {
|
|
fontSize: 24,
|
|
fontWeight: 700,
|
|
color: '#000d26',
|
|
marginBottom: 0,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: -0.5,
|
|
},
|
|
|
|
productMeta: {
|
|
fontSize: 10,
|
|
color: '#4b5563',
|
|
fontWeight: 700,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 1,
|
|
},
|
|
|
|
heroImage: {
|
|
width: '100%',
|
|
height: '100%',
|
|
objectFit: 'contain',
|
|
},
|
|
|
|
noImage: {
|
|
fontSize: 8,
|
|
color: '#9ca3af',
|
|
textAlign: 'center',
|
|
},
|
|
|
|
// Content Area
|
|
content: {
|
|
paddingHorizontal: 72,
|
|
},
|
|
|
|
// Content sections
|
|
section: {
|
|
marginBottom: 20,
|
|
},
|
|
|
|
sectionTitle: {
|
|
fontSize: 14,
|
|
fontWeight: 700,
|
|
color: '#000d26', // Primary Dark
|
|
marginBottom: 8,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: -0.2,
|
|
},
|
|
|
|
sectionAccent: {
|
|
width: 30,
|
|
height: 3,
|
|
backgroundColor: '#82ed20', // Accent Green
|
|
marginBottom: 8,
|
|
borderRadius: 1.5,
|
|
},
|
|
|
|
description: {
|
|
fontSize: 11,
|
|
lineHeight: 1.7,
|
|
color: '#4b5563', // Text Secondary
|
|
},
|
|
|
|
// Technical data table
|
|
specsTable: {
|
|
marginTop: 8,
|
|
border: '1px solid #e5e7eb',
|
|
borderRadius: 8,
|
|
overflow: 'hidden',
|
|
},
|
|
|
|
specsTableRow: {
|
|
flexDirection: 'row',
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: '#e5e7eb',
|
|
},
|
|
|
|
specsTableRowLast: {
|
|
borderBottomWidth: 0,
|
|
},
|
|
|
|
specsTableLabelCell: {
|
|
flex: 1,
|
|
paddingVertical: 4,
|
|
paddingHorizontal: 16,
|
|
backgroundColor: '#f8f9fa',
|
|
borderRightWidth: 1,
|
|
borderRightColor: '#e5e7eb',
|
|
},
|
|
|
|
specsTableValueCell: {
|
|
flex: 1,
|
|
paddingVertical: 4,
|
|
paddingHorizontal: 16,
|
|
},
|
|
|
|
specsTableLabelText: {
|
|
fontSize: 9,
|
|
fontWeight: 700,
|
|
color: '#000d26',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.5,
|
|
},
|
|
|
|
specsTableValueText: {
|
|
fontSize: 10,
|
|
color: '#111827',
|
|
fontWeight: 500,
|
|
},
|
|
|
|
// Categories
|
|
categories: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 8,
|
|
},
|
|
|
|
categoryTag: {
|
|
backgroundColor: '#f8f9fa',
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 6,
|
|
border: '1px solid #e5e7eb',
|
|
borderRadius: 100,
|
|
},
|
|
|
|
categoryText: {
|
|
fontSize: 8,
|
|
color: '#4b5563',
|
|
fontWeight: 700,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 0.5,
|
|
},
|
|
|
|
// Footer
|
|
footer: {
|
|
position: 'absolute',
|
|
bottom: 40,
|
|
left: 72,
|
|
right: 72,
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
paddingTop: 24,
|
|
borderTop: '1px solid #e5e7eb',
|
|
},
|
|
|
|
footerText: {
|
|
fontSize: 8,
|
|
color: '#9ca3af',
|
|
fontWeight: 500,
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 1,
|
|
},
|
|
|
|
footerBrand: {
|
|
fontSize: 10,
|
|
fontWeight: 700,
|
|
color: '#000d26',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 1,
|
|
},
|
|
});
|
|
|
|
interface ProductData {
|
|
id: number;
|
|
name: string;
|
|
shortDescriptionHtml: string;
|
|
descriptionHtml: string;
|
|
applicationHtml?: 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: 'Technical Datasheet',
|
|
description: 'APPLICATION',
|
|
specifications: 'TECHNICAL DATA',
|
|
categories: 'CATEGORIES',
|
|
sku: 'SKU',
|
|
noImage: 'No image available',
|
|
},
|
|
de: {
|
|
productDatasheet: 'Technisches Datenblatt',
|
|
description: 'ANWENDUNG',
|
|
specifications: 'TECHNISCHE DATEN',
|
|
categories: 'KATEGORIEN',
|
|
sku: 'ARTIKELNUMMER',
|
|
noImage: 'Kein Bild verfügbar',
|
|
},
|
|
};
|
|
return labels[locale];
|
|
};
|
|
|
|
export const PDFDatasheet: React.FC<PDFDatasheetProps> = ({ product, locale }) => {
|
|
const labels = getLabels(locale);
|
|
|
|
return (
|
|
<Document>
|
|
<Page size="A4" style={styles.page}>
|
|
{/* Hero Header */}
|
|
<View style={styles.hero}>
|
|
<View style={styles.header}>
|
|
<View>
|
|
<Text style={styles.logoText}>KLZ</Text>
|
|
</View>
|
|
<Text style={styles.docTitle}>{labels.productDatasheet}</Text>
|
|
</View>
|
|
|
|
<View style={styles.productRow}>
|
|
<View style={styles.productInfoCol}>
|
|
<View style={styles.productHero}>
|
|
<View style={styles.categories}>
|
|
{product.categories.map((cat, index) => (
|
|
<Text key={index} style={styles.productMeta}>
|
|
{cat.name}
|
|
{index < product.categories.length - 1 ? ' • ' : ''}
|
|
</Text>
|
|
))}
|
|
</View>
|
|
<Text style={styles.productName}>{product.name}</Text>
|
|
</View>
|
|
</View>
|
|
<View style={styles.productImageCol}>
|
|
{product.featuredImage ? (
|
|
<Image src={product.featuredImage} style={styles.heroImage} />
|
|
) : (
|
|
<Text style={styles.noImage}>{labels.noImage}</Text>
|
|
)}
|
|
</View>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.content}>
|
|
{/* Description section */}
|
|
{(product.applicationHtml || product.shortDescriptionHtml || product.descriptionHtml) && (
|
|
<View style={styles.section}>
|
|
<Text style={styles.sectionTitle}>{labels.description}</Text>
|
|
<View style={styles.sectionAccent} />
|
|
<Text style={styles.description}>
|
|
{stripHtml(
|
|
product.applicationHtml ||
|
|
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.sectionAccent} />
|
|
<View style={styles.specsTable}>
|
|
{product.attributes.map((attr, index) => (
|
|
<View
|
|
key={index}
|
|
style={[
|
|
styles.specsTableRow,
|
|
index === product.attributes.length - 1 && styles.specsTableRowLast,
|
|
]}
|
|
>
|
|
<View style={styles.specsTableLabelCell}>
|
|
<Text style={styles.specsTableLabelText}>{attr.name}</Text>
|
|
</View>
|
|
<View style={styles.specsTableValueCell}>
|
|
<Text style={styles.specsTableValueText}>{attr.options.join(', ')}</Text>
|
|
</View>
|
|
</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.sectionAccent} />
|
|
<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>
|
|
)}
|
|
</View>
|
|
|
|
{/* Minimal footer */}
|
|
<View style={styles.footer} fixed>
|
|
<Text style={styles.footerBrand}>KLZ CABLES</Text>
|
|
<Text style={styles.footerText}>
|
|
{new Date().toLocaleDateString(locale === 'en' ? 'en-US' : 'de-DE', {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
})}
|
|
</Text>
|
|
</View>
|
|
</Page>
|
|
</Document>
|
|
);
|
|
};
|