mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'feat/expense-export' into 'development'
[FEAT/FE] Expense Export See merge request mbugroup/lti-web-client!426
This commit is contained in:
@@ -41,7 +41,7 @@ import Dropdown from '@/components/dropdown/Dropdown';
|
|||||||
import { Expense } from '@/types/api/expense';
|
import { Expense } from '@/types/api/expense';
|
||||||
import { ExpenseApi } from '@/services/api/expense';
|
import { ExpenseApi } from '@/services/api/expense';
|
||||||
import { cn, formatCurrency, formatDate } from '@/lib/helper';
|
import { cn, formatCurrency, formatDate } from '@/lib/helper';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { getErrorMessage, isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
|
|
||||||
@@ -84,43 +84,6 @@ type ApprovalStatusValue =
|
|||||||
const isApprovalDateRequired = (status?: ApprovalStatusValue) =>
|
const isApprovalDateRequired = (status?: ApprovalStatusValue) =>
|
||||||
status === 'REALISASI' || status === 'SELESAI';
|
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 = ({
|
const RowOptionsMenu = ({
|
||||||
popoverPosition = 'bottom',
|
popoverPosition = 'bottom',
|
||||||
props,
|
props,
|
||||||
@@ -314,6 +277,8 @@ const ExpensesTable = () => {
|
|||||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
||||||
|
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
|
||||||
|
useState(false);
|
||||||
const [isExportProgressLoading, setIsExportProgressLoading] = useState(false);
|
const [isExportProgressLoading, setIsExportProgressLoading] = useState(false);
|
||||||
const [, setApprovalNotes] = useState('');
|
const [, setApprovalNotes] = useState('');
|
||||||
const [bulkApprovalStatus, setBulkApprovalStatus] =
|
const [bulkApprovalStatus, setBulkApprovalStatus] =
|
||||||
@@ -603,7 +568,7 @@ const ExpensesTable = () => {
|
|||||||
toast.success('Ekspor berhasil');
|
toast.success('Ekspor berhasil');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.error(
|
||||||
await getExportErrorMessage(error, 'Gagal mengekspor input progress')
|
await getErrorMessage(error, 'Gagal mengekspor input progress')
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsExportProgressLoading(false);
|
setIsExportProgressLoading(false);
|
||||||
@@ -818,6 +783,20 @@ const ExpensesTable = () => {
|
|||||||
resetFilter();
|
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]);
|
||||||
|
|
||||||
// track sorting
|
// track sorting
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name');
|
const isNameSorted = sorting.find((sortItem) => sortItem.id === 'name');
|
||||||
@@ -1031,6 +1010,17 @@ const ExpensesTable = () => {
|
|||||||
</Button>
|
</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
|
<Button
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='none'
|
color='none'
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import Modal, { useModal } from '@/components/Modal';
|
|||||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||||
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import {
|
||||||
|
getErrorMessage,
|
||||||
|
isResponseError,
|
||||||
|
isResponseSuccess,
|
||||||
|
} from '@/lib/api-helper';
|
||||||
import { cn, formatCurrency, formatDate, formatTitleCase } from '@/lib/helper';
|
import { cn, formatCurrency, formatDate, formatTitleCase } from '@/lib/helper';
|
||||||
import {
|
import {
|
||||||
MarketingApi,
|
MarketingApi,
|
||||||
@@ -37,43 +41,6 @@ import MarketingFilterModal from '@/components/pages/marketing/MarketingFilter';
|
|||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
import MarketingTableSkeleton from '@/components/pages/marketing/skeleton/MarketingTableSkeleton';
|
import MarketingTableSkeleton from '@/components/pages/marketing/skeleton/MarketingTableSkeleton';
|
||||||
|
|
||||||
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 RowsOptionsMenu = ({
|
const RowsOptionsMenu = ({
|
||||||
props,
|
props,
|
||||||
deleteClickHandler,
|
deleteClickHandler,
|
||||||
@@ -537,7 +504,7 @@ const MarketingTable = () => {
|
|||||||
toast.success('Ekspor berhasil');
|
toast.success('Ekspor berhasil');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.error(
|
||||||
await getExportErrorMessage(error, 'Gagal mengekspor input progress')
|
await getErrorMessage(error, 'Gagal mengekspor input progress')
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsExportProgressLoading(false);
|
setIsExportProgressLoading(false);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ import Table from '@/components/Table';
|
|||||||
import { type Recording } from '@/types/api/production/recording';
|
import { type Recording } from '@/types/api/production/recording';
|
||||||
import { getRecordingRestriction } from './recording-utils';
|
import { getRecordingRestriction } from './recording-utils';
|
||||||
import { RecordingApi } from '@/services/api/production';
|
import { RecordingApi } from '@/services/api/production';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { getErrorMessage, isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import StatusBadge from '@/components/helper/StatusBadge';
|
import StatusBadge from '@/components/helper/StatusBadge';
|
||||||
@@ -52,43 +52,6 @@ import { Color } from '@/types/theme';
|
|||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
import Dropdown from '@/components/Dropdown';
|
import Dropdown from '@/components/Dropdown';
|
||||||
|
|
||||||
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 =====
|
// ===== STATUS BADGE UTILITIES =====
|
||||||
const statusTextMap: Record<string, string> = {
|
const statusTextMap: Record<string, string> = {
|
||||||
APPROVED: 'Disetujui',
|
APPROVED: 'Disetujui',
|
||||||
@@ -839,7 +802,7 @@ const RecordingTable = () => {
|
|||||||
toast.success('Ekspor berhasil');
|
toast.success('Ekspor berhasil');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.error(
|
||||||
await getExportErrorMessage(error, 'Gagal mengekspor input progress')
|
await getErrorMessage(error, 'Gagal mengekspor input progress')
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsExportProgressLoading(false);
|
setIsExportProgressLoading(false);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import PurchaseFilterModal from '@/components/pages/purchase/PurchaseFilterModal
|
|||||||
import Dropdown from '@/components/dropdown/Dropdown';
|
import Dropdown from '@/components/dropdown/Dropdown';
|
||||||
|
|
||||||
import { cn, formatDate } from '@/lib/helper';
|
import { cn, formatDate } from '@/lib/helper';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { getErrorMessage, isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
|
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
@@ -43,43 +43,6 @@ import { ExpenseApi } from '@/services/api/expense';
|
|||||||
import { Expense } from '@/types/api/expense';
|
import { Expense } from '@/types/api/expense';
|
||||||
import { Color } from '@/types/theme';
|
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 =====
|
// ===== STATUS BADGE UTILITIES =====
|
||||||
const statusTextMap: Record<string, string> = {
|
const statusTextMap: Record<string, string> = {
|
||||||
APPROVED: 'Disetujui',
|
APPROVED: 'Disetujui',
|
||||||
@@ -518,7 +481,7 @@ const PurchaseTable = () => {
|
|||||||
await PurchaseApi.exportToExcel(getTableFilterQueryString());
|
await PurchaseApi.exportToExcel(getTableFilterQueryString());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.error(
|
||||||
await getExportErrorMessage(error, 'Gagal mengekspor data pembelian')
|
await getErrorMessage(error, 'Gagal mengekspor data pembelian')
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoadingExportingToExcel(false);
|
setIsLoadingExportingToExcel(false);
|
||||||
@@ -563,7 +526,7 @@ const PurchaseTable = () => {
|
|||||||
toast.success('Ekspor berhasil');
|
toast.success('Ekspor berhasil');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.error(
|
||||||
await getExportErrorMessage(error, 'Gagal mengekspor input progress')
|
await getErrorMessage(error, 'Gagal mengekspor input progress')
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsExportProgressLoading(false);
|
setIsExportProgressLoading(false);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import Table from '@/components/Table';
|
|||||||
import { formatCurrency, formatDate } from '@/lib/helper';
|
import { formatCurrency, formatDate } from '@/lib/helper';
|
||||||
import { ReportExpense } from '@/types/api/report/report-expense';
|
import { ReportExpense } from '@/types/api/report/report-expense';
|
||||||
import { ReportExpenseApi } from '@/services/api/report/expense-report';
|
import { ReportExpenseApi } from '@/services/api/report/expense-report';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { getErrorMessage, isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
|
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
|
||||||
import Modal, { useModal } from '@/components/Modal';
|
import Modal, { useModal } from '@/components/Modal';
|
||||||
import Pagination from '@/components/Pagination';
|
import Pagination from '@/components/Pagination';
|
||||||
@@ -189,26 +189,47 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
[formik.values.category]
|
[formik.values.category]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const buildReportExpenseQueryString = useCallback(
|
||||||
|
(extraParams?: Record<string, string>) => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (filterParams.location_id) {
|
||||||
|
params.append('location_id', filterParams.location_id);
|
||||||
|
}
|
||||||
|
if (filterParams.supplier_id) {
|
||||||
|
params.append('supplier_id', filterParams.supplier_id);
|
||||||
|
}
|
||||||
|
if (filterParams.kandang_id) {
|
||||||
|
params.append('project_flock_kandang_id', filterParams.kandang_id);
|
||||||
|
}
|
||||||
|
if (filterParams.nonstock_id) {
|
||||||
|
params.append('nonstock_id', filterParams.nonstock_id);
|
||||||
|
}
|
||||||
|
if (filterParams.realization_date) {
|
||||||
|
params.append('realization_date', filterParams.realization_date);
|
||||||
|
}
|
||||||
|
if (filterParams.category) {
|
||||||
|
params.append('category', filterParams.category);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(extraParams ?? {}).forEach(([key, value]) => {
|
||||||
|
params.set(key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return params.toString();
|
||||||
|
},
|
||||||
|
[filterParams]
|
||||||
|
);
|
||||||
|
|
||||||
// ===== DATA FETCHING =====
|
// ===== DATA FETCHING =====
|
||||||
const { data: reportExpenseResponse, isLoading } = useSWR(
|
const { data: reportExpenseResponse, isLoading } = useSWR(
|
||||||
() => {
|
() => {
|
||||||
const params = new URLSearchParams();
|
const queryString = buildReportExpenseQueryString({
|
||||||
if (filterParams.location_id)
|
page: String(page),
|
||||||
params.append('location_id', filterParams.location_id);
|
limit: String(pageSize),
|
||||||
if (filterParams.supplier_id)
|
});
|
||||||
params.append('supplier_id', filterParams.supplier_id);
|
|
||||||
if (filterParams.kandang_id)
|
|
||||||
params.append('project_flock_kandang_id', filterParams.kandang_id);
|
|
||||||
if (filterParams.nonstock_id)
|
|
||||||
params.append('nonstock_id', filterParams.nonstock_id);
|
|
||||||
if (filterParams.realization_date)
|
|
||||||
params.append('realization_date', filterParams.realization_date);
|
|
||||||
if (filterParams.category)
|
|
||||||
params.append('category', filterParams.category);
|
|
||||||
params.append('page', String(page));
|
|
||||||
params.append('limit', String(pageSize));
|
|
||||||
|
|
||||||
return [`${ReportExpenseApi.basePath}?${params.toString()}`];
|
return [`${ReportExpenseApi.basePath}?${queryString}`];
|
||||||
},
|
},
|
||||||
([url]: string[]) => httpClient<BaseApiResponse<ReportExpense[]>>(url)
|
([url]: string[]) => httpClient<BaseApiResponse<ReportExpense[]>>(url)
|
||||||
);
|
);
|
||||||
@@ -233,47 +254,31 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => {
|
|||||||
const reportExpenseExport = useCallback(async (): Promise<
|
const reportExpenseExport = useCallback(async (): Promise<
|
||||||
ReportExpense[] | null
|
ReportExpense[] | null
|
||||||
> => {
|
> => {
|
||||||
const params = new URLSearchParams();
|
const queryString = buildReportExpenseQueryString({
|
||||||
if (filterParams.location_id)
|
page: '1',
|
||||||
params.append('location_id', filterParams.location_id);
|
limit: '100',
|
||||||
if (filterParams.supplier_id)
|
});
|
||||||
params.append('supplier_id', filterParams.supplier_id);
|
|
||||||
if (filterParams.kandang_id)
|
|
||||||
params.append('kandang_id', filterParams.kandang_id);
|
|
||||||
if (filterParams.nonstock_id)
|
|
||||||
params.append('nonstock_id', filterParams.nonstock_id);
|
|
||||||
if (filterParams.realization_date)
|
|
||||||
params.append('realization_date', filterParams.realization_date);
|
|
||||||
if (filterParams.category) params.append('category', filterParams.category);
|
|
||||||
params.append('limit', '100');
|
|
||||||
params.append('page', '1');
|
|
||||||
|
|
||||||
const response = await httpClient<BaseApiResponse<ReportExpense[]>>(
|
const response = await httpClient<BaseApiResponse<ReportExpense[]>>(
|
||||||
`${ReportExpenseApi.basePath}?${params.toString()}`
|
`${ReportExpenseApi.basePath}?${queryString}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return isResponseSuccess(response) ? response.data : null;
|
return isResponseSuccess(response) ? response.data : null;
|
||||||
}, [filterParams]);
|
}, [buildReportExpenseQueryString]);
|
||||||
|
|
||||||
// ===== EXPORT HANDLERS =====
|
// ===== EXPORT HANDLERS =====
|
||||||
const handleExportExcel = useCallback(async () => {
|
const handleExportExcel = useCallback(async () => {
|
||||||
setIsExcelExportLoading(true);
|
setIsExcelExportLoading(true);
|
||||||
try {
|
try {
|
||||||
const allDataForExport = await reportExpenseExport();
|
await ReportExpenseApi.exportToExcel(buildReportExpenseQueryString());
|
||||||
|
} catch (error) {
|
||||||
if (!allDataForExport || allDataForExport.length === 0) {
|
toast.error(
|
||||||
toast.error('Tidak ada data untuk diekspor.');
|
await getErrorMessage(error, 'Gagal mengekspor data pengeluaran')
|
||||||
return;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
await generateReportExpenseExcel(allDataForExport);
|
|
||||||
toast.success('Excel berhasil dibuat dan diunduh.');
|
|
||||||
} catch {
|
|
||||||
toast.error('Gagal membuat Excel. Silakan coba lagi.');
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsExcelExportLoading(false);
|
setIsExcelExportLoading(false);
|
||||||
}
|
}
|
||||||
}, [reportExpenseExport]);
|
}, [buildReportExpenseQueryString]);
|
||||||
|
|
||||||
const handleExportPDF = useCallback(async () => {
|
const handleExportPDF = useCallback(async () => {
|
||||||
setIsPdfExportLoading(true);
|
setIsPdfExportLoading(true);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import axios from 'axios';
|
||||||
import {
|
import {
|
||||||
BaseApiResponse,
|
BaseApiResponse,
|
||||||
ErrorApiResponse,
|
ErrorApiResponse,
|
||||||
@@ -15,3 +16,40 @@ export const isResponseError = <T>(
|
|||||||
): res is ErrorApiResponse => {
|
): res is ErrorApiResponse => {
|
||||||
return res?.status === 'error';
|
return res?.status === 'error';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getErrorMessage = 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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -708,6 +708,33 @@ export class ExpenseApiService extends BaseApiService<
|
|||||||
return formData;
|
return formData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async exportToExcel(initialQueryString: string) {
|
||||||
|
const params = new URLSearchParams(initialQueryString);
|
||||||
|
|
||||||
|
params.set('export', 'excel');
|
||||||
|
params.set('type', 'all');
|
||||||
|
params.set('page', '1');
|
||||||
|
params.set('limit', '99999999999');
|
||||||
|
|
||||||
|
const queryString = `?${params.toString()}`;
|
||||||
|
|
||||||
|
const res = await httpClient<Blob>(`${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 = `BOP-${formatDate(Date.now(), 'DD-MM-YYYY')}.xlsx`;
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
|
|
||||||
async exportInputProgressToExcel(startDate: string, endDate: string) {
|
async exportInputProgressToExcel(startDate: string, endDate: string) {
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import { formatDate } from '@/lib/helper';
|
||||||
import { BaseApiService } from '@/services/api/base';
|
import { BaseApiService } from '@/services/api/base';
|
||||||
import { httpClientFetcher } from '@/services/http/client';
|
import { httpClient, httpClientFetcher } from '@/services/http/client';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
import {
|
import {
|
||||||
ReportDepreciation,
|
ReportDepreciation,
|
||||||
@@ -20,6 +21,33 @@ export class ReportExpenseApiService extends BaseApiService<
|
|||||||
): Promise<BaseApiResponse<ReportExpense[]>> {
|
): Promise<BaseApiResponse<ReportExpense[]>> {
|
||||||
return await httpClientFetcher<BaseApiResponse<ReportExpense[]>>(endpoint);
|
return await httpClientFetcher<BaseApiResponse<ReportExpense[]>>(endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async exportToExcel(initialQueryString: string) {
|
||||||
|
const params = new URLSearchParams(initialQueryString);
|
||||||
|
|
||||||
|
params.set('export', 'excel');
|
||||||
|
params.set('type', 'all');
|
||||||
|
params.set('page', '1');
|
||||||
|
params.set('limit', '99999999999');
|
||||||
|
|
||||||
|
const queryString = `?${params.toString()}`;
|
||||||
|
|
||||||
|
const res = await httpClient<Blob>(`${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 = `Laporan-BOP-${formatDate(Date.now(), 'DD-MM-YYYY')}.xlsx`;
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReportExpenseApi = new ReportExpenseApiService('/reports/expense');
|
export const ReportExpenseApi = new ReportExpenseApiService('/reports/expense');
|
||||||
|
|||||||
Reference in New Issue
Block a user