diff --git a/src/components/pages/finance/FinanceTable.tsx b/src/components/pages/finance/FinanceTable.tsx index c1c7f079..6f422753 100644 --- a/src/components/pages/finance/FinanceTable.tsx +++ b/src/components/pages/finance/FinanceTable.tsx @@ -1,13 +1,8 @@ -import { - ChangeEventHandler, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { CellContext } from '@tanstack/react-table'; import { useSearchParams } from 'next/navigation'; import useSWR from 'swr'; +import { useFormik } from 'formik'; import Button from '@/components/Button'; import Card from '@/components/Card'; @@ -40,6 +35,10 @@ import { Icon } from '@iconify/react'; import RowDropdownOptions from '@/components/table/RowDropdownOptions'; import RowCollapseOptions from '@/components/table/RowCollapseOptions'; import { useUiStore } from '@/stores/ui/ui.store'; +import { + FinanceTableFilterSchema, + FinanceTableFilterValues, +} from './FinanceTableFilter.schema'; const RowOptionsMenu = ({ type = 'dropdown', @@ -152,10 +151,10 @@ const FinanceTable = () => { } = useTableFilter({ initial: { search: searchValue, - transactionType: '', - bankId: '', - customerId: '', - supplierId: '', + transactionTypes: '', + bankIds: '', + customerIds: '', + supplierIds: '', sortBy: '', startDate: '', endDate: '', @@ -163,10 +162,10 @@ const FinanceTable = () => { paramMap: { page: 'page', pageSize: 'limit', - transactionType: 'transaction_type', - bankId: 'bank_id', - customerId: 'customer_id', - supplierId: 'supplier_id', + transactionTypes: 'transaction_types', + bankIds: 'bank_ids', + customerIds: 'customer_ids', + supplierIds: 'supplier_ids', sortBy: 'sort_date', startDate: 'start_date', endDate: 'end_date', @@ -174,18 +173,7 @@ const FinanceTable = () => { }); // ===== 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); @@ -201,6 +189,34 @@ const FinanceTable = () => { const [selectedSortBy, setSelectedSortBy] = useState(null); const [selectedFinance, setSelectedFinance] = useState(null); const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const [dateErrorShown, setDateErrorShown] = useState(false); + + // ===== Formik for Filter ===== + const filterFormik = useFormik({ + initialValues: { + search: searchValue, + transaction_types: '', + bank_ids: '', + customer_ids: '', + supplier_ids: '', + sort_by: '', + start_date: '', + end_date: '', + }, + validationSchema: FinanceTableFilterSchema, + enableReinitialize: true, + onSubmit: (values) => { + updateFilter('search', values.search); + setSearchValue(values.search); + updateFilter('transactionTypes', values.transaction_types); + updateFilter('bankIds', values.bank_ids); + updateFilter('customerIds', values.customer_ids); + updateFilter('supplierIds', values.supplier_ids); + updateFilter('sortBy', values.sort_by); + updateFilter('startDate', values.start_date); + updateFilter('endDate', values.end_date); + }, + }); // ===== Fetch Data ===== const { @@ -237,84 +253,141 @@ const FinanceTable = () => { loadMore: bankLoadMore, } = useSelect(BankApi.basePath, 'id', 'alias'); + const bankSelectOptions = useMemo(() => { + if (!isResponseSuccess(bankRawData)) return []; + + return bankOptions.map((bank) => { + const bankData = bankRawData.data.find((data) => data.id === bank?.value); + return { + label: bankData + ? `${bankData.alias} - ${bankData.account_number} - ${bankData.owner}` + : '', + value: bank?.value, + }; + }); + }, [bankOptions, bankRawData]); + // ===== Handler ===== - const searchChangeHandler: ChangeEventHandler = (e) => { - setPendingFilters((prev) => ({ ...prev, search: e.target.value })); + const searchChangeHandler = (e: React.ChangeEvent) => { + filterFormik.setFieldValue('search', e.target.value); }; const transactionTypeChangeHandler = ( val: OptionType | OptionType[] | null ) => { setSelectedTransactionType(val); - setPendingFilters((prev) => ({ - ...prev, - transactionType: val + filterFormik.setFieldValue( + 'transaction_types', + 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 + filterFormik.setFieldValue( + 'bank_ids', + 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 + filterFormik.setFieldValue( + 'customer_ids', + 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 + filterFormik.setFieldValue( + 'supplier_ids', + 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) : '', - })); + filterFormik.setFieldValue( + 'sort_by', + val ? ((val as OptionType).value as string) : '' + ); }; - const startDateChangeHandler: ChangeEventHandler = (e) => { - setPendingFilters((prev) => ({ ...prev, startDate: e.target.value })); + + const startDateChangeHandler = (e: React.ChangeEvent) => { + const value = e.target.value; + const endDate = filterFormik.values.end_date; + + filterFormik.setFieldValue('start_date', value); + + if (value && endDate) { + const startDate = new Date(value); + const endDateObj = new Date(endDate); + + if (endDateObj < startDate) { + filterFormik.setFieldError( + 'end_date', + 'Tanggal akhir tidak boleh masa lampau' + ); + if (!dateErrorShown) { + toast.error('Tanggal akhir tidak boleh masa lampau', { + duration: Infinity, + }); + setDateErrorShown(true); + } + } else { + filterFormik.setFieldError('end_date', undefined); + if (dateErrorShown) { + toast.dismiss(); + setDateErrorShown(false); + } + } + } }; - 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 endDateChangeHandler = (e: React.ChangeEvent) => { + const value = e.target.value; + const startDate = filterFormik.values.start_date; + + filterFormik.setFieldValue('end_date', value); + + if (value && startDate) { + const startDateObj = new Date(startDate); + const endDate = new Date(value); + + if (endDate < startDateObj) { + filterFormik.setFieldError( + 'end_date', + 'Tanggal akhir tidak boleh masa lampau' + ); + if (!dateErrorShown) { + toast.error('Tanggal akhir tidak boleh masa lampau', { + duration: Infinity, + }); + setDateErrorShown(true); + } + return; + } + } + + filterFormik.setFieldError('end_date', undefined); + if (dateErrorShown) { + toast.dismiss(); + setDateErrorShown(false); + } }; + const resetFilterHandler = () => { setSelectedTransactionType(null); setSelectedBank(null); @@ -322,24 +395,14 @@ const FinanceTable = () => { setSelectedSupplierId(null); setSelectedSortBy(null); - const emptyFilters = { - search: '', - transactionType: '', - bankId: '', - customerId: '', - supplierId: '', - sortBy: '', - startDate: '', - endDate: '', - }; - setPendingFilters(emptyFilters); + filterFormik.resetForm(); updateFilter('search', ''); resetSearchValue(); - updateFilter('transactionType', ''); - updateFilter('bankId', ''); - updateFilter('customerId', ''); - updateFilter('supplierId', ''); + updateFilter('transactionTypes', ''); + updateFilter('bankIds', ''); + updateFilter('customerIds', ''); + updateFilter('supplierIds', ''); updateFilter('sortBy', ''); updateFilter('startDate', ''); updateFilter('endDate', ''); @@ -464,26 +527,36 @@ const FinanceTable = () => { }, []); useEffect(() => { - // Store current path on mount + return () => { + if (dateErrorShown) { + toast.dismiss(); + } + }; + }, [dateErrorShown]); + + useEffect(() => { 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(); } + + if (dateErrorShown) { + toast.dismiss(); + setDateErrorShown(false); + } }; - }, [resetSearchValue]); + }, [resetSearchValue, dateErrorShown]); return ( -
+
@@ -539,6 +612,7 @@ const FinanceTable = () => { label='Jenis Transaksi' value={selectedTransactionType} onChange={transactionTypeChangeHandler} + closeMenuOnSelect={false} isClearable isMulti /> @@ -550,6 +624,7 @@ const FinanceTable = () => { onInputChange={customerInputValue} onMenuScrollToBottom={customerLoadMore} isLoading={customerIsLoadingOptions} + closeMenuOnSelect={false} isClearable isMulti /> @@ -561,31 +636,18 @@ const FinanceTable = () => { onInputChange={supplierInputValue} onMenuScrollToBottom={supplierLoadMore} isLoading={supplierIsLoadingOptions} + closeMenuOnSelect={false} isClearable isMulti /> ({ - 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, - })) - : [] - } + options={bankSelectOptions} label='Bank' value={selectedBank} onChange={bankChangeHandler} onInputChange={bankInputValue} onMenuScrollToBottom={bankLoadMore} + closeMenuOnSelect={false} isClearable isMulti /> @@ -597,22 +659,32 @@ const FinanceTable = () => { isClearable />
diff --git a/src/components/pages/finance/FinanceTableFilter.schema.ts b/src/components/pages/finance/FinanceTableFilter.schema.ts new file mode 100644 index 00000000..fecfc35d --- /dev/null +++ b/src/components/pages/finance/FinanceTableFilter.schema.ts @@ -0,0 +1,38 @@ +import * as yup from 'yup'; + +export type FinanceTableFilterType = { + search: string; + transaction_types: string; + bank_ids: string; + customer_ids: string; + supplier_ids: string; + sort_by: string; + start_date: string; + end_date: string; +}; + +export const FinanceTableFilterSchema = yup.object({ + search: yup.string().optional(), + transaction_types: yup.string().optional(), + bank_ids: yup.string().optional(), + customer_ids: yup.string().optional(), + supplier_ids: yup.string().optional(), + sort_by: yup.string().optional(), + start_date: yup.string().optional(), + end_date: yup + .string() + .optional() + .test( + 'is-greater-than-start', + 'Tanggal akhir tidak boleh masa lampau', + function (value) { + const { start_date } = this.parent; + if (!start_date || !value) return true; + return new Date(value) >= new Date(start_date); + } + ), +}) as yup.ObjectSchema; + +export type FinanceTableFilterValues = yup.InferType< + typeof FinanceTableFilterSchema +>;