refactor(FE): Add initial balance row and normalize empty values

This commit is contained in:
rstubryan
2026-01-23 10:34:53 +07:00
parent c012f39a38
commit a82860cb68
3 changed files with 216 additions and 78 deletions
@@ -320,110 +320,203 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
<View style={[pdfStyles.tableCellHeader, { flex: 1 }]}> <View style={[pdfStyles.tableCellHeader, { flex: 1 }]}>
<Text>Pengambilan</Text> <Text>Pengambilan</Text>
</View> </View>
<View style={[pdfStyles.tableCellHeader, { flex: 1.5 }]}> <View
style={[
pdfStyles.tableCellHeader,
{ flex: 1.5, borderRightWidth: 0 },
]}
>
<Text>Sales</Text> <Text>Sales</Text>
</View> </View>
</View> </View>
{/* Table Body */} {/* Table Body */}
{customerReport.rows.map((item, index) => ( <>
<View {/* Initial Balance Row */}
key={index} <View style={[pdfStyles.tableRow, pdfStyles.tableBorderBottom]}>
style={[
pdfStyles.tableRow,
index < customerReport.rows.length - 1
? pdfStyles.tableBorderBottom
: {},
]}
>
<View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}> <View style={[pdfStyles.tableCellNo, { flex: 0.5 }]}>
<Text>{index + 1}</Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
<Text> <Text></Text>
{item.trans_date
? formatDate(item.trans_date, 'DD MMM YY')
: '-'}
</Text>
</View> </View>
<View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellCenter, { flex: 1.2 }]}>
<Text> <Text></Text>
{item.delivery_date
? formatDate(item.delivery_date, 'DD MMM YY')
: '-'}
</Text>
</View> </View>
<View style={[pdfStyles.tableCellCenter, { flex: 0.8 }]}> <View style={[pdfStyles.tableCellCenter, { flex: 0.8 }]}>
<Text> <Text></Text>
{item.aging_day ? formatNumber(item.aging_day) : '-'} hari
</Text>
</View> </View>
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}> <View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
<Text>{item.reference || '-'}</Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCell, { flex: 1.2 }]}> <View style={[pdfStyles.tableCell, { flex: 1.2 }]}>
<Text> <Text></Text>
{Array.isArray(item.vehicle_numbers)
? item.vehicle_numbers.join(', ')
: item.vehicle_numbers || '-'}
</Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}> <View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatNumber(item.qty)}</Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1 }]}>
<Text>{formatNumber(item.weight)}</Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}> <View style={[pdfStyles.tableCellRight, { flex: 0.8 }]}>
<Text>{formatNumber(item.average_weight)}</Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.unit_price)}</Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.final_price)}</Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.total_price)}</Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}>
<Text>{formatCurrency(item.payment_amount)}</Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCellRight, { flex: 1.2 }]}> <View
<Text style={pdfStyles.textError}> style={[
{formatCurrency(item.accounts_receivable)} pdfStyles.tableCellRight,
{
flex: 1.2,
color:
typeof customerReport.initial_balance === 'number' &&
customerReport.initial_balance < 0
? 'red'
: 'black',
},
]}
>
<Text>
{formatCurrency(customerReport.initial_balance || 0)}
</Text> </Text>
</View> </View>
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}> <View style={[pdfStyles.tableCell, { flex: 1.5 }]}>
{item.status ? ( <Text></Text>
<View
style={[
pdfStyles.badge,
item.status === 'LUNAS'
? pdfStyles.badgeLunas
: pdfStyles.badgeBelumLunas,
]}
>
<Text>
{item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}
</Text>
</View>
) : (
<Text>-</Text>
)}
</View> </View>
<View style={[pdfStyles.tableCell, { flex: 1 }]}> <View style={[pdfStyles.tableCell, { flex: 1 }]}>
<Text> <Text></Text>
{Array.isArray(item.pickup_info)
? item.pickup_info.join(', ')
: item.pickup_info || '-'}
</Text>
</View> </View>
<View style={[pdfStyles.tableCell, { flex: 1.5 }]}> <View
<Text>{item.sales_person || '-'}</Text> style={[
pdfStyles.tableCell,
{ flex: 1.5, borderRightWidth: 0 },
]}
>
<Text></Text>
</View> </View>
</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.tableCell, { 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 */} {/* Summary Row */}
{customerReport.summary && ( {customerReport.summary && (
@@ -488,7 +581,12 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
<View style={[pdfStyles.tableCell, { flex: 1 }]}> <View style={[pdfStyles.tableCell, { flex: 1 }]}>
<Text></Text> <Text></Text>
</View> </View>
<View style={[pdfStyles.tableCellLast, { flex: 1.5 }]}> <View
style={[
pdfStyles.tableCell,
{ flex: 1.5, borderRightWidth: 0 },
]}
>
<Text></Text> <Text></Text>
</View> </View>
</View> </View>
@@ -44,20 +44,50 @@ export const generateCustomerPaymentExcel = async (
const worksheet = workbook.addWorksheet(customerName.substring(0, 31)); const worksheet = workbook.addWorksheet(customerName.substring(0, 31));
worksheet.columns = columns; worksheet.columns = columns;
const initialRow = worksheet.addRow({
no: '',
transDate: '',
deliveryDate: '',
aging: '',
reference: '',
vehicleNumbers: '',
qty: '',
weight: '',
avgWeight: '',
unitPrice: '',
finalPrice: '',
totalPrice: '',
paymentAmount: '',
accountsReceivable: formatCurrency(customerReport.initial_balance || 0),
status: '',
pickupInfo: '',
salesPerson: '',
});
const initialBalanceCell = initialRow.getCell('accountsReceivable');
if (
typeof customerReport.initial_balance === 'number' &&
customerReport.initial_balance < 0
) {
initialBalanceCell.font = { color: { argb: 'FFFF0000' } };
}
customerData.forEach((item, index) => { customerData.forEach((item, index) => {
const row = worksheet.addRow({ const row = worksheet.addRow({
no: index + 1, no: index + 1,
transDate: item.trans_date transDate: item.trans_date
? formatDate(item.trans_date, 'DD MMM YYYY') ? formatDate(item.trans_date, 'DD MMM YYYY')
: '', : '-',
deliveryDate: item.delivery_date deliveryDate: item.delivery_date
? formatDate(item.delivery_date, 'DD MMM YYYY') ? formatDate(item.delivery_date, 'DD MMM YYYY')
: '', : '-',
aging: formatNumber(item.aging_day || 0), aging: item.aging_day != null ? formatNumber(item.aging_day) : '-',
reference: item.reference || '', reference: item.reference || '-',
vehicleNumbers: Array.isArray(item.vehicle_numbers) vehicleNumbers: Array.isArray(item.vehicle_numbers)
? item.vehicle_numbers.join(', ') ? item.vehicle_numbers.length > 0
: '', ? item.vehicle_numbers.join(', ')
: '-'
: '-',
qty: formatNumber(item.qty || 0), qty: formatNumber(item.qty || 0),
weight: formatNumber(item.weight || 0), weight: formatNumber(item.weight || 0),
avgWeight: formatNumber(item.average_weight || 0), avgWeight: formatNumber(item.average_weight || 0),
@@ -66,11 +96,13 @@ export const generateCustomerPaymentExcel = async (
totalPrice: formatCurrency(item.total_price || 0), totalPrice: formatCurrency(item.total_price || 0),
paymentAmount: formatCurrency(item.payment_amount || 0), paymentAmount: formatCurrency(item.payment_amount || 0),
accountsReceivable: formatCurrency(item.accounts_receivable || 0), accountsReceivable: formatCurrency(item.accounts_receivable || 0),
status: item.status || '', status: item.status || '-',
pickupInfo: Array.isArray(item.pickup_info) pickupInfo: Array.isArray(item.pickup_info)
? item.pickup_info.join(', ') ? item.pickup_info.length > 0
: '', ? item.pickup_info.join(', ')
salesPerson: item.sales_person || '', : '-'
: '-',
salesPerson: item.sales_person || '-',
}); });
const accountsReceivableCell = row.getCell('accountsReceivable'); const accountsReceivableCell = row.getCell('accountsReceivable');
@@ -364,7 +364,11 @@ const CustomerPaymentTab = () => {
enableSorting: false, enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.vehicle_numbers; const value = props.row.original.vehicle_numbers;
return Array.isArray(value) ? value.join(', ') : value || '-'; return Array.isArray(value)
? value.length > 0
? value.join(', ')
: '-'
: '-';
}, },
}, },
{ {
@@ -528,7 +532,11 @@ const CustomerPaymentTab = () => {
enableSorting: false, enableSorting: false,
cell: (props) => { cell: (props) => {
const value = props.row.original.pickup_info; const value = props.row.original.pickup_info;
return Array.isArray(value) ? value.join(', ') : value || '-'; return Array.isArray(value)
? value.length > 0
? value.join(', ')
: '-'
: '-';
}, },
}, },
{ {