diff --git a/src/components/input/SelectInput.tsx b/src/components/input/SelectInput.tsx index c1736fc5..f954bc20 100644 --- a/src/components/input/SelectInput.tsx +++ b/src/components/input/SelectInput.tsx @@ -523,7 +523,7 @@ const useSelect = ( const qs = new URLSearchParams({ ...(params ?? {}), - [searchKey]: inputValue ?? '', + [searchKey ? searchKey : 'search']: inputValue ?? '', [pageKey]: String(pageIndex + 1), [limitKey]: String(limit), }).toString(); diff --git a/src/components/pages/expense/ExpensesTable.tsx b/src/components/pages/expense/ExpensesTable.tsx index e41024c1..61fa7fa6 100644 --- a/src/components/pages/expense/ExpensesTable.tsx +++ b/src/components/pages/expense/ExpensesTable.tsx @@ -1,5 +1,6 @@ 'use client'; +import axios from 'axios'; import { ChangeEventHandler, useCallback, @@ -7,8 +8,6 @@ import { useMemo, useState, } from 'react'; -import { usePathname, useRouter, useSearchParams } from 'next/navigation'; -import { useUiStore } from '@/stores/ui/ui.store'; import useSWR from 'swr'; import { CellContext, @@ -21,8 +20,11 @@ import toast from 'react-hot-toast'; import { Icon } from '@iconify/react'; import Table from '@/components/Table'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; +import DateInput from '@/components/input/DateInput'; +import SelectInput, { OptionType } from '@/components/input/SelectInput'; +import TextArea from '@/components/input/TextArea'; import Button from '@/components/Button'; -import { useModal } from '@/components/Modal'; +import Modal, { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import PopoverButton from '@/components/popover/PopoverButton'; import PopoverContent from '@/components/popover/PopoverContent'; @@ -34,10 +36,10 @@ import RequirePermission from '@/components/helper/RequirePermission'; import ButtonFilter from '@/components/helper/ButtonFilter'; import ExpensesFilterModal from '@/components/pages/expense/filter/ExpensesFilterModal'; import ExpenseTableSkeleton from '@/components/pages/expense/skeleton/ExpenseTableSkeleton'; +import Dropdown from '@/components/dropdown/Dropdown'; import { Expense } from '@/types/api/expense'; import { ExpenseApi } from '@/services/api/expense'; -import { buildExpenseActionHref } from '@/lib/expense-list-navigation'; import { cn, formatCurrency, formatDate } from '@/lib/helper'; import { isResponseSuccess } from '@/lib/api-helper'; import { useTableFilter } from '@/services/hooks/useTableFilter'; @@ -53,16 +55,71 @@ type ExpenseTableFilters = { userId: string; }; +const approvalStatusOptions = [ + { value: 'HEAD_AREA', label: 'Approval Head Area' }, + { value: 'UNIT_VICE_PRESIDENT', label: 'Approval Unit Vice President' }, + { value: 'FINANCE', label: 'Approval Finance' }, + { value: 'REALISASI', label: 'Realisasi' }, + { value: 'SELESAI', label: 'Selesai' }, +] as const satisfies OptionType< + 'HEAD_AREA' | 'UNIT_VICE_PRESIDENT' | 'FINANCE' | 'REALISASI' | 'SELESAI' +>[]; + +type ApprovalStatusValue = + | 'HEAD_AREA' + | 'UNIT_VICE_PRESIDENT' + | 'FINANCE' + | 'REALISASI' + | 'SELESAI'; + +const isApprovalDateRequired = (status?: ApprovalStatusValue) => + status === 'REALISASI' || status === 'SELESAI'; + +const getExportErrorMessage = async ( + error: unknown, + fallbackMessage: string +) => { + if (axios.isAxiosError(error)) { + const responseData = error.response?.data; + + if (responseData instanceof Blob) { + try { + const parsed = JSON.parse(await responseData.text()) as { + message?: string; + }; + return parsed.message || fallbackMessage; + } catch { + return fallbackMessage; + } + } + + if ( + responseData && + typeof responseData === 'object' && + 'message' in responseData && + typeof responseData.message === 'string' + ) { + return responseData.message; + } + + return error.message || fallbackMessage; + } + + if (error instanceof Error) { + return error.message; + } + + return fallbackMessage; +}; + const RowOptionsMenu = ({ popoverPosition = 'bottom', props, deleteClickHandler, - returnToSearchParams, }: { popoverPosition: 'bottom' | 'top'; props: CellContext; deleteClickHandler: () => void; - returnToSearchParams: URLSearchParams; }) => { const popoverId = `expense#${props.row.original.id}`; const popoverAnchorName = `--anchor-expense#${props.row.original.id}`; @@ -105,11 +162,7 @@ const RowOptionsMenu = ({
+ - +
)} {/* Search and Filter */} -
+
{ onClick={handleFilterModalOpen} className='px-3 py-2.5' /> + + +
+ + + Export + +
+ + +
+ + } + > + +
@@ -814,8 +1007,8 @@ const ExpensesTable = () => { totalItems={ isResponseSuccess(expenses) ? expenses?.meta?.total_results : 0 } - onPageChange={pageChangeHandler} - onPageSizeChange={pageSizeChangeHandler} + onPageChange={setPage} + onPageSizeChange={setPageSize} isLoading={isLoading} sorting={sorting} setSorting={setSorting} @@ -884,6 +1077,170 @@ const ExpensesTable = () => { }} /> + +
+
+

+ Bulk Approve Expense +

+ +
+ +
+ { + const nextValue = val as OptionType | null; + setBulkApprovalStatus(nextValue); + + if (!isApprovalDateRequired(nextValue?.value)) { + setBulkApprovalDate(''); + } + }} + placeholder='Pilih status approval' + isClearable + /> + + {isApprovalDateRequired(bulkApprovalStatus?.value) && ( + + )} + +