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
@@ -7,6 +7,7 @@ import {
StyleSheet,
Font,
pdf,
Text,
} from '@react-pdf/renderer';
import {
@@ -16,12 +17,7 @@ import {
formatTitleCase,
} from '@/lib/helper';
import { CustomerPaymentReport } from '@/types/api/report/customer-payment';
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';
@@ -61,172 +57,183 @@ interface CustomerPaymentExportPDFParams {
};
}
const getTableColumns = (): PdfColumn[] => [
{ key: 'no', header: 'No', flex: 0.5, align: 'center' },
{ key: 'trans_date', header: 'Tanggal DO', flex: 1.2, align: 'center' },
const getTableColumns = (
summary?: CustomerPaymentReport['summary']
): PdfColumn<CustomerPaymentReport['rows'][number]>[] => [
{
key: 'no',
header: 'No',
flex: 0.5,
align: 'center',
cell: ({ row, index }) => index + 1,
footer: 'Total',
},
{
key: 'trans_date',
header: 'Tanggal DO',
flex: 1.2,
align: 'center',
cell: ({ row }) =>
row.trans_date ? formatDate(row.trans_date, 'DD MMM YY') : '-',
footer: '',
},
{
key: 'delivery_date',
header: 'Tanggal Realisasi',
flex: 1.2,
align: 'center',
cell: ({ row }) =>
row.delivery_date ? formatDate(row.delivery_date, 'DD MMM YY') : '-',
footer: '',
},
{
key: 'aging',
header: 'Aging',
flex: 0.8,
align: 'center',
cell: ({ row }) =>
row.aging_day != null ? `${formatNumber(row.aging_day)} hari` : '-',
footer: '',
},
{
key: 'reference',
header: 'Referensi',
flex: 1.5,
align: 'left',
cell: ({ row }) => row.reference || '-',
footer: '',
},
{
key: 'vehicle_numbers',
header: 'No Polisi',
flex: 1.2,
align: 'left',
cell: ({ row }) =>
Array.isArray(row.vehicle_numbers) && row.vehicle_numbers.length > 0
? row.vehicle_numbers.join(', ')
: '-',
footer: '',
},
{
key: 'qty',
header: 'Qty',
flex: 0.8,
align: 'right',
cell: ({ row }) => formatNumber(row.qty),
footer: summary ? formatNumber(summary.total_qty || 0) : '',
footerAlign: 'right',
},
{
key: 'weight',
header: 'Berat',
flex: 1,
align: 'right',
cell: ({ row }) => formatNumber(row.weight),
footer: summary ? formatNumber(summary.total_weight || 0) : '',
footerAlign: 'right',
},
{
key: 'average_weight',
header: 'Rata-Rata',
flex: 0.8,
align: 'right',
cell: ({ row }) => formatNumber(row.average_weight),
footer: '',
},
{
key: 'unit_price',
header: 'Harga/Unit (Rp)',
flex: 1.2,
align: 'right',
cell: ({ row }) => formatCurrency(row.unit_price),
footer: '',
},
{
key: 'final_price',
header: 'Harga Akhir (Rp)',
flex: 1.2,
align: 'right',
cell: ({ row }) => formatCurrency(row.final_price),
footer: summary ? formatCurrency(summary.total_final_amount || 0) : '',
footerAlign: 'right',
},
{
key: 'total_price',
header: 'Total (Rp)',
flex: 1.2,
align: 'right',
cell: ({ row }) => formatCurrency(row.total_price),
footer: summary ? formatCurrency(summary.total_grand_amount || 0) : '',
footerAlign: 'right',
},
{ key: 'aging', header: 'Aging', flex: 0.8, align: 'center' },
{ key: 'reference', header: 'Referensi', flex: 1.5, align: 'left' },
{ key: 'vehicle_numbers', header: 'No Polisi', flex: 1.2, align: 'left' },
{ key: 'qty', header: 'Qty', flex: 0.8, align: 'right' },
{ key: 'weight', header: 'Berat', flex: 1, align: 'right' },
{ key: 'average_weight', header: 'Rata-Rata', flex: 0.8, align: 'right' },
{ key: 'unit_price', header: 'Harga/Unit (Rp)', flex: 1.2, align: 'right' },
{ key: 'final_price', header: 'Harga Akhir (Rp)', flex: 1.2, align: 'right' },
{ key: 'total_price', header: 'Total (Rp)', flex: 1.2, align: 'right' },
{
key: 'payment_amount',
header: 'Pembayaran (Rp)',
flex: 1.2,
align: 'right',
cell: ({ row }) => formatCurrency(row.payment_amount),
footer: summary ? formatCurrency(summary.total_payment || 0) : '',
footerAlign: 'right',
},
{
key: 'accounts_receivable',
header: 'Saldo (Rp)',
flex: 1.2,
align: 'right',
cell: ({ row }) => (
<Text style={{ color: row.accounts_receivable < 0 ? 'red' : 'black' }}>
{formatCurrency(row.accounts_receivable)}
</Text>
),
footer: summary
? formatCurrency(summary.total_accounts_receivable || 0)
: '',
footerAlign: 'right',
footerColor:
(summary?.total_accounts_receivable || 0) < 0 ? 'red' : undefined,
},
{ key: 'status', header: 'Keterangan', flex: 1.5, align: 'center' },
{ key: 'pickup_info', header: 'Pengambilan', flex: 1, align: 'left' },
{ key: 'sales_person', header: 'Sales', flex: 1.5, align: 'left' },
];
const getTableData = (
rows: CustomerPaymentReport['rows']
): PdfTbodyCell[][] => {
return rows.map((item, index) => [
{ key: 'no', value: index + 1 },
{
key: 'trans_date',
value: item.trans_date ? formatDate(item.trans_date, 'DD MMM YY') : '-',
},
{
key: 'delivery_date',
value: item.delivery_date
? formatDate(item.delivery_date, 'DD MMM YY')
: '-',
},
{
key: 'aging',
value:
item.aging_day != null ? `${formatNumber(item.aging_day)} hari` : '-',
},
{ key: 'reference', value: item.reference || '-' },
{
key: 'vehicle_numbers',
value:
Array.isArray(item.vehicle_numbers) && item.vehicle_numbers.length > 0
? item.vehicle_numbers.join(', ')
: '-',
},
{ key: 'qty', value: formatNumber(item.qty), align: 'right' },
{ key: 'weight', value: formatNumber(item.weight), align: 'right' },
{
key: 'average_weight',
value: formatNumber(item.average_weight),
align: 'right',
},
{
key: 'unit_price',
value: formatCurrency(item.unit_price),
align: 'right',
},
{
key: 'final_price',
value: formatCurrency(item.final_price),
align: 'right',
},
{
key: 'total_price',
value: formatCurrency(item.total_price),
align: 'right',
},
{
key: 'payment_amount',
value: formatCurrency(item.payment_amount),
align: 'right',
},
{
key: 'accounts_receivable',
value: formatCurrency(item.accounts_receivable),
align: 'right',
color: item.accounts_receivable < 0 ? 'red' : undefined,
},
{
key: 'status',
value: item.status ? (
{
key: 'status',
header: 'Keterangan',
flex: 1.5,
align: 'center',
cell: ({ row }) =>
row.status ? (
<View style={{ alignItems: 'center' }}>
<PdfStatusBadge
style={{
backgroundColor: getPDFBadgeStyle(item.status, 'payment').bg,
color: getPDFBadgeStyle(item.status, 'payment').text,
borderColor: getPDFBadgeStyle(item.status, 'payment').border,
backgroundColor: getPDFBadgeStyle(row.status, 'payment').bg,
color: getPDFBadgeStyle(row.status, 'payment').text,
borderColor: getPDFBadgeStyle(row.status, 'payment').border,
}}
>
{formatTitleCase(item.status)}
{formatTitleCase(row.status)}
</PdfStatusBadge>
</View>
) : (
'-'
),
},
{
key: 'pickup_info',
value:
Array.isArray(item.pickup_info) && item.pickup_info.length > 0
? item.pickup_info.join(', ')
: '-',
},
{ key: 'sales_person', value: item.sales_person || '-' },
]);
};
const getTableFooter = (
summary: CustomerPaymentReport['summary']
): PdfTfootCell[] => [
{ key: 'no', value: 'Total' },
{ key: 'trans_date', value: '' },
{ key: 'delivery_date', value: '' },
{ key: 'aging', value: '' },
{ key: 'reference', value: '' },
{ key: 'vehicle_numbers', value: '' },
{ key: 'qty', value: formatNumber(summary?.total_qty || 0), align: 'right' },
{
key: 'weight',
value: formatNumber(summary?.total_weight || 0),
align: 'right',
},
{ key: 'average_weight', value: '' },
{ key: 'unit_price', value: '' },
{
key: 'final_price',
value: formatCurrency(summary?.total_final_amount || 0),
align: 'right',
footer: '',
},
{
key: 'total_price',
value: formatCurrency(summary?.total_grand_amount || 0),
align: 'right',
key: 'pickup_info',
header: 'Pengambilan',
flex: 1,
align: 'left',
cell: ({ row }) =>
Array.isArray(row.pickup_info) && row.pickup_info.length > 0
? row.pickup_info.join(', ')
: '-',
footer: '',
},
{
key: 'payment_amount',
value: formatCurrency(summary?.total_payment || 0),
align: 'right',
key: 'sales_person',
header: 'Sales',
flex: 1.5,
align: 'left',
cell: ({ row }) => row.sales_person || '-',
footer: '',
},
{
key: 'accounts_receivable',
value: formatCurrency(summary?.total_accounts_receivable || 0),
align: 'right',
color: (summary?.total_accounts_receivable || 0) < 0 ? 'red' : undefined,
},
{ key: 'status', value: '' },
{ key: 'pickup_info', value: '' },
{ key: 'sales_person', value: '' },
];
const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
@@ -276,13 +283,9 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
{/* Table */}
<PdfTable
columns={getTableColumns()}
data={getTableData(customerReport.rows)}
footer={
customerReport.summary
? getTableFooter(customerReport.summary)
: undefined
}
columns={getTableColumns(customerReport.summary)}
data={customerReport.rows}
showFooter={!!customerReport.summary}
firstRow={
typeof customerReport.initial_balance === 'number' &&
customerReport.initial_balance !== 0
@@ -14,13 +14,9 @@ import { DebtSupplier } from '@/types/api/report/debt-supplier';
import { PdfParamBadge } from '@/components/helper/pdf/badge/PdfParamBadge';
import { PdfTypography } from '@/components/helper/pdf/typography/PdfTypography';
import { PdfStatusBadge } from '@/components/helper/pdf/badge/PdfStatusBadge';
import {
PdfTable,
PdfColumn,
PdfTbodyCell,
PdfTfootCell,
} from '@/components/helper/pdf/table';
import { PdfTable, PdfColumn } from '@/components/helper/pdf/table';
import { getPDFBadgeStyle } from '@/components/helper/pdf/utils/pdf-badge';
import { Text } from '@react-pdf/renderer';
Font.register({
family: 'Helvetica',
@@ -44,160 +40,225 @@ const pdfStyles = StyleSheet.create({
},
});
const getTableColumns = (): PdfColumn[] => [
{ key: 'no', header: 'No', flex: 0.5, align: 'center' },
{ key: 'pr_number', header: 'No. PR', flex: 1, align: 'left' },
{ key: 'po_number', header: 'No. PO', flex: 1, align: 'left' },
{
key: 'received_date',
header: 'Tgl Terima/Bayar',
flex: 0.7,
align: 'center',
},
{ key: 'po_date', header: 'Tgl PO', flex: 0.7, align: 'center' },
{ key: 'aging', header: 'Aging', flex: 0.6, align: 'center' },
{ key: 'area', header: 'Area', flex: 1, align: 'left' },
{ key: 'warehouse', header: 'Gudang', flex: 1, align: 'left' },
{ key: 'due_date', header: 'Jatuh Tempo', flex: 1, align: 'center' },
{ key: 'due_status', header: 'Status Jatuh Tempo', flex: 2, align: 'center' },
{
key: 'total_price',
header: 'Nominal Pembelian (Rp)',
flex: 1.5,
align: 'right',
},
{
key: 'payment_price',
header: 'Pembayaran (Rp)',
flex: 1.5,
align: 'right',
},
{
key: 'balance',
header: 'Sisa Saldo Hutang (Rp)',
flex: 1.5,
align: 'right',
},
{ key: 'status', header: 'Status', flex: 1.2, align: 'center' },
{ key: 'travel_number', header: 'No. Perjalanan', flex: 1, align: 'left' },
];
const getTableColumns = (total?: DebtSupplier['total']): PdfColumn[] => {
type DebtRow = DebtSupplier['rows'][number];
const getTableData = (rows: DebtSupplier['rows']): PdfTbodyCell[][] => {
return rows.map((item, index) => [
{ key: 'no', value: index + 1 },
{ key: 'pr_number', value: item.pr_number || '-' },
{ key: 'po_number', value: item.po_number || '-' },
return [
{
key: 'no',
header: 'No',
flex: 0.5,
align: 'center',
cell: ({ row, index }) => index + 1,
footer: 'Total',
},
{
key: 'pr_number',
header: 'No. PR',
flex: 1,
align: 'left',
cell: ({ row }) => (row as unknown as DebtRow).pr_number || '-',
footer: '',
},
{
key: 'po_number',
header: 'No. PO',
flex: 1,
align: 'left',
cell: ({ row }) => (row as unknown as DebtRow).po_number || '-',
footer: '',
},
{
key: 'received_date',
value: item.received_date
? formatDate(item.received_date, 'DD MMM YY')
: '-',
header: 'Tgl Terima/Bayar',
flex: 0.7,
align: 'center',
cell: ({ row }) =>
(row as unknown as DebtRow).received_date
? formatDate((row as unknown as DebtRow).received_date, 'DD MMM YY')
: '-',
footer: '',
},
{
key: 'po_date',
value: item.po_date ? formatDate(item.po_date, 'DD MMM YY') : '-',
header: 'Tgl PO',
flex: 0.7,
align: 'center',
cell: ({ row }) =>
(row as unknown as DebtRow).po_date
? formatDate((row as unknown as DebtRow).po_date, 'DD MMM YY')
: '-',
footer: '',
},
{
key: 'aging',
value: item.aging != null ? `${formatNumber(item.aging)}` : '-',
header: 'Aging',
flex: 0.6,
align: 'center',
cell: ({ row }) =>
(row as unknown as DebtRow).aging != null
? `${formatNumber((row as unknown as DebtRow).aging)}`
: '-',
footer: total ? formatNumber(total.aging || 0) + ' Hari' : '',
},
{
key: 'area',
header: 'Area',
flex: 1,
align: 'left',
cell: ({ row }) => (row as unknown as DebtRow).area?.name || '-',
footer: '',
},
{
key: 'warehouse',
header: 'Gudang',
flex: 1,
align: 'left',
cell: ({ row }) => (row as unknown as DebtRow).warehouse?.name || '-',
footer: '',
},
{ key: 'area', value: item.area?.name || '-' },
{ key: 'warehouse', value: item.warehouse?.name || '-' },
{
key: 'due_date',
value: item.due_date ? formatDate(item.due_date, 'DD MMM YY') : '-',
header: 'Jatuh Tempo',
flex: 1,
align: 'center',
cell: ({ row }) =>
(row as unknown as DebtRow).due_date
? formatDate((row as unknown as DebtRow).due_date, 'DD MMM YY')
: '-',
footer: '',
},
{
key: 'due_status',
value:
item.due_status && item.due_status !== '-' ? (
header: 'Status Jatuh Tempo',
flex: 2,
align: 'center',
cell: ({ row }) =>
(row as unknown as DebtRow).due_status &&
(row as unknown as DebtRow).due_status !== '-' ? (
<View style={{ alignItems: 'center' }}>
<PdfStatusBadge
style={{
backgroundColor: getPDFBadgeStyle(item.due_status, 'due').bg,
color: getPDFBadgeStyle(item.due_status, 'due').text,
borderColor: getPDFBadgeStyle(item.due_status, 'due').border,
backgroundColor: getPDFBadgeStyle(
(row as unknown as DebtRow).due_status,
'due'
).bg,
color: getPDFBadgeStyle(
(row as unknown as DebtRow).due_status,
'due'
).text,
borderColor: getPDFBadgeStyle(
(row as unknown as DebtRow).due_status,
'due'
).border,
}}
>
{item.due_status}
{(row as unknown as DebtRow).due_status}
</PdfStatusBadge>
</View>
) : (
'-'
),
footer: '',
},
{
key: 'total_price',
value: formatCurrency(item.total_price),
header: 'Nominal Pembelian (Rp)',
flex: 1.5,
align: 'right',
color: item.total_price < 0 ? 'red' : undefined,
cell: ({ row }) => (
<Text
style={{
color:
(row as unknown as DebtRow).total_price < 0 ? 'red' : 'black',
}}
>
{formatCurrency((row as unknown as DebtRow).total_price)}
</Text>
),
footer: total ? formatCurrency(total.total_price || 0) : '',
footerAlign: 'right',
},
{
key: 'payment_price',
value: formatCurrency(item.payment_price),
header: 'Pembayaran (Rp)',
flex: 1.5,
align: 'right',
color: item.payment_price < 0 ? 'red' : undefined,
cell: ({ row }) => (
<Text
style={{
color:
(row as unknown as DebtRow).payment_price < 0 ? 'red' : 'black',
}}
>
{formatCurrency((row as unknown as DebtRow).payment_price)}
</Text>
),
footer: total ? formatCurrency(total.payment_price || 0) : '',
footerAlign: 'right',
},
{
key: 'balance',
value: formatCurrency(item.balance),
header: 'Sisa Saldo Hutang (Rp)',
flex: 1.5,
align: 'right',
color: item.balance < 0 ? 'red' : undefined,
cell: ({ row }) => (
<Text
style={{
color: (row as unknown as DebtRow).balance < 0 ? 'red' : 'black',
}}
>
{formatCurrency((row as unknown as DebtRow).balance)}
</Text>
),
footer: total ? formatCurrency(total.debt_price || 0) : '',
footerAlign: 'right',
footerColor: (total?.debt_price || 0) < 0 ? 'red' : undefined,
},
{
key: 'status',
value:
item.status && item.status !== '-' ? (
header: 'Status',
flex: 1.2,
align: 'center',
cell: ({ row }) =>
(row as unknown as DebtRow).status &&
(row as unknown as DebtRow).status !== '-' ? (
<View style={{ alignItems: 'center' }}>
<PdfStatusBadge
style={{
backgroundColor: getPDFBadgeStyle(item.status, 'payment').bg,
color: getPDFBadgeStyle(item.status, 'payment').text,
borderColor: getPDFBadgeStyle(item.status, 'payment').border,
backgroundColor: getPDFBadgeStyle(
(row as unknown as DebtRow).status,
'payment'
).bg,
color: getPDFBadgeStyle(
(row as unknown as DebtRow).status,
'payment'
).text,
borderColor: getPDFBadgeStyle(
(row as unknown as DebtRow).status,
'payment'
).border,
}}
>
{item.status}
{(row as unknown as DebtRow).status}
</PdfStatusBadge>
</View>
) : (
'-'
),
footer: '',
},
{ key: 'travel_number', value: item.travel_number || '-' },
]);
{
key: 'travel_number',
header: 'No. Perjalanan',
flex: 1,
align: 'left',
cell: ({ row }) => (row as unknown as DebtRow).travel_number || '-',
footer: '',
},
];
};
const getTableFooter = (total: DebtSupplier['total']): PdfTfootCell[] => [
{ key: 'no', value: 'Total' },
{ key: 'pr_number', value: '' },
{ key: 'po_number', value: '' },
{ key: 'received_date', value: '' },
{ key: 'po_date', value: '' },
{ key: 'aging', value: formatNumber(total?.aging || 0) + ' Hari' },
{ key: 'area', value: '' },
{ key: 'warehouse', value: '' },
{ key: 'due_date', value: '' },
{ key: 'due_status', value: '' },
{
key: 'total_price',
value: formatCurrency(total?.total_price || 0),
align: 'right',
},
{
key: 'payment_price',
value: formatCurrency(total?.payment_price || 0),
align: 'right',
},
{
key: 'balance',
value: formatCurrency(total?.debt_price || 0),
align: 'right',
color: (total?.debt_price || 0) < 0 ? 'red' : undefined,
},
{ key: 'status', value: '' },
{ key: 'travel_number', value: '' },
];
interface DebtSupplierExportPDFParams {
data: DebtSupplier[];
params?: {
@@ -263,13 +324,9 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
{/* Table */}
<PdfTable
columns={getTableColumns()}
data={getTableData(supplierReport.rows)}
footer={
supplierReport.total
? getTableFooter(supplierReport.total)
: undefined
}
columns={getTableColumns(supplierReport.total)}
data={supplierReport.rows as unknown as Record<string, unknown>[]}
showFooter={!!supplierReport.total}
firstRow={
typeof supplierReport.initial_balance === 'number' &&
supplierReport.initial_balance !== 0