mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-23 23:05:46 +00:00
refactor: optimize BalanceMonitoringTab with useTableFilter persistence pattern
Replace single-select customerFilter/salesFilter with OptionType[] multi-select (customers, salesPersons, filterBy), switch SWR to httpClientFetcher with explicit type, remove PDF export, enableReinitialize, useRef modal hack, useMemo on data/meta, and useCallback on trivial handlers. Add formikResetHandler using resetFilter(). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,21 +1,22 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useMemo, useCallback, useEffect, useRef } from 'react';
|
import { useState, useMemo, useEffect } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { ColumnDef, SortingState, Updater } from '@tanstack/react-table';
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
import { FinanceApi } from '@/services/api/report/finance-report';
|
import { FinanceApi } from '@/services/api/report/finance-report';
|
||||||
import { CustomerApi } from '@/services/api/master-data';
|
import { CustomerApi } from '@/services/api/master-data';
|
||||||
import { UserApi } from '@/services/api/user';
|
import { UserApi } from '@/services/api/user';
|
||||||
import SelectInput, {
|
import { useSelect, OptionType } from '@/components/input/SelectInput';
|
||||||
useSelect,
|
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
|
||||||
OptionType,
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
} from '@/components/input/SelectInput';
|
import SelectInputCheckbox from '@/components/input/SelectInputCheckbox';
|
||||||
|
import SelectInputRadio from '@/components/input/SelectInputRadio';
|
||||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||||
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
|
import { useTabActionsStore } from '@/stores/tab-actions/tab-actions.store';
|
||||||
import Dropdown from '@/components/Dropdown';
|
|
||||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||||
import { formatCurrency, formatNumber } from '@/lib/helper';
|
import { formatCurrency, formatNumber } from '@/lib/helper';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
@@ -24,7 +25,6 @@ import { CustomerPaymentRow } from '@/types/api/report/customer-payment';
|
|||||||
import Modal, { useModal } from '@/components/Modal';
|
import Modal, { useModal } from '@/components/Modal';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import DateInput from '@/components/input/DateInput';
|
import DateInput from '@/components/input/DateInput';
|
||||||
import Pagination from '@/components/Pagination';
|
|
||||||
import Table from '@/components/Table';
|
import Table from '@/components/Table';
|
||||||
import CustomerSupplierSkeleton from '@/components/pages/report/finance/skeleton/CustomerSupplierSkeleton';
|
import CustomerSupplierSkeleton from '@/components/pages/report/finance/skeleton/CustomerSupplierSkeleton';
|
||||||
|
|
||||||
@@ -32,12 +32,15 @@ interface BalanceMonitoringTabProps {
|
|||||||
tabId: string;
|
tabId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filterByOptions: OptionType<string>[] = [
|
||||||
|
{ label: 'Tanggal Penjualan (SO Date)', value: 'sold_at' },
|
||||||
|
{ label: 'Tanggal Realisasi (Delivery Date)', value: 'realized_at' },
|
||||||
|
];
|
||||||
|
|
||||||
const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
||||||
const [isPdfExportLoading, setIsPdfExportLoading] = useState(false);
|
|
||||||
const [hasDateError, setHasDateError] = useState(false);
|
const [hasDateError, setHasDateError] = useState(false);
|
||||||
const [dateErrorShown, setDateErrorShown] = useState(false);
|
const [dateErrorShown, setDateErrorShown] = useState(false);
|
||||||
|
|
||||||
const handleFilterModalOpenRef = useRef(() => {});
|
|
||||||
const filterModal = useModal();
|
const filterModal = useModal();
|
||||||
|
|
||||||
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
const setTabActions = useTabActionsStore((state) => state.setTabActions);
|
||||||
@@ -48,20 +51,23 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
updateFilter,
|
updateFilter,
|
||||||
setPage,
|
setPage,
|
||||||
setPageSize,
|
setPageSize,
|
||||||
toQueryString,
|
toQueryString: getTableFilterQueryString,
|
||||||
|
reset: resetFilter,
|
||||||
} = useTableFilter<{
|
} = useTableFilter<{
|
||||||
start_date: string;
|
start_date: string;
|
||||||
end_date: string;
|
end_date: string;
|
||||||
customerFilter?: OptionType<number>;
|
customers: OptionType<number>[];
|
||||||
salesFilter?: OptionType<number>;
|
salesPersons: OptionType<number>[];
|
||||||
|
filterBy?: OptionType<string>;
|
||||||
sort_by: string;
|
sort_by: string;
|
||||||
order_by: string;
|
order_by: string;
|
||||||
}>({
|
}>({
|
||||||
initial: {
|
initial: {
|
||||||
start_date: '',
|
start_date: '',
|
||||||
end_date: '',
|
end_date: '',
|
||||||
customerFilter: undefined,
|
customers: [],
|
||||||
salesFilter: undefined,
|
salesPersons: [],
|
||||||
|
filterBy: undefined,
|
||||||
sort_by: '',
|
sort_by: '',
|
||||||
order_by: '',
|
order_by: '',
|
||||||
},
|
},
|
||||||
@@ -70,8 +76,9 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
pageSize: 'limit',
|
pageSize: 'limit',
|
||||||
start_date: 'start_date',
|
start_date: 'start_date',
|
||||||
end_date: 'end_date',
|
end_date: 'end_date',
|
||||||
customerFilter: 'customer_id',
|
customers: 'customer_ids',
|
||||||
salesFilter: 'sales_id',
|
salesPersons: 'sales_ids',
|
||||||
|
filterBy: 'filter_by',
|
||||||
sort_by: 'sort_by',
|
sort_by: 'sort_by',
|
||||||
order_by: 'sort_order',
|
order_by: 'sort_order',
|
||||||
},
|
},
|
||||||
@@ -79,31 +86,25 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
storeName: 'balance-monitoring-table',
|
storeName: 'balance-monitoring-table',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keep a stable ref so handleExportPDF doesn't need toQueryString as a dep
|
// const sorting: SortingState = tableFilterState.sort_by
|
||||||
const toQueryStringRef = useRef(toQueryString);
|
// ? [
|
||||||
useEffect(() => {
|
// {
|
||||||
toQueryStringRef.current = toQueryString;
|
// id: tableFilterState.sort_by,
|
||||||
});
|
// desc: tableFilterState.order_by === 'desc',
|
||||||
|
// },
|
||||||
|
// ]
|
||||||
|
// : [];
|
||||||
|
|
||||||
const sorting: SortingState = tableFilterState.sort_by
|
// const handleSortingChange = (updater: Updater<SortingState>) => {
|
||||||
? [
|
// const next = typeof updater === 'function' ? updater(sorting) : updater;
|
||||||
{
|
// if (next.length > 0) {
|
||||||
id: tableFilterState.sort_by,
|
// updateFilter('sort_by', next[0].id, true);
|
||||||
desc: tableFilterState.order_by === 'desc',
|
// updateFilter('order_by', next[0].desc ? 'desc' : 'asc', true);
|
||||||
},
|
// } else {
|
||||||
]
|
// updateFilter('sort_by', '', true);
|
||||||
: [];
|
// updateFilter('order_by', '', true);
|
||||||
|
// }
|
||||||
const handleSortingChange = (updater: Updater<SortingState>) => {
|
// };
|
||||||
const next = typeof updater === 'function' ? updater(sorting) : updater;
|
|
||||||
if (next.length > 0) {
|
|
||||||
updateFilter('sort_by', next[0].id, true);
|
|
||||||
updateFilter('order_by', next[0].desc ? 'desc' : 'asc', true);
|
|
||||||
} else {
|
|
||||||
updateFilter('sort_by', '', true);
|
|
||||||
updateFilter('order_by', '', true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
options: customerOptions,
|
options: customerOptions,
|
||||||
@@ -123,33 +124,40 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
initialValues: {
|
initialValues: {
|
||||||
start_date: tableFilterState.start_date,
|
start_date: tableFilterState.start_date,
|
||||||
end_date: tableFilterState.end_date,
|
end_date: tableFilterState.end_date,
|
||||||
customerFilter: tableFilterState.customerFilter,
|
customers: tableFilterState.customers,
|
||||||
salesFilter: tableFilterState.salesFilter,
|
salesPersons: tableFilterState.salesPersons,
|
||||||
|
filterBy: tableFilterState.filterBy,
|
||||||
},
|
},
|
||||||
enableReinitialize: true,
|
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
updateFilter('start_date', values.start_date, true);
|
updateFilter('start_date', values.start_date, true);
|
||||||
updateFilter('end_date', values.end_date, true);
|
updateFilter('end_date', values.end_date, true);
|
||||||
updateFilter('customerFilter', values.customerFilter, true);
|
updateFilter('customers', values.customers, true);
|
||||||
updateFilter('salesFilter', values.salesFilter, true);
|
updateFilter('salesPersons', values.salesPersons, true);
|
||||||
filterModal.closeModal();
|
updateFilter('filterBy', values.filterBy, true);
|
||||||
},
|
|
||||||
onReset: () => {
|
|
||||||
updateFilter('start_date', '', true);
|
|
||||||
updateFilter('end_date', '', true);
|
|
||||||
updateFilter('customerFilter', undefined, true);
|
|
||||||
updateFilter('salesFilter', undefined, true);
|
|
||||||
setHasDateError(false);
|
|
||||||
if (dateErrorShown) {
|
|
||||||
toast.dismiss();
|
|
||||||
setDateErrorShown(false);
|
|
||||||
}
|
|
||||||
filterModal.closeModal();
|
filterModal.closeModal();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
handleFilterModalOpenRef.current = () => {
|
const formikResetHandler = () => {
|
||||||
filterModal.openModal();
|
resetFilter();
|
||||||
|
|
||||||
|
setHasDateError(false);
|
||||||
|
if (dateErrorShown) {
|
||||||
|
toast.dismiss();
|
||||||
|
setDateErrorShown(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
formik.resetForm({
|
||||||
|
values: {
|
||||||
|
start_date: '',
|
||||||
|
end_date: '',
|
||||||
|
customers: [],
|
||||||
|
salesPersons: [],
|
||||||
|
filterBy: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
filterModal.closeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleStartDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -201,41 +209,26 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryString = toQueryString();
|
const { data: balanceMonitoringsResponse, isLoading } = useSWR<
|
||||||
|
BaseApiResponse<BalanceMonitoringRow[]>,
|
||||||
const { data: response, isLoading } = useSWR(queryString, (qs) =>
|
AxiosError<BaseApiResponse>,
|
||||||
FinanceApi.getBalanceMonitoringReport(
|
SWRHttpKey
|
||||||
Object.fromEntries(new URLSearchParams(qs)) as Parameters<
|
>(
|
||||||
typeof FinanceApi.getBalanceMonitoringReport
|
`${FinanceApi.basePath}/balance-monitoring${getTableFilterQueryString()}`,
|
||||||
>[0]
|
httpClientFetcher
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const data: BalanceMonitoringRow[] = useMemo(
|
const balanceMonitorings: BalanceMonitoringRow[] = isResponseSuccess(
|
||||||
() =>
|
balanceMonitoringsResponse
|
||||||
isResponseSuccess(response)
|
)
|
||||||
? ((response.data as BalanceMonitoringRow[]) ?? [])
|
? ((balanceMonitoringsResponse.data as BalanceMonitoringRow[]) ?? [])
|
||||||
: [],
|
: [];
|
||||||
[response]
|
|
||||||
);
|
|
||||||
|
|
||||||
const meta = useMemo(
|
const meta =
|
||||||
() => (isResponseSuccess(response) && response.meta ? response.meta : null),
|
isResponseSuccess(balanceMonitoringsResponse) &&
|
||||||
[response]
|
balanceMonitoringsResponse.meta
|
||||||
);
|
? balanceMonitoringsResponse.meta
|
||||||
|
: null;
|
||||||
// Stable — uses ref so toQueryString is always current without being a dep
|
|
||||||
const handleExportPDF = useCallback(async () => {
|
|
||||||
setIsPdfExportLoading(true);
|
|
||||||
try {
|
|
||||||
await FinanceApi.exportBalanceMonitoringToPDF(toQueryStringRef.current());
|
|
||||||
toast.success('PDF berhasil dibuat dan diunduh.');
|
|
||||||
} catch {
|
|
||||||
toast.error('Gagal membuat PDF. Silakan coba lagi.');
|
|
||||||
} finally {
|
|
||||||
setIsPdfExportLoading(false);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Inject tab actions directly — no nested component, no remount cycle
|
// Inject tab actions directly — no nested component, no remount cycle
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -246,85 +239,39 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
values={{
|
values={{
|
||||||
start_date: tableFilterState.start_date,
|
start_date: tableFilterState.start_date,
|
||||||
end_date: tableFilterState.end_date,
|
end_date: tableFilterState.end_date,
|
||||||
customerFilter: tableFilterState.customerFilter,
|
customers: tableFilterState.customers,
|
||||||
salesFilter: tableFilterState.salesFilter,
|
salesPersons: tableFilterState.salesPersons,
|
||||||
|
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'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<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={isPdfExportLoading}
|
|
||||||
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>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}, [
|
}, [tabId, setTabActions, tableFilterState, filterModal.openModal]);
|
||||||
tabId,
|
|
||||||
setTabActions,
|
|
||||||
isPdfExportLoading,
|
|
||||||
handleExportPDF,
|
|
||||||
tableFilterState.start_date,
|
|
||||||
tableFilterState.end_date,
|
|
||||||
tableFilterState.customerFilter,
|
|
||||||
tableFilterState.salesFilter,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => clearTabActions(tabId);
|
return () => clearTabActions(tabId);
|
||||||
}, [tabId, clearTabActions]);
|
}, [tabId, clearTabActions]);
|
||||||
|
|
||||||
const page = meta?.page || tableFilterState.page;
|
|
||||||
const pageSize = meta?.limit || tableFilterState.pageSize;
|
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
(): ColumnDef<BalanceMonitoringRow>[] => [
|
(): ColumnDef<BalanceMonitoringRow>[] => [
|
||||||
{
|
{
|
||||||
header: 'No',
|
header: 'No',
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
cell: (props) => (page - 1) * pageSize + props.row.index + 1,
|
cell: (props) =>
|
||||||
|
(tableFilterState.page - 1) * tableFilterState.pageSize +
|
||||||
|
props.row.index +
|
||||||
|
1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Customer',
|
header: 'Customer',
|
||||||
accessorKey: 'customer_name',
|
accessorKey: 'customer.name',
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
id: 'customer_name',
|
id: 'customer_name',
|
||||||
|
cell: ({ row }) => row.original.customer.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Saldo Awal',
|
header: 'Saldo Awal',
|
||||||
@@ -342,34 +289,34 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
header: 'Ekor',
|
header: 'Ekor',
|
||||||
accessorKey: 'penjualan_ayam_ekor',
|
accessorKey: 'penjualan_ayam.ekor',
|
||||||
id: 'penjualan_ayam_ekor',
|
id: 'penjualan_ayam_ekor',
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className='text-right'>
|
<div className='text-right'>
|
||||||
{formatNumber(row.original.penjualan_ayam_ekor)}
|
{formatNumber(row.original.penjualan_ayam.ekor)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Kg',
|
header: 'Kg',
|
||||||
accessorKey: 'penjualan_ayam_kg',
|
accessorKey: 'penjualan_ayam.kg',
|
||||||
id: 'penjualan_ayam_kg',
|
id: 'penjualan_ayam_kg',
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className='text-right'>
|
<div className='text-right'>
|
||||||
{formatNumber(row.original.penjualan_ayam_kg)}
|
{formatNumber(row.original.penjualan_ayam.kg)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Nominal',
|
header: 'Nominal',
|
||||||
accessorKey: 'penjualan_ayam_nominal',
|
accessorKey: 'penjualan_ayam.nominal',
|
||||||
id: 'penjualan_ayam_nominal',
|
id: 'penjualan_ayam_nominal',
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className='text-right'>
|
<div className='text-right'>
|
||||||
{formatCurrency(row.original.penjualan_ayam_nominal)}
|
{formatCurrency(row.original.penjualan_ayam.nominal)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -379,35 +326,35 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
header: 'Penjualan Telur',
|
header: 'Penjualan Telur',
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
header: 'Kuantitas',
|
header: 'Butir',
|
||||||
accessorKey: 'penjualan_telur_kuantitas',
|
accessorKey: 'penjualan_telur.butir',
|
||||||
id: 'penjualan_telur_kuantitas',
|
id: 'penjualan_telur_butir',
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className='text-right'>
|
<div className='text-right'>
|
||||||
{formatNumber(row.original.penjualan_telur_kuantitas)}
|
{formatNumber(row.original.penjualan_telur.butir)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Kg',
|
header: 'Kg',
|
||||||
accessorKey: 'penjualan_telur_kg',
|
accessorKey: 'penjualan_telur.kg',
|
||||||
id: 'penjualan_telur_kg',
|
id: 'penjualan_telur_kg',
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className='text-right'>
|
<div className='text-right'>
|
||||||
{formatNumber(row.original.penjualan_telur_kg)}
|
{formatNumber(row.original.penjualan_telur.kg)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Nominal',
|
header: 'Nominal',
|
||||||
accessorKey: 'penjualan_telur_nominal',
|
accessorKey: 'penjualan_telur.nominal',
|
||||||
id: 'penjualan_telur_nominal',
|
id: 'penjualan_telur_nominal',
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className='text-right'>
|
<div className='text-right'>
|
||||||
{formatCurrency(row.original.penjualan_telur_nominal)}
|
{formatCurrency(row.original.penjualan_telur.nominal)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -415,12 +362,12 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Penjualan Trading',
|
header: 'Penjualan Trading',
|
||||||
accessorKey: 'penjualan_trading',
|
accessorKey: 'penjualan_trading.nominal',
|
||||||
id: 'penjualan_trading',
|
id: 'penjualan_trading',
|
||||||
enableSorting: true,
|
enableSorting: true,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className='text-right'>
|
<div className='text-right'>
|
||||||
{formatCurrency(row.original.penjualan_trading)}
|
{formatCurrency(row.original.penjualan_trading.nominal)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -471,7 +418,7 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[page, pageSize]
|
[tableFilterState.page, tableFilterState.pageSize]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -483,7 +430,7 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isLoading && data.length === 0 && (
|
{!isLoading && balanceMonitorings.length === 0 && (
|
||||||
<CustomerSupplierSkeleton
|
<CustomerSupplierSkeleton
|
||||||
columns={columns as unknown as ColumnDef<CustomerPaymentRow>[]}
|
columns={columns as unknown as ColumnDef<CustomerPaymentRow>[]}
|
||||||
icon={
|
icon={
|
||||||
@@ -499,20 +446,20 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isLoading && data.length > 0 && (
|
{!isLoading && balanceMonitorings.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className='w-full overflow-x-auto'>
|
<div className='w-full overflow-x-auto'>
|
||||||
<Table
|
<Table
|
||||||
data={data}
|
data={balanceMonitorings}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pageSize={pageSize}
|
pageSize={tableFilterState.pageSize || 10}
|
||||||
page={meta?.page || 1}
|
page={tableFilterState.page || 1}
|
||||||
totalItems={meta?.total_results || 0}
|
totalItems={meta?.total_results || 0}
|
||||||
onPageChange={setPage}
|
onPageChange={setPage}
|
||||||
onPageSizeChange={setPageSize}
|
onPageSizeChange={setPageSize}
|
||||||
sorting={sorting}
|
// sorting={sorting}
|
||||||
setSorting={handleSortingChange}
|
// setSorting={handleSortingChange}
|
||||||
manualSorting
|
// manualSorting
|
||||||
className={{
|
className={{
|
||||||
containerClassName: 'w-full mb-0!',
|
containerClassName: 'w-full mb-0!',
|
||||||
tableWrapperClassName: 'overflow-x-auto',
|
tableWrapperClassName: 'overflow-x-auto',
|
||||||
@@ -524,31 +471,9 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
|
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
|
||||||
bodyColumnClassName:
|
bodyColumnClassName:
|
||||||
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
||||||
paginationClassName: 'hidden',
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{meta && (
|
|
||||||
<div className='mt-5 px-3'>
|
|
||||||
<Pagination
|
|
||||||
totalItems={meta.total_results || 0}
|
|
||||||
itemsPerPage={meta.limit || 0}
|
|
||||||
currentPage={meta.page || 0}
|
|
||||||
onPrevPage={() => setPage(Math.max(1, (meta.page || 1) - 1))}
|
|
||||||
onNextPage={() =>
|
|
||||||
setPage(
|
|
||||||
meta.total_pages && (meta.page || 1) < meta.total_pages
|
|
||||||
? (meta.page || 1) + 1
|
|
||||||
: meta.page || 1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onPageChange={setPage}
|
|
||||||
rowOptions={[10, 20, 50, 100]}
|
|
||||||
onRowChange={setPageSize}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -576,7 +501,7 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={formik.handleSubmit} onReset={formik.handleReset}>
|
<form onSubmit={formik.handleSubmit} onReset={formikResetHandler}>
|
||||||
<div className='p-4 flex flex-col gap-3'>
|
<div className='p-4 flex flex-col gap-3'>
|
||||||
<div>
|
<div>
|
||||||
<label className='block text-xs font-semibold text-base-content py-2'>
|
<label className='block text-xs font-semibold text-base-content py-2'>
|
||||||
@@ -602,16 +527,13 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SelectInput
|
<SelectInputCheckbox
|
||||||
label='Customer'
|
label='Customer'
|
||||||
placeholder='Pilih Customer'
|
placeholder='Pilih Customer'
|
||||||
options={customerOptions}
|
options={customerOptions}
|
||||||
value={formik.values.customerFilter ?? null}
|
value={formik.values.customers}
|
||||||
onChange={(val) =>
|
onChange={(val) =>
|
||||||
formik.setFieldValue(
|
formik.setFieldValue('customers', Array.isArray(val) ? val : [])
|
||||||
'customerFilter',
|
|
||||||
val as OptionType<number> | null
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
onInputChange={setCustomerInput}
|
onInputChange={setCustomerInput}
|
||||||
isLoading={isLoadingCustomers}
|
isLoading={isLoadingCustomers}
|
||||||
@@ -620,15 +542,15 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectInput
|
<SelectInputCheckbox
|
||||||
label='Sales'
|
label='Sales'
|
||||||
placeholder='Pilih Sales'
|
placeholder='Pilih Sales'
|
||||||
options={salesOptions}
|
options={salesOptions}
|
||||||
value={formik.values.salesFilter ?? null}
|
value={formik.values.salesPersons}
|
||||||
onChange={(val) =>
|
onChange={(val) =>
|
||||||
formik.setFieldValue(
|
formik.setFieldValue(
|
||||||
'salesFilter',
|
'salesPersons',
|
||||||
val as OptionType<number> | null
|
Array.isArray(val) ? val : []
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onInputChange={setSalesInput}
|
onInputChange={setSalesInput}
|
||||||
@@ -637,6 +559,21 @@ const BalanceMonitoringTab = ({ tabId }: BalanceMonitoringTabProps) => {
|
|||||||
onMenuScrollToBottom={loadMoreSales}
|
onMenuScrollToBottom={loadMoreSales}
|
||||||
className={{ wrapper: 'w-full' }}
|
className={{ wrapper: 'w-full' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SelectInputRadio
|
||||||
|
label='Filter Berdasarkan'
|
||||||
|
placeholder='Pilih Filter Berdasarkan'
|
||||||
|
options={filterByOptions}
|
||||||
|
value={formik.values.filterBy ?? null}
|
||||||
|
onChange={(val) =>
|
||||||
|
formik.setFieldValue(
|
||||||
|
'filterBy',
|
||||||
|
!Array.isArray(val) ? (val ?? undefined) : undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
isClearable
|
||||||
|
className={{ wrapper: 'w-full' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Modal Footer */}
|
{/* Modal Footer */}
|
||||||
|
|||||||
Reference in New Issue
Block a user