refactor(FE): Add utility for PDF badge styles and integrate into

reports
This commit is contained in:
rstubryan
2026-02-10 11:27:43 +07:00
parent 5fb065de3e
commit 80763acc53
4 changed files with 111 additions and 69 deletions
@@ -0,0 +1,65 @@
export type StatusColor = {
bg: string;
text: string;
border: string;
};
// Due status colors (for debt supplier reports)
export const dueStatusColors: Record<string, StatusColor> = {
'SUDAH JATUH TEMPO': {
bg: '#FEE2E2',
text: '#991B1B',
border: '#F87171',
}, // error/red
'BELUM JATUH TEMPO': {
bg: '#D1FAE5',
text: '#065F46',
border: '#34D399',
}, // success/green
'MENDEKATI JATUH TEMPO': {
bg: '#FEF3C7',
text: '#92400E',
border: '#FBBF24',
}, // warning/yellow
};
// Payment status colors (for customer payment & debt supplier reports)
export const paymentStatusColors: Record<string, StatusColor> = {
'BELUM LUNAS': {
bg: '#FEF3C7',
text: '#92400E',
border: '#FBBF24',
}, // warning/yellow
LUNAS: {
bg: '#DBEAFE',
text: '#1E40AF',
border: '#60A5FA',
}, // primary/blue
'PEMBAYARAN SEBAGIAN': { bg: '#D1FAE5', text: '#065F46', border: '#34D399' }, // success/green
PEMBAYARAN: {
bg: '#D1FAE5',
text: '#065F46',
border: '#34D399',
}, // success/green
};
// Fallback color for unknown statuses
export const fallbackStatusColor: StatusColor = {
bg: '#F3F4F6',
text: '#374151',
border: '#D1D5DB',
}; // neutral
export const getPDFBadgeStyle = (
statusText: string,
type: 'due' | 'payment' = 'payment'
): StatusColor => {
const normalizedStatus = statusText.toUpperCase().trim();
const colors =
type === 'due'
? dueStatusColors[normalizedStatus]
: paymentStatusColors[normalizedStatus];
return colors || fallbackStatusColor;
};
@@ -9,7 +9,12 @@ import {
pdf,
} from '@react-pdf/renderer';
import { formatDate, formatCurrency, formatNumber } from '@/lib/helper';
import {
formatDate,
formatCurrency,
formatNumber,
formatTitleCase,
} from '@/lib/helper';
import { CustomerPaymentReport } from '@/types/api/report/customer-payment';
import {
PdfTable,
@@ -20,6 +25,7 @@ import {
import { PdfParamBadge } from '@/components/helper/pdf/badge/PdfParamBadge';
import { PdfStatusBadge } from '@/components/helper/pdf/badge/PdfStatusBadge';
import { PdfTypography } from '@/components/helper/pdf/typography/PdfTypography';
import { getPDFBadgeStyle } from '@/components/helper/pdf/utils/pdf-badge';
Font.register({
family: 'Helvetica',
@@ -70,11 +76,21 @@ const getTableColumns = (): PdfColumn[] => [
{ 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: 'unit_price', header: 'Harga/Unit (Rp)', flex: 1.2, align: 'right' },
{ key: 'final_price', header: 'Harga Akhir (Rp)', flex: 1.2, align: 'right' },
{ key: 'total_price', header: 'Total (Rp)', flex: 1.2, align: 'right' },
{
key: 'payment_amount',
header: 'Pembayaran (Rp)',
flex: 1.2,
align: 'right',
},
{
key: 'accounts_receivable',
header: 'Saldo (Rp)',
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' },
@@ -139,18 +155,18 @@ const getTableData = (
key: 'accounts_receivable',
value: formatCurrency(item.accounts_receivable),
align: 'right',
color: item.accounts_receivable < 0 ? '#DC2626' : undefined,
color: item.accounts_receivable < 0 ? 'red' : undefined,
},
{
key: 'status',
value: item.status ? (
<View style={{ alignItems: 'center' }}>
<PdfStatusBadge
backgroundColor={item.status === 'LUNAS' ? '#DBEAFE' : '#FEE2E2'}
textColor={item.status === 'LUNAS' ? '#1E40AF' : '#991B1B'}
borderColor={item.status === 'LUNAS' ? '#60A5FA' : '#F87171'}
backgroundColor={getPDFBadgeStyle(item.status, 'payment').bg}
textColor={getPDFBadgeStyle(item.status, 'payment').text}
borderColor={getPDFBadgeStyle(item.status, 'payment').border}
>
{item.status === 'LUNAS' ? 'Lunas' : 'Belum Lunas'}
{formatTitleCase(item.status)}
</PdfStatusBadge>
</View>
) : (
@@ -204,8 +220,7 @@ const getTableFooter = (
key: 'accounts_receivable',
value: formatCurrency(summary?.total_accounts_receivable || 0),
align: 'right',
color:
(summary?.total_accounts_receivable || 0) < 0 ? '#DC2626' : undefined,
color: (summary?.total_accounts_receivable || 0) < 0 ? 'red' : undefined,
},
{ key: 'status', value: '' },
{ key: 'pickup_info', value: '' },
@@ -273,8 +288,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
valueKey: 'accounts_receivable',
value: customerReport.initial_balance,
align: 'right',
color:
customerReport.initial_balance < 0 ? '#DC2626' : 'black',
color: customerReport.initial_balance < 0 ? 'red' : 'black',
}
: undefined
}
@@ -27,11 +27,11 @@ export const generateCustomerPaymentExcel = async (
{ header: 'Ekor/Qty', key: 'qty', width: 10 },
{ header: 'Berat (Kg)', key: 'weight', width: 12 },
{ header: 'AVG', key: 'avgWeight', width: 10 },
{ header: 'Harga/Unit', key: 'unitPrice', width: 15 },
{ header: 'Harga Akhir', key: 'finalPrice', width: 15 },
{ header: 'Total', key: 'totalPrice', width: 15 },
{ header: 'Pembayaran', key: 'paymentAmount', width: 15 },
{ header: 'Saldo Piutang', key: 'accountsReceivable', width: 15 },
{ header: 'Harga/Unit (Rp)', key: 'unitPrice', width: 15 },
{ header: 'Harga Akhir (Rp)', key: 'finalPrice', width: 15 },
{ header: 'Total (Rp)', key: 'totalPrice', width: 15 },
{ header: 'Pembayaran (Rp)', key: 'paymentAmount', width: 15 },
{ header: 'Saldo Piutang (Rp)', key: 'accountsReceivable', width: 15 },
{ header: 'Keterangan', key: 'status', width: 20 },
{ header: 'Pengambilan', key: 'pickupInfo', width: 15 },
{ header: 'Sales/Marketing', key: 'salesPerson', width: 20 },
@@ -20,53 +20,13 @@ import {
PdfTbodyCell,
PdfTfootCell,
} from '@/components/helper/pdf/table';
import { getPDFBadgeStyle } from '@/components/helper/pdf/utils/pdf-badge';
Font.register({
family: 'Helvetica',
src: 'helvetica',
});
// Status color mappings (same as in DebtSupplierTab)
const dueStatusColors: Record<
string,
{ bg: string; text: string; border: string }
> = {
'Sudah Jatuh Tempo': { bg: '#FEE2E2', text: '#991B1B', border: '#F87171' }, // error/red
'Belum Jatuh Tempo': { bg: '#D1FAE5', text: '#065F46', border: '#34D399' }, // success/green
'Mendekati Jatuh Tempo': {
bg: '#FEF3C7',
text: '#92400E',
border: '#FBBF24',
}, // warning/yellow
};
const paymentStatusColors: Record<
string,
{ bg: string; text: string; border: string }
> = {
'Belum Lunas': { bg: '#FEF3C7', text: '#92400E', border: '#FBBF24' }, // warning/yellow
Lunas: { bg: '#DBEAFE', text: '#1E40AF', border: '#60A5FA' }, // primary/blue
Pembayaran: { bg: '#D1FAE5', text: '#065F46', border: '#34D399' }, // success/green
};
/**
* Get badge style for PDF rendering
* @param statusText - The status text
* @param type - Type of status: 'due' or 'payment'
* @returns Style object with background and text colors
*/
const getPDFBadgeStyle = (
statusText: string,
type: 'due' | 'payment' = 'payment'
) => {
const colors =
type === 'due'
? dueStatusColors[statusText]
: paymentStatusColors[statusText];
return colors || { bg: '#F3F4F6', text: '#374151', border: '#D1D5DB' }; // neutral fallback
};
const pdfStyles = StyleSheet.create({
page: {
fontSize: 10,
@@ -99,7 +59,7 @@ const getTableColumns = (): PdfColumn[] => [
{ 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: 'due_status', header: 'Status Jatuh Tempo', flex: 2, align: 'center' },
{
key: 'total_price',
header: 'Nominal Pembelian (Rp)',
@@ -151,13 +111,15 @@ const getTableData = (rows: DebtSupplier['rows']): PdfTbodyCell[][] => {
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>
<View style={{ alignItems: 'center' }}>
<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>
</View>
) : (
'-'
),
@@ -226,6 +188,7 @@ const getTableFooter = (total: DebtSupplier['total']): PdfTfootCell[] => [
key: 'balance',
value: formatCurrency(total?.debt_price || 0),
align: 'right',
color: (total?.debt_price || 0) < 0 ? 'red' : undefined,
},
{ key: 'status', value: '' },
{ key: 'travel_number', value: '' },