diff --git a/src/components/pages/purchase/PurchaseTable.tsx b/src/components/pages/purchase/PurchaseTable.tsx index d074a583..67555522 100644 --- a/src/components/pages/purchase/PurchaseTable.tsx +++ b/src/components/pages/purchase/PurchaseTable.tsx @@ -1,5 +1,6 @@ 'use client'; +import axios from 'axios'; import { ChangeEventHandler, useCallback, @@ -18,8 +19,9 @@ import Link from 'next/link'; import { Icon } from '@iconify/react'; import Table from '@/components/Table'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; +import DateInput from '@/components/input/DateInput'; 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'; @@ -28,6 +30,7 @@ import StatusBadge from '@/components/helper/StatusBadge'; import PurchaseTableSkeleton from '@/components/pages/purchase/skeleton/PurchaseTableSkeleton'; import ButtonFilter from '@/components/helper/ButtonFilter'; import PurchaseFilterModal from '@/components/pages/purchase/PurchaseFilterModal'; +import Dropdown from '@/components/dropdown/Dropdown'; import { cn, formatDate } from '@/lib/helper'; import { isResponseSuccess } from '@/lib/api-helper'; @@ -40,6 +43,43 @@ import { ExpenseApi } from '@/services/api/expense'; import { Expense } from '@/types/api/expense'; import { Color } from '@/types/theme'; +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; +}; + // ===== STATUS BADGE UTILITIES ===== const statusTextMap: Record = { APPROVED: 'Disetujui', @@ -152,9 +192,12 @@ const PurchaseTable = () => { // ===== STATE MANAGEMENT ===== const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const [isExportProgressLoading, setIsExportProgressLoading] = useState(false); const [selectedPurchase, setSelectedPurchase] = useState( null ); + const [exportProgressStartDate, setExportProgressStartDate] = useState(''); + const [exportProgressEndDate, setExportProgressEndDate] = useState(''); const [sorting, setSorting] = useState([]); // ===== TABLE FILTER STATE ===== @@ -183,6 +226,7 @@ const PurchaseTable = () => { // ===== MODAL HOOKS ===== const filterModal = useModal(); const deleteModal = useModal(); + const exportProgressInputModal = useModal(); // ===== API DATA FETCHING ===== const { @@ -431,6 +475,56 @@ const PurchaseTable = () => { updateFilter('approval_status', ''); }; + const resetExportProgressForm = useCallback(() => { + setExportProgressStartDate(''); + setExportProgressEndDate(''); + }, []); + + const exportProgressStartDateChangeHandler: ChangeEventHandler = + useCallback((e) => { + setExportProgressStartDate(e.target.value); + }, []); + + const exportProgressEndDateChangeHandler: ChangeEventHandler = + useCallback((e) => { + setExportProgressEndDate(e.target.value); + }, []); + + const exportProgressInputToExcelClickHandler = useCallback(() => { + resetExportProgressForm(); + exportProgressInputModal.openModal(); + }, [exportProgressInputModal, resetExportProgressForm]); + + const submitExportProgressInputHandler = useCallback(async () => { + if (!exportProgressStartDate || !exportProgressEndDate) { + return; + } + + setIsExportProgressLoading(true); + + try { + await PurchaseApi.exportInputProgressToExcel( + exportProgressStartDate, + exportProgressEndDate + ); + + exportProgressInputModal.closeModal(); + resetExportProgressForm(); + toast.success('Ekspor berhasil'); + } catch (error) { + toast.error( + await getExportErrorMessage(error, 'Gagal mengekspor input progress') + ); + } finally { + setIsExportProgressLoading(false); + } + }, [ + exportProgressEndDate, + exportProgressInputModal, + exportProgressStartDate, + resetExportProgressForm, + ]); + return ( <>
@@ -482,6 +576,50 @@ const PurchaseTable = () => { onClick={filterModal.openModal} className='px-3 py-2.5' /> + + +
+ + + Export + +
+ + +
+ + } + > + +
@@ -562,6 +700,76 @@ const PurchaseTable = () => { onClick: confirmationModalDeleteClickHandler, }} /> + + +
+
+

+ Ekspor Input Progress +

+ +
+ +
+ + + +
+ +
+ + +
+
+
); }; diff --git a/src/services/api/purchase.ts b/src/services/api/purchase.ts index 38ace6be..db9f01ed 100644 --- a/src/services/api/purchase.ts +++ b/src/services/api/purchase.ts @@ -10,6 +10,8 @@ import { } from '@/types/api/purchase/purchase'; import { BaseApiService } from '@/services/api/base'; import { BaseApiResponse } from '@/types/api/api-general'; +import { formatDate } from '@/lib/helper'; +import { httpClient } from '../http/client'; const basePurchaseApi = new BaseApiService< Purchase, @@ -112,4 +114,34 @@ export const PurchaseApi = { }); }, }, + + async exportInputProgressToExcel(startDate: string, endDate: string) { + const params = new URLSearchParams(); + + params.set('export', 'excel'); + params.set('type', 'progress'); + params.set('start_date', formatDate(startDate, 'YYYY-MM-DD')); + params.set('end_date', formatDate(endDate, 'YYYY-MM-DD')); + + const queryString = `?${params.toString()}`; + + const res = await httpClient( + `${basePurchaseApi.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-pembelian-${formatDate(startDate, 'DD-MM-YYYY')}-ke-${formatDate(endDate, 'DD-MM-YYYY')}.xlsx`; + link.setAttribute('download', fileName); + + document.body.appendChild(link); + link.click(); + link.remove(); + }, };