diff --git a/src/components/pages/expense/ExpenseRequestContent.tsx b/src/components/pages/expense/ExpenseRequestContent.tsx index a0438197..878b2ea7 100644 --- a/src/components/pages/expense/ExpenseRequestContent.tsx +++ b/src/components/pages/expense/ExpenseRequestContent.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; +import { useSWRConfig } from 'swr'; import { useFormik } from 'formik'; import toast from 'react-hot-toast'; @@ -19,6 +20,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import ExpensePDFPreviewButton from '@/components/pages/expense//pdf/ExpensePDFButton'; import RequirePermission from '@/components/helper/RequirePermission'; +import StatusBadge from '@/components/helper/StatusBadge'; import { Expense } from '@/types/api/expense'; import { formatCurrency, formatDate } from '@/lib/helper'; @@ -26,7 +28,7 @@ import { UploadRequestDocumentsFormSchema, UploadRequestDocumentsFormValues, } from '@/components/pages/expense/form/ExpenseRequestForm.schema'; -import { ACCEPTED_FILE_TYPE, S3_PUBLIC_BASE_URL } from '@/config/constant'; +import { ACCEPTED_FILE_TYPE } from '@/config/constant'; import { ExpenseApi } from '@/services/api/expense'; import { isResponseSuccess } from '@/lib/api-helper'; import { EXPENSE_REQUEST_APPROVAL_LINE } from '@/config/approval-line'; @@ -46,6 +48,11 @@ const ExpenseRequestContent = ({ const router = useRouter(); const searchParams = useSearchParams(); const returnTo = getExpenseListReturnTo(searchParams); + const { mutate } = useSWRConfig(); + + const refreshExpense = () => { + mutate((key) => Array.isArray(key) && key[0] === 'expense-detail'); + }; const { approvals: approvalHistory, isLoading: isLoadingApprovalHistory } = useApprovalSteps({ @@ -95,17 +102,24 @@ const ExpenseRequestContent = ({ !isLatestApprovalRejected && initialValues?.latest_approval.step_number === 4; + const isExpensePaidOff = initialValues?.is_paid; + + const showPaidOffButton = + !isExpensePaidOff && (initialValues?.latest_approval.step_number ?? 0) > 4; + // Modal hooks const deleteModal = useModal(); const completeModal = useModal(); const approveModal = useModal(); const rejectModal = useModal(); + const paidOffModal = useModal(); // Modal loading state const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isCompleteLoading, setIsCompleteLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false); + const [isPaidOffLoading, setIsPaidOffLoading] = useState(false); const [, setApprovalNotes] = useState(''); const formik = useFormik({ @@ -146,7 +160,31 @@ const ExpenseRequestContent = ({ rejectModal.openModal(); }; + const paidOffClickHandler = () => { + paidOffModal.openModal(); + }; + // Modal confirm click handler + const confirmationModalPaidOffClickHandler = async () => { + setIsPaidOffLoading(true); + + const paidOffResponse = await ExpenseApi.setExpensePaidOff( + initialValues?.id as number + ); + + if (isResponseSuccess(paidOffResponse)) { + toast.success('Berhasil menandai biaya operasional sebagai lunas!'); + refreshExpense(); + } else { + toast.error( + 'Gagal menandai biaya operasional sebagai lunas!: ' + + paidOffResponse?.message + ); + } + + paidOffModal.closeModal(); + setIsPaidOffLoading(false); + }; const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); @@ -388,6 +426,24 @@ const ExpenseRequestContent = ({ )} + {showPaidOffButton && ( + + + + )} +
{showEditButton && ( @@ -533,6 +589,19 @@ const ExpenseRequestContent = ({ /> + + Status Lunas + : + + + + Dokumen Pengajuan : @@ -752,6 +821,21 @@ const ExpenseRequestContent = ({ onClick: confirmationModalRejectClickHandler, }} /> + + ); }; diff --git a/src/components/pages/expense/ExpensesTable.tsx b/src/components/pages/expense/ExpensesTable.tsx index 9a133447..b2dcf4ea 100644 --- a/src/components/pages/expense/ExpensesTable.tsx +++ b/src/components/pages/expense/ExpensesTable.tsx @@ -36,6 +36,7 @@ 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'; @@ -87,10 +88,12 @@ const RowOptionsMenu = ({ popoverPosition = 'bottom', props, deleteClickHandler, + paidOffClickHandler, }: { popoverPosition: 'bottom' | 'top'; props: CellContext; deleteClickHandler: () => void; + paidOffClickHandler: () => void; }) => { const popoverId = `expense#${props.row.original.id}`; const popoverAnchorName = `--anchor-expense#${props.row.original.id}`; @@ -112,6 +115,11 @@ const RowOptionsMenu = ({ 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 (
)} + {showPaidOffButton && ( + + + + )} +