[] => [
+ {
+ header: 'No',
+ enableSorting: false,
+ cell: (props) =>
+ (tableFilterState.page - 1) * tableFilterState.pageSize +
+ props.row.index +
+ 1,
+ },
+ {
+ header: 'Customer',
+ accessorKey: 'customer.name',
+ enableSorting: true,
+ id: 'customer_name',
+ cell: ({ row }) => row.original.customer.name,
+ },
+ {
+ header: 'Saldo Awal',
+ accessorKey: 'saldo_awal',
+ id: 'saldo_awal',
+ enableSorting: true,
+ cell: ({ row }) => (
+
+ {formatCurrency(row.original.saldo_awal)}
+
+ ),
+ },
+ {
+ header: 'Penjualan Ayam',
+ columns: [
+ {
+ header: 'Ekor',
+ accessorKey: 'penjualan_ayam.ekor',
+ id: 'penjualan_ayam_ekor',
+ enableSorting: true,
+ cell: ({ row }) => (
+
+ {formatNumber(row.original.penjualan_ayam.ekor)}
+
+ ),
+ },
+ {
+ header: 'Kg',
+ accessorKey: 'penjualan_ayam.kg',
+ id: 'penjualan_ayam_kg',
+ enableSorting: true,
+ cell: ({ row }) => (
+
+ {formatNumber(row.original.penjualan_ayam.kg)}
+
+ ),
+ },
+ {
+ header: 'Nominal',
+ accessorKey: 'penjualan_ayam.nominal',
+ id: 'penjualan_ayam_nominal',
+ enableSorting: true,
+ cell: ({ row }) => (
+
+ {formatCurrency(row.original.penjualan_ayam.nominal)}
+
+ ),
+ },
+ ],
+ },
+ {
+ header: 'Penjualan Telur',
+ columns: [
+ {
+ header: 'Butir',
+ accessorKey: 'penjualan_telur.butir',
+ id: 'penjualan_telur_butir',
+ enableSorting: true,
+ cell: ({ row }) => (
+
+ {formatNumber(row.original.penjualan_telur.butir)}
+
+ ),
+ },
+ {
+ header: 'Kg',
+ accessorKey: 'penjualan_telur.kg',
+ id: 'penjualan_telur_kg',
+ enableSorting: true,
+ cell: ({ row }) => (
+
+ {formatNumber(row.original.penjualan_telur.kg)}
+
+ ),
+ },
+ {
+ header: 'Nominal',
+ accessorKey: 'penjualan_telur.nominal',
+ id: 'penjualan_telur_nominal',
+ enableSorting: true,
+ cell: ({ row }) => (
+
+ {formatCurrency(row.original.penjualan_telur.nominal)}
+
+ ),
+ },
+ ],
+ },
+ {
+ header: 'Penjualan Trading',
+ accessorKey: 'penjualan_trading.nominal',
+ id: 'penjualan_trading',
+ enableSorting: true,
+ cell: ({ row }) => (
+
+ {formatCurrency(row.original.penjualan_trading.nominal)}
+
+ ),
+ },
+ {
+ header: 'Pembayaran',
+ accessorKey: 'pembayaran',
+ id: 'pembayaran',
+ enableSorting: true,
+ cell: ({ row }) => (
+
+ {formatCurrency(row.original.pembayaran)}
+
+ ),
+ },
+ {
+ header: 'Aging',
+ accessorKey: 'aging',
+ id: 'aging',
+ enableSorting: true,
+ cell: ({ row }) => (
+
+ {formatNumber(row.original.aging)} hari
+
+ ),
+ },
+ {
+ header: 'Aging Rata-Rata',
+ accessorKey: 'aging_rata_rata',
+ id: 'aging_rata_rata',
+ enableSorting: true,
+ cell: ({ row }) => (
+
+ {formatNumber(row.original.aging_rata_rata)} hari
+
+ ),
+ },
+ {
+ header: 'Saldo Akhir',
+ accessorKey: 'saldo_akhir',
+ id: 'saldo_akhir',
+ enableSorting: true,
+ cell: ({ row }) => (
+
+ {formatCurrency(row.original.saldo_akhir)}
+
+ ),
+ },
+ ],
+ [tableFilterState.page, tableFilterState.pageSize]
+ );
+
+ return (
+ <>
+
+ {isLoading && (
+
+
+
+ )}
+
+ {!isLoading && balanceMonitorings.length === 0 && (
+
[]}
+ icon={
+
+ }
+ title='Data Not Yet Available'
+ subtitle='Please change your filters to get the data.'
+ />
+ )}
+
+ {!isLoading && balanceMonitorings.length > 0 && (
+ <>
+
+ >
+ )}
+
+
+ {/* Filter Modal */}
+
+ {/* Modal Header */}
+
+
+
+
Filter Data
+
+
+
+
+
+
+ >
+ );
+};
+
+export default BalanceMonitoringTab;
diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
index e9c20053..e786c9a5 100644
--- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
+++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
@@ -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[] = [
+ { 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({});
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[];
+ filterBy?: OptionType;
+ }>({
+ 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({
+ 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) => {
- const value = e.target.value;
- formik.setFieldValue('start_date', value || null);
+ const handleStartDateChange = (e: React.ChangeEvent) => {
+ 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) => {
- 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) => {
+ 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,
+ AxiosError,
+ 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,
+
+
- useEffect(() => {
- setTabActions(
- tabId,
-
-
handleFilterModalOpenRef.current()}
+
-
-
-
-
- }
+ 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'
>
-
-
-
-
-
-
- );
- }, [setTabActions]);
-
- useEffect(() => {
- return () => {
- clearTabActions(tabId);
- };
- }, [clearTabActions]);
-
- return null;
- };
+
+
+ }
+ >
+
+
+
+
+
+ );
}, [
tabId,
+ setTabActions,
+ tableFilterState,
+ filterModal.openModal,
isAnyExportLoading,
- handleExportExcelGeneral,
handleExportExcel,
+ handleExportExcelGeneral,
handleExportPdf,
- isExcelGeneralExportLoading,
isExcelExportLoading,
+ isExcelGeneralExportLoading,
isPdfExportLoading,
- filterParams,
]);
- const TabActionsElement = useMemo(() => , [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 (
{
return (
<>
- {TabActionsElement}
{isLoading && (
@@ -762,16 +693,16 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
- 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) => {
- 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) => {
-
@@ -1001,7 +917,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => {
diff --git a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx
index 95bed1f2..78d27f2a 100644
--- a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx
+++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx
@@ -9,23 +9,15 @@ import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
import { isResponseSuccess } from '@/lib/api-helper';
import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
import { SupplierApi } from '@/services/api/master-data';
-import {
- DebtRow,
- DebtSupplier,
- DebtSupplierFilter,
-} from '@/types/api/report/debt-supplier';
+import { DebtRow, DebtSupplier } from '@/types/api/report/debt-supplier';
import { generateDebtSupplierPDF } from '@/components/pages/report/finance/export/DebtSupllierExportPDF';
import { Icon } from '@iconify/react';
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 useSWR from 'swr';
import { DebtSupplierApi } from '@/services/api/report/debt-supplier';
import { useFormik } from 'formik';
-import {
- DebtSupplierFilterSchema,
- DebtSupplierFilterType,
-} from '@/components/pages/report/finance/filter/DebtSupplierFilter';
import ButtonFilter from '@/components/helper/ButtonFilter';
import { Color } from '@/types/theme';
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 StatusBadge from '@/components/helper/StatusBadge';
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 = {
'Sudah Jatuh Tempo': 'error',
@@ -51,7 +47,6 @@ const getPillBadge = (
statusText: string,
type: 'due' | 'payment' = 'payment'
) => {
- // Get color based on type
const color =
type === 'due'
? dueStatus[statusText] || 'neutral'
@@ -68,6 +63,11 @@ const getPillBadge = (
);
};
+const dataTypeOptions: OptionType[] = [
+ { value: 'received_date', label: 'Tanggal Terima' },
+ { value: 'po_date', label: 'Tanggal PO' },
+];
+
interface DebtSupplierTabProps {
tabId: string;
}
@@ -81,26 +81,45 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
const isAnyExportLoading =
isPdfExportLoading || isExcelExportLoading || isExcelGeneralExportLoading;
- // ===== PAGINATION STATE =====
- const [currentPage, setCurrentPage] = useState(1);
- const [pageSize, setPageSize] = useState(10);
-
- // ===== SUBMISSION STATE =====
- const [filterParams, setFilterParams] = useState({
- start_date: undefined,
- end_date: undefined,
- supplier_ids: undefined,
- filter_by: undefined,
- });
-
- // ===== DATE ERROR STATE =====
const [dateErrorShown, setDateErrorShown] = useState(false);
const [hasDateError, setHasDateError] = useState(false);
- const handleFilterModalOpenRef = useRef(() => {});
-
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[];
+ filterBy?: OptionType;
+ }>({
+ 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 {
setInputValue: setSupplierInputValue,
options: supplierOptions,
@@ -108,154 +127,149 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
loadMore: loadMoreSuppliers,
} = useSelect(SupplierApi.basePath, 'id', 'name');
- const dataTypeOptions = useMemo(
- () => [
- { value: 'received_date', label: 'Tanggal Terima' },
- { value: 'po_date', label: 'Tanggal PO' },
- ],
- []
- );
-
// ===== FORMIK SETUP =====
- const formik = useFormik({
+ const formik = useFormik({
initialValues: {
- startDate: null,
- endDate: null,
- supplierIds: null,
- filterBy: null,
+ start_date: tableFilterState.start_date,
+ end_date: tableFilterState.end_date,
+ suppliers: tableFilterState.suppliers,
+ filterBy: tableFilterState.filterBy,
},
- validationSchema: DebtSupplierFilterSchema,
onSubmit: (values) => {
- setFilterParams({
- start_date: values.startDate?.toString() || undefined,
- end_date: values.endDate?.toString() || undefined,
- supplier_ids:
- 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);
+ updateFilter('start_date', values.start_date, true);
+ updateFilter('end_date', values.end_date, true);
+ updateFilter('suppliers', values.suppliers, true);
+ updateFilter('filterBy', values.filterBy, true);
filterModal.closeModal();
},
});
- handleFilterModalOpenRef.current = () => {
- const restoredFilterBy =
- dataTypeOptions.find((opt) => opt.value === filterParams.filter_by) ||
- null;
+ const formikResetHandler = () => {
+ resetFilter();
- const supplierIdList = filterParams.supplier_ids
- ? filterParams.supplier_ids.split(',')
- : [];
- const restoredSupplierIds = supplierOptions.filter((opt) =>
- supplierIdList.includes(String(opt.value))
- );
+ setHasDateError(false);
+ if (dateErrorShown) {
+ toast.dismiss();
+ setDateErrorShown(false);
+ }
- formik.setValues({
- startDate: filterParams.start_date || null,
- endDate: filterParams.end_date || null,
- supplierIds: restoredSupplierIds.length > 0 ? restoredSupplierIds : null,
- filterBy: restoredFilterBy,
+ formik.resetForm({
+ values: {
+ start_date: '',
+ end_date: '',
+ suppliers: [],
+ filterBy: undefined,
+ },
});
- filterModal.openModal();
+
+ filterModal.closeModal();
+ };
+
+ // ===== DATE CHANGE HANDLERS =====
+ const handleStartDateChange = (e: React.ChangeEvent) => {
+ 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) => {
+ 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 =====
- const { data: debtSupplier, isLoading } = useSWR(
- () => {
- const params = {
- supplier_ids: filterParams.supplier_ids,
- filter_by: filterParams.filter_by,
- start_date: filterParams.start_date,
- end_date: filterParams.end_date,
- 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: debtSupplierResponse, isLoading } = useSWR<
+ BaseApiResponse,
+ AxiosError,
+ SWRHttpKey
+ >(
+ `${DebtSupplierApi.basePath}/debt-supplier${getTableFilterQueryString()}`,
+ httpClientFetcher
);
- const data: DebtSupplier[] = useMemo(
- () =>
- isResponseSuccess(debtSupplier)
- ? (debtSupplier?.data as unknown as DebtSupplier[]) || []
- : [],
- [debtSupplier]
- );
+ const data: DebtSupplier[] = isResponseSuccess(debtSupplierResponse)
+ ? ((debtSupplierResponse?.data as unknown as DebtSupplier[]) ?? [])
+ : [];
- const meta = useMemo(
- () =>
- isResponseSuccess(debtSupplier) && debtSupplier.meta
- ? debtSupplier.meta
- : null,
- [debtSupplier]
- );
+ const meta =
+ isResponseSuccess(debtSupplierResponse) && debtSupplierResponse.meta
+ ? debtSupplierResponse.meta
+ : null;
// ===== EXPORT DATA FETCHER =====
const debtSupplierExport = useCallback(async (): Promise<
DebtSupplier[] | null
> => {
- const params = {
- supplier_ids:
- formik.values.supplierIds && formik.values.supplierIds.length > 0
- ? formik.values.supplierIds.map((v) => String(v.value)).join(',')
- : 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 supplier_ids =
+ tableFilterState.suppliers.length > 0
+ ? tableFilterState.suppliers.map((o) => String(o.value)).join(',')
+ : undefined;
const response = await DebtSupplierApi.getDebtSupplierReport(
- params.supplier_ids,
- params.filter_by,
- params.start_date,
- params.end_date
+ supplier_ids,
+ tableFilterState.filterBy?.value,
+ tableFilterState.start_date || undefined,
+ tableFilterState.end_date || undefined,
+ 1,
+ 100
);
return isResponseSuccess(response)
? (response.data as unknown as DebtSupplier[])
: null;
- }, [
- formik.values.supplierIds,
- formik.values.startDate,
- formik.values.endDate,
- formik.values.filterBy,
- ]);
+ }, [tableFilterState]);
// ===== EXPORT HANDLERS =====
const handleExportExcel = useCallback(async () => {
setIsExcelExportLoading(true);
try {
+ const supplier_ids =
+ tableFilterState.suppliers.length > 0
+ ? tableFilterState.suppliers.map((o) => String(o.value)).join(',')
+ : undefined;
await DebtSupplierApi.exportToExcelSupplierPerSheet(
- filterParams.supplier_ids,
- filterParams.filter_by,
- filterParams.start_date,
- filterParams.end_date
+ supplier_ids,
+ tableFilterState.filterBy?.value,
+ tableFilterState.start_date || undefined,
+ tableFilterState.end_date || undefined
);
toast.success('Excel berhasil dibuat dan diunduh.');
} catch {
@@ -263,7 +277,30 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
} finally {
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 () => {
setIsPdfExportLoading(true);
@@ -279,15 +316,18 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
return;
}
+ const supplierName =
+ tableFilterState.suppliers.length > 0
+ ? tableFilterState.suppliers.map((o) => o.label).join(', ')
+ : undefined;
+
await generateDebtSupplierPDF({
data: allDataForExport,
params: {
- supplier_name: formik.values.supplierIds
- ?.map((v) => v.label)
- .join(', '),
- filter_by: formik.values.filterBy?.label,
- start_date: formik.values.startDate || undefined,
- end_date: formik.values.endDate || undefined,
+ supplier_name: supplierName,
+ filter_by: tableFilterState.filterBy?.label,
+ start_date: tableFilterState.start_date || undefined,
+ end_date: tableFilterState.end_date || undefined,
},
});
toast.success('PDF berhasil dibuat dan diunduh.');
@@ -296,131 +336,91 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
} finally {
setIsPdfExportLoading(false);
}
- }, [
- debtSupplierExport,
- formik.values.supplierIds,
- formik.values.filterBy,
- formik.values.startDate,
- formik.values.endDate,
- ]);
+ }, [debtSupplierExport, tableFilterState]);
- 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 =====
+ useEffect(() => {
+ setTabActions(
+ tabId,
+
+
- // ===== TAB ACTIONS COMPONENT =====
- const TabActions = useMemo(() => {
- return function TabActionsComponent() {
- const setTabActions = useTabActionsStore((state) => state.setTabActions);
- const clearTabActions = useTabActionsStore(
- (state) => state.clearTabActions
- );
-
- useEffect(() => {
- setTabActions(
- tabId,
-
-
handleFilterModalOpenRef.current()}
+
-
-
-
-
- }
+ 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'
>
-
-
-
-
-
- );
- }, [setTabActions]);
-
- useEffect(() => {
- return () => {
- clearTabActions(tabId);
- };
- }, [clearTabActions]);
-
- return null;
- };
+
+
+ }
+ >
+
+
+
+
+
+ );
}, [
tabId,
- filterParams,
+ setTabActions,
+ tableFilterState,
+ filterModal.openModal,
isAnyExportLoading,
handleExportExcel,
handleExportExcelGeneral,
@@ -430,24 +430,9 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
isPdfExportLoading,
]);
- const TabActionsElement = useMemo(() => , [TabActions]);
-
useEffect(() => {
- return () => {
- if (dateErrorShown) {
- toast.dismiss();
- }
- };
- }, [dateErrorShown]);
-
- useEffect(() => {
- return () => {
- if (dateErrorShown) {
- toast.dismiss();
- setDateErrorShown(false);
- }
- };
- }, [filterModal.open, dateErrorShown]);
+ return () => clearTabActions(tabId);
+ }, [tabId, clearTabActions]);
const getTableColumns = (supplier?: DebtSupplier): ColumnDef[] => [
{
@@ -662,9 +647,9 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
},
},
];
+
return (
<>
- {TabActionsElement}
{isLoading && (
@@ -693,16 +678,16 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
- 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}
/>
@@ -802,16 +787,16 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
- 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}
/>
@@ -827,23 +812,23 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => {
modalBox: 'p-0 rounded-[0.875rem] xl:max-w-4/12 max-w-sm',
}}
>
-