mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'feat/reusable-pdf-component' into 'development'
[FEAT/FE] Reusable PDF Component and Hotfix Penjualan See merge request mbugroup/lti-web-client!320
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import { Text, View, StyleSheet } from '@react-pdf/renderer';
|
||||
|
||||
type PdfParamBadgeProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
parameterBadge: {
|
||||
backgroundColor: '#F5F5F5',
|
||||
color: '#333333',
|
||||
padding: 4,
|
||||
borderRadius: 4,
|
||||
fontSize: 8,
|
||||
marginRight: 8,
|
||||
marginBottom: 4,
|
||||
},
|
||||
});
|
||||
|
||||
export const PdfParamBadge = ({ children }: PdfParamBadgeProps) => {
|
||||
return (
|
||||
<View style={styles.parameterBadge}>
|
||||
<Text>{children}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Text, View, StyleSheet } from '@react-pdf/renderer';
|
||||
|
||||
type PdfStatusBadgeProps = {
|
||||
children: React.ReactNode;
|
||||
backgroundColor?: string;
|
||||
textColor?: string;
|
||||
borderColor?: string;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
statusBadge: {
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
borderRadius: 12,
|
||||
fontSize: 7,
|
||||
fontWeight: 'bold',
|
||||
borderWidth: 1,
|
||||
borderStyle: 'solid',
|
||||
},
|
||||
statusBadgeText: {
|
||||
fontSize: 7,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
export const PdfStatusBadge = ({
|
||||
children,
|
||||
backgroundColor = '#F5F5F5',
|
||||
textColor = '#333333',
|
||||
borderColor = '#E5E7EB',
|
||||
}: PdfStatusBadgeProps) => {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.statusBadge,
|
||||
{
|
||||
backgroundColor,
|
||||
borderColor,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text style={[styles.statusBadgeText, { color: textColor }]}>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Color } from '@/types/theme';
|
||||
import { Text, StyleSheet } from '@react-pdf/renderer';
|
||||
|
||||
type TypographySize = 'h1' | 'h2' | 'h3' | 'h4' | 'p' | 'small' | 'label';
|
||||
|
||||
type TypographyVariant = Color | 'default';
|
||||
|
||||
type PdfTypographyProps = {
|
||||
children: React.ReactNode;
|
||||
size?: TypographySize;
|
||||
variant?: TypographyVariant;
|
||||
color?: string;
|
||||
marginBottom?: number;
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
h1: {
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 5,
|
||||
},
|
||||
h2: {
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 8,
|
||||
},
|
||||
h3: {
|
||||
fontSize: 10,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 4,
|
||||
},
|
||||
h4: {
|
||||
fontSize: 9,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 3,
|
||||
},
|
||||
p: {
|
||||
fontSize: 10,
|
||||
marginBottom: 4,
|
||||
},
|
||||
small: {
|
||||
fontSize: 8,
|
||||
marginBottom: 2,
|
||||
},
|
||||
label: {
|
||||
fontSize: 9,
|
||||
marginBottom: 5,
|
||||
},
|
||||
});
|
||||
|
||||
const variantColors: Record<TypographyVariant, string> = {
|
||||
default: '#333333',
|
||||
primary: '#1f74bf',
|
||||
secondary: '#6B7280',
|
||||
accent: '#8B5CF6',
|
||||
neutral: '#6B7280',
|
||||
info: '#3B82F6',
|
||||
success: '#065F46',
|
||||
warning: '#92400E',
|
||||
error: '#DC2626',
|
||||
none: '#333333',
|
||||
};
|
||||
|
||||
export const PdfTypography = ({
|
||||
children,
|
||||
size = 'p',
|
||||
variant = 'default',
|
||||
color,
|
||||
marginBottom,
|
||||
}: PdfTypographyProps) => {
|
||||
const sizeStyle = styles[size];
|
||||
const textColor = color || variantColors[variant];
|
||||
|
||||
const customStyle = {
|
||||
...(marginBottom !== undefined && { marginBottom }),
|
||||
};
|
||||
|
||||
return (
|
||||
<Text style={[sizeStyle, { color: textColor }, customStyle]}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
@@ -198,6 +198,13 @@ const SalesOrderFormModal = ({
|
||||
: 'KG' // termasuk "QTY" dan "KG"
|
||||
: undefined;
|
||||
|
||||
// Jika value dari data product ada week, kirim "AYAM_PULLET, jika tidak ada kirim "AYAM"
|
||||
let marketingTypeValue =
|
||||
product.marketing_type?.value?.toUpperCase() || '';
|
||||
if (marketingTypeValue === 'AYAM,AYAM_PULLET') {
|
||||
marketingTypeValue = product.week ? 'AYAM_PULLET' : 'AYAM';
|
||||
}
|
||||
|
||||
return {
|
||||
vehicle_number: product.vehicle_number as string,
|
||||
kandang_id: product.kandang_id as number,
|
||||
@@ -207,8 +214,7 @@ const SalesOrderFormModal = ({
|
||||
qty: parseFloat(String(product.qty || 0)),
|
||||
avg_weight: parseFloat(String(product.avg_weight || 0)),
|
||||
total_price: parseFloat(String(product.total_price || 0)),
|
||||
marketing_type:
|
||||
product.marketing_type?.value?.toUpperCase() || '',
|
||||
marketing_type: marketingTypeValue,
|
||||
convertion_unit: normalizedConvertionUnit,
|
||||
weight_per_convertion:
|
||||
product.weight_per_convertion ?? undefined,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import {
|
||||
Page,
|
||||
Text,
|
||||
View,
|
||||
Document,
|
||||
StyleSheet,
|
||||
@@ -18,6 +17,9 @@ import {
|
||||
PdfTbodyCell,
|
||||
PdfTfootCell,
|
||||
} 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({
|
||||
family: 'Helvetica',
|
||||
@@ -34,53 +36,6 @@ const pdfStyles = StyleSheet.create({
|
||||
titleSection: {
|
||||
marginBottom: 10,
|
||||
},
|
||||
mainTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 5,
|
||||
color: '#1f74bf',
|
||||
},
|
||||
supplierTitle: {
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 8,
|
||||
color: '#1f74bf',
|
||||
},
|
||||
supplierInfo: {
|
||||
fontSize: 9,
|
||||
marginBottom: 5,
|
||||
color: '#333333',
|
||||
},
|
||||
badge: {
|
||||
backgroundColor: '#1f74bf',
|
||||
color: '#FFFFFF',
|
||||
padding: 2,
|
||||
borderRadius: 2,
|
||||
fontSize: 7,
|
||||
fontWeight: 'bold',
|
||||
alignSelf: 'center',
|
||||
marginRight: 4,
|
||||
},
|
||||
badgeLunas: {
|
||||
backgroundColor: '#1f74bf',
|
||||
color: '#FFFFFF',
|
||||
},
|
||||
badgeBelumLunas: {
|
||||
backgroundColor: '#F97316',
|
||||
color: '#FFFFFF',
|
||||
},
|
||||
textError: {
|
||||
color: '#DC2626',
|
||||
},
|
||||
parameterBadge: {
|
||||
backgroundColor: '#F5F5F5',
|
||||
color: '#333333',
|
||||
padding: 4,
|
||||
borderRadius: 4,
|
||||
fontSize: 8,
|
||||
marginRight: 8,
|
||||
marginBottom: 4,
|
||||
},
|
||||
parameterContainer: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
@@ -100,38 +55,6 @@ interface CustomerPaymentExportPDFParams {
|
||||
};
|
||||
}
|
||||
|
||||
const getParameterText = (
|
||||
params?: CustomerPaymentExportPDFParams['params']
|
||||
) => {
|
||||
const paramsText = [];
|
||||
|
||||
if (params?.customer_name) {
|
||||
paramsText.push(`Customer: ${params.customer_name}`);
|
||||
} else {
|
||||
paramsText.push('Semua Customer');
|
||||
}
|
||||
|
||||
// TODO: Uncomment when BE is ready
|
||||
// if (params?.sales) {
|
||||
// paramsText.push(`Sales: ${params.sales}`);
|
||||
// }
|
||||
|
||||
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[] => [
|
||||
{ key: 'no', header: 'No', flex: 0.5, align: 'center' },
|
||||
{ key: 'trans_date', header: 'Tanggal DO', flex: 1.2, align: 'center' },
|
||||
@@ -221,15 +144,14 @@ const getTableData = (
|
||||
{
|
||||
key: 'status',
|
||||
value: item.status ? (
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.badge,
|
||||
item.status === 'LUNAS'
|
||||
? pdfStyles.badgeLunas
|
||||
: pdfStyles.badgeBelumLunas,
|
||||
]}
|
||||
>
|
||||
<Text>{item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}</Text>
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
<PdfStatusBadge
|
||||
backgroundColor={item.status === 'LUNAS' ? '#DBEAFE' : '#FEE2E2'}
|
||||
textColor={item.status === 'LUNAS' ? '#1E40AF' : '#991B1B'}
|
||||
borderColor={item.status === 'LUNAS' ? '#60A5FA' : '#F87171'}
|
||||
>
|
||||
{item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}
|
||||
</PdfStatusBadge>
|
||||
</View>
|
||||
) : (
|
||||
'-'
|
||||
@@ -302,43 +224,37 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
|
||||
>
|
||||
{/* Title and Parameters */}
|
||||
<View style={pdfStyles.titleSection}>
|
||||
<Text style={pdfStyles.mainTitle}>
|
||||
<PdfTypography size='h1' variant='primary'>
|
||||
Laporan > Kontrol Pembayaran Customer
|
||||
</Text>
|
||||
</PdfTypography>
|
||||
<View style={pdfStyles.parameterContainer}>
|
||||
<View style={pdfStyles.parameterBadge}>
|
||||
<Text>
|
||||
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')
|
||||
: '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<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>
|
||||
{/* TODO: Uncomment when BE is ready */}
|
||||
{/* <View style={pdfStyles.parameterBadge}>
|
||||
<Text>Filter Tanggal: Tanggal DO</Text>
|
||||
</View> */}
|
||||
<View style={pdfStyles.parameterBadge}>
|
||||
<Text>
|
||||
Customer: {params.params?.customer_name || 'Semua Customer'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.parameterBadge}>
|
||||
<Text>
|
||||
Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
|
||||
</Text>
|
||||
</View>
|
||||
{/* <PdfParamBadge>
|
||||
Filter Tanggal: Tanggal DO
|
||||
</PdfParamBadge> */}
|
||||
<PdfParamBadge>
|
||||
Customer: {params.params?.customer_name || 'Semua Customer'}
|
||||
</PdfParamBadge>
|
||||
<PdfParamBadge>
|
||||
Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
|
||||
</PdfParamBadge>
|
||||
</View>
|
||||
<Text style={pdfStyles.supplierTitle}>
|
||||
<PdfTypography size='h2' variant='primary'>
|
||||
{customerReport.customer.name}
|
||||
</Text>
|
||||
<Text style={pdfStyles.supplierInfo}>
|
||||
</PdfTypography>
|
||||
<PdfTypography size='label'>
|
||||
Alamat: {customerReport.customer.address || '-'}
|
||||
</Text>
|
||||
</PdfTypography>
|
||||
</View>
|
||||
|
||||
{/* Table */}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import {
|
||||
Page,
|
||||
Text,
|
||||
View,
|
||||
Document,
|
||||
StyleSheet,
|
||||
@@ -12,6 +11,15 @@ import {
|
||||
|
||||
import { formatDate, formatCurrency, formatNumber } from '@/lib/helper';
|
||||
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';
|
||||
|
||||
Font.register({
|
||||
family: 'Helvetica',
|
||||
@@ -69,133 +77,6 @@ const pdfStyles = StyleSheet.create({
|
||||
titleSection: {
|
||||
marginBottom: 10,
|
||||
},
|
||||
mainTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 5,
|
||||
color: '#1f74bf',
|
||||
},
|
||||
supplierTitle: {
|
||||
fontSize: 12,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 8,
|
||||
color: '#1f74bf',
|
||||
},
|
||||
supplierInfo: {
|
||||
fontSize: 9,
|
||||
marginBottom: 5,
|
||||
color: '#333333',
|
||||
},
|
||||
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: 7,
|
||||
textAlign: 'left',
|
||||
},
|
||||
tableCellNo: {
|
||||
flex: 0.5,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableCellLast: {
|
||||
flex: 1,
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
},
|
||||
tableCellHeader: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
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: 7,
|
||||
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: 7,
|
||||
textAlign: 'right',
|
||||
},
|
||||
tableCellCenter: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderRightColor: '#000000',
|
||||
borderRightStyle: 'solid',
|
||||
padding: 4,
|
||||
fontSize: 7,
|
||||
textAlign: 'center',
|
||||
},
|
||||
tableBorderBottom: {
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#000000',
|
||||
borderBottomStyle: 'solid',
|
||||
},
|
||||
summaryRow: {
|
||||
backgroundColor: '#F0F0F0',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
badge: {
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
borderRadius: 12,
|
||||
fontSize: 5,
|
||||
fontWeight: 'bold',
|
||||
borderWidth: 1,
|
||||
textAlign: 'center',
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
parameterBadge: {
|
||||
backgroundColor: '#F5F5F5',
|
||||
color: '#333333',
|
||||
padding: 4,
|
||||
borderRadius: 4,
|
||||
fontSize: 8,
|
||||
marginRight: 8,
|
||||
marginBottom: 4,
|
||||
},
|
||||
parameterContainer: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
@@ -203,6 +84,153 @@ 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: 'left' },
|
||||
{
|
||||
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 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 || '-' },
|
||||
{
|
||||
key: 'received_date',
|
||||
value: item.received_date
|
||||
? formatDate(item.received_date, 'DD MMM YY')
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
key: 'po_date',
|
||||
value: item.po_date ? formatDate(item.po_date, 'DD MMM YY') : '-',
|
||||
},
|
||||
{
|
||||
key: 'aging',
|
||||
value: item.aging != null ? `${formatNumber(item.aging)}` : '-',
|
||||
},
|
||||
{ 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') : '-',
|
||||
},
|
||||
{
|
||||
key: 'due_status',
|
||||
value:
|
||||
item.due_status && item.due_status !== '-' ? (
|
||||
<PdfStatusBadge
|
||||
backgroundColor={getPDFBadgeStyle(item.due_status, 'due').bg}
|
||||
textColor={getPDFBadgeStyle(item.due_status, 'due').text}
|
||||
borderColor={getPDFBadgeStyle(item.due_status, 'due').border}
|
||||
>
|
||||
{item.due_status}
|
||||
</PdfStatusBadge>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'total_price',
|
||||
value: formatCurrency(item.total_price),
|
||||
align: 'right',
|
||||
color: item.total_price < 0 ? 'red' : undefined,
|
||||
},
|
||||
{
|
||||
key: 'payment_price',
|
||||
value: formatCurrency(item.payment_price),
|
||||
align: 'right',
|
||||
color: item.payment_price < 0 ? 'red' : undefined,
|
||||
},
|
||||
{
|
||||
key: 'balance',
|
||||
value: formatCurrency(item.balance),
|
||||
align: 'right',
|
||||
color: item.balance < 0 ? 'red' : undefined,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
value:
|
||||
item.status && item.status !== '-' ? (
|
||||
<View style={{ alignItems: 'center' }}>
|
||||
<PdfStatusBadge
|
||||
backgroundColor={getPDFBadgeStyle(item.status, 'payment').bg}
|
||||
textColor={getPDFBadgeStyle(item.status, 'payment').text}
|
||||
borderColor={getPDFBadgeStyle(item.status, 'payment').border}
|
||||
>
|
||||
{item.status}
|
||||
</PdfStatusBadge>
|
||||
</View>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
},
|
||||
{ key: 'travel_number', value: item.travel_number || '-' },
|
||||
]);
|
||||
};
|
||||
|
||||
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',
|
||||
},
|
||||
{ key: 'status', value: '' },
|
||||
{ key: 'travel_number', value: '' },
|
||||
];
|
||||
|
||||
interface DebtSupplierExportPDFParams {
|
||||
data: DebtSupplier[];
|
||||
params?: {
|
||||
@@ -219,418 +247,74 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => {
|
||||
{params.data.map((supplierReport, supplierIndex) => (
|
||||
<Page
|
||||
key={supplierIndex}
|
||||
size='A4'
|
||||
size='A3'
|
||||
orientation='landscape'
|
||||
style={pdfStyles.page}
|
||||
>
|
||||
{/* Title and Supplier Info */}
|
||||
<View style={pdfStyles.titleSection}>
|
||||
<Text style={pdfStyles.mainTitle}>
|
||||
<PdfTypography size='h1' variant='primary'>
|
||||
Laporan > Rekapitulasi Hutang ke Supplier
|
||||
</Text>
|
||||
</PdfTypography>
|
||||
<View style={pdfStyles.parameterContainer}>
|
||||
<View style={pdfStyles.parameterBadge}>
|
||||
<Text>
|
||||
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')
|
||||
: '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<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>
|
||||
{params.params?.filter_by && (
|
||||
<View style={pdfStyles.parameterBadge}>
|
||||
<Text>
|
||||
Filter Tanggal:{' '}
|
||||
{params.params.filter_by === 'po_date'
|
||||
? 'Tanggal PO'
|
||||
: params.params.filter_by === 'received_date'
|
||||
? 'Tanggal Terima'
|
||||
: params.params.filter_by === 'due_date'
|
||||
? 'Tanggal Jatuh Tempo'
|
||||
: params.params.filter_by}
|
||||
</Text>
|
||||
</View>
|
||||
<PdfParamBadge>
|
||||
Filter Tanggal:{' '}
|
||||
{params.params.filter_by === 'po_date'
|
||||
? 'Tanggal PO'
|
||||
: params.params.filter_by === 'received_date'
|
||||
? 'Tanggal Terima'
|
||||
: params.params.filter_by === 'due_date'
|
||||
? 'Tanggal Jatuh Tempo'
|
||||
: params.params.filter_by}
|
||||
</PdfParamBadge>
|
||||
)}
|
||||
<View style={pdfStyles.parameterBadge}>
|
||||
<Text>
|
||||
Supplier: {params.params?.supplier_name || 'Semua Supplier'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={pdfStyles.parameterBadge}>
|
||||
<Text>
|
||||
Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
|
||||
</Text>
|
||||
</View>
|
||||
<PdfParamBadge>
|
||||
Supplier: {params.params?.supplier_name || 'Semua Supplier'}
|
||||
</PdfParamBadge>
|
||||
<PdfParamBadge>
|
||||
Dicetak: {formatDate(new Date(), 'DD MMM YYYY HH:mm')}
|
||||
</PdfParamBadge>
|
||||
</View>
|
||||
<Text style={pdfStyles.supplierTitle}>
|
||||
<PdfTypography size='h2' variant='primary'>
|
||||
{supplierReport.supplier.name}
|
||||
</Text>
|
||||
<Text style={pdfStyles.supplierInfo}>
|
||||
</PdfTypography>
|
||||
<PdfTypography size='label'>
|
||||
{supplierReport.supplier.category}
|
||||
</Text>
|
||||
</PdfTypography>
|
||||
</View>
|
||||
|
||||
{/* Table */}
|
||||
<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 }]}>
|
||||
<Text>No. PR</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||
<Text>No. PO</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 0.7 }]}>
|
||||
<Text>Tgl Terima/Bayar</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 0.7 }]}>
|
||||
<Text>Tgl PO</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 0.6 }]}>
|
||||
<Text>Aging</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||
<Text>Area</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||
<Text>Gudang</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
|
||||
<Text>Jatuh Tempo</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 2 }]}>
|
||||
<Text>Status Jatuh Tempo</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.5 }]}>
|
||||
<Text>Nominal Pembelian (Rp)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.5 }]}>
|
||||
<Text>Pembayaran (Rp)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.5 }]}>
|
||||
<Text>Sisa Saldo Hutang (Rp)</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
|
||||
<Text>Status</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellHeader,
|
||||
{ flex: 1, borderRight: 'none' },
|
||||
]}
|
||||
>
|
||||
<Text>No. Perjalanan</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Initial Balance Row */}
|
||||
<View style={[pdfStyles.tableRow, pdfStyles.tableBorderBottom]}>
|
||||
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
|
||||
<Text></Text> {/* NO */}
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text> {/* No. PR */}
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text> {/* No. PO */}
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 0.7 }]}>
|
||||
<Text></Text> {/* Tgl Terima/Bayar */}
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 0.7 }]}>
|
||||
<Text></Text> {/* Tgl PO */}
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 0.6 }]}>
|
||||
<Text></Text> {/* Aging */}
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text> {/* Area */}
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text> {/* Gudang */}
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 1 }]}>
|
||||
<Text></Text> {/* Jatuh Tempo */}
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 2 }]}>
|
||||
<Text></Text> {/* Status Jatuh Tempo */}
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.5 }]}>
|
||||
<Text></Text> {/* Nominal Pembelian (Rp) */}
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellRight, { flex: 1.5 }]}>
|
||||
<Text></Text> {/* Pembayaran (Rp) */}
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellRight,
|
||||
{
|
||||
flex: 1.5,
|
||||
<PdfTable
|
||||
columns={getTableColumns()}
|
||||
data={getTableData(supplierReport.rows)}
|
||||
footer={
|
||||
supplierReport.total
|
||||
? getTableFooter(supplierReport.total)
|
||||
: undefined
|
||||
}
|
||||
firstRow={
|
||||
typeof supplierReport.initial_balance === 'number' &&
|
||||
supplierReport.initial_balance !== 0
|
||||
? {
|
||||
valueKey: 'balance',
|
||||
value: supplierReport.initial_balance,
|
||||
align: 'right',
|
||||
color: supplierReport.initial_balance < 0 ? 'red' : 'black',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{' '}
|
||||
{/* Sisa Saldo Hutang (Rp) */}
|
||||
{formatCurrency(supplierReport.initial_balance || 0)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||
<Text></Text> {/* Status */}
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCell, // No. Perjalanan
|
||||
{ flex: 1, borderRight: 'none' },
|
||||
]}
|
||||
>
|
||||
<Text></Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Table Body */}
|
||||
{supplierReport.rows.map((item, index) => (
|
||||
<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, { flex: 1 }]}>
|
||||
<Text>{item.pr_number || '-'}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text>{item.po_number || '-'}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 0.7 }]}>
|
||||
<Text>
|
||||
{item.received_date
|
||||
? item.received_date != '-'
|
||||
? formatDate(item.received_date, 'DD MMM YY')
|
||||
: '-'
|
||||
: '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 0.7 }]}>
|
||||
<Text>
|
||||
{item.po_date
|
||||
? item.po_date != '-'
|
||||
? formatDate(item.po_date, 'DD MMM YY')
|
||||
: '-'
|
||||
: '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 0.6 }]}>
|
||||
<Text>{formatNumber(item.aging)} Hari</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text>{item.area?.name || '-'}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text>{item.warehouse?.name || '-'}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 1 }]}>
|
||||
<Text>
|
||||
{item.due_date
|
||||
? item.due_date != '-'
|
||||
? formatDate(item.due_date, 'DD MMM YY')
|
||||
: '-'
|
||||
: '-'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 2 }]}>
|
||||
{item.due_status && item.due_status !== '-' ? (
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.badge,
|
||||
{
|
||||
backgroundColor: getPDFBadgeStyle(
|
||||
item.due_status,
|
||||
'due'
|
||||
).bg,
|
||||
borderColor: getPDFBadgeStyle(item.due_status, 'due')
|
||||
.border,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: getPDFBadgeStyle(item.due_status, 'due').text,
|
||||
}}
|
||||
>
|
||||
{item.due_status}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Text>-</Text>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellRight,
|
||||
{
|
||||
flex: 1.5,
|
||||
color: item.total_price < 0 ? 'red' : 'black',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>{formatCurrency(item.total_price)}</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellRight,
|
||||
{
|
||||
flex: 1.5,
|
||||
color: item.payment_price < 0 ? 'red' : 'black',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>{formatCurrency(item.payment_price)}</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellRight,
|
||||
{ flex: 1.5, color: item.balance < 0 ? 'red' : 'black' },
|
||||
]}
|
||||
>
|
||||
<Text>{formatCurrency(item.balance)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||
{item.status && item.status !== '-' ? (
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.badge,
|
||||
{
|
||||
backgroundColor: getPDFBadgeStyle(
|
||||
item.status,
|
||||
'payment'
|
||||
).bg,
|
||||
borderColor: getPDFBadgeStyle(item.status, 'payment')
|
||||
.border,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: getPDFBadgeStyle(item.status, 'payment').text,
|
||||
}}
|
||||
>
|
||||
{item.status}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Text>-</Text>
|
||||
)}
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCell, // No. Perjalanan
|
||||
{ flex: 1, borderRight: 'none' },
|
||||
]}
|
||||
>
|
||||
<Text>{item.travel_number || '-'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
|
||||
{/* Summary Row */}
|
||||
{supplierReport.total && (
|
||||
<View style={[pdfStyles.tableRow, pdfStyles.summaryRow]}>
|
||||
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
|
||||
<Text>Total</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 0.7 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 0.7 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellCenter, { flex: 0.6 }]}>
|
||||
<Text>{formatNumber(supplierReport.total.aging)} Hari</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellRight,
|
||||
{
|
||||
flex: 1.5,
|
||||
color:
|
||||
supplierReport.total.total_price < 0 ? 'red' : 'black',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{formatCurrency(supplierReport.total.total_price)}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellRight,
|
||||
{
|
||||
flex: 1.5,
|
||||
color:
|
||||
supplierReport.total.payment_price < 0
|
||||
? 'red'
|
||||
: 'black',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>
|
||||
{formatCurrency(supplierReport.total.payment_price)}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
pdfStyles.tableCellRight,
|
||||
{
|
||||
flex: 1.5,
|
||||
color:
|
||||
supplierReport.total.debt_price < 0 ? 'red' : 'black',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text>{formatCurrency(supplierReport.total.debt_price)}</Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
<View style={[pdfStyles.tableCellLast, { flex: 1 }]}>
|
||||
<Text></Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</Page>
|
||||
))}
|
||||
</Document>
|
||||
|
||||
@@ -40,6 +40,7 @@ export const safeRound = (num: number, decimals: number) => {
|
||||
export const formatTitleCase = (value: string) => {
|
||||
return value
|
||||
.toLowerCase()
|
||||
.replace(/_/g, ' ')
|
||||
.split(' ')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
|
||||
Reference in New Issue
Block a user