refactor(FE): Refactor PDF generation for purchases per supplier

This commit is contained in:
rstubryan
2026-02-10 11:43:23 +07:00
parent 4f9401ed34
commit def894e5f4
2 changed files with 162 additions and 152 deletions
@@ -2,7 +2,6 @@
import { import {
Page, Page,
Text,
View, View,
Document, Document,
StyleSheet, StyleSheet,
@@ -15,7 +14,11 @@ import {
PdfTable, PdfTable,
PdfColumn, PdfColumn,
PdfTbodyCell, PdfTbodyCell,
PdfTfootCell,
} from '@/components/helper/pdf/table'; } 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';
Font.register({ Font.register({
family: 'Helvetica', family: 'Helvetica',
@@ -32,53 +35,16 @@ const pdfStyles = StyleSheet.create({
titleSection: { titleSection: {
marginBottom: 10, marginBottom: 10,
}, },
mainTitle: {
fontSize: 14,
fontWeight: 'bold',
marginBottom: 5,
color: '#1f74bf',
},
supplierTitle: {
fontSize: 12,
fontWeight: 'bold',
marginBottom: 8,
color: '#1f74bf',
},
badge: {
backgroundColor: '#1f74bf',
color: '#FFFFFF',
padding: 2,
borderRadius: 2,
fontSize: 7,
fontWeight: 'bold',
alignSelf: 'center',
marginRight: 4,
},
parameterBadge: {
backgroundColor: '#F5F5F5',
color: '#333333',
padding: 4,
borderRadius: 4,
fontSize: 8,
marginRight: 8,
marginBottom: 4,
},
parameterContainer: { parameterContainer: {
flexDirection: 'row', flexDirection: 'row',
flexWrap: 'wrap', flexWrap: 'wrap',
marginBottom: 8, marginBottom: 8,
}, },
supplierSection: {
marginBottom: 10,
},
supplierSectionBreak: {
marginBottom: 15,
},
}); });
interface PurchasesPerSupplierExportParams { interface PurchasesPerSupplierExportParams {
data: LogisticPurchasePerSupplierReport[]; data: LogisticPurchasePerSupplierReport[];
params: { params?: {
area_name?: string; area_name?: string;
supplier_name?: string; supplier_name?: string;
product_name?: string; product_name?: string;
@@ -92,73 +58,57 @@ interface PurchasesPerSupplierExportParams {
}; };
} }
const getParameterText = (
params: PurchasesPerSupplierExportParams['params']
) => {
const paramsText = [];
if (params.supplier_name) {
paramsText.push(`Supplier: ${params.supplier_name}`);
} else {
paramsText.push('Semua Supplier');
}
if (params.start_date && params.end_date) {
const startDate = formatDate(params.start_date, 'DD MMM YYYY');
const endDate = formatDate(params.end_date, 'DD MMM YYYY');
paramsText.push(`Periode: ${startDate} - ${endDate}`);
} else if (params.start_date) {
const startDate = formatDate(params.start_date, 'DD MMM YYYY');
paramsText.push(`Tanggal: ${startDate}`);
}
const currentDate = formatDate(new Date(), 'DD MMM YYYY HH:mm');
paramsText.push(`Dicetak: ${currentDate}`);
return paramsText;
};
// Helper functions for PdfTable
const getTableColumns = (): PdfColumn[] => [ const getTableColumns = (): PdfColumn[] => [
{ key: 'no', header: 'No', flex: 0.5, align: 'center' }, { 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: 'receive_date',
{ key: 'po_number', header: 'Referensi', flex: 1, align: 'left' }, header: 'Tanggal Terima',
{ key: 'product', header: 'Produk', flex: 1, align: 'left' }, flex: 1.2,
{ key: 'warehouse', header: 'Tujuan', flex: 1, align: 'left' }, align: 'center',
{ key: 'qty', header: 'Qty', flex: 0.8, align: 'right' }, },
{ key: 'unit_price', header: 'Harga Beli', flex: 1.2, align: 'right' }, { key: 'po_date', header: 'Tanggal PO', flex: 1.2, align: 'center' },
{ key: 'po_number', header: 'No. Referensi', flex: 1.5, align: 'left' },
{ key: 'product', header: 'Nama Produk', flex: 2, align: 'left' },
{ key: 'warehouse', header: 'Tujuan', flex: 1.5, align: 'left' },
{ key: 'qty', header: 'QTY', flex: 0.8, align: 'right' },
{ key: 'unit_price', header: 'Harga Beli (Rp)', flex: 1.5, align: 'right' },
{ {
key: 'purchase_value', key: 'purchase_value',
header: 'Nilai Pembelian', header: 'Value Harga Beli (Rp)',
flex: 1.5, flex: 1.8,
align: 'right', align: 'right',
}, },
{ {
key: 'transport_price', key: 'transport_unit_price',
header: 'Biaya Transport', header: 'Transport (Rp)',
flex: 1.2, flex: 1.3,
align: 'right', align: 'right',
}, },
{ key: 'total_amount', header: 'Total', flex: 1.5, align: 'right' }, {
{ key: 'expedition', header: 'Armada', flex: 1.2, align: 'center' }, key: 'transport_value',
{ key: 'delivery_number', header: 'Surat Jalan', flex: 1, align: 'left' }, header: 'Value Transport (Rp)',
flex: 1.8,
align: 'right',
},
{ key: 'total_amount', header: 'Jumlah (Rp)', flex: 1.5, align: 'right' },
{ key: 'expedition', header: 'Ekspedisi', flex: 1.2, align: 'center' },
{ key: 'delivery_number', header: 'Surat Jalan', flex: 1.2, align: 'left' },
]; ];
const getTableData = ( const getTableData = (
rows: LogisticPurchasePerSupplierReport['rows'] rows: LogisticPurchasePerSupplierReport['rows']
): PdfTbodyCell[][] => { ): PdfTbodyCell[][] => {
return rows.map((item, index) => [ return rows.map((item, index) => [
{ key: 'no', value: index + 1, align: 'center' }, { key: 'no', value: index + 1 },
{ {
key: 'receive_date', key: 'receive_date',
value: formatDate(item.receive_date, 'DD-MMM-YYYY'), value: item.receive_date
align: 'center', ? formatDate(item.receive_date, 'DD MMM YY')
: '-',
}, },
{ {
key: 'po_date', key: 'po_date',
value: formatDate(item.po_date, 'DD-MMM-YYYY'), value: item.po_date ? formatDate(item.po_date, 'DD MMM YY') : '-',
align: 'center',
}, },
{ key: 'po_number', value: item.po_number || '-' }, { key: 'po_number', value: item.po_number || '-' },
{ key: 'product', value: item.product?.name || '-' }, { key: 'product', value: item.product?.name || '-' },
@@ -175,10 +125,15 @@ const getTableData = (
align: 'right', align: 'right',
}, },
{ {
key: 'transport_price', key: 'transport_unit_price',
value: formatCurrency(item.transport_unit_price || 0), value: formatCurrency(item.transport_unit_price || 0),
align: 'right', align: 'right',
}, },
{
key: 'transport_value',
value: formatCurrency(item.transport_value || 0),
align: 'right',
},
{ {
key: 'total_amount', key: 'total_amount',
value: formatCurrency(item.total_amount || 0), value: formatCurrency(item.total_amount || 0),
@@ -186,82 +141,134 @@ const getTableData = (
}, },
{ {
key: 'expedition', key: 'expedition',
value: ( value: item.expedition ? (
<View style={pdfStyles.badge}> <View style={{ alignItems: 'center' }}>
<Text>{item.expedition || '-'}</Text> <PdfStatusBadge
backgroundColor='#DBEAFE'
textColor='#1E40AF'
borderColor='#60A5FA'
>
{item.expedition}
</PdfStatusBadge>
</View> </View>
) : (
'-'
), ),
align: 'center',
}, },
{ key: 'delivery_number', value: item.delivery_number || '-' }, { key: 'delivery_number', value: item.delivery_number || '-' },
]); ]);
}; };
const createPDFDocument = ( const getTableFooter = (
supplierReports: LogisticPurchasePerSupplierReport[], summary: LogisticPurchasePerSupplierReport['summary']
params: PurchasesPerSupplierExportParams['params'] ): PdfTfootCell[] => [
) => ( { key: 'no', value: 'Total' },
<Document> { key: 'receive_date', value: '' },
<Page size='A3' orientation='landscape' style={pdfStyles.page}> { key: 'po_date', value: '' },
{/* Title and Parameters */} { key: 'po_number', value: '' },
<View style={pdfStyles.titleSection}> { key: 'product', value: '' },
<Text style={pdfStyles.mainTitle}> { key: 'warehouse', value: '' },
Laporan &gt; Rekapitulasi Pembelian Per Supplier {
</Text> key: 'qty',
<View style={pdfStyles.parameterContainer}> value: formatNumber(summary?.total_qty || 0),
<View style={pdfStyles.parameterBadge}> align: 'right',
<Text> },
Jenis Tanggal:{' '} { key: 'unit_price', value: '' },
{params.filter_by === 'received_date' {
? 'Tanggal Terima' key: 'purchase_value',
: 'Tanggal PO'} value: formatCurrency(summary?.total_purchase_value || 0),
</Text> align: 'right',
},
{ key: 'transport_unit_price', value: '' },
{
key: 'transport_value',
value: formatCurrency(summary?.total_transport_value || 0),
align: 'right',
},
{
key: 'total_amount',
value: formatCurrency(summary?.total_amount || 0),
align: 'right',
},
{ key: 'expedition', value: '' },
{ key: 'delivery_number', value: '' },
];
const createPDFDocument = (params: PurchasesPerSupplierExportParams) => {
return (
<Document>
{params.data.map((supplierReport, supplierIndex) => (
<Page
key={supplierIndex}
size='A3'
orientation='landscape'
style={pdfStyles.page}
>
{/* Title and Parameters */}
<View style={pdfStyles.titleSection}>
<PdfTypography size='h1' variant='primary'>
Laporan &gt; Rekapitulasi Pembelian Per Supplier
</PdfTypography>
<View style={pdfStyles.parameterContainer}>
<PdfParamBadge>
Jenis Tanggal:{' '}
{params.params?.filter_by === 'received_date'
? 'Tanggal Terima'
: 'Tanggal PO'}
</PdfParamBadge>
<PdfParamBadge>
Periode:{' '}
{params.params?.start_date
? formatDate(params.params.start_date, 'DD MMM YYYY')
: '-'}{' '}
s.d{' '}
{params.params?.end_date
? formatDate(params.params.end_date, 'DD MMM YYYY')
: '-'}
</PdfParamBadge>
<PdfParamBadge>
Supplier: {params.params?.supplier_name || 'Semua Supplier'}
</PdfParamBadge>
<PdfParamBadge>
Area: {params.params?.area_name || 'Semua Area'}
</PdfParamBadge>
<PdfParamBadge>
Produk: {params.params?.product_name || 'Semua Produk'}
</PdfParamBadge>
<PdfParamBadge>
Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
</PdfParamBadge>
</View>
<PdfTypography size='h2' variant='primary'>
{supplierReport.supplier.name}
</PdfTypography>
{supplierReport.supplier.address && (
<PdfTypography size='label'>
Alamat: {supplierReport.supplier.address}
</PdfTypography>
)}
</View> </View>
{getParameterText(params).map((param, index) => (
<View key={index} style={pdfStyles.parameterBadge}>
<Text>{param}</Text>
</View>
))}
</View>
</View>
{/* Supplier Sections */} {/* Table */}
{supplierReports.map( <PdfTable
( columns={getTableColumns()}
supplierReport: LogisticPurchasePerSupplierReport, data={getTableData(supplierReport.rows)}
supplierIndex: number footer={
) => { supplierReport.summary
return ( ? getTableFooter(supplierReport.summary)
<View : undefined
key={supplierReport.supplier.id} }
style={[ />
pdfStyles.supplierSection, </Page>
supplierIndex < supplierReports.length - 1 ))}
? pdfStyles.supplierSectionBreak </Document>
: {}, );
]} };
>
<Text style={pdfStyles.supplierTitle}>
{supplierReport.supplier.name}
</Text>
<PdfTable
columns={getTableColumns()}
data={getTableData(supplierReport.rows)}
/>
</View>
);
}
)}
</Page>
</Document>
);
export const generatePurchasesPerSupplierPDF = async ( export const generatePurchasesPerSupplierPDF = async (
data: LogisticPurchasePerSupplierReport[], params: PurchasesPerSupplierExportParams
params: PurchasesPerSupplierExportParams['params']
): Promise<void> => { ): Promise<void> => {
const PDFDocument = createPDFDocument(data, params); const PDFDocument = createPDFDocument(params);
try { try {
const blob = await pdf(PDFDocument).toBlob(); const blob = await pdf(PDFDocument).toBlob();
@@ -433,7 +433,10 @@ const PurchasesPerSupplierTab = () => {
end_date: tableFilterState.end_date || '', end_date: tableFilterState.end_date || '',
}; };
await generatePurchasesPerSupplierPDF(allDataForExport, exportParams); await generatePurchasesPerSupplierPDF({
data: allDataForExport,
params: exportParams,
});
toast.success('PDF berhasil dibuat dan diunduh.'); toast.success('PDF berhasil dibuat dan diunduh.');
} catch { } catch {
toast.error('Gagal membuat PDF. Silakan coba lagi.'); toast.error('Gagal membuat PDF. Silakan coba lagi.');