diff --git a/src/components/pages/finance/FinanceTable.tsx b/src/components/pages/finance/FinanceTable.tsx index 50e27ac1..2665dabb 100644 --- a/src/components/pages/finance/FinanceTable.tsx +++ b/src/components/pages/finance/FinanceTable.tsx @@ -39,6 +39,7 @@ import PopoverContent from '@/components/popover/PopoverContent'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import toast from 'react-hot-toast'; import RequirePermission from '@/components/helper/RequirePermission'; +import ButtonFilter from '@/components/helper/ButtonFilter'; import { useUiStore } from '@/stores/ui/ui.store'; import { FinanceTableFilterSchema, @@ -195,6 +196,9 @@ const FinanceTable = () => { sortBy: '', startDate: '', endDate: '', + bankNames: '', + customerNames: '', + supplierNames: '', }, paramMap: { page: 'page', @@ -207,6 +211,9 @@ const FinanceTable = () => { startDate: 'start_date', endDate: 'end_date', }, + excludeKeysFromUrl: ['bankNames', 'customerNames', 'supplierNames'], + persist: true, + storeName: 'finance-table', }); // ===== FILTER MODAL STATE ===== @@ -256,9 +263,20 @@ const FinanceTable = () => { updateFilter('sortBy', values.sort_by); updateFilter('startDate', values.start_date); updateFilter('endDate', values.end_date); + // Save display names for restoration on modal reopen + const toNames = (val: OptionType | OptionType[] | null) => + val ? (Array.isArray(val) ? val : [val]).map((o) => String(o.label)).join(',') : ''; + updateFilter('bankNames', toNames(selectedBank)); + updateFilter('customerNames', toNames(selectedCustomerId)); + updateFilter('supplierNames', toNames(selectedSupplierId)); filterModal.closeModal(); }, onReset: () => { + setSelectedTransactionType(null); + setSelectedBank(null); + setSelectedCustomerId(null); + setSelectedSupplierId(null); + setSelectedSortBy(null); updateFilter('search', ''); resetSearchValue(); updateFilter('transactionTypes', ''); @@ -268,6 +286,10 @@ const FinanceTable = () => { updateFilter('sortBy', ''); updateFilter('startDate', ''); updateFilter('endDate', ''); + updateFilter('bankNames', ''); + updateFilter('customerNames', ''); + updateFilter('supplierNames', ''); + filterModal.closeModal(); }, }); @@ -320,31 +342,6 @@ const FinanceTable = () => { }); }, [bankOptions, bankRawData]); - // ===== ACTIVE FILTERS COUNT ===== - const activeFiltersCount = useMemo(() => { - let count = 0; - - if (tableFilterState.transactionTypes) count += 1; - if (tableFilterState.bankIds) count += 1; - if (tableFilterState.customerIds) count += 1; - if (tableFilterState.supplierIds) count += 1; - if (tableFilterState.sortBy) count += 1; - if (tableFilterState.startDate) count += 1; - if (tableFilterState.endDate) count += 1; - - return count; - }, [ - tableFilterState.transactionTypes, - tableFilterState.bankIds, - tableFilterState.customerIds, - tableFilterState.supplierIds, - tableFilterState.sortBy, - tableFilterState.startDate, - tableFilterState.endDate, - ]); - - const hasFilters = activeFiltersCount > 0; - // ===== Handler ===== const searchChangeHandler = useCallback( (e: React.ChangeEvent) => { @@ -469,28 +466,73 @@ const FinanceTable = () => { }; const handleFilterModalOpen = () => { + // Restore transaction types from stored comma-separated IDs + const txIds = tableFilterState.transactionTypes + ? tableFilterState.transactionTypes.split(',') + : []; + const restoredTxTypes = FINANCE_TRANSACTION_TYPE_OPTIONS.filter((opt) => + txIds.includes(String(opt.value)) + ); + setSelectedTransactionType(restoredTxTypes.length ? restoredTxTypes : null); + + // Restore banks from stored IDs and names + const bankIdList = tableFilterState.bankIds + ? tableFilterState.bankIds.split(',') + : []; + const bankNameList = tableFilterState.bankNames + ? tableFilterState.bankNames.split(',') + : []; + const restoredBanks = bankIdList.map((id, i) => ({ + value: id, + label: bankNameList[i] || id, + })); + setSelectedBank(restoredBanks.length ? restoredBanks : null); + + // Restore customers from stored IDs and names + const customerIdList = tableFilterState.customerIds + ? tableFilterState.customerIds.split(',') + : []; + const customerNameList = tableFilterState.customerNames + ? tableFilterState.customerNames.split(',') + : []; + const restoredCustomers = customerIdList.map((id, i) => ({ + value: id, + label: customerNameList[i] || id, + })); + setSelectedCustomerId(restoredCustomers.length ? restoredCustomers : null); + + // Restore suppliers from stored IDs and names + const supplierIdList = tableFilterState.supplierIds + ? tableFilterState.supplierIds.split(',') + : []; + const supplierNameList = tableFilterState.supplierNames + ? tableFilterState.supplierNames.split(',') + : []; + const restoredSuppliers = supplierIdList.map((id, i) => ({ + value: id, + label: supplierNameList[i] || id, + })); + setSelectedSupplierId(restoredSuppliers.length ? restoredSuppliers : null); + + // Restore sort by + const restoredSortBy = + sortByOptions.find((opt) => String(opt.value) === tableFilterState.sortBy) || + null; + setSelectedSortBy(restoredSortBy); + + // Restore formik values + filterFormik.setValues({ + search: tableFilterState.search || '', + transaction_types: tableFilterState.transactionTypes || '', + bank_ids: tableFilterState.bankIds || '', + customer_ids: tableFilterState.customerIds || '', + supplier_ids: tableFilterState.supplierIds || '', + sort_by: tableFilterState.sortBy || '', + start_date: tableFilterState.startDate || '', + end_date: tableFilterState.endDate || '', + }); + filterModal.openModal(); - filterFormik.validateForm(); - }; - - const resetFilterHandler = () => { - setSelectedTransactionType(null); - setSelectedBank(null); - setSelectedCustomerId(null); - setSelectedSupplierId(null); - setSelectedSortBy(null); - - filterFormik.resetForm(); - - updateFilter('search', ''); - resetSearchValue(); - updateFilter('transactionTypes', ''); - updateFilter('bankIds', ''); - updateFilter('customerIds', ''); - updateFilter('supplierIds', ''); - updateFilter('sortBy', ''); - updateFilter('startDate', ''); - updateFilter('endDate', ''); }; const confirmationModalDeleteClickHandler = async () => { @@ -687,25 +729,19 @@ const FinanceTable = () => { }} /> - + className='px-3 py-2.5' + /> @@ -874,19 +910,9 @@ const FinanceTable = () => { {/* Modal Footer */}
diff --git a/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx b/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx index ed34efc2..1ca68235 100644 --- a/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx +++ b/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx @@ -119,6 +119,8 @@ const InventoryAdjustmentTable = () => { productFilter: '', warehouseFilter: '', transactionTypeFilter: '', + productName: '', + warehouseName: '', }, paramMap: { page: 'page', @@ -131,6 +133,9 @@ const InventoryAdjustmentTable = () => { warehouseFilter: 'warehouse_id', transactionTypeFilter: 'transaction_type', }, + excludeKeysFromUrl: ['productName', 'warehouseName'], + persist: true, + storeName: 'inventory-adjustment-table', }); // ===== FILTER MODAL STATE ===== @@ -140,14 +145,16 @@ const InventoryAdjustmentTable = () => { const formik = useFormik({ initialValues: { product_id: null, - warehouse: null, + warehouse_id: null, transaction_type: null, }, validationSchema: AdjustmentFilterSchema, onSubmit: (values, { setSubmitting }) => { updateFilter('productFilter', values.product_id || ''); - updateFilter('warehouseFilter', String(values.warehouse?.value) || ''); + updateFilter('warehouseFilter', values.warehouse_id || ''); updateFilter('transactionTypeFilter', values.transaction_type || ''); + updateFilter('productName', productIdValue?.label ? String(productIdValue.label) : ''); + updateFilter('warehouseName', warehouseIdValue?.label ? String(warehouseIdValue.label) : ''); filterModal.closeModal(); setSubmitting(false); }, @@ -155,6 +162,9 @@ const InventoryAdjustmentTable = () => { updateFilter('productFilter', ''); updateFilter('warehouseFilter', ''); updateFilter('transactionTypeFilter', ''); + updateFilter('productName', ''); + updateFilter('warehouseName', ''); + filterModal.closeModal(); }, }); @@ -205,7 +215,8 @@ const InventoryAdjustmentTable = () => { const handleFilterWarehouseChange = ( val: OptionType | OptionType[] | null ) => { - formik.setFieldValue('warehouse', val); + const warehouse = val as OptionType | null; + formik.setFieldValue('warehouse_id', warehouse?.value ? String(warehouse.value) : null); }; const handleFilterTransactionTypeChange = useCallback( @@ -220,12 +231,27 @@ const InventoryAdjustmentTable = () => { // ===== FILTER HELPERS ===== const productIdValue = useMemo(() => { if (!formik.values.product_id) return null; - return ( - productOptions.find( - (opt) => String(opt.value) === formik.values.product_id - ) || null + const found = productOptions.find( + (opt) => String(opt.value) === formik.values.product_id ); - }, [formik.values.product_id, productOptions]); + if (found) return found; + if (tableFilterState.productName) { + return { value: formik.values.product_id, label: tableFilterState.productName }; + } + return null; + }, [formik.values.product_id, productOptions, tableFilterState.productName]); + + const warehouseIdValue = useMemo(() => { + if (!formik.values.warehouse_id) return null; + const found = warehouseOptions.find( + (opt) => String(opt.value) === formik.values.warehouse_id + ); + if (found) return found; + if (tableFilterState.warehouseName) { + return { value: formik.values.warehouse_id, label: tableFilterState.warehouseName }; + } + return null; + }, [formik.values.warehouse_id, warehouseOptions, tableFilterState.warehouseName]); const transactionTypeValue = useMemo(() => { if (!formik.values.transaction_type) return null; @@ -238,8 +264,12 @@ const InventoryAdjustmentTable = () => { // ===== HANDLE FILTER MODAL OPEN ===== const handleFilterModalOpen = () => { + formik.setValues({ + product_id: tableFilterState.productFilter || null, + warehouse_id: tableFilterState.warehouseFilter || null, + transaction_type: tableFilterState.transactionTypeFilter || null, + }); filterModal.openModal(); - formik.validateForm(); }; const { @@ -507,6 +537,8 @@ const InventoryAdjustmentTable = () => { 'productSort', 'warehouseSort', 'stockSort', + 'productName', + 'warehouseName', ]} onClick={handleFilterModalOpen} className='px-3 py-2.5' @@ -608,7 +640,7 @@ const InventoryAdjustmentTable = () => { label='Gudang' placeholder='Pilih Gudang' options={warehouseOptions} - value={formik.values.warehouse} + value={warehouseIdValue} onChange={handleFilterWarehouseChange} onInputChange={setWarehouseInputValue} isLoading={isLoadingWarehouseOptions} @@ -630,13 +662,9 @@ const InventoryAdjustmentTable = () => { {/* Modal Footer */}
diff --git a/src/components/pages/inventory/adjustment/filter/AdjustmentFilter.ts b/src/components/pages/inventory/adjustment/filter/AdjustmentFilter.ts index e4015e07..4568618f 100644 --- a/src/components/pages/inventory/adjustment/filter/AdjustmentFilter.ts +++ b/src/components/pages/inventory/adjustment/filter/AdjustmentFilter.ts @@ -1,5 +1,4 @@ import { string, object } from 'yup'; -import { OptionType } from '@/components/input/SelectInput'; export const AdjustmentFilterSchema = object().shape({ product_id: string().nullable(), @@ -9,6 +8,6 @@ export const AdjustmentFilterSchema = object().shape({ export type AdjustmentFilterType = { product_id: string | null; + warehouse_id: string | null; transaction_type: string | null; - warehouse: OptionType | null; }; diff --git a/src/components/pages/inventory/movement/MovementTable.tsx b/src/components/pages/inventory/movement/MovementTable.tsx index 2b6f11e6..d2a808d3 100644 --- a/src/components/pages/inventory/movement/MovementTable.tsx +++ b/src/components/pages/inventory/movement/MovementTable.tsx @@ -122,6 +122,8 @@ const MovementTable = () => { search: '', productFilter: '', warehouseFilter: '', + productName: '', + warehouseName: '', }, paramMap: { page: 'page', @@ -129,6 +131,9 @@ const MovementTable = () => { productFilter: 'product_id', warehouseFilter: 'warehouse_id', }, + excludeKeysFromUrl: ['productName', 'warehouseName'], + persist: true, + storeName: 'movement-table', }); // ===== FILTER MODAL STATE ===== @@ -144,12 +149,17 @@ const MovementTable = () => { onSubmit: (values, { setSubmitting }) => { updateFilter('productFilter', values.product_id || ''); updateFilter('warehouseFilter', values.warehouse_id || ''); + updateFilter('productName', productIdValue?.label ? String(productIdValue.label) : ''); + updateFilter('warehouseName', warehouseIdValue?.label ? String(warehouseIdValue.label) : ''); filterModal.closeModal(); setSubmitting(false); }, onReset: () => { updateFilter('productFilter', ''); updateFilter('warehouseFilter', ''); + updateFilter('productName', ''); + updateFilter('warehouseName', ''); + filterModal.closeModal(); }, }); @@ -201,26 +211,35 @@ const MovementTable = () => { // ===== FILTER HELPERS ===== const productIdValue = useMemo(() => { if (!formik.values.product_id) return null; - return ( - productOptions.find( - (opt) => String(opt.value) === formik.values.product_id - ) || null + const found = productOptions.find( + (opt) => String(opt.value) === formik.values.product_id ); - }, [formik.values.product_id, productOptions]); + if (found) return found; + if (tableFilterState.productName) { + return { value: formik.values.product_id, label: tableFilterState.productName }; + } + return null; + }, [formik.values.product_id, productOptions, tableFilterState.productName]); const warehouseIdValue = useMemo(() => { if (!formik.values.warehouse_id) return null; - return ( - warehouseOptions.find( - (opt) => String(opt.value) === formik.values.warehouse_id - ) || null + const found = warehouseOptions.find( + (opt) => String(opt.value) === formik.values.warehouse_id ); - }, [formik.values.warehouse_id, warehouseOptions]); + if (found) return found; + if (tableFilterState.warehouseName) { + return { value: formik.values.warehouse_id, label: tableFilterState.warehouseName }; + } + return null; + }, [formik.values.warehouse_id, warehouseOptions, tableFilterState.warehouseName]); // ===== HANDLE FILTER MODAL OPEN ===== const handleFilterModalOpen = () => { + formik.setValues({ + product_id: tableFilterState.productFilter || null, + warehouse_id: tableFilterState.warehouseFilter || null, + }); filterModal.openModal(); - formik.validateForm(); }; const [sorting, setSorting] = useState([]); @@ -384,7 +403,7 @@ const MovementTable = () => { @@ -489,13 +508,9 @@ const MovementTable = () => { {/* Modal Footer */}
diff --git a/src/components/pages/inventory/product/InventoryProductTable.tsx b/src/components/pages/inventory/product/InventoryProductTable.tsx index 21ded2bc..ad712e24 100644 --- a/src/components/pages/inventory/product/InventoryProductTable.tsx +++ b/src/components/pages/inventory/product/InventoryProductTable.tsx @@ -4,17 +4,25 @@ import Button from '@/components/Button'; import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import Table from '@/components/Table'; import RequirePermission from '@/components/helper/RequirePermission'; +import ButtonFilter from '@/components/helper/ButtonFilter'; +import Modal, { useModal } from '@/components/Modal'; +import SelectInput, { useSelect } from '@/components/input/SelectInput'; +import { OptionType } from '@/components/input/SelectInput'; import { isResponseSuccess } from '@/lib/api-helper'; import { cn, formatCurrency, formatNumber } from '@/lib/helper'; import { InventoryProductApi } from '@/services/api/inventory'; +import { ProductCategoryApi } from '@/services/api/master-data'; import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useUiStore } from '@/stores/ui/ui.store'; import { InventoryProduct } from '@/types/api/inventory/product'; +import { ProductCategory } from '@/types/api/master-data/product-category'; import { Icon } from '@iconify/react'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { ChangeEventHandler, useEffect, useMemo, useState } from 'react'; import { usePathname } from 'next/navigation'; import useSWR from 'swr'; +import { useFormik } from 'formik'; +import { object, string } from 'yup'; import PopoverButton from '@/components/popover/PopoverButton'; import PopoverContent from '@/components/popover/PopoverContent'; import InventoryProductTableSkeleton from '@/components/pages/inventory/product/skeleton/InventoryProductTableSkeleton'; @@ -83,13 +91,76 @@ const InventoryProductTable = () => { } = useTableFilter({ initial: { search: '', + categoryFilter: '', + categoryName: '', }, paramMap: { page: 'page', pageSize: 'limit', + categoryFilter: 'product_category_id', + }, + excludeKeysFromUrl: ['categoryName'], + persist: true, + storeName: 'inventory-product-table', + }); + + // ===== FILTER MODAL STATE ===== + const filterModal = useModal(); + + // ===== FORMIK SETUP ===== + const formik = useFormik<{ category_id: string | null }>({ + initialValues: { category_id: null }, + validationSchema: object().shape({ category_id: string().nullable() }), + onSubmit: (values, { setSubmitting }) => { + updateFilter('categoryFilter', values.category_id || ''); + updateFilter('categoryName', categoryIdValue?.label ? String(categoryIdValue.label) : ''); + filterModal.closeModal(); + setSubmitting(false); + }, + onReset: () => { + updateFilter('categoryFilter', ''); + updateFilter('categoryName', ''); + filterModal.closeModal(); }, }); + // ===== CATEGORY OPTIONS ===== + const { + setInputValue: setCategoryInputValue, + options: categoryOptions, + isLoadingOptions: isLoadingCategoryOptions, + loadMore: loadMoreCategories, + } = useSelect( + filterModal.open ? ProductCategoryApi.basePath : null, + 'id', + 'name', + 'search' + ); + + // ===== FILTER HELPERS ===== + const categoryIdValue = useMemo(() => { + if (!formik.values.category_id) return null; + const found = categoryOptions.find( + (opt) => String(opt.value) === formik.values.category_id + ); + if (found) return found; + if (tableFilterState.categoryName) { + return { value: formik.values.category_id, label: tableFilterState.categoryName }; + } + return null; + }, [formik.values.category_id, categoryOptions, tableFilterState.categoryName]); + + // ===== HANDLE FILTER MODAL OPEN ===== + const handleFilterModalOpen = () => { + formik.setValues({ category_id: tableFilterState.categoryFilter || null }); + filterModal.openModal(); + }; + + const handleFilterCategoryChange = (val: OptionType | OptionType[] | null) => { + const category = val as OptionType | null; + formik.setFieldValue('category_id', category?.value ? String(category.value) : null); + }; + const [sorting, setSorting] = useState([]); const { data: inventoryProducts, isLoading } = useSWR( @@ -182,6 +253,7 @@ const InventoryProductTable = () => { ); return ( + <>
{/* Header Section */}
@@ -199,7 +271,7 @@ const InventoryProductTable = () => {
- {/* Search */} + {/* Search and Filter */}
{ 'placeholder:font-semibold placeholder:text-base-content/50', }} /> +
@@ -272,6 +350,62 @@ const InventoryProductTable = () => { )}
+ + {/* Filter Modal */} + +
+
+ +

Filter Data

+
+ +
+
+
+ +
+
+ + +
+
+
+ ); }; diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index e3018e38..1e6145f3 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -172,6 +172,9 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { kandang_id: '', category: '', period: '', + area_name: '', + location_name: '', + kandang_name: '', }, paramMap: { page: 'page', @@ -183,7 +186,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { category: 'category', period: 'period', }, - + excludeKeysFromUrl: ['area_name', 'location_name', 'kandang_name'], persist: true, storeName: 'project-flock-table', }); @@ -259,6 +262,9 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { updateFilter('kandang_id', values.kandang_id || ''); updateFilter('category', values.category || ''); updateFilter('period', values.period || ''); + updateFilter('area_name', areaValue?.label ? String(areaValue.label) : ''); + updateFilter('location_name', locationValue?.label ? String(locationValue.label) : ''); + updateFilter('kandang_name', kandangValue?.label ? String(kandangValue.label) : ''); filterModal.closeModal(); setSubmitting(false); }, @@ -268,6 +274,9 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { updateFilter('kandang_id', ''); updateFilter('category', ''); updateFilter('period', ''); + updateFilter('area_name', ''); + updateFilter('location_name', ''); + updateFilter('kandang_name', ''); setFilterAreaId(undefined); setFilterLocationId(undefined); filterModal.closeModal(); @@ -320,29 +329,37 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { // ===== FILTER HELPERS ===== const areaValue = useMemo(() => { if (!formik.values.area_id) return null; - return ( - areaOptions.find((opt) => String(opt.value) === formik.values.area_id) || - null - ); - }, [formik.values.area_id, areaOptions]); + const found = areaOptions.find((opt) => String(opt.value) === formik.values.area_id); + if (found) return found; + if (tableFilterState.area_name) { + return { value: formik.values.area_id, label: tableFilterState.area_name }; + } + return null; + }, [formik.values.area_id, areaOptions, tableFilterState.area_name]); const locationValue = useMemo(() => { if (!formik.values.location_id) return null; - return ( - locationOptions.find( - (opt) => String(opt.value) === formik.values.location_id - ) || null + const found = locationOptions.find( + (opt) => String(opt.value) === formik.values.location_id ); - }, [formik.values.location_id, locationOptions]); + if (found) return found; + if (tableFilterState.location_name) { + return { value: formik.values.location_id, label: tableFilterState.location_name }; + } + return null; + }, [formik.values.location_id, locationOptions, tableFilterState.location_name]); const kandangValue = useMemo(() => { if (!formik.values.kandang_id) return null; - return ( - kandangOptions.find( - (opt) => String(opt.value) === formik.values.kandang_id - ) || null + const found = kandangOptions.find( + (opt) => String(opt.value) === formik.values.kandang_id ); - }, [formik.values.kandang_id, kandangOptions]); + if (found) return found; + if (tableFilterState.kandang_name) { + return { value: formik.values.kandang_id, label: tableFilterState.kandang_name }; + } + return null; + }, [formik.values.kandang_id, kandangOptions, tableFilterState.kandang_name]); const categoryValue = useMemo(() => { if (!formik.values.category) return null; @@ -967,7 +984,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { diff --git a/src/components/pages/production/transfer-to-laying/TransferToLayingFilterModal.tsx b/src/components/pages/production/transfer-to-laying/TransferToLayingFilterModal.tsx index b00af1eb..e0ed8fc8 100644 --- a/src/components/pages/production/transfer-to-laying/TransferToLayingFilterModal.tsx +++ b/src/components/pages/production/transfer-to-laying/TransferToLayingFilterModal.tsx @@ -13,7 +13,6 @@ import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; import { OptionType, useSelect } from '@/components/input/SelectInput'; import { ProjectFlockApi } from '@/services/api/production'; import { Flock } from '@/types/api/master-data/flock'; -import { TransferToLayingFilter } from '@/types/api/production/transfer-to-laying'; import { TransferToLayingFilterSchema, TransferToLayingFilterValues, @@ -21,12 +20,14 @@ import { interface TransferToLayingFilterModal { ref: RefObject; - onSubmit?: (values: TransferToLayingFilter) => void; + initialValues?: Partial; + onSubmit?: (values: TransferToLayingFilterValues) => void; onReset?: () => void; } const TransferToLayingFilterModal = ({ ref, + initialValues: initialValuesProp, onSubmit, onReset, }: TransferToLayingFilterModal) => { @@ -86,28 +87,16 @@ const TransferToLayingFilterModal = ({ const formik = useFormik({ initialValues: { - startDate: '', - endDate: '', - flockSource: [], - flockDestination: [], - status: [], + startDate: initialValuesProp?.startDate ?? '', + endDate: initialValuesProp?.endDate ?? '', + flockSource: initialValuesProp?.flockSource ?? [], + flockDestination: initialValuesProp?.flockDestination ?? [], + status: initialValuesProp?.status ?? [], }, + enableReinitialize: true, validationSchema: TransferToLayingFilterSchema, onSubmit: async (values) => { - const formattedValues = { - ...values, - flockSource: values.flockSource - ? (values.flockSource as OptionType[]).map((item) => item.value) - : [], - flockDestination: values.flockDestination - ? (values.flockDestination as OptionType[]).map((item) => item.value) - : [], - status: values.status - ? (values.status as OptionType[]).map((item) => item.value) - : [], - }; - - onSubmit?.(formattedValues as TransferToLayingFilter); + onSubmit?.(values); closeModalHandler(); }, onReset: () => { diff --git a/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx b/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx index e46cd476..3891cd4a 100644 --- a/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx +++ b/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx @@ -1,6 +1,6 @@ 'use client'; -import { ChangeEventHandler, useEffect, useState } from 'react'; +import { ChangeEventHandler, useEffect, useMemo, useState } from 'react'; import { usePathname } from 'next/navigation'; import { useUiStore } from '@/stores/ui/ui.store'; import useSWR from 'swr'; @@ -26,10 +26,9 @@ import TransferToLayingFilterModal from '@/components/pages/production/transfer- import TransferToLayingConfirmationModal from '@/components/pages/production/transfer-to-laying/TransferToLayingConfirmationModal'; import TransferToLayingTableSkeleton from '@/components/pages/production/transfer-to-laying/skeleton/TransferToLayingTableSkeleton'; -import { - TransferToLaying, - TransferToLayingFilter, -} from '@/types/api/production/transfer-to-laying'; +import { TransferToLaying } from '@/types/api/production/transfer-to-laying'; +import { TransferToLayingFilterValues } from '@/components/pages/production/transfer-to-laying/filter/TransferToLayingFilter'; +import { OptionType } from '@/components/input/SelectInput'; import { TransferToLayingApi } from '@/services/api/production/transfer-to-laying'; import { cn, formatDate, formatNumber } from '@/lib/helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; @@ -142,6 +141,8 @@ const TransferToLayingsTable = () => { status: '', filter_by: '', sort_by: '', + flockSourceNames: '', + flockDestinationNames: '', }, paramMap: { page: 'page', @@ -154,6 +155,9 @@ const TransferToLayingsTable = () => { filter_by: 'filter_by', sort_by: 'sort_by', }, + excludeKeysFromUrl: ['flockSourceNames', 'flockDestinationNames'], + persist: true, + storeName: 'transfer-to-laying-table', }); const { @@ -431,12 +435,72 @@ const TransferToLayingsTable = () => { updateFilter('search', e.target.value); }; - const filterSubmitHandler = (values: TransferToLayingFilter) => { - updateFilter('startDate', values.startDate); - updateFilter('endDate', values.endDate); - updateFilter('flockSource', values.flockSource.join(',')); - updateFilter('flockDestination', values.flockDestination.join(',')); - updateFilter('status', values.status.join(',')); + const STATUS_FILTER_OPTIONS = [ + { value: 'PENDING', label: 'Pengajuan' }, + { value: 'APPROVED', label: 'Disetujui' }, + { value: 'REJECTED', label: 'Ditolak' }, + ]; + + const filterModalInitialValues = useMemo(() => { + const flockSourceIds = tableFilterState.flockSource + ? tableFilterState.flockSource.split(',') + : []; + const flockSourceNameList = tableFilterState.flockSourceNames + ? tableFilterState.flockSourceNames.split(',') + : []; + const flockSourceOptions = flockSourceIds.filter(Boolean).map((id, i) => ({ + value: parseInt(id), + label: flockSourceNameList[i] || id, + })); + + const flockDestIds = tableFilterState.flockDestination + ? tableFilterState.flockDestination.split(',') + : []; + const flockDestNameList = tableFilterState.flockDestinationNames + ? tableFilterState.flockDestinationNames.split(',') + : []; + const flockDestOptions = flockDestIds.filter(Boolean).map((id, i) => ({ + value: parseInt(id), + label: flockDestNameList[i] || id, + })); + + const statusIds = tableFilterState.status + ? tableFilterState.status.split(',') + : []; + const statusOptions = statusIds.filter(Boolean).map((id) => { + const found = STATUS_FILTER_OPTIONS.find((opt) => opt.value === id); + return found || { value: id, label: id }; + }); + + return { + startDate: tableFilterState.startDate || '', + endDate: tableFilterState.endDate || '', + flockSource: flockSourceOptions, + flockDestination: flockDestOptions, + status: statusOptions, + }; + }, [ + tableFilterState.startDate, + tableFilterState.endDate, + tableFilterState.flockSource, + tableFilterState.flockDestination, + tableFilterState.status, + tableFilterState.flockSourceNames, + tableFilterState.flockDestinationNames, + ]); + + const filterSubmitHandler = (values: TransferToLayingFilterValues) => { + const flockSourceOpts = (values.flockSource as OptionType[]) || []; + const flockDestOpts = (values.flockDestination as OptionType[]) || []; + const statusOpts = (values.status as OptionType[]) || []; + + updateFilter('startDate', values.startDate || ''); + updateFilter('endDate', values.endDate || ''); + updateFilter('flockSource', flockSourceOpts.map((o) => String(o.value)).join(',')); + updateFilter('flockDestination', flockDestOpts.map((o) => String(o.value)).join(',')); + updateFilter('status', statusOpts.map((o) => String(o.value)).join(',')); + updateFilter('flockSourceNames', flockSourceOpts.map((o) => String(o.label)).join(',')); + updateFilter('flockDestinationNames', flockDestOpts.map((o) => String(o.label)).join(',')); }; const filterResetHandler = () => { @@ -445,6 +509,8 @@ const TransferToLayingsTable = () => { updateFilter('flockSource', ''); updateFilter('flockDestination', ''); updateFilter('status', ''); + updateFilter('flockSourceNames', ''); + updateFilter('flockDestinationNames', ''); }; const exportToExcelHandler = async () => { @@ -558,6 +624,8 @@ const TransferToLayingsTable = () => { 'search', 'filter_by', 'sort_by', + 'flockSourceNames', + 'flockDestinationNames', ]} fieldGroups={[['startDate', 'endDate']]} onClick={filterModal.openModal} @@ -670,6 +738,7 @@ const TransferToLayingsTable = () => { diff --git a/src/components/pages/report/expense/tab/ReportDepreciationFilterModal.tsx b/src/components/pages/report/expense/tab/ReportDepreciationFilterModal.tsx index 91e5f536..4c4a814e 100644 --- a/src/components/pages/report/expense/tab/ReportDepreciationFilterModal.tsx +++ b/src/components/pages/report/expense/tab/ReportDepreciationFilterModal.tsx @@ -1,6 +1,6 @@ 'use client'; -import { RefObject, useMemo, useState } from 'react'; +import { RefObject, useEffect, useMemo, useState } from 'react'; import { useFormik } from 'formik'; import * as yup from 'yup'; @@ -60,6 +60,11 @@ const ReportDepreciationFilterModal = ({ string | undefined >(initialValues?.location_id || undefined); + useEffect(() => { + setSelectedAreaId(initialValues?.area_id || undefined); + setSelectedLocationId(initialValues?.location_id || undefined); + }, [initialValues?.area_id, initialValues?.location_id]); + const closeModalHandler = () => { ref.current?.close(); }; @@ -97,6 +102,7 @@ const ReportDepreciationFilterModal = ({ const formik = useFormik({ initialValues: initialValues || defaultInitialValues, + enableReinitialize: true, validationSchema: ReportDepreciationFilterSchema, onSubmit: async (values) => { onSubmit?.(values); diff --git a/src/components/pages/report/expense/tab/ReportExpenseTab.tsx b/src/components/pages/report/expense/tab/ReportExpenseTab.tsx index fab616e0..78627fc7 100644 --- a/src/components/pages/report/expense/tab/ReportExpenseTab.tsx +++ b/src/components/pages/report/expense/tab/ReportExpenseTab.tsx @@ -126,8 +126,35 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => { }); handleFilterModalOpenRef.current = () => { + const restoredLocation = filterParams.location_id + ? locationOptions.find((opt) => String(opt.value) === filterParams.location_id) || + { value: filterParams.location_id, label: filterParams.location_id } + : null; + const restoredSupplier = filterParams.supplier_id + ? supplierOptions.find((opt) => String(opt.value) === filterParams.supplier_id) || + { value: filterParams.supplier_id, label: filterParams.supplier_id } + : null; + const restoredKandang = filterParams.kandang_id + ? projectFlockKandangOptions.find((opt) => String(opt.value) === filterParams.kandang_id) || + { value: filterParams.kandang_id, label: filterParams.kandang_id } + : null; + const restoredNonstock = filterParams.nonstock_id + ? nonstockOptions.find((opt) => String(opt.value) === filterParams.nonstock_id) || + { value: filterParams.nonstock_id, label: filterParams.nonstock_id } + : null; + const restoredCategory = filterParams.category + ? categoryOptions.find((opt) => opt.value === filterParams.category) || null + : null; + + formik.setValues({ + location_id: restoredLocation, + supplier_id: restoredSupplier, + kandang_id: restoredKandang, + nonstock_id: restoredNonstock, + realization_date: filterParams.realization_date || null, + category: restoredCategory, + }); filterModal.openModal(); - formik.validateForm(); }; // ===== OPTIONS ===== diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index c1428119..8ca4a3ec 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -38,6 +38,7 @@ import CustomerSupplierSkeleton from '@/components/pages/report/finance/skeleton import { OptionType } from '@/components/table/TableRowSizeSelector'; import { Color } from '@/types/theme'; import ButtonFilter from '@/components/helper/ButtonFilter'; +import Pagination from '@/components/Pagination'; interface CustomerPaymentTabProps { tabId: string; @@ -58,7 +59,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => { // ===== PAGINATION STATE ===== const [currentPage, setCurrentPage] = useState(1); - const [pageSize] = useState(10); + const [pageSize, setPageSize] = useState(10); // ===== SUBMISSION STATE ===== const [filterParams, setFilterParams] = useState({}); @@ -117,8 +118,13 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => { }); 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, + }); filterModal.openModal(); - formik.validateForm(); }; const getPaymentStatusBadgeColor = (notes: string): Color => { @@ -249,6 +255,14 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => { [customerPayment] ); + const meta = useMemo( + () => + isResponseSuccess(customerPayment) && customerPayment.meta + ? customerPayment.meta + : null, + [customerPayment] + ); + // ===== EXPORT DATA FETCHER ===== const customerPaymentExport = useCallback(async (): Promise< CustomerPaymentReport[] | null @@ -717,6 +731,29 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => { /> )} + {!isLoading && data.length > 0 && meta && ( +
+ + setCurrentPage((curr) => (curr > 1 ? curr - 1 : curr)) + } + onNextPage={() => + setCurrentPage((curr) => + meta.total_pages && curr < meta.total_pages + ? curr + 1 + : curr + ) + } + onPageChange={(pageNumber) => setCurrentPage(pageNumber)} + rowOptions={[10, 20, 50, 100]} + onRowChange={setPageSize} + /> +
+ )} + {!isLoading && data.length > 0 && data.map((customerReport) => { diff --git a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx index a425a66b..c9a772b4 100644 --- a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx +++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx @@ -1,6 +1,7 @@ import Button from '@/components/Button'; import Card from '@/components/Card'; import Dropdown from '@/components/Dropdown'; +import Pagination from '@/components/Pagination'; import DateInput from '@/components/input/DateInput'; import { OptionType, useSelect } from '@/components/input/SelectInput'; import Modal, { useModal } from '@/components/Modal'; @@ -78,6 +79,10 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { const [isExcelExportLoading, setIsExcelExportLoading] = useState(false); const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading; + // ===== PAGINATION STATE ===== + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + // ===== SUBMISSION STATE ===== const [filterParams, setFilterParams] = useState({ start_date: undefined, @@ -128,7 +133,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { filter_by: values.filterBy?.value?.toString() || undefined, }); filterModal.closeModal(); - // setIsSubmitted(true); + setCurrentPage(1); }, onReset: () => { setFilterParams({ @@ -137,14 +142,29 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { supplier_ids: undefined, filter_by: undefined, }); - // setIsSubmitted(false); + setCurrentPage(1); filterModal.closeModal(); }, }); handleFilterModalOpenRef.current = () => { + const restoredFilterBy = + dataTypeOptions.find((opt) => opt.value === filterParams.filter_by) || null; + + const supplierIdList = filterParams.supplier_ids + ? filterParams.supplier_ids.split(',') + : []; + const restoredSupplierIds = supplierOptions.filter((opt) => + supplierIdList.includes(String(opt.value)) + ); + + formik.setValues({ + startDate: filterParams.start_date || null, + endDate: filterParams.end_date || null, + supplierIds: restoredSupplierIds.length > 0 ? restoredSupplierIds : null, + filterBy: restoredFilterBy, + }); filterModal.openModal(); - formik.validateForm(); }; // ===== DATA FETCHING ===== @@ -155,6 +175,8 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { filter_by: filterParams.filter_by, start_date: filterParams.start_date, end_date: filterParams.end_date, + page: currentPage, + limit: pageSize, }; return ['debt-supplier-report', params]; @@ -164,7 +186,9 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { params.supplier_ids, params.filter_by, params.start_date, - params.end_date + params.end_date, + params.page, + params.limit ) ); @@ -176,6 +200,14 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { [debtSupplier] ); + const meta = useMemo( + () => + isResponseSuccess(debtSupplier) && debtSupplier.meta + ? debtSupplier.meta + : null, + [debtSupplier] + ); + // ===== EXPORT DATA FETCHER ===== const debtSupplierExport = useCallback(async (): Promise< DebtSupplier[] | null @@ -630,6 +662,29 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { /> )} + {!isLoading && data.length > 0 && meta && ( +
+ + setCurrentPage((curr) => (curr > 1 ? curr - 1 : curr)) + } + onNextPage={() => + setCurrentPage((curr) => + meta.total_pages && curr < meta.total_pages + ? curr + 1 + : curr + ) + } + onPageChange={(pageNumber) => setCurrentPage(pageNumber)} + rowOptions={[10, 20, 50, 100]} + onRowChange={setPageSize} + /> +
+ )} + {!isLoading && data.length > 0 && data.map((supplierReport) => { diff --git a/src/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab.tsx b/src/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab.tsx index a0a4cabc..9a9bb6c2 100644 --- a/src/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab.tsx +++ b/src/components/pages/report/logistic-stock/tab/PurchasesPerSupplierTab.tsx @@ -156,8 +156,17 @@ const PurchasesPerSupplierTab = ({ tabId }: PurchasesPerSupplierTabProps) => { }); handleFilterModalOpenRef.current = () => { + formik.setValues({ + start_date: filterParams.start_date || null, + end_date: filterParams.end_date || null, + area_ids: filterParams.area_id || null, + supplier_ids: filterParams.supplier_id || null, + product_ids: filterParams.product_id || null, + product_category_ids: filterParams.product_category_id || null, + filter_by: filterParams.filter_by || null, + sort_by: filterParams.sort_by || null, + }); filterModal.openModal(); - formik.validateForm(); }; const { setFieldValue } = formik; diff --git a/src/components/pages/report/marketing/tab/DailyMarketingTab.tsx b/src/components/pages/report/marketing/tab/DailyMarketingTab.tsx index 01e1eea9..167be1fa 100644 --- a/src/components/pages/report/marketing/tab/DailyMarketingTab.tsx +++ b/src/components/pages/report/marketing/tab/DailyMarketingTab.tsx @@ -156,8 +156,21 @@ const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => { }); handleFilterModalOpenRef.current = () => { + formik.setValues({ + page: formik.values.page, + pageSize: formik.values.pageSize, + search: formik.values.search, + area_id: filterParams.area_id || null, + location_id: filterParams.location_id || null, + warehouse_id: filterParams.warehouse_id || null, + customer_id: filterParams.customer_id || null, + start_date: filterParams.start_date || null, + end_date: filterParams.end_date || null, + filter_by: filterParams.filter_by || null, + marketing_type: filterParams.marketing_type || null, + sort_by: filterParams.sort_by || null, + }); filterModal.openModal(); - formik.validateForm(); }; // ===== SEARCH CHANGE HANDLER ===== diff --git a/src/components/pages/report/marketing/tab/HppPerKandangTab.tsx b/src/components/pages/report/marketing/tab/HppPerKandangTab.tsx index c290618a..6ad9a7f0 100644 --- a/src/components/pages/report/marketing/tab/HppPerKandangTab.tsx +++ b/src/components/pages/report/marketing/tab/HppPerKandangTab.tsx @@ -152,8 +152,19 @@ const HppPerKandangTab = ({ tabId }: HppPerKandangTabProps) => { }); handleFilterModalOpenRef.current = () => { + formik.setValues({ + page: formik.values.page, + pageSize: formik.values.pageSize, + area_id: filterParams.area_id || null, + location_id: filterParams.location_id || null, + kandang_id: filterParams.kandang_id || null, + weight_min: filterParams.weight_min || null, + weight_max: filterParams.weight_max || null, + period: filterParams.period || null, + sort_by: filterParams.sort_by || null, + show_unrecorded: filterParams.show_unrecorded ?? false, + }); filterModal.openModal(); - formik.validateForm(); }; // ===== WEIGHT CHANGE HANDLERS ===== diff --git a/src/components/pages/report/production-result/tab/ProductionResultProjectFlockKandangTab.tsx b/src/components/pages/report/production-result/tab/ProductionResultProjectFlockKandangTab.tsx index 8f5fbdc9..973b3083 100644 --- a/src/components/pages/report/production-result/tab/ProductionResultProjectFlockKandangTab.tsx +++ b/src/components/pages/report/production-result/tab/ProductionResultProjectFlockKandangTab.tsx @@ -263,8 +263,30 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => { }); handleFilterModalOpenRef.current = () => { + const restoredAreaId = filterParams.area_id + ? areaOptions.find((opt) => String(opt.value) === filterParams.area_id) || + { value: filterParams.area_id, label: filterParams.area_id } + : null; + const restoredLocationId = filterParams.location_id + ? locationOptions.find((opt) => String(opt.value) === filterParams.location_id) || + { value: filterParams.location_id, label: filterParams.location_id } + : null; + const restoredProjectFlockId = filterParams.project_flock_id + ? projectFlockOptions.find((opt) => String(opt.value) === filterParams.project_flock_id) || + { value: filterParams.project_flock_id, label: filterParams.project_flock_id } + : null; + const restoredKandangId = filterParams.project_flock_kandang_id + ? projectFlockKandangOptions.find((opt) => String(opt.value) === filterParams.project_flock_kandang_id) || + { value: filterParams.project_flock_kandang_id, label: filterParams.project_flock_kandang_id } + : null; + + formik.setValues({ + area_id: restoredAreaId, + location_id: restoredLocationId, + project_flock_id: restoredProjectFlockId, + kandang_id: restoredKandangId, + }); filterModal.openModal(); - formik.validateForm(); }; const [selectedProjectFlockKandang, setSelectedProjectFlockKandang] = diff --git a/src/services/api/report/debt-supplier.ts b/src/services/api/report/debt-supplier.ts index 706c873e..dad46d18 100644 --- a/src/services/api/report/debt-supplier.ts +++ b/src/services/api/report/debt-supplier.ts @@ -15,7 +15,9 @@ export class DebtSupplierApiService extends BaseApiService< supplier_ids?: string, filter_by?: string, start_date?: string, - end_date?: string + end_date?: string, + page?: number, + limit?: number ): Promise | undefined> { return await this.customRequest>( `debt-supplier`, @@ -26,6 +28,8 @@ export class DebtSupplierApiService extends BaseApiService< filter_by: filter_by, start_date: start_date, end_date: end_date, + page: page, + limit: limit, }, } );