import { Logger } from '@jig-software/logger'; import { Controller, Inject } from '@jig-software/sdk'; import ExcelJS from 'exceljs'; import path from 'path'; import { readingReportContract } from '../shared'; import { createFileOutput } from '@jig-software/trest-core'; import fs from 'fs'; interface ReadingStats { min?: string; max?: string; avg?: string; } interface DeviceData { building: string; floor: string; device_name: string; min_max_avg: Record; readings: Record>; } interface ReportParams { start_date?: string; end_date?: string; time_step?: string; } export class ReadingReportController extends Controller { @Inject() protected logger!: Logger; constructor() { super(); this.logger.setTag(this.constructor.name); this.implement(readingReportContract, (r) => r.handlers({ generateReadingsReport: { handler: async (FullReportSchema) => { const data: DeviceData[] = FullReportSchema.data; const reportParams: ReportParams = FullReportSchema.report_params; const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('Event Records'); // ---------------- Logos ---------------- const jecLogoId = workbook.addImage({ filename: path.resolve('asset/jec_logo.jpg'), extension: 'jpeg', }); const fastLogoId = workbook.addImage({ filename: path.resolve('asset/fast_logo.jpg'), extension: 'jpeg', }); worksheet.addImage(jecLogoId, { tl: { col: 0, row: 0 }, ext: { width: 120, height: 60 }, }); worksheet.addImage(fastLogoId, { tl: { col: 7, row: 0 }, ext: { width: 120, height: 60 }, }); // ---------------- Title ---------------- worksheet.mergeCells('B1:G2'); const titleCell = worksheet.getCell('B1'); titleCell.value = 'Event Report'; titleCell.alignment = { horizontal: 'center', vertical: 'middle' }; titleCell.font = { bold: true, size: 18 }; // ---------------- Fixed params ---------------- const fixedParams = [ ['Start Date:', reportParams.start_date ?? ''], ['End Date:', reportParams.end_date ?? ''], ['Time Step:', reportParams.time_step ?? ''], ]; fixedParams.forEach(([label, value], idx) => { worksheet.getCell(4 + idx, 2).value = label; worksheet.getCell(4 + idx, 3).value = value; }); // ---------------- Headers ---------------- const allDates = Array.from( new Set( data.flatMap((device) => Object.values(device.readings).flatMap((r) => Object.keys(r)), ), ), ).sort(); const headers = [ 'Building', 'Floor', 'Device Name', 'Reading Type', 'Minimum', 'Maximum', 'Average', ...allDates, ]; const headerRowIndex = 7; const headerRow = worksheet.getRow(headerRowIndex); headerRow.values = headers; headerRow.eachCell((cell) => { cell.font = { bold: true }; cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFD9D9D9' }, }; }); // ---------------- Data ---------------- let currentRow = headerRowIndex + 1; for (const device of data) { const types = Object.keys(device.readings); const startRow = currentRow; const endRow = currentRow + types.length - 1; if (types.length > 1) { worksheet.mergeCells(startRow, 1, endRow, 1); worksheet.mergeCells(startRow, 2, endRow, 2); worksheet.mergeCells(startRow, 3, endRow, 3); } worksheet.getCell(startRow, 1).value = device.building; worksheet.getCell(startRow, 2).value = device.floor; worksheet.getCell(startRow, 3).value = device.device_name; for (const type of types) { const row = worksheet.getRow(currentRow); row.getCell(4).value = type; const stats = device.min_max_avg?.[type] ?? {}; row.getCell(5).value = stats.min ?? '-'; row.getCell(6).value = stats.max ?? '-'; row.getCell(7).value = stats.avg ?? '-'; allDates.forEach((date, idx) => { row.getCell(8 + idx).value = device.readings[type]?.[date] ?? '-'; }); currentRow++; } console.log( `Processed device '${device.device_name}' rows ${startRow}–${endRow}`, ); } // ---------------- Auto-width ---------------- worksheet.columns.forEach((col) => { let max = 10; col.eachCell!({ includeEmpty: true }, (cell) => { const len = String(cell.value ?? '').length; max = Math.max(max, len); }); col.width = max + 2; }); worksheet.autoFilter = { from: { row: headerRowIndex, column: 1 }, to: { row: currentRow - 1, column: headers.length }, }; // ---------------- Save file ---------------- const outputPath = path.resolve('event_report.xlsx'); await workbook.xlsx.writeFile(outputPath); console.log(`Excel report generated: ${outputPath}`); const readStream = fs.createReadStream(outputPath); return createFileOutput(readStream, 'event_report.xlsx'); }, }, }), ); } }