This commit is contained in:
parent
9ecd725e04
commit
effe22c4b5
Binary file not shown.
@ -20,6 +20,17 @@ interface DeviceData {
|
|||||||
readings: Record<string, Record<string, string>>;
|
readings: Record<string, Record<string, string>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EventData {
|
||||||
|
building: string;
|
||||||
|
floor: string;
|
||||||
|
device_name: string;
|
||||||
|
event_time: string;
|
||||||
|
event_type: string;
|
||||||
|
temperature: string;
|
||||||
|
humidity: string;
|
||||||
|
battery: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface ReportParams {
|
interface ReportParams {
|
||||||
start_date?: string;
|
start_date?: string;
|
||||||
end_date?: string;
|
end_date?: string;
|
||||||
@ -65,7 +76,7 @@ export class ReadingReportController extends Controller {
|
|||||||
// ---------------- Title ----------------
|
// ---------------- Title ----------------
|
||||||
worksheet.mergeCells('B1:G2');
|
worksheet.mergeCells('B1:G2');
|
||||||
const titleCell = worksheet.getCell('B1');
|
const titleCell = worksheet.getCell('B1');
|
||||||
titleCell.value = 'Event Report';
|
titleCell.value = 'Reading Report';
|
||||||
titleCell.alignment = { horizontal: 'center', vertical: 'middle' };
|
titleCell.alignment = { horizontal: 'center', vertical: 'middle' };
|
||||||
titleCell.font = { bold: true, size: 18 };
|
titleCell.font = { bold: true, size: 18 };
|
||||||
|
|
||||||
@ -169,10 +180,10 @@ export class ReadingReportController extends Controller {
|
|||||||
col.width = max + 2;
|
col.width = max + 2;
|
||||||
});
|
});
|
||||||
|
|
||||||
worksheet.autoFilter = {
|
// worksheet.autoFilter = {
|
||||||
from: { row: headerRowIndex, column: 1 },
|
// from: { row: headerRowIndex, column: 1 },
|
||||||
to: { row: currentRow - 1, column: headers.length },
|
// to: { row: currentRow - 1, column: headers.length },
|
||||||
};
|
// };
|
||||||
|
|
||||||
// ---------------- Save file ----------------
|
// ---------------- Save file ----------------
|
||||||
const outputPath = path.resolve('event_report.xlsx');
|
const outputPath = path.resolve('event_report.xlsx');
|
||||||
@ -183,6 +194,106 @@ export class ReadingReportController extends Controller {
|
|||||||
return createFileOutput(readStream, 'event_report.xlsx');
|
return createFileOutput(readStream, 'event_report.xlsx');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
generateEventsReport: {
|
||||||
|
handler: async (FullEventSchema) => {
|
||||||
|
const { data, report_params } = FullEventSchema;
|
||||||
|
|
||||||
|
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: 120 },
|
||||||
|
});
|
||||||
|
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 ----------------
|
||||||
|
worksheet.getCell(4, 2).value = 'Start Date:';
|
||||||
|
worksheet.getCell(4, 3).value = report_params.start_date;
|
||||||
|
worksheet.getCell(5, 2).value = 'End Date:';
|
||||||
|
worksheet.getCell(5, 3).value = report_params.end_date;
|
||||||
|
|
||||||
|
// ---------------- Headers ----------------
|
||||||
|
const headers = [
|
||||||
|
'Building',
|
||||||
|
'Floor',
|
||||||
|
'Device Name',
|
||||||
|
'Event Time',
|
||||||
|
'Event Type',
|
||||||
|
'Temperature',
|
||||||
|
'Humidity',
|
||||||
|
'Battery',
|
||||||
|
];
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
data.forEach((event) => {
|
||||||
|
const row = worksheet.getRow(currentRow);
|
||||||
|
row.getCell(1).value = event.building;
|
||||||
|
row.getCell(2).value = event.floor;
|
||||||
|
row.getCell(3).value = event.device_name;
|
||||||
|
row.getCell(4).value = event.event_time;
|
||||||
|
row.getCell(5).value = event.event_type;
|
||||||
|
row.getCell(6).value = event.temperature;
|
||||||
|
row.getCell(7).value = event.humidity;
|
||||||
|
row.getCell(8).value = event.battery;
|
||||||
|
|
||||||
|
currentRow++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------------- 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;
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------------- Save file ----------------
|
||||||
|
const outputPath = path.resolve('event_report.xlsx');
|
||||||
|
await workbook.xlsx.writeFile(outputPath);
|
||||||
|
console.log(`Excel report generated: ${outputPath}`);
|
||||||
|
|
||||||
|
// ---------------- Return as stream ----------------
|
||||||
|
const readStream = fs.createReadStream(outputPath);
|
||||||
|
return createFileOutput(readStream, 'event_report.xlsx'); // your helper
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,3 +51,30 @@ export const FullReportInput = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export type FullReportInput = z.infer<typeof FullReportInput>;
|
export type FullReportInput = z.infer<typeof FullReportInput>;
|
||||||
|
|
||||||
|
export const FullEventInput = z.object({
|
||||||
|
data: z.array(
|
||||||
|
z.object({
|
||||||
|
building: z.string(),
|
||||||
|
floor: z.string(),
|
||||||
|
device_name: z.string(),
|
||||||
|
event_time: z.string().refine((val) => !isNaN(Date.parse(val)), {
|
||||||
|
message: 'Invalid event_time',
|
||||||
|
}),
|
||||||
|
event_type: z.string(),
|
||||||
|
temperature: z.string(),
|
||||||
|
humidity: z.string(),
|
||||||
|
battery: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
report_params: z.object({
|
||||||
|
start_date: z.string().refine((val) => !isNaN(Date.parse(val)), {
|
||||||
|
message: 'Invalid start_date',
|
||||||
|
}),
|
||||||
|
end_date: z.string().refine((val) => !isNaN(Date.parse(val)), {
|
||||||
|
message: 'Invalid end_date',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FullEventInput = z.infer<typeof FullEventInput>;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { A, buildContract } from '@jig-software/trest-core';
|
import { A, buildContract } from '@jig-software/trest-core';
|
||||||
import { FullReportInput } from '../constants';
|
import { FullReportInput, FullEventInput } from '../constants';
|
||||||
|
|
||||||
export const readingReportContract = buildContract((c) =>
|
export const readingReportContract = buildContract((c) =>
|
||||||
c
|
c
|
||||||
@ -12,5 +12,11 @@ export const readingReportContract = buildContract((c) =>
|
|||||||
.path('/reading-report')
|
.path('/reading-report')
|
||||||
.prepare((p) => p.body.json(FullReportInput).annotate<[query: A]>())
|
.prepare((p) => p.body.json(FullReportInput).annotate<[query: A]>())
|
||||||
.parse((p) => p.success.file(200)),
|
.parse((p) => p.success.file(200)),
|
||||||
|
generateEventsReport: (e) =>
|
||||||
|
e
|
||||||
|
.method('POST')
|
||||||
|
.path('/event-report')
|
||||||
|
.prepare((p) => p.body.json(FullEventInput).annotate<[query: A]>())
|
||||||
|
.parse((p) => p.success.file(200)),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user