refactor(FE): Refactor PDF table components to simplify imports

This commit is contained in:
rstubryan
2026-02-11 10:39:37 +07:00
parent 70b63f7773
commit 0f1d2ce477
6 changed files with 973 additions and 885 deletions
@@ -11,12 +11,7 @@ import {
formatNumber,
formatTitleCase,
} from '@/lib/helper';
import {
PdfTable,
PdfColumn,
PdfTbodyCell,
PdfTfootCell,
} from '@/components/helper/pdf/table';
import { PdfTable, PdfColumn } from '@/components/helper/pdf/table';
import { PdfParamBadge } from '@/components/helper/pdf/badge/PdfParamBadge';
import { PdfStatusBadge } from '@/components/helper/pdf/badge/PdfStatusBadge';
import { PdfTypography } from '@/components/helper/pdf/typography/PdfTypography';
@@ -49,60 +44,90 @@ interface DailyMarketingReportPDFProps {
total?: SalesSummary;
}
const getTableColumns = (): PdfColumn[] => [
{ key: 'no', header: 'No', flex: 0.5, align: 'center' },
{ key: 'so_date', header: 'Tanggal Sales Order', flex: 1.3, align: 'center' },
const getTableColumns = (
summary?: SalesSummary
): PdfColumn<DailyMarketingReport[number]>[] => [
{
key: 'no',
header: 'No',
flex: 0.5,
align: 'center',
cell: ({ row, index }) => index + 1,
},
{
key: 'so_date',
header: 'Tanggal Sales Order',
flex: 1.3,
align: 'center',
cell: ({ row }) =>
row.so_date ? formatDate(row.so_date, 'DD MMM YY') : '-',
},
{
key: 'do_date',
header: 'Tanggal Delivery Order',
flex: 1.3,
align: 'center',
},
{ key: 'aging', header: 'Aging (Hari)', flex: 0.7, align: 'center' },
{ key: 'warehouse', header: 'Gudang', flex: 1.2, align: 'left' },
{ key: 'customer', header: 'Pelanggan', flex: 1.5, align: 'left' },
{ key: 'sales', header: 'Sales', flex: 1, align: 'left' },
{ key: 'product', header: 'Produk', flex: 1.3, align: 'left' },
{ key: 'do_number', header: 'Nomor DO', flex: 1.2, align: 'left' },
{ key: 'vehicle', header: 'Nomor Polisi', flex: 1, align: 'left' },
{ key: 'marketing_type', header: 'Tipe Marketing', flex: 1, align: 'center' },
{ key: 'qty', header: 'Quantity', flex: 0.7, align: 'right' },
{ key: 'avg_weight', header: 'Rata-Rata (Kg)', flex: 0.8, align: 'right' },
{
key: 'total_weight',
header: 'Total Berat (Kg)',
flex: 0.9,
align: 'right',
},
{ key: 'sales_price', header: 'Harga Jual (Rp)', flex: 0.9, align: 'right' },
{ key: 'hpp_price', header: 'HPP (Rp)', flex: 1.3, align: 'right' },
{ key: 'sales_amount', header: 'Total Jual (Rp)', flex: 1, align: 'right' },
{ key: 'hpp_amount', header: 'Total HPP (Rp)', flex: 1.3, align: 'right' },
];
const getTableData = (rows: DailyMarketingReport): PdfTbodyCell[][] => {
return rows.map((row, index) => [
{ key: 'no', value: index + 1 },
{
key: 'so_date',
value: row.so_date ? formatDate(row.so_date, 'DD MMM YY') : '-',
},
{
key: 'do_date',
value: row.realization_date
cell: ({ row }) =>
row.realization_date
? formatDate(row.realization_date, 'DD MMM YY')
: '-',
},
{ key: 'aging', value: row.aging_days ?? '-' },
{ key: 'warehouse', value: row.warehouse?.name ?? '-' },
{ key: 'customer', value: row.customer?.name ?? '-' },
{ key: 'sales', value: row.sales?.name ?? '-' },
{ key: 'product', value: row.product?.name ?? '-' },
{ key: 'do_number', value: row.do_number ?? '-' },
{ key: 'vehicle', value: row.vehicle_number ?? '-' },
{
key: 'marketing_type',
value: row.marketing_type ? (
},
{
key: 'aging',
header: 'Aging (Hari)',
flex: 0.7,
align: 'center',
cell: ({ row }) => row.aging_days ?? '-',
},
{
key: 'warehouse',
header: 'Gudang',
flex: 1.2,
align: 'left',
cell: ({ row }) => row.warehouse?.name ?? '-',
},
{
key: 'customer',
header: 'Pelanggan',
flex: 1.5,
align: 'left',
cell: ({ row }) => row.customer?.name ?? '-',
},
{
key: 'sales',
header: 'Sales',
flex: 1,
align: 'left',
cell: ({ row }) => row.sales?.name ?? '-',
},
{
key: 'product',
header: 'Produk',
flex: 1.3,
align: 'left',
cell: ({ row }) => row.product?.name ?? '-',
},
{
key: 'do_number',
header: 'Nomor DO',
flex: 1.2,
align: 'left',
cell: ({ row }) => row.do_number ?? '-',
},
{
key: 'vehicle',
header: 'Nomor Polisi',
flex: 1,
align: 'left',
cell: ({ row }) => row.vehicle_number ?? '-',
},
{
key: 'marketing_type',
header: 'Tipe Marketing',
flex: 1,
align: 'center',
cell: ({ row }) =>
row.marketing_type ? (
<View style={{ alignItems: 'center' }}>
<PdfStatusBadge
style={{
@@ -138,89 +163,70 @@ const getTableData = (rows: DailyMarketingReport): PdfTbodyCell[][] => {
) : (
'-'
),
},
{ key: 'qty', value: formatNumber(row.qty ?? 0), align: 'right' },
{
key: 'avg_weight',
value: formatNumber(row.average_weight_kg ?? 0),
align: 'right',
},
{
key: 'total_weight',
value: formatNumber(row.total_weight_kg ?? 0),
align: 'right',
},
{
key: 'sales_price',
value: formatCurrency(row.sales_price_per_kg ?? 0),
align: 'right',
},
{
key: 'hpp_price',
value: formatCurrency(row.hpp_price_per_kg ?? 0),
align: 'right',
},
{
key: 'sales_amount',
value: formatCurrency(row.sales_amount ?? 0),
align: 'right',
},
{
key: 'hpp_amount',
value: formatCurrency(row.hpp_amount ?? 0),
align: 'right',
},
]);
};
const getTableFooter = (summary?: SalesSummary): PdfTfootCell[] => {
if (!summary) return [];
return [
{ key: 'no', value: 'TOTAL' },
{ key: 'so_date', value: '' },
{ key: 'do_date', value: '' },
{ key: 'aging', value: '' },
{ key: 'warehouse', value: '' },
{ key: 'customer', value: '' },
{ key: 'sales', value: '' },
{ key: 'product', value: '' },
{ key: 'do_number', value: '' },
{ key: 'vehicle', value: '' },
{ key: 'marketing_type', value: '' },
{
key: 'qty',
value: formatNumber(summary.total_qty ?? 0),
align: 'right',
},
{
key: 'avg_weight',
value: formatNumber(summary.total_weight_kg ?? 0),
align: 'right',
},
{
key: 'total_weight',
value: formatNumber(summary.total_weight_kg ?? 0),
align: 'right',
},
{ key: 'sales_price', value: '' },
{
key: 'hpp_price',
value: formatCurrency(summary.total_hpp_price_per_kg ?? 0),
align: 'right',
},
{
key: 'sales_amount',
value: formatCurrency(summary.total_sales_amount ?? 0),
align: 'right',
},
{
key: 'hpp_amount',
value: formatCurrency(summary.total_hpp_amount ?? 0),
align: 'right',
},
];
};
},
{
key: 'qty',
header: 'Quantity',
flex: 0.7,
align: 'right',
cell: ({ row }) => formatNumber(row.qty ?? 0),
footer: summary ? formatNumber(summary.total_qty ?? 0) : '',
footerAlign: 'right',
},
{
key: 'avg_weight',
header: 'Rata-Rata (Kg)',
flex: 0.8,
align: 'right',
cell: ({ row }) => formatNumber(row.average_weight_kg ?? 0),
footer: summary ? formatNumber(summary.total_weight_kg ?? 0) : '',
footerAlign: 'right',
},
{
key: 'total_weight',
header: 'Total Berat (Kg)',
flex: 0.9,
align: 'right',
cell: ({ row }) => formatNumber(row.total_weight_kg ?? 0),
footer: summary ? formatNumber(summary.total_weight_kg ?? 0) : '',
footerAlign: 'right',
},
{
key: 'sales_price',
header: 'Harga Jual (Rp)',
flex: 0.9,
align: 'right',
cell: ({ row }) => formatCurrency(row.sales_price_per_kg ?? 0),
footer: '',
},
{
key: 'hpp_price',
header: 'HPP (Rp)',
flex: 1.3,
align: 'right',
cell: ({ row }) => formatCurrency(row.hpp_price_per_kg ?? 0),
footer: summary ? formatCurrency(summary.total_hpp_price_per_kg ?? 0) : '',
footerAlign: 'right',
},
{
key: 'sales_amount',
header: 'Total Jual (Rp)',
flex: 1,
align: 'right',
cell: ({ row }) => formatCurrency(row.sales_amount ?? 0),
footer: summary ? formatCurrency(summary.total_sales_amount ?? 0) : '',
footerAlign: 'right',
},
{
key: 'hpp_amount',
header: 'Total HPP (Rp)',
flex: 1.3,
align: 'right',
cell: ({ row }) => formatCurrency(row.hpp_amount ?? 0),
footer: summary ? formatCurrency(summary.total_hpp_amount ?? 0) : '',
footerAlign: 'right',
},
];
const DailyMarketingReportPDF = ({
data,
@@ -249,9 +255,9 @@ const DailyMarketingReportPDF = ({
{/* Table */}
<PdfTable
columns={getTableColumns()}
data={getTableData(rows)}
footer={getTableFooter(summary)}
columns={getTableColumns(summary)}
data={rows}
showFooter={!!summary}
footerLabel='TOTAL'
/>
@@ -14,12 +14,7 @@ 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';
import { PdfTable, PdfColumn } from '@/components/helper/pdf/table';
import { PdfParamBadge } from '@/components/helper/pdf/badge/PdfParamBadge';
import { PdfTypography } from '@/components/helper/pdf/typography/PdfTypography';
@@ -70,234 +65,185 @@ const formatSuppliers = (
};
// 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' },
const getRekapitulasiColumns = (): PdfColumn<HppPerKandangPerWeightRange>[] => [
{
key: 'rentang_bw',
header: 'Rentang BW',
flex: 1.2,
align: 'center',
cell: ({ row }) => row.label || '-',
},
{
key: 'sisa_butir',
header: 'Sisa Butir',
flex: 1,
align: 'right',
cell: ({ row }) => formatNumber(row.egg_production_pieces || 0),
},
{
key: 'sisa_kg',
header: 'Sisa Kg',
flex: 1,
align: 'right',
cell: ({ row }) => formatNumber(row.egg_production_kg || 0),
},
{
key: 'rata_rata_bobot',
header: 'Rata-Rata Bobot (Kg)',
flex: 1.2,
align: 'right',
cell: ({ row }) => formatNumber(row.avg_weight_kg || 0),
},
{
key: 'feed_supplier',
header: 'Feed (Supplier)',
flex: 1.5,
align: 'left',
cell: ({ row }) => formatSuppliers(row.feed_suppliers),
},
{
key: 'doc_supplier',
header: 'DOC (Supplier)',
flex: 1.2,
align: 'left',
cell: ({ row }) => formatSuppliers(row.doc_suppliers),
},
{
key: 'rata_harga_doc',
header: 'Rata-Rata Harga DOC',
flex: 1.2,
align: 'right',
cell: ({ row }) => formatCurrency(row.average_doc_price_rp || 0),
},
{
key: 'hpp_telur',
header: 'HPP Telur (Rp/Kg)',
flex: 1.2,
align: 'right',
cell: ({ row }) => formatCurrency(row.egg_hpp_rp_per_kg || 0),
},
{
key: 'nominal_sisa',
header: 'Nominal Sisa',
flex: 1.2,
align: 'right',
cell: ({ row }) => formatCurrency(row.egg_value_rp || 0),
},
];
const getRekapitulasiData = (
perWeightRange: HppPerKandangPerWeightRange[]
): PdfTbodyCell[][] => {
return perWeightRange.map((group) => [
{ key: 'rentang_bw', value: group.label || '-' },
{
key: 'sisa_butir',
value: formatNumber(group.egg_production_pieces || 0),
align: 'right',
},
{
key: 'sisa_kg',
value: formatNumber(group.egg_production_kg || 0),
align: 'right',
},
{
key: 'rata_rata_bobot',
value: formatNumber(group.avg_weight_kg || 0),
align: 'right',
},
{
key: 'feed_supplier',
value: formatSuppliers(group.feed_suppliers),
},
{
key: 'doc_supplier',
value: formatSuppliers(group.doc_suppliers),
},
{
key: 'rata_harga_doc',
value: formatCurrency(group.average_doc_price_rp || 0),
align: 'right',
},
{
key: 'hpp_telur',
value: formatCurrency(group.egg_hpp_rp_per_kg || 0),
align: 'right',
},
{
key: 'nominal_sisa',
value: formatCurrency(group.egg_value_rp || 0),
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' },
const getDetailColumns = (
summary?: HppPerKandangReport['summary'],
allFeedSuppliers?: string,
allDocSuppliers?: string
): PdfColumn<HppPerKandangRow>[] => [
{
key: 'no',
header: 'No',
flex: 0.5,
align: 'center',
cell: ({ row, index }) => index + 1,
footer: 'TOTAL',
},
{
key: 'kandang',
header: 'Kandang',
flex: 1.5,
align: 'left',
cell: ({ row }) => row.kandang?.name || '-',
footer: 'ALL',
},
{
key: 'rentang_bw',
header: 'Rentang BW',
flex: 1,
align: 'left',
cell: ({ row }) =>
`${row.weight_range.weight_min.toFixed(2)} - ${row.weight_range.weight_max.toFixed(2)}`,
footer: '-',
},
{
key: 'rata_rata_bobot',
header: 'Rata-Rata Bobot (Kg)',
flex: 1,
align: 'right',
cell: ({ row }) => formatNumber(row.avg_weight_kg || 0),
footer: summary ? formatNumber(summary.total.average_weight_kg || 0) : '',
footerAlign: 'right',
},
{
key: 'sisa_butir',
header: 'Sisa Butir',
flex: 0.8,
align: 'right',
cell: ({ row }) => formatNumber(row.egg_production_pieces || 0),
footer: summary
? formatNumber(summary.total.total_egg_production_pieces || 0)
: '',
footerAlign: 'right',
},
{
key: 'sisa_kg',
header: 'Sisa Kg (Telur)',
flex: 0.8,
align: 'right',
cell: ({ row }) => formatNumber(row.egg_production_kg || 0),
footer: summary
? formatNumber(summary.total.total_egg_production_kg || 0)
: '',
footerAlign: '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',
cell: ({ row }) => formatSuppliers(row.feed_suppliers),
footer: allFeedSuppliers || '-',
},
{
key: 'doc_supplier',
header: 'DOC (Supplier)',
flex: 1,
align: 'left',
cell: ({ row }) => formatSuppliers(row.doc_suppliers),
footer: allDocSuppliers || '-',
},
{
key: 'rata_harga_doc',
header: 'Rata-Rata Harga DOC',
flex: 1.2,
align: 'right',
cell: ({ row }) => formatCurrency(row.average_doc_price_rp || 0),
footer: summary
? formatCurrency(summary.total.total_average_doc_price_rp || 0)
: '',
footerAlign: 'right',
},
{
key: 'hpp_telur',
header: 'HPP Telur (Rp/Kg)',
flex: 1,
align: 'right',
cell: ({ row }) => formatCurrency(row.egg_hpp_rp_per_kg || 0),
footer: summary
? formatCurrency(summary.total.average_egg_hpp_rp_per_kg || 0)
: '',
footerAlign: 'right',
},
{
key: 'nominal_sisa',
header: 'Nominal Sisa',
flex: 1.2,
align: 'right',
cell: ({ row }) => formatCurrency(row.egg_value_rp || 0),
footer: summary
? formatCurrency(summary.total.total_egg_value_rp || 0)
: '',
footerAlign: 'right',
},
];
const getDetailData = (rows: HppPerKandangRow[]): PdfTbodyCell[][] => {
return rows.map((item, index) => [
{ key: 'no', value: index + 1 },
{ 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 || 0),
align: 'right',
},
{
key: 'sisa_butir',
value: formatNumber(item.egg_production_pieces || 0),
align: 'right',
},
{
key: 'sisa_kg',
value: formatNumber(item.egg_production_kg || 0),
align: 'right',
},
{
key: 'feed_supplier',
value: formatSuppliers(item.feed_suppliers),
},
{
key: 'doc_supplier',
value: formatSuppliers(item.doc_suppliers),
},
{
key: 'rata_harga_doc',
value: formatCurrency(item.average_doc_price_rp || 0),
align: 'right',
},
{
key: 'hpp_telur',
value: formatCurrency(item.egg_hpp_rp_per_kg || 0),
align: 'right',
},
{
key: 'nominal_sisa',
value: formatCurrency(item.egg_value_rp || 0),
align: 'right',
},
]);
};
const getDetailFooter = (
summary: HppPerKandangReport['summary'],
allFeedSuppliers: string,
allDocSuppliers: string
): PdfTfootCell[] => {
if (!summary?.total) return [];
return [
{ key: 'no', value: 'TOTAL' },
{ key: 'kandang', value: 'ALL' },
{ key: 'rentang_bw', value: '-' },
{
key: 'rata_rata_bobot',
value: formatNumber(summary.total.average_weight_kg || 0),
align: 'right',
},
{
key: 'sisa_butir',
value: formatNumber(summary.total.total_egg_production_pieces || 0),
align: 'right',
},
{
key: 'sisa_kg',
value: formatNumber(summary.total.total_egg_production_kg || 0),
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 || 0),
align: 'right',
},
{
key: 'hpp_telur',
value: formatCurrency(summary.total.average_egg_hpp_rp_per_kg || 0),
align: 'right',
},
{
key: 'nominal_sisa',
value: formatCurrency(summary.total.total_egg_value_rp || 0),
align: 'right',
},
];
};
const createPDFDocument = (
params: HppPerKandangExportParams,
allFeedSuppliers: string,
@@ -355,7 +301,7 @@ const createPDFDocument = (
</PdfTypography>
<PdfTable
columns={getRekapitulasiColumns()}
data={getRekapitulasiData(rekapitulasiByWeightRange)}
data={rekapitulasiByWeightRange}
/>
</View>
@@ -365,17 +311,13 @@ const createPDFDocument = (
Detail Per Kandang
</PdfTypography>
<PdfTable
columns={getDetailColumns()}
data={getDetailData(params.data.rows)}
footer={
params.data.summary
? getDetailFooter(
params.data.summary,
allFeedSuppliers,
allDocSuppliers
)
: undefined
}
columns={getDetailColumns(
params.data.summary,
allFeedSuppliers,
allDocSuppliers
)}
data={params.data.rows}
showFooter={!!params.data.summary}
footerLabel='TOTAL'
/>
</View>