diff --git a/src/components/pages/expense/ExpensesTable.tsx b/src/components/pages/expense/ExpensesTable.tsx index f7f68805..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, @@ -35,6 +36,7 @@ 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'; @@ -73,6 +75,43 @@ type ApprovalStatusValue = 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, @@ -236,6 +275,7 @@ const ExpensesTable = () => { const approveModal = useModal(); const rejectModal = useModal(); const bulkApproveFormModal = useModal(); + const exportProgressInputModal = useModal(); // ===== FILTER MODAL STATE ===== const filterModal = useModal(); @@ -246,11 +286,14 @@ const ExpensesTable = () => { const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false); const [isRejectLoading, setIsRejectLoading] = useState(false); + const [isExportProgressLoading, setIsExportProgressLoading] = useState(false); const [, setApprovalNotes] = useState(''); const [bulkApprovalStatus, setBulkApprovalStatus] = useState | null>(null); const [bulkApprovalDate, setBulkApprovalDate] = useState(''); const [bulkApprovalNotes, setBulkApprovalNotes] = useState(''); + const [exportProgressStartDate, setExportProgressStartDate] = useState(''); + const [exportProgressEndDate, setExportProgressEndDate] = useState(''); const [sorting, setSorting] = useState([]); const [rowSelection, setRowSelection] = useState>({}); @@ -492,6 +535,53 @@ const ExpensesTable = () => { 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 getExportErrorMessage(error, 'Gagal mengekspor input progress') + ); + } finally { + setIsExportProgressLoading(false); + } + }; + const confirmationModalDeleteClickHandler = async () => { setIsDeleteLoading(true); @@ -841,6 +931,50 @@ const ExpensesTable = () => { onClick={handleFilterModalOpen} className='px-3 py-2.5' /> + + +
+ + + Export + +
+ + +
+ + } + > + +
@@ -1037,6 +1171,76 @@ const ExpensesTable = () => { + +
+
+

+ Ekspor Input Progress +

+ +
+ +
+ + + +
+ +
+ + +
+
+
+ (`${this.basePath}${queryString}`, { + method: 'GET', + responseType: 'blob', + }); + + const url = window.URL.createObjectURL(new Blob([res])); + const link = document.createElement('a'); + link.href = url; + + const fileName = `input-progres-BOP-${formatDate(startDate, 'DD-MM-YYYY')}-ke-${formatDate(endDate, 'DD-MM-YYYY')}.xlsx`; + link.setAttribute('download', fileName); + + document.body.appendChild(link); + link.click(); + link.remove(); + } } export const ExpenseApi = new ExpenseApiService('/expenses');