210 lines
6.2 KiB
TypeScript
210 lines
6.2 KiB
TypeScript
import ExcelJS from 'exceljs';
|
|
import type { DashboardReportPayload } from '../types/dashboardReport.ts';
|
|
import { buildPdfFilename, isoDataDates } from '../components/shared/PdfReportHeader.tsx';
|
|
import { countingTotals, feedInTotals, feedUseTotals } from './reportTotals.ts';
|
|
|
|
const dayRangeLabel = (upToDay: number): string => `Hari ke-0 s/d ke-${upToDay}`;
|
|
|
|
const accuracyText = (value: number | null | undefined): string =>
|
|
value == null || !Number.isFinite(value) ? 'N/A' : `${value.toFixed(1)}%`;
|
|
|
|
const numOrNa = (value: number | null | undefined): number | string =>
|
|
value == null || !Number.isFinite(value) ? 'N/A' : value;
|
|
|
|
function writeMetaRow(sheet: ExcelJS.Worksheet, snapshot: DashboardReportPayload): void {
|
|
sheet.addRow([
|
|
`Kandang: ${snapshot.kandangName}`,
|
|
`Siklus: ${snapshot.cycleId}`,
|
|
`Periode: ${dayRangeLabel(snapshot.upToDay)}`,
|
|
`Dibuat: ${snapshot.generatedAt}`,
|
|
]);
|
|
}
|
|
|
|
export async function buildReportWorkbook(
|
|
snapshot: DashboardReportPayload
|
|
): Promise<ExcelJS.Workbook> {
|
|
const workbook = new ExcelJS.Workbook();
|
|
workbook.creator = 'Dashboard CPSP';
|
|
workbook.created = snapshot.generatedAt ? new Date(snapshot.generatedAt) : new Date();
|
|
|
|
const counting = workbook.addWorksheet('Counting');
|
|
writeMetaRow(counting, snapshot);
|
|
counting.addRow([
|
|
'Tanggal',
|
|
'Umur (hari)',
|
|
'Stok awal',
|
|
'Mortalitas',
|
|
'Panen',
|
|
'Berat panen',
|
|
'Stok akhir',
|
|
'IoT',
|
|
'Akurasi %',
|
|
]);
|
|
for (const row of snapshot.counting || []) {
|
|
counting.addRow([
|
|
row.date,
|
|
row.day,
|
|
numOrNa(row.countBefore),
|
|
numOrNa(row.mortality),
|
|
numOrNa(row.harvest),
|
|
numOrNa(row.harvestWeight),
|
|
numOrNa(row.countAfter),
|
|
numOrNa(row.iotCount),
|
|
accuracyText(row.accuracy),
|
|
]);
|
|
}
|
|
const countingTotal = countingTotals(snapshot.counting || []);
|
|
if (countingTotal) {
|
|
counting.addRow([
|
|
countingTotal.label,
|
|
'—',
|
|
numOrNa(countingTotal.countBefore),
|
|
numOrNa(countingTotal.mortality),
|
|
numOrNa(countingTotal.harvest),
|
|
numOrNa(countingTotal.harvestWeight),
|
|
numOrNa(countingTotal.countAfter),
|
|
numOrNa(countingTotal.iotCount),
|
|
accuracyText(countingTotal.accuracy),
|
|
]);
|
|
}
|
|
|
|
const weight = workbook.addWorksheet('Weight');
|
|
writeMetaRow(weight, snapshot);
|
|
weight.addRow([
|
|
'Tanggal',
|
|
'Umur (hari)',
|
|
'Rata-rata manual',
|
|
'Rata-rata IoT',
|
|
'Keseragaman',
|
|
'Selisih',
|
|
'Akurasi %',
|
|
]);
|
|
for (const row of snapshot.weight || []) {
|
|
weight.addRow([
|
|
row.date,
|
|
row.day,
|
|
numOrNa(row.avgManual),
|
|
numOrNa(row.avgIot),
|
|
accuracyText(row.uniformity),
|
|
numOrNa(row.difference),
|
|
accuracyText(row.accuracy),
|
|
]);
|
|
}
|
|
|
|
const feedIn = workbook.addWorksheet('Feed In');
|
|
writeMetaRow(feedIn, snapshot);
|
|
feedIn.addRow(['Tanggal', 'Manual masuk', 'IoT masuk', 'Akurasi %']);
|
|
for (const row of snapshot.feedIn || []) {
|
|
feedIn.addRow([row.date, numOrNa(row.manualIn), numOrNa(row.iotIn), accuracyText(row.accuracy)]);
|
|
}
|
|
const feedInTotal = feedInTotals(snapshot.feedIn || []);
|
|
if (feedInTotal) {
|
|
feedIn.addRow([
|
|
feedInTotal.label,
|
|
numOrNa(feedInTotal.manualIn),
|
|
numOrNa(feedInTotal.iotIn),
|
|
accuracyText(feedInTotal.accuracy),
|
|
]);
|
|
}
|
|
|
|
const feedUse = workbook.addWorksheet('Feed Use');
|
|
writeMetaRow(feedUse, snapshot);
|
|
feedUse.addRow(['Tanggal', 'Umur (hari)', 'Manual dituang', 'IoT dituang', 'Akurasi %']);
|
|
for (const row of snapshot.feedUse || []) {
|
|
feedUse.addRow([
|
|
row.date,
|
|
row.day,
|
|
numOrNa(row.manualUse),
|
|
numOrNa(row.iotUse),
|
|
accuracyText(row.accuracy),
|
|
]);
|
|
}
|
|
const feedUseTotal = feedUseTotals(snapshot.feedUse || []);
|
|
if (feedUseTotal) {
|
|
feedUse.addRow([
|
|
feedUseTotal.label,
|
|
'—',
|
|
numOrNa(feedUseTotal.manualUse),
|
|
numOrNa(feedUseTotal.iotUse),
|
|
accuracyText(feedUseTotal.accuracy),
|
|
]);
|
|
}
|
|
|
|
if (snapshot.reportType === 'end_cycle') {
|
|
const harvestKg = (grams: number | null | undefined): number | string =>
|
|
grams == null || !Number.isFinite(grams) ? 'N/A' : grams / 1000;
|
|
|
|
const fcr = workbook.addWorksheet('FCR');
|
|
writeMetaRow(fcr, snapshot);
|
|
fcr.addRow(['Umur', 'Total Pakan', 'Bobot IoT', 'Ayam Hidup', 'Total Bobot Panen', 'FCR']);
|
|
for (const row of snapshot.fcr || []) {
|
|
fcr.addRow([
|
|
row.day,
|
|
numOrNa(row.feedTotal),
|
|
numOrNa(row.iotWeight),
|
|
numOrNa(row.chickenLife),
|
|
harvestKg(row.harvestWeightTotal),
|
|
numOrNa(row.iotFcr),
|
|
]);
|
|
}
|
|
|
|
const eef = workbook.addWorksheet('EEF');
|
|
writeMetaRow(eef, snapshot);
|
|
eef.addRow([
|
|
'Umur',
|
|
'Persentase Ayam Hidup',
|
|
'BW Rata-rata Panen',
|
|
'Umur Rata-rata Panen',
|
|
'FCR',
|
|
'EEF',
|
|
]);
|
|
for (const row of snapshot.eef || []) {
|
|
eef.addRow([
|
|
row.day,
|
|
numOrNa(row.chickenLifePercentage),
|
|
numOrNa(row.iotAverageHarvestBw),
|
|
numOrNa(row.iotAverageDayWeight),
|
|
numOrNa(row.iotFcr),
|
|
numOrNa(row.iotEef),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return workbook;
|
|
}
|
|
|
|
export function reportExcelFilename(
|
|
snapshot: DashboardReportPayload,
|
|
cycleStartDate?: Date | string | null
|
|
): string {
|
|
const isEndCycle = snapshot.reportType === 'end_cycle';
|
|
const pdfName = buildPdfFilename({
|
|
prefix: 'Laporan',
|
|
reportName: isEndCycle ? 'Akhir-Siklus' : 'Harian',
|
|
area: snapshot.area,
|
|
kandangLabel: snapshot.kandangName,
|
|
periodSlug: isEndCycle ? 'Akhir-Siklus' : `Harian-Hari-${snapshot.upToDay}`,
|
|
isoDates: isoDataDates(cycleStartDate, 0, snapshot.upToDay),
|
|
});
|
|
return pdfName.replace(/\.pdf$/i, '.xlsx');
|
|
}
|
|
|
|
export async function downloadReportExcel(
|
|
snapshot: DashboardReportPayload,
|
|
cycleStartDate?: Date | string | null
|
|
): Promise<void> {
|
|
const workbook = await buildReportWorkbook(snapshot);
|
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
const blob = new Blob([buffer], {
|
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
});
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = reportExcelFilename(snapshot, cycleStartDate);
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
link.remove();
|
|
URL.revokeObjectURL(url);
|
|
}
|