import { ChangeEventHandler, useEffect, useMemo, useRef, useState, } from 'react'; import { CellContext } from '@tanstack/react-table'; import { useSearchParams } from 'next/navigation'; import useSWR from 'swr'; import Button from '@/components/Button'; import Card from '@/components/Card'; import DateInput from '@/components/input/DateInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import SelectInput, { OptionType, useSelect, } from '@/components/input/SelectInput'; import Table from '@/components/Table'; import { formatCurrency, formatDate, formatTitleCase } from '@/lib/helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { Finance } from '@/types/api/finance/finance'; import { FINANCE_INITIAL_BALANCE_STATUS, FINANCE_INJECTION_STATUS, FINANCE_TRANSACTION_STATUS, FINANCE_TRANSACTION_TYPE_OPTIONS, } from '@/config/constant'; import { FinanceApi } from '@/services/api/finance'; import { isResponseSuccess } from '@/lib/api-helper'; import { BankApi, CustomerApi, SupplierApi } from '@/services/api/master-data'; import { Bank } from '@/types/api/master-data/bank'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import toast from 'react-hot-toast'; import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; import RequirePermission from '@/components/helper/RequirePermission'; import { Icon } from '@iconify/react'; import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import { useUiStore } from '@/stores/ui/ui.store'; const RowOptionsMenu = ({ type = 'dropdown', props, deleteClickHandler, }: { type: 'dropdown' | 'collapse'; props: CellContext; deleteClickHandler: () => void; }) => { return ( {FINANCE_TRANSACTION_STATUS.includes( props.row.original.transaction_type ) && ( )} {FINANCE_INITIAL_BALANCE_STATUS.includes( props.row.original.transaction_type ) && ( )} {FINANCE_INJECTION_STATUS.includes( props.row.original.transaction_type ) && ( )} ); }; const FinanceTable = () => { const { searchValue, setSearchValue, resetSearchValue } = useUiStore(); const previousPathRef = useRef(null); const { state: tableFilterState, updateFilter, setPage, setPageSize, toQueryString: getTableFilterQueryString, } = useTableFilter({ initial: { search: searchValue, transactionType: '', bankId: '', customerId: '', supplierId: '', sortBy: '', startDate: '', endDate: '', }, paramMap: { page: 'page', pageSize: 'limit', transactionType: 'transaction_type', bankId: 'bank_id', customerId: 'customer_id', supplierId: 'supplier_id', sortBy: 'sort_date', startDate: 'start_date', endDate: 'end_date', }, }); // ===== State ===== const [searchParams, setSearchParams] = useSearchParams(); const deleteModal = useModal(); const [pendingFilters, setPendingFilters] = useState({ search: searchValue, transactionType: '', bankId: '', customerId: '', supplierId: '', sortBy: '', startDate: '', endDate: '', }); const [selectedTransactionType, setSelectedTransactionType] = useState< OptionType | OptionType[] | null >(null); const [selectedBank, setSelectedBank] = useState< OptionType | OptionType[] | null >(null); const [selectedCustomerId, setSelectedCustomerId] = useState< OptionType | OptionType[] | null >(null); const [selectedSupplierId, setSelectedSupplierId] = useState< OptionType | OptionType[] | null >(null); const [selectedSortBy, setSelectedSortBy] = useState(null); const [selectedFinance, setSelectedFinance] = useState(null); const [isDeleteLoading, setIsDeleteLoading] = useState(false); // ===== Fetch Data ===== const { data: finances, isLoading, mutate: refreshFinances, } = useSWR( `${FinanceApi.basePath}/transactions${getTableFilterQueryString()}`, FinanceApi.getAllFetcher ); const { options: customerOptions, isLoadingOptions: customerIsLoadingOptions, setInputValue: customerInputValue, loadMore: customerLoadMore, } = useSelect(CustomerApi.basePath, 'id', 'name'); const { options: supplierOptions, isLoadingOptions: supplierIsLoadingOptions, setInputValue: supplierInputValue, loadMore: supplierLoadMore, } = useSelect(SupplierApi.basePath, 'id', 'name'); const sortByOptions = useMemo(() => { return [ { label: 'Tanggal Pembayaran', value: 'payment_date' }, { label: 'Tanggal Dibuat', value: 'created_at' }, ]; }, []); const { options: bankOptions, rawData: bankRawData, setInputValue: bankInputValue, loadMore: bankLoadMore, } = useSelect(BankApi.basePath, 'id', 'alias'); // ===== Handler ===== const searchChangeHandler: ChangeEventHandler = (e) => { setPendingFilters((prev) => ({ ...prev, search: e.target.value })); }; const transactionTypeChangeHandler = ( val: OptionType | OptionType[] | null ) => { setSelectedTransactionType(val); setPendingFilters((prev) => ({ ...prev, transactionType: val ? Array.isArray(val) ? val.map((item) => item.value).join(',') : (val.value as string) : '', })); }; const bankChangeHandler = (val: OptionType | OptionType[] | null) => { setSelectedBank(val); setPendingFilters((prev) => ({ ...prev, bankId: val ? Array.isArray(val) ? val.map((item) => item.value).join(',') : (val.value as string) : '', })); }; const customerIdChangeHandler = (val: OptionType | OptionType[] | null) => { setSelectedCustomerId(val); setPendingFilters((prev) => ({ ...prev, customerId: val ? Array.isArray(val) ? val.map((item) => item.value).join(',') : (val.value as string) : '', })); }; const supplierIdChangeHandler = (val: OptionType | OptionType[] | null) => { setSelectedSupplierId(val); setPendingFilters((prev) => ({ ...prev, supplierId: val ? Array.isArray(val) ? val.map((item) => item.value).join(',') : (val.value as string) : '', })); }; const sortByChangeHandler = (val: OptionType | OptionType[] | null) => { setSelectedSortBy(val as OptionType); setPendingFilters((prev) => ({ ...prev, sortBy: val ? ((val as OptionType).value as string) : '', })); }; const startDateChangeHandler: ChangeEventHandler = (e) => { setPendingFilters((prev) => ({ ...prev, startDate: e.target.value })); }; const endDateChangeHandler: ChangeEventHandler = (e) => { setPendingFilters((prev) => ({ ...prev, endDate: e.target.value })); }; const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { const newVal = val as OptionType; setPageSize(newVal.value as number); }; const submitFilterHandler = () => { updateFilter('search', pendingFilters.search); setSearchValue(pendingFilters.search); updateFilter('transactionType', pendingFilters.transactionType); updateFilter('bankId', pendingFilters.bankId); updateFilter('customerId', pendingFilters.customerId); updateFilter('supplierId', pendingFilters.supplierId); updateFilter('sortBy', pendingFilters.sortBy); updateFilter('startDate', pendingFilters.startDate); updateFilter('endDate', pendingFilters.endDate); }; const resetFilterHandler = () => { setSelectedTransactionType(null); setSelectedBank(null); setSelectedCustomerId(null); setSelectedSupplierId(null); setSelectedSortBy(null); const emptyFilters = { search: '', transactionType: '', bankId: '', customerId: '', supplierId: '', sortBy: '', startDate: '', endDate: '', }; setPendingFilters(emptyFilters); updateFilter('search', ''); resetSearchValue(); updateFilter('transactionType', ''); updateFilter('bankId', ''); updateFilter('customerId', ''); updateFilter('supplierId', ''); updateFilter('sortBy', ''); updateFilter('startDate', ''); updateFilter('endDate', ''); }; const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); await FinanceApi.delete(selectedFinance?.id as number); refreshFinances(); deleteModal.closeModal(); toast.success('Successfully delete Finance!'); setIsDeleteLoading(false); }; const columns = useMemo(() => { return [ { header: 'ID', accessorKey: 'payment_code', }, { header: 'References Number', accessorKey: 'reference_number', cell: (props: CellContext) => { const value = props.row.original.reference_number; return {value ?? '-'}; }, }, { header: 'Jenis Transaksi', accessorKey: 'transaction_type', cell: (props: CellContext) => { const value = props.row.original.transaction_type .split('_') .join(' '); return {formatTitleCase(value)}; }, }, { header: 'Pihak', accessorFn: (finance: Finance) => finance.party?.name, cell: (props: CellContext) => { if (props.row.original.party?.id) { return {props.row.original.party?.name}; } return {'-'}; }, }, { header: 'Tanggal', accessorFn: (finance: Finance) => formatDate(finance.payment_date, 'DD MMM YYYY'), }, { header: 'Metode Pembayaran', accessorKey: 'payment_method', cell: (props: CellContext) => { const value = props.row.original.payment_method.split('_').join(' '); return {formatTitleCase(value)}; }, }, { header: 'Bank', accessorFn: (finance: Finance) => finance.bank ? `${finance.bank?.alias} - ${finance.bank?.account_number} - ${finance.bank?.owner}` : '-', }, { header: 'Pengeluaran (Rp)', accessorFn: (finance: Finance) => formatCurrency(Math.abs(finance.expense_amount)), }, { header: 'Pemasukan (Rp)', accessorFn: (finance: Finance) => formatCurrency(finance.income_amount), }, { header: 'Aksi', cell: (props: CellContext) => { const currentPageSize = props.table.getPaginationRowModel().rows.length; const currentPageRows = props.table.getPaginationRowModel().flatRows; const currentRowRelativeIndex = currentPageRows.findIndex((r) => r.id === props.row.id) + 1; const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2; const deleteClickHandler = () => { setSelectedFinance(props.row.original); deleteModal.openModal(); }; return ( <> {currentPageSize > 2 && ( )} {currentPageSize <= 2 && ( )} ); }, }, ]; }, []); useEffect(() => { // Store current path on mount previousPathRef.current = window.location.pathname; return () => { const currentPath = window.location.pathname; // if both paths are within /finance module const isCurrentPathFinance = currentPath.includes('/finance'); const isPreviousPathFinance = previousPathRef.current?.includes('/finance'); // reset if we outside finance module entirely if (isPreviousPathFinance && !isCurrentPathFinance) { resetSearchValue(); } }; }, [resetSearchValue]); return (
} >
({ label: bankRawData.data.find((data) => data.id === bank?.value) ?.alias + ' - ' + bankRawData.data.find((data) => data.id === bank?.value) ?.account_number + ' - ' + bankRawData.data.find((data) => data.id === bank?.value) ?.owner, value: bank?.value, })) : [] } label='Bank' value={selectedBank} onChange={bankChangeHandler} onInputChange={bankInputValue} onMenuScrollToBottom={bankLoadMore} isClearable isMulti />
data={isResponseSuccess(finances) ? finances.data : []} columns={columns} pageSize={tableFilterState.pageSize} page={tableFilterState.page} onPageChange={setPage} onPageSizeChange={setPageSize} totalItems={ isResponseSuccess(finances) ? finances.meta?.total_results : 0 } isLoading={isLoading} />
); }; export default FinanceTable;