refactor(FE): Rename initialBalanceRow to firstRow

This commit is contained in:
rstubryan
2026-01-28 10:00:01 +07:00
parent 3d8d0d9e4d
commit ca0d379c2c
3 changed files with 197 additions and 436 deletions
+3 -7
View File
@@ -18,7 +18,7 @@ interface PdfTableProps {
data: PdfTbodyCell[][]; data: PdfTbodyCell[][];
footer?: PdfTfootCell[]; footer?: PdfTfootCell[];
footerLabel?: string; footerLabel?: string;
initialBalanceRow?: { firstRow?: {
valueKey: string; valueKey: string;
value: number; value: number;
align?: 'right'; align?: 'right';
@@ -31,16 +31,12 @@ export const PdfTable = ({
data, data,
footer, footer,
footerLabel = 'Total', footerLabel = 'Total',
initialBalanceRow, firstRow,
}: PdfTableProps) => { }: PdfTableProps) => {
return ( return (
<View style={styles.table}> <View style={styles.table}>
<PdfThead columns={columns} /> <PdfThead columns={columns} />
<PdfTbody <PdfTbody columns={columns} rows={data} firstRow={firstRow} />
columns={columns}
rows={data}
initialBalanceRow={initialBalanceRow}
/>
{footer && footer.length > 0 && ( {footer && footer.length > 0 && (
<PdfTfoot columns={columns} cells={footer} label={footerLabel} /> <PdfTfoot columns={columns} cells={footer} label={footerLabel} />
)} )}
+8 -15
View File
@@ -74,7 +74,7 @@ const styles = StyleSheet.create({
interface PdfTbodyProps { interface PdfTbodyProps {
columns: PdfColumn[]; columns: PdfColumn[];
rows: PdfTbodyCell[][]; rows: PdfTbodyCell[][];
initialBalanceRow?: { firstRow?: {
valueKey: string; valueKey: string;
value: number; value: number;
align?: 'right'; align?: 'right';
@@ -85,31 +85,26 @@ interface PdfTbodyProps {
formatCurrency?: (num: number) => string; formatCurrency?: (num: number) => string;
} }
export const PdfTbody = ({ export const PdfTbody = ({ columns, rows, firstRow }: PdfTbodyProps) => {
columns,
rows,
initialBalanceRow,
}: PdfTbodyProps) => {
return ( return (
<> <>
{/* Initial Balance Row */} {/* First Row */}
{initialBalanceRow && ( {firstRow && (
<View style={[styles.tableRow, styles.tableBorderBottom]}> <View style={[styles.tableRow, styles.tableBorderBottom]}>
{columns.map((column, index) => { {columns.map((column, index) => {
const isLastColumn = index === columns.length - 1; const isLastColumn = index === columns.length - 1;
const isInitialBalanceColumn = const isfirstRowColumn = column.key === firstRow.valueKey;
column.key === initialBalanceRow.valueKey;
const align = column.align || 'center'; const align = column.align || 'center';
const cellStyle = const cellStyle =
column.key === 'no' column.key === 'no'
? [styles.tableCellNo, { flex: column.flex }] ? [styles.tableCellNo, { flex: column.flex }]
: isInitialBalanceColumn : isfirstRowColumn
? [ ? [
styles.tableCellRight, styles.tableCellRight,
{ {
flex: column.flex, flex: column.flex,
color: initialBalanceRow.color || 'black', color: firstRow.color || 'black',
borderRightWidth: isLastColumn ? 0 : 1, borderRightWidth: isLastColumn ? 0 : 1,
}, },
] ]
@@ -141,9 +136,7 @@ export const PdfTbody = ({
return ( return (
<View key={column.key} style={cellStyle}> <View key={column.key} style={cellStyle}>
<Text> <Text>{isfirstRowColumn ? firstRow.value : ''}</Text>
{isInitialBalanceColumn ? initialBalanceRow.value : ''}
</Text>
</View> </View>
); );
})} })}
@@ -12,6 +12,12 @@ import {
import { formatDate, formatCurrency, formatNumber } from '@/lib/helper'; import { formatDate, formatCurrency, formatNumber } from '@/lib/helper';
import { CustomerPaymentReport } from '@/types/api/report/customer-payment'; import { CustomerPaymentReport } from '@/types/api/report/customer-payment';
import {
PdfTable,
PdfColumn,
PdfTbodyCell,
PdfTfootCell,
} from '@/components/helper/pdf/table';
Font.register({ Font.register({
family: 'Helvetica', family: 'Helvetica',
@@ -45,97 +51,6 @@ const pdfStyles = StyleSheet.create({
marginBottom: 5, marginBottom: 5,
color: '#333333', 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: { badge: {
backgroundColor: '#1f74bf', backgroundColor: '#1f74bf',
color: '#FFFFFF', color: '#FFFFFF',
@@ -217,6 +132,165 @@ const getParameterText = (
return paramsText; 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' },
{
key: 'delivery_date',
header: 'Tanggal Realisasi',
flex: 1.2,
align: 'center',
},
{ 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', flex: 1.2, align: 'right' },
{ key: 'final_price', header: 'Harga Akhir', flex: 1.2, align: 'right' },
{ key: 'total_price', header: 'Total', flex: 1.2, align: 'right' },
{ key: 'payment_amount', header: 'Pembayaran', flex: 1.2, align: 'right' },
{ key: 'accounts_receivable', header: 'Saldo', flex: 1.2, align: 'right' },
{ 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 ? '#DC2626' : undefined,
},
{
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>
) : (
'-'
),
},
{
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',
},
{
key: 'total_price',
value: formatCurrency(summary?.total_grand_amount || 0),
align: 'right',
},
{
key: 'payment_amount',
value: formatCurrency(summary?.total_payment || 0),
align: 'right',
},
{
key: 'accounts_receivable',
value: formatCurrency(summary?.total_accounts_receivable || 0),
align: 'right',
color:
(summary?.total_accounts_receivable || 0) < 0 ? '#DC2626' : undefined,
},
{ key: 'status', value: '' },
{ key: 'pickup_info', value: '' },
{ key: 'sales_person', value: '' },
];
const createPDFDocument = (params: CustomerPaymentExportPDFParams) => { const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
return ( return (
<Document> <Document>
@@ -269,329 +343,27 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
</View> </View>
{/* Table */} {/* Table */}
<View style={pdfStyles.table}> <PdfTable
{/* Table Header */} columns={getTableColumns()}
<View style={[pdfStyles.tableRow, pdfStyles.tableHeader]}> data={getTableData(customerReport.rows)}
<View style={[pdfStyles.tableCellHeader, { flex: 0.5 }]}> footer={
<Text>No</Text> customerReport.summary
</View> ? getTableFooter(customerReport.summary)
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}> : undefined
<Text>Tanggal DO</Text> }
</View> firstRow={
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
<Text>Tanggal Realisasi</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 0.8 }]}>
<Text>Aging</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}>
<Text>Referensi</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 1.2 }]}>
<Text>No Polisi</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
<Text>Qty</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1 }]}>
<Text>Berat</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 0.8 }]}>
<Text>Rata-Rata</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Harga/Unit</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Harga Akhir</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Total</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Pembayaran</Text>
</View>
<View style={[pdfStyles.tableCellHeaderRight, { flex: 1.2 }]}>
<Text>Saldo</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}>
<Text>Keterangan</Text>
</View>
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
<Text>Pengambilan</Text>
</View>
<View
style={[
pdfStyles.tableCellHeader,
{ flex: 1.5, borderRightWidth: 0 },
]}
>
<Text>Sales</Text>
</View>
</View>
{/* Table Body */}
<>
{/* Initial Balance Row */}
<View style={[pdfStyles.tableRow, pdfStyles.tableBorderBottom]}>
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellCenter, { flex: 0.8 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text></Text>
</View>
<View
style={[
pdfStyles.tableCellRight,
{
flex: 1.2,
color:
typeof customerReport.initial_balance === 'number' && typeof customerReport.initial_balance === 'number' &&
customerReport.initial_balance < 0 customerReport.initial_balance !== 0
? 'red' ? {
: 'black', valueKey: 'accounts_receivable',
}, value: customerReport.initial_balance,
]} align: 'right',
> color:
<Text> customerReport.initial_balance < 0 ? '#DC2626' : 'black',
{formatCurrency(customerReport.initial_balance || 0)} }
</Text> : undefined
</View> }
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}> />
<Text></Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
<Text></Text>
</View>
<View
style={[
pdfStyles.tableCell,
{ flex: 1.5, borderRightWidth: 0 },
]}
>
<Text></Text>
</View>
</View>
{/* Data Rows */}
{customerReport.rows.map((item, index) => (
<View
key={index}
style={[
pdfStyles.tableRow,
index < customerReport.rows.length - 1
? pdfStyles.tableBorderBottom
: {},
]}
>
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
<Text>{index + 1}</Text>
</View>
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
<Text>
{item.trans_date
? formatDate(item.trans_date, 'DD MMM YY')
: '-'}
</Text>
</View>
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
<Text>
{item.delivery_date
? formatDate(item.delivery_date, 'DD MMM YY')
: '-'}
</Text>
</View>
<View style={[pdfStyles.tableCellCenter, { flex: 0.8 }]}>
<Text>
{item.aging_day != null
? `${formatNumber(item.aging_day)} hari`
: '-'}
</Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
<Text>{item.reference || '-'}</Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
<Text>
{Array.isArray(item.vehicle_numbers)
? item.vehicle_numbers.length > 0
? item.vehicle_numbers.join(', ')
: '-'
: '-'}
</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatNumber(item.qty)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatNumber(item.weight)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatNumber(item.average_weight)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.unit_price)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.final_price)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.total_price)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.payment_amount)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text style={pdfStyles.textError}>
{formatCurrency(item.accounts_receivable)}
</Text>
</View>
<View style={[pdfStyles.tableCellCenter, { flex: 1.5 }]}>
{item.status ? (
<View
style={[
pdfStyles.badge,
item.status === 'LUNAS'
? pdfStyles.badgeLunas
: pdfStyles.badgeBelumLunas,
]}
>
<Text>
{item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}
</Text>
</View>
) : (
<Text>-</Text>
)}
</View>
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
<Text>
{Array.isArray(item.pickup_info)
? item.pickup_info.length > 0
? item.pickup_info.join(', ')
: '-'
: '-'}
</Text>
</View>
<View
style={[
pdfStyles.tableCell,
{ flex: 1.5, borderRightWidth: 0 },
]}
>
<Text>{item.sales_person || '-'}</Text>
</View>
</View>
))}
</>
{/* Summary Row */}
{customerReport.summary && (
<View style={[pdfStyles.tableRow, pdfStyles.summaryRow]}>
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
<Text>Total</Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 0.8 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatNumber(customerReport.summary.total_qty)}</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>
{formatNumber(customerReport.summary.total_weight)}
</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>
{formatCurrency(customerReport.summary.total_final_amount)}
</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>
{formatCurrency(customerReport.summary.total_grand_amount)}
</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>
{formatCurrency(customerReport.summary.total_payment)}
</Text>
</View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text style={pdfStyles.textError}>
{formatCurrency(
customerReport.summary.total_accounts_receivable
)}
</Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
<Text></Text>
</View>
<View style={[pdfStyles.tableCell, { flex: 1 }]}>
<Text></Text>
</View>
<View
style={[
pdfStyles.tableCell,
{ flex: 1.5, borderRightWidth: 0 },
]}
>
<Text></Text>
</View>
</View>
)}
</View>
</Page> </Page>
))} ))}
</Document> </Document>