mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
1484 lines
46 KiB
TypeScript
1484 lines
46 KiB
TypeScript
'use client';
|
|
|
|
import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
|
|
import useSWR from 'swr';
|
|
import {
|
|
CellContext,
|
|
ColumnDef,
|
|
Row,
|
|
SortingState,
|
|
Updater,
|
|
} from '@tanstack/react-table';
|
|
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 Modal, { useModal } from '@/components/Modal';
|
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
|
import PopoverButton from '@/components/popover/PopoverButton';
|
|
import PopoverContent from '@/components/popover/PopoverContent';
|
|
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 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 StatusBadge from '@/components/helper/StatusBadge';
|
|
|
|
import { Expense } from '@/types/api/expense';
|
|
import { ExpenseApi } from '@/services/api/expense';
|
|
import { cn, formatCurrency, formatDate } from '@/lib/helper';
|
|
import { getErrorMessage, isResponseSuccess } from '@/lib/api-helper';
|
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
|
import { BaseApiResponse } from '@/types/api/api-general';
|
|
|
|
type ExpenseTableFilters = {
|
|
search: string;
|
|
sort_by: string;
|
|
order_by: string;
|
|
transactionDate: string;
|
|
realizationDate: string;
|
|
locationId: string;
|
|
locationName: string;
|
|
vendorId: string;
|
|
vendorName: string;
|
|
category: string;
|
|
approvalStatus: string;
|
|
realizationStatus: string;
|
|
projectFlockId: string;
|
|
projectFlockName: string;
|
|
projectFlockKandangId: string;
|
|
projectFlockKandangName: string;
|
|
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 RowOptionsMenu = ({
|
|
popoverPosition = 'bottom',
|
|
props,
|
|
deleteClickHandler,
|
|
paidOffClickHandler,
|
|
}: {
|
|
popoverPosition: 'bottom' | 'top';
|
|
props: CellContext<Expense, unknown>;
|
|
deleteClickHandler: () => void;
|
|
paidOffClickHandler: () => void;
|
|
}) => {
|
|
const popoverId = `expense#${props.row.original.id}`;
|
|
const popoverAnchorName = `--anchor-expense#${props.row.original.id}`;
|
|
|
|
const closePopover = () => {
|
|
document.getElementById(popoverId)?.hidePopover();
|
|
};
|
|
|
|
const showEditButton = props.row.original.latest_approval
|
|
? props.row.original.latest_approval.step_number !== 6 &&
|
|
(props.row.original.latest_approval.step_number === 1 ||
|
|
props.row.original.latest_approval.step_number === 2 ||
|
|
props.row.original.latest_approval.step_number === 3 ||
|
|
props.row.original.latest_approval.step_number === 4)
|
|
: false;
|
|
|
|
const showRealizationButton = props.row.original.latest_approval
|
|
? props.row.original.latest_approval.action !== 'REJECTED' &&
|
|
props.row.original.latest_approval.step_number === 4
|
|
: false;
|
|
|
|
const showPaidOffButton = props.row.original.latest_approval
|
|
? props.row.original.latest_approval.step_number >= 4 &&
|
|
!props.row.original.is_paid
|
|
: false;
|
|
|
|
return (
|
|
<div className='relative'>
|
|
<PopoverButton
|
|
tabIndex={0}
|
|
variant='ghost'
|
|
color='none'
|
|
popoverTarget={popoverId}
|
|
anchorName={popoverAnchorName}
|
|
>
|
|
<Icon icon='material-symbols:more-vert' width={16} height={16} />
|
|
</PopoverButton>
|
|
|
|
<PopoverContent
|
|
id={popoverId}
|
|
anchorName={popoverAnchorName}
|
|
position={popoverPosition === 'bottom' ? 'bottom-start' : 'left'}
|
|
className='w-full max-w-40 rounded-xl border border-base-content/5 shadow-sm'
|
|
>
|
|
<div className='flex flex-col bg-base-100 rounded-xl'>
|
|
<RequirePermission permissions='lti.expense.detail'>
|
|
<Button
|
|
href={`/expense/detail/?expenseId=${props.row.original.id}`}
|
|
variant='ghost'
|
|
color='none'
|
|
className='p-3 justify-start text-sm font-semibold w-full'
|
|
onClick={closePopover}
|
|
>
|
|
<Icon icon='heroicons:eye' width={20} height={20} />
|
|
Detail
|
|
</Button>
|
|
</RequirePermission>
|
|
|
|
{showEditButton && (
|
|
<RequirePermission permissions='lti.expense.update'>
|
|
<Button
|
|
href={`/expense/detail/edit/?expenseId=${props.row.original.id}`}
|
|
variant='ghost'
|
|
color='none'
|
|
className='p-3 justify-start text-sm font-semibold w-full'
|
|
onClick={closePopover}
|
|
>
|
|
<Icon icon='mdi:pencil-outline' width={20} height={20} />
|
|
Edit
|
|
</Button>
|
|
</RequirePermission>
|
|
)}
|
|
|
|
{showRealizationButton && (
|
|
<RequirePermission permissions='lti.expense.create.realization'>
|
|
<Button
|
|
href={`/expense/realization/?expenseId=${props.row.original.id}`}
|
|
variant='ghost'
|
|
color='none'
|
|
className='p-3 justify-start text-sm font-semibold w-full'
|
|
onClick={closePopover}
|
|
>
|
|
<Icon
|
|
icon='material-symbols:money-bag-rounded'
|
|
width={20}
|
|
height={20}
|
|
className='text-info'
|
|
/>
|
|
Realisasi
|
|
</Button>
|
|
</RequirePermission>
|
|
)}
|
|
|
|
{showPaidOffButton && (
|
|
<RequirePermission permissions='lti.expense.create.realization'>
|
|
<Button
|
|
onClick={() => {
|
|
paidOffClickHandler();
|
|
closePopover();
|
|
}}
|
|
variant='ghost'
|
|
color='none'
|
|
className='p-3 justify-start text-sm font-semibold w-full'
|
|
>
|
|
<Icon
|
|
icon='material-symbols:check-circle-outline'
|
|
width={20}
|
|
height={20}
|
|
className='text-success'
|
|
/>
|
|
Tandai Lunas
|
|
</Button>
|
|
</RequirePermission>
|
|
)}
|
|
|
|
<RequirePermission permissions='lti.expense.delete'>
|
|
<Button
|
|
onClick={() => {
|
|
deleteClickHandler();
|
|
closePopover();
|
|
}}
|
|
variant='ghost'
|
|
color='error'
|
|
className='p-3 justify-start text-sm font-semibold w-full focus-visible:text-error-content hover:text-error-content'
|
|
>
|
|
<Icon icon='mdi:delete-outline' width={20} height={20} />
|
|
Delete
|
|
</Button>
|
|
</RequirePermission>
|
|
</div>
|
|
</PopoverContent>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const ExpensesTable = () => {
|
|
const {
|
|
state: tableFilterState,
|
|
updateFilter,
|
|
setPage,
|
|
setPageSize,
|
|
toQueryString: getTableFilterQueryString,
|
|
reset: resetFilter,
|
|
} = useTableFilter<ExpenseTableFilters>({
|
|
initial: {
|
|
page: 1,
|
|
pageSize: 10,
|
|
search: '',
|
|
sort_by: '',
|
|
order_by: '',
|
|
transactionDate: '',
|
|
realizationDate: '',
|
|
locationId: '',
|
|
locationName: '',
|
|
vendorId: '',
|
|
vendorName: '',
|
|
category: '',
|
|
approvalStatus: '',
|
|
realizationStatus: '',
|
|
projectFlockId: '',
|
|
projectFlockName: '',
|
|
projectFlockKandangId: '',
|
|
projectFlockKandangName: '',
|
|
userId: '',
|
|
},
|
|
paramMap: {
|
|
page: 'page',
|
|
pageSize: 'limit',
|
|
sort_by: 'sort_by',
|
|
order_by: 'sort_order',
|
|
transactionDate: 'transaction_date',
|
|
realizationDate: 'realization_date',
|
|
locationId: 'location_id',
|
|
locationName: 'location_name',
|
|
vendorId: 'vendor_id',
|
|
vendorName: 'vendor_name',
|
|
category: 'category',
|
|
approvalStatus: 'approval_status',
|
|
realizationStatus: 'realization_status',
|
|
projectFlockId: 'project_flock_id',
|
|
projectFlockName: 'project_flock_name',
|
|
projectFlockKandangId: 'project_flock_kandang_id',
|
|
projectFlockKandangName: 'project_flock_kandang_name',
|
|
userId: 'user_id',
|
|
},
|
|
|
|
persist: true,
|
|
storeName: 'expense-table',
|
|
});
|
|
|
|
const {
|
|
data: expenses,
|
|
isLoading,
|
|
mutate: refreshExpenses,
|
|
} = useSWR(
|
|
`${ExpenseApi.basePath}${getTableFilterQueryString()}`,
|
|
ExpenseApi.getAllFetcher
|
|
);
|
|
|
|
const deleteModal = useModal();
|
|
const approveModal = useModal();
|
|
const rejectModal = useModal();
|
|
const paidOffModal = useModal();
|
|
const bulkApproveFormModal = useModal();
|
|
const exportProgressInputModal = useModal();
|
|
|
|
// ===== FILTER MODAL STATE =====
|
|
const filterModal = useModal();
|
|
|
|
const [selectedExpense, setSelectedExpense] = useState<Expense | undefined>(
|
|
undefined
|
|
);
|
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
|
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
|
const [isPaidOffLoading, setIsPaidOffLoading] = useState(false);
|
|
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
|
|
useState(false);
|
|
const [isExportProgressLoading, setIsExportProgressLoading] = useState(false);
|
|
const [, setApprovalNotes] = useState('');
|
|
const [bulkApprovalStatus, setBulkApprovalStatus] =
|
|
useState<OptionType<ApprovalStatusValue> | null>(null);
|
|
const [bulkApprovalDate, setBulkApprovalDate] = useState('');
|
|
const [bulkApprovalNotes, setBulkApprovalNotes] = useState('');
|
|
const [exportProgressStartDate, setExportProgressStartDate] = useState('');
|
|
const [exportProgressEndDate, setExportProgressEndDate] = useState('');
|
|
|
|
const sorting: SortingState = tableFilterState.sort_by
|
|
? [
|
|
{
|
|
id: tableFilterState.sort_by,
|
|
desc: tableFilterState.order_by === 'desc',
|
|
},
|
|
]
|
|
: [];
|
|
|
|
const handleSortingChange = (updater: Updater<SortingState>) => {
|
|
const next = typeof updater === 'function' ? updater(sorting) : updater;
|
|
if (next.length > 0) {
|
|
updateFilter('sort_by', next[0].id, true);
|
|
updateFilter('order_by', next[0].desc ? 'desc' : 'asc', true);
|
|
} else {
|
|
updateFilter('sort_by', '', true);
|
|
updateFilter('order_by', '', true);
|
|
}
|
|
};
|
|
|
|
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
|
|
const selectedRowIds = Object.keys(rowSelection).map((item) =>
|
|
parseInt(item)
|
|
);
|
|
|
|
const isAllSelectedRowLatestApprovalOnHeadArea = useMemo(() => {
|
|
return selectedRowIds.every((rowId) => {
|
|
if (!isResponseSuccess(expenses)) return false;
|
|
|
|
const expenseItem = expenses.data.find((item) => item.id === rowId);
|
|
|
|
const isLatestApprovalRejected =
|
|
expenseItem?.latest_approval.action === 'REJECTED';
|
|
|
|
const isCurrentApprovalOnHeadArea =
|
|
!isLatestApprovalRejected &&
|
|
expenseItem?.latest_approval.step_number === 1;
|
|
|
|
return isCurrentApprovalOnHeadArea;
|
|
});
|
|
}, [expenses, selectedRowIds]);
|
|
|
|
const isAllSelectedRowLatestApprovalOnUnitVicePresident = useMemo(() => {
|
|
return selectedRowIds.every((rowId) => {
|
|
if (!isResponseSuccess(expenses)) return false;
|
|
|
|
const expenseItem = expenses.data.find((item) => item.id === rowId);
|
|
|
|
const isLatestApprovalRejected =
|
|
expenseItem?.latest_approval.action === 'REJECTED';
|
|
|
|
const isCurrentApprovalOnUnitVicePresident =
|
|
!isLatestApprovalRejected &&
|
|
expenseItem?.latest_approval.step_number === 2;
|
|
|
|
return isCurrentApprovalOnUnitVicePresident;
|
|
});
|
|
}, [expenses, selectedRowIds]);
|
|
|
|
const isAllSelectedRowLatestApprovalOnFinance = useMemo(() => {
|
|
return selectedRowIds.every((rowId) => {
|
|
if (!isResponseSuccess(expenses)) return false;
|
|
|
|
const expenseItem = expenses.data.find((item) => item.id === rowId);
|
|
|
|
const isLatestApprovalRejected =
|
|
expenseItem?.latest_approval.action === 'REJECTED';
|
|
|
|
const isCurrentApprovalOnFinance =
|
|
!isLatestApprovalRejected &&
|
|
expenseItem?.latest_approval.step_number === 3;
|
|
|
|
return isCurrentApprovalOnFinance;
|
|
});
|
|
}, [expenses, selectedRowIds]);
|
|
|
|
const expensesColumns: ColumnDef<Expense>[] = [
|
|
{
|
|
id: 'select',
|
|
header: ({ table }) => (
|
|
<div className='w-full flex flex-row justify-center'>
|
|
<CheckboxInput
|
|
name='allRow'
|
|
checked={table.getIsAllRowsSelected()}
|
|
indeterminate={table.getIsSomeRowsSelected()}
|
|
onChange={table.getToggleAllRowsSelectedHandler()}
|
|
/>
|
|
</div>
|
|
),
|
|
cell: ({ row }) => {
|
|
const isCheckboxDisabled =
|
|
!row.getCanSelect() ||
|
|
!row.original.latest_approval ||
|
|
row.original.latest_approval.action === 'REJECTED';
|
|
|
|
return (
|
|
<div>
|
|
<CheckboxInput
|
|
name='row'
|
|
checked={row.getIsSelected()}
|
|
disabled={isCheckboxDisabled}
|
|
indeterminate={row.getIsSomeSelected()}
|
|
onChange={row.getToggleSelectedHandler()}
|
|
/>
|
|
</div>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
accessorKey: 'reference_number',
|
|
header: 'Nomor Referensi',
|
|
cell: (props) => {
|
|
return props.row.original.reference_number ?? '-';
|
|
},
|
|
},
|
|
{
|
|
accessorKey: 'transaction_date',
|
|
header: 'Tanggal Pengajuan',
|
|
cell: (props) =>
|
|
props.row.original.transaction_date
|
|
? formatDate(props.row.original.transaction_date, 'DD MMM YYYY')
|
|
: '-',
|
|
},
|
|
{
|
|
accessorKey: 'realization_date',
|
|
header: 'Tanggal Realisasi',
|
|
cell: (props) =>
|
|
props.row.original.realization_date
|
|
? formatDate(props.row.original.realization_date, 'DD MMM YYYY')
|
|
: '-',
|
|
},
|
|
{
|
|
accessorKey: 'location',
|
|
header: 'Lokasi',
|
|
cell: (props) => props.row.original.location?.name ?? '-',
|
|
},
|
|
{
|
|
accessorKey: 'created_user',
|
|
accessorFn: (row) => row.created_user.name ?? '-',
|
|
header: 'Nama Pengaju',
|
|
},
|
|
{
|
|
accessorKey: 'supplier',
|
|
accessorFn: (row) => row.supplier.name ?? '-',
|
|
header: 'Uraian',
|
|
},
|
|
{
|
|
accessorKey: 'grand_total',
|
|
header: 'Nominal',
|
|
cell: (props) =>
|
|
props.row.original.grand_total
|
|
? formatCurrency(props.row.original.grand_total)
|
|
: '-',
|
|
},
|
|
{
|
|
header: 'Status Pencairan',
|
|
enableSorting: false,
|
|
cell: (props) => (
|
|
<RealizationStatusBadge approval={props.row.original.latest_approval} />
|
|
),
|
|
},
|
|
{
|
|
header: 'Status BOP',
|
|
enableSorting: false,
|
|
cell: (props) => (
|
|
<ExpenseStatusBadge approval={props.row.original.latest_approval} />
|
|
),
|
|
},
|
|
{
|
|
accessorKey: 'is_paid',
|
|
header: 'Status Lunas',
|
|
cell: (props) => {
|
|
return (
|
|
<StatusBadge
|
|
color={props.row.original.is_paid ? 'primary' : 'warning'}
|
|
text={props.row.original.is_paid ? 'Lunas' : 'Belum Lunas'}
|
|
className={{
|
|
badge: 'w-fit whitespace-nowrap',
|
|
}}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
accessorKey: 'created_at',
|
|
header: 'Tanggal Dibuat',
|
|
cell: (props) =>
|
|
props.row.original.created_at
|
|
? formatDate(props.row.original.created_at, 'DD MMM YYYY')
|
|
: '-',
|
|
},
|
|
{
|
|
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 - 2;
|
|
|
|
const deleteClickHandler = () => {
|
|
setSelectedExpense(props.row.original);
|
|
deleteModal.openModal();
|
|
};
|
|
|
|
const paidOffClickHandler = () => {
|
|
setSelectedExpense(props.row.original);
|
|
paidOffModal.openModal();
|
|
};
|
|
|
|
return (
|
|
<RowOptionsMenu
|
|
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
|
|
props={props}
|
|
deleteClickHandler={deleteClickHandler}
|
|
paidOffClickHandler={paidOffClickHandler}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
|
|
const tableEnableRowSelectionHandler: (row: Row<Expense>) => boolean = (
|
|
row
|
|
) => {
|
|
if (!row.original.latest_approval) return false;
|
|
|
|
return (
|
|
row.original.latest_approval.action !== 'REJECTED' &&
|
|
row.original.latest_approval.step_number !== 6
|
|
);
|
|
};
|
|
|
|
const resetBulkApproveForm = useCallback(() => {
|
|
setBulkApprovalStatus(null);
|
|
setBulkApprovalDate('');
|
|
setBulkApprovalNotes('');
|
|
}, []);
|
|
|
|
const openBulkApproveForm = useCallback(
|
|
(presetStatus?: ApprovalStatusValue) => {
|
|
resetBulkApproveForm();
|
|
|
|
if (presetStatus) {
|
|
const selectedStatus = approvalStatusOptions.find(
|
|
(option) => option.value === presetStatus
|
|
);
|
|
|
|
if (selectedStatus) {
|
|
setBulkApprovalStatus(selectedStatus);
|
|
}
|
|
}
|
|
|
|
bulkApproveFormModal.openModal();
|
|
},
|
|
[bulkApproveFormModal, resetBulkApproveForm]
|
|
);
|
|
|
|
const bulkApproveClickHandler = () => {
|
|
openBulkApproveForm();
|
|
};
|
|
|
|
const bulkApproveHeadAreaClickHandler = () => {
|
|
openBulkApproveForm('HEAD_AREA');
|
|
};
|
|
|
|
const bulkApproveUnitVicePresidentClickHandler = () => {
|
|
openBulkApproveForm('UNIT_VICE_PRESIDENT');
|
|
};
|
|
|
|
const bulkApproveFinanceClickHandler = () => {
|
|
openBulkApproveForm('FINANCE');
|
|
};
|
|
|
|
const bulkRejectClickHandler = () => {
|
|
setApprovalNotes('');
|
|
rejectModal.openModal();
|
|
};
|
|
|
|
const bulkApprovalDateChangeHandler: ChangeEventHandler<HTMLInputElement> = (
|
|
e
|
|
) => {
|
|
setBulkApprovalDate(e.target.value);
|
|
};
|
|
|
|
const bulkApprovalNotesChangeHandler: ChangeEventHandler<
|
|
HTMLTextAreaElement
|
|
> = (e) => {
|
|
setBulkApprovalNotes(e.target.value);
|
|
};
|
|
|
|
const resetExportProgressForm = useCallback(() => {
|
|
setExportProgressStartDate('');
|
|
setExportProgressEndDate('');
|
|
}, []);
|
|
|
|
const exportProgressStartDateChangeHandler: ChangeEventHandler<
|
|
HTMLInputElement
|
|
> = (e) => {
|
|
setExportProgressStartDate(e.target.value);
|
|
};
|
|
|
|
const exportProgressEndDateChangeHandler: ChangeEventHandler<
|
|
HTMLInputElement
|
|
> = (e) => {
|
|
setExportProgressEndDate(e.target.value);
|
|
};
|
|
|
|
const exportProgressInputToExcelClickHandler = () => {
|
|
resetExportProgressForm();
|
|
exportProgressInputModal.openModal();
|
|
};
|
|
|
|
const submitExportProgressInputHandler = async () => {
|
|
if (!exportProgressStartDate || !exportProgressEndDate) {
|
|
return;
|
|
}
|
|
|
|
setIsExportProgressLoading(true);
|
|
|
|
try {
|
|
await ExpenseApi.exportInputProgressToExcel(
|
|
exportProgressStartDate,
|
|
exportProgressEndDate
|
|
);
|
|
|
|
exportProgressInputModal.closeModal();
|
|
resetExportProgressForm();
|
|
toast.success('Ekspor berhasil');
|
|
} catch (error) {
|
|
toast.error(
|
|
await getErrorMessage(error, 'Gagal mengekspor input progress')
|
|
);
|
|
} finally {
|
|
setIsExportProgressLoading(false);
|
|
}
|
|
};
|
|
|
|
const confirmationModalDeleteClickHandler = async () => {
|
|
setIsDeleteLoading(true);
|
|
|
|
const deleteResponse = await ExpenseApi.delete(
|
|
selectedExpense?.id as number
|
|
);
|
|
|
|
if (isResponseSuccess(deleteResponse)) {
|
|
refreshExpenses();
|
|
deleteModal.closeModal();
|
|
toast.success('Berhasil menghapus biaya operasional!');
|
|
} else {
|
|
deleteModal.closeModal();
|
|
toast.error('Gagal menghapus biaya operasional!');
|
|
}
|
|
|
|
setIsDeleteLoading(false);
|
|
};
|
|
|
|
const confirmationModalPaidOffClickHandler = async () => {
|
|
setIsPaidOffLoading(true);
|
|
|
|
const paidOffResponse = await ExpenseApi.setExpensePaidOff(
|
|
selectedExpense?.id as number
|
|
);
|
|
|
|
if (isResponseSuccess(paidOffResponse)) {
|
|
refreshExpenses();
|
|
paidOffModal.closeModal();
|
|
toast.success('Berhasil menandai biaya operasional sebagai lunas!');
|
|
refreshExpenses();
|
|
} else {
|
|
paidOffModal.closeModal();
|
|
toast.error(
|
|
'Gagal menandai biaya operasional sebagai lunas!: ' +
|
|
paidOffResponse?.message
|
|
);
|
|
}
|
|
|
|
setIsPaidOffLoading(false);
|
|
};
|
|
|
|
const confirmationModalApproveClickHandler = async (notes: string) => {
|
|
setIsApproveLoading(true);
|
|
|
|
let bulkApproveResponse: BaseApiResponse<Expense> | undefined = undefined;
|
|
|
|
if (isAllSelectedRowLatestApprovalOnHeadArea) {
|
|
bulkApproveResponse = await ExpenseApi.bulkApproveHeadArea(
|
|
selectedRowIds,
|
|
notes
|
|
);
|
|
} else if (isAllSelectedRowLatestApprovalOnUnitVicePresident) {
|
|
bulkApproveResponse = await ExpenseApi.bulkApproveUnitVicePresident(
|
|
selectedRowIds,
|
|
notes
|
|
);
|
|
} else if (isAllSelectedRowLatestApprovalOnFinance) {
|
|
bulkApproveResponse = await ExpenseApi.bulkApproveFinance(
|
|
selectedRowIds,
|
|
notes
|
|
);
|
|
}
|
|
|
|
if (isResponseSuccess(bulkApproveResponse)) {
|
|
refreshExpenses();
|
|
approveModal.closeModal();
|
|
|
|
toast.success(
|
|
`Berhasil approve ${selectedRowIds.length} data biaya operasional!`
|
|
);
|
|
|
|
setApprovalNotes('');
|
|
setRowSelection({});
|
|
} else {
|
|
approveModal.closeModal();
|
|
|
|
toast.error(
|
|
`Gagal approve ${selectedRowIds.length} data biaya operasional!`
|
|
);
|
|
}
|
|
|
|
setIsApproveLoading(false);
|
|
};
|
|
|
|
const bulkApproveSubmitHandler = async () => {
|
|
if (!bulkApprovalStatus) {
|
|
return;
|
|
}
|
|
|
|
if (isApprovalDateRequired(bulkApprovalStatus.value) && !bulkApprovalDate) {
|
|
toast.error('Tanggal realisasi wajib diisi.');
|
|
return;
|
|
}
|
|
|
|
if (!bulkApprovalNotes.trim()) {
|
|
toast.error('Catatan wajib diisi.');
|
|
return;
|
|
}
|
|
|
|
setIsApproveLoading(true);
|
|
|
|
const bulkApproveResponse = await ExpenseApi.bulkApprovals(
|
|
selectedRowIds,
|
|
bulkApprovalStatus.value,
|
|
isApprovalDateRequired(bulkApprovalStatus.value) ? bulkApprovalDate : '',
|
|
bulkApprovalNotes
|
|
);
|
|
|
|
if (isResponseSuccess(bulkApproveResponse)) {
|
|
refreshExpenses();
|
|
bulkApproveFormModal.closeModal();
|
|
toast.success(
|
|
`Berhasil approve ${selectedRowIds.length} data biaya operasional!`
|
|
);
|
|
resetBulkApproveForm();
|
|
setRowSelection({});
|
|
} else {
|
|
toast.error(
|
|
bulkApproveResponse?.message ??
|
|
`Gagal approve ${selectedRowIds.length} data biaya operasional!`
|
|
);
|
|
}
|
|
|
|
setIsApproveLoading(false);
|
|
};
|
|
|
|
const confirmationModalRejectClickHandler = async (notes: string) => {
|
|
setIsRejectLoading(true);
|
|
|
|
let bulkRejectResponse: BaseApiResponse<Expense> | undefined = undefined;
|
|
|
|
if (isAllSelectedRowLatestApprovalOnHeadArea) {
|
|
bulkRejectResponse = await ExpenseApi.bulkRejectHeadArea(
|
|
selectedRowIds,
|
|
notes
|
|
);
|
|
} else if (isAllSelectedRowLatestApprovalOnUnitVicePresident) {
|
|
bulkRejectResponse = await ExpenseApi.bulkRejectUnitVicePresident(
|
|
selectedRowIds,
|
|
notes
|
|
);
|
|
} else if (isAllSelectedRowLatestApprovalOnFinance) {
|
|
bulkRejectResponse = await ExpenseApi.bulkRejectFinance(
|
|
selectedRowIds,
|
|
notes
|
|
);
|
|
}
|
|
|
|
if (isResponseSuccess(bulkRejectResponse)) {
|
|
refreshExpenses();
|
|
rejectModal.closeModal();
|
|
|
|
toast.success(
|
|
`Berhasil reject ${selectedRowIds.length} data biaya operasional!`
|
|
);
|
|
setApprovalNotes('');
|
|
setRowSelection({});
|
|
} else {
|
|
rejectModal.closeModal();
|
|
|
|
toast.error(
|
|
`Gagal reject ${selectedRowIds.length} data biaya operasional!`
|
|
);
|
|
}
|
|
|
|
setIsRejectLoading(false);
|
|
};
|
|
|
|
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
|
|
updateFilter('search', e.target.value, true);
|
|
};
|
|
|
|
// ===== FILTER MODAL HANDLERS =====
|
|
const handleFilterModalOpen = () => {
|
|
filterModal.openModal();
|
|
};
|
|
|
|
const handleFilterSubmit = (values: {
|
|
transaction_date?: string | null;
|
|
realization_date?: string | null;
|
|
location?: { value: number; label: string } | null;
|
|
vendor?: { value: number; label: string } | null;
|
|
category?: OptionType<string> | null;
|
|
approval_status?: OptionType<string> | null;
|
|
realization_status?: OptionType<string> | null;
|
|
project_flock?: OptionType<number> | null;
|
|
project_flock_kandang?: OptionType<number> | null;
|
|
}) => {
|
|
updateFilter('transactionDate', values.transaction_date || '', true);
|
|
updateFilter('realizationDate', values.realization_date || '', true);
|
|
updateFilter(
|
|
'locationId',
|
|
values.location?.value ? String(values.location?.value) : '',
|
|
true
|
|
);
|
|
updateFilter(
|
|
'locationName',
|
|
values.location?.label ? String(values.location?.label) : '',
|
|
true
|
|
);
|
|
updateFilter(
|
|
'vendorId',
|
|
values.vendor?.value ? String(values.vendor?.value) : '',
|
|
true
|
|
);
|
|
updateFilter(
|
|
'vendorName',
|
|
values.vendor?.label ? String(values.vendor?.label) : '',
|
|
true
|
|
);
|
|
updateFilter('category', values.category?.value || '', true);
|
|
updateFilter('approvalStatus', values.approval_status?.value || '', true);
|
|
updateFilter(
|
|
'realizationStatus',
|
|
values.realization_status?.value || '',
|
|
true
|
|
);
|
|
updateFilter(
|
|
'projectFlockId',
|
|
values.project_flock?.value ? String(values.project_flock.value) : '',
|
|
true
|
|
);
|
|
updateFilter('projectFlockName', values.project_flock?.label || '', true);
|
|
updateFilter(
|
|
'projectFlockKandangId',
|
|
values.project_flock_kandang?.value
|
|
? String(values.project_flock_kandang.value)
|
|
: '',
|
|
true
|
|
);
|
|
updateFilter(
|
|
'projectFlockKandangName',
|
|
values.project_flock_kandang?.label || '',
|
|
true
|
|
);
|
|
};
|
|
|
|
const handleFilterReset = () => {
|
|
resetFilter();
|
|
};
|
|
|
|
const exportToExcel = useCallback(async () => {
|
|
setIsLoadingExportingToExcel(true);
|
|
|
|
try {
|
|
await ExpenseApi.exportToExcel(getTableFilterQueryString());
|
|
} catch (error) {
|
|
toast.error(
|
|
await getErrorMessage(error, 'Gagal mengekspor data pengeluaran')
|
|
);
|
|
} finally {
|
|
setIsLoadingExportingToExcel(false);
|
|
}
|
|
}, [getTableFilterQueryString]);
|
|
|
|
return (
|
|
<>
|
|
<div className='w-full'>
|
|
<div className='w-full p-3 flex flex-row justify-between gap-3 flex-wrap border-b border-base-content/10'>
|
|
{/* Action Buttons */}
|
|
<div className='w-fit flex flex-col gap-3 flex-wrap'>
|
|
<RequirePermission permissions='lti.expense.create'>
|
|
<Button
|
|
href='/expense/add'
|
|
color='primary'
|
|
className='px-3 py-2.5 w-fit text-sm text-base-100 rounded-xl shadow-button-soft'
|
|
>
|
|
<Icon icon='heroicons:plus' width={20} height={20} />
|
|
Add Expense
|
|
</Button>
|
|
</RequirePermission>
|
|
|
|
{selectedRowIds.length > 0 && (
|
|
<div className='flex flex-row gap-3 flex-wrap'>
|
|
<RequirePermission
|
|
permissions={[
|
|
'lti.expense.approve.head_area',
|
|
'lti.expense.approve.unit_vice_president',
|
|
'lti.expense.approve.finance',
|
|
'lti.expense.create.realization',
|
|
]}
|
|
>
|
|
<Button
|
|
variant='outline'
|
|
color='none'
|
|
onClick={bulkApproveClickHandler}
|
|
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
|
>
|
|
<Icon
|
|
icon='lucide-lab:farm'
|
|
width={20}
|
|
height={20}
|
|
className='text-success'
|
|
/>
|
|
Bulk Approve
|
|
</Button>
|
|
</RequirePermission>
|
|
|
|
<RequirePermission permissions='lti.expense.approve.head_area'>
|
|
<Button
|
|
variant='outline'
|
|
color='none'
|
|
onClick={bulkApproveHeadAreaClickHandler}
|
|
disabled={!isAllSelectedRowLatestApprovalOnHeadArea}
|
|
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
|
>
|
|
<Icon
|
|
icon='lucide-lab:farm'
|
|
width={20}
|
|
height={20}
|
|
className='text-success'
|
|
/>
|
|
Approve Head Area
|
|
</Button>
|
|
</RequirePermission>
|
|
|
|
<RequirePermission permissions='lti.expense.approve.unit_vice_president'>
|
|
<Button
|
|
variant='outline'
|
|
color='none'
|
|
onClick={bulkApproveUnitVicePresidentClickHandler}
|
|
disabled={
|
|
!isAllSelectedRowLatestApprovalOnUnitVicePresident
|
|
}
|
|
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
|
>
|
|
<Icon
|
|
icon='tdesign:money'
|
|
width={20}
|
|
height={20}
|
|
className='text-success'
|
|
/>
|
|
Approve Unit Vice President
|
|
</Button>
|
|
</RequirePermission>
|
|
|
|
<RequirePermission permissions='lti.expense.approve.finance'>
|
|
<Button
|
|
variant='outline'
|
|
color='none'
|
|
onClick={bulkApproveFinanceClickHandler}
|
|
disabled={!isAllSelectedRowLatestApprovalOnFinance}
|
|
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
|
>
|
|
<Icon
|
|
icon='tdesign:money'
|
|
width={20}
|
|
height={20}
|
|
className='text-success'
|
|
/>
|
|
Approve Finance
|
|
</Button>
|
|
</RequirePermission>
|
|
|
|
<RequirePermission
|
|
permissions={[
|
|
'lti.expense.approve.head_area',
|
|
'lti.expense.approve.unit_vice_president',
|
|
'lti.expense.approve.finance',
|
|
]}
|
|
>
|
|
<Button
|
|
variant='outline'
|
|
color='none'
|
|
onClick={bulkRejectClickHandler}
|
|
disabled={
|
|
!isAllSelectedRowLatestApprovalOnHeadArea &&
|
|
!isAllSelectedRowLatestApprovalOnUnitVicePresident &&
|
|
!isAllSelectedRowLatestApprovalOnFinance
|
|
}
|
|
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
|
>
|
|
<Icon
|
|
icon='material-symbols:close'
|
|
width={20}
|
|
height={20}
|
|
className='text-error'
|
|
/>
|
|
Reject
|
|
</Button>
|
|
</RequirePermission>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Search and Filter */}
|
|
<div className='flex flex-1 flex-row justify-start sm:justify-end items-start gap-3 flex-wrap'>
|
|
<DebouncedTextInput
|
|
name='search'
|
|
placeholder='Search'
|
|
value={tableFilterState.search ?? ''}
|
|
onChange={searchChangeHandler}
|
|
startAdornment={
|
|
<Icon
|
|
icon='heroicons:magnifying-glass'
|
|
width={20}
|
|
height={20}
|
|
/>
|
|
}
|
|
className={{
|
|
wrapper: 'w-full min-w-24 max-w-3xs',
|
|
inputWrapper: 'rounded-xl! shadow-button-soft',
|
|
input:
|
|
'placeholder:font-semibold placeholder:text-base-content/50',
|
|
}}
|
|
/>
|
|
|
|
<ButtonFilter
|
|
values={tableFilterState}
|
|
excludeFields={[
|
|
'page',
|
|
'pageSize',
|
|
'search',
|
|
'sort_by',
|
|
'order_by',
|
|
'userId',
|
|
'locationName',
|
|
'vendorName',
|
|
'projectFlockName',
|
|
'projectFlockKandangName',
|
|
]}
|
|
onClick={handleFilterModalOpen}
|
|
className='px-3 py-2.5'
|
|
/>
|
|
|
|
<Dropdown
|
|
align='end'
|
|
direction='bottom'
|
|
className={{
|
|
content:
|
|
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
|
}}
|
|
trigger={
|
|
<Button
|
|
variant='outline'
|
|
color='none'
|
|
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
|
>
|
|
<div className='flex flex-row items-center gap-1.5'>
|
|
<Icon
|
|
icon='heroicons:cloud-arrow-down'
|
|
width={20}
|
|
height={20}
|
|
/>
|
|
|
|
<span>Export</span>
|
|
|
|
<div className='w-px self-stretch bg-base-content/10' />
|
|
|
|
<Icon
|
|
icon='heroicons:chevron-down'
|
|
width={14}
|
|
height={14}
|
|
/>
|
|
</div>
|
|
</Button>
|
|
}
|
|
>
|
|
<Button
|
|
variant='ghost'
|
|
color='none'
|
|
onClick={exportToExcel}
|
|
isLoading={isLoadingExportingToExcel}
|
|
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
|
>
|
|
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
|
Ekspor ke Excel
|
|
</Button>
|
|
|
|
<Button
|
|
variant='ghost'
|
|
color='none'
|
|
onClick={exportProgressInputToExcelClickHandler}
|
|
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
|
>
|
|
<Icon icon='heroicons:table-cells' width={20} height={20} />
|
|
Ekspor Input Progress (Excel)
|
|
</Button>
|
|
</Dropdown>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Table Section */}
|
|
<div className='flex flex-col mb-4'>
|
|
{isLoading ? (
|
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
|
<span className='loading loading-spinner loading-xl' />
|
|
</div>
|
|
) : !isResponseSuccess(expenses) || expenses.data?.length === 0 ? (
|
|
<div className='p-3'>
|
|
<ExpenseTableSkeleton
|
|
columns={expensesColumns}
|
|
icon={
|
|
<Icon
|
|
icon='heroicons:document-text'
|
|
className='text-white'
|
|
width={20}
|
|
height={20}
|
|
/>
|
|
}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<Table<Expense>
|
|
data={isResponseSuccess(expenses) ? expenses?.data : []}
|
|
columns={expensesColumns}
|
|
pageSize={tableFilterState.pageSize}
|
|
page={isResponseSuccess(expenses) ? expenses?.meta?.page : 0}
|
|
totalItems={
|
|
isResponseSuccess(expenses) ? expenses?.meta?.total_results : 0
|
|
}
|
|
onPageChange={setPage}
|
|
onPageSizeChange={setPageSize}
|
|
isLoading={isLoading}
|
|
sorting={sorting}
|
|
setSorting={handleSortingChange}
|
|
manualSorting
|
|
rowSelection={rowSelection}
|
|
setRowSelection={setRowSelection}
|
|
enableRowSelection={tableEnableRowSelectionHandler}
|
|
className={{
|
|
containerClassName: cn('p-3 mb-0'),
|
|
headerColumnClassName: 'text-nowrap',
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<ConfirmationModal
|
|
ref={deleteModal.ref}
|
|
type='error'
|
|
text='Apakah anda yakin ingin menghapus data biaya operasional ini?'
|
|
secondaryButton={{
|
|
text: 'Tidak',
|
|
}}
|
|
primaryButton={{
|
|
text: 'Ya',
|
|
color: 'error',
|
|
isLoading: isDeleteLoading,
|
|
onClick: confirmationModalDeleteClickHandler,
|
|
}}
|
|
/>
|
|
|
|
<ConfirmationModal
|
|
ref={paidOffModal.ref}
|
|
type='success'
|
|
text='Apakah anda yakin ingin menandai biaya operasional ini sebagai lunas?'
|
|
secondaryButton={{
|
|
text: 'Tidak',
|
|
}}
|
|
primaryButton={{
|
|
text: 'Ya',
|
|
color: 'success',
|
|
isLoading: isPaidOffLoading,
|
|
onClick: confirmationModalPaidOffClickHandler,
|
|
}}
|
|
/>
|
|
|
|
<ConfirmationModalWithNotes
|
|
ref={approveModal.ref}
|
|
type='success'
|
|
text='Apakah anda yakin ingin approve data biaya operasional ini?'
|
|
secondaryButton={{
|
|
text: 'Tidak',
|
|
onClick: () => {
|
|
setApprovalNotes('');
|
|
approveModal.closeModal();
|
|
},
|
|
}}
|
|
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?'
|
|
secondaryButton={{
|
|
text: 'Tidak',
|
|
onClick: () => {
|
|
setApprovalNotes('');
|
|
rejectModal.closeModal();
|
|
},
|
|
}}
|
|
primaryButton={{
|
|
text: 'Ya',
|
|
color: 'error',
|
|
isLoading: isRejectLoading,
|
|
onClick: confirmationModalRejectClickHandler,
|
|
}}
|
|
/>
|
|
|
|
<Modal
|
|
ref={bulkApproveFormModal.ref}
|
|
className={{
|
|
modalBox: 'max-w-lg rounded-lg p-0',
|
|
}}
|
|
closeOnBackdrop
|
|
>
|
|
<div className='flex flex-col'>
|
|
<div className='flex items-center justify-between border-b border-base-content/10 p-4'>
|
|
<h4 className='text-sm font-semibold text-base-content'>
|
|
Bulk Approve Expense
|
|
</h4>
|
|
<Button
|
|
variant='ghost'
|
|
color='none'
|
|
onClick={() => {
|
|
bulkApproveFormModal.closeModal();
|
|
resetBulkApproveForm();
|
|
}}
|
|
className='p-1'
|
|
>
|
|
<Icon icon='mdi:close' width={20} height={20} />
|
|
</Button>
|
|
</div>
|
|
|
|
<div className='flex flex-col gap-4 p-4'>
|
|
<SelectInput
|
|
label='Status Approval'
|
|
options={approvalStatusOptions as OptionType[]}
|
|
value={bulkApprovalStatus}
|
|
onChange={(val) => {
|
|
const nextValue = val as OptionType<ApprovalStatusValue> | null;
|
|
setBulkApprovalStatus(nextValue);
|
|
|
|
if (!isApprovalDateRequired(nextValue?.value)) {
|
|
setBulkApprovalDate('');
|
|
}
|
|
}}
|
|
placeholder='Pilih status approval'
|
|
isClearable
|
|
/>
|
|
|
|
{isApprovalDateRequired(bulkApprovalStatus?.value) && (
|
|
<DateInput
|
|
name='bulk_approval_date'
|
|
label='Tanggal Realisasi'
|
|
value={bulkApprovalDate}
|
|
onChange={bulkApprovalDateChangeHandler}
|
|
isNestedModal
|
|
required
|
|
/>
|
|
)}
|
|
|
|
<TextArea
|
|
name='bulk_approval_notes'
|
|
label='Catatan'
|
|
value={bulkApprovalNotes}
|
|
onChange={bulkApprovalNotesChangeHandler}
|
|
placeholder='Masukkan catatan approval...'
|
|
rows={4}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className='flex justify-end gap-3 border-t border-base-content/10 p-4'>
|
|
<Button
|
|
variant='outline'
|
|
color='none'
|
|
onClick={() => {
|
|
bulkApproveFormModal.closeModal();
|
|
resetBulkApproveForm();
|
|
}}
|
|
className='px-3 py-2.5'
|
|
>
|
|
Batal
|
|
</Button>
|
|
<Button
|
|
color='success'
|
|
onClick={bulkApproveSubmitHandler}
|
|
isLoading={isApproveLoading}
|
|
disabled={
|
|
!bulkApprovalStatus ||
|
|
!bulkApprovalNotes.trim() ||
|
|
(isApprovalDateRequired(bulkApprovalStatus.value) &&
|
|
!bulkApprovalDate)
|
|
}
|
|
className='px-3 py-2.5'
|
|
>
|
|
Submit
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
|
|
<Modal
|
|
ref={exportProgressInputModal.ref}
|
|
className={{
|
|
modalBox: 'max-w-lg rounded-lg p-0',
|
|
}}
|
|
closeOnBackdrop
|
|
>
|
|
<div className='flex flex-col'>
|
|
<div className='flex items-center justify-between border-b border-base-content/10 p-4'>
|
|
<h4 className='text-sm font-semibold text-base-content'>
|
|
Ekspor Input Progress
|
|
</h4>
|
|
<Button
|
|
variant='ghost'
|
|
color='none'
|
|
onClick={() => {
|
|
exportProgressInputModal.closeModal();
|
|
resetExportProgressForm();
|
|
}}
|
|
className='p-1'
|
|
>
|
|
<Icon icon='mdi:close' width={20} height={20} />
|
|
</Button>
|
|
</div>
|
|
|
|
<div className='flex flex-col gap-4 p-4'>
|
|
<DateInput
|
|
name='export_progress_start_date'
|
|
label='Tanggal Mulai'
|
|
value={exportProgressStartDate}
|
|
onChange={exportProgressStartDateChangeHandler}
|
|
isNestedModal
|
|
required
|
|
/>
|
|
|
|
<DateInput
|
|
name='export_progress_end_date'
|
|
label='Tanggal Selesai'
|
|
value={exportProgressEndDate}
|
|
onChange={exportProgressEndDateChangeHandler}
|
|
isNestedModal
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div className='flex justify-end gap-3 border-t border-base-content/10 p-4'>
|
|
<Button
|
|
variant='outline'
|
|
color='none'
|
|
onClick={() => {
|
|
exportProgressInputModal.closeModal();
|
|
resetExportProgressForm();
|
|
}}
|
|
className='px-3 py-2.5'
|
|
>
|
|
Batal
|
|
</Button>
|
|
<Button
|
|
color='success'
|
|
onClick={submitExportProgressInputHandler}
|
|
isLoading={isExportProgressLoading}
|
|
disabled={!exportProgressStartDate || !exportProgressEndDate}
|
|
className='px-3 py-2.5'
|
|
>
|
|
Submit
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
|
|
<ExpensesFilterModal
|
|
ref={filterModal.ref}
|
|
onSubmit={handleFilterSubmit}
|
|
onReset={handleFilterReset}
|
|
initialValues={{
|
|
location:
|
|
tableFilterState.locationId && tableFilterState.locationName
|
|
? {
|
|
value: Number(tableFilterState.locationId),
|
|
label: tableFilterState.locationName,
|
|
}
|
|
: null,
|
|
vendor:
|
|
tableFilterState.vendorId && tableFilterState.vendorName
|
|
? {
|
|
value: Number(tableFilterState.vendorId),
|
|
label: tableFilterState.vendorName,
|
|
}
|
|
: null,
|
|
realization_date: tableFilterState.realizationDate,
|
|
transaction_date: tableFilterState.transactionDate,
|
|
category: tableFilterState.category
|
|
? {
|
|
value: tableFilterState.category,
|
|
label: tableFilterState.category,
|
|
}
|
|
: null,
|
|
approval_status: tableFilterState.approvalStatus
|
|
? approvalStatusOptions.find(
|
|
(item) => item.value === tableFilterState.approvalStatus
|
|
) || null
|
|
: null,
|
|
realization_status: tableFilterState.realizationStatus
|
|
? [
|
|
{ value: 'NOT_REALIZED', label: 'Belum Realisasi' },
|
|
{ value: 'REALIZED', label: 'Sudah Realisasi' },
|
|
{ value: 'REJECTED', label: 'Ditolak' },
|
|
].find(
|
|
(item) => item.value === tableFilterState.realizationStatus
|
|
) || null
|
|
: null,
|
|
project_flock:
|
|
tableFilterState.projectFlockId && tableFilterState.projectFlockName
|
|
? {
|
|
value: Number(tableFilterState.projectFlockId),
|
|
label: tableFilterState.projectFlockName,
|
|
}
|
|
: null,
|
|
project_flock_kandang:
|
|
tableFilterState.projectFlockKandangId &&
|
|
tableFilterState.projectFlockKandangName
|
|
? {
|
|
value: Number(tableFilterState.projectFlockKandangId),
|
|
label: tableFilterState.projectFlockKandangName,
|
|
}
|
|
: null,
|
|
}}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default ExpensesTable;
|