mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
refactor: optimize DebtSupplierTab with useTableFilter persistence pattern
Replace filterParams/currentPage/pageSize state with useTableFilter (persist:true), switch SWR to httpClientFetcher with explicit type, store OptionType[] directly for suppliers/filterBy, add formikResetHandler using resetFilter(), remove TabActions component anti-pattern and handleFilterModalOpenRef, pass filterModal.openModal directly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,23 +9,15 @@ import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
|
|||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||||
import { SupplierApi } from '@/services/api/master-data';
|
import { SupplierApi } from '@/services/api/master-data';
|
||||||
import {
|
import { DebtRow, DebtSupplier } from '@/types/api/report/debt-supplier';
|
||||||
DebtRow,
|
|
||||||
DebtSupplier,
|
|
||||||
DebtSupplierFilter,
|
|
||||||
} from '@/types/api/report/debt-supplier';
|
|
||||||
import { generateDebtSupplierPDF } from '@/components/pages/report/finance/export/DebtSupllierExportPDF';
|
import { generateDebtSupplierPDF } from '@/components/pages/report/finance/export/DebtSupllierExportPDF';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { DebtSupplierApi } from '@/services/api/report/debt-supplier';
|
import { DebtSupplierApi } from '@/services/api/report/debt-supplier';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import {
|
|
||||||
DebtSupplierFilterSchema,
|
|
||||||
DebtSupplierFilterType,
|
|
||||||
} from '@/components/pages/report/finance/filter/DebtSupplierFilter';
|
|
||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
import { Color } from '@/types/theme';
|
import { Color } from '@/types/theme';
|
||||||
import { Supplier } from '@/types/api/master-data/supplier';
|
import { Supplier } from '@/types/api/master-data/supplier';
|
||||||
@@ -34,6 +26,10 @@ import SelectInputRadio from '@/components/input/SelectInputRadio';
|
|||||||
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
|
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
|
||||||
import StatusBadge from '@/components/helper/StatusBadge';
|
import StatusBadge from '@/components/helper/StatusBadge';
|
||||||
import DebtSupplierSkeleton from '@/components/pages/report/finance/skeleton/DebtSupplierSkeleton';
|
import DebtSupplierSkeleton from '@/components/pages/report/finance/skeleton/DebtSupplierSkeleton';
|
||||||
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
|
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
|
||||||
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
|
||||||
const dueStatus: Record<string, Color> = {
|
const dueStatus: Record<string, Color> = {
|
||||||
'Sudah Jatuh Tempo': 'error',
|
'Sudah Jatuh Tempo': 'error',
|
||||||
@@ -51,7 +47,6 @@ const getPillBadge = (
|
|||||||
statusText: string,
|
statusText: string,
|
||||||
type: 'due' | 'payment' = 'payment'
|
type: 'due' | 'payment' = 'payment'
|
||||||
) => {
|
) => {
|
||||||
// Get color based on type
|
|
||||||
const color =
|
const color =
|
||||||
type === 'due'
|
type === 'due'
|
||||||
? dueStatus[statusText] || 'neutral'
|
? dueStatus[statusText] || 'neutral'
|
||||||
@@ -68,6 +63,11 @@ const getPillBadge = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dataTypeOptions: OptionType<string>[] = [
|
||||||
|
{ value: 'received_date', label: 'Tanggal Terima' },
|
||||||
|
{ value: 'po_date', label: 'Tanggal PO' },
|
||||||
|
];
|
||||||
|
|
||||||
interface DebtSupplierTabProps {
|
interface DebtSupplierTabProps {
|
||||||
tabId: string;
|
tabId: string;
|
||||||
}
|
}
|
||||||
@@ -81,26 +81,45 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
const isAnyExportLoading =
|
const isAnyExportLoading =
|
||||||
isPdfExportLoading || isExcelExportLoading || isExcelGeneralExportLoading;
|
isPdfExportLoading || isExcelExportLoading || isExcelGeneralExportLoading;
|
||||||
|
|
||||||
// ===== PAGINATION STATE =====
|
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
|
||||||
const [pageSize, setPageSize] = useState(10);
|
|
||||||
|
|
||||||
// ===== SUBMISSION STATE =====
|
|
||||||
const [filterParams, setFilterParams] = useState<DebtSupplierFilter>({
|
|
||||||
start_date: undefined,
|
|
||||||
end_date: undefined,
|
|
||||||
supplier_ids: undefined,
|
|
||||||
filter_by: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
// ===== DATE ERROR STATE =====
|
|
||||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||||
const [hasDateError, setHasDateError] = useState(false);
|
const [hasDateError, setHasDateError] = useState(false);
|
||||||
|
|
||||||
const handleFilterModalOpenRef = useRef(() => {});
|
|
||||||
|
|
||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
|
|
||||||
|
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||||
|
const clearTabActions = useTabActionsStore((state) => state.clearTabActions);
|
||||||
|
|
||||||
|
const {
|
||||||
|
state: tableFilterState,
|
||||||
|
updateFilter,
|
||||||
|
setPage,
|
||||||
|
setPageSize,
|
||||||
|
toQueryString: getTableFilterQueryString,
|
||||||
|
reset: resetFilter,
|
||||||
|
} = useTableFilter<{
|
||||||
|
start_date: string;
|
||||||
|
end_date: string;
|
||||||
|
suppliers: OptionType<number>[];
|
||||||
|
filterBy?: OptionType<string>;
|
||||||
|
}>({
|
||||||
|
initial: {
|
||||||
|
start_date: '',
|
||||||
|
end_date: '',
|
||||||
|
suppliers: [],
|
||||||
|
filterBy: undefined,
|
||||||
|
},
|
||||||
|
paramMap: {
|
||||||
|
page: 'page',
|
||||||
|
pageSize: 'limit',
|
||||||
|
start_date: 'start_date',
|
||||||
|
end_date: 'end_date',
|
||||||
|
suppliers: 'supplier_ids',
|
||||||
|
filterBy: 'filter_by',
|
||||||
|
},
|
||||||
|
persist: true,
|
||||||
|
storeName: 'debt-supplier-report-table',
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
setInputValue: setSupplierInputValue,
|
setInputValue: setSupplierInputValue,
|
||||||
options: supplierOptions,
|
options: supplierOptions,
|
||||||
@@ -108,154 +127,149 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
loadMore: loadMoreSuppliers,
|
loadMore: loadMoreSuppliers,
|
||||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
||||||
|
|
||||||
const dataTypeOptions = useMemo(
|
|
||||||
() => [
|
|
||||||
{ value: 'received_date', label: 'Tanggal Terima' },
|
|
||||||
{ value: 'po_date', label: 'Tanggal PO' },
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
// ===== FORMIK SETUP =====
|
// ===== FORMIK SETUP =====
|
||||||
const formik = useFormik<DebtSupplierFilterType>({
|
const formik = useFormik({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
startDate: null,
|
start_date: tableFilterState.start_date,
|
||||||
endDate: null,
|
end_date: tableFilterState.end_date,
|
||||||
supplierIds: null,
|
suppliers: tableFilterState.suppliers,
|
||||||
filterBy: null,
|
filterBy: tableFilterState.filterBy,
|
||||||
},
|
},
|
||||||
validationSchema: DebtSupplierFilterSchema,
|
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
setFilterParams({
|
updateFilter('start_date', values.start_date, true);
|
||||||
start_date: values.startDate?.toString() || undefined,
|
updateFilter('end_date', values.end_date, true);
|
||||||
end_date: values.endDate?.toString() || undefined,
|
updateFilter('suppliers', values.suppliers, true);
|
||||||
supplier_ids:
|
updateFilter('filterBy', values.filterBy, true);
|
||||||
values.supplierIds?.map((v) => String(v.value)).join(',') ||
|
|
||||||
undefined,
|
|
||||||
filter_by: values.filterBy?.value?.toString() || undefined,
|
|
||||||
});
|
|
||||||
filterModal.closeModal();
|
|
||||||
setCurrentPage(1);
|
|
||||||
},
|
|
||||||
onReset: () => {
|
|
||||||
setFilterParams({
|
|
||||||
start_date: undefined,
|
|
||||||
end_date: undefined,
|
|
||||||
supplier_ids: undefined,
|
|
||||||
filter_by: undefined,
|
|
||||||
});
|
|
||||||
setCurrentPage(1);
|
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
handleFilterModalOpenRef.current = () => {
|
const formikResetHandler = () => {
|
||||||
const restoredFilterBy =
|
resetFilter();
|
||||||
dataTypeOptions.find((opt) => opt.value === filterParams.filter_by) ||
|
|
||||||
null;
|
|
||||||
|
|
||||||
const supplierIdList = filterParams.supplier_ids
|
setHasDateError(false);
|
||||||
? filterParams.supplier_ids.split(',')
|
if (dateErrorShown) {
|
||||||
: [];
|
toast.dismiss();
|
||||||
const restoredSupplierIds = supplierOptions.filter((opt) =>
|
setDateErrorShown(false);
|
||||||
supplierIdList.includes(String(opt.value))
|
}
|
||||||
);
|
|
||||||
|
|
||||||
formik.setValues({
|
formik.resetForm({
|
||||||
startDate: filterParams.start_date || null,
|
values: {
|
||||||
endDate: filterParams.end_date || null,
|
start_date: '',
|
||||||
supplierIds: restoredSupplierIds.length > 0 ? restoredSupplierIds : null,
|
end_date: '',
|
||||||
filterBy: restoredFilterBy,
|
suppliers: [],
|
||||||
|
filterBy: undefined,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
filterModal.openModal();
|
|
||||||
|
filterModal.closeModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== DATE CHANGE HANDLERS =====
|
||||||
|
const handleStartDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
formik.setFieldValue('start_date', value);
|
||||||
|
|
||||||
|
if (value && formik.values.end_date) {
|
||||||
|
if (new Date(formik.values.end_date) < new Date(value)) {
|
||||||
|
setHasDateError(true);
|
||||||
|
if (!dateErrorShown) {
|
||||||
|
toast.error('Tanggal akhir tidak boleh masa lampau', {
|
||||||
|
duration: Infinity,
|
||||||
|
});
|
||||||
|
setDateErrorShown(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setHasDateError(false);
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDateErrorShown(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setHasDateError(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEndDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
formik.setFieldValue('end_date', value);
|
||||||
|
|
||||||
|
if (value && formik.values.start_date) {
|
||||||
|
if (new Date(value) < new Date(formik.values.start_date)) {
|
||||||
|
setHasDateError(true);
|
||||||
|
if (!dateErrorShown) {
|
||||||
|
toast.error('Tanggal akhir tidak boleh masa lampau', {
|
||||||
|
duration: Infinity,
|
||||||
|
});
|
||||||
|
setDateErrorShown(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setHasDateError(false);
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDateErrorShown(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ===== DATA FETCHING =====
|
// ===== DATA FETCHING =====
|
||||||
const { data: debtSupplier, isLoading } = useSWR(
|
const { data: debtSupplierResponse, isLoading } = useSWR<
|
||||||
() => {
|
BaseApiResponse<DebtSupplier[]>,
|
||||||
const params = {
|
AxiosError<BaseApiResponse>,
|
||||||
supplier_ids: filterParams.supplier_ids,
|
SWRHttpKey
|
||||||
filter_by: filterParams.filter_by,
|
>(
|
||||||
start_date: filterParams.start_date,
|
`${DebtSupplierApi.basePath}/debt-supplier${getTableFilterQueryString()}`,
|
||||||
end_date: filterParams.end_date,
|
httpClientFetcher
|
||||||
page: currentPage,
|
|
||||||
limit: pageSize,
|
|
||||||
};
|
|
||||||
|
|
||||||
return ['debt-supplier-report', params];
|
|
||||||
},
|
|
||||||
([, params]) =>
|
|
||||||
DebtSupplierApi.getDebtSupplierReport(
|
|
||||||
params.supplier_ids,
|
|
||||||
params.filter_by,
|
|
||||||
params.start_date,
|
|
||||||
params.end_date,
|
|
||||||
params.page,
|
|
||||||
params.limit
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const data: DebtSupplier[] = useMemo(
|
const data: DebtSupplier[] = isResponseSuccess(debtSupplierResponse)
|
||||||
() =>
|
? ((debtSupplierResponse?.data as unknown as DebtSupplier[]) ?? [])
|
||||||
isResponseSuccess(debtSupplier)
|
: [];
|
||||||
? (debtSupplier?.data as unknown as DebtSupplier[]) || []
|
|
||||||
: [],
|
|
||||||
[debtSupplier]
|
|
||||||
);
|
|
||||||
|
|
||||||
const meta = useMemo(
|
const meta =
|
||||||
() =>
|
isResponseSuccess(debtSupplierResponse) && debtSupplierResponse.meta
|
||||||
isResponseSuccess(debtSupplier) && debtSupplier.meta
|
? debtSupplierResponse.meta
|
||||||
? debtSupplier.meta
|
: null;
|
||||||
: null,
|
|
||||||
[debtSupplier]
|
|
||||||
);
|
|
||||||
|
|
||||||
// ===== EXPORT DATA FETCHER =====
|
// ===== EXPORT DATA FETCHER =====
|
||||||
const debtSupplierExport = useCallback(async (): Promise<
|
const debtSupplierExport = useCallback(async (): Promise<
|
||||||
DebtSupplier[] | null
|
DebtSupplier[] | null
|
||||||
> => {
|
> => {
|
||||||
const params = {
|
const supplier_ids =
|
||||||
supplier_ids:
|
tableFilterState.suppliers.length > 0
|
||||||
formik.values.supplierIds && formik.values.supplierIds.length > 0
|
? tableFilterState.suppliers.map((o) => String(o.value)).join(',')
|
||||||
? formik.values.supplierIds.map((v) => String(v.value)).join(',')
|
: undefined;
|
||||||
: undefined,
|
|
||||||
filter_by: formik.values.filterBy?.value?.toString() || undefined,
|
|
||||||
start_date: formik.values.startDate || undefined,
|
|
||||||
end_date: formik.values.endDate || undefined,
|
|
||||||
date_type: formik.values.filterBy
|
|
||||||
? formik.values.filterBy.value
|
|
||||||
: undefined,
|
|
||||||
limit: 100,
|
|
||||||
page: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await DebtSupplierApi.getDebtSupplierReport(
|
const response = await DebtSupplierApi.getDebtSupplierReport(
|
||||||
params.supplier_ids,
|
supplier_ids,
|
||||||
params.filter_by,
|
tableFilterState.filterBy?.value,
|
||||||
params.start_date,
|
tableFilterState.start_date || undefined,
|
||||||
params.end_date
|
tableFilterState.end_date || undefined,
|
||||||
|
1,
|
||||||
|
100
|
||||||
);
|
);
|
||||||
|
|
||||||
return isResponseSuccess(response)
|
return isResponseSuccess(response)
|
||||||
? (response.data as unknown as DebtSupplier[])
|
? (response.data as unknown as DebtSupplier[])
|
||||||
: null;
|
: null;
|
||||||
}, [
|
}, [tableFilterState]);
|
||||||
formik.values.supplierIds,
|
|
||||||
formik.values.startDate,
|
|
||||||
formik.values.endDate,
|
|
||||||
formik.values.filterBy,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// ===== EXPORT HANDLERS =====
|
// ===== EXPORT HANDLERS =====
|
||||||
const handleExportExcel = useCallback(async () => {
|
const handleExportExcel = useCallback(async () => {
|
||||||
setIsExcelExportLoading(true);
|
setIsExcelExportLoading(true);
|
||||||
try {
|
try {
|
||||||
|
const supplier_ids =
|
||||||
|
tableFilterState.suppliers.length > 0
|
||||||
|
? tableFilterState.suppliers.map((o) => String(o.value)).join(',')
|
||||||
|
: undefined;
|
||||||
await DebtSupplierApi.exportToExcelSupplierPerSheet(
|
await DebtSupplierApi.exportToExcelSupplierPerSheet(
|
||||||
filterParams.supplier_ids,
|
supplier_ids,
|
||||||
filterParams.filter_by,
|
tableFilterState.filterBy?.value,
|
||||||
filterParams.start_date,
|
tableFilterState.start_date || undefined,
|
||||||
filterParams.end_date
|
tableFilterState.end_date || undefined
|
||||||
);
|
);
|
||||||
toast.success('Excel berhasil dibuat dan diunduh.');
|
toast.success('Excel berhasil dibuat dan diunduh.');
|
||||||
} catch {
|
} catch {
|
||||||
@@ -263,7 +277,30 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsExcelExportLoading(false);
|
setIsExcelExportLoading(false);
|
||||||
}
|
}
|
||||||
}, [filterParams]);
|
}, [tableFilterState]);
|
||||||
|
|
||||||
|
const handleExportExcelGeneral = useCallback(async () => {
|
||||||
|
setIsExcelGeneralExportLoading(true);
|
||||||
|
try {
|
||||||
|
const supplier_ids =
|
||||||
|
tableFilterState.suppliers.length > 0
|
||||||
|
? tableFilterState.suppliers.map((o) => String(o.value)).join(',')
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
await DebtSupplierApi.exportToExcelGeneral(
|
||||||
|
supplier_ids,
|
||||||
|
tableFilterState.filterBy?.value,
|
||||||
|
tableFilterState.start_date || undefined,
|
||||||
|
tableFilterState.end_date || undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
toast.success('Excel General berhasil dibuat dan diunduh.');
|
||||||
|
} catch {
|
||||||
|
toast.error('Gagal membuat Excel General. Silakan coba lagi.');
|
||||||
|
} finally {
|
||||||
|
setIsExcelGeneralExportLoading(false);
|
||||||
|
}
|
||||||
|
}, [tableFilterState]);
|
||||||
|
|
||||||
const handleExportPdf = useCallback(async () => {
|
const handleExportPdf = useCallback(async () => {
|
||||||
setIsPdfExportLoading(true);
|
setIsPdfExportLoading(true);
|
||||||
@@ -279,15 +316,18 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const supplierName =
|
||||||
|
tableFilterState.suppliers.length > 0
|
||||||
|
? tableFilterState.suppliers.map((o) => o.label).join(', ')
|
||||||
|
: undefined;
|
||||||
|
|
||||||
await generateDebtSupplierPDF({
|
await generateDebtSupplierPDF({
|
||||||
data: allDataForExport,
|
data: allDataForExport,
|
||||||
params: {
|
params: {
|
||||||
supplier_name: formik.values.supplierIds
|
supplier_name: supplierName,
|
||||||
?.map((v) => v.label)
|
filter_by: tableFilterState.filterBy?.label,
|
||||||
.join(', '),
|
start_date: tableFilterState.start_date || undefined,
|
||||||
filter_by: formik.values.filterBy?.label,
|
end_date: tableFilterState.end_date || undefined,
|
||||||
start_date: formik.values.startDate || undefined,
|
|
||||||
end_date: formik.values.endDate || undefined,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
toast.success('PDF berhasil dibuat dan diunduh.');
|
toast.success('PDF berhasil dibuat dan diunduh.');
|
||||||
@@ -296,47 +336,22 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
} finally {
|
} finally {
|
||||||
setIsPdfExportLoading(false);
|
setIsPdfExportLoading(false);
|
||||||
}
|
}
|
||||||
}, [
|
}, [debtSupplierExport, tableFilterState]);
|
||||||
debtSupplierExport,
|
|
||||||
formik.values.supplierIds,
|
|
||||||
formik.values.filterBy,
|
|
||||||
formik.values.startDate,
|
|
||||||
formik.values.endDate,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleExportExcelGeneral = useCallback(async () => {
|
|
||||||
setIsExcelGeneralExportLoading(true);
|
|
||||||
try {
|
|
||||||
await DebtSupplierApi.exportToExcelGeneral(
|
|
||||||
filterParams.supplier_ids,
|
|
||||||
filterParams.filter_by,
|
|
||||||
filterParams.start_date,
|
|
||||||
filterParams.end_date
|
|
||||||
);
|
|
||||||
toast.success('Excel General berhasil dibuat dan diunduh.');
|
|
||||||
} catch {
|
|
||||||
toast.error('Gagal membuat Excel General. Silakan coba lagi.');
|
|
||||||
} finally {
|
|
||||||
setIsExcelGeneralExportLoading(false);
|
|
||||||
}
|
|
||||||
}, [filterParams]);
|
|
||||||
|
|
||||||
// ===== TAB ACTIONS COMPONENT =====
|
|
||||||
const TabActions = useMemo(() => {
|
|
||||||
return function TabActionsComponent() {
|
|
||||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
|
||||||
const clearTabActions = useTabActionsStore(
|
|
||||||
(state) => state.clearTabActions
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// ===== TAB ACTIONS =====
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTabActions(
|
setTabActions(
|
||||||
tabId,
|
tabId,
|
||||||
<div className='flex flex-row gap-3'>
|
<div className='flex flex-row gap-3'>
|
||||||
<ButtonFilter
|
<ButtonFilter
|
||||||
values={filterParams}
|
values={{
|
||||||
|
start_date: tableFilterState.start_date,
|
||||||
|
end_date: tableFilterState.end_date,
|
||||||
|
suppliers: tableFilterState.suppliers,
|
||||||
|
filterBy: tableFilterState.filterBy,
|
||||||
|
}}
|
||||||
fieldGroups={[['start_date', 'end_date']]}
|
fieldGroups={[['start_date', 'end_date']]}
|
||||||
onClick={() => handleFilterModalOpenRef.current()}
|
onClick={filterModal.openModal}
|
||||||
variant='outline'
|
variant='outline'
|
||||||
className='px-3 py-2.5'
|
className='px-3 py-2.5'
|
||||||
/>
|
/>
|
||||||
@@ -361,16 +376,9 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
width={20}
|
width={20}
|
||||||
height={20}
|
height={20}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span>Export</span>
|
<span>Export</span>
|
||||||
|
|
||||||
<div className='w-px self-stretch bg-base-content/10' />
|
<div className='w-px self-stretch bg-base-content/10' />
|
||||||
|
<Icon icon='heroicons:chevron-down' width={14} height={14} />
|
||||||
<Icon
|
|
||||||
icon='heroicons:chevron-down'
|
|
||||||
width={14}
|
|
||||||
height={14}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@@ -408,19 +416,11 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, [setTabActions]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
clearTabActions(tabId);
|
|
||||||
};
|
|
||||||
}, [clearTabActions]);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
}, [
|
}, [
|
||||||
tabId,
|
tabId,
|
||||||
filterParams,
|
setTabActions,
|
||||||
|
tableFilterState,
|
||||||
|
filterModal.openModal,
|
||||||
isAnyExportLoading,
|
isAnyExportLoading,
|
||||||
handleExportExcel,
|
handleExportExcel,
|
||||||
handleExportExcelGeneral,
|
handleExportExcelGeneral,
|
||||||
@@ -430,24 +430,9 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
isPdfExportLoading,
|
isPdfExportLoading,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => clearTabActions(tabId);
|
||||||
if (dateErrorShown) {
|
}, [tabId, clearTabActions]);
|
||||||
toast.dismiss();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [dateErrorShown]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (dateErrorShown) {
|
|
||||||
toast.dismiss();
|
|
||||||
setDateErrorShown(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [filterModal.open, dateErrorShown]);
|
|
||||||
|
|
||||||
const getTableColumns = (supplier?: DebtSupplier): ColumnDef<DebtRow>[] => [
|
const getTableColumns = (supplier?: DebtSupplier): ColumnDef<DebtRow>[] => [
|
||||||
{
|
{
|
||||||
@@ -662,9 +647,9 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{TabActionsElement}
|
|
||||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
@@ -693,16 +678,16 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
<Pagination
|
<Pagination
|
||||||
totalItems={meta.total_results || 0}
|
totalItems={meta.total_results || 0}
|
||||||
itemsPerPage={meta.limit || 0}
|
itemsPerPage={meta.limit || 0}
|
||||||
currentPage={meta.page || 0}
|
currentPage={tableFilterState.page}
|
||||||
onPrevPage={() =>
|
onPrevPage={() => setPage(Math.max(1, tableFilterState.page - 1))}
|
||||||
setCurrentPage((curr) => (curr > 1 ? curr - 1 : curr))
|
|
||||||
}
|
|
||||||
onNextPage={() =>
|
onNextPage={() =>
|
||||||
setCurrentPage((curr) =>
|
setPage(
|
||||||
meta.total_pages && curr < meta.total_pages ? curr + 1 : curr
|
meta.total_pages && tableFilterState.page < meta.total_pages
|
||||||
|
? tableFilterState.page + 1
|
||||||
|
: tableFilterState.page
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onPageChange={(pageNumber) => setCurrentPage(pageNumber)}
|
onPageChange={setPage}
|
||||||
rowOptions={[10, 20, 50, 100]}
|
rowOptions={[10, 20, 50, 100]}
|
||||||
onRowChange={setPageSize}
|
onRowChange={setPageSize}
|
||||||
/>
|
/>
|
||||||
@@ -802,16 +787,16 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
<Pagination
|
<Pagination
|
||||||
totalItems={meta.total_results || 0}
|
totalItems={meta.total_results || 0}
|
||||||
itemsPerPage={meta.limit || 0}
|
itemsPerPage={meta.limit || 0}
|
||||||
currentPage={meta.page || 0}
|
currentPage={tableFilterState.page}
|
||||||
onPrevPage={() =>
|
onPrevPage={() => setPage(Math.max(1, tableFilterState.page - 1))}
|
||||||
setCurrentPage((curr) => (curr > 1 ? curr - 1 : curr))
|
|
||||||
}
|
|
||||||
onNextPage={() =>
|
onNextPage={() =>
|
||||||
setCurrentPage((curr) =>
|
setPage(
|
||||||
meta.total_pages && curr < meta.total_pages ? curr + 1 : curr
|
meta.total_pages && tableFilterState.page < meta.total_pages
|
||||||
|
? tableFilterState.page + 1
|
||||||
|
: tableFilterState.page
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onPageChange={(pageNumber) => setCurrentPage(pageNumber)}
|
onPageChange={setPage}
|
||||||
rowOptions={[10, 20, 50, 100]}
|
rowOptions={[10, 20, 50, 100]}
|
||||||
onRowChange={setPageSize}
|
onRowChange={setPageSize}
|
||||||
/>
|
/>
|
||||||
@@ -827,7 +812,6 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
modalBox: 'p-0 rounded-[0.875rem] xl:max-w-4/12 max-w-sm',
|
modalBox: 'p-0 rounded-[0.875rem] xl:max-w-4/12 max-w-sm',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<form onSubmit={formik.handleSubmit} onReset={formik.handleReset}>
|
|
||||||
{/* Modal Header */}
|
{/* Modal Header */}
|
||||||
<div className='flex items-center justify-between gap-2 border-b border-base-content/10 p-4'>
|
<div className='flex items-center justify-between gap-2 border-b border-base-content/10 p-4'>
|
||||||
<div className='flex items-center gap-2 text-primary'>
|
<div className='flex items-center gap-2 text-primary'>
|
||||||
@@ -844,6 +828,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={formik.handleSubmit} onReset={formikResetHandler}>
|
||||||
{/* Modal Body */}
|
{/* Modal Body */}
|
||||||
<div className='p-4 flex flex-col gap-1.5'>
|
<div className='p-4 flex flex-col gap-1.5'>
|
||||||
<div>
|
<div>
|
||||||
@@ -852,153 +837,68 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
|
|||||||
</label>
|
</label>
|
||||||
<div className='flex flex-row gap-1.5 items-center justify-between'>
|
<div className='flex flex-row gap-1.5 items-center justify-between'>
|
||||||
<DateInput
|
<DateInput
|
||||||
name='startDate'
|
name='start_date'
|
||||||
value={formik.values.startDate || ''}
|
value={formik.values.start_date || ''}
|
||||||
onChange={(e) => {
|
onChange={handleStartDateChange}
|
||||||
const value = e.target.value;
|
|
||||||
formik.setFieldValue('startDate', value || null);
|
|
||||||
|
|
||||||
if (value && formik.values.endDate) {
|
|
||||||
const startDate = new Date(value);
|
|
||||||
const endDateObj = new Date(formik.values.endDate);
|
|
||||||
|
|
||||||
if (endDateObj < startDate) {
|
|
||||||
setHasDateError(true);
|
|
||||||
if (!dateErrorShown) {
|
|
||||||
toast.error('Tanggal akhir tidak boleh masa lampau', {
|
|
||||||
duration: Infinity,
|
|
||||||
});
|
|
||||||
setDateErrorShown(true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setHasDateError(false);
|
|
||||||
if (dateErrorShown) {
|
|
||||||
toast.dismiss();
|
|
||||||
setDateErrorShown(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setHasDateError(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
isError={
|
|
||||||
formik.touched.startDate && !!formik.errors.startDate
|
|
||||||
}
|
|
||||||
errorMessage={formik.errors.startDate}
|
|
||||||
isNestedModal
|
isNestedModal
|
||||||
/>
|
/>
|
||||||
<hr className='w-full max-w-3 h-px border-base-content/10'></hr>
|
<hr className='w-full max-w-3 h-px border-base-content/10' />
|
||||||
<DateInput
|
<DateInput
|
||||||
name='endDate'
|
name='end_date'
|
||||||
value={formik.values.endDate || ''}
|
value={formik.values.end_date || ''}
|
||||||
onChange={(e) => {
|
onChange={handleEndDateChange}
|
||||||
const value = e.target.value;
|
|
||||||
formik.setFieldValue('endDate', value || null);
|
|
||||||
|
|
||||||
if (value && formik.values.startDate) {
|
|
||||||
const startDateObj = new Date(formik.values.startDate);
|
|
||||||
const endDate = new Date(value);
|
|
||||||
|
|
||||||
if (endDate < startDateObj) {
|
|
||||||
setHasDateError(true);
|
|
||||||
if (!dateErrorShown) {
|
|
||||||
toast.error('Tanggal akhir tidak boleh masa lampau', {
|
|
||||||
duration: Infinity,
|
|
||||||
});
|
|
||||||
setDateErrorShown(true);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setHasDateError(false);
|
|
||||||
if (dateErrorShown) {
|
|
||||||
toast.dismiss();
|
|
||||||
setDateErrorShown(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
isError={
|
|
||||||
(formik.touched.endDate && !!formik.errors.endDate) ||
|
|
||||||
hasDateError
|
|
||||||
}
|
|
||||||
errorMessage={formik.errors.endDate}
|
|
||||||
isNestedModal
|
isNestedModal
|
||||||
|
isError={hasDateError}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<SelectInputCheckbox
|
<SelectInputCheckbox
|
||||||
label='Supplier'
|
label='Supplier'
|
||||||
placeholder='Pilih Supplier'
|
placeholder='Pilih Supplier'
|
||||||
isMulti
|
|
||||||
options={supplierOptions}
|
options={supplierOptions}
|
||||||
value={
|
value={formik.values.suppliers}
|
||||||
(formik.values.supplierIds as
|
onChange={(val) =>
|
||||||
| { value: number; label: string }
|
formik.setFieldValue('suppliers', Array.isArray(val) ? val : [])
|
||||||
| { value: number; label: string }[]
|
|
||||||
| null
|
|
||||||
| undefined) || []
|
|
||||||
}
|
}
|
||||||
onChange={(val) => {
|
|
||||||
formik.setFieldValue(
|
|
||||||
'supplierIds',
|
|
||||||
Array.isArray(val) ? val : val ? [val] : null
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
onInputChange={setSupplierInputValue}
|
onInputChange={setSupplierInputValue}
|
||||||
onMenuScrollToBottom={loadMoreSuppliers}
|
onMenuScrollToBottom={loadMoreSuppliers}
|
||||||
isLoading={isLoadingSupplierOptions}
|
isLoading={isLoadingSupplierOptions}
|
||||||
isClearable
|
isClearable
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
isError={
|
|
||||||
formik.touched.supplierIds && !!formik.errors.supplierIds
|
|
||||||
}
|
|
||||||
errorMessage={formik.errors.supplierIds as string}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<SelectInputRadio
|
<SelectInputRadio
|
||||||
label='Filter Berdasarkan'
|
label='Filter Berdasarkan'
|
||||||
placeholder='Pilih Filter Berdasarkan'
|
placeholder='Pilih Filter Berdasarkan'
|
||||||
options={dataTypeOptions}
|
options={dataTypeOptions}
|
||||||
value={
|
value={formik.values.filterBy ?? null}
|
||||||
(formik.values.filterBy as
|
onChange={(val) =>
|
||||||
| { value: string; label: string }
|
|
||||||
| { value: string; label: string }[]
|
|
||||||
| null
|
|
||||||
| undefined) || null
|
|
||||||
}
|
|
||||||
onChange={(val) => {
|
|
||||||
formik.setFieldValue(
|
formik.setFieldValue(
|
||||||
'filterBy',
|
'filterBy',
|
||||||
val ? (val as OptionType) : null
|
!Array.isArray(val) ? (val ?? undefined) : undefined
|
||||||
);
|
)
|
||||||
}}
|
}
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
isClearable
|
isClearable
|
||||||
isError={formik.touched.filterBy && !!formik.errors.filterBy}
|
|
||||||
errorMessage={formik.errors.filterBy as string}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Action Buttons */}
|
{/* Modal Footer */}
|
||||||
<div className='flex justify-between items-center gap-4 p-4 border-t border-base-content/10 bg-gray-50'>
|
<div className='flex justify-between items-center gap-4 p-4 border-t border-base-content/10 bg-gray-50'>
|
||||||
<Button
|
<Button
|
||||||
variant='soft'
|
|
||||||
color='none'
|
|
||||||
className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2'
|
|
||||||
type='reset'
|
type='reset'
|
||||||
|
variant='soft'
|
||||||
|
className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2'
|
||||||
>
|
>
|
||||||
Reset Filter
|
Reset Filter
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
|
|
||||||
type='submit'
|
type='submit'
|
||||||
|
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
|
||||||
|
disabled={hasDateError}
|
||||||
>
|
>
|
||||||
Apply Filter
|
Apply Filter
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user