refactor(FE): Refactor HppPerKandang export logic to use ExcelJS

This commit is contained in:
rstubryan
2026-02-10 11:54:15 +07:00
parent def894e5f4
commit d0dea834c1
3 changed files with 142 additions and 125 deletions
@@ -0,0 +1,135 @@
'use client';
import ExcelJS from 'exceljs';
import { formatCurrency, formatNumber } from '@/lib/helper';
import {
HppPerKandangReport,
HppPerKandangRow,
HppPerKandangPerWeightRange,
} from '@/types/api/report/hpp-per-kandang';
interface HppPerKandangExportExcelParams {
data: HppPerKandangReport;
allFeedSuppliers: string;
allDocSuppliers: string;
}
const formatSuppliers = (
suppliers: { alias?: string; name: string }[] | null
): string => {
if (!suppliers || suppliers.length === 0) return '';
return suppliers.map((s) => s.alias || s.name).join(' | ');
};
export const generateHppPerKandangExcel = async (
params: HppPerKandangExportExcelParams
): Promise<void> => {
if (!params.data || !params.data.rows || params.data.rows.length === 0) {
return;
}
const workbook = new ExcelJS.Workbook();
// ===== REKAPITULASI WORKSHEET =====
const rekapitulasiColumns = [
{ header: 'No', key: 'no', width: 5 },
{ header: 'Rentang BW', key: 'weightRange', width: 15 },
{ header: 'Sisa Butir', key: 'eggPieces', width: 15 },
{ header: 'Sisa Kg', key: 'eggKg', width: 12 },
{ header: 'Rata-Rata Bobot (Kg)', key: 'avgWeight', width: 18 },
{ header: 'Feed (Supplier)', key: 'feedSuppliers', width: 20 },
{ header: 'DOC (Supplier)', key: 'docSuppliers', width: 20 },
{ header: 'Rata-Rata Harga DOC', key: 'avgDocPrice', width: 20 },
{ header: 'HPP Telur (Rp/Kg)', key: 'eggHpp', width: 18 },
{ header: 'Nominal Sisa', key: 'eggValue', width: 25 },
];
const rekapitulasiWorksheet = workbook.addWorksheet('Rekapitulasi');
rekapitulasiWorksheet.columns = rekapitulasiColumns;
const perWeightRangeSummary = params.data.summary.per_weight_range || [];
perWeightRangeSummary.forEach(
(item: HppPerKandangPerWeightRange, index: number) => {
rekapitulasiWorksheet.addRow({
no: index + 1,
weightRange: item.label || '',
eggPieces: formatNumber(item.egg_production_pieces || 0),
eggKg: formatNumber(item.egg_production_kg || 0),
avgWeight: formatNumber(item.avg_weight_kg || 0),
feedSuppliers: formatSuppliers(item.feed_suppliers),
docSuppliers: formatSuppliers(item.doc_suppliers),
avgDocPrice: formatCurrency(item.average_doc_price_rp || 0),
eggHpp: formatCurrency(item.egg_hpp_rp_per_kg || 0),
eggValue: formatCurrency(item.egg_value_rp || 0),
});
}
);
// ===== DETAIL PER KANDANG WORKSHEET =====
const detailColumns = [
{ header: 'No', key: 'no', width: 5 },
{ header: 'Kandang', key: 'kandang', width: 30 },
{ header: 'Rentang Bobot', key: 'weightRange', width: 15 },
{ header: 'Rata-Rata Bobot (KG)', key: 'avgWeightKg', width: 18 },
{ header: 'Sisa Telur (Butir)', key: 'eggPieces', width: 15 },
{ header: 'Sisa Telur (KG)', key: 'eggKg', width: 15 },
{ header: 'Feed (Supplier)', key: 'feedSuppliers', width: 20 },
{ header: 'DOC (Supplier)', key: 'docSuppliers', width: 20 },
{ header: 'Rata-Rata Harga DOC (Rp)', key: 'avgDocPrice', width: 20 },
{ header: 'HPP Telur (Rp/Kg)', key: 'eggHpp', width: 18 },
{ header: 'Nilai Nominal Sisa Telur (Rp)', key: 'eggValue', width: 25 },
];
const detailWorksheet = workbook.addWorksheet('Detail Per Kandang');
detailWorksheet.columns = detailColumns;
const allExportData = params.data.rows;
allExportData.forEach((item: HppPerKandangRow, index: number) => {
detailWorksheet.addRow({
no: index + 1,
kandang: item.kandang?.name || '',
weightRange: item.weight_range
? `${formatNumber(item.weight_range.weight_min)} - ${formatNumber(item.weight_range.weight_max)}`
: '',
avgWeightKg: formatNumber(item.avg_weight_kg || 0),
eggPieces: formatNumber(item.egg_production_pieces || 0),
eggKg: formatNumber(item.egg_production_kg || 0),
feedSuppliers: formatSuppliers(item.feed_suppliers),
docSuppliers: formatSuppliers(item.doc_suppliers),
avgDocPrice: formatCurrency(item.average_doc_price_rp || 0),
eggHpp: formatCurrency(item.egg_hpp_rp_per_kg || 0),
eggValue: formatCurrency(item.egg_value_rp || 0),
});
});
// Add TOTAL row
const summaryTotal = params.data.summary.total;
detailWorksheet.addRow({
no: 'TOTAL',
kandang: 'ALL',
weightRange: '-',
avgWeightKg: formatNumber(summaryTotal?.average_weight_kg || 0),
eggPieces: formatNumber(summaryTotal?.total_egg_production_pieces || 0),
eggKg: formatNumber(summaryTotal?.total_egg_production_kg || 0),
feedSuppliers: params.allFeedSuppliers,
docSuppliers: params.allDocSuppliers,
avgDocPrice: formatCurrency(summaryTotal?.total_average_doc_price_rp || 0),
eggHpp: formatCurrency(summaryTotal?.average_egg_hpp_rp_per_kg || 0),
eggValue: formatCurrency(summaryTotal?.total_egg_value_rp || 0),
});
const filename = `laporan-hpp-harian-kandang-periode-${params.data.period}.xlsx`;
const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
window.URL.revokeObjectURL(url);
};
@@ -26,9 +26,9 @@ import Button from '@/components/Button';
import Dropdown from '@/components/Dropdown';
import MenuItem from '@/components/menu/MenuItem';
import Menu from '@/components/menu/Menu';
import { generateHppPerKandangPDF } from '../export/HppPerkandangExport';
import { generateHppPerKandangPDF } from '../export/HppPerkandangExportPDF';
import { generateHppPerKandangExcel } from '../export/HppPerkandangExportXLSX';
import toast from 'react-hot-toast';
import * as XLSX from 'xlsx';
import { Icon } from '@iconify/react';
const HppPerKandangTab = () => {
@@ -346,136 +346,18 @@ const HppPerKandangTab = () => {
return;
}
const allExportData =
allDataForExport.rows as HppPerKandangReport['rows'];
const perWeightRangeSummary =
allDataForExport.summary.per_weight_range || [];
const summaryTotal = allDataForExport.summary.total;
const rekapitulasiData: { [key: string]: string | number }[] =
perWeightRangeSummary.map(
(item: HppPerKandangPerWeightRange, index: number) => ({
No: index + 1,
'Rentang BW': item.label || '',
'Sisa Butir': item.egg_production_pieces || 0,
'Sisa Kg': item.egg_production_kg || 0,
'Rata-Rata Bobot (Kg)': item.avg_weight_kg || 0,
'Feed (Supplier)':
item.feed_suppliers
?.map(
(s: { alias?: string; name: string }) => s.alias || s.name
)
.join(' | ') || '',
'DOC (Supplier)':
item.doc_suppliers
?.map(
(s: { alias?: string; name: string }) => s.alias || s.name
)
.join(' | ') || '',
'Rata-Rata Harga DOC': item.average_doc_price_rp || 0,
'HPP Telur (RP/KG)': item.egg_hpp_rp_per_kg || 0,
'Nominal Sisa': item.egg_value_rp || 0,
})
);
const rekapitulasiWorksheet = XLSX.utils.json_to_sheet(rekapitulasiData);
const rekapitulasiColWidths = [
{ wch: 5 }, // No
{ wch: 15 }, // Rentang BW
{ wch: 15 }, // Sisa Butir
{ wch: 12 }, // Sisa Kg
{ wch: 18 }, // Rata-Rata Bobot (Kg)
{ wch: 20 }, // Feed (Supplier)
{ wch: 20 }, // DOC (Supplier)
{ wch: 20 }, // Rata-Rata Harga DOC
{ wch: 18 }, // HPP Telur (RP/KG)
{ wch: 25 }, // Nominal Sisa
];
rekapitulasiWorksheet['!cols'] = rekapitulasiColWidths;
const excelData: { [key: string]: string | number }[] = allExportData.map(
(item: HppPerKandangRow, index: number) => ({
No: index + 1,
Kandang: item.kandang?.name || '',
'Rentang Bobot': item.weight_range
? `${formatNumber(item.weight_range.weight_min)} - ${formatNumber(item.weight_range.weight_max)}`
: '',
'Rata-Rata Bobot (KG)': item.avg_weight_kg || 0,
'Sisa Telur (Butir)': item.egg_production_pieces || 0,
'Sisa Telur (KG)': item.egg_production_kg || 0,
'Feed (Supplier)':
item.feed_suppliers
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
.join(' | ') || '',
'DOC (Supplier)':
item.doc_suppliers
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
.join(' | ') || '',
'Rata-Rata Harga DOC (RP)': item.average_doc_price_rp || 0,
'HPP Telur (RP/KG)': item.egg_hpp_rp_per_kg || 0,
'Nilai Nominal Sisa Telur (RP)': item.egg_value_rp || 0,
})
);
excelData.push({
No: 'TOTAL',
Kandang: 'ALL',
'Rentang Bobot': '-',
'Rata-Rata Bobot (KG)': summaryTotal?.average_weight_kg || 0,
'Sisa Telur (Butir)': summaryTotal?.total_egg_production_pieces || 0,
'Sisa Telur (KG)': summaryTotal?.total_egg_production_kg || 0,
'Feed (Supplier)': allFeedSuppliers,
'DOC (Supplier)': allDocSuppliers,
'Rata-Rata Harga DOC (RP)':
summaryTotal?.total_average_doc_price_rp || 0,
'HPP Telur (RP/KG)': summaryTotal?.average_egg_hpp_rp_per_kg || 0,
'Nilai Nominal Sisa Telur (RP)': summaryTotal?.total_egg_value_rp || 0,
await generateHppPerKandangExcel({
data: allDataForExport,
allFeedSuppliers,
allDocSuppliers,
});
const worksheet = XLSX.utils.json_to_sheet(excelData);
const colWidths = [
{ wch: 5 }, // No
{ wch: 30 }, // Kandang
{ wch: 15 }, // Rentang Bobot
{ wch: 18 }, // Rata-Rata Bobot (KG)
{ wch: 15 }, // Sisa Telur (Butir)
{ wch: 15 }, // Sisa Telur (KG)
{ wch: 20 }, // Feed (Supplier)
{ wch: 20 }, // DOC (Supplier)
{ wch: 20 }, // Rata-Rata Harga DOC (RP)
{ wch: 18 }, // HPP Telur (RP/KG)
{ wch: 25 }, // Nilai Nominal Sisa Telur (RP)
];
worksheet['!cols'] = colWidths;
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(
workbook,
rekapitulasiWorksheet,
'Rekapitulasi'
);
XLSX.utils.book_append_sheet(workbook, worksheet, 'Detail Per Kandang');
const filename = `laporan-hpp-harian-kandang-periode-${tableFilterState.period}.xlsx`;
XLSX.writeFile(workbook, filename);
toast.success('Excel berhasil dibuat dan diunduh.');
} catch {
toast.error('Gagal membuat Excel. Silakan coba lagi.');
} finally {
setIsExcelExportLoading(false);
}
}, [
hppPerKandangExport,
tableFilterState,
areaOptions,
locationOptions,
kandangOptions,
]);
}, [hppPerKandangExport, allFeedSuppliers, allDocSuppliers]);
const handleExportPDF = useCallback(async () => {
setIsPdfExportLoading(true);