diff --git a/src/components/input/SelectInput.tsx b/src/components/input/SelectInput.tsx index c1736fc5..f954bc20 100644 --- a/src/components/input/SelectInput.tsx +++ b/src/components/input/SelectInput.tsx @@ -523,7 +523,7 @@ const useSelect = ( const qs = new URLSearchParams({ ...(params ?? {}), - [searchKey]: inputValue ?? '', + [searchKey ? searchKey : 'search']: inputValue ?? '', [pageKey]: String(pageIndex + 1), [limitKey]: String(limit), }).toString(); diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index 17d5227b..e3018e38 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -23,7 +23,6 @@ import { Icon } from '@iconify/react'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { useRouter, usePathname } from 'next/navigation'; import { ChangeEventHandler, useEffect, useMemo, useState } from 'react'; -import { useUiStore } from '@/stores/ui/ui.store'; import toast from 'react-hot-toast'; import useSWR from 'swr'; import { useFormik } from 'formik'; @@ -148,7 +147,6 @@ const RowOptionsMenu = ({ }; const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { - const { searchValue, setSearchValue, setTableState } = useUiStore(); const pathname = usePathname(); const isSuccess = useProjectFlockStore((s) => s.isSuccess); @@ -185,7 +183,11 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { category: 'category', period: 'period', }, + + persist: true, + storeName: 'project-flock-table', }); + const router = useRouter(); // ===== State ===== @@ -425,18 +427,11 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { setIsDeleteLoading(false); setRowSelection({}); }; - useEffect(() => { - updateFilter('search', searchValue); - }, [searchValue, updateFilter]); - - useEffect(() => { - setTableState('project-flock-table', pathname); - }, [pathname, setTableState]); const searchChangeHandler: ChangeEventHandler = (e) => { - setSearchValue(e.target.value); updateFilter('search', e.target.value); }; + const confirmApprovalHandler = async ( notes: string, approvalAction: 'APPROVED' | 'REJECTED' diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx index 2dfff34f..4013e25c 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -261,7 +261,7 @@ const ProjectFlockForm = ({ isLoadingOptions: isLoadingFlocks, options: optionsFlock, loadMore: loadMoreFlock, - } = useSelect(FlockApi.basePath, 'id', 'name', '', { + } = useSelect(FlockApi.basePath, 'id', 'name', 'search', { project_category: selectedCategory, location_id: selectedLocation, area_id: selectedArea, @@ -279,7 +279,7 @@ const ProjectFlockForm = ({ isLoadingOptions: isLoadingLocations, setInputValue: setInputValueLocation, loadMore: loadMoreLocation, - } = useSelect(LocationApi.basePath, 'id', 'name', '', { + } = useSelect(LocationApi.basePath, 'id', 'name', 'search', { area_id: selectedArea != '' ? selectedArea @@ -291,7 +291,7 @@ const ProjectFlockForm = ({ isLoadingOptions: isLoadingProductionStandards, setInputValue: setInputValueProductionStandard, loadMore: loadMoreProductionStandard, - } = useSelect(ProductionStandardApi.basePath, 'id', 'name', '', { + } = useSelect(ProductionStandardApi.basePath, 'id', 'name', 'search', { project_category: selectedCategory, }); @@ -307,7 +307,7 @@ const ProjectFlockForm = ({ } = useSWR(kandangUrl, KandangApi.getAllFetcher); const { data: periodFlocks, mutate: refreshPeriodFlocks } = useSWR( - `${selectedFlock?.toString()}/periods`, + selectedFlock ? `${selectedFlock?.toString()}/periods` : undefined, () => ProjectFlockApi.getNextPeriod(parseInt(selectedLocation as string)) ); @@ -793,6 +793,7 @@ const ProjectFlockForm = ({ formik.values.kandang_ids?.includes(kandang.id) )?.period : undefined; + const inputPeriod = (initialValues?.period ?? selectedPeriod == 0) ? 1 : selectedPeriod; diff --git a/src/services/hooks/useTableFilter.tsx b/src/services/hooks/useTableFilter.tsx index 17b875f0..43fc173c 100644 --- a/src/services/hooks/useTableFilter.tsx +++ b/src/services/hooks/useTableFilter.tsx @@ -1,4 +1,5 @@ -import { useCallback, useMemo, useReducer } from 'react'; +import { useCallback, useEffect, useMemo, useReducer } from 'react'; +import { useTableFilterStore } from '@/stores/table/table-filter.store'; /** Core filter shape (page + pageSize) extended by your custom fields */ export type TableFilterState> = { @@ -30,6 +31,9 @@ export type UseTableFilterOptions> = { paramMap?: Partial, string>>; /** If true, `toSearchParams`/`toQueryString` will omit values equal to defaults */ omitDefaultsInUrl?: boolean; + + persist?: boolean; + storeName?: string; }; function clampToInt(n: number, min = 1) { @@ -90,9 +94,37 @@ function shallowEqual>( export function useTableFilter>( options?: UseTableFilterOptions ) { - const defaults = useMemo( - () => createInitialState(options), - [options] + if (options?.persist && !options?.storeName) { + throw new Error( + 'storeName is required if persist is true in useTableFilter!' + ); + } + + const storeName = options?.storeName ?? ''; + const persistedState = useTableFilterStore( + useCallback( + (storeState) => + storeName + ? (storeState.data[storeName] as Partial>) + : undefined, + [storeName] + ) + ); + const setTableData = useTableFilterStore( + (storeState) => storeState.setTableData + ); + + const defaults = useMemo(() => { + return createInitialState(options); + }, [options]); + + const initialState = useMemo( + () => + ({ + ...defaults, + ...(persistedState as object), + }) as TableFilterState, + [defaults, persistedState] ); const [state, dispatch] = useReducer( @@ -106,15 +138,22 @@ export function useTableFilter>( case 'SET_PAGE_SIZE': { const pageSize = clampToInt(a.pageSize); const page = a.resetPage ? 1 : s.page; + return { ...s, pageSize, page }; } case 'SET_FILTERS': { const page = a.resetPage ? 1 : s.page; + return { ...s, ...a.filters, page }; } case 'UPDATE_FILTER': { const page = a.resetPage ? 1 : s.page; - return { ...s, [a.key]: a.value, page } as TableFilterState; + + return { + ...s, + [a.key]: a.value, + page, + } as TableFilterState; } case 'REPLACE_ALL': return { @@ -128,12 +167,19 @@ export function useTableFilter>( return s; } }, - defaults + initialState ); - // Notify consumer on change (stable ref) + useEffect(() => { + if (!options?.persist || !storeName) { + return; + } + + setTableData(storeName, state); + }, [options?.persist, setTableData, state, storeName]); + const onChange = options?.onChange; - useMemo(() => { + useEffect(() => { if (onChange) onChange(state); }, [state, onChange]); diff --git a/src/stores/table/table-filter.store.ts b/src/stores/table/table-filter.store.ts new file mode 100644 index 00000000..cae14e10 --- /dev/null +++ b/src/stores/table/table-filter.store.ts @@ -0,0 +1,60 @@ +import { create } from 'zustand'; +import { createJSONStorage, devtools, persist } from 'zustand/middleware'; +import { TableFilterStore } from '@/types/stores'; + +type TableFilterStoreState = TableFilterStore< + Record> +>; + +export const useTableFilterStore = create()( + devtools( + persist( + (set) => ({ + data: {}, + + setData: (newData) => { + set({ data: newData }); + }, + + setTableData: (key, tableData) => { + set((state) => ({ + data: { + ...state.data, + [key]: tableData, + }, + })); + }, + + setTableDataField: (key, field, value) => { + set((state) => ({ + data: { + ...state.data, + [key]: { + ...state.data[key], + [field]: value, + }, + }, + })); + }, + + setSearchValue: (key, searchValue) => { + set((state) => ({ + data: { + ...state.data, + [key]: { + ...state.data[key], + + // search key + search: searchValue, + }, + }, + })); + }, + }), + { + name: 'table-filter-store', + storage: createJSONStorage(() => sessionStorage), + } + ) + ) +); diff --git a/src/stores/ui/ui.store.ts b/src/stores/ui/ui.store.ts index 4b8379db..b2ed0cbd 100644 --- a/src/stores/ui/ui.store.ts +++ b/src/stores/ui/ui.store.ts @@ -1,7 +1,7 @@ 'use client'; import { create } from 'zustand'; -import { devtools, persist } from 'zustand/middleware'; +import { createJSONStorage, devtools, persist } from 'zustand/middleware'; import { UIStore } from '@/types/stores'; import { createMainUiSlice } from '@/stores/ui/slices/main.slice'; @@ -20,6 +20,7 @@ export const useUiStore = create()( }), { name: 'search-store', + storage: createJSONStorage(() => sessionStorage), partialize: (state) => ({ key: state.key, path: state.path, diff --git a/src/types/stores.d.ts b/src/types/stores.d.ts index 331bbb82..117f40cc 100644 --- a/src/types/stores.d.ts +++ b/src/types/stores.d.ts @@ -117,3 +117,11 @@ export type ProjectFlockSlice = { setCreatedProjectFlock: (data: ProjectFlock | null) => void; resetProjectFlock: () => void; }; + +export type TableFilterStore>> = { + data: T; + setData: (newData: T) => void; + setTableData: (key: string, tableData: Record) => void; + setTableDataField: (key: string, field: string, value: unknown) => void; + setSearchValue: (key: string, searchValue: string) => void; +};