568 lines
22 KiB
TypeScript
568 lines
22 KiB
TypeScript
import * as ExcelJS from 'exceljs';
|
|
import * as FileSaver from 'file-saver';
|
|
|
|
interface ReportData {
|
|
id: number;
|
|
createdDate: string;
|
|
shift: string;
|
|
area: { name: string };
|
|
dredgerLocation: { name: string };
|
|
dredgerLineLength: number;
|
|
reclamationLocation: { name: string };
|
|
shoreConnection: number;
|
|
reclamationHeight: { base: number; extra: number };
|
|
pipelineLength: { main: number; ext1: number; reserve: number; ext2: number };
|
|
stats: { Dozers: number; Exc: number; Loaders: number; Foreman: string; Laborer: number };
|
|
timeSheet: Array<{
|
|
machine: string;
|
|
from1: string;
|
|
to1: string;
|
|
from2: string;
|
|
to2: string;
|
|
total: string;
|
|
reason: string;
|
|
}>;
|
|
stoppages: Array<{
|
|
from: string;
|
|
to: string;
|
|
total: string;
|
|
reason: string;
|
|
responsible: string;
|
|
note: string;
|
|
}>;
|
|
notes: string;
|
|
}
|
|
|
|
export async function exportReportToExcel(report: ReportData) {
|
|
const workbook = new ExcelJS.Workbook();
|
|
const worksheet = workbook.addWorksheet('Report');
|
|
|
|
// Set column widths to match the professional layout from the reference file
|
|
worksheet.columns = [
|
|
{ width: 30 }, // A - Labels/Machine names
|
|
{ width: 20 }, // B - Data/Values
|
|
{ width: 20 }, // C - Labels/Data
|
|
{ width: 20 }, // D - Data/Values
|
|
{ width: 15 }, // E - Pipeline data
|
|
{ width: 15 }, // F - Pipeline data
|
|
{ width: 25 } // G - Reason/Notes
|
|
];
|
|
|
|
let currentRow = 1;
|
|
|
|
// 1. HEADER SECTION - Professional layout matching reference file
|
|
// Main header with company info
|
|
worksheet.mergeCells(`A${currentRow}:E${currentRow + 2}`);
|
|
const headerCell = worksheet.getCell(`A${currentRow}`);
|
|
headerCell.value = 'Reclamation Work Diary';
|
|
headerCell.style = {
|
|
font: { name: 'Arial', size: 16, bold: true },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFFF' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
// Logo area
|
|
worksheet.mergeCells(`F${currentRow}:G${currentRow + 2}`);
|
|
const logoCell = worksheet.getCell(`F${currentRow}`);
|
|
logoCell.value = 'Arab Potash\nCompany Logo';
|
|
logoCell.style = {
|
|
font: { name: 'Arial', size: 12, bold: true },
|
|
alignment: { horizontal: 'center', vertical: 'middle', wrapText: true },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFFF' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
// Sub-header info
|
|
const qfCell = worksheet.getCell(`A${currentRow + 1}`);
|
|
qfCell.value = 'QF-3.6.1-08';
|
|
qfCell.style = {
|
|
font: { name: 'Arial', size: 10 },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
border: {
|
|
top: { style: 'thin', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thin', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
const revCell = worksheet.getCell(`A${currentRow + 2}`);
|
|
revCell.value = 'Rev. 1.0';
|
|
revCell.style = {
|
|
font: { name: 'Arial', size: 10 },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
border: {
|
|
top: { style: 'thin', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
currentRow += 4; // Skip to next section
|
|
|
|
// 2. REPORT INFO SECTION - Professional table layout
|
|
const infoRowCells = [
|
|
{ col: 'A', label: 'Date:', value: new Date(report.createdDate).toLocaleDateString('en-GB') },
|
|
{ col: 'C', label: 'Report No.', value: report.id.toString() }
|
|
];
|
|
|
|
// Create bordered info section
|
|
['A', 'B', 'C', 'D', 'E', 'F', 'G'].forEach(col => {
|
|
const cell = worksheet.getCell(`${col}${currentRow}`);
|
|
cell.style = {
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
});
|
|
|
|
const dateCell = worksheet.getCell(`A${currentRow}`);
|
|
dateCell.value = 'Date:';
|
|
dateCell.style = {
|
|
font: { name: 'Arial', size: 11, bold: true },
|
|
alignment: { horizontal: 'left', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'F0F0F0' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
const dateValueCell = worksheet.getCell(`B${currentRow}`);
|
|
dateValueCell.value = new Date(report.createdDate).toLocaleDateString('en-GB');
|
|
dateValueCell.style = {
|
|
font: { name: 'Arial', size: 11 },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
const reportNoCell = worksheet.getCell(`E${currentRow}`);
|
|
reportNoCell.value = 'Report No.';
|
|
reportNoCell.style = {
|
|
font: { name: 'Arial', size: 11, bold: true },
|
|
alignment: { horizontal: 'left', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'F0F0F0' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
const reportNoValueCell = worksheet.getCell(`F${currentRow}`);
|
|
reportNoValueCell.value = report.id.toString();
|
|
reportNoValueCell.style = {
|
|
font: { name: 'Arial', size: 11 },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
currentRow += 2; // Skip empty row
|
|
|
|
// 3. DREDGER SECTION - Professional centered title
|
|
worksheet.mergeCells(`A${currentRow}:G${currentRow}`);
|
|
const dredgerCell = worksheet.getCell(`A${currentRow}`);
|
|
dredgerCell.value = `${report.area.name} Dredger`;
|
|
dredgerCell.style = {
|
|
font: { name: 'Arial', size: 18, bold: true, underline: true },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'E6F3FF' } }
|
|
};
|
|
|
|
currentRow += 2; // Skip empty row
|
|
|
|
// 4. LOCATION DATA SECTION - Professional table with green headers
|
|
const locationRows = [
|
|
['Dredger Location', report.dredgerLocation.name, '', 'Dredger Line Length', report.dredgerLineLength.toString()],
|
|
['Reclamation Location', report.reclamationLocation.name, '', 'Shore Connection', report.shoreConnection.toString()],
|
|
['Reclamation Height', `${report.reclamationHeight?.base || 0}m - ${(report.reclamationHeight?.base || 0) + (report.reclamationHeight?.extra || 0)}m`, '', '', '']
|
|
];
|
|
|
|
locationRows.forEach((rowData, index) => {
|
|
const row = currentRow + index;
|
|
|
|
// Apply styling to all cells in the row first
|
|
for (let col = 1; col <= 7; col++) {
|
|
const cell = worksheet.getCell(row, col);
|
|
cell.style = {
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
}
|
|
|
|
rowData.forEach((cellValue, colIndex) => {
|
|
if (cellValue !== '') {
|
|
const cell = worksheet.getCell(row, colIndex + 1);
|
|
cell.value = cellValue;
|
|
|
|
const isGreenHeader = (colIndex === 0 || colIndex === 3);
|
|
cell.style = {
|
|
font: { name: 'Arial', size: 11, bold: isGreenHeader, color: isGreenHeader ? { argb: 'FFFFFF' } : { argb: '000000' } },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: isGreenHeader ? { type: 'pattern', pattern: 'solid', fgColor: { argb: '70AD47' } } : { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFFF' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
}
|
|
});
|
|
|
|
// Merge cells for better layout
|
|
if (index === 0) {
|
|
worksheet.mergeCells(`B${row}:C${row}`); // Dredger Location value
|
|
worksheet.mergeCells(`E${row}:G${row}`); // Dredger Line Length value
|
|
} else if (index === 1) {
|
|
worksheet.mergeCells(`B${row}:C${row}`); // Reclamation Location value
|
|
worksheet.mergeCells(`E${row}:G${row}`); // Shore Connection value
|
|
} else if (index === 2) {
|
|
worksheet.mergeCells(`B${row}:G${row}`); // Reclamation Height spans all remaining columns
|
|
}
|
|
});
|
|
|
|
currentRow += 4; // Skip empty row
|
|
|
|
// 5. PIPELINE LENGTH SECTION - Professional table with green headers
|
|
const pipelineHeaderRow = currentRow;
|
|
|
|
// First row - main header with rowspan
|
|
const mainHeaderCell = worksheet.getCell(pipelineHeaderRow, 1);
|
|
mainHeaderCell.value = 'Pipeline Length "from Shore Connection"';
|
|
mainHeaderCell.style = {
|
|
font: { name: 'Arial', size: 11, bold: true, color: { argb: 'FFFFFF' } },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: '70AD47' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
// Sub-headers
|
|
const pipelineSubHeaders = ['Main', 'extension', 'total', 'Reserve', 'extension', 'total'];
|
|
pipelineSubHeaders.forEach((header, colIndex) => {
|
|
const cell = worksheet.getCell(pipelineHeaderRow, colIndex + 2);
|
|
cell.value = header;
|
|
cell.style = {
|
|
font: { name: 'Arial', size: 10, bold: true, color: { argb: 'FFFFFF' } },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: '70AD47' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
});
|
|
|
|
// Data row
|
|
const pipelineDataRow = currentRow + 1;
|
|
const pipelineData = ['',
|
|
(report.pipelineLength?.main || 0).toString(),
|
|
(report.pipelineLength?.ext1 || 0).toString(),
|
|
((report.pipelineLength?.main || 0) + (report.pipelineLength?.ext1 || 0)).toString(),
|
|
(report.pipelineLength?.reserve || 0).toString(),
|
|
(report.pipelineLength?.ext2 || 0).toString(),
|
|
((report.pipelineLength?.reserve || 0) + (report.pipelineLength?.ext2 || 0)).toString()
|
|
];
|
|
|
|
pipelineData.forEach((data, colIndex) => {
|
|
const cell = worksheet.getCell(pipelineDataRow, colIndex + 1);
|
|
cell.value = data;
|
|
cell.style = {
|
|
font: { name: 'Arial', size: 11 },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFFF' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
});
|
|
|
|
currentRow += 4; // Skip empty row
|
|
|
|
// 6. SHIFT HEADER SECTION - Professional full-width header
|
|
worksheet.mergeCells(`A${currentRow}:G${currentRow}`);
|
|
const shiftCell = worksheet.getCell(`A${currentRow}`);
|
|
shiftCell.value = `${report.shift.charAt(0).toUpperCase() + report.shift.slice(1)} Shift`;
|
|
shiftCell.style = {
|
|
font: { name: 'Arial', size: 14, bold: true, color: { argb: 'FFFFFF' } },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: '70AD47' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
currentRow += 2; // Skip empty row
|
|
|
|
// 7. EQUIPMENT STATS SECTION - Professional table with green headers
|
|
const equipmentHeaders = ['Dozers', 'Exc.', 'Loader', 'Foreman', 'Laborer'];
|
|
|
|
// Apply borders to all cells in the equipment section
|
|
for (let col = 1; col <= 7; col++) {
|
|
for (let row = currentRow; row <= currentRow + 1; row++) {
|
|
const cell = worksheet.getCell(row, col);
|
|
cell.style = {
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
equipmentHeaders.forEach((header, colIndex) => {
|
|
const cell = worksheet.getCell(currentRow, colIndex + 1);
|
|
cell.value = header;
|
|
cell.style = {
|
|
font: { name: 'Arial', size: 11, bold: true, color: { argb: 'FFFFFF' } },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: '70AD47' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
});
|
|
|
|
const equipmentData = [
|
|
(report.stats?.Dozers || 0).toString(),
|
|
(report.stats?.Exc || 0).toString(),
|
|
(report.stats?.Loaders || 0).toString(),
|
|
report.stats?.Foreman || '',
|
|
(report.stats?.Laborer || 0).toString()
|
|
];
|
|
|
|
equipmentData.forEach((data, colIndex) => {
|
|
const cell = worksheet.getCell(currentRow + 1, colIndex + 1);
|
|
cell.value = data;
|
|
cell.style = {
|
|
font: { name: 'Arial', size: 11 },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFFF' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
});
|
|
|
|
currentRow += 4; // Skip empty row
|
|
|
|
// 8. TIME SHEET SECTION - Professional table
|
|
const createProfessionalTable = (headers: string[], data: any[][], startRow: number) => {
|
|
// Headers
|
|
headers.forEach((header, colIndex) => {
|
|
const cell = worksheet.getCell(startRow, colIndex + 1);
|
|
cell.value = header;
|
|
cell.style = {
|
|
font: { name: 'Arial', size: 11, bold: true, color: { argb: 'FFFFFF' } },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: '70AD47' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
});
|
|
|
|
// Data rows
|
|
data.forEach((rowData, rowIndex) => {
|
|
const row = startRow + rowIndex + 1;
|
|
rowData.forEach((cellData, colIndex) => {
|
|
const cell = worksheet.getCell(row, colIndex + 1);
|
|
cell.value = cellData;
|
|
cell.style = {
|
|
font: { name: 'Arial', size: 10, bold: colIndex === 0 },
|
|
alignment: { horizontal: colIndex === 0 ? 'left' : 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFFF' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
});
|
|
});
|
|
|
|
return startRow + data.length + 1;
|
|
};
|
|
|
|
const timeSheetHeaders = ['Time Sheet', 'From', 'To', 'From', 'To', 'Total', 'Reason'];
|
|
const timeSheetData = Array.isArray(report.timeSheet) && report.timeSheet.length > 0
|
|
? report.timeSheet.map(entry => [entry.machine, entry.from1, entry.to1, entry.from2, entry.to2, entry.total, entry.reason])
|
|
: [['No time sheet entries', '', '', '', '', '', '']];
|
|
|
|
currentRow = createProfessionalTable(timeSheetHeaders, timeSheetData, currentRow);
|
|
|
|
currentRow += 2; // Skip empty row
|
|
|
|
// 9. STOPPAGES SECTION - Professional section with header
|
|
worksheet.mergeCells(`A${currentRow}:G${currentRow}`);
|
|
const stoppagesHeaderCell = worksheet.getCell(`A${currentRow}`);
|
|
stoppagesHeaderCell.value = 'Dredger Stoppages';
|
|
stoppagesHeaderCell.style = {
|
|
font: { name: 'Arial', size: 14, bold: true, color: { argb: 'FFFFFF' } },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: '70AD47' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
currentRow++;
|
|
|
|
const stoppagesHeaders = ['From', 'To', 'Total', 'Reason', 'Responsible', 'Notes', ''];
|
|
const stoppagesData = Array.isArray(report.stoppages) && report.stoppages.length > 0
|
|
? report.stoppages.map(entry => [entry.from, entry.to, entry.total, entry.reason, entry.responsible, entry.note, ''])
|
|
: [['No stoppages recorded', '', '', '', '', '', '']];
|
|
|
|
currentRow = createProfessionalTable(stoppagesHeaders, stoppagesData, currentRow);
|
|
|
|
currentRow += 2; // Skip empty row
|
|
|
|
// 10. NOTES SECTION - Professional notes section
|
|
worksheet.mergeCells(`A${currentRow}:G${currentRow}`);
|
|
const notesHeaderCell = worksheet.getCell(`A${currentRow}`);
|
|
notesHeaderCell.value = 'Notes & Comments';
|
|
notesHeaderCell.style = {
|
|
font: { name: 'Arial', size: 14, bold: true, color: { argb: 'FFFFFF' } },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: '70AD47' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
currentRow++;
|
|
|
|
worksheet.mergeCells(`A${currentRow}:G${currentRow + 3}`);
|
|
const notesContentCell = worksheet.getCell(`A${currentRow}`);
|
|
notesContentCell.value = report.notes || 'No additional notes';
|
|
notesContentCell.style = {
|
|
font: { name: 'Arial', size: 11 },
|
|
alignment: { horizontal: 'left', vertical: 'top', wrapText: true },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFFFF' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
currentRow += 6; // Skip to footer
|
|
|
|
// 11. FOOTER SECTION - Professional footer
|
|
worksheet.mergeCells(`A${currentRow}:G${currentRow}`);
|
|
const footerCell = worksheet.getCell(`A${currentRow}`);
|
|
footerCell.value = 'موقعة لأعمال الصيانة';
|
|
footerCell.style = {
|
|
font: { name: 'Arial', size: 12, bold: true },
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'E6F3FF' } },
|
|
border: {
|
|
top: { style: 'thick', color: { argb: '000000' } },
|
|
left: { style: 'thick', color: { argb: '000000' } },
|
|
bottom: { style: 'thick', color: { argb: '000000' } },
|
|
right: { style: 'thick', color: { argb: '000000' } }
|
|
}
|
|
};
|
|
|
|
// Set row heights for professional appearance
|
|
worksheet.eachRow((row, rowNumber) => {
|
|
if (rowNumber <= 3) {
|
|
row.height = 25; // Header rows
|
|
} else if (row.getCell(1).value && typeof row.getCell(1).value === 'string' &&
|
|
(row.getCell(1).value.includes('Shift') ||
|
|
row.getCell(1).value.includes('Stoppages') ||
|
|
row.getCell(1).value.includes('Notes'))) {
|
|
row.height = 22; // Section headers
|
|
} else {
|
|
row.height = 18; // Standard rows
|
|
}
|
|
});
|
|
|
|
// Set print settings for professional output
|
|
worksheet.pageSetup = {
|
|
paperSize: 9, // A4
|
|
orientation: 'landscape',
|
|
fitToPage: true,
|
|
fitToWidth: 1,
|
|
fitToHeight: 0,
|
|
margins: {
|
|
left: 0.7,
|
|
right: 0.7,
|
|
top: 0.75,
|
|
bottom: 0.75,
|
|
header: 0.3,
|
|
footer: 0.3
|
|
}
|
|
};
|
|
|
|
// Generate and save file
|
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
|
|
|
const fileName = `Report_${report.id}_${new Date(report.createdDate).toLocaleDateString('en-GB').replace(/\//g, '-')}.xlsx`;
|
|
FileSaver.saveAs(blob, fileName);
|
|
} |