diff --git a/src/components/pages/report/sale/export/HppPerkandangExport.tsx b/src/components/pages/report/sale/export/HppPerkandangExport.tsx new file mode 100644 index 00000000..dc0c95f0 --- /dev/null +++ b/src/components/pages/report/sale/export/HppPerkandangExport.tsx @@ -0,0 +1,490 @@ +'use client'; + +import { + Page, + Text, + View, + Document, + StyleSheet, + Font, + pdf, +} from '@react-pdf/renderer'; +import { HppPerKandangReport } from '@/types/api/report/hpp-per-kandang'; +import { formatDate, formatNumber } from '@/lib/helper'; + +Font.register({ + family: 'Helvetica', + src: 'helvetica', +}); + +const pdfStyles = StyleSheet.create({ + page: { + fontSize: 10, + fontFamily: 'Helvetica', + padding: 20, + backgroundColor: '#FFFFFF', + }, + titleSection: { + marginBottom: 10, + }, + mainTitle: { + fontSize: 14, + fontWeight: 'bold', + marginBottom: 5, + color: '#1f74bf', + }, + supplierTitle: { + fontSize: 12, + fontWeight: 'bold', + marginBottom: 8, + color: '#1f74bf', + }, + table: { + borderWidth: 1, + borderColor: '#000000', + marginBottom: 15, + }, + tableRow: { + flexDirection: 'row', + }, + tableHeader: { + backgroundColor: '#F5F5F5', + }, + tableCell: { + flex: 1, + borderRightWidth: 1, + borderRightColor: '#000000', + borderRightStyle: 'solid', + padding: 4, + fontSize: 8, + textAlign: 'left', + }, + tableCellHeader: { + flex: 1, + borderRightWidth: 1, + borderRightColor: '#000000', + borderRightStyle: 'solid', + padding: 4, + fontSize: 8, + fontWeight: 'bold', + backgroundColor: '#F5F5F5', + borderBottomWidth: 1, + borderBottomColor: '#000000', + borderBottomStyle: 'solid', + paddingVertical: 12, + textAlign: 'center', + }, + tableCellHeaderRight: { + flex: 1, + borderRightWidth: 1, + borderRightColor: '#000000', + borderRightStyle: 'solid', + padding: 4, + fontSize: 8, + fontWeight: 'bold', + backgroundColor: '#F5F5F5', + textAlign: 'right', + borderBottomWidth: 1, + borderBottomColor: '#000000', + borderBottomStyle: 'solid', + paddingVertical: 12, + }, + tableCellRight: { + flex: 1, + borderRightWidth: 1, + borderRightColor: '#000000', + borderRightStyle: 'solid', + padding: 4, + fontSize: 8, + textAlign: 'right', + }, + tableCellCenter: { + flex: 1, + borderRightWidth: 1, + borderRightColor: '#000000', + borderRightStyle: 'solid', + padding: 4, + fontSize: 8, + textAlign: 'center', + }, + tableBorderBottom: { + borderBottomWidth: 1, + borderBottomColor: '#000000', + borderBottomStyle: 'solid', + }, + supplierSection: { + marginBottom: 10, + }, + supplierSectionBreak: { + marginBottom: 15, + }, + parameterBadge: { + backgroundColor: '#F5F5F5', + color: '#333333', + padding: 4, + borderRadius: 4, + fontSize: 8, + marginRight: 8, + marginBottom: 4, + }, + parameterContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + marginBottom: 8, + }, +}); + +interface HppPerKandangExportParams { + data: HppPerKandangReport; + params: { + area_name?: string; + location_name?: string; + kandang_name?: string; + period?: string; + weight_min?: string; + weight_max?: string; + show_unrecorded?: string; + sort_by?: string; + }; +} + +interface WeightRangeGroup { + weight_min: number; + weight_max: number; + items: HppPerKandangReport['rows']; + totals: { + total_remaining_chicken_birds: number; + total_remaining_chicken_weight_kg: number; + average_weight_kg: number; + total_hpp_rp: number; + total_remaining_value_rp: number; + all_feed_suppliers: string[]; + all_doc_suppliers: string[]; + average_doc_price_rp: number; + }; +} + +const groupDataByWeightRange = ( + data: HppPerKandangReport['rows'] +): WeightRangeGroup[] => { + const groups: { + [key: string]: WeightRangeGroup; + } = {}; + + data.forEach((item) => { + const key = `${item.weight_range.weight_min}-${item.weight_range.weight_max}`; + if (!groups[key]) { + groups[key] = { + weight_min: item.weight_range.weight_min, + weight_max: item.weight_range.weight_max, + items: [], + totals: { + total_remaining_chicken_birds: 0, + total_remaining_chicken_weight_kg: 0, + average_weight_kg: 0, + total_hpp_rp: 0, + total_remaining_value_rp: 0, + all_feed_suppliers: [], + all_doc_suppliers: [], + average_doc_price_rp: 0, + }, + }; + } + + groups[key].items.push(item); + + groups[key].totals.total_remaining_chicken_birds += + item.remaining_chicken_birds; + groups[key].totals.total_remaining_chicken_weight_kg += + item.remaining_chicken_weight_kg; + groups[key].totals.total_hpp_rp += item.hpp_rp; + groups[key].totals.total_remaining_value_rp += item.remaining_value_rp; + + item.feed_suppliers?.forEach((supplier) => { + const alias = supplier.alias || supplier.name; + if (!groups[key].totals.all_feed_suppliers.includes(alias)) { + groups[key].totals.all_feed_suppliers.push(alias); + } + }); + + item.doc_suppliers?.forEach((supplier) => { + const alias = supplier.alias || supplier.name; + if (!groups[key].totals.all_doc_suppliers.includes(alias)) { + groups[key].totals.all_doc_suppliers.push(alias); + } + }); + }); + + Object.values(groups).forEach((group) => { + group.totals.average_weight_kg = + group.totals.total_remaining_chicken_weight_kg / + group.totals.total_remaining_chicken_birds; + group.totals.average_doc_price_rp = + group.items.reduce((sum, item) => sum + item.average_doc_price_rp, 0) / + group.items.length; + }); + + return Object.values(groups).sort((a, b) => a.weight_min - b.weight_min); +}; + +const getParameterText = (params: HppPerKandangExportParams['params']) => { + const paramsText = []; + + if (params.period) { + const formattedDate = formatDate(params.period, 'DD MMM YYYY'); + paramsText.push(`Tanggal: ${formattedDate}`); + } + + const currentDate = formatDate(new Date().toISOString(), 'DD MMM YYYY HH:mm'); + paramsText.push(`Dicetak: ${currentDate}`); + + return paramsText; +}; + +const createPDFDocument = ( + data: HppPerKandangExportParams['data'], + params: HppPerKandangExportParams['params'] +) => { + const groupedByWeightRange = groupDataByWeightRange(data.rows); + + return ( + + + {/* Title and Parameters */} + + + Laporan > HPP Harian Kandang + + + {getParameterText(params).map((param, index) => ( + + {param} + + ))} + + + + {/* Rekapitulasi Section */} + + Rekapitulasi + + + {/* Table Header */} + + + Rentang BW + + + Sisa Ekor + + + Sisa Kg + + + Rata-Rata Bobot (Kg) + + + Feed (Supplier) + + + DOC (Supplier) + + + Rata-Rata Harga DOC + + + HPP + + + Nominal Sisa + + + + {/* Table Body - Rekapitulasi */} + {groupedByWeightRange.map((group, index) => ( + + + + {group.weight_min.toFixed(2)} -{' '} + {group.weight_max.toFixed(2)} + + + + + {formatNumber(group.totals.total_remaining_chicken_birds)} + + + + + {formatNumber( + group.totals.total_remaining_chicken_weight_kg + )} + + + + {formatNumber(group.totals.average_weight_kg)} + + + {group.totals.all_feed_suppliers.join(' | ')} + + + {group.totals.all_doc_suppliers.join(' | ')} + + + {formatNumber(group.totals.average_doc_price_rp)} + + + + { + (group.totals.total_remaining_chicken_birds > 0 + ? group.totals.total_hpp_rp / + group.totals.total_remaining_chicken_birds + : 0, + 2) + } + + + + + {formatNumber(group.totals.total_remaining_value_rp)} + + + + ))} + + + + {/* Detail Per Kandang Section */} + + Detail Per Kandang + + + {/* Table Header */} + + + No + + + Kandang + + + Rentang BW + + + Sisa Ekor + + + Sisa Kg + + + Rata-Rata Bobot (Kg) + + + Feed (Supplier) + + + DOC (Supplier) + + + Rata-Rata Harga DOC + + + HPP + + + Nominal Sisa + + + + {/* Table Body - Detail Per Kandang */} + {data.rows.map((item, index) => ( + + + {index + 1} + + + {item.kandang?.name || '-'} + + + + {item.weight_range.weight_min.toFixed(2)} -{' '} + {item.weight_range.weight_max.toFixed(2)} + + + + {formatNumber(item.remaining_chicken_birds)} + + + {formatNumber(item.remaining_chicken_weight_kg)} + + + {formatNumber(item.avg_weight_kg)} + + + + {item.feed_suppliers + ?.map((s) => s.alias || s.name) + .join(' | ')} + + + + + {item.doc_suppliers + ?.map((s) => s.alias || s.name) + .join(' | ')} + + + + {formatNumber(item.average_doc_price_rp)} + + + {formatNumber(item.hpp_rp)} + + + {formatNumber(item.remaining_value_rp)} + + + ))} + + + + + ); +}; + +export const generateHppPerKandangPDF = async ( + data: HppPerKandangExportParams['data'], + params: HppPerKandangExportParams['params'] +): Promise => { + const PDFDocument = createPDFDocument(data, params); + + try { + const blob = await pdf(PDFDocument).toBlob(); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `laporan-hpp-harian-kandang-${formatDate(new Date(), 'YYYY-MM-DD-HHmm')}.pdf`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + } catch (error) { + throw error; + } +}; diff --git a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx index 0481dd2a..d90040c6 100644 --- a/src/components/pages/report/sale/tab/HppPerKandangTab.tsx +++ b/src/components/pages/report/sale/tab/HppPerKandangTab.tsx @@ -23,6 +23,7 @@ import Button from '@/components/Button'; import Dropdown from '@/components/dropdown/Dropdown'; import MenuItem from '@/components/menu/MenuItem'; import Menu from '@/components/menu/Menu'; +import { generateHppPerKandangPDF } from '../export/HppPerkandangExport'; import toast from 'react-hot-toast'; import * as XLSX from 'xlsx'; @@ -380,6 +381,54 @@ const HppPerKandangTab = () => { kandangOptions, ]); + const handleExportPDF = useCallback(async () => { + if (!hppPerKandang || !isResponseSuccess(hppPerKandang)) { + toast.error('Tidak ada data untuk diekspor.'); + return; + } + + try { + const areaName = tableFilterState.area_id + ? areaOptions.find( + (opt) => opt.value === Number(tableFilterState.area_id) + )?.label || 'Semua Area' + : 'Semua Area'; + + const locationName = tableFilterState.location_id + ? locationOptions.find( + (opt) => opt.value === Number(tableFilterState.location_id) + )?.label || 'Semua Lokasi' + : 'Semua Lokasi'; + + const kandangName = tableFilterState.kandang_id + ? kandangOptions.find( + (opt) => opt.value === Number(tableFilterState.kandang_id) + )?.label || 'Semua Kandang' + : 'Semua Kandang'; + + await generateHppPerKandangPDF(hppPerKandang.data, { + area_name: areaName, + location_name: locationName, + kandang_name: kandangName, + period: tableFilterState.period, + weight_min: tableFilterState.weight_min, + weight_max: tableFilterState.weight_max, + show_unrecorded: tableFilterState.show_unrecorded.toString(), + sort_by: tableFilterState.sort_by, + }); + + toast.success('PDF berhasil dibuat dan diunduh.'); + } catch { + toast.error('Gagal membuat PDF. Silakan coba lagi.'); + } + }, [ + hppPerKandang, + tableFilterState, + areaOptions, + locationOptions, + kandangOptions, + ]); + // ===== TABLE COLUMNS DEFINITION ===== const totals: Totals = useMemo(() => { return { @@ -592,12 +641,7 @@ const HppPerKandangTab = () => { > - { - alert('Fitur belum tersedia'); - }} - /> +