This commit is contained in:
2026-01-14 18:49:33 +01:00
parent 558bcbd946
commit f29ceacb51
57 changed files with 237 additions and 66 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.

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

@@ -70,6 +70,18 @@ function parseVoltageSortKey(voltageLabel: string): number {
function compactNumericForLocale(value: string, locale: 'en' | 'de'): string { function compactNumericForLocale(value: string, locale: 'en' | 'de'): string {
const v = normalizeValue(value); const v = normalizeValue(value);
if (!v) return ''; if (!v) return '';
// Compact common bending-radius style: "15xD (Single core); 12xD (Multi core)" -> "15/12xD".
// Keep semantics, reduce width. Never truncate with ellipses.
if (/\d+xD/i.test(v)) {
const nums = Array.from(v.matchAll(/(\d+)xD/gi)).map(m => m[1]).filter(Boolean);
const unique: string[] = [];
for (const n of nums) {
if (!unique.includes(n)) unique.push(n);
}
if (unique.length) return `${unique.join('/') }xD`;
}
const hasDigit = /\d/.test(v); const hasDigit = /\d/.test(v);
if (!hasDigit) return v; if (!hasDigit) return v;
const trimmed = v.replace(/\s+/g, ' ').trim(); const trimmed = v.replace(/\s+/g, ' ').trim();
@@ -242,6 +254,12 @@ function denseAbbrevLabel(args: { key: string; locale: 'en' | 'de'; unit?: strin
return args.locale === 'de' ? 'Leiter' : 'Cond.'; return args.locale === 'de' ? 'Leiter' : 'Cond.';
case 'shape': case 'shape':
return args.locale === 'de' ? 'Form' : 'Shape'; return args.locale === 'de' ? 'Form' : 'Shape';
// Electrical
case 'cap':
// Capacitance. Use a clear label; lowercase "cap" looks like an internal key.
return `Cap${suffix}`;
case 'X':
return `X${suffix}`;
case 'test_volt': case 'test_volt':
return `U_test${suffix}`; return `U_test${suffix}`;
case 'rated_volt': case 'rated_volt':
@@ -265,13 +283,14 @@ function denseAbbrevLabel(args: { key: string; locale: 'en' | 'de'; unit?: strin
} }
} }
function summarizeOptions(options: string[] | undefined, maxItems: number = 3): string { function summarizeOptions(options: string[] | undefined): string {
const vals = (options || []).map(normalizeValue).filter(Boolean); const vals = (options || []).map(normalizeValue).filter(Boolean);
if (vals.length === 0) return ''; if (vals.length === 0) return '';
const uniq = Array.from(new Set(vals)); const uniq = Array.from(new Set(vals));
if (uniq.length === 1) return uniq[0]; if (uniq.length === 1) return uniq[0];
if (uniq.length <= maxItems) return uniq.join(' / '); // Never use ellipsis truncation in datasheets. Prefer full value list.
return `${uniq.slice(0, maxItems).join(' / ')} / ...`; // (Long values should be handled by layout; if needed we can later add wrapping rules.)
return uniq.join(' / ');
} }
function parseNumericOption(value: string): number | null { function parseNumericOption(value: string): number | null {
@@ -294,10 +313,17 @@ function summarizeNumericRange(options: string[] | undefined): { ok: boolean; te
return { ok: true, text: `${fmt(min)}${fmt(max)}` }; return { ok: true, text: `${fmt(min)}${fmt(max)}` };
} }
function summarizeSmartOptions(label: string, options: string[] | undefined): string { function summarizeSmartOptions(_label: string, options: string[] | undefined): string {
const range = summarizeNumericRange(options); const range = summarizeNumericRange(options);
if (range.ok) return range.text; if (range.ok) return range.text;
return summarizeOptions(options, 3); return summarizeOptions(options);
}
function normalizeDesignation(value: string): string {
return String(value || '')
.toUpperCase()
.replace(/-\d+$/g, '')
.replace(/[^A-Z0-9]+/g, '');
} }
function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): BuildExcelModelResult { function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): BuildExcelModelResult {
@@ -361,6 +387,14 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
'cpr class': { header: 'CPR class', unit: '', key: 'cpr' }, 'cpr class': { header: 'CPR class', unit: '', key: 'cpr' },
'flame retardant': { header: 'Flame retardant', unit: '', key: 'flame' }, 'flame retardant': { header: 'Flame retardant', unit: '', key: 'flame' },
'self-extinguishing of single cable': { header: 'Flame retardant', unit: '', key: 'flame' }, 'self-extinguishing of single cable': { header: 'Flame retardant', unit: '', key: 'flame' },
// High-value electrical/screen columns
'capacitance (approx.)': { header: 'Capacitance', unit: 'uF/km', key: 'cap' },
'capacitance': { header: 'Capacitance', unit: 'uF/km', key: 'cap' },
'reactance': { header: 'Reactance', unit: 'Ohm/km', key: 'X' },
'diameter over screen': { header: 'Diameter over screen', unit: 'mm', key: 'D_screen' },
'metallic screen mm2': { header: 'Metallic screen', unit: 'mm2', key: 'S_screen' },
'metallic screen': { header: 'Metallic screen', unit: 'mm2', key: 'S_screen' },
}; };
const excelKeys = Object.keys(sample).filter(k => k && k !== 'Part Number' && k !== 'Units'); const excelKeys = Object.keys(sample).filter(k => k && k !== 'Part Number' && k !== 'Units');
@@ -464,6 +498,10 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
const denseTableKeyOrder = [ const denseTableKeyOrder = [
'Cond', 'Cond',
'shape', 'shape',
// Electrical properties (when present)
'cap',
'X',
// Dimensions and ratings
'DI', 'DI',
'RI', 'RI',
'Wi', 'Wi',
@@ -473,6 +511,9 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
'Wm', 'Wm',
'Rbv', 'Rbv',
'Ø', 'Ø',
// Screen data (when present)
'D_screen',
'S_screen',
'Fzv', 'Fzv',
'G', 'G',
] as const; ] as const;
@@ -510,16 +551,33 @@ function buildExcelModel(args: { product: ProductData; locale: 'en' | 'de' }): B
if (!mappedByKey.has(c.mapping.key)) mappedByKey.set(c.mapping.key, c); if (!mappedByKey.has(c.mapping.key)) mappedByKey.set(c.mapping.key, c);
} }
// If conductor material is missing in Excel, derive it from designation.
// NA... => Al, N... => Cu (common for this dataset).
if (!mappedByKey.has('Cond')) {
mappedByKey.set('Cond', {
excelKey: '',
mapping: { header: 'Cond.', unit: '', key: 'Cond' },
});
}
const orderedTableColumns = denseTableKeyOrder const orderedTableColumns = denseTableKeyOrder
.filter(k => mappedByKey.has(k)) .filter(k => mappedByKey.has(k))
.map(k => mappedByKey.get(k)!) .map(k => mappedByKey.get(k)!)
.map(({ excelKey, mapping }) => { .map(({ excelKey, mapping }) => {
const unit = normalizeUnit(units[excelKey] || mapping.unit || ''); const unit = normalizeUnit((excelKey ? units[excelKey] : '') || mapping.unit || '');
return { return {
key: mapping.key, key: mapping.key,
label: denseAbbrevLabel({ key: mapping.key, locale: args.locale, unit }) || formatExcelHeaderLabel(excelKey, unit), label: denseAbbrevLabel({ key: mapping.key, locale: args.locale, unit }) || formatExcelHeaderLabel(excelKey, unit),
get: (rowIndex: number) => { get: (rowIndex: number) => {
const srcRowIndex = indices[rowIndex]; const srcRowIndex = indices[rowIndex];
if (mapping.key === 'Cond' && !excelKey) {
const pn = normalizeDesignation(args.product.name || args.product.slug || args.product.sku || '');
if (/^NA/.test(pn)) return 'Al';
if (/^N/.test(pn)) return 'Cu';
return '';
}
const raw = excelKey ? normalizeValue(String(compatibleRows[srcRowIndex]?.[excelKey] ?? '')) : ''; const raw = excelKey ? normalizeValue(String(compatibleRows[srcRowIndex]?.[excelKey] ?? '')) : '';
return compactCellForDenseTable(raw, unit, args.locale); return compactCellForDenseTable(raw, unit, args.locale);
}, },

View File

@@ -2,6 +2,7 @@ import * as React from 'react';
import { Document, Image, Page, Text, View } from '@react-pdf/renderer'; import { Document, Image, Page, Text, View } from '@react-pdf/renderer';
import type { DatasheetModel, DatasheetVoltageTable } from '../model/types'; import type { DatasheetModel, DatasheetVoltageTable } from '../model/types';
import { CONFIG } from '../model/utils';
import { styles } from './styles'; import { styles } from './styles';
import { Header } from './components/Header'; import { Header } from './components/Header';
import { Footer } from './components/Footer'; import { Footer } from './components/Footer';
@@ -25,9 +26,9 @@ function chunk<T>(arr: T[], size: number): T[][] {
export function DatasheetDocument(props: { model: DatasheetModel; assets: Assets }): React.ReactElement { export function DatasheetDocument(props: { model: DatasheetModel; assets: Assets }): React.ReactElement {
const { model, assets } = props; const { model, assets } = props;
const headerTitle = model.labels.datasheet; const headerTitle = model.labels.datasheet;
const footerLeft = `${model.labels.sku}: ${model.product.sku}`;
const firstColLabel = model.locale === 'de' ? 'Adern & Querschnitt' : 'Cores & cross-section'; // Dense tables require compact headers (no wrapping). Use standard abbreviations.
const firstColLabel = model.locale === 'de' ? 'Adern & QS' : 'Cores & CS';
const tablePages: Array<{ table: DatasheetVoltageTable; rows: DatasheetVoltageTable['rows'] }> = const tablePages: Array<{ table: DatasheetVoltageTable; rows: DatasheetVoltageTable['rows'] }> =
model.voltageTables.flatMap(t => { model.voltageTables.flatMap(t => {
@@ -40,7 +41,7 @@ export function DatasheetDocument(props: { model: DatasheetModel; assets: Assets
<Document> <Document>
<Page size="A4" style={styles.page}> <Page size="A4" style={styles.page}>
<Header title={headerTitle} logoDataUrl={assets.logoDataUrl} qrDataUrl={assets.qrDataUrl} /> <Header title={headerTitle} logoDataUrl={assets.logoDataUrl} qrDataUrl={assets.qrDataUrl} />
<Footer leftText={footerLeft} locale={model.locale} /> <Footer locale={model.locale} siteUrl={CONFIG.siteUrl} />
<Text style={styles.h1}>{model.product.name}</Text> <Text style={styles.h1}>{model.product.name}</Text>
{model.product.categoriesLine ? <Text style={styles.subhead}>{model.product.categoriesLine}</Text> : null} {model.product.categoriesLine ? <Text style={styles.subhead}>{model.product.categoriesLine}</Text> : null}
@@ -66,19 +67,18 @@ export function DatasheetDocument(props: { model: DatasheetModel; assets: Assets
) : null} ) : null}
</Page> </Page>
{tablePages.map((p, index) => ( {tablePages.map((p, index) => (
<Page key={`${p.table.voltageLabel}-${index}`} size="A4" style={styles.page}> <Page key={`${p.table.voltageLabel}-${index}`} size="A4" style={styles.page}>
<Header title={headerTitle} logoDataUrl={assets.logoDataUrl} qrDataUrl={assets.qrDataUrl} /> <Header title={headerTitle} logoDataUrl={assets.logoDataUrl} qrDataUrl={assets.qrDataUrl} />
<Footer leftText={footerLeft} locale={model.locale} /> <Footer locale={model.locale} siteUrl={CONFIG.siteUrl} />
<Section title={`${model.labels.crossSection}${p.table.voltageLabel}`}> <Section title={`${model.labels.crossSection}${p.table.voltageLabel}`}>
{p.table.metaItems.length ? <KeyValueGrid items={p.table.metaItems} /> : null} {p.table.metaItems.length ? <KeyValueGrid items={p.table.metaItems} /> : null}
</Section> </Section>
<DenseTable table={{ columns: p.table.columns, rows: p.rows }} firstColLabel={firstColLabel} /> <DenseTable table={{ columns: p.table.columns, rows: p.rows }} firstColLabel={firstColLabel} />
</Page> </Page>
))} ))}
</Document> </Document>
); );
} }

View File

@@ -15,38 +15,99 @@ export function DenseTable(props: {
const cols = props.table.columns; const cols = props.table.columns;
const rows = props.table.rows; const rows = props.table.rows;
const cfgPct = cols.length >= 12 ? 0.28 : 0.32; const noWrapHeader = (label: string): string => {
const dataPct = 1 - cfgPct; const raw = String(label || '').trim();
const each = cols.length ? dataPct / cols.length : dataPct; if (!raw) return '';
const cfgW = `${Math.round(cfgPct * 100)}%`;
const dataW = `${Math.round(clamp(each, 0.03, 0.12) * 1000) / 10}%`; // Ensure the header never wraps into a second line.
// - Remove whitespace break opportunities (NBSP)
// NOTE: Avoid inserting zero-width joiners between letters.
// Some PDF viewers render them with spacing/odd glyph behavior.
// This is intentionally aggressive because broken headers destroy scanability.
return raw.replace(/\s+/g, '\u00A0');
};
// Column widths: use explicit percentages (no rounding gaps) so the table always
// consumes the full content width.
// Goal:
// - keep the designation column *not too wide*
// - guarantee enough width for data headers when there is available space
const cfgMin = 0.18;
const cfgMax = 0.30;
let cfgPct = cols.length >= 14 ? 0.22 : cols.length >= 12 ? 0.24 : cols.length >= 10 ? 0.26 : 0.30;
cfgPct = clamp(cfgPct, cfgMin, cfgMax);
const minDataPct = cols.length >= 14 ? 0.045 : cols.length >= 12 ? 0.05 : cols.length >= 10 ? 0.055 : 0.06;
// If the initial cfgPct leaves too little width per data column, reduce cfgPct.
const cfgPctMaxForMinData = 1 - cols.length * minDataPct;
if (Number.isFinite(cfgPctMaxForMinData)) {
cfgPct = Math.min(cfgPct, cfgPctMaxForMinData);
cfgPct = clamp(cfgPct, cfgMin, cfgMax);
}
const cfgW = `${(cfgPct * 100).toFixed(4)}%`;
const dataTotal = 1 - cfgPct;
const each = cols.length ? dataTotal / cols.length : dataTotal;
const dataWs = cols.map((_, idx) => {
// Keep the last column as the remainder so percentages sum to exactly 100%.
if (idx === cols.length - 1) {
const used = each * Math.max(0, cols.length - 1);
const remainder = Math.max(0, dataTotal - used);
return `${(remainder * 100).toFixed(4)}%`;
}
return `${(each * 100).toFixed(4)}%`;
});
return ( return (
<View style={styles.tableWrap}> <View style={styles.tableWrap}>
<View style={styles.tableHeader}> <View style={styles.tableHeader} wrap={false}>
<View style={{ width: cfgW }}> <View style={{ width: cfgW }}>
<Text style={styles.tableHeaderCell}>{props.firstColLabel}</Text> <Text
style={[
styles.tableHeaderCell,
styles.tableHeaderCellCfg,
cols.length ? styles.tableHeaderCellDivider : null,
]}
wrap={false}
>
{noWrapHeader(props.firstColLabel)}
</Text>
</View> </View>
{cols.map(c => ( {cols.map((c, idx) => {
<View key={c.key} style={{ width: dataW }}> const isLast = idx === cols.length - 1;
<Text style={styles.tableHeaderCell}>{c.label}</Text> return (
</View> <View key={c.key} style={{ width: dataWs[idx] }}>
))} <Text
style={[styles.tableHeaderCell, !isLast ? styles.tableHeaderCellDivider : null]}
wrap={false}
>
{noWrapHeader(c.label)}
</Text>
</View>
);
})}
</View> </View>
{rows.map((r, ri) => ( {rows.map((r, ri) => (
<View key={`${r.configuration}-${ri}`} style={[styles.tableRow, ri % 2 === 0 ? styles.tableRowAlt : null]}> <View
key={`${r.configuration}-${ri}`}
style={[styles.tableRow, ri % 2 === 0 ? styles.tableRowAlt : null]}
wrap={false}
>
<View style={{ width: cfgW }}> <View style={{ width: cfgW }}>
<Text style={styles.tableCell}>{r.configuration}</Text> <Text style={[styles.tableCell, styles.tableCellCfg, cols.length ? styles.tableCellDivider : null]}>{r.configuration}</Text>
</View> </View>
{r.cells.map((cell, ci) => ( {r.cells.map((cell, ci) => {
<View key={`${cols[ci]?.key || ci}`} style={{ width: dataW }}> const isLast = ci === r.cells.length - 1;
<Text style={styles.tableCell}>{cell}</Text> return (
</View> <View key={`${cols[ci]?.key || ci}`} style={{ width: dataWs[ci] }}>
))} <Text style={[styles.tableCell, !isLast ? styles.tableCellDivider : null]}>{cell}</Text>
</View>
);
})}
</View> </View>
))} ))}
</View> </View>
); );
} }

View File

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

View File

@@ -8,18 +8,36 @@ export function KeyValueGrid(props: { items: KeyValueItem[] }): React.ReactEleme
const items = (props.items || []).filter(i => i.label && i.value); const items = (props.items || []).filter(i => i.label && i.value);
if (!items.length) return null; if (!items.length) return null;
// 4-column layout: (label, value, label, value)
const rows: Array<[KeyValueItem, KeyValueItem | null]> = [];
for (let i = 0; i < items.length; i += 2) {
rows.push([items[i], items[i + 1] || null]);
}
return ( return (
<View style={styles.kvGrid}> <View style={styles.kvGrid}>
{items.map((item, index) => { {rows.map(([left, right], rowIndex) => {
const isLast = index === items.length - 1; const isLast = rowIndex === rows.length - 1;
const valueText = item.unit ? `${item.value} ${item.unit}` : item.value; const leftValue = left.unit ? `${left.value} ${left.unit}` : left.value;
const rightValue = right ? (right.unit ? `${right.value} ${right.unit}` : right.value) : '';
return ( return (
<View key={`${item.label}-${index}`} style={[styles.kvRow, isLast ? styles.kvRowLast : null]}> <View
<View style={styles.kvLabel}> key={`${left.label}-${rowIndex}`}
<Text style={styles.kvLabelText}>{item.label}</Text> style={[styles.kvRow, isLast ? styles.kvRowLast : null]}
wrap={false}
>
<View style={[styles.kvCell, { width: '23%' }]}>
<Text style={styles.kvLabelText}>{left.label}</Text>
</View> </View>
<View style={styles.kvValue}> <View style={[styles.kvCell, styles.kvMidDivider, { width: '27%' }]}>
<Text style={styles.kvValueText}>{valueText}</Text> <Text style={styles.kvValueText}>{leftValue}</Text>
</View>
<View style={[styles.kvCell, { width: '23%' }]}>
<Text style={styles.kvLabelText}>{right?.label || ''}</Text>
</View>
<View style={[styles.kvCell, { width: '27%' }]}>
<Text style={styles.kvValueText}>{rightValue}</Text>
</View> </View>
</View> </View>
); );
@@ -27,4 +45,3 @@ export function KeyValueGrid(props: { items: KeyValueItem[] }): React.ReactEleme
</View> </View>
); );
} }

View File

@@ -3,12 +3,16 @@ import { Text, View } from '@react-pdf/renderer';
import { styles } from '../styles'; import { styles } from '../styles';
export function Section(props: { title: string; children: React.ReactNode }): React.ReactElement { export function Section(props: {
title: string;
children: React.ReactNode;
boxed?: boolean;
}): React.ReactElement {
const boxed = props.boxed ?? true;
return ( return (
<View style={styles.section}> <View style={boxed ? styles.section : styles.sectionPlain}>
<Text style={styles.sectionTitle}>{props.title}</Text> <Text style={styles.sectionTitle}>{props.title}</Text>
{props.children} {props.children}
</View> </View>
); );
} }

View File

@@ -1,4 +1,8 @@
import { StyleSheet } from '@react-pdf/renderer'; import { Font, StyleSheet } from '@react-pdf/renderer';
// Prevent automatic word hyphenation, which can create multi-line table headers
// even when we try to keep them single-line.
Font.registerHyphenationCallback(word => [word]);
export const COLORS = { export const COLORS = {
navy: '#0E2A47', navy: '#0E2A47',
@@ -76,6 +80,10 @@ export const styles = StyleSheet.create({
padding: 14, padding: 14,
marginBottom: 14, marginBottom: 14,
}, },
sectionPlain: {
paddingVertical: 2,
marginBottom: 12,
},
sectionTitle: { sectionTitle: {
fontSize: 10, fontSize: 10,
fontWeight: 700, fontWeight: 700,
@@ -86,34 +94,56 @@ export const styles = StyleSheet.create({
body: { fontSize: 10, lineHeight: 1.5, color: COLORS.darkGray }, body: { fontSize: 10, lineHeight: 1.5, color: COLORS.darkGray },
kvGrid: { kvGrid: {
width: '100%',
borderWidth: 1, borderWidth: 1,
borderColor: COLORS.lightGray, borderColor: COLORS.lightGray,
}, },
kvRow: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: COLORS.lightGray }, kvRow: {
flexDirection: 'row',
borderBottomWidth: 1,
borderBottomColor: COLORS.lightGray,
},
kvRowLast: { borderBottomWidth: 0 }, kvRowLast: { borderBottomWidth: 0 },
kvLabel: { kvCell: { paddingVertical: 6, paddingHorizontal: 8 },
width: '45%', // Visual separator between (label,value) pairs in the 4-col KV grid.
paddingVertical: 6, // Matches the engineering-table look and improves scanability.
paddingHorizontal: 8, kvMidDivider: {
backgroundColor: COLORS.almostWhite,
borderRightWidth: 1, borderRightWidth: 1,
borderRightColor: COLORS.lightGray, borderRightColor: COLORS.lightGray,
}, },
kvValue: { width: '55%', paddingVertical: 6, paddingHorizontal: 8 },
kvLabelText: { fontSize: 8.5, fontWeight: 700, color: COLORS.mediumGray }, kvLabelText: { fontSize: 8.5, fontWeight: 700, color: COLORS.mediumGray },
kvValueText: { fontSize: 9.5, color: COLORS.darkGray }, kvValueText: { fontSize: 9.5, color: COLORS.darkGray },
tableWrap: { borderWidth: 1, borderColor: COLORS.lightGray }, tableWrap: { width: '100%', borderWidth: 1, borderColor: COLORS.lightGray },
tableHeader: { flexDirection: 'row', backgroundColor: '#6B707A' }, tableHeader: {
width: '100%',
flexDirection: 'row',
backgroundColor: '#FFFFFF',
borderBottomWidth: 1,
borderBottomColor: COLORS.lightGray,
},
tableHeaderCell: { tableHeaderCell: {
paddingVertical: 5, paddingVertical: 5,
paddingHorizontal: 4, paddingHorizontal: 4,
fontSize: 6.6, fontSize: 6.6,
fontWeight: 700, fontWeight: 700,
color: '#FFFFFF', color: COLORS.navy,
}, },
tableRow: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: COLORS.lightGray }, tableHeaderCellCfg: {
paddingHorizontal: 6,
},
tableHeaderCellDivider: {
borderRightWidth: 1,
borderRightColor: COLORS.lightGray,
},
tableRow: { width: '100%', flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: COLORS.lightGray },
tableRowAlt: { backgroundColor: COLORS.almostWhite }, tableRowAlt: { backgroundColor: COLORS.almostWhite },
tableCell: { paddingVertical: 4, paddingHorizontal: 4, fontSize: 6.6, color: COLORS.darkGray }, tableCell: { paddingVertical: 4, paddingHorizontal: 4, fontSize: 6.6, color: COLORS.darkGray },
tableCellCfg: {
paddingHorizontal: 6,
},
tableCellDivider: {
borderRightWidth: 1,
borderRightColor: COLORS.lightGray,
},
}); });