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([]); const [filterSales, setFilterSales] = useState([]); 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[] => { const tableColumns: ColumnDef[] = [ { id: 'no', header: 'No', cell: (props) => props.row.index + 1, footer: () =>
Total
, }, { 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 (
{value ? formatNumber(value) : '-'} hari
); }, }, { 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
{formatNumber(value)}
; }, footer: () => (
{formatNumber(summary.total_qty) || '-'}
), }, { id: 'weight', header: 'Berat (Kg)', accessorKey: 'weight', cell: (props) => { const value = props.row.original.weight; return
{formatNumber(value)}
; }, footer: () => (
{formatNumber(summary.total_weight) || '-'}
), }, { id: 'average_weight', header: 'AVG', accessorKey: 'average_weight', cell: (props) => { const value = props.row.original.average_weight; return
{formatNumber(value)}
; }, footer: () => (
-
), }, { id: 'price', header: 'Harga Awal', accessorKey: 'price', cell: (props) => { const value = props.row.original.price; return
{formatCurrency(value)}
; }, footer: () => (
{formatCurrency(summary.total_initial_amount) || '-'}
), }, { id: 'credit_note', header: 'CN', accessorKey: 'credit_note', cell: (props) => { const value = props.row.original.credit_note; return
{formatCurrency(value)}
; }, footer: () => (
{formatCurrency(summary.total_credit_note) || '-'}
), }, { id: 'final_price', header: 'Harga Akhir', accessorKey: 'final_price', cell: (props) => { const value = props.row.original.final_price; return
{formatCurrency(value)}
; }, footer: () => (
{formatCurrency(summary.total_final_amount) || '-'}
), }, { id: 'ppn', header: 'PPN (%)', accessorKey: 'ppn', cell: (props) => { const value = props.row.original.ppn; return
{formatNumber(value)}%
; }, footer: () => (
-
), }, { id: 'total', header: 'Total', accessorKey: 'total', cell: (props) => { const value = props.row.original.total; return
{formatCurrency(value)}
; }, footer: () => (
{formatCurrency(summary.total_grand_amount) || '-'}
), }, { id: 'payment', header: 'Pembayaran', accessorKey: 'payment', cell: (props) => { const value = props.row.original.payment; return
{formatCurrency(value)}
; }, footer: () => (
{formatCurrency(summary.total_payment) || '-'}
), }, { id: 'accounts_receivable', header: 'Saldo Piutang', accessorKey: 'accounts_receivable', cell: (props) => { const value = props.row.original.accounts_receivable; return (
{formatCurrency(value)}
); }, footer: () => (
{formatCurrency(summary.total_accounts_receivable) || '-'}
), }, { id: 'notes', header: 'Keterangan', accessorKey: 'notes', cell: (props) => { const value = props.row.original.notes; if (!value) { return '-'; } return ( {getPaymentStatusText(value)} ); }, }, { 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 (
Export } align='end' >
{/* Filter Modal */}
{/* Modal Header */}

Filter Data

{ setFilterStartDate(e.target.value); }} className={{ wrapper: 'w-full' }} />
{ setFilterEndDate(e.target.value); }} className={{ wrapper: 'w-full' }} />
{ setFilterCustomer( Array.isArray(val) ? val : val ? [val] : [] ); }} isLoading={isLoadingCustomers} isClearable className={{ wrapper: 'w-full' }} />
{ setFilterSales(Array.isArray(val) ? val : val ? [val] : []); }} isClearable className={{ wrapper: 'w-full' }} />
{/* Action Buttons */}
{!isSubmitted ? (
Silakan klik tombol Filter untuk mengatur filter dan menampilkan data.
) : isLoading ? (
) : data.length === 0 ? (
Tidak ada data yang dapat ditampilkan...
) : ( 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 ( 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', }} /> ); }) )} {meta && data.length > 0 && (
)} ); }; export default CustomerPaymentTab;