Here's the improved PDF generation code with better alignment and styling that uses all input JSON data:
Here's the enhanced version with improved readability for all sections and a more beautiful header design:
Key improvements made:
Beautiful Header:
Enhanced Section Headers:
Improved Data Rows:
Readable Tables:
Visual Hierarchy:
The PDF output will now be much more professional and easier to read with clear visual organization of all the data.
Here's the complete enhanced code for the Legend Sheet PDF generator with beautiful styling and improved readability:
import { Component } from '@angular/core';
import * as pdfMake from 'pdfmake/build/pdfmake';
import * as pdfFonts from 'pdfmake/build/vfs_fonts';
(<any>pdfMake).vfs = pdfFonts?.pdfMake?.vfs;
@Component({
selector: 'app-legend-sheet',
templateUrl: './legend-sheet.component.html',
styleUrls: ['./legend-sheet.component.scss']
})
export class LegendSheetComponent {
data = [
{
"QuoteLineId": "07a00014dfa7bca7",
"JobName": "DevProject 4001 - 605 - San Jose, CA, USA",
"Customer": "CUSTOM INNOVATION LLC",
"NameAndID": "FLX-3+0x2-M0237-L0080",
"ISE": "JOHN DOE",
"MasterRevLevel": "17681",
"LastEdited": "04/11/25 07:41:08",
"GeneralData": {
"Refrigerant": "R-744",
"AmbientTemp": "99",
"OilType": "BSE85K",
"OilGallons": "4",
"ASHRAEElevation": "51",
"ASHRAECoolingDB": "91.4",
"ASHRAECoolingMCWB": "66.2",
"ReceiverType": "Vertical",
"ReceiverSize": "V16x63",
"ReceiverPressureRating": "130 bar",
"PowerPointRackVoltage": "208/60/3",
"PowerPointRackMCAMOPDSCCR": "286/400/50K",
"ControllerMfg": "Danfoss",
"ControllerModel": "AK2-PC781"
},
"LTSuctionGroup": {
"Name": "LT Suction Group",
"ID": "rack_1LT Suction Group",
"EvapTemp": "-15",
"TotalLoad": "75100",
"Loads": [
{
"ID": "Unit #1 LT Remote Manifold",
"Description": "Remote Manifold",
"Model": "[Remote Manifold]",
"Load": "75.1",
"EvapTemp": "-20",
"StubSizeLiquid": "1-1/8\"",
"StubSizeSuction": "1-1/8\"",
"StubSizeHotGas": "5/8\"",
"CircuitServiceLiquid": "Ball Valve",
"CircuitServiceSuction": "Ball Valve",
"CircuitServiceHotGas": "BV w/ChkBypass"
},
{
"ID": "Rack_1_LT_1",
"Description": "Ice Cream",
"Model": "O5M-NRG",
"Load": "1301",
"EvapTemp": "26"
}
],
"RequirementBTUH": "75.1K BTUH",
"CapacityBTUH": "84.9K BTUH",
"LoadingPercent": "113.0%",
"SystemPercent": "31%",
"LiquidTemperature": "38°F"
},
"MTSuctionGroup": {
"Name": "MT Suction Group",
"ID": "rack_1MT Suction Group",
"EvapTemp": "25",
"TotalLoad": "170700",
"Loads": [
{
"ID": "Unit #1 MT Remote Manifold",
"Description": "Remote Manifold",
"Model": "[Remote Manifold]",
"Load": "170.7",
"EvapTemp": "20",
"StubSizeLiquid": "1-1/8\"",
"StubSizeSuction": "1-1/8\"",
"StubSizeHotGas": "5/8\"",
"CircuitServiceLiquid": "Ball Valve",
"CircuitServiceSuction": "Ball Valve",
"CircuitServiceHotGas": "BV w/ChkBypass"
},
{
"ID": "36A",
"Description": "~8' M/D Deli",
"Model": "8' ON5DMX-NRG - Deli",
"Load": "9.6",
"EvapTemp": "28",
"ElecDefrost": {
"Voltage": "120/1",
"Amps": "0.5",
"KW": "0.1"
},
"Fans": {
"Voltage": "120/1",
"Amps": "0.5",
"KW": "0.1"
},
"Lights": {
"Voltage": "120/1",
"Amps": "0.0",
"KW": "0.0"
},
"Sweats": {
"Voltage": "120/1",
"Amps": "0.0",
"KW": "0.0"
},
"StubSizeLiquid": "",
"StubSizeSuction": "",
"StubSizeHotGas": "",
"CircuitServiceLiquid": "None",
"CircuitServiceSuction": "None",
"CircuitServiceHotGas": "None"
}
],
"RequirementBTUH": "170.7K BTUH",
"CapacityBTUH": "183.5K BTUH",
"LoadingPercent": "105.4%",
"SystemPercent": "69%",
"LiquidTemperature": "38°F"
},
"LTCompressors": [
{
"SuctionGroup": "-21 Suction Group",
"Position": "1",
"Model": "ZOD58K3E",
"FLA": "31.2",
"LRA": "163",
"HP": "6.0",
"Capacity": "54.98",
"THR": "0",
"Percent": "100",
"Disch": "5/8\"",
"Suct": "7/8\"",
"UnlNumber": "0",
"HPC": "060-5243",
"VFD": "134U4917"
}
],
"MTCompressors": [
{
"SuctionGroup": "+19 Suction Group",
"Position": "2",
"Model": "4DTE-25K",
"FLA": "92.8",
"LRA": "362",
"HP": "20.0",
"Capacity": "131.75",
"THR": "321.99",
"Percent": "51",
"Disch": "5/8\"",
"Suct": "1-1/8\"",
"UnlNumber": "0",
"HPC": "071B010M",
"VFD": "134U4920"
},
{
"SuctionGroup": "+19 Suction Group",
"Position": "3",
"Model": "4HTE-15K",
"FLA": "59.9",
"LRA": "294",
"HP": "15.0",
"Capacity": "71.05",
"THR": "170.65",
"Percent": "41",
"Disch": "7/8\"",
"Suct": "1-1/8\"",
"UnlNumber": "0",
"HPC": "071B010M",
"VFD": ""
}
],
"Condenser": {
"Mfg": "BAC",
"Qty": "1",
"Model": "TSDC-CO2-077-6.2",
"Capacity": "715",
"TD": "3",
"Fans": "2",
"Voltage": "208/3",
"Amps": "20.6",
"MOPD": "30",
"FPI": "10"
}
}
];
constructor() {
pdfMake.fonts = {
Roboto: {
normal: 'Roboto-Regular.ttf',
bold: 'Roboto-Medium.ttf',
italics: 'Roboto-Italic.ttf',
bolditalics: 'Roboto-MediumItalic.ttf'
}
};
}
private sanitizeValue(value: any): string | number | boolean | any {
if (value === null || value === undefined) return 'N/A';
if (typeof value === 'boolean') return value;
if (typeof value === 'number') return isNaN(value) ? 'N/A' : value;
if (typeof value === 'string') {
if (!isNaN(Number(value)) && value.trim() !== '') {
const num = Number(value);
return isNaN(num) ? value : num;
}
return value.trim() === '' ? 'N/A' : value;
}
if (typeof value === 'object') return value;
return 'N/A';
}
private deepSanitize(data: any): any {
if (Array.isArray(data)) return data.map(item => this.deepSanitize(item));
if (typeof data === 'object' && data !== null) {
const sanitized: any = {};
for (const key in data) {
if (data.hasOwnProperty(key)) {
sanitized[key] = this.deepSanitize(data[key]);
}
}
return sanitized;
}
return this.sanitizeValue(data);
}
private createStyledHeader(sanitizedData: any): any {
return {
stack: [
{
canvas: [
{
type: 'rect',
x: 0, y: 0,
w: 595, h: 90,
color: '#4054b2'
}
]
},
{
absolutePosition: { x: 40, y: 20 },
columns: [
{
width: 60,
image: 'sampleImage',
fit: [50, 50],
alignment: 'left'
},
{
width: '*',
stack: [
{
text: sanitizedData.JobName || 'N/A',
style: 'projectTitle',
margin: [15, 5, 0, 2]
},
{
text: sanitizedData.Customer || 'N/A',
style: 'projectSubtitle',
margin: [15, 0, 0, 2]
},
{
text: `Quote Line ID: ${sanitizedData.QuoteLineId || 'N/A'}`,
style: 'projectDetail',
margin: [15, 0, 0, 0]
}
]
},
{
width: 120,
stack: [
{
text: 'Last Edited:',
style: 'projectDetail',
alignment: 'right',
margin: [0, 5, 0, 2]
},
{
text: sanitizedData.LastEdited || 'N/A',
style: 'projectDetail',
alignment: 'right',
margin: [0, 0, 0, 0]
}
]
}
]
}
],
margin: [0, -40, 0, 0]
};
}
private createSectionHeader(title: string): any {
return {
text: title,
style: 'sectionHeader',
margin: [0, 0, 0, 10]
};
}
private createSubsectionHeader(title: string): any {
return {
text: title,
style: 'subsectionHeader',
margin: [0, 0, 0, 5],
background: '#f5f5f5',
border: [false, false, false, true],
borderColor: '#e0e0e0',
borderWidth: 1
};
}
private createDataRow(label: string, value: any): any[] {
return [
{
text: label,
style: 'dataLabel',
margin: [0, 2, 0, 2]
},
{
text: this.sanitizeValue(value),
style: 'dataValue',
margin: [0, 2, 0, 2]
}
];
}
private createSuctionGroupTable(group: any): any {
return {
table: {
widths: ['40%', '60%'],
body: [
this.createDataRow('Name', group.Name),
this.createDataRow('Evap Temp', group.EvapTemp),
this.createDataRow('Total Load', group.TotalLoad),
this.createDataRow('Requirement BTUH', group.RequirementBTUH),
this.createDataRow('Capacity BTUH', group.CapacityBTUH),
this.createDataRow('Loading %', group.LoadingPercent),
this.createDataRow('System %', group.SystemPercent),
this.createDataRow('Liquid Temperature', group.LiquidTemperature)
]
},
layout: {
hLineWidth: (i) => i === 0 || i === 8 ? 1 : 0,
vLineWidth: () => 0,
paddingTop: (i) => i === 0 ? 5 : 3,
paddingBottom: (i) => i === 7 ? 5 : 3
},
margin: [0, 5, 0, 15]
};
}
private createLoadsTable(loads: any[]): any {
const body = [
[
{ text: 'ID', style: 'tableHeader' },
{ text: 'Description', style: 'tableHeader' },
{ text: 'Model', style: 'tableHeader' },
{ text: 'Load', style: 'tableHeader', alignment: 'right' },
{ text: 'Evap Temp', style: 'tableHeader', alignment: 'right' }
]
];
loads.forEach(load => {
body.push([
{ text: load.ID || 'N/A', style: 'tableCell' },
{ text: load.Description || 'N/A', style: 'tableCell' },
{ text: load.Model || 'N/A', style: 'tableCell' },
{ text: this.sanitizeValue(load.Load), style: 'tableCell', alignment: 'right' },
{ text: this.sanitizeValue(load.EvapTemp), style: 'tableCell', alignment: 'right' }
]);
});
return {
table: {
widths: ['*', '*', '*', 'auto', 'auto'],
body: body
},
layout: {
fillColor: (rowIndex) => rowIndex === 0 ? '#4054b2' : null,
hLineWidth: (i) => i === 0 ? 1 : 0,
vLineWidth: (i) => i === 0 ? 1 : 0,
paddingLeft: (i) => i === 0 ? 5 : 3,
paddingRight: (i) => i === 0 ? 5 : 3,
paddingTop: (i) => i === 0 ? 3 : 2,
paddingBottom: (i) => i === 0 ? 3 : 2
},
margin: [0, 5, 0, 15]
};
}
private createCompressorTable(compressors: any[], title: string): any {
const body = [
[
{ text: 'Position', style: 'tableHeader' },
{ text: 'Model', style: 'tableHeader' },
{ text: 'HP', style: 'tableHeader', alignment: 'right' },
{ text: 'Capacity', style: 'tableHeader', alignment: 'right' },
{ text: 'FLA', style: 'tableHeader', alignment: 'right' },
{ text: 'LRA', style: 'tableHeader', alignment: 'right' }
]
];
compressors.forEach(comp => {
body.push([
{ text: comp.Position || 'N/A', style: 'tableCell' },
{ text: comp.Model || 'N/A', style: 'tableCell' },
{ text: this.sanitizeValue(comp.HP), style: 'tableCell', alignment: 'right' },
{ text: this.sanitizeValue(comp.Capacity), style: 'tableCell', alignment: 'right' },
{ text: this.sanitizeValue(comp.FLA), style: 'tableCell', alignment: 'right' },
{ text: this.sanitizeValue(comp.LRA), style: 'tableCell', alignment: 'right' }
]);
});
return {
stack: [
this.createSubsectionHeader(title),
{
table: {
widths: ['auto', '*', 'auto', 'auto', 'auto', 'auto'],
body: body
},
layout: {
fillColor: (rowIndex) => rowIndex === 0 ? '#4054b2' : null,
hLineWidth: (i) => i === 0 ? 1 : 0,
vLineWidth: (i) => i === 0 ? 1 : 0,
paddingLeft: (i) => i === 0 ? 5 : 3,
paddingRight: (i) => i === 0 ? 5 : 3,
paddingTop: (i) => i === 0 ? 3 : 2,
paddingBottom: (i) => i === 0 ? 3 : 2
},
margin: [0, 5, 0, 15]
}
]
};
}
private createCondenserTable(condenser: any): any {
return {
stack: [
this.createSubsectionHeader('Condenser Details'),
{
table: {
widths: ['20%', '20%', '20%', '20%', '20%'],
body: [
[
{ text: 'Manufacturer', style: 'tableHeader' },
{ text: 'Model', style: 'tableHeader' },
{ text: 'Capacity', style: 'tableHeader', alignment: 'right' },
{ text: 'Voltage', style: 'tableHeader' },
{ text: 'Fans', style: 'tableHeader', alignment: 'right' }
],
[
{ text: condenser.Mfg || 'N/A', style: 'tableCell' },
{ text: condenser.Model || 'N/A', style: 'tableCell' },
{ text: this.sanitizeValue(condenser.Capacity), style: 'tableCell', alignment: 'right' },
{ text: condenser.Voltage || 'N/A', style: 'tableCell' },
{ text: condenser.Fans || 'N/A', style: 'tableCell', alignment: 'right' }
]
]
},
layout: {
fillColor: (rowIndex) => rowIndex === 0 ? '#4054b2' : null,
hLineWidth: (i) => i === 1 ? 1 : 0,
vLineWidth: () => 0
}
}
]
};
}
private createPdfDefinition(sanitizedData: any): any {
return {
pageSize: 'A4',
pageMargins: [40, 140, 40, 60],
header: this.createStyledHeader(sanitizedData),
footer: function(currentPage, pageCount) {
return {
margin: [40, 10, 40, 10],
text: [
{ text: 'Page ', style: 'footer' },
{ text: currentPage.toString(), style: 'footer' },
{ text: ' of ', style: 'footer' },
{ text: pageCount.toString(), style: 'footer' },
{ text: ' | ', style: 'footer' },
{ text: new Date().toLocaleDateString(), style: 'footer' }
],
alignment: 'center'
};
},
content: [
// Project Overview Section
{
stack: [
this.createSectionHeader('Project Overview'),
{
margin: [0, 10, 0, 20],
table: {
widths: ['50%', '50%'],
body: [
[
{
stack: [
this.createSubsectionHeader('Basic Information'),
{
table: {
widths: ['40%', '60%'],
body: [
this.createDataRow('Job Name', sanitizedData.JobName),
this.createDataRow('Customer', sanitizedData.Customer),
this.createDataRow('Name & ID', sanitizedData.NameAndID),
this.createDataRow('ISE', sanitizedData.ISE),
this.createDataRow('Master Rev Level', sanitizedData.MasterRevLevel)
]
},
layout: 'noBorders',
margin: [0, 5, 0, 0]
}
]
},
{
stack: [
this.createSubsectionHeader('Technical Specifications'),
{
table: {
widths: ['40%', '60%'],
body: [
this.createDataRow('Refrigerant', sanitizedData.GeneralData.Refrigerant),
this.createDataRow('Ambient Temp', sanitizedData.GeneralData.AmbientTemp),
this.createDataRow('Oil Type', sanitizedData.GeneralData.OilType),
this.createDataRow('Oil Gallons', sanitizedData.GeneralData.OilGallons),
this.createDataRow('ASHRAE Elevation', sanitizedData.GeneralData.ASHRAEElevation)
]
},
layout: 'noBorders',
margin: [0, 5, 0, 0]
}
]
}
]
]
},
layout: 'noBorders'
}
]
},
// Suction Groups Section
{
stack: [
this.createSectionHeader('Suction Groups'),
{
margin: [0, 10, 0, 0],
columns: [
{
width: '48%',
stack: [
this.createSuctionGroupTable(sanitizedData.LTSuctionGroup),
this.createSubsectionHeader('LT Loads'),
this.createLoadsTable(sanitizedData.LTSuctionGroup.Loads)
]
},
{
width: '4%',
text: ''
},
{
width: '48%',
stack: [
this.createSuctionGroupTable(sanitizedData.MTSuctionGroup),
this.createSubsectionHeader('MT Loads'),
this.createLoadsTable(sanitizedData.MTSuctionGroup.Loads)
]
}
]
}
]
},
// Compressors Section
{
stack: [
this.createSectionHeader('Compressors'),
{
margin: [0, 10, 0, 0],
columns: [
{
width: '48%',
stack: [
this.createCompressorTable(sanitizedData.LTCompressors, 'LT Compressors')
]
},
{
width: '4%',
text: ''
},
{
width: '48%',
stack: [
this.createCompressorTable(sanitizedData.MTCompressors, 'MT Compressors')
]
}
]
}
]
},
// Condenser Section
{
stack: [
this.createSectionHeader('Condenser'),
{
margin: [0, 10, 0, 0],
...this.createCondenserTable(sanitizedData.Condenser)
}
]
}
],
styles: {
projectTitle: {
fontSize: 16,
bold: true,
color: 'white'
},
projectSubtitle: {
fontSize: 14,
bold: false,
color: 'white'
},
projectDetail: {
fontSize: 10,
color: '#e0e0e0'
},
sectionHeader: {
fontSize: 16,
bold: true,
color: '#4054b2',
margin: [0, 0, 0, 10],
border: [false, false, false, true],
borderColor: '#4054b2',
borderWidth: 2
},
subsectionHeader: {
fontSize: 13,
bold: true,
color: '#333',
margin: [0, 0, 0, 5],
background: '#f5f5f5',
border: [false, false, false, true],
borderColor: '#e0e0e0',
borderWidth: 1
},
dataLabel: {
fontSize: 10,
bold: true,
color: '#555'
},
dataValue: {
fontSize: 10,
color: '#333'
},
tableHeader: {
bold: true,
fontSize: 10,
color: 'white',
margin: [0, 3, 0, 3]
},
tableCell: {
fontSize: 9,
color: '#333',
margin: [0, 2, 0, 2]
},
footer: {
fontSize: 9,
color: '#999'
}
},
defaultStyle: {
font: 'Roboto',
fontSize: 10
},
images: {
sampleImage: 'http://localhost:4200/assets/images/dfr/dfr_logo.png'
}
};
}
public generatePDF(): void {
try {
const sanitizedData = this.deepSanitize(JSON.parse(JSON.stringify(this.data[0])));
const docDefinition = this.createPdfDefinition(sanitizedData);
pdfMake.createPdf(docDefinition).open();
} catch (error) {
console.error('PDF generation failed:', error);
pdfMake.createPdf({
content: [
{ text: 'Error Generating Report', style: 'errorHeader' },
{ text: 'An error occurred while generating the PDF report.', margin: [0, 10, 0, 0] },
{ text: 'Technical details:', margin: [0, 10, 0, 0] },
{ text: error.message, style: 'errorDetails' }
],
styles: {
errorHeader: {
fontSize: 18,
bold: true,
color: '#ff0000'
},
errorDetails: {
fontSize: 10,
color: '#666'
}
}
}).open();
}
}
}
import { Component } from '@angular/core';
import * as pdfMake from 'pdfmake/build/pdfmake';
import * as pdfFonts from 'pdfmake/build/vfs_fonts';
(<any>pdfMake).vfs = pdfFonts?.pdfMake?.vfs;
@Component({
selector: 'app-legend-sheet',
templateUrl: './legend-sheet.component.html',
styleUrls: ['./legend-sheet.component.scss']
})
export class LegendSheetComponent {
data = [
{
"QuoteLineId": "07a00014dfa7bca7",
"JobName": "DevProject 4001 - 605 - San Jose, CA, USA",
"Customer": "CUSTOM INNOVATION LLC",
"NameAndID": "FLX-3+0x2-M0237-L0080",
"ISE": "JOHN DOE",
"MasterRevLevel": "17681",
"LastEdited": "04/11/25 07:41:08",
"GeneralData": {
"Refrigerant": "R-744",
"AmbientTemp": "99",
"OilType": "BSE85K",
"OilGallons": "4",
"ASHRAEElevation": "51",
"ASHRAECoolingDB": "91.4",
"ASHRAECoolingMCWB": "66.2",
"ReceiverType": "Vertical",
"ReceiverSize": "V16x63",
"ReceiverPressureRating": "130 bar",
"PowerPointRackVoltage": "208/60/3",
"PowerPointRackMCAMOPDSCCR": "286/400/50K",
"ControllerMfg": "Danfoss",
"ControllerModel": "AK2-PC781"
},
"LTSuctionGroup": {
"Name": "LT Suction Group",
"ID": "rack_1LT Suction Group",
"EvapTemp": "-15",
"TotalLoad": "75100",
"Loads": [
{
"ID": "Unit #1 LT Remote Manifold",
"Description": "Remote Manifold",
"Model": "[Remote Manifold]",
"Load": "75.1",
"EvapTemp": "-20",
"StubSizeLiquid": "1-1/8\"",
"StubSizeSuction": "1-1/8\"",
"StubSizeHotGas": "5/8\"",
"CircuitServiceLiquid": "Ball Valve",
"CircuitServiceSuction": "Ball Valve",
"CircuitServiceHotGas": "BV w/ChkBypass"
},
{
"ID": "Rack_1_LT_1",
"Description": "Ice Cream",
"Model": "O5M-NRG",
"Load": "1301",
"EvapTemp": "26"
}
],
"RequirementBTUH": "75.1K BTUH",
"CapacityBTUH": "84.9K BTUH",
"LoadingPercent": "113.0%",
"SystemPercent": "31%",
"LiquidTemperature": "38°F"
},
"MTSuctionGroup": {
"Name": "MT Suction Group",
"ID": "rack_1MT Suction Group",
"EvapTemp": "25",
"TotalLoad": "170700",
"Loads": [
{
"ID": "Unit #1 MT Remote Manifold",
"Description": "Remote Manifold",
"Model": "[Remote Manifold]",
"Load": "170.7",
"EvapTemp": "20",
"StubSizeLiquid": "1-1/8\"",
"StubSizeSuction": "1-1/8\"",
"StubSizeHotGas": "5/8\"",
"CircuitServiceLiquid": "Ball Valve",
"CircuitServiceSuction": "Ball Valve",
"CircuitServiceHotGas": "BV w/ChkBypass"
},
{
"ID": "36A",
"Description": "~8' M/D Deli",
"Model": "8' ON5DMX-NRG - Deli",
"Load": "9.6",
"EvapTemp": "28",
"ElecDefrost": {
"Voltage": "120/1",
"Amps": "0.5",
"KW": "0.1"
},
"Fans": {
"Voltage": "120/1",
"Amps": "0.5",
"KW": "0.1"
},
"Lights": {
"Voltage": "120/1",
"Amps": "0.0",
"KW": "0.0"
},
"Sweats": {
"Voltage": "120/1",
"Amps": "0.0",
"KW": "0.0"
},
"StubSizeLiquid": "",
"StubSizeSuction": "",
"StubSizeHotGas": "",
"CircuitServiceLiquid": "None",
"CircuitServiceSuction": "None",
"CircuitServiceHotGas": "None"
}
],
"RequirementBTUH": "170.7K BTUH",
"CapacityBTUH": "183.5K BTUH",
"LoadingPercent": "105.4%",
"SystemPercent": "69%",
"LiquidTemperature": "38°F"
},
"LTCompressors": [
{
"SuctionGroup": "-21 Suction Group",
"Position": "1",
"Model": "ZOD58K3E",
"FLA": "31.2",
"LRA": "163",
"HP": "6.0",
"Capacity": "54.98",
"THR": "0",
"Percent": "100",
"Disch": "5/8\"",
"Suct": "7/8\"",
"UnlNumber": "0",
"HPC": "060-5243",
"VFD": "134U4917"
}
],
"MTCompressors": [
{
"SuctionGroup": "+19 Suction Group",
"Position": "2",
"Model": "4DTE-25K",
"FLA": "92.8",
"LRA": "362",
"HP": "20.0",
"Capacity": "131.75",
"THR": "321.99",
"Percent": "51",
"Disch": "5/8\"",
"Suct": "1-1/8\"",
"UnlNumber": "0",
"HPC": "071B010M",
"VFD": "134U4920"
},
{
"SuctionGroup": "+19 Suction Group",
"Position": "3",
"Model": "4HTE-15K",
"FLA": "59.9",
"LRA": "294",
"HP": "15.0",
"Capacity": "71.05",
"THR": "170.65",
"Percent": "41",
"Disch": "7/8\"",
"Suct": "1-1/8\"",
"UnlNumber": "0",
"HPC": "071B010M",
"VFD": ""
}
],
"Condenser": {
"Mfg": "BAC",
"Qty": "1",
"Model": "TSDC-CO2-077-6.2",
"Capacity": "715",
"TD": "3",
"Fans": "2",
"Voltage": "208/3",
"Amps": "20.6",
"MOPD": "30",
"FPI": "10"
}
}
];
constructor() {
pdfMake.fonts = {
Roboto: {
normal: 'Roboto-Regular.ttf',
bold: 'Roboto-Medium.ttf',
italics: 'Roboto-Italic.ttf',
bolditalics: 'Roboto-MediumItalic.ttf'
}
};
}
private sanitizeValue(value: any): string | number | boolean | any {
if (value === null || value === undefined) return 'N/A';
if (typeof value === 'boolean') return value;
if (typeof value === 'number') return isNaN(value) ? 'N/A' : value;
if (typeof value === 'string') {
if (!isNaN(Number(value)) && value.trim() !== '') {
const num = Number(value);
return isNaN(num) ? value : num;
}
return value.trim() === '' ? 'N/A' : value;
}
if (typeof value === 'object') return value;
return 'N/A';
}
private deepSanitize(data: any): any {
if (Array.isArray(data)) return data.map(item => this.deepSanitize(item));
if (typeof data === 'object' && data !== null) {
const sanitized: any = {};
for (const key in data) {
if (data.hasOwnProperty(key)) {
sanitized[key] = this.deepSanitize(data[key]);
}
}
return sanitized;
}
return this.sanitizeValue(data);
}
private createStyledHeader(sanitizedData: any): any {
return {
stack: [
{
canvas: [
{
type: 'rect',
x: 0, y: 0,
w: 595, h: 90,
color: '#4054b2'
}
]
},
{
absolutePosition: { x: 40, y: 20 },
columns: [
{
width: 60,
image: 'sampleImage',
fit: [50, 50],
alignment: 'left'
},
{
width: '*',
stack: [
{
text: sanitizedData.JobName || 'N/A',
style: 'projectTitle',
margin: [15, 5, 0, 2]
},
{
text: sanitizedData.Customer || 'N/A',
style: 'projectSubtitle',
margin: [15, 0, 0, 2]
},
{
text: `Quote Line ID: ${sanitizedData.QuoteLineId || 'N/A'}`,
style: 'projectDetail',
margin: [15, 0, 0, 0]
}
]
},
{
width: 120,
stack: [
{
text: 'Last Edited:',
style: 'projectDetail',
alignment: 'right',
margin: [0, 5, 0, 2]
},
{
text: sanitizedData.LastEdited || 'N/A',
style: 'projectDetail',
alignment: 'right',
margin: [0, 0, 0, 0]
}
]
}
]
}
],
margin: [0, -40, 0, 0]
};
}
private createSectionHeader(title: string): any {
return {
text: title,
style: 'sectionHeader',
margin: [0, 0, 0, 10]
};
}
private createSubsectionHeader(title: string): any {
return {
text: title,
style: 'subsectionHeader',
margin: [0, 0, 0, 5],
background: '#f5f5f5',
border: [false, false, false, true],
borderColor: '#e0e0e0',
borderWidth: 1
};
}
private createDataRow(label: string, value: any): any[] {
return [
{
text: label,
style: 'dataLabel',
margin: [0, 2, 0, 2]
},
{
text: this.sanitizeValue(value),
style: 'dataValue',
margin: [0, 2, 0, 2]
}
];
}
private createSuctionGroupTable(group: any): any {
return {
table: {
widths: ['40%', '60%'],
body: [
this.createDataRow('Name', group.Name),
this.createDataRow('Evap Temp', group.EvapTemp),
this.createDataRow('Total Load', group.TotalLoad),
this.createDataRow('Requirement BTUH', group.RequirementBTUH),
this.createDataRow('Capacity BTUH', group.CapacityBTUH),
this.createDataRow('Loading %', group.LoadingPercent),
this.createDataRow('System %', group.SystemPercent),
this.createDataRow('Liquid Temperature', group.LiquidTemperature)
]
},
layout: {
hLineWidth: (i) => i === 0 || i === 8 ? 1 : 0,
vLineWidth: () => 0,
paddingTop: (i) => i === 0 ? 5 : 3,
paddingBottom: (i) => i === 7 ? 5 : 3
},
margin: [0, 5, 0, 15]
};
}
private createLoadsTable(loads: any[]): any {
const body = [
[
{ text: 'ID', style: 'tableHeader' },
{ text: 'Description', style: 'tableHeader' },
{ text: 'Model', style: 'tableHeader' },
{ text: 'Load', style: 'tableHeader', alignment: 'right' },
{ text: 'Evap Temp', style: 'tableHeader', alignment: 'right' }
]
];
loads.forEach(load => {
body.push([
{ text: load.ID || 'N/A', style: 'tableCell' },
{ text: load.Description || 'N/A', style: 'tableCell' },
{ text: load.Model || 'N/A', style: 'tableCell' },
{ text: this.sanitizeValue(load.Load), style: 'tableCell', alignment: 'right' },
{ text: this.sanitizeValue(load.EvapTemp), style: 'tableCell', alignment: 'right' }
]);
});
return {
table: {
widths: ['*', '*', '*', 'auto', 'auto'],
body: body
},
layout: {
fillColor: (rowIndex) => rowIndex === 0 ? '#4054b2' : null,
hLineWidth: (i) => i === 0 ? 1 : 0,
vLineWidth: (i) => i === 0 ? 1 : 0,
paddingLeft: (i) => i === 0 ? 5 : 3,
paddingRight: (i) => i === 0 ? 5 : 3,
paddingTop: (i) => i === 0 ? 3 : 2,
paddingBottom: (i) => i === 0 ? 3 : 2
},
margin: [0, 5, 0, 15]
};
}
private createCompressorTable(compressors: any[], title: string): any {
const body = [
[
{ text: 'Position', style: 'tableHeader' },
{ text: 'Model', style: 'tableHeader' },
{ text: 'HP', style: 'tableHeader', alignment: 'right' },
{ text: 'Capacity', style: 'tableHeader', alignment: 'right' },
{ text: 'FLA', style: 'tableHeader', alignment: 'right' },
{ text: 'LRA', style: 'tableHeader', alignment: 'right' }
]
];
compressors.forEach(comp => {
body.push([
{ text: comp.Position || 'N/A', style: 'tableCell' },
{ text: comp.Model || 'N/A', style: 'tableCell' },
{ text: this.sanitizeValue(comp.HP), style: 'tableCell', alignment: 'right' },
{ text: this.sanitizeValue(comp.Capacity), style: 'tableCell', alignment: 'right' },
{ text: this.sanitizeValue(comp.FLA), style: 'tableCell', alignment: 'right' },
{ text: this.sanitizeValue(comp.LRA), style: 'tableCell', alignment: 'right' }
]);
});
return {
stack: [
this.createSubsectionHeader(title),
{
table: {
widths: ['auto', '*', 'auto', 'auto', 'auto', 'auto'],
body: body
},
layout: {
fillColor: (rowIndex) => rowIndex === 0 ? '#4054b2' : null,
hLineWidth: (i) => i === 0 ? 1 : 0,
vLineWidth: (i) => i === 0 ? 1 : 0,
paddingLeft: (i) => i === 0 ? 5 : 3,
paddingRight: (i) => i === 0 ? 5 : 3,
paddingTop: (i) => i === 0 ? 3 : 2,
paddingBottom: (i) => i === 0 ? 3 : 2
},
margin: [0, 5, 0, 15]
}
]
};
}
private createCondenserTable(condenser: any): any {
return {
stack: [
this.createSubsectionHeader('Condenser Details'),
{
table: {
widths: ['20%', '20%', '20%', '20%', '20%'],
body: [
[
{ text: 'Manufacturer', style: 'tableHeader' },
{ text: 'Model', style: 'tableHeader' },
{ text: 'Capacity', style: 'tableHeader', alignment: 'right' },
{ text: 'Voltage', style: 'tableHeader' },
{ text: 'Fans', style: 'tableHeader', alignment: 'right' }
],
[
{ text: condenser.Mfg || 'N/A', style: 'tableCell' },
{ text: condenser.Model || 'N/A', style: 'tableCell' },
{ text: this.sanitizeValue(condenser.Capacity), style: 'tableCell', alignment: 'right' },
{ text: condenser.Voltage || 'N/A', style: 'tableCell' },
{ text: condenser.Fans || 'N/A', style: 'tableCell', alignment: 'right' }
]
]
},
layout: {
fillColor: (rowIndex) => rowIndex === 0 ? '#4054b2' : null,
hLineWidth: (i) => i === 1 ? 1 : 0,
vLineWidth: () => 0
}
}
]
};
}
private createPdfDefinition(sanitizedData: any): any {
return {
pageSize: 'A4',
pageMargins: [40, 140, 40, 60],
header: this.createStyledHeader(sanitizedData),
footer: function(currentPage, pageCount) {
return {
margin: [40, 10, 40, 10],
text: [
{ text: 'Page ', style: 'footer' },
{ text: currentPage.toString(), style: 'footer' },
{ text: ' of ', style: 'footer' },
{ text: pageCount.toString(), style: 'footer' },
{ text: ' | ', style: 'footer' },
{ text: new Date().toLocaleDateString(), style: 'footer' }
],
alignment: 'center'
};
},
content: [
// Project Overview Section
{
stack: [
this.createSectionHeader('Project Overview'),
{
margin: [0, 10, 0, 20],
table: {
widths: ['50%', '50%'],
body: [
[
{
stack: [
this.createSubsectionHeader('Basic Information'),
{
table: {
widths: ['40%', '60%'],
body: [
this.createDataRow('Job Name', sanitizedData.JobName),
this.createDataRow('Customer', sanitizedData.Customer),
this.createDataRow('Name & ID', sanitizedData.NameAndID),
this.createDataRow('ISE', sanitizedData.ISE),
this.createDataRow('Master Rev Level', sanitizedData.MasterRevLevel)
]
},
layout: 'noBorders',
margin: [0, 5, 0, 0]
}
]
},
{
stack: [
this.createSubsectionHeader('Technical Specifications'),
{
table: {
widths: ['40%', '60%'],
body: [
this.createDataRow('Refrigerant', sanitizedData.GeneralData.Refrigerant),
this.createDataRow('Ambient Temp', sanitizedData.GeneralData.AmbientTemp),
this.createDataRow('Oil Type', sanitizedData.GeneralData.OilType),
this.createDataRow('Oil Gallons', sanitizedData.GeneralData.OilGallons),
this.createDataRow('ASHRAE Elevation', sanitizedData.GeneralData.ASHRAEElevation)
]
},
layout: 'noBorders',
margin: [0, 5, 0, 0]
}
]
}
]
]
},
layout: 'noBorders'
}
]
},
// Suction Groups Section
{
stack: [
this.createSectionHeader('Suction Groups'),
{
margin: [0, 10, 0, 0],
columns: [
{
width: '48%',
stack: [
this.createSuctionGroupTable(sanitizedData.LTSuctionGroup),
this.createSubsectionHeader('LT Loads'),
this.createLoadsTable(sanitizedData.LTSuctionGroup.Loads)
]
},
{
width: '4%',
text: ''
},
{
width: '48%',
stack: [
this.createSuctionGroupTable(sanitizedData.MTSuctionGroup),
this.createSubsectionHeader('MT Loads'),
this.createLoadsTable(sanitizedData.MTSuctionGroup.Loads)
]
}
]
}
]
},
// Compressors Section
{
stack: [
this.createSectionHeader('Compressors'),
{
margin: [0, 10, 0, 0],
columns: [
{
width: '48%',
stack: [
this.createCompressorTable(sanitizedData.LTCompressors, 'LT Compressors')
]
},
{
width: '4%',
text: ''
},
{
width: '48%',
stack: [
this.createCompressorTable(sanitizedData.MTCompressors, 'MT Compressors')
]
}
]
}
]
},
// Condenser Section
{
stack: [
this.createSectionHeader('Condenser'),
{
margin: [0, 10, 0, 0],
...this.createCondenserTable(sanitizedData.Condenser)
}
]
}
],
styles: {
projectTitle: {
fontSize: 16,
bold: true,
color: 'white'
},
projectSubtitle: {
fontSize: 14,
bold: false,
color: 'white'
},
projectDetail: {
fontSize: 10,
color: '#e0e0e0'
},
sectionHeader: {
fontSize: 16,
bold: true,
color: '#4054b2',
margin: [0, 0, 0, 10],
border: [false, false, false, true],
borderColor: '#4054b2',
borderWidth: 2
},
subsectionHeader: {
fontSize: 13,
bold: true,
color: '#333',
margin: [0, 0, 0, 5],
background: '#f5f5f5',
border: [false, false, false, true],
borderColor: '#e0e0e0',
borderWidth: 1
},
dataLabel: {
fontSize: 10,
bold: true,
color: '#555'
},
dataValue: {
fontSize: 10,
color: '#333'
},
tableHeader: {
bold: true,
fontSize: 10,
color: 'white',
margin: [0, 3, 0, 3]
},
tableCell: {
fontSize: 9,
color: '#333',
margin: [0, 2, 0, 2]
},
footer: {
fontSize: 9,
color: '#999'
}
},
defaultStyle: {
font: 'Roboto',
fontSize: 10
},
images: {
sampleImage: 'http://localhost:4200/assets/images/dfr/dfr_logo.png'
}
};
}
public generatePDF(): void {
try {
const sanitizedData = this.deepSanitize(JSON.parse(JSON.stringify(this.data[0])));
const docDefinition = this.createPdfDefinition(sanitizedData);
pdfMake.createPdf(docDefinition).open();
} catch (error) {
console.error('PDF generation failed:', error);
pdfMake.createPdf({
content: [
{ text: 'Error Generating Report', style: 'errorHeader' },
{ text: 'An error occurred while generating the PDF report.', margin: [0, 10, 0, 0] },
{ text: 'Technical details:', margin: [0, 10, 0, 0] },
{ text: error.message, style: 'errorDetails' }
],
styles: {
errorHeader: {
fontSize: 18,
bold: true,
color: '#ff0000'
},
errorDetails: {
fontSize: 10,
color: '#666'
}
}
}).open();
}
}
}
Key features of this implementation:
Beautiful Header Design:
Improved Section Layout:
Enhanced Data Tables:
Responsive Layout:
Error Handling:
Styling Improvements:
To use this component, simply call the generatePDF() method when you want to create the PDF. The PDF will automatically open in a new window with all the data beautifully formatted.*_
Ah, I see the issue. The error occurs because we're calling deepSanitize() before defining it in the class. Let me fix this by properly organizing the methods and ensuring all referenced methods exist. Here's the corrected version:
import { Component } from '@angular/core';
import * as pdfMake from 'pdfmake/build/pdfmake';
import * as pdfFonts from 'pdfmake/build/vfs_fonts';
(<any>pdfMake).vfs = pdfFonts?.pdfMake?.vfs;
@Component({
selector: 'app-legend-sheet',
templateUrl: './legend-sheet.component.html',
styleUrls: ['./legend-sheet.component.scss']
})
export class LegendSheetComponent {
// ... (keep the same data structure as before)
constructor() {
pdfMake.fonts = {
Roboto: {
normal: 'Roboto-Regular.ttf',
bold: 'Roboto-Medium.ttf',
italics: 'Roboto-Italic.ttf',
bolditalics: 'Roboto-MediumItalic.ttf'
}
};
}
// Move these methods before any methods that call them
private sanitizeValue(value: any): string | number | boolean | any {
if (value === null || value === undefined) return 'N/A';
if (typeof value === 'boolean') return value;
if (typeof value === 'number') return isNaN(value) ? 'N/A' : value;
if (typeof value === 'string') {
if (!isNaN(Number(value)) && value.trim() !== '') {
const num = Number(value);
return isNaN(num) ? value : num;
}
return value.trim() === '' ? 'N/A' : value;
}
if (typeof value === 'object') return value;
return 'N/A';
}
private deepSanitize(data: any): any {
if (Array.isArray(data)) return data.map(item => this.deepSanitize(item));
if (typeof data === 'object' && data !== null) {
const sanitized: any = {};
for (const key in data) {
if (data.hasOwnProperty(key)) {
sanitized[key] = this.deepSanitize(data[key]);
}
}
return sanitized;
}
return this.sanitizeValue(data);
}
// ... (keep all other methods the same as in the previous implementation)
public generatePDF(): void {
try {
// Now deepSanitize() will be properly recognized
const sanitizedData = this.deepSanitize(JSON.parse(JSON.stringify(this.data[0])));
const docDefinition = this.createPdfDefinition(sanitizedData);
pdfMake.createPdf(docDefinition).open();
} catch (error) {
console.error('PDF generation failed:', error);
pdfMake.createPdf({
content: [
{ text: 'Error Generating Report', style: 'errorHeader' },
{ text: 'An error occurred while generating the PDF report.', margin: [0, 10, 0, 0] },
{ text: 'Technical details:', margin: [0, 10, 0, 0] },
{ text: error.message, style: 'errorDetails' }
],
styles: {
errorHeader: {
fontSize: 18,
bold: true,
color: '#ff0000'
},
errorDetails: {
fontSize: 10,
color: '#666'
}
}
}).open();
}
}
}
import { Component } from '@angular/core';
import * as pdfMake from 'pdfmake/build/pdfmake';
import * as pdfFonts from 'pdfmake/build/vfs_fonts';
(<any>pdfMake).vfs = pdfFonts?.pdfMake?.vfs;
@Component({
selector: 'app-legend-sheet',
templateUrl: './legend-sheet.component.html',
styleUrls: ['./legend-sheet.component.scss']
})
export class LegendSheetComponent {
// ... (keep the same data structure as before)
constructor() {
pdfMake.fonts = {
Roboto: {
normal: 'Roboto-Regular.ttf',
bold: 'Roboto-Medium.ttf',
italics: 'Roboto-Italic.ttf',
bolditalics: 'Roboto-MediumItalic.ttf'
}
};
}
// Move these methods before any methods that call them
private sanitizeValue(value: any): string | number | boolean | any {
if (value === null || value === undefined) return 'N/A';
if (typeof value === 'boolean') return value;
if (typeof value === 'number') return isNaN(value) ? 'N/A' : value;
if (typeof value === 'string') {
if (!isNaN(Number(value)) && value.trim() !== '') {
const num = Number(value);
return isNaN(num) ? value : num;
}
return value.trim() === '' ? 'N/A' : value;
}
if (typeof value === 'object') return value;
return 'N/A';
}
private deepSanitize(data: any): any {
if (Array.isArray(data)) return data.map(item => this.deepSanitize(item));
if (typeof data === 'object' && data !== null) {
const sanitized: any = {};
for (const key in data) {
if (data.hasOwnProperty(key)) {
sanitized[key] = this.deepSanitize(data[key]);
}
}
return sanitized;
}
return this.sanitizeValue(data);
}
// ... (keep all other methods the same as in the previous implementation)
public generatePDF(): void {
try {
// Now deepSanitize() will be properly recognized
const sanitizedData = this.deepSanitize(JSON.parse(JSON.stringify(this.data[0])));
const docDefinition = this.createPdfDefinition(sanitizedData);
pdfMake.createPdf(docDefinition).open();
} catch (error) {
console.error('PDF generation failed:', error);
pdfMake.createPdf({
content: [
{ text: 'Error Generating Report', style: 'errorHeader' },
{ text: 'An error occurred while generating the PDF report.', margin: [0, 10, 0, 0] },
{ text: 'Technical details:', margin: [0, 10, 0, 0] },
{ text: error.message, style: 'errorDetails' }
],
styles: {
errorHeader: {
fontSize: 18,
bold: true,
color: '#ff0000'
},
errorDetails: {
fontSize: 10,
color: '#666'
}
}
}).open();
}
}
}
The key changes made:
sanitizeValue() and deepSanitize() methods to be declared before any methods that call themthis.This should resolve the TypeScript error you encountered while keeping all the beautiful PDF formatting improvements we made earlier. The PDF generation will now work correctly with proper data sanitization._
Here's the improved version with perfect header alignment and each section in a well-structured table format:
import { Component } from '@angular/core';
import * as pdfMake from 'pdfmake/build/pdfmake';
import * as pdfFonts from 'pdfmake/build/vfs_fonts';
(<any>pdfMake).vfs = pdfFonts?.pdfMake?.vfs;
@Component({
selector: 'app-legend-sheet',
templateUrl: './legend-sheet.component.html',
styleUrls: ['./legend-sheet.component.scss']
})
export class LegendSheetComponent {
// ... (keep the same data structure as before)
private createHeader(sanitizedData: any) {
return {
table: {
widths: ['auto', '*', 'auto'],
body: [
[
{
image: 'sampleImage',
width: 60,
height: 60,
margin: [0, 0, 10, 0],
alignment: 'left'
},
{
stack: [
{
text: sanitizedData.JobName || 'N/A',
style: 'headerTitle',
margin: [0, 5, 0, 2]
},
{
text: sanitizedData.Customer || 'N/A',
style: 'headerSubtitle',
margin: [0, 0, 0, 2]
}
],
alignment: 'left'
},
{
stack: [
{
text: `Rev: ${sanitizedData.MasterRevLevel || 'N/A'}`,
style: 'headerDetail',
alignment: 'right'
},
{
text: sanitizedData.LastEdited || 'N/A',
style: 'headerDetail',
alignment: 'right'
}
],
margin: [0, 5, 0, 0]
}
]
]
},
layout: 'noBorders',
margin: [0, 10, 0, 20]
};
}
private createSection(title: string, content: any) {
return {
table: {
widths: ['*'],
body: [
[
{
stack: [
{ text: title, style: 'sectionTitle' },
content
],
margin: [0, 0, 0, 15]
}
]
]
},
layout: 'noBorders'
};
}
private createTwoColumnSection(leftContent: any, rightContent: any) {
return {
table: {
widths: ['48%', '4%', '48%'],
body: [
[
leftContent,
'',
rightContent
]
]
},
layout: 'noBorders'
};
}
private createDataTable(headers: string[], rows: any[][]) {
const headerRow = headers.map(header => ({
text: header,
style: 'tableHeader'
}));
return {
table: {
headerRows: 1,
widths: headers.map(() => '*'),
body: [headerRow, ...rows]
},
layout: {
fillColor: (rowIndex: number) =>
rowIndex === 0 ? '#4054b2' : (rowIndex % 2 === 0 ? '#f5f5f5' : null),
hLineWidth: () => 0.5,
vLineWidth: () => 0.5,
hLineColor: () => '#dddddd',
vLineColor: () => '#dddddd'
}
};
}
private createPdfDefinition(sanitizedData: any): any {
return {
pageSize: 'A4',
pageMargins: [40, 40, 40, 60],
header: this.createHeader(sanitizedData),
content: [
// Project Overview
this.createSection('Project Overview', {
table: {
widths: ['50%', '50%'],
body: [
[
this.createDataTable(
['Property', 'Value'],
[
['Job Name', sanitizedData.JobName],
['Customer', sanitizedData.Customer],
['Name & ID', sanitizedData.NameAndID],
['ISE', sanitizedData.ISE]
]
),
this.createDataTable(
['Property', 'Value'],
[
['Refrigerant', sanitizedData.GeneralData.Refrigerant],
['Ambient Temp', sanitizedData.GeneralData.AmbientTemp],
['Oil Type', sanitizedData.GeneralData.OilType],
['ASHRAE Elevation', sanitizedData.GeneralData.ASHRAEElevation]
]
)
]
]
}
}),
// Suction Groups
this.createSection('Suction Groups',
this.createTwoColumnSection(
this.createDataTable(
['Property', 'Value'],
[
['Name', sanitizedData.LTSuctionGroup.Name],
['Evap Temp', sanitizedData.LTSuctionGroup.EvapTemp],
['Total Load', sanitizedData.LTSuctionGroup.TotalLoad],
['Requirement BTUH', sanitizedData.LTSuctionGroup.RequirementBTUH],
['Capacity BTUH', sanitizedData.LTSuctionGroup.CapacityBTUH]
]
),
this.createDataTable(
['Property', 'Value'],
[
['Name', sanitizedData.MTSuctionGroup.Name],
['Evap Temp', sanitizedData.MTSuctionGroup.EvapTemp],
['Total Load', sanitizedData.MTSuctionGroup.TotalLoad],
['Requirement BTUH', sanitizedData.MTSuctionGroup.RequirementBTUH],
['Capacity BTUH', sanitizedData.MTSuctionGroup.CapacityBTUH]
]
)
)
),
// Compressors
this.createSection('Compressors',
this.createTwoColumnSection(
this.createDataTable(
['Position', 'Model', 'HP', 'Capacity'],
sanitizedData.LTCompressors.map(comp => [
comp.Position,
comp.Model,
comp.HP,
comp.Capacity
])
),
this.createDataTable(
['Position', 'Model', 'HP', 'Capacity'],
sanitizedData.MTCompressors.map(comp => [
comp.Position,
comp.Model,
comp.HP,
comp.Capacity
])
)
)
),
// Condenser
this.createSection('Condenser',
this.createDataTable(
['Property', 'Value', 'Property', 'Value'],
[
['Manufacturer', sanitizedData.Condenser.Mfg, 'Model', sanitizedData.Condenser.Model],
['Capacity', sanitizedData.Condenser.Capacity, 'Voltage', sanitizedData.Condenser.Voltage],
['Fans', sanitizedData.Condenser.Fans, 'Amps', sanitizedData.Condenser.Amps]
]
)
)
],
styles: {
headerTitle: {
fontSize: 16,
bold: true,
color: '#333'
},
headerSubtitle: {
fontSize: 14,
color: '#555'
},
headerDetail: {
fontSize: 10,
color: '#777'
},
sectionTitle: {
fontSize: 14,
bold: true,
color: '#4054b2',
margin: [0, 0, 0, 10]
},
tableHeader: {
bold: true,
fontSize: 10,
color: '#ffffff',
fillColor: '#4054b2',
margin: [0, 3, 0, 3],
alignment: 'left'
},
footer: {
fontSize: 9,
color: '#999'
}
},
defaultStyle: {
font: 'Roboto',
fontSize: 10
}
};
}
public generatePDF(): void {
try {
const sanitizedData = this.deepSanitize(JSON.parse(JSON.stringify(this.data[0])));
const docDefinition = this.createPdfDefinition(sanitizedData);
pdfMake.createPdf(docDefinition).open();
} catch (error) {
console.error('PDF generation failed:', error);
// Error handling as before
}
}
}
import { Component } from '@angular/core';
import * as pdfMake from 'pdfmake/build/pdfmake';
import * as pdfFonts from 'pdfmake/build/vfs_fonts';
(<any>pdfMake).vfs = pdfFonts?.pdfMake?.vfs;
@Component({
selector: 'app-legend-sheet',
templateUrl: './legend-sheet.component.html',
styleUrls: ['./legend-sheet.component.scss']
})
export class LegendSheetComponent {
// ... (keep the same data structure as before)
private createHeader(sanitizedData: any) {
return {
table: {
widths: ['auto', '*', 'auto'],
body: [
[
{
image: 'sampleImage',
width: 60,
height: 60,
margin: [0, 0, 10, 0],
alignment: 'left'
},
{
stack: [
{
text: sanitizedData.JobName || 'N/A',
style: 'headerTitle',
margin: [0, 5, 0, 2]
},
{
text: sanitizedData.Customer || 'N/A',
style: 'headerSubtitle',
margin: [0, 0, 0, 2]
}
],
alignment: 'left'
},
{
stack: [
{
text: `Rev: ${sanitizedData.MasterRevLevel || 'N/A'}`,
style: 'headerDetail',
alignment: 'right'
},
{
text: sanitizedData.LastEdited || 'N/A',
style: 'headerDetail',
alignment: 'right'
}
],
margin: [0, 5, 0, 0]
}
]
]
},
layout: 'noBorders',
margin: [0, 10, 0, 20]
};
}
private createSection(title: string, content: any) {
return {
table: {
widths: ['*'],
body: [
[
{
stack: [
{ text: title, style: 'sectionTitle' },
content
],
margin: [0, 0, 0, 15]
}
]
]
},
layout: 'noBorders'
};
}
private createTwoColumnSection(leftContent: any, rightContent: any) {
return {
table: {
widths: ['48%', '4%', '48%'],
body: [
[
leftContent,
'',
rightContent
]
]
},
layout: 'noBorders'
};
}
private createDataTable(headers: string[], rows: any[][]) {
const headerRow = headers.map(header => ({
text: header,
style: 'tableHeader'
}));
return {
table: {
headerRows: 1,
widths: headers.map(() => '*'),
body: [headerRow, ...rows]
},
layout: {
fillColor: (rowIndex: number) =>
rowIndex === 0 ? '#4054b2' : (rowIndex % 2 === 0 ? '#f5f5f5' : null),
hLineWidth: () => 0.5,
vLineWidth: () => 0.5,
hLineColor: () => '#dddddd',
vLineColor: () => '#dddddd'
}
};
}
private createPdfDefinition(sanitizedData: any): any {
return {
pageSize: 'A4',
pageMargins: [40, 40, 40, 60],
header: this.createHeader(sanitizedData),
content: [
// Project Overview
this.createSection('Project Overview', {
table: {
widths: ['50%', '50%'],
body: [
[
this.createDataTable(
['Property', 'Value'],
[
['Job Name', sanitizedData.JobName],
['Customer', sanitizedData.Customer],
['Name & ID', sanitizedData.NameAndID],
['ISE', sanitizedData.ISE]
]
),
this.createDataTable(
['Property', 'Value'],
[
['Refrigerant', sanitizedData.GeneralData.Refrigerant],
['Ambient Temp', sanitizedData.GeneralData.AmbientTemp],
['Oil Type', sanitizedData.GeneralData.OilType],
['ASHRAE Elevation', sanitizedData.GeneralData.ASHRAEElevation]
]
)
]
]
}
}),
// Suction Groups
this.createSection('Suction Groups',
this.createTwoColumnSection(
this.createDataTable(
['Property', 'Value'],
[
['Name', sanitizedData.LTSuctionGroup.Name],
['Evap Temp', sanitizedData.LTSuctionGroup.EvapTemp],
['Total Load', sanitizedData.LTSuctionGroup.TotalLoad],
['Requirement BTUH', sanitizedData.LTSuctionGroup.RequirementBTUH],
['Capacity BTUH', sanitizedData.LTSuctionGroup.CapacityBTUH]
]
),
this.createDataTable(
['Property', 'Value'],
[
['Name', sanitizedData.MTSuctionGroup.Name],
['Evap Temp', sanitizedData.MTSuctionGroup.EvapTemp],
['Total Load', sanitizedData.MTSuctionGroup.TotalLoad],
['Requirement BTUH', sanitizedData.MTSuctionGroup.RequirementBTUH],
['Capacity BTUH', sanitizedData.MTSuctionGroup.CapacityBTUH]
]
)
)
),
// Compressors
this.createSection('Compressors',
this.createTwoColumnSection(
this.createDataTable(
['Position', 'Model', 'HP', 'Capacity'],
sanitizedData.LTCompressors.map(comp => [
comp.Position,
comp.Model,
comp.HP,
comp.Capacity
])
),
this.createDataTable(
['Position', 'Model', 'HP', 'Capacity'],
sanitizedData.MTCompressors.map(comp => [
comp.Position,
comp.Model,
comp.HP,
comp.Capacity
])
)
)
),
// Condenser
this.createSection('Condenser',
this.createDataTable(
['Property', 'Value', 'Property', 'Value'],
[
['Manufacturer', sanitizedData.Condenser.Mfg, 'Model', sanitizedData.Condenser.Model],
['Capacity', sanitizedData.Condenser.Capacity, 'Voltage', sanitizedData.Condenser.Voltage],
['Fans', sanitizedData.Condenser.Fans, 'Amps', sanitizedData.Condenser.Amps]
]
)
)
],
styles: {
headerTitle: {
fontSize: 16,
bold: true,
color: '#333'
},
headerSubtitle: {
fontSize: 14,
color: '#555'
},
headerDetail: {
fontSize: 10,
color: '#777'
},
sectionTitle: {
fontSize: 14,
bold: true,
color: '#4054b2',
margin: [0, 0, 0, 10]
},
tableHeader: {
bold: true,
fontSize: 10,
color: '#ffffff',
fillColor: '#4054b2',
margin: [0, 3, 0, 3],
alignment: 'left'
},
footer: {
fontSize: 9,
color: '#999'
}
},
defaultStyle: {
font: 'Roboto',
fontSize: 10
}
};
}
public generatePDF(): void {
try {
const sanitizedData = this.deepSanitize(JSON.parse(JSON.stringify(this.data[0])));
const docDefinition = this.createPdfDefinition(sanitizedData);
pdfMake.createPdf(docDefinition).open();
} catch (error) {
console.error('PDF generation failed:', error);
// Error handling as before
}
}
}
Key improvements:
Perfect Header Alignment:
Structured Section Layout:
Improved Table Formatting:
Better Code Organization:
Visual Enhancements:
The PDF output will now have:
Here's the enhanced version with a more professional and visually appealing header design:
private createHeader(sanitizedData: any) {
return {
stack: [
// Blue background bar
{
canvas: [
{
type: 'rect',
x: 0, y: 0,
w: 595, h: 80,
color: '#1a5276'
}
]
},
// Header content
{
absolutePosition: { x: 40, y: 20 },
columns: [
// Logo
{
width: 60,
image: 'sampleImage',
fit: [50, 50],
alignment: 'left'
},
// Main title and info
{
width: '*',
stack: [
{
text: sanitizedData.JobName || 'N/A',
style: 'headerTitle',
margin: [15, 5, 0, 2]
},
{
text: sanitizedData.Customer || 'N/A',
style: 'headerSubtitle',
margin: [15, 0, 0, 2]
},
{
text: `Quote Line: ${sanitizedData.QuoteLineId || 'N/A'}`,
style: 'headerDetail',
margin: [15, 0, 0, 0]
}
]
},
// Right side info
{
width: 150,
stack: [
{
text: 'PROJECT DETAILS',
style: 'headerRightTitle',
margin: [0, 5, 0, 5]
},
{
text: `Rev: ${sanitizedData.MasterRevLevel || 'N/A'}`,
style: 'headerRightDetail',
margin: [0, 0, 0, 2]
},
{
text: `Last Edited: ${sanitizedData.LastEdited || 'N/A'}`,
style: 'headerRightDetail',
margin: [0, 0, 0, 0]
}
],
alignment: 'right'
}
]
}
],
margin: [0, -40, 0, 20]
};
}
// Update your styles to include:
styles: {
headerTitle: {
fontSize: 18,
bold: true,
color: 'white'
},
headerSubtitle: {
fontSize: 14,
color: '#e0e0e0'
},
headerDetail: {
fontSize: 10,
color: '#e0e0e0'
},
headerRightTitle: {
fontSize: 11,
bold: true,
color: 'white',
decoration: 'underline'
},
headerRightDetail: {
fontSize: 10,
color: 'white'
},
// ... keep other styles the same
}
private createHeader(sanitizedData: any) {
return {
stack: [
// Blue background bar
{
canvas: [
{
type: 'rect',
x: 0, y: 0,
w: 595, h: 80,
color: '#1a5276'
}
]
},
// Header content
{
absolutePosition: { x: 40, y: 20 },
columns: [
// Logo
{
width: 60,
image: 'sampleImage',
fit: [50, 50],
alignment: 'left'
},
// Main title and info
{
width: '*',
stack: [
{
text: sanitizedData.JobName || 'N/A',
style: 'headerTitle',
margin: [15, 5, 0, 2]
},
{
text: sanitizedData.Customer || 'N/A',
style: 'headerSubtitle',
margin: [15, 0, 0, 2]
},
{
text: `Quote Line: ${sanitizedData.QuoteLineId || 'N/A'}`,
style: 'headerDetail',
margin: [15, 0, 0, 0]
}
]
},
// Right side info
{
width: 150,
stack: [
{
text: 'PROJECT DETAILS',
style: 'headerRightTitle',
margin: [0, 5, 0, 5]
},
{
text: `Rev: ${sanitizedData.MasterRevLevel || 'N/A'}`,
style: 'headerRightDetail',
margin: [0, 0, 0, 2]
},
{
text: `Last Edited: ${sanitizedData.LastEdited || 'N/A'}`,
style: 'headerRightDetail',
margin: [0, 0, 0, 0]
}
],
alignment: 'right'
}
]
}
],
margin: [0, -40, 0, 20]
};
}
// Update your styles to include:
styles: {
headerTitle: {
fontSize: 18,
bold: true,
color: 'white'
},
headerSubtitle: {
fontSize: 14,
color: '#e0e0e0'
},
headerDetail: {
fontSize: 10,
color: '#e0e0e0'
},
headerRightTitle: {
fontSize: 11,
bold: true,
color: 'white',
decoration: 'underline'
},
headerRightDetail: {
fontSize: 10,
color: 'white'
},
// ... keep other styles the same
}
This header design features:
Full-Width Blue Bar Background:
Three Distinct Sections:
Improved Typography:
Perfect Alignment:
Visual Enhancements:
The header will now stand out as a professional, well-organized section that clearly presents all key information at the top of your PDF document. The dark blue background with white text creates high contrast for excellent readability while maintaining a polished, corporate appearance.*
Here's the enhanced header with perfect alignment for all key fields:
private createHeader(sanitizedData: any) {
return {
stack: [
// Full-width blue background bar
{
canvas: [
{
type: 'rect',
x: 0, y: 0,
w: 595, h: 100, // Increased height to accommodate more info
color: '#1a5276'
}
]
},
// Header content with perfect alignment
{
absolutePosition: { x: 40, y: 20 },
columns: [
// Logo column
{
width: 60,
image: 'sampleImage',
fit: [50, 50],
alignment: 'left'
},
// Main info column (now with perfect grid alignment)
{
width: '*',
table: {
widths: ['30%', '70%'],
body: [
[
{ text: 'Job Name:', style: 'headerLabel' },
{ text: sanitizedData.JobName || 'N/A', style: 'headerValue' }
],
[
{ text: 'Customer:', style: 'headerLabel' },
{ text: sanitizedData.Customer || 'N/A', style: 'headerValue' }
],
[
{ text: 'Name & ID:', style: 'headerLabel' },
{ text: sanitizedData.NameAndID || 'N/A', style: 'headerValue' }
],
[
{ text: 'ISE:', style: 'headerLabel' },
{ text: sanitizedData.ISE || 'N/A', style: 'headerValue' }
]
]
},
layout: 'noBorders',
margin: [15, 0, 0, 0]
},
// Right-side metadata column
{
width: 150,
table: {
widths: ['100%'],
body: [
[{ text: 'PROJECT DETAILS', style: 'headerRightTitle' }],
[{ text: `Rev: ${sanitizedData.MasterRevLevel || 'N/A'}`, style: 'headerRightDetail' }],
[{ text: `Last Edited: ${sanitizedData.LastEdited || 'N/A'}`, style: 'headerRightDetail' }],
[{ text: `Quote Line: ${sanitizedData.QuoteLineId || 'N/A'}`, style: 'headerRightDetail' }]
]
},
layout: 'noBorders',
alignment: 'right'
}
]
}
],
margin: [0, -40, 0, 20]
};
}
// Updated styles for perfect alignment:
styles: {
headerLabel: {
fontSize: 10,
color: '#e0e0e0',
bold: true,
margin: [0, 2, 0, 2]
},
headerValue: {
fontSize: 10,
color: 'white',
margin: [0, 2, 0, 2]
},
headerRightTitle: {
fontSize: 11,
bold: true,
color: 'white',
decoration: 'underline',
margin: [0, 5, 0, 5],
alignment: 'right'
},
headerRightDetail: {
fontSize: 9,
color: 'white',
margin: [0, 2, 0, 2],
alignment: 'right'
},
// ... keep other styles the same
}
private createHeader(sanitizedData: any) {
return {
stack: [
// Full-width blue background bar
{
canvas: [
{
type: 'rect',
x: 0, y: 0,
w: 595, h: 100, // Increased height to accommodate more info
color: '#1a5276'
}
]
},
// Header content with perfect alignment
{
absolutePosition: { x: 40, y: 20 },
columns: [
// Logo column
{
width: 60,
image: 'sampleImage',
fit: [50, 50],
alignment: 'left'
},
// Main info column (now with perfect grid alignment)
{
width: '*',
table: {
widths: ['30%', '70%'],
body: [
[
{ text: 'Job Name:', style: 'headerLabel' },
{ text: sanitizedData.JobName || 'N/A', style: 'headerValue' }
],
[
{ text: 'Customer:', style: 'headerLabel' },
{ text: sanitizedData.Customer || 'N/A', style: 'headerValue' }
],
[
{ text: 'Name & ID:', style: 'headerLabel' },
{ text: sanitizedData.NameAndID || 'N/A', style: 'headerValue' }
],
[
{ text: 'ISE:', style: 'headerLabel' },
{ text: sanitizedData.ISE || 'N/A', style: 'headerValue' }
]
]
},
layout: 'noBorders',
margin: [15, 0, 0, 0]
},
// Right-side metadata column
{
width: 150,
table: {
widths: ['100%'],
body: [
[{ text: 'PROJECT DETAILS', style: 'headerRightTitle' }],
[{ text: `Rev: ${sanitizedData.MasterRevLevel || 'N/A'}`, style: 'headerRightDetail' }],
[{ text: `Last Edited: ${sanitizedData.LastEdited || 'N/A'}`, style: 'headerRightDetail' }],
[{ text: `Quote Line: ${sanitizedData.QuoteLineId || 'N/A'}`, style: 'headerRightDetail' }]
]
},
layout: 'noBorders',
alignment: 'right'
}
]
}
],
margin: [0, -40, 0, 20]
};
}
// Updated styles for perfect alignment:
styles: {
headerLabel: {
fontSize: 10,
color: '#e0e0e0',
bold: true,
margin: [0, 2, 0, 2]
},
headerValue: {
fontSize: 10,
color: 'white',
margin: [0, 2, 0, 2]
},
headerRightTitle: {
fontSize: 11,
bold: true,
color: 'white',
decoration: 'underline',
margin: [0, 5, 0, 5],
alignment: 'right'
},
headerRightDetail: {
fontSize: 9,
color: 'white',
margin: [0, 2, 0, 2],
alignment: 'right'
},
// ... keep other styles the same
}
Key improvements in this version:
Perfect Grid Alignment:
Complete Field Inclusion:
Enhanced Visual Hierarchy:
Professional Spacing:
Improved Readability:
This header design ensures all information is perfectly aligned and professionally presented, with clear visual distinction between labels and values while maintaining a cohesive look.*
Here's the enhanced code with a beautiful, professional header that includes all requested fields perfectly aligned:
private createHeader(sanitizedData: any) {
return {
stack: [
// Full-width dark blue background bar
{
canvas: [
{
type: 'rect',
x: 0, y: 0,
w: 595, h: 100,
color: '#1a5276'
}
]
},
// Header content with perfect alignment
{
absolutePosition: { x: 40, y: 20 },
columns: [
// Logo column
{
width: 60,
image: 'sampleImage',
fit: [50, 50],
alignment: 'left'
},
// Main info column with perfect grid alignment
{
width: '*',
table: {
widths: ['25%', '75%'],
body: [
[
{ text: 'JOB NAME:', style: 'headerLabel' },
{ text: sanitizedData.JobName || 'N/A', style: 'headerValue' }
],
[
{ text: 'CUSTOMER:', style: 'headerLabel' },
{ text: sanitizedData.Customer || 'N/A', style: 'headerValue' }
],
[
{ text: 'NAME & ID:', style: 'headerLabel' },
{ text: sanitizedData.NameAndID || 'N/A', style: 'headerValue' }
],
[
{ text: 'ISE:', style: 'headerLabel' },
{ text: sanitizedData.ISE || 'N/A', style: 'headerValue' }
]
]
},
layout: {
hLineWidth: () => 0,
vLineWidth: () => 0,
paddingTop: (i) => i === 0 ? 0 : 3,
paddingBottom: (i) => i === 3 ? 0 : 3
},
margin: [15, 0, 0, 0]
},
// Right-side metadata column
{
width: 180,
table: {
widths: ['100%'],
body: [
[{ text: 'PROJECT DETAILS', style: 'headerRightTitle' }],
[{
columns: [
{ text: 'REVISION:', width: '40%', style: 'headerRightLabel' },
{ text: sanitizedData.MasterRevLevel || 'N/A', style: 'headerRightValue' }
]
}],
[{
columns: [
{ text: 'LAST EDITED:', width: '40%', style: 'headerRightLabel' },
{ text: sanitizedData.LastEdited || 'N/A', style: 'headerRightValue' }
]
}],
[{
columns: [
{ text: 'QUOTE LINE:', width: '40%', style: 'headerRightLabel' },
{ text: sanitizedData.QuoteLineId || 'N/A', style: 'headerRightValue' }
]
}]
]
},
layout: {
hLineWidth: () => 0,
vLineWidth: () => 0,
paddingTop: (i) => i === 0 ? 0 : 3,
paddingBottom: (i) => i === 3 ? 0 : 3
},
margin: [0, 0, 0, 0]
}
]
}
],
margin: [0, -40, 0, 20]
};
}
// Updated styles for the header:
styles: {
headerLabel: {
fontSize: 10,
color: '#a8d8ea',
bold: true,
margin: [0, 2, 5, 2]
},
headerValue: {
fontSize: 10,
color: 'white',
bold: false,
margin: [0, 2, 0, 2]
},
headerRightTitle: {
fontSize: 11,
bold: true,
color: 'white',
margin: [0, 0, 0, 5],
decoration: 'underline'
},
headerRightLabel: {
fontSize: 9,
color: '#a8d8ea',
bold: true,
margin: [0, 2, 5, 2],
alignment: 'left'
},
headerRightValue: {
fontSize: 9,
color: 'white',
bold: false,
margin: [0, 2, 0, 2],
alignment: 'left'
},
// ... keep other styles the same
}
private createHeader(sanitizedData: any) {
return {
stack: [
// Full-width dark blue background bar
{
canvas: [
{
type: 'rect',
x: 0, y: 0,
w: 595, h: 100,
color: '#1a5276'
}
]
},
// Header content with perfect alignment
{
absolutePosition: { x: 40, y: 20 },
columns: [
// Logo column
{
width: 60,
image: 'sampleImage',
fit: [50, 50],
alignment: 'left'
},
// Main info column with perfect grid alignment
{
width: '*',
table: {
widths: ['25%', '75%'],
body: [
[
{ text: 'JOB NAME:', style: 'headerLabel' },
{ text: sanitizedData.JobName || 'N/A', style: 'headerValue' }
],
[
{ text: 'CUSTOMER:', style: 'headerLabel' },
{ text: sanitizedData.Customer || 'N/A', style: 'headerValue' }
],
[
{ text: 'NAME & ID:', style: 'headerLabel' },
{ text: sanitizedData.NameAndID || 'N/A', style: 'headerValue' }
],
[
{ text: 'ISE:', style: 'headerLabel' },
{ text: sanitizedData.ISE || 'N/A', style: 'headerValue' }
]
]
},
layout: {
hLineWidth: () => 0,
vLineWidth: () => 0,
paddingTop: (i) => i === 0 ? 0 : 3,
paddingBottom: (i) => i === 3 ? 0 : 3
},
margin: [15, 0, 0, 0]
},
// Right-side metadata column
{
width: 180,
table: {
widths: ['100%'],
body: [
[{ text: 'PROJECT DETAILS', style: 'headerRightTitle' }],
[{
columns: [
{ text: 'REVISION:', width: '40%', style: 'headerRightLabel' },
{ text: sanitizedData.MasterRevLevel || 'N/A', style: 'headerRightValue' }
]
}],
[{
columns: [
{ text: 'LAST EDITED:', width: '40%', style: 'headerRightLabel' },
{ text: sanitizedData.LastEdited || 'N/A', style: 'headerRightValue' }
]
}],
[{
columns: [
{ text: 'QUOTE LINE:', width: '40%', style: 'headerRightLabel' },
{ text: sanitizedData.QuoteLineId || 'N/A', style: 'headerRightValue' }
]
}]
]
},
layout: {
hLineWidth: () => 0,
vLineWidth: () => 0,
paddingTop: (i) => i === 0 ? 0 : 3,
paddingBottom: (i) => i === 3 ? 0 : 3
},
margin: [0, 0, 0, 0]
}
]
}
],
margin: [0, -40, 0, 20]
};
}
// Updated styles for the header:
styles: {
headerLabel: {
fontSize: 10,
color: '#a8d8ea',
bold: true,
margin: [0, 2, 5, 2]
},
headerValue: {
fontSize: 10,
color: 'white',
bold: false,
margin: [0, 2, 0, 2]
},
headerRightTitle: {
fontSize: 11,
bold: true,
color: 'white',
margin: [0, 0, 0, 5],
decoration: 'underline'
},
headerRightLabel: {
fontSize: 9,
color: '#a8d8ea',
bold: true,
margin: [0, 2, 5, 2],
alignment: 'left'
},
headerRightValue: {
fontSize: 9,
color: 'white',
bold: false,
margin: [0, 2, 0, 2],
alignment: 'left'
},
// ... keep other styles the same
}
Key improvements in this header design:
Perfectly Aligned Grid Layout:
Professional Color Scheme:
Complete Field Inclusion:
Enhanced Readability:
Visual Hierarchy:
Technical Improvements:
This header design presents all critical information in a clean, professional layout that's both visually appealing and highly functional. The color contrast ensures excellent readability while maintaining a corporate aesthetic.*