This commit is contained in:
2026-01-14 18:02:47 +01:00
parent 2c41f5619b
commit 558bcbd946
68 changed files with 5490 additions and 4032 deletions

View File

@@ -0,0 +1,52 @@
import * as React from 'react';
import { Text, View } from '@react-pdf/renderer';
import type { DatasheetVoltageTable } from '../../model/types';
import { styles } from '../styles';
function clamp(n: number, min: number, max: number): number {
return Math.max(min, Math.min(max, n));
}
export function DenseTable(props: {
table: Pick<DatasheetVoltageTable, 'columns' | 'rows'>;
firstColLabel: string;
}): React.ReactElement {
const cols = props.table.columns;
const rows = props.table.rows;
const cfgPct = cols.length >= 12 ? 0.28 : 0.32;
const dataPct = 1 - cfgPct;
const each = cols.length ? dataPct / cols.length : dataPct;
const cfgW = `${Math.round(cfgPct * 100)}%`;
const dataW = `${Math.round(clamp(each, 0.03, 0.12) * 1000) / 10}%`;
return (
<View style={styles.tableWrap}>
<View style={styles.tableHeader}>
<View style={{ width: cfgW }}>
<Text style={styles.tableHeaderCell}>{props.firstColLabel}</Text>
</View>
{cols.map(c => (
<View key={c.key} style={{ width: dataW }}>
<Text style={styles.tableHeaderCell}>{c.label}</Text>
</View>
))}
</View>
{rows.map((r, ri) => (
<View key={`${r.configuration}-${ri}`} style={[styles.tableRow, ri % 2 === 0 ? styles.tableRowAlt : null]}>
<View style={{ width: cfgW }}>
<Text style={styles.tableCell}>{r.configuration}</Text>
</View>
{r.cells.map((cell, ci) => (
<View key={`${cols[ci]?.key || ci}`} style={{ width: dataW }}>
<Text style={styles.tableCell}>{cell}</Text>
</View>
))}
</View>
))}
</View>
);
}

View File

@@ -0,0 +1,21 @@
import * as React from 'react';
import { Text, View } from '@react-pdf/renderer';
import { styles } from '../styles';
export function Footer(props: { leftText: string; locale: 'en' | 'de' }): React.ReactElement {
const date = new Date().toLocaleDateString(props.locale === 'en' ? 'en-US' : 'de-DE', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
return (
<View style={styles.footer} fixed>
<Text>{props.leftText}</Text>
<Text>{date}</Text>
<Text render={({ pageNumber, totalPages }) => `${pageNumber}/${totalPages}`} />
</View>
);
}

View File

@@ -0,0 +1,26 @@
import * as React from 'react';
import { Image, Text, View } from '@react-pdf/renderer';
import { styles } from '../styles';
export function Header(props: { title: string; logoDataUrl?: string | null; qrDataUrl?: string | null }): React.ReactElement {
return (
<View style={styles.header} fixed>
<View style={styles.headerLeft}>
{props.logoDataUrl ? (
<Image src={props.logoDataUrl} style={styles.logo} />
) : (
<View style={styles.brandFallback}>
<Text style={styles.brandFallbackKlz}>KLZ</Text>
<Text style={styles.brandFallbackCables}>Cables</Text>
</View>
)}
</View>
<View style={styles.headerRight}>
<Text style={styles.headerTitle}>{props.title}</Text>
{props.qrDataUrl ? <Image src={props.qrDataUrl} style={styles.qr} /> : null}
</View>
</View>
);
}

View File

@@ -0,0 +1,30 @@
import * as React from 'react';
import { Text, View } from '@react-pdf/renderer';
import type { KeyValueItem } from '../../model/types';
import { styles } from '../styles';
export function KeyValueGrid(props: { items: KeyValueItem[] }): React.ReactElement | null {
const items = (props.items || []).filter(i => i.label && i.value);
if (!items.length) return null;
return (
<View style={styles.kvGrid}>
{items.map((item, index) => {
const isLast = index === items.length - 1;
const valueText = item.unit ? `${item.value} ${item.unit}` : item.value;
return (
<View key={`${item.label}-${index}`} style={[styles.kvRow, isLast ? styles.kvRowLast : null]}>
<View style={styles.kvLabel}>
<Text style={styles.kvLabelText}>{item.label}</Text>
</View>
<View style={styles.kvValue}>
<Text style={styles.kvValueText}>{valueText}</Text>
</View>
</View>
);
})}
</View>
);
}

View File

@@ -0,0 +1,14 @@
import * as React from 'react';
import { Text, View } from '@react-pdf/renderer';
import { styles } from '../styles';
export function Section(props: { title: string; children: React.ReactNode }): React.ReactElement {
return (
<View style={styles.section}>
<Text style={styles.sectionTitle}>{props.title}</Text>
{props.children}
</View>
);
}