diff --git a/src/components/pages/finance/FinancesTable.tsx b/src/components/pages/finance/FinancesTable.tsx new file mode 100644 index 00000000..4cd6f571 --- /dev/null +++ b/src/components/pages/finance/FinancesTable.tsx @@ -0,0 +1,484 @@ +'use client'; + +import { ChangeEventHandler, useEffect, useState } from 'react'; +import useSWR from 'swr'; +import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; + +import { Icon } from '@iconify/react'; +import Table from '@/components/Table'; +import DebouncedTextInput from '@/components/input/DebouncedTextInput'; +import Button from '@/components/Button'; +import RowDropdownOptions from '@/components/table/RowDropdownOptions'; +import RowCollapseOptions from '@/components/table/RowCollapseOptions'; +import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper'; +import SelectInput, { + OptionType, + useSelect, +} from '@/components/input/SelectInput'; +import DateInput from '@/components/input/DateInput'; +import { cn, formatCurrency, formatDate } from '@/lib/helper'; +import { isResponseSuccess } from '@/lib/api-helper'; +import { useTableFilter } from '@/services/hooks/useTableFilter'; +import { FinanceApi } from '@/services/api/finance'; +import { Finance } from '@/types/api/finance'; +import { + BankApi, + CustomerApi, + KandangApi, + SupplierApi, +} from '@/services/api/master-data'; +import { Customer } from '@/types/api/master-data/customer'; +import { Supplier } from '@/types/api/master-data/supplier'; +import { Kandang } from '@/types/api/master-data/kandang'; +import { Bank } from '@/types/api/master-data/bank'; + +type FinanceTableFilter = { + search: string; + nameSort: string; + transactionType: string; + customerId: string; + supplierId: string; + kandangId: string; + bankId: string; + sortBy: string; + startDate: string; + endDate: string; +}; + +const TRANSACTION_TYPE_OPTIONS = [ + { value: 'REVENUE', label: 'Pemasukan' }, + { value: 'EXPENSE', label: 'Pengeluaran' }, +]; + +const SORT_OPTIONS = [ + { value: 'payment_date', label: 'Tanggal Pembayaran' }, + { value: 'created_date', label: 'Tanggal Dibuat' }, +]; + +const RowOptionsMenu = ({ + type = 'dropdown', + props, +}: { + type: 'dropdown' | 'collapse'; + props: CellContext; +}) => { + return ( + +
+ + +
+
+ ); +}; + +const FinancesTable = () => { + const { + state: tableFilterState, + updateFilter, + setPage, + setPageSize, + toQueryString: getTableFilterQueryString, + } = useTableFilter({ + initial: { + search: '', + nameSort: '', + transactionType: '', + customerId: '', + supplierId: '', + kandangId: '', + bankId: '', + sortBy: '', + startDate: '', + endDate: '', + }, + paramMap: { + page: 'page', + pageSize: 'limit', + nameSort: 'sort_name', + transactionType: 'transaction_type', + customerId: 'customer_id', + supplierId: 'supplier_id', + kandangId: 'kandang_id', + bankId: 'bank_id', + sortBy: 'sort_by', + startDate: 'start_date', + endDate: 'end_date', + }, + }); + + const { data: finances, isLoading: isLoadingFinances } = useSWR( + `${FinanceApi.basePath}${getTableFilterQueryString()}`, + FinanceApi.getAllFetcher + ); + + const [sorting, setSorting] = useState([]); + const [rowSelection, setRowSelection] = useState>({}); + + // Filter Selection States + const [selectedTransactionType, setSelectedTransactionType] = + useState(null); + const [selectedCustomer, setSelectedCustomer] = useState( + null + ); + const [selectedSupplier, setSelectedSupplier] = useState( + null + ); + const [selectedKandang, setSelectedKandang] = useState( + null + ); + const [selectedBank, setSelectedBank] = useState(null); + const [selectedSortBy, setSelectedSortBy] = useState(null); + + // APIs for SelectInputs + const { + options: customerOptions, + isLoadingOptions: isLoadingCustomerOptions, + setInputValue: setCustomerInputValue, + } = useSelect(CustomerApi.basePath, 'id', 'name'); + + const { + options: supplierOptions, + isLoadingOptions: isLoadingSupplierOptions, + setInputValue: setSupplierInputValue, + } = useSelect(SupplierApi.basePath, 'id', 'name'); + + const { + options: kandangOptions, + isLoadingOptions: isLoadingKandangOptions, + setInputValue: setKandangInputValue, + } = useSelect(KandangApi.basePath, 'id', 'name'); + + const { + options: bankOptions, + isLoadingOptions: isLoadingBankOptions, + setInputValue: setBankInputValue, + } = useSelect(BankApi.basePath, 'id', 'name'); + + const financeColumns: ColumnDef[] = [ + { + header: '#', + cell: (props) => props.row.index + 1, + }, + { + accessorKey: 'reference_number', + header: 'No Referensi', + }, + { + accessorKey: 'transaction_type', + header: 'Jenis Transaksi', + }, + { + accessorKey: 'customer_name', + header: 'Pelanggan', + }, + { + accessorKey: 'payment_date', + header: 'Tanggal Pembayaran', + cell: (props) => + formatDate(props.row.original.payment_date, 'DD MMM YYYY'), + }, + { + accessorKey: 'created_date', + header: 'Tanggal Dibuat', + cell: (props) => + formatDate(props.row.original.created_date, 'DD MMM YYYY'), + }, + { + accessorKey: 'payment_method', + header: 'Metode Pembayaran', + }, + { + accessorKey: 'bank_name', + header: 'Bank', + }, + { + accessorKey: 'expense_amount', + header: 'Pengeluaran', + cell: (props) => ( + + {formatCurrency(props.row.original.expense_amount)} + + ), + }, + { + accessorKey: 'revenue_amount', + header: 'Pemasukan', + cell: (props) => ( + + {formatCurrency(props.row.original.revenue_amount)} + + ), + }, + { + header: 'Aksi', + cell: (props) => { + 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 - 3; + + return ( + <> + {currentPageSize > 3 && ( + + + + )} + + {currentPageSize <= 3 && ( + + + + )} + + ); + }, + }, + ]; + + const searchChangeHandler: ChangeEventHandler = (e) => { + updateFilter('search', e.target.value); + }; + + const handleFilterChange = ( + value: OptionType | null, + setter: (val: OptionType | null) => void, + filterKey: keyof FinanceTableFilter + ) => { + setter(value); + updateFilter(filterKey, value ? String(value.value) : ''); + }; + + const resetFilters = () => { + setSelectedTransactionType(null); + setSelectedCustomer(null); + setSelectedSupplier(null); + setSelectedKandang(null); + setSelectedBank(null); + setSelectedSortBy(null); + + updateFilter('search', ''); + updateFilter('transactionType', ''); + updateFilter('customerId', ''); + updateFilter('supplierId', ''); + updateFilter('kandangId', ''); + updateFilter('bankId', ''); + updateFilter('sortBy', ''); + updateFilter('startDate', ''); + updateFilter('endDate', ''); + }; + + // track sorting + useEffect(() => { + const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name'); + + if (!isNameSorted) { + updateFilter('nameSort', ''); + } else { + updateFilter('nameSort', isNameSorted.desc ? 'desc' : 'asc'); + } + }, [sorting, updateFilter]); + + return ( +
+
+ {/* Row 1: Search */} +
+ +
+ + {/* Row 2: Transaction Type, Customer, Supplier, Mitra */} +
+ + handleFilterChange( + val as OptionType, + setSelectedTransactionType, + 'transactionType' + ) + } + isClearable + className={{ wrapper: 'col-span-12 sm:col-span-3' }} + /> + + handleFilterChange( + val as OptionType, + setSelectedCustomer, + 'customerId' + ) + } + isClearable + className={{ wrapper: 'col-span-12 sm:col-span-3' }} + /> + + handleFilterChange( + val as OptionType, + setSelectedSupplier, + 'supplierId' + ) + } + isClearable + className={{ wrapper: 'col-span-12 sm:col-span-3' }} + /> + + handleFilterChange( + val as OptionType, + setSelectedKandang, + 'kandangId' + ) + } + isClearable + className={{ wrapper: 'col-span-12 sm:col-span-3' }} + /> +
+ + {/* Row 3: Bank, Sort By, Start Date, End Date */} +
+ + handleFilterChange(val as OptionType, setSelectedBank, 'bankId') + } + isClearable + className={{ wrapper: 'col-span-12 sm:col-span-3' }} + /> + + handleFilterChange(val as OptionType, setSelectedSortBy, 'sortBy') + } + isClearable + className={{ wrapper: 'col-span-12 sm:col-span-3' }} + /> + updateFilter('startDate', e.target.value)} + className={{ wrapper: 'col-span-12 sm:col-span-3' }} + /> + updateFilter('endDate', e.target.value)} + className={{ wrapper: 'col-span-12 sm:col-span-3' }} + /> +
+ + {/* Row 4: Page Size, Filter Actions */} +
+
+ {/* Page size selector logic is inside Table usually, or we can add it here if needed, but Table component handles it via props. Let's keep it clean or add a rows selector if requested. Screenshot shows 'Baris 10' dropdown. */} + {/* Table component usually has a prop for rowOptions, but doesn't expose the selector UI outside unless we use TableRowSizeSelector. Let's just rely on Table's implicit size control or add it if strictly needed. The screenshot shows it outside. Implementing minimally for now. */} +
+
+ + +
+
+
+ + + data={isResponseSuccess(finances) ? finances?.data : []} + columns={financeColumns} + pageSize={tableFilterState.pageSize} + onPageSizeChange={setPageSize} + rowOptions={[10, 20, 50, 100]} + page={isResponseSuccess(finances) ? finances?.meta?.page : 0} + totalItems={ + isResponseSuccess(finances) ? finances?.meta?.total_results : 0 + } + onPageChange={setPage} + isLoading={isLoadingFinances} + sorting={sorting} + setSorting={setSorting} + rowSelection={rowSelection} + setRowSelection={setRowSelection} + className={{ + containerClassName: cn({ + 'w-full mb-20': + isResponseSuccess(finances) && finances?.data?.length === 0, + }), + }} + /> +
+ ); +}; + +export default FinancesTable;