mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
760 lines
24 KiB
TypeScript
760 lines
24 KiB
TypeScript
import { useState, useMemo, useCallback } from 'react';
|
||
import useSWR from 'swr';
|
||
import { Icon } from '@iconify/react';
|
||
import Card from '@/components/Card';
|
||
import Badge from '@/components/Badge';
|
||
import SelectInput, {
|
||
useSelect,
|
||
OptionType,
|
||
} from '@/components/input/SelectInput';
|
||
import DateInput from '@/components/input/DateInput';
|
||
import { CustomerApi } from '@/services/api/master-data';
|
||
import { FinanceApi } from '@/services/api/report/finance-report';
|
||
import Table from '@/components/Table';
|
||
import { ColumnDef } from '@tanstack/react-table';
|
||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||
import {
|
||
CustomerPaymentReport,
|
||
CustomerPaymentSummary,
|
||
} from '@/types/api/report/customer-payment';
|
||
import { isResponseSuccess } from '@/lib/api-helper';
|
||
import Pagination from '@/components/Pagination';
|
||
import Button from '@/components/Button';
|
||
import Dropdown from '@/components/Dropdown';
|
||
import MenuItem from '@/components/menu/MenuItem';
|
||
import Menu from '@/components/menu/Menu';
|
||
import Modal from '@/components/Modal';
|
||
import { useModal } from '@/components/Modal';
|
||
import toast from 'react-hot-toast';
|
||
import { generateCustomerPaymentExcel } from '@/components/pages/report/finance/export/CustomerPaymentExportXLSX';
|
||
import { generateCustomerPaymentPDF } from '@/components/pages/report/finance/export/CustomerPaymentExportPDF';
|
||
|
||
const CustomerPaymentTab = () => {
|
||
// ===== STATE MANAGEMENT =====
|
||
const [isPdfExportLoading, setIsPdfExportLoading] = useState(false);
|
||
const [isExcelExportLoading, setIsExcelExportLoading] = useState(false);
|
||
const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading;
|
||
|
||
// ===== PAGINATION STATE =====
|
||
const [currentPage, setCurrentPage] = useState(1);
|
||
const [pageSize, setPageSize] = useState(10);
|
||
|
||
// ===== SUBMISSION STATE =====
|
||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||
|
||
// ===== FILTER STATE =====
|
||
const [filterCustomer, setFilterCustomer] = useState<OptionType[]>([]);
|
||
const [filterSales, setFilterSales] = useState<OptionType[]>([]);
|
||
const [filterStartDate, setFilterStartDate] = useState('');
|
||
const [filterEndDate, setFilterEndDate] = useState('');
|
||
|
||
const filterModal = useModal();
|
||
|
||
const { options: customerOptions, isLoadingOptions: isLoadingCustomers } =
|
||
useSelect(CustomerApi.basePath, 'id', 'name', 'search');
|
||
|
||
const salesOptions = useMemo(
|
||
() => [
|
||
{ value: 'Sales A', label: 'Sales A' },
|
||
{ value: 'Sales B', label: 'Sales B' },
|
||
{ value: 'Sales C', label: 'Sales C' },
|
||
// TODO: Fetch sales options from API
|
||
],
|
||
[]
|
||
);
|
||
|
||
const dataTypeOptions = useMemo(
|
||
() => [{ value: 'do_date', label: 'Tanggal Jual' }],
|
||
[]
|
||
);
|
||
|
||
const getPaymentStatusColor = (notes: string) => {
|
||
const normalizedValue = notes.toLowerCase();
|
||
|
||
if (normalizedValue === 'lunas') {
|
||
return 'bg-info/10 text-info border-info';
|
||
}
|
||
|
||
if (normalizedValue.includes('belum')) {
|
||
return 'bg-warning/10 text-warning border-warning';
|
||
}
|
||
|
||
return 'bg-gray-100 text-gray-600 border-gray-300';
|
||
};
|
||
|
||
const getPaymentStatusIndicatorColor = (notes: string) => {
|
||
const normalizedValue = notes.toLowerCase();
|
||
|
||
if (normalizedValue === 'lunas') {
|
||
return 'bg-info';
|
||
}
|
||
|
||
if (normalizedValue.includes('belum')) {
|
||
return 'bg-warning';
|
||
}
|
||
|
||
return 'bg-gray-400';
|
||
};
|
||
|
||
const getPaymentStatusText = (notes: string) => {
|
||
return notes;
|
||
};
|
||
|
||
// ===== FILTER HANDLERS =====
|
||
const handleResetFilters = useCallback(() => {
|
||
setIsSubmitted(false);
|
||
setFilterCustomer([]);
|
||
setFilterSales([]);
|
||
setFilterStartDate('');
|
||
setFilterEndDate('');
|
||
}, []);
|
||
|
||
const handleApplyFilters = useCallback(() => {
|
||
setIsSubmitted(true);
|
||
setCurrentPage(1);
|
||
filterModal.closeModal();
|
||
}, [filterModal]);
|
||
|
||
// ===== DATA FETCHING =====
|
||
const { data: customerPayment, isLoading } = useSWR(
|
||
isSubmitted
|
||
? () => {
|
||
const params = {
|
||
customer_id:
|
||
filterCustomer.length > 0
|
||
? filterCustomer.map((v) => String(v.value)).join(',')
|
||
: undefined,
|
||
sales:
|
||
filterSales.length > 0
|
||
? filterSales.map((v) => String(v.value)).join(',')
|
||
: undefined,
|
||
filter_by: 'do_date' as const,
|
||
start_date: filterStartDate || undefined,
|
||
end_date: filterEndDate || undefined,
|
||
page: currentPage,
|
||
limit: pageSize,
|
||
};
|
||
|
||
return ['customer-payment-report', params];
|
||
}
|
||
: null,
|
||
([, params]) =>
|
||
FinanceApi.getCustomerPaymentReport(
|
||
params.customer_id,
|
||
params.sales,
|
||
params.filter_by,
|
||
params.start_date,
|
||
params.end_date,
|
||
params.page,
|
||
params.limit
|
||
)
|
||
);
|
||
|
||
const data: CustomerPaymentReport[] = useMemo(
|
||
() =>
|
||
isResponseSuccess(customerPayment)
|
||
? (customerPayment?.data as unknown as CustomerPaymentReport[]) || []
|
||
: [],
|
||
[customerPayment]
|
||
);
|
||
|
||
const meta =
|
||
isResponseSuccess(customerPayment) && customerPayment?.meta
|
||
? customerPayment.meta
|
||
: null;
|
||
|
||
// ===== EXPORT DATA FETCHER =====
|
||
const customerPaymentExport = useCallback(async (): Promise<
|
||
CustomerPaymentReport[] | null
|
||
> => {
|
||
const params = {
|
||
customer_id:
|
||
filterCustomer.length > 0
|
||
? filterCustomer.map((v) => String(v.value)).join(',')
|
||
: undefined,
|
||
sales:
|
||
filterSales.length > 0
|
||
? filterSales.map((v) => String(v.value)).join(',')
|
||
: undefined,
|
||
filter_by: 'do_date' as const,
|
||
start_date: filterStartDate || undefined,
|
||
end_date: filterEndDate || undefined,
|
||
limit: 100,
|
||
page: 1,
|
||
};
|
||
|
||
const response = await FinanceApi.getCustomerPaymentReport(
|
||
params.customer_id,
|
||
params.sales,
|
||
params.filter_by,
|
||
params.start_date,
|
||
params.end_date,
|
||
params.page,
|
||
params.limit
|
||
);
|
||
|
||
return isResponseSuccess(response)
|
||
? (response.data as unknown as CustomerPaymentReport[])
|
||
: null;
|
||
}, [filterCustomer, filterSales, filterStartDate, filterEndDate]);
|
||
|
||
// ===== EXPORT HANDLERS =====
|
||
const handleExportExcel = useCallback(async () => {
|
||
setIsExcelExportLoading(true);
|
||
try {
|
||
const allDataForExport = await customerPaymentExport();
|
||
|
||
if (
|
||
!allDataForExport ||
|
||
!Array.isArray(allDataForExport) ||
|
||
allDataForExport.length === 0
|
||
) {
|
||
toast.error('Tidak ada data untuk diekspor.');
|
||
return;
|
||
}
|
||
|
||
generateCustomerPaymentExcel({ data: allDataForExport });
|
||
toast.success('Excel berhasil dibuat dan diunduh.');
|
||
} catch {
|
||
toast.error('Gagal membuat Excel. Silakan coba lagi.');
|
||
} finally {
|
||
setIsExcelExportLoading(false);
|
||
}
|
||
}, [customerPaymentExport]);
|
||
|
||
const handleExportPdf = useCallback(async () => {
|
||
setIsPdfExportLoading(true);
|
||
try {
|
||
const allDataForExport = await customerPaymentExport();
|
||
|
||
if (
|
||
!allDataForExport ||
|
||
!Array.isArray(allDataForExport) ||
|
||
allDataForExport.length === 0
|
||
) {
|
||
toast.error('Tidak ada data untuk diekspor.');
|
||
return;
|
||
}
|
||
|
||
await generateCustomerPaymentPDF({
|
||
data: allDataForExport,
|
||
params: {
|
||
customer_name:
|
||
filterCustomer.length > 0
|
||
? filterCustomer.map((c) => c.label).join(', ')
|
||
: undefined,
|
||
sales:
|
||
filterSales.length > 0
|
||
? filterSales.map((s) => s.label).join(', ')
|
||
: undefined,
|
||
start_date: filterStartDate || undefined,
|
||
end_date: filterEndDate || undefined,
|
||
filter_by: 'do_date',
|
||
},
|
||
});
|
||
toast.success('PDF berhasil dibuat dan diunduh.');
|
||
} catch {
|
||
toast.error('Gagal membuat PDF. Silakan coba lagi.');
|
||
} finally {
|
||
setIsPdfExportLoading(false);
|
||
}
|
||
}, [customerPaymentExport]);
|
||
|
||
// ===== PAGINATION HANDLERS =====
|
||
const handlePageChange = (page: number) => {
|
||
setCurrentPage(page);
|
||
};
|
||
|
||
const handleRowChange = (pageSize: number) => {
|
||
setPageSize(pageSize);
|
||
};
|
||
|
||
const handleNextPage = () => {
|
||
if (meta && currentPage < meta.total_pages) {
|
||
setCurrentPage(currentPage + 1);
|
||
}
|
||
};
|
||
|
||
const handlePrevPage = () => {
|
||
if (currentPage > 1) {
|
||
setCurrentPage(currentPage - 1);
|
||
}
|
||
};
|
||
|
||
const getTableColumns = (
|
||
summary: CustomerPaymentSummary
|
||
): ColumnDef<CustomerPaymentReport['rows'][0]>[] => {
|
||
const tableColumns: ColumnDef<CustomerPaymentReport['rows'][0]>[] = [
|
||
{
|
||
id: 'no',
|
||
header: 'No',
|
||
cell: (props) => props.row.index + 1,
|
||
footer: () => <div className='font-semibold text-gray-900'>Total</div>,
|
||
},
|
||
{
|
||
id: 'do_date_or_payment_date',
|
||
header: 'Tanggal DO/Bayar',
|
||
accessorKey: 'do_date',
|
||
cell: (props) => {
|
||
const value = props.row.original.do_date;
|
||
return formatDate(value, 'DD MMM YYYY');
|
||
},
|
||
},
|
||
{
|
||
id: 'realization_date',
|
||
header: 'Tanggal Realisasi',
|
||
accessorKey: 'realization_date',
|
||
cell: (props) => {
|
||
const value = props.row.original.realization_date;
|
||
return formatDate(value, 'DD MMM YYYY');
|
||
},
|
||
},
|
||
{
|
||
id: 'aging',
|
||
header: 'Aging',
|
||
accessorKey: 'aging_day',
|
||
cell: (props) => {
|
||
const value = props.row.original.aging_day;
|
||
return (
|
||
<div className='text-center'>
|
||
{value ? formatNumber(value) : '-'} hari
|
||
</div>
|
||
);
|
||
},
|
||
},
|
||
{
|
||
id: 'reference',
|
||
header: 'Referensi',
|
||
accessorKey: 'reference',
|
||
cell: (props) => {
|
||
const value = props.row.original.reference;
|
||
return value || '-';
|
||
},
|
||
},
|
||
{
|
||
id: 'vehicle_plate',
|
||
header: 'Nomor Polisi',
|
||
accessorKey: 'vehicle_plate',
|
||
cell: (props) => {
|
||
const value = props.row.original.vehicle_plate;
|
||
return value || '-';
|
||
},
|
||
},
|
||
{
|
||
id: 'qty',
|
||
header: 'Ekor/Qty',
|
||
accessorKey: 'qty',
|
||
cell: (props) => {
|
||
const value = props.row.original.qty;
|
||
return <div className='text-right'>{formatNumber(value)}</div>;
|
||
},
|
||
footer: () => (
|
||
<div className='text-right font-semibold text-gray-900'>
|
||
{formatNumber(summary.total_qty) || '-'}
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
id: 'weight',
|
||
header: 'Berat (Kg)',
|
||
accessorKey: 'weight',
|
||
cell: (props) => {
|
||
const value = props.row.original.weight;
|
||
return <div className='text-right'>{formatNumber(value)}</div>;
|
||
},
|
||
footer: () => (
|
||
<div className='text-right font-semibold text-gray-900'>
|
||
{formatNumber(summary.total_weight) || '-'}
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
id: 'average_weight',
|
||
header: 'AVG',
|
||
accessorKey: 'average_weight',
|
||
cell: (props) => {
|
||
const value = props.row.original.average_weight;
|
||
return <div className='text-right'>{formatNumber(value)}</div>;
|
||
},
|
||
footer: () => (
|
||
<div className='text-right font-semibold text-gray-900'>-</div>
|
||
),
|
||
},
|
||
{
|
||
id: 'price',
|
||
header: 'Harga Awal',
|
||
accessorKey: 'price',
|
||
cell: (props) => {
|
||
const value = props.row.original.price;
|
||
return <div className='text-right'>{formatCurrency(value)}</div>;
|
||
},
|
||
footer: () => (
|
||
<div className='text-right font-semibold text-gray-900'>
|
||
{formatCurrency(summary.total_initial_amount) || '-'}
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
id: 'credit_note',
|
||
header: 'CN',
|
||
accessorKey: 'credit_note',
|
||
cell: (props) => {
|
||
const value = props.row.original.credit_note;
|
||
return <div className='text-right'>{formatCurrency(value)}</div>;
|
||
},
|
||
footer: () => (
|
||
<div className='text-right font-semibold text-gray-900'>
|
||
{formatCurrency(summary.total_credit_note) || '-'}
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
id: 'final_price',
|
||
header: 'Harga Akhir',
|
||
accessorKey: 'final_price',
|
||
cell: (props) => {
|
||
const value = props.row.original.final_price;
|
||
return <div className='text-right'>{formatCurrency(value)}</div>;
|
||
},
|
||
footer: () => (
|
||
<div className='text-right font-semibold text-gray-900'>
|
||
{formatCurrency(summary.total_final_amount) || '-'}
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
id: 'ppn',
|
||
header: 'PPN (%)',
|
||
accessorKey: 'ppn',
|
||
cell: (props) => {
|
||
const value = props.row.original.ppn;
|
||
return <div className='text-right'>{formatNumber(value)}%</div>;
|
||
},
|
||
footer: () => (
|
||
<div className='text-right font-semibold text-gray-900'>-</div>
|
||
),
|
||
},
|
||
{
|
||
id: 'total',
|
||
header: 'Total',
|
||
accessorKey: 'total',
|
||
cell: (props) => {
|
||
const value = props.row.original.total;
|
||
return <div className='text-right'>{formatCurrency(value)}</div>;
|
||
},
|
||
footer: () => (
|
||
<div className='text-right font-semibold text-gray-900'>
|
||
{formatCurrency(summary.total_grand_amount) || '-'}
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
id: 'payment',
|
||
header: 'Pembayaran',
|
||
accessorKey: 'payment',
|
||
cell: (props) => {
|
||
const value = props.row.original.payment;
|
||
return <div className='text-right'>{formatCurrency(value)}</div>;
|
||
},
|
||
footer: () => (
|
||
<div className='text-right font-semibold text-gray-900'>
|
||
{formatCurrency(summary.total_payment) || '-'}
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
id: 'accounts_receivable',
|
||
header: 'Saldo Piutang',
|
||
accessorKey: 'accounts_receivable',
|
||
cell: (props) => {
|
||
const value = props.row.original.accounts_receivable;
|
||
return (
|
||
<div className='text-right text-error'>{formatCurrency(value)}</div>
|
||
);
|
||
},
|
||
footer: () => (
|
||
<div className='text-right font-semibold text-gray-900'>
|
||
{formatCurrency(summary.total_accounts_receivable) || '-'}
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
id: 'notes',
|
||
header: 'Keterangan',
|
||
accessorKey: 'notes',
|
||
cell: (props) => {
|
||
const value = props.row.original.notes;
|
||
|
||
if (!value) {
|
||
return '-';
|
||
}
|
||
|
||
return (
|
||
<Badge
|
||
statusIndicator={true}
|
||
variant='soft'
|
||
className={{
|
||
badge: `rounded-xl justify-start border border-gray-200 ${getPaymentStatusColor(value)}`,
|
||
status: getPaymentStatusIndicatorColor(value),
|
||
}}
|
||
>
|
||
{getPaymentStatusText(value)}
|
||
</Badge>
|
||
);
|
||
},
|
||
},
|
||
{
|
||
id: 'pickup_info',
|
||
header: 'Pengambilan',
|
||
accessorKey: 'pickup_info',
|
||
cell: (props) => {
|
||
const value = props.row.original.pickup_info;
|
||
return value || '-';
|
||
},
|
||
},
|
||
{
|
||
id: 'sales_marketing',
|
||
header: 'Sales/Marketing',
|
||
accessorKey: 'sales_marketing',
|
||
cell: (props) => {
|
||
const value = props.row.original.sales_marketing;
|
||
return value || '-';
|
||
},
|
||
},
|
||
];
|
||
return tableColumns;
|
||
};
|
||
|
||
return (
|
||
<div className='w-full p-0 sm:p-4'>
|
||
<Card
|
||
subtitle='Laporan > Kontrol Pembayaran Customer'
|
||
className={{ wrapper: 'w-full', body: 'p-1!' }}
|
||
>
|
||
<div className='mb-4 flex justify-end gap-2 [&_button]:px-4'>
|
||
<Button variant='outline' onClick={filterModal.openModal}>
|
||
<Icon icon='heroicons:funnel' width={18} height={18} />
|
||
Filter
|
||
</Button>
|
||
|
||
<Dropdown
|
||
trigger={
|
||
<Button variant='outline' isLoading={isAnyExportLoading}>
|
||
<Icon
|
||
icon='heroicons:cloud-arrow-down'
|
||
width={18}
|
||
height={18}
|
||
/>
|
||
Export
|
||
</Button>
|
||
}
|
||
align='end'
|
||
>
|
||
<Menu>
|
||
<MenuItem title='Excel' onClick={handleExportExcel} />
|
||
<MenuItem title='PDF' onClick={handleExportPdf} />
|
||
</Menu>
|
||
</Dropdown>
|
||
</div>
|
||
|
||
{/* Filter Modal */}
|
||
<Modal
|
||
ref={filterModal.ref}
|
||
className={{
|
||
modal: 'p-0',
|
||
modalBox: 'p-0 rounded-2xl xl:max-w-4/12 max-w-sm',
|
||
}}
|
||
>
|
||
<div className='space-y-6'>
|
||
{/* Modal Header */}
|
||
<div className='flex items-center justify-between gap-2 py-3 border-b border-gray-300 px-4'>
|
||
<div className='flex items-center gap-2 text-primary'>
|
||
<Icon icon='heroicons:funnel' width={20} height={20} />
|
||
<h3 className='font-semibold'>Filter Data</h3>
|
||
</div>
|
||
<Button
|
||
variant='link'
|
||
onClick={filterModal.closeModal}
|
||
className='text-gray-500 hover:text-gray-700 transition-colors cursor-pointer'
|
||
>
|
||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||
</Button>
|
||
</div>
|
||
<div className='space-y-4 px-4'>
|
||
<div className='grid grid-cols-1 sm:grid-cols-2 sm:gap-4'>
|
||
<div>
|
||
<DateInput
|
||
label='Tanggal'
|
||
name='start_date'
|
||
value={filterStartDate}
|
||
onChange={(e) => {
|
||
setFilterStartDate(e.target.value);
|
||
}}
|
||
className={{ wrapper: 'w-full' }}
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<DateInput
|
||
label=' '
|
||
name='end_date'
|
||
value={filterEndDate}
|
||
onChange={(e) => {
|
||
setFilterEndDate(e.target.value);
|
||
}}
|
||
className={{ wrapper: 'w-full' }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<SelectInput
|
||
label='Customer'
|
||
placeholder='Pilih Customer'
|
||
isMulti
|
||
options={customerOptions}
|
||
value={filterCustomer}
|
||
onChange={(val) => {
|
||
setFilterCustomer(
|
||
Array.isArray(val) ? val : val ? [val] : []
|
||
);
|
||
}}
|
||
isLoading={isLoadingCustomers}
|
||
isClearable
|
||
className={{ wrapper: 'w-full' }}
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<SelectInput
|
||
label='Sales'
|
||
placeholder='Pilih Sales'
|
||
isMulti
|
||
options={salesOptions}
|
||
value={filterSales}
|
||
onChange={(val) => {
|
||
setFilterSales(Array.isArray(val) ? val : val ? [val] : []);
|
||
}}
|
||
isClearable
|
||
className={{ wrapper: 'w-full' }}
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<SelectInput
|
||
label='Filter Berdasarkan'
|
||
placeholder='Pilih Filter Berdasarkan'
|
||
options={dataTypeOptions}
|
||
value={dataTypeOptions[0]}
|
||
isDisabled={true}
|
||
className={{ wrapper: 'w-full' }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Action Buttons */}
|
||
<div className='flex justify-between gap-4 py-4 mt-8 border-t border-gray-300 bg-gray-100'>
|
||
<Button
|
||
variant='soft'
|
||
className='ms-4 min-w-36 rounded-lg'
|
||
onClick={handleResetFilters}
|
||
>
|
||
Reset Filter
|
||
</Button>
|
||
<Button
|
||
className='me-4 min-w-36 rounded-lg'
|
||
onClick={handleApplyFilters}
|
||
>
|
||
Apply Filter
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
|
||
{!isSubmitted ? (
|
||
<div className='mt-6 text-center text-gray-500'>
|
||
Silakan klik tombol Filter untuk mengatur filter dan menampilkan
|
||
data.
|
||
</div>
|
||
) : isLoading ? (
|
||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||
<span className='loading loading-spinner loading-xl' />
|
||
</div>
|
||
) : data.length === 0 ? (
|
||
<div className='mt-6 text-center text-gray-500'>
|
||
Tidak ada data yang dapat ditampilkan...
|
||
</div>
|
||
) : (
|
||
data.map((customerReport) => {
|
||
const summary = customerReport.summary || {
|
||
total_qty: 0,
|
||
total_weight: 0,
|
||
total_initial_amount: 0,
|
||
total_credit_note: 0,
|
||
total_final_amount: 0,
|
||
total_ppn: 0,
|
||
total_grand_amount: 0,
|
||
total_payment: 0,
|
||
total_accounts_receivable: 0,
|
||
};
|
||
|
||
const tableColumns = getTableColumns(summary);
|
||
|
||
return (
|
||
<Card
|
||
key={customerReport.customer.id}
|
||
title={customerReport.customer.name}
|
||
subtitle={`${customerReport.customer.address || ''}`}
|
||
className={{ wrapper: 'w-full' }}
|
||
variant='bordered'
|
||
collapsible={true}
|
||
>
|
||
<Table
|
||
data={customerReport.rows}
|
||
columns={tableColumns}
|
||
pageSize={10}
|
||
renderFooter={customerReport.rows.length > 0}
|
||
className={{
|
||
containerClassName: 'w-full',
|
||
tableWrapperClassName: 'overflow-x-auto mt-4',
|
||
tableClassName: 'w-full table-auto text-sm',
|
||
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
|
||
headerColumnClassName:
|
||
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
|
||
bodyRowClassName:
|
||
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
|
||
bodyColumnClassName:
|
||
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
||
tableFooterClassName:
|
||
'bg-gray-100 font-semibold border border-gray-200',
|
||
footerRowClassName: 'border-t-2 border-gray-300',
|
||
footerColumnClassName:
|
||
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
||
paginationClassName: 'hidden',
|
||
}}
|
||
/>
|
||
</Card>
|
||
);
|
||
})
|
||
)}
|
||
</Card>
|
||
{meta && data.length > 0 && (
|
||
<div className='mt-6'>
|
||
<Pagination
|
||
currentPage={meta.page}
|
||
totalItems={meta.total_results}
|
||
onPageChange={handlePageChange}
|
||
onRowChange={handleRowChange}
|
||
onNextPage={handleNextPage}
|
||
onPrevPage={handlePrevPage}
|
||
rowOptions={[10, 25, 50, 100]}
|
||
itemsPerPage={meta.limit}
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default CustomerPaymentTab;
|