mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 21:41:57 +00:00
refactor: optimize CustomerPaymentTab with useTableFilter persistence pattern
Replace filterParams/currentPage/pageSize state with useTableFilter (persist:true), switch SWR to httpClientFetcher with explicit type, store OptionType[] directly for customers/filterBy, add formikResetHandler using resetFilter(), remove enableReinitialize and handleFilterModalOpenRef, pass filterModal.openModal directly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { AxiosError } from 'axios';
|
||||
import Card from '@/components/Card';
|
||||
import StatusBadge from '@/components/helper/StatusBadge';
|
||||
import { useSelect } from '@/components/input/SelectInput';
|
||||
import { useSelect, OptionType } from '@/components/input/SelectInput';
|
||||
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||
import SelectInputRadio from '@/components/input/SelectInputRadio';
|
||||
import DateInput from '@/components/input/DateInput';
|
||||
import { CustomerApi } from '@/services/api/master-data';
|
||||
import { FinanceApi } from '@/services/api/report/finance-report';
|
||||
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
|
||||
import { BaseApiResponse } from '@/types/api/api-general';
|
||||
import Table from '@/components/Table';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import {
|
||||
@@ -27,28 +30,22 @@ import Dropdown from '@/components/Dropdown';
|
||||
import Modal, { useModal } from '@/components/Modal';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useFormik } from 'formik';
|
||||
import {
|
||||
CustomerPaymentFilterSchema,
|
||||
CustomerPaymentFilterType,
|
||||
} from '@/components/pages/report/finance/filter/CustomerPaymentFilter';
|
||||
import { generateCustomerPaymentPDF } from '@/components/pages/report/finance/export/CustomerPaymentExportPDF';
|
||||
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
|
||||
import CustomerSupplierSkeleton from '@/components/pages/report/finance/skeleton/CustomerSupplierSkeleton';
|
||||
import { OptionType } from '@/components/table/TableRowSizeSelector';
|
||||
import { Color } from '@/types/theme';
|
||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
|
||||
interface CustomerPaymentTabProps {
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
interface FilterParams {
|
||||
customer_ids?: string;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
filter_by?: string;
|
||||
}
|
||||
const dataTypeOptions: OptionType<string>[] = [
|
||||
{ value: 'trans_date', label: 'Tanggal Jual/Bayar' },
|
||||
{ value: 'realization_date', label: 'Tanggal Realisasi' },
|
||||
];
|
||||
|
||||
const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
// ===== STATE MANAGEMENT =====
|
||||
@@ -59,26 +56,44 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
const isAnyExportLoading =
|
||||
isPdfExportLoading || isExcelExportLoading || isExcelGeneralExportLoading;
|
||||
|
||||
// ===== PAGINATION STATE =====
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
// ===== SUBMISSION STATE =====
|
||||
const [filterParams, setFilterParams] = useState<FilterParams>({});
|
||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||
const [hasDateError, setHasDateError] = useState(false);
|
||||
|
||||
const handleFilterModalOpenRef = useRef(() => {});
|
||||
|
||||
const filterModal = useModal();
|
||||
|
||||
const dataTypeOptions = useMemo(
|
||||
() => [
|
||||
{ value: 'trans_date', label: 'Tanggal Jual/Bayar' },
|
||||
{ value: 'realization_date', label: 'Tanggal Realisasi' },
|
||||
],
|
||||
[]
|
||||
);
|
||||
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;
|
||||
customers: OptionType<number>[];
|
||||
filterBy?: OptionType<string>;
|
||||
}>({
|
||||
initial: {
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
customers: [],
|
||||
filterBy: undefined,
|
||||
},
|
||||
paramMap: {
|
||||
page: 'page',
|
||||
pageSize: 'limit',
|
||||
start_date: 'start_date',
|
||||
end_date: 'end_date',
|
||||
customers: 'customer_ids',
|
||||
filterBy: 'filter_by',
|
||||
},
|
||||
persist: true,
|
||||
storeName: 'customer-payment-report-table',
|
||||
});
|
||||
|
||||
const {
|
||||
options: customerOptions,
|
||||
@@ -88,222 +103,159 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
} = useSelect(CustomerApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
// ===== FORMIK SETUP =====
|
||||
const formik = useFormik<CustomerPaymentFilterType>({
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
start_date: null,
|
||||
end_date: null,
|
||||
customer_ids: null,
|
||||
filter_by: null,
|
||||
start_date: tableFilterState.start_date,
|
||||
end_date: tableFilterState.end_date,
|
||||
customers: tableFilterState.customers,
|
||||
filterBy: tableFilterState.filterBy,
|
||||
},
|
||||
validationSchema: CustomerPaymentFilterSchema,
|
||||
onSubmit: (values, { setSubmitting }) => {
|
||||
setFilterParams({
|
||||
start_date: values.start_date || undefined,
|
||||
end_date: values.end_date || undefined,
|
||||
customer_ids: values.customer_ids || undefined,
|
||||
filter_by: values.filter_by || undefined,
|
||||
});
|
||||
filterModal.closeModal();
|
||||
setCurrentPage(1);
|
||||
setSubmitting(false);
|
||||
},
|
||||
onReset: () => {
|
||||
setFilterParams({});
|
||||
setCurrentPage(1);
|
||||
setHasDateError(false);
|
||||
if (dateErrorShown) {
|
||||
toast.dismiss();
|
||||
setDateErrorShown(false);
|
||||
}
|
||||
onSubmit: (values) => {
|
||||
updateFilter('start_date', values.start_date, true);
|
||||
updateFilter('end_date', values.end_date, true);
|
||||
updateFilter('customers', values.customers, true);
|
||||
updateFilter('filterBy', values.filterBy, true);
|
||||
filterModal.closeModal();
|
||||
},
|
||||
});
|
||||
|
||||
handleFilterModalOpenRef.current = () => {
|
||||
formik.setValues({
|
||||
start_date: filterParams.start_date || null,
|
||||
end_date: filterParams.end_date || null,
|
||||
customer_ids: filterParams.customer_ids || null,
|
||||
filter_by: filterParams.filter_by || null,
|
||||
const formikResetHandler = () => {
|
||||
resetFilter();
|
||||
|
||||
setHasDateError(false);
|
||||
if (dateErrorShown) {
|
||||
toast.dismiss();
|
||||
setDateErrorShown(false);
|
||||
}
|
||||
|
||||
formik.resetForm({
|
||||
values: {
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
customers: [],
|
||||
filterBy: undefined,
|
||||
},
|
||||
});
|
||||
filterModal.openModal();
|
||||
|
||||
filterModal.closeModal();
|
||||
};
|
||||
|
||||
const getPaymentStatusBadgeColor = (notes: string): Color => {
|
||||
const normalizedValue = notes.toLowerCase();
|
||||
|
||||
if (normalizedValue === 'lunas') {
|
||||
return 'primary';
|
||||
}
|
||||
|
||||
if (normalizedValue.includes('belum')) {
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
if (normalizedValue === 'lunas') return 'primary';
|
||||
if (normalizedValue.includes('belum')) return 'warning';
|
||||
return 'neutral';
|
||||
};
|
||||
|
||||
// ===== DATE CHANGE HANDLERS =====
|
||||
const handleStartDateChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
formik.setFieldValue('start_date', value || null);
|
||||
const handleStartDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
formik.setFieldValue('start_date', value);
|
||||
|
||||
if (value && formik.values.end_date) {
|
||||
const startDate = new Date(value);
|
||||
const endDateObj = new Date(formik.values.end_date);
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
},
|
||||
[formik, dateErrorShown]
|
||||
);
|
||||
|
||||
const handleEndDateChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
formik.setFieldValue('end_date', value || null);
|
||||
|
||||
if (value && formik.values.start_date) {
|
||||
const startDateObj = new Date(formik.values.start_date);
|
||||
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;
|
||||
if (dateErrorShown) {
|
||||
toast.dismiss();
|
||||
setDateErrorShown(false);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
setHasDateError(false);
|
||||
if (dateErrorShown) {
|
||||
toast.dismiss();
|
||||
setDateErrorShown(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;
|
||||
}
|
||||
},
|
||||
[formik, dateErrorShown]
|
||||
);
|
||||
}
|
||||
|
||||
// ===== FILTER HELPERS =====
|
||||
const customerIdsValue = useMemo(() => {
|
||||
if (!formik.values.customer_ids) return [];
|
||||
return customerOptions.filter((opt) =>
|
||||
formik.values.customer_ids?.split(',').includes(String(opt.value))
|
||||
);
|
||||
}, [formik.values.customer_ids, customerOptions]);
|
||||
|
||||
const filterByValue = useMemo(() => {
|
||||
if (!formik.values.filter_by) return null;
|
||||
return (
|
||||
dataTypeOptions.find((opt) => opt.value === formik.values.filter_by) ||
|
||||
null
|
||||
);
|
||||
}, [formik.values.filter_by, dataTypeOptions]);
|
||||
setHasDateError(false);
|
||||
if (dateErrorShown) {
|
||||
toast.dismiss();
|
||||
setDateErrorShown(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ===== DATA FETCHING =====
|
||||
const { data: customerPayment, isLoading } = useSWR(
|
||||
() => {
|
||||
const params = {
|
||||
customer_ids: filterParams.customer_ids,
|
||||
filter_by: filterParams.filter_by as
|
||||
| 'trans_date'
|
||||
| 'realization_date'
|
||||
| undefined,
|
||||
start_date: filterParams.start_date,
|
||||
end_date: filterParams.end_date,
|
||||
page: currentPage,
|
||||
limit: pageSize,
|
||||
};
|
||||
|
||||
return ['customer-payment-report', params];
|
||||
},
|
||||
([, params]) =>
|
||||
FinanceApi.getCustomerPaymentReport(
|
||||
params.customer_ids,
|
||||
params.filter_by,
|
||||
params.start_date,
|
||||
params.end_date,
|
||||
params.page,
|
||||
params.limit
|
||||
)
|
||||
const { data: customerPayment, isLoading } = useSWR<
|
||||
BaseApiResponse<CustomerPaymentReport>,
|
||||
AxiosError<BaseApiResponse>,
|
||||
SWRHttpKey
|
||||
>(
|
||||
`${FinanceApi.basePath}/customer-payment${getTableFilterQueryString()}`,
|
||||
httpClientFetcher
|
||||
);
|
||||
|
||||
const data: CustomerPaymentReport[] = useMemo(
|
||||
() =>
|
||||
isResponseSuccess(customerPayment)
|
||||
? (customerPayment?.data as unknown as CustomerPaymentReport[]) || []
|
||||
: [],
|
||||
[customerPayment]
|
||||
);
|
||||
const data: CustomerPaymentReport[] = isResponseSuccess(customerPayment)
|
||||
? (customerPayment?.data as unknown as CustomerPaymentReport[]) || []
|
||||
: [];
|
||||
|
||||
const meta = useMemo(
|
||||
() =>
|
||||
isResponseSuccess(customerPayment) && customerPayment.meta
|
||||
? customerPayment.meta
|
||||
: null,
|
||||
[customerPayment]
|
||||
);
|
||||
const meta =
|
||||
isResponseSuccess(customerPayment) && customerPayment.meta
|
||||
? customerPayment.meta
|
||||
: null;
|
||||
|
||||
// ===== EXPORT DATA FETCHER =====
|
||||
const customerPaymentExport = useCallback(async (): Promise<
|
||||
CustomerPaymentReport[] | null
|
||||
> => {
|
||||
const params = {
|
||||
customer_ids: filterParams.customer_ids,
|
||||
filter_by: filterParams.filter_by as
|
||||
| 'trans_date'
|
||||
| 'realization_date'
|
||||
| undefined,
|
||||
start_date: filterParams.start_date,
|
||||
end_date: filterParams.end_date,
|
||||
limit: 100,
|
||||
page: 1,
|
||||
};
|
||||
const customer_ids =
|
||||
tableFilterState.customers.length > 0
|
||||
? tableFilterState.customers.map((o) => String(o.value)).join(',')
|
||||
: undefined;
|
||||
const filter_by = tableFilterState.filterBy?.value as
|
||||
| 'trans_date'
|
||||
| 'realization_date'
|
||||
| undefined;
|
||||
|
||||
const response = await FinanceApi.getCustomerPaymentReport(
|
||||
params.customer_ids,
|
||||
params.filter_by,
|
||||
params.start_date,
|
||||
params.end_date,
|
||||
params.page,
|
||||
params.limit
|
||||
customer_ids,
|
||||
filter_by,
|
||||
tableFilterState.start_date || undefined,
|
||||
tableFilterState.end_date || undefined,
|
||||
1,
|
||||
100
|
||||
);
|
||||
|
||||
return isResponseSuccess(response)
|
||||
? (response.data as unknown as CustomerPaymentReport[])
|
||||
: null;
|
||||
}, [filterParams]);
|
||||
}, [tableFilterState]);
|
||||
|
||||
// ===== EXPORT HANDLERS =====
|
||||
const handleExportExcelGeneral = useCallback(async () => {
|
||||
setIsExcelGeneralExportLoading(true);
|
||||
try {
|
||||
const customer_ids =
|
||||
tableFilterState.customers.length > 0
|
||||
? tableFilterState.customers.map((o) => String(o.value)).join(',')
|
||||
: undefined;
|
||||
await FinanceApi.exportCustomerPaymentToExcelGeneral(
|
||||
filterParams.customer_ids,
|
||||
filterParams.filter_by,
|
||||
filterParams.start_date,
|
||||
filterParams.end_date
|
||||
customer_ids,
|
||||
tableFilterState.filterBy?.value,
|
||||
tableFilterState.start_date || undefined,
|
||||
tableFilterState.end_date || undefined
|
||||
);
|
||||
toast.success('Excel General berhasil dibuat dan diunduh.');
|
||||
} catch {
|
||||
@@ -311,16 +263,20 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
} finally {
|
||||
setIsExcelGeneralExportLoading(false);
|
||||
}
|
||||
}, [filterParams]);
|
||||
}, [tableFilterState]);
|
||||
|
||||
const handleExportExcel = useCallback(async () => {
|
||||
setIsExcelExportLoading(true);
|
||||
try {
|
||||
const customer_ids =
|
||||
tableFilterState.customers.length > 0
|
||||
? tableFilterState.customers.map((o) => String(o.value)).join(',')
|
||||
: undefined;
|
||||
await FinanceApi.exportCustomerPaymentToExcelCustomerPerSheet(
|
||||
filterParams.customer_ids,
|
||||
filterParams.filter_by,
|
||||
filterParams.start_date,
|
||||
filterParams.end_date
|
||||
customer_ids,
|
||||
tableFilterState.filterBy?.value,
|
||||
tableFilterState.start_date || undefined,
|
||||
tableFilterState.end_date || undefined
|
||||
);
|
||||
toast.success('Excel berhasil dibuat dan diunduh.');
|
||||
} catch {
|
||||
@@ -328,7 +284,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
} finally {
|
||||
setIsExcelExportLoading(false);
|
||||
}
|
||||
}, [filterParams]);
|
||||
}, [tableFilterState]);
|
||||
|
||||
const handleExportPdf = useCallback(async () => {
|
||||
setIsPdfExportLoading(true);
|
||||
@@ -344,22 +300,18 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const customerName = filterParams.customer_ids
|
||||
? customerOptions
|
||||
.filter((opt) =>
|
||||
filterParams.customer_ids?.split(',').includes(String(opt.value))
|
||||
)
|
||||
.map((opt) => opt.label)
|
||||
.join(', ') || 'Semua Customer'
|
||||
: 'Semua Customer';
|
||||
const customerName =
|
||||
tableFilterState.customers.length > 0
|
||||
? tableFilterState.customers.map((o) => o.label).join(', ')
|
||||
: 'Semua Customer';
|
||||
|
||||
await generateCustomerPaymentPDF({
|
||||
data: allDataForExport,
|
||||
params: {
|
||||
customer_name: customerName,
|
||||
start_date: filterParams.start_date,
|
||||
end_date: filterParams.end_date,
|
||||
filter_by: filterParams.filter_by as
|
||||
start_date: tableFilterState.start_date || undefined,
|
||||
end_date: tableFilterState.end_date || undefined,
|
||||
filter_by: tableFilterState.filterBy?.value as
|
||||
| 'trans_date'
|
||||
| 'realization_date'
|
||||
| undefined,
|
||||
@@ -371,119 +323,103 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
} finally {
|
||||
setIsPdfExportLoading(false);
|
||||
}
|
||||
}, [customerPaymentExport, filterParams, customerOptions]);
|
||||
}, [customerPaymentExport, tableFilterState]);
|
||||
|
||||
// ===== TAB ACTIONS COMPONENT =====
|
||||
const TabActions = useMemo(() => {
|
||||
return function TabActionsComponent() {
|
||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||
const clearTabActions = useTabActionsStore(
|
||||
(state) => state.clearTabActions
|
||||
);
|
||||
// ===== TAB ACTIONS =====
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={{
|
||||
start_date: tableFilterState.start_date,
|
||||
end_date: tableFilterState.end_date,
|
||||
customers: tableFilterState.customers,
|
||||
filterBy: tableFilterState.filterBy,
|
||||
}}
|
||||
fieldGroups={[['start_date', 'end_date']]}
|
||||
onClick={filterModal.openModal}
|
||||
variant='outline'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
useEffect(() => {
|
||||
setTabActions(
|
||||
tabId,
|
||||
<div className='flex flex-row gap-3'>
|
||||
<ButtonFilter
|
||||
values={filterParams}
|
||||
fieldGroups={[['start_date', 'end_date']]}
|
||||
onClick={() => handleFilterModalOpenRef.current()}
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
variant='outline'
|
||||
className='px-3 py-2.5'
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
align='end'
|
||||
direction='bottom'
|
||||
className={{
|
||||
content:
|
||||
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
|
||||
}}
|
||||
trigger={
|
||||
<Button
|
||||
variant='outline'
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
|
||||
<span>Export</span>
|
||||
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
|
||||
<Icon
|
||||
icon='heroicons:chevron-down'
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
color='none'
|
||||
isLoading={isAnyExportLoading}
|
||||
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
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} />
|
||||
Export to Excel - Customer Per Sheet
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcelGeneral}
|
||||
isLoading={isExcelGeneralExportLoading}
|
||||
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} />
|
||||
Export to Excel - General
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPdf}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}, [setTabActions]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTabActions(tabId);
|
||||
};
|
||||
}, [clearTabActions]);
|
||||
|
||||
return null;
|
||||
};
|
||||
<div className='flex flex-row items-center gap-1.5'>
|
||||
<Icon
|
||||
icon='heroicons:cloud-arrow-down'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<span>Export</span>
|
||||
<div className='w-px self-stretch bg-base-content/10' />
|
||||
<Icon icon='heroicons:chevron-down' width={14} height={14} />
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcel}
|
||||
isLoading={isExcelExportLoading}
|
||||
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} />
|
||||
Export to Excel - Customer Per Sheet
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportExcelGeneral}
|
||||
isLoading={isExcelGeneralExportLoading}
|
||||
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} />
|
||||
Export to Excel - General
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={handleExportPdf}
|
||||
isLoading={isPdfExportLoading}
|
||||
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
|
||||
>
|
||||
<Icon icon='heroicons:document' width={20} height={20} />
|
||||
Export to PDF
|
||||
</Button>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}, [
|
||||
tabId,
|
||||
setTabActions,
|
||||
tableFilterState,
|
||||
filterModal.openModal,
|
||||
isAnyExportLoading,
|
||||
handleExportExcelGeneral,
|
||||
handleExportExcel,
|
||||
handleExportExcelGeneral,
|
||||
handleExportPdf,
|
||||
isExcelGeneralExportLoading,
|
||||
isExcelExportLoading,
|
||||
isExcelGeneralExportLoading,
|
||||
isPdfExportLoading,
|
||||
filterParams,
|
||||
]);
|
||||
|
||||
const TabActionsElement = useMemo(() => <TabActions />, [TabActions]);
|
||||
useEffect(() => {
|
||||
return () => clearTabActions(tabId);
|
||||
}, [tabId, clearTabActions]);
|
||||
|
||||
const getTableColumns = (
|
||||
summary: CustomerPaymentSummary
|
||||
@@ -690,11 +626,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
enableSorting: false,
|
||||
cell: (props) => {
|
||||
const value = props.row.original.status;
|
||||
|
||||
if (!value) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
if (!value) return '-';
|
||||
return (
|
||||
<StatusBadge
|
||||
color={getPaymentStatusBadgeColor(value)}
|
||||
@@ -733,7 +665,6 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{TabActionsElement}
|
||||
<div className='w-full p-0 sm:p-3 flex flex-col gap-3'>
|
||||
{isLoading && (
|
||||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||
@@ -762,16 +693,16 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
<Pagination
|
||||
totalItems={meta.total_results || 0}
|
||||
itemsPerPage={meta.limit || 0}
|
||||
currentPage={meta.page || 0}
|
||||
onPrevPage={() =>
|
||||
setCurrentPage((curr) => (curr > 1 ? curr - 1 : curr))
|
||||
}
|
||||
currentPage={tableFilterState.page}
|
||||
onPrevPage={() => setPage(Math.max(1, tableFilterState.page - 1))}
|
||||
onNextPage={() =>
|
||||
setCurrentPage((curr) =>
|
||||
meta.total_pages && curr < meta.total_pages ? curr + 1 : curr
|
||||
setPage(
|
||||
meta.total_pages && tableFilterState.page < meta.total_pages
|
||||
? tableFilterState.page + 1
|
||||
: tableFilterState.page
|
||||
)
|
||||
}
|
||||
onPageChange={(pageNumber) => setCurrentPage(pageNumber)}
|
||||
onPageChange={setPage}
|
||||
rowOptions={[10, 20, 50, 100]}
|
||||
onRowChange={setPageSize}
|
||||
/>
|
||||
@@ -878,16 +809,16 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
<Pagination
|
||||
totalItems={meta.total_results || 0}
|
||||
itemsPerPage={meta.limit || 0}
|
||||
currentPage={meta.page || 0}
|
||||
onPrevPage={() =>
|
||||
setCurrentPage((curr) => (curr > 1 ? curr - 1 : curr))
|
||||
}
|
||||
currentPage={tableFilterState.page}
|
||||
onPrevPage={() => setPage(Math.max(1, tableFilterState.page - 1))}
|
||||
onNextPage={() =>
|
||||
setCurrentPage((curr) =>
|
||||
meta.total_pages && curr < meta.total_pages ? curr + 1 : curr
|
||||
setPage(
|
||||
meta.total_pages && tableFilterState.page < meta.total_pages
|
||||
? tableFilterState.page + 1
|
||||
: tableFilterState.page
|
||||
)
|
||||
}
|
||||
onPageChange={(pageNumber) => setCurrentPage(pageNumber)}
|
||||
onPageChange={setPage}
|
||||
rowOptions={[10, 20, 50, 100]}
|
||||
onRowChange={setPageSize}
|
||||
/>
|
||||
@@ -917,7 +848,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
<Icon icon='heroicons:x-mark' width={20} height={20} />
|
||||
</Button>
|
||||
</div>
|
||||
<form onSubmit={formik.handleSubmit} onReset={formik.handleReset}>
|
||||
<form onSubmit={formik.handleSubmit} onReset={formikResetHandler}>
|
||||
<div className='p-4 flex flex-col gap-1.5'>
|
||||
<div>
|
||||
<label className='block text-xs font-semibold text-base-content py-2'>
|
||||
@@ -927,29 +858,18 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
<DateInput
|
||||
name='start_date'
|
||||
value={formik.values.start_date || ''}
|
||||
errorMessage={formik.errors.start_date}
|
||||
onChange={handleStartDateChange}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
isNestedModal
|
||||
isError={
|
||||
formik.touched.start_date &&
|
||||
Boolean(formik.errors.start_date)
|
||||
}
|
||||
/>
|
||||
<hr className='w-full max-w-3 h-px border-base-content/10' />
|
||||
|
||||
<DateInput
|
||||
name='end_date'
|
||||
value={formik.values.end_date || ''}
|
||||
errorMessage={formik.errors.end_date}
|
||||
onChange={handleEndDateChange}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
isNestedModal
|
||||
isError={
|
||||
(formik.touched.end_date &&
|
||||
Boolean(formik.errors.end_date)) ||
|
||||
hasDateError
|
||||
}
|
||||
isError={hasDateError}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -958,15 +878,10 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
label='Customer'
|
||||
placeholder='Pilih Customer'
|
||||
options={customerOptions}
|
||||
value={customerIdsValue}
|
||||
onChange={(val) => {
|
||||
formik.setFieldValue(
|
||||
'customer_ids',
|
||||
Array.isArray(val) && val.length > 0
|
||||
? val.map((v: OptionType) => String(v.value)).join(',')
|
||||
: null
|
||||
);
|
||||
}}
|
||||
value={formik.values.customers}
|
||||
onChange={(val) =>
|
||||
formik.setFieldValue('customers', Array.isArray(val) ? val : [])
|
||||
}
|
||||
onInputChange={setCustomerInputValue}
|
||||
isLoading={isLoadingCustomers}
|
||||
isClearable
|
||||
@@ -978,14 +893,15 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
label='Filter Berdasarkan'
|
||||
placeholder='Pilih Filter Berdasarkan'
|
||||
options={dataTypeOptions}
|
||||
value={filterByValue}
|
||||
onChange={(val) => {
|
||||
if (!Array.isArray(val)) {
|
||||
formik.setFieldValue('filter_by', val?.value || null);
|
||||
}
|
||||
}}
|
||||
value={formik.values.filterBy ?? null}
|
||||
onChange={(val) =>
|
||||
formik.setFieldValue(
|
||||
'filterBy',
|
||||
!Array.isArray(val) ? (val ?? undefined) : undefined
|
||||
)
|
||||
}
|
||||
className={{ wrapper: 'w-full' }}
|
||||
isClearable={true}
|
||||
isClearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1001,7 +917,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
|
||||
<Button
|
||||
type='submit'
|
||||
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
|
||||
disabled={hasDateError || !formik.isValid || formik.isSubmitting}
|
||||
disabled={hasDateError}
|
||||
>
|
||||
Apply Filter
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user