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

View File

@@ -56,18 +56,9 @@ function normalizeExcelKey(value: string): string {
.replace(/[^A-Z0-9]+/g, '');
}
function extractDescriptionHtmlFromMdxBody(body: string): string {
const content = String(body || '').trim();
if (!content) return '';
// 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 extractDescriptionFromMdxFrontmatter(data: any): string {
const description = normalizeValue(String(data?.description || ''));
return description;
}
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 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');
idx.set(normalizeExcelKey(title), { slug, title, sku, categories, images, descriptionHtml });

View File

@@ -639,10 +639,10 @@ function extractAbbrevColumnsFromMediumVoltageHeader(args: {
function buildMediumVoltageCrossSectionTableFromNewExcel(args: {
product: ProductData;
locale: 'en' | 'de';
}): BuildExcelModelResult {
}): BuildExcelModelResult & { legendItems: KeyValueItem[] } {
const mv = findMediumVoltageCrossSectionExcelForProduct(args.product) as MediumVoltageCrossSectionExcelMatch | null;
if (!mv || !mv.rows.length) return { ok: false, technicalItems: [], voltageTables: [] };
if (!mv.crossSectionKey) return { ok: false, technicalItems: [], voltageTables: [] };
if (!mv || !mv.rows.length) return { ok: false, technicalItems: [], voltageTables: [], legendItems: [] };
if (!mv.crossSectionKey) return { ok: false, technicalItems: [], voltageTables: [], legendItems: [] };
const abbrevCols = extractAbbrevColumnsFromMediumVoltageHeader({
headerRow: mv.headerRow,
@@ -651,7 +651,19 @@ function buildMediumVoltageCrossSectionTableFromNewExcel(args: {
crossSectionKey: mv.crossSectionKey,
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[]>();
for (let i = 0; i < mv.rows.length; i++) {
@@ -710,7 +722,7 @@ function buildMediumVoltageCrossSectionTableFromNewExcel(args: {
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 {
@@ -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).
const crossSectionModel = isMediumVoltageProduct(args.product)
? buildMediumVoltageCrossSectionTableFromNewExcel({ product: args.product, locale: args.locale })
: { ok: false, technicalItems: [], voltageTables: [] };
: { ok: false, technicalItems: [], voltageTables: [], legendItems: [] };
const voltageTablesSrc = crossSectionModel.ok
? crossSectionModel.voltageTables
@@ -762,5 +774,6 @@ export function buildDatasheetModel(args: { product: ProductData; locale: 'en' |
labels,
technicalItems: excelModel.ok ? excelModel.technicalItems : [],
voltageTables,
legendItems: crossSectionModel.legendItems || [],
};
}

View File

@@ -47,5 +47,6 @@ export type DatasheetModel = {
};
technicalItems: KeyValueItem[];
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} />
{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>
<DenseTable table={{ columns: t.columns, rows: t.rows }} firstColLabel={firstColLabel} />
</View>
))}
{model.legendItems.length ? (
<Section title={model.locale === 'de' ? 'ABKÜRZUNGEN' : 'ABBREVIATIONS'}>
<KeyValueGrid items={model.legendItems} />
</Section>
) : null}
</Page>
</Document>
);

View File

@@ -27,16 +27,19 @@ async function readBytesFromPublic(localPath: string): Promise<Uint8Array> {
function transformLogoSvgToPrintBlack(svg: string): string {
return svg
.replace(/fill\s*:\s*white/gi, 'fill:#0E2A47')
.replace(/fill\s*=\s*"white"/gi, 'fill="#0E2A47"')
.replace(/fill\s*=\s*'white'/gi, "fill='#0E2A47'");
.replace(/fill\s*:\s*white/gi, 'fill:#000000')
.replace(/fill\s*=\s*"white"/gi, 'fill="#000000"')
.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> {
const ext = (path.extname(inputHint).toLowerCase() || '').replace('.', '');
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');
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;
return (
<View style={styles.tableWrap}>
<View style={styles.tableWrap} break={false}>
<View style={styles.tableHeader} wrap={false}>
<View style={{ width: cfgW }}>
<Text

View File

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