mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat(FE-195): add filter and approve/reject functionality
This commit is contained in:
@@ -2,7 +2,12 @@
|
|||||||
|
|
||||||
import { ChangeEventHandler, useEffect, useState } from 'react';
|
import { ChangeEventHandler, useEffect, useState } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
|
import {
|
||||||
|
CellContext,
|
||||||
|
ColumnDef,
|
||||||
|
Row,
|
||||||
|
SortingState,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
@@ -11,10 +16,18 @@ import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
|||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import { useModal } from '@/components/Modal';
|
import { useModal } from '@/components/Modal';
|
||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||||
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
|
||||||
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
|
||||||
|
import RealizationStatusBadge from '@/components/pages/expense/RealizationStatusBadge';
|
||||||
|
import ExpenseStatusBadge from '@/components/pages/expense/ExpenseStatusBadge';
|
||||||
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||||
|
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||||
|
import DateInput from '@/components/input/DateInput';
|
||||||
|
|
||||||
import { Expense } from '@/types/api/expense';
|
import { Expense } from '@/types/api/expense';
|
||||||
import { ExpenseApi } from '@/services/api/expense';
|
import { ExpenseApi } from '@/services/api/expense';
|
||||||
@@ -22,16 +35,34 @@ import { cn, formatCurrency } from '@/lib/helper';
|
|||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import { ROWS_OPTIONS } from '@/config/constant';
|
import { ROWS_OPTIONS } from '@/config/constant';
|
||||||
|
import { LocationApi, SupplierApi } from '@/services/api/master-data';
|
||||||
|
import { Location } from '@/types/api/master-data/location';
|
||||||
|
import { Supplier } from '@/types/api/master-data/supplier';
|
||||||
|
|
||||||
const RowOptionsMenu = ({
|
const RowOptionsMenu = ({
|
||||||
type = 'dropdown',
|
type = 'dropdown',
|
||||||
props,
|
props,
|
||||||
|
approveClickHandler,
|
||||||
|
rejectClickHandler,
|
||||||
deleteClickHandler,
|
deleteClickHandler,
|
||||||
}: {
|
}: {
|
||||||
type: 'dropdown' | 'collapse';
|
type: 'dropdown' | 'collapse';
|
||||||
props: CellContext<Expense, unknown>;
|
props: CellContext<Expense, unknown>;
|
||||||
|
approveClickHandler: () => void;
|
||||||
|
rejectClickHandler: () => void;
|
||||||
deleteClickHandler: () => void;
|
deleteClickHandler: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const showEditButton =
|
||||||
|
props.row.original.approval.action !== 'REJECTED' &&
|
||||||
|
props.row.original.approval.step_number !== 5 &&
|
||||||
|
props.row.original.approval.action !== 'APPROVED';
|
||||||
|
|
||||||
|
const showDeleteButton = showEditButton;
|
||||||
|
|
||||||
|
// TODO: apply RBAC
|
||||||
|
const showApproveButton = showEditButton;
|
||||||
|
const showRejectButton = showEditButton;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<Button
|
||||||
@@ -44,30 +75,59 @@ const RowOptionsMenu = ({
|
|||||||
Detail
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
{showEditButton && (
|
||||||
href={`/expense/detail/edit/?expenseId=${props.row.original.id}`}
|
<Button
|
||||||
variant='ghost'
|
href={`/expense/detail/edit/?expenseId=${props.row.original.id}`}
|
||||||
color='warning'
|
variant='ghost'
|
||||||
className='justify-start text-sm'
|
color='warning'
|
||||||
>
|
|
||||||
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={deleteClickHandler}
|
|
||||||
variant='ghost'
|
|
||||||
color='error'
|
|
||||||
className='text-error hover:text-inherit'
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon='material-symbols:delete-outline-rounded'
|
|
||||||
width={16}
|
|
||||||
height={16}
|
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
/>
|
>
|
||||||
Delete
|
<Icon icon='material-symbols:edit-outline' width={16} height={16} />
|
||||||
</Button>
|
Edit
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* TODO: apply RBAC */}
|
||||||
|
{showApproveButton && (
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
color='success'
|
||||||
|
onClick={approveClickHandler}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:check' width={24} height={24} />
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showRejectButton && (
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
onClick={rejectClickHandler}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:close' width={24} height={24} />
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showDeleteButton && (
|
||||||
|
<Button
|
||||||
|
onClick={deleteClickHandler}
|
||||||
|
variant='ghost'
|
||||||
|
color='error'
|
||||||
|
className='justify-start text-sm text-error focus-visible:text-error-content hover:text-error-content'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:delete-outline-rounded'
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className='justify-start text-sm'
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</RowOptionsMenuWrapper>
|
</RowOptionsMenuWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -80,8 +140,25 @@ const ExpensesTable = () => {
|
|||||||
setPageSize,
|
setPageSize,
|
||||||
toQueryString: getTableFilterQueryString,
|
toQueryString: getTableFilterQueryString,
|
||||||
} = useTableFilter({
|
} = useTableFilter({
|
||||||
initial: { search: '', nameSort: '' },
|
initial: {
|
||||||
paramMap: { page: 'page', pageSize: 'limit', nameSort: 'sort_name' },
|
search: '',
|
||||||
|
nameSort: '',
|
||||||
|
transactionDate: '',
|
||||||
|
realizationDate: '',
|
||||||
|
locationId: '',
|
||||||
|
vendorId: '',
|
||||||
|
userId: '',
|
||||||
|
},
|
||||||
|
paramMap: {
|
||||||
|
page: 'page',
|
||||||
|
pageSize: 'limit',
|
||||||
|
nameSort: 'sort_name',
|
||||||
|
transactionDate: 'transaction_date',
|
||||||
|
realizationDate: 'realization_date',
|
||||||
|
locationId: 'location_id',
|
||||||
|
vendorId: 'vendor_id',
|
||||||
|
userId: 'user_id',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -94,21 +171,51 @@ const ExpensesTable = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const deleteModal = useModal();
|
const deleteModal = useModal();
|
||||||
|
const approveModal = useModal();
|
||||||
|
const rejectModal = useModal();
|
||||||
|
|
||||||
const [selectedExpense, setSelectedExpense] = useState<Expense | undefined>(
|
const [selectedExpense, setSelectedExpense] = useState<Expense | undefined>(
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
|
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
||||||
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
||||||
|
const selectedRowIds = Object.keys(rowSelection).map((item) =>
|
||||||
|
parseInt(item)
|
||||||
|
);
|
||||||
|
|
||||||
const expensesColumns: ColumnDef<Expense>[] = [
|
const expensesColumns: ColumnDef<Expense>[] = [
|
||||||
{
|
{
|
||||||
header: '#',
|
id: 'select',
|
||||||
cell: (props) =>
|
header: ({ table }) => (
|
||||||
tableFilterState.pageSize * (tableFilterState.page - 1) +
|
<div className='w-full flex flex-row justify-center'>
|
||||||
props.row.index +
|
<CheckboxInput
|
||||||
1,
|
name='allRow'
|
||||||
|
checked={table.getIsAllRowsSelected()}
|
||||||
|
indeterminate={table.getIsSomeRowsSelected()}
|
||||||
|
onChange={table.getToggleAllRowsSelectedHandler()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const isCheckboxDisabled =
|
||||||
|
!row.getCanSelect() || row.original.approval.action === 'REJECTED';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<CheckboxInput
|
||||||
|
name='row'
|
||||||
|
checked={row.getIsSelected()}
|
||||||
|
disabled={isCheckboxDisabled}
|
||||||
|
indeterminate={row.getIsSomeSelected()}
|
||||||
|
onChange={row.getToggleSelectedHandler()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'transaction_date',
|
accessorKey: 'transaction_date',
|
||||||
@@ -158,17 +265,15 @@ const ExpensesTable = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Status Pencairan',
|
header: 'Status Pencairan',
|
||||||
cell: (props) => {
|
cell: (props) => (
|
||||||
// TODO: integrate this to API
|
<RealizationStatusBadge approval={props.row.original.approval} />
|
||||||
return 'test';
|
),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Status BOP',
|
header: 'Status BOP',
|
||||||
cell: (props) => {
|
cell: (props) => (
|
||||||
// TODO: integrate this to API
|
<ExpenseStatusBadge approval={props.row.original.approval} />
|
||||||
return 'test';
|
),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Aksi',
|
header: 'Aksi',
|
||||||
@@ -180,6 +285,28 @@ const ExpensesTable = () => {
|
|||||||
|
|
||||||
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
|
const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
|
||||||
|
|
||||||
|
const approveClickHandler = () => {
|
||||||
|
setSelectedExpense(props.row.original);
|
||||||
|
|
||||||
|
// Set row selection
|
||||||
|
setRowSelection({
|
||||||
|
[String(props.row.original.id)]: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
approveModal.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const rejectClickHandler = () => {
|
||||||
|
setSelectedExpense(props.row.original);
|
||||||
|
|
||||||
|
// Set row selection
|
||||||
|
setRowSelection({
|
||||||
|
[String(props.row.original.id)]: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
rejectModal.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
const deleteClickHandler = () => {
|
const deleteClickHandler = () => {
|
||||||
setSelectedExpense(props.row.original);
|
setSelectedExpense(props.row.original);
|
||||||
deleteModal.openModal();
|
deleteModal.openModal();
|
||||||
@@ -192,6 +319,8 @@ const ExpensesTable = () => {
|
|||||||
<RowOptionsMenu
|
<RowOptionsMenu
|
||||||
type='dropdown'
|
type='dropdown'
|
||||||
props={props}
|
props={props}
|
||||||
|
approveClickHandler={approveClickHandler}
|
||||||
|
rejectClickHandler={rejectClickHandler}
|
||||||
deleteClickHandler={deleteClickHandler}
|
deleteClickHandler={deleteClickHandler}
|
||||||
/>
|
/>
|
||||||
</RowDropdownOptions>
|
</RowDropdownOptions>
|
||||||
@@ -202,6 +331,8 @@ const ExpensesTable = () => {
|
|||||||
<RowOptionsMenu
|
<RowOptionsMenu
|
||||||
type='dropdown'
|
type='dropdown'
|
||||||
props={props}
|
props={props}
|
||||||
|
approveClickHandler={approveClickHandler}
|
||||||
|
rejectClickHandler={rejectClickHandler}
|
||||||
deleteClickHandler={deleteClickHandler}
|
deleteClickHandler={deleteClickHandler}
|
||||||
/>
|
/>
|
||||||
</RowCollapseOptions>
|
</RowCollapseOptions>
|
||||||
@@ -212,6 +343,20 @@ const ExpensesTable = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const tableEnableRowSelectionHandler: (row: Row<Expense>) => boolean = (
|
||||||
|
row
|
||||||
|
) => {
|
||||||
|
return row.original.approval.action !== 'REJECTED';
|
||||||
|
};
|
||||||
|
|
||||||
|
const bulkApproveClickHandler = () => {
|
||||||
|
approveModal.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const bulkRejectClickHandler = () => {
|
||||||
|
rejectModal.openModal();
|
||||||
|
};
|
||||||
|
|
||||||
const confirmationModalDeleteClickHandler = async () => {
|
const confirmationModalDeleteClickHandler = async () => {
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
|
|
||||||
@@ -223,10 +368,108 @@ const ExpensesTable = () => {
|
|||||||
setIsDeleteLoading(false);
|
setIsDeleteLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const confirmationModalApproveClickHandler = async (notes: string) => {
|
||||||
|
setIsApproveLoading(true);
|
||||||
|
|
||||||
|
const bulkApproveResponse = await ExpenseApi.bulkApprove(
|
||||||
|
selectedRowIds,
|
||||||
|
notes
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isResponseSuccess(bulkApproveResponse)) {
|
||||||
|
refreshExpenses();
|
||||||
|
approveModal.closeModal();
|
||||||
|
|
||||||
|
toast.success(
|
||||||
|
`Berhasil approve ${selectedRowIds.length} data transfer ke laying!`
|
||||||
|
);
|
||||||
|
|
||||||
|
setRowSelection({});
|
||||||
|
} else {
|
||||||
|
approveModal.closeModal();
|
||||||
|
|
||||||
|
toast.error(
|
||||||
|
`Gagal approve ${selectedRowIds.length} data transfer ke laying!`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsApproveLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmationModalRejectClickHandler = async (notes: string) => {
|
||||||
|
setIsRejectLoading(true);
|
||||||
|
|
||||||
|
const bulkRejectResponse = await ExpenseApi.bulkReject(
|
||||||
|
selectedRowIds,
|
||||||
|
notes
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isResponseSuccess(bulkRejectResponse)) {
|
||||||
|
refreshExpenses();
|
||||||
|
rejectModal.closeModal();
|
||||||
|
|
||||||
|
toast.success(
|
||||||
|
`Berhasil reject ${selectedRowIds.length} data transfer ke laying!`
|
||||||
|
);
|
||||||
|
setRowSelection({});
|
||||||
|
} else {
|
||||||
|
rejectModal.closeModal();
|
||||||
|
|
||||||
|
toast.error(
|
||||||
|
`Gagal reject ${selectedRowIds.length} data transfer ke laying!`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsRejectLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
setInputValue: setLocationInputValue,
|
||||||
|
options: locationOptions,
|
||||||
|
isLoadingOptions: isLoadingLocationOptions,
|
||||||
|
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||||
|
|
||||||
|
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
|
setSelectedLocation(val as OptionType);
|
||||||
|
updateFilter(
|
||||||
|
'locationId',
|
||||||
|
val ? ((val as OptionType).value as string) : ''
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
setInputValue: setVendorInputValue,
|
||||||
|
options: vendorOptions,
|
||||||
|
isLoadingOptions: isLoadingVendorOptions,
|
||||||
|
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
||||||
|
|
||||||
|
const [selectedVendor, setSelectedVendor] = useState<OptionType | null>(null);
|
||||||
|
|
||||||
|
const vendorChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
|
setSelectedVendor(val as OptionType);
|
||||||
|
updateFilter('vendorId', val ? ((val as OptionType).value as string) : '');
|
||||||
|
};
|
||||||
|
|
||||||
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
||||||
updateFilter('search', e.target.value);
|
updateFilter('search', e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const transactionDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (
|
||||||
|
e
|
||||||
|
) => {
|
||||||
|
updateFilter('transactionDate', e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const realizationDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (
|
||||||
|
e
|
||||||
|
) => {
|
||||||
|
updateFilter('realizationDate', e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
const newVal = val as OptionType;
|
const newVal = val as OptionType;
|
||||||
|
|
||||||
@@ -248,39 +491,128 @@ const ExpensesTable = () => {
|
|||||||
<>
|
<>
|
||||||
<div className='w-full p-0 sm:p-4'>
|
<div className='w-full p-0 sm:p-4'>
|
||||||
<div className='flex flex-col gap-2 mb-4'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-2'>
|
<div className='flex flex-col gap-2 mb-4'>
|
||||||
<div className='w-full flex flex-row'>
|
<div className='w-full flex flex-col sm:flex-row justify-between items-end sm:items-center gap-4'>
|
||||||
<Button
|
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
||||||
href='/expense/add'
|
<Button
|
||||||
variant='outline'
|
href='/expense/add'
|
||||||
color='primary'
|
variant='outline'
|
||||||
className='w-full sm:w-fit'
|
color='primary'
|
||||||
>
|
className='w-full sm:w-fit'
|
||||||
<Icon icon='ic:round-plus' width={24} height={24} />
|
>
|
||||||
Tambah
|
<Icon icon='ic:round-plus' width={24} height={24} />
|
||||||
</Button>
|
Tambah
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{selectedRowIds.length > 0 && (
|
||||||
|
<>
|
||||||
|
{/* TODO: apply RBAC */}
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='success'
|
||||||
|
onClick={bulkApproveClickHandler}
|
||||||
|
disabled={selectedRowIds.length === 0}
|
||||||
|
className='w-full sm:w-fit'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:check'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant='outline'
|
||||||
|
color='error'
|
||||||
|
onClick={bulkRejectClickHandler}
|
||||||
|
disabled={selectedRowIds.length === 0}
|
||||||
|
className='w-full sm:w-fit'
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:close'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DebouncedTextInput
|
||||||
|
name='search'
|
||||||
|
placeholder='Cari Biaya Operasional'
|
||||||
|
value={tableFilterState.search}
|
||||||
|
onChange={searchChangeHandler}
|
||||||
|
className={{ wrapper: 'sm:max-w-3xs' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DebouncedTextInput
|
<div className='grid grid-cols-12 justify-end gap-2'>
|
||||||
name='search'
|
<DateInput
|
||||||
placeholder='Cari Biaya Operasional'
|
required
|
||||||
value={tableFilterState.search}
|
label='Tanggal Transaksi'
|
||||||
onChange={searchChangeHandler}
|
name='transaction_date'
|
||||||
className={{ wrapper: 'sm:max-w-3xs' }}
|
placeholder='Masukkan tanggal transaksi'
|
||||||
/>
|
value={tableFilterState.transactionDate}
|
||||||
</div>
|
onChange={transactionDateChangeHandler}
|
||||||
|
className={{
|
||||||
|
wrapper: 'col-span-12 sm:col-span-3',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className='flex flex-row justify-end'>
|
<DateInput
|
||||||
<SelectInput
|
required
|
||||||
label='Baris'
|
label='Tanggal Realisasi'
|
||||||
options={ROWS_OPTIONS}
|
name='realization_date'
|
||||||
value={{
|
placeholder='Masukkan tanggal realisasi'
|
||||||
label: String(tableFilterState.pageSize),
|
value={tableFilterState.realizationDate}
|
||||||
value: tableFilterState.pageSize,
|
onChange={realizationDateChangeHandler}
|
||||||
}}
|
className={{
|
||||||
onChange={pageSizeChangeHandler}
|
wrapper: 'col-span-12 sm:col-span-3',
|
||||||
className={{ wrapper: 'max-w-28' }}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SelectInput
|
||||||
|
label='Lokasi'
|
||||||
|
options={locationOptions}
|
||||||
|
isLoading={isLoadingLocationOptions}
|
||||||
|
value={selectedLocation}
|
||||||
|
onChange={locationChangeHandler}
|
||||||
|
onInputChange={setLocationInputValue}
|
||||||
|
isClearable
|
||||||
|
className={{
|
||||||
|
wrapper: 'col-span-12 sm:col-span-3',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectInput
|
||||||
|
label='Vendor'
|
||||||
|
options={vendorOptions}
|
||||||
|
isLoading={isLoadingVendorOptions}
|
||||||
|
value={selectedVendor}
|
||||||
|
onChange={vendorChangeHandler}
|
||||||
|
onInputChange={setVendorInputValue}
|
||||||
|
isClearable
|
||||||
|
className={{
|
||||||
|
wrapper: 'col-span-12 sm:col-span-3',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectInput
|
||||||
|
label='Baris'
|
||||||
|
options={ROWS_OPTIONS}
|
||||||
|
value={{
|
||||||
|
label: String(tableFilterState.pageSize),
|
||||||
|
value: tableFilterState.pageSize,
|
||||||
|
}}
|
||||||
|
onChange={pageSizeChangeHandler}
|
||||||
|
className={{
|
||||||
|
wrapper: 'col-span-12 max-w-28 justify-self-end',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -296,6 +628,9 @@ const ExpensesTable = () => {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
sorting={sorting}
|
sorting={sorting}
|
||||||
setSorting={setSorting}
|
setSorting={setSorting}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
setRowSelection={setRowSelection}
|
||||||
|
enableRowSelection={tableEnableRowSelectionHandler}
|
||||||
className={{
|
className={{
|
||||||
containerClassName: cn({
|
containerClassName: cn({
|
||||||
'mb-20':
|
'mb-20':
|
||||||
@@ -327,6 +662,36 @@ const ExpensesTable = () => {
|
|||||||
onClick: confirmationModalDeleteClickHandler,
|
onClick: confirmationModalDeleteClickHandler,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ConfirmationModalWithNotes
|
||||||
|
ref={approveModal.ref}
|
||||||
|
type='success'
|
||||||
|
text={`Apakah anda yakin ingin approve data biaya operasional ini (${selectedRowIds.length} data)?`}
|
||||||
|
secondaryButton={{
|
||||||
|
text: 'Tidak',
|
||||||
|
}}
|
||||||
|
primaryButton={{
|
||||||
|
text: 'Ya',
|
||||||
|
color: 'success',
|
||||||
|
isLoading: isApproveLoading,
|
||||||
|
onClick: confirmationModalApproveClickHandler,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ConfirmationModalWithNotes
|
||||||
|
ref={rejectModal.ref}
|
||||||
|
type='error'
|
||||||
|
text={`Apakah anda yakin ingin reject data biaya operasional ini (${selectedRowIds.length} data)?`}
|
||||||
|
secondaryButton={{
|
||||||
|
text: 'Tidak',
|
||||||
|
}}
|
||||||
|
primaryButton={{
|
||||||
|
text: 'Ya',
|
||||||
|
color: 'error',
|
||||||
|
isLoading: isRejectLoading,
|
||||||
|
onClick: confirmationModalRejectClickHandler,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user