This commit is contained in:
2026-01-23 13:59:35 +01:00
parent e5e2b646a0
commit d90d7502c3
53 changed files with 41 additions and 27 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -56,18 +56,9 @@ function normalizeExcelKey(value: string): string {
.replace(/[^A-Z0-9]+/g, ''); .replace(/[^A-Z0-9]+/g, '');
} }
function extractDescriptionHtmlFromMdxBody(body: string): string { function extractDescriptionFromMdxFrontmatter(data: any): string {
const content = String(body || '').trim(); const description = normalizeValue(String(data?.description || ''));
if (!content) return ''; return description;
// MDX product files are wrapped like:
// <ProductTabs technicalData={...}>
// <section>...</section>
// </ProductTabs>
// For PDF, we only want the inner description content.
const withoutOpen = content.replace(/^\s*<ProductTabs[\s\S]*?>\s*/i, '');
const withoutClose = withoutOpen.replace(/\s*<\/ProductTabs>\s*$/i, '');
return withoutClose.trim();
} }
function buildMdxIndex(locale: 'en' | 'de'): MdxIndex { function buildMdxIndex(locale: 'en' | 'de'): MdxIndex {
@@ -93,7 +84,7 @@ function buildMdxIndex(locale: 'en' | 'de'): MdxIndex {
const categories = Array.isArray(data.categories) ? data.categories.map((c: any) => normalizeValue(String(c))).filter(Boolean) : []; const categories = Array.isArray(data.categories) ? data.categories.map((c: any) => normalizeValue(String(c))).filter(Boolean) : [];
const images = Array.isArray(data.images) ? data.images.map((i: any) => normalizeValue(String(i))).filter(Boolean) : []; const images = Array.isArray(data.images) ? data.images.map((i: any) => normalizeValue(String(i))).filter(Boolean) : [];
const descriptionHtml = extractDescriptionHtmlFromMdxBody(parsed.content); const descriptionHtml = extractDescriptionFromMdxFrontmatter(data);
const slug = path.basename(file, '.mdx'); const slug = path.basename(file, '.mdx');
idx.set(normalizeExcelKey(title), { slug, title, sku, categories, images, descriptionHtml }); idx.set(normalizeExcelKey(title), { slug, title, sku, categories, images, descriptionHtml });

View File

@@ -639,10 +639,10 @@ function extractAbbrevColumnsFromMediumVoltageHeader(args: {
function buildMediumVoltageCrossSectionTableFromNewExcel(args: { function buildMediumVoltageCrossSectionTableFromNewExcel(args: {
product: ProductData; product: ProductData;
locale: 'en' | 'de'; locale: 'en' | 'de';
}): BuildExcelModelResult { }): BuildExcelModelResult & { legendItems: KeyValueItem[] } {
const mv = findMediumVoltageCrossSectionExcelForProduct(args.product) as MediumVoltageCrossSectionExcelMatch | null; const mv = findMediumVoltageCrossSectionExcelForProduct(args.product) as MediumVoltageCrossSectionExcelMatch | null;
if (!mv || !mv.rows.length) return { ok: false, technicalItems: [], voltageTables: [] }; if (!mv || !mv.rows.length) return { ok: false, technicalItems: [], voltageTables: [], legendItems: [] };
if (!mv.crossSectionKey) return { ok: false, technicalItems: [], voltageTables: [] }; if (!mv.crossSectionKey) return { ok: false, technicalItems: [], voltageTables: [], legendItems: [] };
const abbrevCols = extractAbbrevColumnsFromMediumVoltageHeader({ const abbrevCols = extractAbbrevColumnsFromMediumVoltageHeader({
headerRow: mv.headerRow, headerRow: mv.headerRow,
@@ -651,7 +651,19 @@ function buildMediumVoltageCrossSectionTableFromNewExcel(args: {
crossSectionKey: mv.crossSectionKey, crossSectionKey: mv.crossSectionKey,
ratedVoltageKey: mv.ratedVoltageKey, ratedVoltageKey: mv.ratedVoltageKey,
}); });
if (!abbrevCols.length) return { ok: false, technicalItems: [], voltageTables: [] }; if (!abbrevCols.length) return { ok: false, technicalItems: [], voltageTables: [], legendItems: [] };
// Collect legend items: abbreviation -> description from header row
const legendItems: KeyValueItem[] = [];
for (const col of abbrevCols) {
const description = normalizeValue(String(mv.headerRow[col.colKey] || ''));
if (description && description !== col.colKey) {
legendItems.push({
label: col.colKey,
value: description,
});
}
}
const byVoltage = new Map<string, number[]>(); const byVoltage = new Map<string, number[]>();
for (let i = 0; i < mv.rows.length; i++) { for (let i = 0; i < mv.rows.length; i++) {
@@ -710,7 +722,7 @@ function buildMediumVoltageCrossSectionTableFromNewExcel(args: {
voltageTables.push({ voltageLabel: vKey, metaItems, crossSections, columns }); voltageTables.push({ voltageLabel: vKey, metaItems, crossSections, columns });
} }
return { ok: true, technicalItems: [], voltageTables }; return { ok: true, technicalItems: [], voltageTables, legendItems };
} }
export function buildDatasheetModel(args: { product: ProductData; locale: 'en' | 'de' }): DatasheetModel { export function buildDatasheetModel(args: { product: ProductData; locale: 'en' | 'de' }): DatasheetModel {
@@ -726,7 +738,7 @@ export function buildDatasheetModel(args: { product: ProductData; locale: 'en' |
// Cross-section tables: for medium voltage only, prefer the new MV sheet (abbrev columns in header row). // Cross-section tables: for medium voltage only, prefer the new MV sheet (abbrev columns in header row).
const crossSectionModel = isMediumVoltageProduct(args.product) const crossSectionModel = isMediumVoltageProduct(args.product)
? buildMediumVoltageCrossSectionTableFromNewExcel({ product: args.product, locale: args.locale }) ? buildMediumVoltageCrossSectionTableFromNewExcel({ product: args.product, locale: args.locale })
: { ok: false, technicalItems: [], voltageTables: [] }; : { ok: false, technicalItems: [], voltageTables: [], legendItems: [] };
const voltageTablesSrc = crossSectionModel.ok const voltageTablesSrc = crossSectionModel.ok
? crossSectionModel.voltageTables ? crossSectionModel.voltageTables
@@ -762,5 +774,6 @@ export function buildDatasheetModel(args: { product: ProductData; locale: 'en' |
labels, labels,
technicalItems: excelModel.ok ? excelModel.technicalItems : [], technicalItems: excelModel.ok ? excelModel.technicalItems : [],
voltageTables, voltageTables,
legendItems: crossSectionModel.legendItems || [],
}; };
} }

View File

@@ -47,5 +47,6 @@ export type DatasheetModel = {
}; };
technicalItems: KeyValueItem[]; technicalItems: KeyValueItem[];
voltageTables: DatasheetVoltageTable[]; voltageTables: DatasheetVoltageTable[];
legendItems: KeyValueItem[];
}; };

View File

@@ -62,12 +62,18 @@ export function DatasheetDocument(props: { model: DatasheetModel; assets: Assets
<Footer locale={model.locale} siteUrl={CONFIG.siteUrl} /> <Footer locale={model.locale} siteUrl={CONFIG.siteUrl} />
{model.voltageTables.map((t: DatasheetVoltageTable) => ( {model.voltageTables.map((t: DatasheetVoltageTable) => (
<View key={t.voltageLabel} style={{ marginBottom: 14 }}> <View key={t.voltageLabel} style={{ marginBottom: 14 }} break={false}>
<Text style={styles.sectionTitle}>{`${model.labels.crossSection}${t.voltageLabel}`}</Text> <Text style={styles.sectionTitle}>{`${model.labels.crossSection}${t.voltageLabel}`}</Text>
<DenseTable table={{ columns: t.columns, rows: t.rows }} firstColLabel={firstColLabel} /> <DenseTable table={{ columns: t.columns, rows: t.rows }} firstColLabel={firstColLabel} />
</View> </View>
))} ))}
{model.legendItems.length ? (
<Section title={model.locale === 'de' ? 'ABKÜRZUNGEN' : 'ABBREVIATIONS'}>
<KeyValueGrid items={model.legendItems} />
</Section>
) : null}
</Page> </Page>
</Document> </Document>
); );

View File

@@ -27,16 +27,19 @@ async function readBytesFromPublic(localPath: string): Promise<Uint8Array> {
function transformLogoSvgToPrintBlack(svg: string): string { function transformLogoSvgToPrintBlack(svg: string): string {
return svg return svg
.replace(/fill\s*:\s*white/gi, 'fill:#0E2A47') .replace(/fill\s*:\s*white/gi, 'fill:#000000')
.replace(/fill\s*=\s*"white"/gi, 'fill="#0E2A47"') .replace(/fill\s*=\s*"white"/gi, 'fill="#000000"')
.replace(/fill\s*=\s*'white'/gi, "fill='#0E2A47'"); .replace(/fill\s*=\s*'white'/gi, "fill='#000000'")
.replace(/fill\s*:\s*#[0-9a-fA-F]{6}/gi, 'fill:#000000')
.replace(/fill\s*=\s*"#[0-9a-fA-F]{6}"/gi, 'fill="#000000"')
.replace(/fill\s*=\s*'#[0-9a-fA-F]{6}'/gi, "fill='#000000'");
} }
async function toPngBytes(inputBytes: Uint8Array, inputHint: string): Promise<Uint8Array> { async function toPngBytes(inputBytes: Uint8Array, inputHint: string): Promise<Uint8Array> {
const ext = (path.extname(inputHint).toLowerCase() || '').replace('.', ''); const ext = (path.extname(inputHint).toLowerCase() || '').replace('.', '');
if (ext === 'png') return inputBytes; if (ext === 'png') return inputBytes;
if (ext === 'svg' && /\/media\/logo\.svg$/i.test(inputHint)) { if (ext === 'svg' && (/\/media\/logo\.svg$/i.test(inputHint) || /\/logo-blue\.svg$/i.test(inputHint))) {
const svg = Buffer.from(inputBytes).toString('utf8'); const svg = Buffer.from(inputBytes).toString('utf8');
inputBytes = new Uint8Array(Buffer.from(transformLogoSvgToPrintBlack(svg), 'utf8')); inputBytes = new Uint8Array(Buffer.from(transformLogoSvgToPrintBlack(svg), 'utf8'));
} }

View File

@@ -141,7 +141,7 @@ export function DenseTable(props: {
const headerFontSize = cols.length >= 14 ? 5.7 : cols.length >= 12 ? 5.9 : cols.length >= 10 ? 6.2 : 6.6; const headerFontSize = cols.length >= 14 ? 5.7 : cols.length >= 12 ? 5.9 : cols.length >= 10 ? 6.2 : 6.6;
return ( return (
<View style={styles.tableWrap}> <View style={styles.tableWrap} break={false}>
<View style={styles.tableHeader} wrap={false}> <View style={styles.tableHeader} wrap={false}>
<View style={{ width: cfgW }}> <View style={{ width: cfgW }}>
<Text <Text

View File

@@ -13,8 +13,8 @@ export async function generateDatasheetPdfBuffer(args: {
const model = buildDatasheetModel({ product: args.product, locale: args.locale }); const model = buildDatasheetModel({ product: args.product, locale: args.locale });
const logoDataUrl = const logoDataUrl =
(await loadImageAsPngDataUrl('/media/logo.svg')) || (await loadImageAsPngDataUrl('/logo-blue.svg')) ||
(await loadImageAsPngDataUrl('/media/logo.webp')) || (await loadImageAsPngDataUrl('/logo-white.svg')) ||
null; null;
const heroDataUrl = await loadImageAsPngDataUrl(model.product.heroSrc); const heroDataUrl = await loadImageAsPngDataUrl(model.product.heroSrc);