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); }