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>
);
}