mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
refactor(FE): Refactor HppPerKandang export logic to use ExcelJS
This commit is contained in:
@@ -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 Dropdown from '@/components/Dropdown';
|
||||||
import MenuItem from '@/components/menu/MenuItem';
|
import MenuItem from '@/components/menu/MenuItem';
|
||||||
import Menu from '@/components/menu/Menu';
|
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 toast from 'react-hot-toast';
|
||||||
import * as XLSX from 'xlsx';
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
|
|
||||||
const HppPerKandangTab = () => {
|
const HppPerKandangTab = () => {
|
||||||
@@ -346,136 +346,18 @@ const HppPerKandangTab = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allExportData =
|
await generateHppPerKandangExcel({
|
||||||
allDataForExport.rows as HppPerKandangReport['rows'];
|
data: allDataForExport,
|
||||||
|
allFeedSuppliers,
|
||||||
const perWeightRangeSummary =
|
allDocSuppliers,
|
||||||
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,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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.');
|
toast.success('Excel berhasil dibuat dan diunduh.');
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Gagal membuat Excel. Silakan coba lagi.');
|
toast.error('Gagal membuat Excel. Silakan coba lagi.');
|
||||||
} finally {
|
} finally {
|
||||||
setIsExcelExportLoading(false);
|
setIsExcelExportLoading(false);
|
||||||
}
|
}
|
||||||
}, [
|
}, [hppPerKandangExport, allFeedSuppliers, allDocSuppliers]);
|
||||||
hppPerKandangExport,
|
|
||||||
tableFilterState,
|
|
||||||
areaOptions,
|
|
||||||
locationOptions,
|
|
||||||
kandangOptions,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleExportPDF = useCallback(async () => {
|
const handleExportPDF = useCallback(async () => {
|
||||||
setIsPdfExportLoading(true);
|
setIsPdfExportLoading(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user