refactor(FE): Refactor PDF exports to use PdfTable

This commit is contained in:
rstubryan
2026-01-28 11:04:06 +07:00
parent 0a5efbe383
commit c20b1c5942
2 changed files with 324 additions and 691 deletions
@@ -11,6 +11,11 @@ import {
} from '@react-pdf/renderer';
import { LogisticPurchasePerSupplierReport } from '@/types/api/report/logistic-stock';
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
import {
PdfTable,
PdfColumn,
PdfTbodyCell,
} from '@/components/helper/pdf/table';
Font.register({
family: 'Helvetica',
@@ -39,117 +44,6 @@ const pdfStyles = StyleSheet.create({
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',
},
tableCellNo: {
flex: 1,
borderRightWidth: 1,
borderRightColor: '#000000',
borderRightStyle: 'solid',
padding: 4,
fontSize: 8,
textAlign: 'center',
},
tableCellLast: {
flex: 1,
padding: 4,
fontSize: 8,
},
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,
},
tableCellHeaderLast: {
flex: 1,
padding: 4,
fontSize: 8,
fontWeight: 'bold',
backgroundColor: '#F5F5F5',
borderBottomWidth: 1,
borderBottomColor: '#000000',
borderBottomStyle: 'solid',
paddingVertical: 12,
textAlign: 'center',
},
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',
},
tableCellCenterLast: {
flex: 1,
padding: 4,
fontSize: 8,
textAlign: 'center',
},
tableBorderBottom: {
borderBottomWidth: 1,
borderBottomColor: '#000000',
borderBottomStyle: 'solid',
},
supplierSection: {
marginBottom: 10,
},
supplierSectionBreak: {
marginBottom: 15,
},
badge: {
backgroundColor: '#1f74bf',
color: '#FFFFFF',
@@ -174,6 +68,12 @@ const pdfStyles = StyleSheet.create({
flexWrap: 'wrap',
marginBottom: 8,
},
supplierSection: {
marginBottom: 10,
},
supplierSectionBreak: {
marginBottom: 15,
},
});
interface PurchasesPerSupplierExportParams {
@@ -218,6 +118,85 @@ const getParameterText = (
return paramsText;
};
// Helper functions for PdfTable
const getTableColumns = (): PdfColumn[] => [
{ key: 'no', header: 'No', flex: 0.5, align: 'center' },
{ key: 'receive_date', header: 'Tanggal Terima', flex: 1, align: 'center' },
{ key: 'po_date', header: 'Tanggal PO', flex: 1, align: 'center' },
{ key: 'po_number', header: 'Referensi', flex: 1, align: 'left' },
{ key: 'product', header: 'Produk', flex: 1, align: 'left' },
{ key: 'warehouse', header: 'Tujuan', flex: 1, align: 'left' },
{ key: 'qty', header: 'Qty', flex: 0.8, align: 'right' },
{ key: 'unit_price', header: 'Harga Beli', flex: 1.2, align: 'right' },
{
key: 'purchase_value',
header: 'Nilai Pembelian',
flex: 1.5,
align: 'right',
},
{
key: 'transport_price',
header: 'Biaya Transport',
flex: 1.2,
align: 'right',
},
{ key: 'total_amount', header: 'Total', flex: 1.5, align: 'right' },
{ key: 'expedition', header: 'Armada', flex: 1.2, align: 'center' },
{ key: 'delivery_number', header: 'Surat Jalan', flex: 1, align: 'left' },
];
const getTableData = (
rows: LogisticPurchasePerSupplierReport['rows']
): PdfTbodyCell[][] => {
return rows.map((item, index) => [
{ key: 'no', value: index + 1, align: 'center' },
{
key: 'receive_date',
value: formatDate(item.receive_date, 'DD-MMM-YYYY'),
align: 'center',
},
{
key: 'po_date',
value: formatDate(item.po_date, 'DD-MMM-YYYY'),
align: 'center',
},
{ key: 'po_number', value: item.po_number || '-' },
{ key: 'product', value: item.product?.name || '-' },
{ key: 'warehouse', value: item.warehouse?.name || '-' },
{ key: 'qty', value: formatNumber(item.qty || 0), align: 'right' },
{
key: 'unit_price',
value: formatCurrency(item.unit_price || 0),
align: 'right',
},
{
key: 'purchase_value',
value: formatCurrency(item.purchase_value || 0),
align: 'right',
},
{
key: 'transport_price',
value: formatCurrency(item.transport_unit_price || 0),
align: 'right',
},
{
key: 'total_amount',
value: formatCurrency(item.total_amount || 0),
align: 'right',
},
{
key: 'expedition',
value: (
<View style={pdfStyles.badge}>
<Text>{item.expedition || '-'}</Text>
</View>
),
align: 'center',
},
{ key: 'delivery_number', value: item.delivery_number || '-' },
]);
};
const createPDFDocument = (
supplierReports: LogisticPurchasePerSupplierReport[],
params: PurchasesPerSupplierExportParams['params']
@@ -266,114 +245,10 @@ const createPDFDocument = (
{supplierReport.supplier.name}
</Text>
<View style={pdfStyles.table}>
{/* Table Header */}
<View style={[pdfStyles.tableRow, pdfStyles.tableHeader]}>
<View style={[pdfStyles.tableCellHeader, { flex: 0.5 }]}>
<Text>No</Text>
</View>
<View style={pdfStyles.tableCellHeader}>
<Text>Tanggal Terima</Text>
</View>
<View style={pdfStyles.tableCellHeader}>
<Text>Tanggal PO</Text>
</View>
<View style={pdfStyles.tableCellHeader}>
<Text>Referensi</Text>
</View>
<View style={pdfStyles.tableCellHeader}>
<Text>Produk</Text>
</View>
<View style={pdfStyles.tableCellHeader}>
<Text>Tujuan</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
<Text>Qty</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Harga Beli</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.5 }]}>
<Text>Nilai Pembelian</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Biaya Transport</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.5 }]}>
<Text>Total</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
<Text>Armada</Text>
</View>
<View style={pdfStyles.tableCellHeaderLast}>
<Text>Surat Jalan</Text>
</View>
</View>
{/* Table Body */}
{supplierReport.rows.map(
(
item: LogisticPurchasePerSupplierReport['rows'][number],
index: number
) => (
<View
key={index}
style={[
pdfStyles.tableRow,
index < supplierReport.rows.length - 1
? pdfStyles.tableBorderBottom
: {},
]}
>
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
<Text>{index + 1}</Text>
</View>
<View style={pdfStyles.tableCell}>
<Text>
{formatDate(item.receive_date, 'DD-MMM-YYYY')}
</Text>
</View>
<View style={pdfStyles.tableCell}>
<Text>{formatDate(item.po_date, 'DD-MMM-YYYY')}</Text>
</View>
<View style={pdfStyles.tableCell}>
<Text>{item.po_number || '-'}</Text>
</View>
<View style={pdfStyles.tableCell}>
<Text>{item.product?.name || '-'}</Text>
</View>
<View style={pdfStyles.tableCell}>
<Text>{item.warehouse?.name || '-'}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatNumber(item.qty || 0)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.unit_price || 0)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.5 }]}>
<Text>{formatCurrency(item.purchase_value || 0)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>
{formatCurrency(item.transport_unit_price || 0)}
</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.5 }]}>
<Text>{formatCurrency(item.total_amount || 0)}</Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
<View style={pdfStyles.badge}>
<Text>{item.expedition || '-'}</Text>
</View>
</View>
<View style={pdfStyles.tableCellLast}>
<Text>{item.delivery_number || '-'}</Text>
</View>
</View>
)
)}
</View>
<PdfTable
columns={getTableColumns()}
data={getTableData(supplierReport.rows)}
/>
</View>
);
}
@@ -15,6 +15,12 @@ import {
HppPerKandangPerWeightRange,
} from '@/types/api/report/hpp-per-kandang';
import { formatDate, formatNumber, formatCurrency } from '@/lib/helper';
import {
PdfTable,
PdfColumn,
PdfTbodyCell,
PdfTfootCell,
} from '@/components/helper/pdf/table';
Font.register({
family: 'Helvetica',
@@ -43,85 +49,6 @@ const pdfStyles = StyleSheet.create({
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',
@@ -136,6 +63,9 @@ const pdfStyles = StyleSheet.create({
flexWrap: 'wrap',
marginBottom: 8,
},
section: {
marginBottom: 15,
},
});
interface HppPerKandangExportParams {
@@ -192,6 +122,215 @@ const getParameterText = (params: HppPerKandangExportParams['params']) => {
return paramsText;
};
// Helper functions for PdfTable - Rekapitulasi
const getRekapitulasiColumns = (): PdfColumn[] => [
{ key: 'rentang_bw', header: 'Rentang BW', flex: 1.2, align: 'center' },
{ key: 'sisa_butir', header: 'Sisa Butir', flex: 1, align: 'right' },
{ key: 'sisa_kg', header: 'Sisa Kg', flex: 1, align: 'right' },
{
key: 'rata_rata_bobot',
header: 'Rata-Rata Bobot (Kg)',
flex: 1.2,
align: 'right',
},
{ key: 'feed_supplier', header: 'Feed (Supplier)', flex: 1.5, align: 'left' },
{ key: 'doc_supplier', header: 'DOC (Supplier)', flex: 1.2, align: 'left' },
{
key: 'rata_harga_doc',
header: 'Rata-Rata Harga DOC',
flex: 1.2,
align: 'right',
},
{ key: 'hpp_telur', header: 'HPP Telur (RP/KG)', flex: 1.2, align: 'right' },
{ key: 'nominal_sisa', header: 'Nominal Sisa', flex: 1.2, align: 'right' },
];
const getRekapitulasiData = (
perWeightRange: HppPerKandangPerWeightRange[]
): PdfTbodyCell[][] => {
return perWeightRange.map((group) => [
{ key: 'rentang_bw', value: group.label, align: 'center' },
{
key: 'sisa_butir',
value: formatNumber(group.egg_production_pieces),
align: 'right',
},
{
key: 'sisa_kg',
value: formatNumber(group.egg_production_kg),
align: 'right',
},
{
key: 'rata_rata_bobot',
value: formatNumber(group.avg_weight_kg),
align: 'right',
},
{
key: 'feed_supplier',
value:
group.feed_suppliers
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
.join(' | ') || '-',
},
{
key: 'doc_supplier',
value:
group.doc_suppliers
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
.join(' | ') || '-',
},
{
key: 'rata_harga_doc',
value: formatCurrency(group.average_doc_price_rp),
align: 'right',
},
{
key: 'hpp_telur',
value: formatCurrency(group.egg_hpp_rp_per_kg),
align: 'right',
},
{
key: 'nominal_sisa',
value: formatCurrency(group.egg_value_rp),
align: 'right',
},
]);
};
// Helper functions for PdfTable - Detail Per Kandang
const getDetailColumns = (): PdfColumn[] => [
{ key: 'no', header: 'No', flex: 0.5, align: 'center' },
{ key: 'kandang', header: 'Kandang', flex: 1.5, align: 'left' },
{ key: 'rentang_bw', header: 'Rentang BW', flex: 1, align: 'left' },
{
key: 'rata_rata_bobot',
header: 'Rata-Rata Bobot (Kg)',
flex: 1,
align: 'right',
},
{ key: 'sisa_butir', header: 'Sisa Butir', flex: 0.8, align: 'right' },
{ key: 'sisa_kg', header: 'Sisa Kg (Telur)', flex: 0.8, align: 'right' },
{ key: 'feed_supplier', header: 'Feed (Supplier)', flex: 1.2, align: 'left' },
{ key: 'doc_supplier', header: 'DOC (Supplier)', flex: 1, align: 'left' },
{
key: 'rata_harga_doc',
header: 'Rata-Rata Harga DOC',
flex: 1.2,
align: 'right',
},
{ key: 'hpp_telur', header: 'HPP Telur (RP/KG)', flex: 1, align: 'right' },
{ key: 'nominal_sisa', header: 'Nominal Sisa', flex: 1.2, align: 'right' },
];
const getDetailData = (rows: HppPerKandangRow[]): PdfTbodyCell[][] => {
return rows.map((item, index) => [
{ key: 'no', value: index + 1, align: 'center' },
{ key: 'kandang', value: item.kandang?.name || '-' },
{
key: 'rentang_bw',
value: `${item.weight_range.weight_min.toFixed(2)} - ${item.weight_range.weight_max.toFixed(2)}`,
},
{
key: 'rata_rata_bobot',
value: formatNumber(item.avg_weight_kg),
align: 'right',
},
{
key: 'sisa_butir',
value: formatNumber(item.egg_production_pieces),
align: 'right',
},
{
key: 'sisa_kg',
value: formatNumber(item.egg_production_kg),
align: 'right',
},
{
key: 'feed_supplier',
value:
item.feed_suppliers
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
.join(' | ') || '-',
},
{
key: 'doc_supplier',
value:
item.doc_suppliers
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
.join(' | ') || '-',
},
{
key: 'rata_harga_doc',
value: formatCurrency(item.average_doc_price_rp),
align: 'right',
},
{
key: 'hpp_telur',
value: formatCurrency(item.egg_hpp_rp_per_kg),
align: 'right',
},
{
key: 'nominal_sisa',
value: formatCurrency(item.egg_value_rp),
align: 'right',
},
]);
};
const getDetailFooter = (
summary: HppPerKandangReport['summary']
): PdfTfootCell[] => {
if (!summary?.total) return [];
const allFeedSuppliers =
summary.total.feed_suppliers
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
.join(' | ') || '-';
const allDocSuppliers =
summary.total.doc_suppliers
?.map((s: { alias?: string; name: string }) => s.alias || s.name)
.join(' | ') || '-';
return [
{ key: 'no', value: 'TOTAL' },
{ key: 'kandang', value: 'ALL' },
{ key: 'rentang_bw', value: '-' },
{
key: 'rata_rata_bobot',
value: formatNumber(summary.total.average_weight_kg),
align: 'right',
},
{
key: 'sisa_butir',
value: formatNumber(summary.total.total_egg_production_pieces),
align: 'right',
},
{
key: 'sisa_kg',
value: formatNumber(summary.total.total_egg_production_kg),
align: 'right',
},
{ key: 'feed_supplier', value: allFeedSuppliers },
{ key: 'doc_supplier', value: allDocSuppliers },
{
key: 'rata_harga_doc',
value: formatCurrency(summary.total.total_average_doc_price_rp),
align: 'right',
},
{
key: 'hpp_telur',
value: formatCurrency(summary.total.average_egg_hpp_rp_per_kg),
align: 'right',
},
{
key: 'nominal_sisa',
value: formatCurrency(summary.total.total_egg_value_rp),
align: 'right',
},
];
};
const createPDFDocument = (
data: HppPerKandangExportParams['data'],
params: HppPerKandangExportParams['params']
@@ -216,404 +355,23 @@ const createPDFDocument = (
</View>
{/* Rekapitulasi Section */}
<View style={pdfStyles.supplierSection}>
<View style={pdfStyles.section}>
<Text style={pdfStyles.supplierTitle}>Rekapitulasi</Text>
<View style={pdfStyles.table}>
{/* Table Header */}
<View style={[pdfStyles.tableRow, pdfStyles.tableHeader]}>
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
<Text>Rentang BW</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
<Text>Sisa Butir</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
<Text>Sisa Kg</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Rata-Rata Bobot (Kg)</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}>
<Text>Feed (Supplier)</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
<Text>DOC (Supplier)</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Rata-Rata Harga DOC</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>HPP Telur (RP/KG)</Text>
</View>
<View
style={[
pdfStyles.tableCellHeaderRight,
{ flex: 1.2, borderRightWidth: 0 },
]}
>
<Text>Nominal Sisa</Text>
</View>
</View>
{/* Table Body - Rekapitulasi */}
{rekapitulasiByWeightRange.map(
(group: HppPerKandangPerWeightRange, index: number) => (
<View
key={index}
style={[
pdfStyles.tableRow,
index < rekapitulasiByWeightRange.length - 1
? pdfStyles.tableBorderBottom
: {},
]}
>
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
<Text>{group.label}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatNumber(group.egg_production_pieces)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatNumber(group.egg_production_kg)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatNumber(group.avg_weight_kg)}</Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
<Text>
{group.feed_suppliers
?.map(
(s: { alias?: string; name: string }) =>
s.alias || s.name
)
.join(' | ') || '-'}
</Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
<Text>
{group.doc_suppliers
?.map(
(s: { alias?: string; name: string }) =>
s.alias || s.name
)
.join(' | ') || '-'}
</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(group.average_doc_price_rp)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(group.egg_hpp_rp_per_kg)}</Text>
</View>
<View
style={[
pdfStyles.tableCellRight,
{ flex: 1.2, borderRightWidth: 0 },
]}
>
<Text>{formatCurrency(group.egg_value_rp)}</Text>
</View>
</View>
)
)}
</View>
<PdfTable
columns={getRekapitulasiColumns()}
data={getRekapitulasiData(rekapitulasiByWeightRange)}
/>
</View>
{/* Detail Per Kandang Section */}
<View style={pdfStyles.supplierSectionBreak}>
<View style={pdfStyles.section}>
<Text style={pdfStyles.supplierTitle}>Detail Per Kandang</Text>
<View style={pdfStyles.table}>
{/* Table Header */}
<View style={[pdfStyles.tableRow, pdfStyles.tableHeader]}>
<View style={[pdfStyles.tableCellHeader, { flex: 0.5 }]}>
<Text>No</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}>
<Text>Kandang</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
<Text>Rentang BW</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
<Text>Rata-Rata Bobot (Kg)</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
<Text>Sisa Butir</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
<Text>Sisa Kg (Telur)</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
<Text>Feed (Supplier)</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
<Text>DOC (Supplier)</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Rata-Rata Harga DOC</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
<Text>HPP Telur (RP/KG)</Text>
</View>
<View
style={[
pdfStyles.tableCellHeaderRight,
{ flex: 1.2, borderRightWidth: 0 },
]}
>
<Text>Nominal Sisa</Text>
</View>
</View>
{/* Table Body - Detail Per Kandang */}
{data.rows.map((item: HppPerKandangRow, index: number) => (
<View
key={index}
style={[pdfStyles.tableRow, pdfStyles.tableBorderBottom]}
>
<View style={[pdfStyles.tableCellCenter, { flex: 0.5 }]}>
<Text>{index + 1}</Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
<Text>{item.kandang?.name || '-'}</Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
<Text>
{item.weight_range.weight_min.toFixed(2)} -{' '}
{item.weight_range.weight_max.toFixed(2)}
</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatNumber(item.avg_weight_kg)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatNumber(item.egg_production_pieces)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatNumber(item.egg_production_kg)}</Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
<Text>
{item.feed_suppliers
?.map(
(s: { alias?: string; name: string }) =>
s.alias || s.name
)
.join(' | ')}
</Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
<Text>
{item.doc_suppliers
?.map(
(s: { alias?: string; name: string }) =>
s.alias || s.name
)
.join(' | ')}
</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.average_doc_price_rp)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatCurrency(item.egg_hpp_rp_per_kg)}</Text>
</View>
<View
style={[
pdfStyles.tableCellRight,
{ flex: 1.2, borderRightWidth: 0 },
]}
>
<Text>{formatCurrency(item.egg_value_rp)}</Text>
</View>
</View>
))}
{/* TOTAL Row */}
{data.summary?.total && (
<View style={pdfStyles.tableRow}>
<View
style={[
pdfStyles.tableCellHeader,
{
flex: 0.5,
backgroundColor: '#F5F5F5',
borderBottomWidth: 0,
},
]}
>
<Text>TOTAL</Text>
</View>
<View
style={[
pdfStyles.tableCellHeader,
{
flex: 1.5,
backgroundColor: '#F5F5F5',
borderBottomWidth: 0,
},
]}
>
<Text>ALL</Text>
</View>
<View
style={[
pdfStyles.tableCellHeader,
{
flex: 1,
backgroundColor: '#F5F5F5',
borderBottomWidth: 0,
},
]}
>
<Text>-</Text>
</View>
<View
style={[
pdfStyles.tableCellHeaderRight,
{
flex: 1,
backgroundColor: '#F5F5F5',
borderBottomWidth: 0,
},
]}
>
<Text>
{formatNumber(data.summary.total.average_weight_kg)}
</Text>
</View>
<View
style={[
pdfStyles.tableCellHeaderRight,
{
flex: 0.8,
backgroundColor: '#F5F5F5',
borderBottomWidth: 0,
},
]}
>
<Text>
{formatNumber(
data.summary.total.total_egg_production_pieces
)}
</Text>
</View>
<View
style={[
pdfStyles.tableCellHeaderRight,
{
flex: 0.8,
backgroundColor: '#F5F5F5',
borderBottomWidth: 0,
},
]}
>
<Text>
{formatNumber(data.summary.total.total_egg_production_kg)}
</Text>
</View>
<View
style={[
pdfStyles.tableCellHeader,
{
flex: 1.2,
backgroundColor: '#F5F5F5',
borderBottomWidth: 0,
},
]}
>
<Text>
{data.rows
.flatMap((row: HppPerKandangRow) =>
row.feed_suppliers?.map(
(s: { alias?: string; name: string }) =>
s.alias || s.name
)
)
.filter(
(v: string, i: number, a: string[]) =>
a.indexOf(v) === i
)
.join(' | ') || '-'}
</Text>
</View>
<View
style={[
pdfStyles.tableCellHeader,
{
flex: 1,
backgroundColor: '#F5F5F5',
borderBottomWidth: 0,
},
]}
>
<Text>
{data.rows
.flatMap((row: HppPerKandangRow) =>
row.doc_suppliers?.map(
(s: { alias?: string; name: string }) =>
s.alias || s.name
)
)
.filter(
(v: string, i: number, a: string[]) =>
a.indexOf(v) === i
)
.join(' | ') || '-'}
</Text>
</View>
<View
style={[
pdfStyles.tableCellHeaderRight,
{
flex: 1.2,
backgroundColor: '#F5F5F5',
borderBottomWidth: 0,
},
]}
>
<Text>
{formatCurrency(
data.summary.total.total_average_doc_price_rp
)}
</Text>
</View>
<View
style={[
pdfStyles.tableCellHeaderRight,
{
flex: 1,
backgroundColor: '#F5F5F5',
borderBottomWidth: 0,
},
]}
>
<Text>
{formatCurrency(
data.summary.total.average_egg_hpp_rp_per_kg
)}
</Text>
</View>
<View
style={[
pdfStyles.tableCellHeaderRight,
{
flex: 1.2,
backgroundColor: '#F5F5F5',
borderBottomWidth: 0,
borderRightWidth: 0,
},
]}
>
<Text>
{formatCurrency(data.summary.total.total_egg_value_rp)}
</Text>
</View>
</View>
)}
</View>
<PdfTable
columns={getDetailColumns()}
data={getDetailData(data.rows)}
footer={data.summary ? getDetailFooter(data.summary) : undefined}
footerLabel='TOTAL'
/>
</View>
</Page>
</Document>