import Button from '@/components/Button'; import Card from '@/components/Card'; import Dropdown from '@/components/Dropdown'; import Pagination from '@/components/Pagination'; import DateInput from '@/components/input/DateInput'; import { OptionType, useSelect } from '@/components/input/SelectInput'; import Modal, { useModal } from '@/components/Modal'; import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table'; import { isResponseSuccess } from '@/lib/api-helper'; import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper'; import { SupplierApi } from '@/services/api/master-data'; import { DebtRow, DebtSupplier } from '@/types/api/report/debt-supplier'; import { generateDebtSupplierPDF } from '@/components/pages/report/finance/export/DebtSupllierExportPDF'; import { Icon } from '@iconify/react'; import { ColumnDef } from '@tanstack/react-table'; import { useCallback, useEffect, useState } from 'react'; import toast from 'react-hot-toast'; import useSWR from 'swr'; import { DebtSupplierApi } from '@/services/api/report/debt-supplier'; import { useFormik } from 'formik'; import ButtonFilter from '@/components/helper/ButtonFilter'; import { Color } from '@/types/theme'; import { Supplier } from '@/types/api/master-data/supplier'; import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; import SelectInputRadio from '@/components/input/SelectInputRadio'; import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store'; import StatusBadge from '@/components/helper/StatusBadge'; import DebtSupplierSkeleton from '@/components/pages/report/finance/skeleton/DebtSupplierSkeleton'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { httpClientFetcher, SWRHttpKey } from '@/services/http/client'; import { BaseApiResponse } from '@/types/api/api-general'; import { AxiosError } from 'axios'; const dueStatus: Record = { 'Sudah Jatuh Tempo': 'error', 'Belum Jatuh Tempo': 'success', 'Mendekati Jatuh Tempo': 'warning', }; const paymentStatus: Record = { 'Belum Lunas': 'warning', Lunas: 'primary', Pembayaran: 'success', }; const getPillBadge = ( statusText: string, type: 'due' | 'payment' = 'payment' ) => { const color = type === 'due' ? dueStatus[statusText] || 'neutral' : paymentStatus[statusText] || 'neutral'; return ( ); }; const dataTypeOptions: OptionType[] = [ { value: 'received_date', label: 'Tanggal Terima' }, { value: 'po_date', label: 'Tanggal PO' }, ]; interface DebtSupplierTabProps { tabId: string; } const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { // ===== STATE MANAGEMENT ===== const [isPdfExportLoading, setIsPdfExportLoading] = useState(false); const [isExcelExportLoading, setIsExcelExportLoading] = useState(false); const [isExcelGeneralExportLoading, setIsExcelGeneralExportLoading] = useState(false); const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading || isExcelGeneralExportLoading; const [dateErrorShown, setDateErrorShown] = useState(false); const [hasDateError, setHasDateError] = useState(false); const filterModal = useModal(); const setTabActions = useTabActionsStore((state) => state.setTabActions); const clearTabActions = useTabActionsStore((state) => state.clearTabActions); const { state: tableFilterState, updateFilter, setPage, setPageSize, toQueryString: getTableFilterQueryString, reset: resetFilter, } = useTableFilter<{ start_date: string; end_date: string; suppliers: OptionType[]; filterBy?: OptionType; }>({ initial: { start_date: '', end_date: '', suppliers: [], filterBy: undefined, }, paramMap: { page: 'page', pageSize: 'limit', start_date: 'start_date', end_date: 'end_date', suppliers: 'supplier_ids', filterBy: 'filter_by', }, persist: true, storeName: 'debt-supplier-report-table', }); const { setInputValue: setSupplierInputValue, options: supplierOptions, isLoadingOptions: isLoadingSupplierOptions, loadMore: loadMoreSuppliers, } = useSelect(SupplierApi.basePath, 'id', 'name'); // ===== FORMIK SETUP ===== const formik = useFormik({ initialValues: { start_date: tableFilterState.start_date, end_date: tableFilterState.end_date, suppliers: tableFilterState.suppliers, filterBy: tableFilterState.filterBy, }, onSubmit: (values) => { updateFilter('start_date', values.start_date, true); updateFilter('end_date', values.end_date, true); updateFilter('suppliers', values.suppliers, true); updateFilter('filterBy', values.filterBy, true); filterModal.closeModal(); }, }); const formikResetHandler = () => { resetFilter(); setHasDateError(false); if (dateErrorShown) { toast.dismiss(); setDateErrorShown(false); } formik.resetForm({ values: { start_date: '', end_date: '', suppliers: [], filterBy: undefined, }, }); filterModal.closeModal(); }; // ===== DATE CHANGE HANDLERS ===== const handleStartDateChange = (e: React.ChangeEvent) => { const value = e.target.value; formik.setFieldValue('start_date', value); if (value && formik.values.end_date) { if (new Date(formik.values.end_date) < new Date(value)) { setHasDateError(true); if (!dateErrorShown) { toast.error('Tanggal akhir tidak boleh masa lampau', { duration: Infinity, }); setDateErrorShown(true); } } else { setHasDateError(false); if (dateErrorShown) { toast.dismiss(); setDateErrorShown(false); } } } else { setHasDateError(false); } }; const handleEndDateChange = (e: React.ChangeEvent) => { const value = e.target.value; formik.setFieldValue('end_date', value); if (value && formik.values.start_date) { if (new Date(value) < new Date(formik.values.start_date)) { setHasDateError(true); if (!dateErrorShown) { toast.error('Tanggal akhir tidak boleh masa lampau', { duration: Infinity, }); setDateErrorShown(true); } return; } } setHasDateError(false); if (dateErrorShown) { toast.dismiss(); setDateErrorShown(false); } }; // ===== DATA FETCHING ===== const { data: debtSupplierResponse, isLoading } = useSWR< BaseApiResponse, AxiosError, SWRHttpKey >( `${DebtSupplierApi.basePath}/debt-supplier${getTableFilterQueryString()}`, httpClientFetcher ); const data: DebtSupplier[] = isResponseSuccess(debtSupplierResponse) ? ((debtSupplierResponse?.data as unknown as DebtSupplier[]) ?? []) : []; const meta = isResponseSuccess(debtSupplierResponse) && debtSupplierResponse.meta ? debtSupplierResponse.meta : null; // ===== EXPORT DATA FETCHER ===== const debtSupplierExport = useCallback(async (): Promise< DebtSupplier[] | null > => { const supplier_ids = tableFilterState.suppliers.length > 0 ? tableFilterState.suppliers.map((o) => String(o.value)).join(',') : undefined; const response = await DebtSupplierApi.getDebtSupplierReport( supplier_ids, tableFilterState.filterBy?.value, tableFilterState.start_date || undefined, tableFilterState.end_date || undefined, 1, 100 ); return isResponseSuccess(response) ? (response.data as unknown as DebtSupplier[]) : null; }, [tableFilterState]); // ===== EXPORT HANDLERS ===== const handleExportExcel = useCallback(async () => { setIsExcelExportLoading(true); try { const supplier_ids = tableFilterState.suppliers.length > 0 ? tableFilterState.suppliers.map((o) => String(o.value)).join(',') : undefined; await DebtSupplierApi.exportToExcelSupplierPerSheet( supplier_ids, tableFilterState.filterBy?.value, tableFilterState.start_date || undefined, tableFilterState.end_date || undefined ); toast.success('Excel berhasil dibuat dan diunduh.'); } catch { toast.error('Gagal membuat Excel. Silakan coba lagi.'); } finally { setIsExcelExportLoading(false); } }, [tableFilterState]); const handleExportExcelGeneral = useCallback(async () => { setIsExcelGeneralExportLoading(true); try { const supplier_ids = tableFilterState.suppliers.length > 0 ? tableFilterState.suppliers.map((o) => String(o.value)).join(',') : undefined; await DebtSupplierApi.exportToExcelGeneral( supplier_ids, tableFilterState.filterBy?.value, tableFilterState.start_date || undefined, tableFilterState.end_date || undefined ); toast.success('Excel General berhasil dibuat dan diunduh.'); } catch { toast.error('Gagal membuat Excel General. Silakan coba lagi.'); } finally { setIsExcelGeneralExportLoading(false); } }, [tableFilterState]); const handleExportPdf = useCallback(async () => { setIsPdfExportLoading(true); try { const allDataForExport = await debtSupplierExport(); if ( !allDataForExport || !Array.isArray(allDataForExport) || allDataForExport.length === 0 ) { toast.error('Tidak ada data untuk diekspor.'); return; } const supplierName = tableFilterState.suppliers.length > 0 ? tableFilterState.suppliers.map((o) => o.label).join(', ') : undefined; await generateDebtSupplierPDF({ data: allDataForExport, params: { supplier_name: supplierName, filter_by: tableFilterState.filterBy?.label, start_date: tableFilterState.start_date || undefined, end_date: tableFilterState.end_date || undefined, }, }); toast.success('PDF berhasil dibuat dan diunduh.'); } catch { toast.error('Gagal membuat PDF. Silakan coba lagi.'); } finally { setIsPdfExportLoading(false); } }, [debtSupplierExport, tableFilterState]); // ===== TAB ACTIONS ===== useEffect(() => { setTabActions( tabId,
Export
} >
); }, [ tabId, setTabActions, tableFilterState, filterModal.openModal, isAnyExportLoading, handleExportExcel, handleExportExcelGeneral, handleExportPdf, isExcelExportLoading, isExcelGeneralExportLoading, isPdfExportLoading, ]); useEffect(() => { return () => clearTabActions(tabId); }, [tabId, clearTabActions]); const getTableColumns = (supplier?: DebtSupplier): ColumnDef[] => [ { id: 'no', header: 'No', enableSorting: false, cell: (props) => props.row.index, footer: () => 'Total', }, { id: 'pr_number', header: 'Nomor PR', accessorKey: 'pr_number', enableSorting: false, cell: (props) => { const value = props.row.original.pr_number; return value || '-'; }, }, { id: 'po_number', header: 'Nomor PO', accessorKey: 'po_number', enableSorting: false, cell: (props) => { const value = props.row.original.po_number; return value || '-'; }, }, { id: 'received_date', header: 'Tanggal Terima/Bayar', accessorKey: 'received_date', enableSorting: false, cell: (props) => { const value = props.row.original.received_date; return value ? value != '-' ? formatDate(value, 'DD MMM YYYY') : '-' : '-'; }, }, { id: 'po_date', header: 'Tanggal PO', accessorKey: 'po_date', enableSorting: false, cell: (props) => { const value = props.row.original.po_date; return value ? value != '-' ? formatDate(value, 'DD MMM YYYY') : '-' : '-'; }, }, { id: 'aging', header: 'Aging', accessorKey: 'aging', enableSorting: false, cell: (props) => { const value = props.row.original.aging; return
{formatNumber(value)} Hari
; }, footer: () => { const value = supplier?.total.aging; return (
{formatNumber(value || 0)} Hari
); }, }, { id: 'area', header: 'Area', accessorKey: 'area', enableSorting: false, cell: (props) => { const value = props.row.original.area?.name; return value || '-'; }, }, { id: 'warehouse', header: 'Gudang', accessorKey: 'warehouse', enableSorting: false, cell: (props) => { const value = props.row.original.warehouse?.name; return value || '-'; }, }, { id: 'due_date', header: 'Jatuh Tempo', accessorKey: 'due_date', enableSorting: false, cell: (props) => { const value = props.row.original.due_date; return value ? value != '-' ? formatDate(value, 'DD MMM YYYY') : '-' : '-'; }, }, { id: 'due_status', header: 'Status Jatuh Tempo', accessorKey: 'due_status', enableSorting: false, cell: (props) => { const value = props.row.original.due_status; return value ? (value != '-' ? getPillBadge(value, 'due') : '-') : '-'; }, }, { id: 'total_price', header: 'Nominal Pembelian', accessorKey: 'total_price', enableSorting: false, cell: (props) => { const value = props.row.original.total_price; return (
{formatCurrency(value)}
); }, footer: () => { const value = supplier?.total.total_price; return (
{formatCurrency(value || 0)}
); }, }, { id: 'payment_price', header: 'Pembayaran', accessorKey: 'payment_price', enableSorting: false, cell: (props) => { const value = props.row.original.payment_price; return (
{formatCurrency(value)}
); }, footer: () => { const value = supplier?.total.payment_price; return (
{formatCurrency(value || 0)}
); }, }, { id: 'balance', header: 'Sisa Saldo Hutang', accessorKey: 'balance', enableSorting: false, cell: (props) => { const value = props.row.original.balance; return (
{formatCurrency(value)}
); }, footer: () => { const value = supplier?.total.debt_price; return (
{formatCurrency(value || 0)}
); }, }, { id: 'status', header: 'Status', accessorKey: 'status', enableSorting: false, cell: (props) => { const value = props.row.original.status; return value ? value != '-' ? getPillBadge(value, 'payment') : '-' : '-'; }, }, { id: 'travel_number', header: 'Nomor Perjalanan', accessorKey: 'travel_number', enableSorting: false, cell: (props) => { const value = props.row.original.travel_number; return value || '-'; }, }, ]; return ( <>
{isLoading && (
)} {!isLoading && data.length === 0 && ( } title='Data Not Yet Available' subtitle='Please change your filters to get the data.' /> )} {!isLoading && data.length > 0 && meta && (
setPage(Math.max(1, tableFilterState.page - 1))} onNextPage={() => setPage( meta.total_pages && tableFilterState.page < meta.total_pages ? tableFilterState.page + 1 : tableFilterState.page ) } onPageChange={setPage} rowOptions={[10, 20, 50, 100]} onRowChange={setPageSize} />
)} {!isLoading && data.length > 0 && data.map((supplierReport) => { return ( 0} className={{ containerClassName: 'w-full mb-0', tableWrapperClassName: 'overflow-x-auto rounded-tr-none rounded-tl-none', headerColumnClassName: cn( TABLE_DEFAULT_STYLING.headerColumnClassName, 'whitespace-nowrap' ), bodyColumnClassName: cn( TABLE_DEFAULT_STYLING.bodyColumnClassName, 'whitespace-nowrap' ), footerRowClassName: cn( TABLE_DEFAULT_STYLING.footerRowClassName, 'bg-white' ), footerColumnClassName: cn( TABLE_DEFAULT_STYLING.footerColumnClassName, 'whitespace-nowrap p-3' ), paginationClassName: 'hidden', }} renderCustomRow={(row) => { if (row.index == 0) { return ( ); } }} /> ); })} {!isLoading && data.length > 0 && meta && (
setPage(Math.max(1, tableFilterState.page - 1))} onNextPage={() => setPage( meta.total_pages && tableFilterState.page < meta.total_pages ? tableFilterState.page + 1 : tableFilterState.page ) } onPageChange={setPage} rowOptions={[10, 20, 50, 100]} onRowChange={setPageSize} />
)} {/* Filter Modal */} {/* Modal Header */}

Filter Data

{/* Modal Body */}

formik.setFieldValue('suppliers', Array.isArray(val) ? val : []) } onInputChange={setSupplierInputValue} onMenuScrollToBottom={loadMoreSuppliers} isLoading={isLoadingSupplierOptions} isClearable className={{ wrapper: 'w-full' }} /> formik.setFieldValue( 'filterBy', !Array.isArray(val) ? (val ?? undefined) : undefined ) } className={{ wrapper: 'w-full' }} isClearable />
{/* Modal Footer */}
); }; export default DebtSupplierTab;
{formatCurrency(row.original.balance)}