fix: persist table filter state in master data

This commit is contained in:
ValdiANS
2026-04-29 15:10:41 +07:00
parent 29347c24f4
commit 46daed8fc4
17 changed files with 276 additions and 434 deletions
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { ChangeEventHandler, useEffect, useMemo, useState } from 'react'; import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -20,8 +20,6 @@ import { Area } from '@/types/api/master-data/area';
import { AreaApi } from '@/services/api/master-data'; import { AreaApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { usePathname } from 'next/navigation';
import { useUiStore } from '@/stores/ui/ui.store';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
popoverPosition = 'bottom', popoverPosition = 'bottom',
@@ -103,9 +101,6 @@ const RowOptionsMenu = ({
}; };
const AreasTable = () => { const AreasTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter, updateFilter,
@@ -114,12 +109,14 @@ const AreasTable = () => {
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
} = useTableFilter({ } = useTableFilter({
initial: { initial: {
search: searchValue, search: '',
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
pageSize: 'limit', pageSize: 'limit',
}, },
persist: true,
storeName: 'areas-table',
}); });
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
@@ -137,17 +134,8 @@ const AreasTable = () => {
const [selectedArea, setSelectedArea] = useState<Area | undefined>(undefined); const [selectedArea, setSelectedArea] = useState<Area | undefined>(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
useEffect(() => {
setTableState('areas-table', pathname);
}, [pathname, setTableState]);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value); updateFilter('search', e.target.value, true);
updateFilter('search', e.target.value);
}; };
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { ChangeEventHandler, useEffect, useMemo, useState } from 'react'; import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -20,8 +20,6 @@ import { Bank } from '@/types/api/master-data/bank';
import { BankApi } from '@/services/api/master-data'; import { BankApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { usePathname } from 'next/navigation';
import { useUiStore } from '@/stores/ui/ui.store';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
popoverPosition = 'bottom', popoverPosition = 'bottom',
@@ -103,9 +101,6 @@ const RowOptionsMenu = ({
}; };
const BanksTable = () => { const BanksTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter, updateFilter,
@@ -114,12 +109,14 @@ const BanksTable = () => {
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
} = useTableFilter({ } = useTableFilter({
initial: { initial: {
search: searchValue, search: '',
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
pageSize: 'limit', pageSize: 'limit',
}, },
persist: true,
storeName: 'banks-table',
}); });
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
@@ -137,17 +134,8 @@ const BanksTable = () => {
const [selectedBank, setSelectedBank] = useState<Bank | undefined>(undefined); const [selectedBank, setSelectedBank] = useState<Bank | undefined>(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
useEffect(() => {
setTableState('banks-table', pathname);
}, [pathname, setTableState]);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value); updateFilter('search', e.target.value, true);
updateFilter('search', e.target.value);
}; };
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { ChangeEventHandler, useEffect, useMemo, useState } from 'react'; import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -20,8 +20,6 @@ import { Customer } from '@/types/api/master-data/customer';
import { CustomerApi } from '@/services/api/master-data'; import { CustomerApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { usePathname } from 'next/navigation';
import { useUiStore } from '@/stores/ui/ui.store';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
popoverPosition = 'bottom', popoverPosition = 'bottom',
@@ -103,9 +101,6 @@ const RowOptionsMenu = ({
}; };
const CustomersTable = () => { const CustomersTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter, updateFilter,
@@ -114,12 +109,14 @@ const CustomersTable = () => {
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
} = useTableFilter({ } = useTableFilter({
initial: { initial: {
search: searchValue, search: '',
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
pageSize: 'limit', pageSize: 'limit',
}, },
persist: true,
storeName: 'customers-table',
}); });
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
@@ -139,17 +136,8 @@ const CustomersTable = () => {
>(undefined); >(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
useEffect(() => {
setTableState('customers-table', pathname);
}, [pathname, setTableState]);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value); updateFilter('search', e.target.value, true);
updateFilter('search', e.target.value);
}; };
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { ChangeEventHandler, useEffect, useMemo, useState } from 'react'; import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -20,8 +20,6 @@ import { Flock } from '@/types/api/master-data/flock';
import { FlockApi } from '@/services/api/master-data'; import { FlockApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { usePathname } from 'next/navigation';
import { useUiStore } from '@/stores/ui/ui.store';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
popoverPosition = 'bottom', popoverPosition = 'bottom',
@@ -103,9 +101,6 @@ const RowOptionsMenu = ({
}; };
const FlockTable = () => { const FlockTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter, updateFilter,
@@ -114,12 +109,14 @@ const FlockTable = () => {
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
} = useTableFilter({ } = useTableFilter({
initial: { initial: {
search: searchValue, search: '',
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
pageSize: 'limit', pageSize: 'limit',
}, },
persist: true,
storeName: 'flock-table',
}); });
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
@@ -139,17 +136,8 @@ const FlockTable = () => {
); );
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
useEffect(() => {
setTableState('flocks-table', pathname);
}, [pathname, setTableState]);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value); updateFilter('search', e.target.value, true);
updateFilter('search', e.target.value);
}; };
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -1,13 +1,6 @@
'use client'; 'use client';
import { import { ChangeEventHandler, useMemo, useState } from 'react';
ChangeEventHandler,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { usePathname } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -35,7 +28,6 @@ import { User } from '@/types/api/api-general';
import { formatNumber } from '@/lib/helper'; import { formatNumber } from '@/lib/helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { useUiStore } from '@/stores/ui/ui.store';
import { import {
KandangFilterSchema, KandangFilterSchema,
KandangFilterType, KandangFilterType,
@@ -122,20 +114,21 @@ const RowOptionsMenu = ({
}; };
const KandangsTable = () => { const KandangsTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter, updateFilter,
setPage, setPage,
setPageSize, setPageSize,
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
} = useTableFilter({ } = useTableFilter<{
search: string;
locationFilter?: OptionType<string>;
picFilter?: OptionType<string>;
}>({
initial: { initial: {
search: '', search: '',
locationFilter: '', locationFilter: undefined,
picFilter: '', picFilter: undefined,
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
@@ -143,6 +136,8 @@ const KandangsTable = () => {
locationFilter: 'location_id', locationFilter: 'location_id',
picFilter: 'pic_id', picFilter: 'pic_id',
}, },
persist: true,
storeName: 'kandangs-table',
}); });
// ===== FILTER MODAL STATE ===== // ===== FILTER MODAL STATE =====
@@ -151,22 +146,34 @@ const KandangsTable = () => {
// ===== FORMIK SETUP ===== // ===== FORMIK SETUP =====
const formik = useFormik<KandangFilterType>({ const formik = useFormik<KandangFilterType>({
initialValues: { initialValues: {
location_id: null, location: tableFilterState.locationFilter,
pic_id: null, pic: tableFilterState.picFilter,
}, },
validationSchema: KandangFilterSchema, validationSchema: KandangFilterSchema,
onSubmit: (values, { setSubmitting }) => { onSubmit: (values, { setSubmitting }) => {
updateFilter('locationFilter', values.location_id || ''); updateFilter('locationFilter', values.location || undefined, true);
updateFilter('picFilter', values.pic_id || ''); updateFilter('picFilter', values.pic || undefined, true);
filterModal.closeModal(); filterModal.closeModal();
setSubmitting(false); setSubmitting(false);
}, },
onReset: () => { });
updateFilter('locationFilter', '');
updateFilter('picFilter', ''); const formikResetHandler = () => {
updateFilter('locationFilter', undefined, true);
updateFilter('picFilter', undefined, true);
formik.resetForm({
values: {
location: undefined,
pic: undefined,
}, },
}); });
filterModal.closeModal();
};
const { setFieldValue } = formik;
// ===== LOCATION OPTIONS ===== // ===== LOCATION OPTIONS =====
const { const {
setInputValue: setLocationInputValue, setInputValue: setLocationInputValue,
@@ -194,43 +201,15 @@ const KandangsTable = () => {
); );
// ===== FILTER HANDLERS ===== // ===== FILTER HANDLERS =====
const handleFilterLocationChange = useCallback( const handleFilterLocationChange = (
(val: OptionType | OptionType[] | null) => { val: OptionType | OptionType[] | null
const location = val as OptionType | null; ) => {
const locationId = location?.value ? String(location.value) : null; setFieldValue('location', val);
};
formik.setFieldValue('location_id', locationId); const handleFilterPicChange = (val: OptionType | OptionType[] | null) => {
}, setFieldValue('pic', val);
[formik] };
);
const handleFilterPicChange = useCallback(
(val: OptionType | OptionType[] | null) => {
const pic = val as OptionType | null;
const picId = pic?.value ? String(pic.value) : null;
formik.setFieldValue('pic_id', picId);
},
[formik]
);
// ===== FILTER HELPERS =====
const locationIdValue = useMemo(() => {
if (!formik.values.location_id) return null;
return (
locationOptions.find(
(opt) => String(opt.value) === formik.values.location_id
) || null
);
}, [formik.values.location_id, locationOptions]);
const picIdValue = useMemo(() => {
if (!formik.values.pic_id) return null;
return (
picOptions.find((opt) => String(opt.value) === formik.values.pic_id) ||
null
);
}, [formik.values.pic_id, picOptions]);
// ===== HANDLE FILTER MODAL OPEN ===== // ===== HANDLE FILTER MODAL OPEN =====
const handleFilterModalOpen = () => { const handleFilterModalOpen = () => {
@@ -255,17 +234,8 @@ const KandangsTable = () => {
); );
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
useEffect(() => {
setTableState('kandangs-table', pathname);
}, [pathname, setTableState]);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value); updateFilter('search', e.target.value, true);
updateFilter('search', e.target.value);
}; };
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -475,13 +445,13 @@ const KandangsTable = () => {
<Icon icon='heroicons:x-mark' width={20} height={20} /> <Icon icon='heroicons:x-mark' width={20} height={20} />
</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-1.5'> <div className='p-4 flex flex-col gap-1.5'>
<SelectInput <SelectInput
label='Lokasi' label='Lokasi'
placeholder='Pilih Lokasi' placeholder='Pilih Lokasi'
options={locationOptions} options={locationOptions}
value={locationIdValue} value={formik.values.location}
onChange={handleFilterLocationChange} onChange={handleFilterLocationChange}
onInputChange={setLocationInputValue} onInputChange={setLocationInputValue}
isLoading={isLoadingLocationOptions} isLoading={isLoadingLocationOptions}
@@ -494,7 +464,7 @@ const KandangsTable = () => {
label='PIC' label='PIC'
placeholder='Pilih PIC' placeholder='Pilih PIC'
options={picOptions} options={picOptions}
value={picIdValue} value={formik.values.pic}
onChange={handleFilterPicChange} onChange={handleFilterPicChange}
onInputChange={setPicInputValue} onInputChange={setPicInputValue}
isLoading={isLoadingPicOptions} isLoading={isLoadingPicOptions}
@@ -510,17 +480,14 @@ const KandangsTable = () => {
type='button' type='button'
variant='soft' variant='soft'
className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2' className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2'
onClick={() => { onClick={formikResetHandler}
formik.resetForm();
filterModal.closeModal();
}}
> >
Reset Filter Reset Filter
</Button> </Button>
<Button <Button
type='submit' type='submit'
className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold' className='min-w-40 text-sm rounded-lg py-3 text-white font-semibold'
disabled={!formik.isValid || formik.isSubmitting} disabled={!formik.isValid}
> >
Apply Filter Apply Filter
</Button> </Button>
@@ -1,11 +1,19 @@
import { string, object } from 'yup'; import { OptionType } from '@/components/input/SelectInput';
import * as Yup from 'yup';
export const KandangFilterSchema = object().shape({ export const KandangFilterSchema = Yup.object().shape({
location_id: string().nullable(), location: Yup.object({
pic_id: string().nullable(), value: Yup.string().nullable(),
label: Yup.string().nullable(),
}).nullable(),
pic: Yup.object({
value: Yup.string().nullable(),
label: Yup.string().nullable(),
}).nullable(),
}); });
export type KandangFilterType = { export type KandangFilterType = {
location_id: string | null; location?: OptionType<string>;
pic_id: string | null; pic?: OptionType<string>;
}; };
@@ -1,13 +1,6 @@
'use client'; 'use client';
import { import { ChangeEventHandler, useMemo, useState } from 'react';
ChangeEventHandler,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { usePathname } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -32,7 +25,6 @@ import { Area } from '@/types/api/master-data/area';
import { LocationApi, AreaApi } from '@/services/api/master-data'; import { LocationApi, AreaApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { useUiStore } from '@/stores/ui/ui.store';
import { import {
LocationFilterSchema, LocationFilterSchema,
LocationFilterType, LocationFilterType,
@@ -118,25 +110,27 @@ const RowOptionsMenu = ({
}; };
const LocationsTable = () => { const LocationsTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter, updateFilter,
setPage, setPage,
setPageSize, setPageSize,
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
} = useTableFilter({ } = useTableFilter<{
search: string;
areaFilter?: OptionType<string>;
}>({
initial: { initial: {
search: '', search: '',
areaFilter: '', areaFilter: undefined,
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
pageSize: 'limit', pageSize: 'limit',
areaFilter: 'area_id', areaFilter: 'area_id',
}, },
persist: true,
storeName: 'locations-table',
}); });
// ===== FILTER MODAL STATE ===== // ===== FILTER MODAL STATE =====
@@ -145,19 +139,28 @@ const LocationsTable = () => {
// ===== FORMIK SETUP ===== // ===== FORMIK SETUP =====
const formik = useFormik<LocationFilterType>({ const formik = useFormik<LocationFilterType>({
initialValues: { initialValues: {
area_id: null, area: tableFilterState.areaFilter,
}, },
validationSchema: LocationFilterSchema, validationSchema: LocationFilterSchema,
onSubmit: (values, { setSubmitting }) => { onSubmit: (values, { setSubmitting }) => {
updateFilter('areaFilter', values.area_id || ''); updateFilter('areaFilter', values.area || undefined, true);
filterModal.closeModal(); filterModal.closeModal();
setSubmitting(false); setSubmitting(false);
}, },
onReset: () => { });
updateFilter('areaFilter', '');
const formikResetHandler = () => {
updateFilter('areaFilter', undefined, true);
formik.resetForm({
values: {
area: undefined,
}, },
}); });
filterModal.closeModal();
};
// ===== AREA OPTIONS ===== // ===== AREA OPTIONS =====
const { const {
setInputValue: setAreaInputValue, setInputValue: setAreaInputValue,
@@ -172,24 +175,9 @@ const LocationsTable = () => {
); );
// ===== FILTER HANDLERS ===== // ===== FILTER HANDLERS =====
const handleFilterAreaChange = useCallback( const handleFilterAreaChange = (val: OptionType | OptionType[] | null) => {
(val: OptionType | OptionType[] | null) => { formik.setFieldValue('area', val);
const area = val as OptionType | null; };
const areaId = area?.value ? String(area.value) : null;
formik.setFieldValue('area_id', areaId);
},
[formik]
);
// ===== FILTER HELPERS =====
const areaIdValue = 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]);
// ===== HANDLE FILTER MODAL OPEN ===== // ===== HANDLE FILTER MODAL OPEN =====
const handleFilterModalOpen = () => { const handleFilterModalOpen = () => {
@@ -212,19 +200,10 @@ const LocationsTable = () => {
>(undefined); >(undefined);
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
useEffect(() => {
setTableState('locations-table', pathname);
}, [pathname, setTableState]);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value); updateFilter('search', e.target.value, true);
updateFilter('search', e.target.value);
}; };
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -425,13 +404,13 @@ const LocationsTable = () => {
<Icon icon='heroicons:x-mark' width={20} height={20} /> <Icon icon='heroicons:x-mark' width={20} height={20} />
</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-1.5'> <div className='p-4 flex flex-col gap-1.5'>
<SelectInput <SelectInput
label='Area' label='Area'
placeholder='Pilih Area' placeholder='Pilih Area'
options={areaOptions} options={areaOptions}
value={areaIdValue} value={formik.values.area}
onChange={handleFilterAreaChange} onChange={handleFilterAreaChange}
onInputChange={setAreaInputValue} onInputChange={setAreaInputValue}
isLoading={isLoadingAreaOptions} isLoading={isLoadingAreaOptions}
@@ -447,10 +426,7 @@ const LocationsTable = () => {
type='button' type='button'
variant='soft' variant='soft'
className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2' className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2'
onClick={() => { onClick={formikResetHandler}
formik.resetForm();
filterModal.closeModal();
}}
> >
Reset Filter Reset Filter
</Button> </Button>
@@ -1,9 +1,13 @@
import { string, object } from 'yup'; import { OptionType } from '@/components/input/SelectInput';
import * as Yup from 'yup';
export const LocationFilterSchema = object().shape({ export const LocationFilterSchema = Yup.object().shape({
area_id: string().nullable(), area: Yup.object({
value: Yup.string().nullable(),
label: Yup.string().nullable(),
}).nullable(),
}); });
export type LocationFilterType = { export type LocationFilterType = {
area_id: string | null; area?: OptionType<string>;
}; };
@@ -20,8 +20,6 @@ import { Nonstock } from '@/types/api/master-data/nonstock';
import { NonstockApi } from '@/services/api/master-data'; import { NonstockApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { usePathname } from 'next/navigation';
import { useUiStore } from '@/stores/ui/ui.store';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
popoverPosition = 'bottom', popoverPosition = 'bottom',
@@ -103,9 +101,6 @@ const RowOptionsMenu = ({
}; };
const NonstocksTable = () => { const NonstocksTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter, updateFilter,
@@ -114,22 +109,16 @@ const NonstocksTable = () => {
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
} = useTableFilter({ } = useTableFilter({
initial: { initial: {
search: searchValue, search: '',
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
pageSize: 'limit', pageSize: 'limit',
}, },
persist: true,
storeName: 'nonstock-table',
}); });
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
useEffect(() => {
setTableState('nonstocks-table', pathname);
}, [pathname, setTableState]);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const { const {
@@ -148,8 +137,7 @@ const NonstocksTable = () => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value); updateFilter('search', e.target.value, true);
updateFilter('search', e.target.value);
}; };
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -1,7 +1,6 @@
'use client'; 'use client';
import { ChangeEventHandler, useEffect, useMemo, useState } from 'react'; import { ChangeEventHandler, useMemo, useState } from 'react';
import { usePathname } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -21,7 +20,6 @@ import { ProductCategory } from '@/types/api/master-data/product-category';
import { ProductCategoryApi } from '@/services/api/master-data'; import { ProductCategoryApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { useUiStore } from '@/stores/ui/ui.store';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
popoverPosition = 'bottom', popoverPosition = 'bottom',
@@ -103,9 +101,6 @@ const RowOptionsMenu = ({
}; };
const ProductCategoryTable = () => { const ProductCategoryTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter, updateFilter,
@@ -120,12 +115,10 @@ const ProductCategoryTable = () => {
page: 'page', page: 'page',
pageSize: 'limit', pageSize: 'limit',
}, },
persist: true,
storeName: 'product-category-table',
}); });
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const { const {
@@ -144,8 +137,7 @@ const ProductCategoryTable = () => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value); updateFilter('search', e.target.value, true);
updateFilter('search', e.target.value);
}; };
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -214,10 +206,6 @@ const ProductCategoryTable = () => {
[tableFilterState.pageSize, tableFilterState.page, deleteModal] [tableFilterState.pageSize, tableFilterState.page, deleteModal]
); );
useEffect(() => {
setTableState('product-category-table', pathname);
}, [pathname, setTableState]);
return ( return (
<> <>
<div className='w-full'> <div className='w-full'>
@@ -1,13 +1,6 @@
'use client'; 'use client';
import { import { ChangeEventHandler, useMemo, useState } from 'react';
ChangeEventHandler,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { usePathname } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -33,7 +26,6 @@ import { ProductApi, ProductCategoryApi } from '@/services/api/master-data';
import { formatCurrency } from '@/lib/helper'; import { formatCurrency } from '@/lib/helper';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { useUiStore } from '@/stores/ui/ui.store';
import { import {
ProductFilterSchema, ProductFilterSchema,
ProductFilterType, ProductFilterType,
@@ -119,25 +111,27 @@ const RowOptionsMenu = ({
}; };
const ProductsTable = () => { const ProductsTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter, updateFilter,
setPage, setPage,
setPageSize, setPageSize,
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
} = useTableFilter({ } = useTableFilter<{
search: string;
productCategoryFilter?: OptionType<string>;
}>({
initial: { initial: {
search: '', search: '',
productCategoryFilter: '', productCategoryFilter: undefined,
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
pageSize: 'limit', pageSize: 'limit',
productCategoryFilter: 'product_category_id', productCategoryFilter: 'product_category_id',
}, },
persist: true,
storeName: 'product-table',
}); });
// ===== FILTER MODAL STATE ===== // ===== FILTER MODAL STATE =====
@@ -146,19 +140,32 @@ const ProductsTable = () => {
// ===== FORMIK SETUP ===== // ===== FORMIK SETUP =====
const formik = useFormik<ProductFilterType>({ const formik = useFormik<ProductFilterType>({
initialValues: { initialValues: {
product_category_id: null, product_category: tableFilterState.productCategoryFilter,
}, },
validationSchema: ProductFilterSchema, validationSchema: ProductFilterSchema,
onSubmit: (values, { setSubmitting }) => { onSubmit: (values, { setSubmitting }) => {
updateFilter('productCategoryFilter', values.product_category_id || ''); updateFilter(
'productCategoryFilter',
values.product_category || undefined,
true
);
filterModal.closeModal(); filterModal.closeModal();
setSubmitting(false); setSubmitting(false);
}, },
onReset: () => { });
updateFilter('productCategoryFilter', '');
const formikResetHandler = () => {
updateFilter('productCategoryFilter', undefined, true);
formik.resetForm({
values: {
product_category: undefined,
}, },
}); });
filterModal.closeModal();
};
// ===== PRODUCT CATEGORY OPTIONS ===== // ===== PRODUCT CATEGORY OPTIONS =====
const { const {
setInputValue: setProductCategoryInputValue, setInputValue: setProductCategoryInputValue,
@@ -173,25 +180,11 @@ const ProductsTable = () => {
); );
// ===== FILTER HANDLERS ===== // ===== FILTER HANDLERS =====
const handleFilterProductCategoryChange = useCallback( const handleFilterProductCategoryChange = (
(val: OptionType | OptionType[] | null) => { val: OptionType | OptionType[] | null
const category = val as OptionType | null; ) => {
const categoryId = category?.value ? String(category.value) : null; formik.setFieldValue('product_category', val);
};
formik.setFieldValue('product_category_id', categoryId);
},
[formik]
);
// ===== FILTER HELPERS =====
const productCategoryIdValue = useMemo(() => {
if (!formik.values.product_category_id) return null;
return (
productCategoryOptions.find(
(opt) => String(opt.value) === formik.values.product_category_id
) || null
);
}, [formik.values.product_category_id, productCategoryOptions]);
// ===== HANDLE FILTER MODAL OPEN ===== // ===== HANDLE FILTER MODAL OPEN =====
const handleFilterModalOpen = () => { const handleFilterModalOpen = () => {
@@ -199,10 +192,6 @@ const ProductsTable = () => {
formik.validateForm(); formik.validateForm();
}; };
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const { const {
@@ -220,13 +209,8 @@ const ProductsTable = () => {
); );
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
useEffect(() => {
setTableState('product-table', pathname);
}, [pathname, setTableState]);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value); updateFilter('search', e.target.value, true);
updateFilter('search', e.target.value);
}; };
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -477,13 +461,13 @@ const ProductsTable = () => {
<Icon icon='heroicons:x-mark' width={20} height={20} /> <Icon icon='heroicons:x-mark' width={20} height={20} />
</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-1.5'> <div className='p-4 flex flex-col gap-1.5'>
<SelectInput <SelectInput
label='Kategori Produk' label='Kategori Produk'
placeholder='Pilih Kategori Produk' placeholder='Pilih Kategori Produk'
options={productCategoryOptions} options={productCategoryOptions}
value={productCategoryIdValue} value={formik.values.product_category}
onChange={handleFilterProductCategoryChange} onChange={handleFilterProductCategoryChange}
onInputChange={setProductCategoryInputValue} onInputChange={setProductCategoryInputValue}
isLoading={isLoadingProductCategoryOptions} isLoading={isLoadingProductCategoryOptions}
@@ -499,10 +483,7 @@ const ProductsTable = () => {
type='button' type='button'
variant='soft' variant='soft'
className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2' className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2'
onClick={() => { onClick={formikResetHandler}
formik.resetForm();
filterModal.closeModal();
}}
> >
Reset Filter Reset Filter
</Button> </Button>
@@ -1,9 +1,13 @@
import { string, object } from 'yup'; import { OptionType } from '@/components/input/SelectInput';
import * as Yup from 'yup';
export const ProductFilterSchema = object().shape({ export const ProductFilterSchema = Yup.object().shape({
product_category_id: string().nullable(), product_category: Yup.object({
value: Yup.string().nullable(),
label: Yup.string().nullable(),
}).nullable(),
}); });
export type ProductFilterType = { export type ProductFilterType = {
product_category_id: string | null; product_category?: OptionType<string>;
}; };
@@ -128,27 +128,44 @@ const ProductionStandardTable = () => {
pageSize: 'limit', pageSize: 'limit',
projectCategoryFilter: 'project_category', projectCategoryFilter: 'project_category',
}, },
persist: true,
storeName: 'production-standard-table',
}); });
// ===== FILTER MODAL STATE ===== // ===== FILTER MODAL STATE =====
const filterModal = useModal(); const filterModal = useModal();
// ===== FILTER INITIAL VALUES (derived from persisted state) =====
const filterInitialValues = useMemo<ProductionStandardFilterType>(
() => ({
project_category: tableFilterState.projectCategoryFilter || null,
}),
[tableFilterState.projectCategoryFilter]
);
// ===== FORMIK SETUP ===== // ===== FORMIK SETUP =====
const formik = useFormik<ProductionStandardFilterType>({ const formik = useFormik<ProductionStandardFilterType>({
initialValues: { initialValues: filterInitialValues,
project_category: null,
},
validationSchema: ProductionStandardFilterSchema, validationSchema: ProductionStandardFilterSchema,
onSubmit: (values, { setSubmitting }) => { onSubmit: (values, { setSubmitting }) => {
updateFilter('projectCategoryFilter', values.project_category || ''); updateFilter('projectCategoryFilter', values.project_category || '');
filterModal.closeModal(); filterModal.closeModal();
setSubmitting(false); setSubmitting(false);
}, },
onReset: () => { });
updateFilter('projectCategoryFilter', '');
const formikResetHandler = () => {
updateFilter('projectCategoryFilter', '', true);
formik.resetForm({
values: {
project_category: null,
}, },
}); });
filterModal.closeModal();
};
// ===== PROJECT CATEGORY OPTIONS (GROWING or LAYING) ===== // ===== PROJECT CATEGORY OPTIONS (GROWING or LAYING) =====
const projectCategoryOptions = useMemo( const projectCategoryOptions = useMemo(
() => [ () => [
@@ -381,7 +398,7 @@ const ProductionStandardTable = () => {
<Icon icon='heroicons:x-mark' width={20} height={20} /> <Icon icon='heroicons:x-mark' width={20} height={20} />
</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-1.5'> <div className='p-4 flex flex-col gap-1.5'>
<SelectInputRadio <SelectInputRadio
label='Kategori' label='Kategori'
@@ -397,13 +414,9 @@ const ProductionStandardTable = () => {
{/* Modal Footer */} {/* Modal Footer */}
<div className='flex justify-between items-center gap-4 p-4 border-t border-base-content/10 bg-gray-50'> <div className='flex justify-between items-center gap-4 p-4 border-t border-base-content/10 bg-gray-50'>
<Button <Button
type='button' type='reset'
variant='soft' variant='soft'
className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2' className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2'
onClick={() => {
formik.resetForm();
filterModal.closeModal();
}}
> >
Reset Filter Reset Filter
</Button> </Button>
@@ -7,7 +7,6 @@ import {
useMemo, useMemo,
useState, useState,
} from 'react'; } from 'react';
import { usePathname } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -30,7 +29,7 @@ import { Supplier } from '@/types/api/master-data/supplier';
import { SupplierApi } from '@/services/api/master-data'; import { SupplierApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { useUiStore } from '@/stores/ui/ui.store';
import { import {
SupplierFilterSchema, SupplierFilterSchema,
SupplierFilterType, SupplierFilterType,
@@ -117,20 +116,21 @@ const RowOptionsMenu = ({
}; };
const SuppliersTable = () => { const SuppliersTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter, updateFilter,
setPage, setPage,
setPageSize, setPageSize,
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
} = useTableFilter({ } = useTableFilter<{
search: string;
categoryFilter?: OptionType<string>;
flagFilter?: string;
}>({
initial: { initial: {
search: '', search: '',
categoryFilter: '', categoryFilter: undefined,
flagFilter: '', flagFilter: undefined,
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
@@ -138,6 +138,8 @@ const SuppliersTable = () => {
categoryFilter: 'category_id', categoryFilter: 'category_id',
flagFilter: 'flag', flagFilter: 'flag',
}, },
persist: true,
storeName: 'supplier-table',
}); });
// ===== FILTER MODAL STATE ===== // ===== FILTER MODAL STATE =====
@@ -146,26 +148,33 @@ const SuppliersTable = () => {
// ===== FORMIK SETUP ===== // ===== FORMIK SETUP =====
const formik = useFormik<SupplierFilterType>({ const formik = useFormik<SupplierFilterType>({
initialValues: { initialValues: {
category_id: null, category: tableFilterState.categoryFilter,
flag: false, flag: tableFilterState.flagFilter === 'EKSPEDISI',
}, },
validationSchema: SupplierFilterSchema, validationSchema: SupplierFilterSchema,
onSubmit: (values, { setSubmitting }) => { onSubmit: (values, { setSubmitting }) => {
updateFilter('categoryFilter', values.category_id || ''); updateFilter('categoryFilter', values.category || undefined, true);
updateFilter( updateFilter('flagFilter', values.flag === true ? 'EKSPEDISI' : '', true);
'flagFilter',
values.flag === true ? 'EKSPEDISI' : values.flag === false ? '' : ''
);
filterModal.closeModal(); filterModal.closeModal();
setSubmitting(false); setSubmitting(false);
}, },
onReset: () => { });
updateFilter('categoryFilter', '');
updateFilter('flagFilter', ''); const formikResetHandler = () => {
formik.setFieldValue('flag', false); updateFilter('categoryFilter', undefined, true);
updateFilter('flagFilter', '', true);
formik.resetForm({
values: {
category: undefined,
flag: false,
}, },
}); });
filterModal.closeModal();
};
const { setFieldValue } = formik; const { setFieldValue } = formik;
// ===== CATEGORY OPTIONS (SAPRONAK or BOP) ===== // ===== CATEGORY OPTIONS (SAPRONAK or BOP) =====
@@ -187,15 +196,11 @@ const SuppliersTable = () => {
); );
// ===== FILTER HANDLERS ===== // ===== FILTER HANDLERS =====
const handleFilterCategoryChange = useCallback( const handleFilterCategoryChange = (
(val: OptionType | OptionType[] | null) => { val: OptionType | OptionType[] | null
const option = val as OptionType | null; ) => {
const categoryId = option?.value ? String(option.value) : null; setFieldValue('category', val);
};
setFieldValue('category_id', categoryId);
},
[setFieldValue]
);
const handleFilterFlagChange = useCallback( const handleFilterFlagChange = useCallback(
(val: OptionType | OptionType[] | null) => { (val: OptionType | OptionType[] | null) => {
@@ -213,13 +218,13 @@ const SuppliersTable = () => {
); );
// ===== FILTER HELPERS ===== // ===== FILTER HELPERS =====
const categoryIdValue = useMemo(() => { // const categoryIdValue = useMemo(() => {
if (!formik.values.category_id) return null; // if (!formik.values.category_id) return null;
return ( // return (
categoryOptions.find((opt) => opt.value === formik.values.category_id) || // categoryOptions.find((opt) => opt.value === formik.values.category_id) ||
null // null
); // );
}, [formik.values.category_id, categoryOptions]); // }, [formik.values.category_id, categoryOptions]);
const flagValue = useMemo(() => { const flagValue = useMemo(() => {
if (formik.values.flag === null) return null; if (formik.values.flag === null) return null;
@@ -243,14 +248,6 @@ const SuppliersTable = () => {
} }
}, [filterModal.open, tableFilterState.flagFilter, setFieldValue]); }, [filterModal.open, tableFilterState.flagFilter, setFieldValue]);
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
useEffect(() => {
setTableState('suppliers-table', pathname);
}, [pathname, setTableState]);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const { const {
@@ -269,8 +266,7 @@ const SuppliersTable = () => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value); updateFilter('search', e.target.value, true);
updateFilter('search', e.target.value);
}; };
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -491,13 +487,13 @@ const SuppliersTable = () => {
<Icon icon='heroicons:x-mark' width={20} height={20} /> <Icon icon='heroicons:x-mark' width={20} height={20} />
</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-1.5'> <div className='p-4 flex flex-col gap-1.5'>
<SelectInputRadio <SelectInputRadio
label='Kategori' label='Kategori'
placeholder='Pilih Kategori' placeholder='Pilih Kategori'
options={categoryOptions} options={categoryOptions}
value={categoryIdValue} value={formik.values.category}
onChange={handleFilterCategoryChange} onChange={handleFilterCategoryChange}
isClearable isClearable
className={{ wrapper: 'w-full' }} className={{ wrapper: 'w-full' }}
@@ -517,13 +513,9 @@ const SuppliersTable = () => {
{/* Modal Footer */} {/* Modal Footer */}
<div className='flex justify-between items-center gap-4 p-4 border-t border-base-content/10 bg-gray-50'> <div className='flex justify-between items-center gap-4 p-4 border-t border-base-content/10 bg-gray-50'>
<Button <Button
type='button' type='reset'
variant='soft' variant='soft'
className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2' className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2'
onClick={() => {
formik.resetForm();
filterModal.closeModal();
}}
> >
Reset Filter Reset Filter
</Button> </Button>
@@ -1,11 +1,16 @@
import { string, boolean, object } from 'yup'; import { OptionType } from '@/components/input/SelectInput';
import * as Yup from 'yup';
export const SupplierFilterSchema = object().shape({ export const SupplierFilterSchema = Yup.object().shape({
category_id: string().nullable(), category: Yup.object({
flag: boolean().nullable(), value: Yup.string().required(),
label: Yup.string().required(),
}).nullable(),
flag: Yup.boolean().nullable(),
}); });
export type SupplierFilterType = { export type SupplierFilterType = {
category_id: string | null; category?: OptionType<string>;
flag: boolean | null; flag: boolean | null;
}; };
@@ -1,6 +1,6 @@
'use client'; 'use client';
import { ChangeEventHandler, useEffect, useMemo, useState } from 'react'; import { ChangeEventHandler, useMemo, useState } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -20,8 +20,6 @@ import { Uom } from '@/types/api/master-data/uom';
import { UomApi } from '@/services/api/master-data'; import { UomApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { usePathname } from 'next/navigation';
import { useUiStore } from '@/stores/ui/ui.store';
const RowOptionsMenu = ({ const RowOptionsMenu = ({
popoverPosition = 'bottom', popoverPosition = 'bottom',
@@ -103,9 +101,6 @@ const RowOptionsMenu = ({
}; };
const UomsTable = () => { const UomsTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter, updateFilter,
@@ -114,22 +109,16 @@ const UomsTable = () => {
toQueryString: getTableFilterQueryString, toQueryString: getTableFilterQueryString,
} = useTableFilter({ } = useTableFilter({
initial: { initial: {
search: searchValue, search: '',
}, },
paramMap: { paramMap: {
page: 'page', page: 'page',
pageSize: 'limit', pageSize: 'limit',
}, },
persist: true,
storeName: 'uom-table',
}); });
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
useEffect(() => {
setTableState('uoms-table', pathname);
}, [pathname, setTableState]);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const { const {
@@ -146,8 +135,7 @@ const UomsTable = () => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value); updateFilter('search', e.target.value, true);
updateFilter('search', e.target.value);
}; };
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -1,13 +1,6 @@
'use client'; 'use client';
import { import { ChangeEventHandler, useCallback, useMemo, useState } from 'react';
ChangeEventHandler,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { usePathname } from 'next/navigation';
import useSWR from 'swr'; import useSWR from 'swr';
import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table'; import { CellContext, ColumnDef, SortingState } from '@tanstack/react-table';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
@@ -31,7 +24,6 @@ import { Warehouse } from '@/types/api/master-data/warehouse';
import { WarehouseApi, AreaApi } from '@/services/api/master-data'; import { WarehouseApi, AreaApi } from '@/services/api/master-data';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { useUiStore } from '@/stores/ui/ui.store';
import { import {
WarehouseFilterSchema, WarehouseFilterSchema,
WarehouseFilterType, WarehouseFilterType,
@@ -120,9 +112,6 @@ const RowOptionsMenu = ({
}; };
const WarehousesTable = () => { const WarehousesTable = () => {
const { searchValue, setSearchValue, setTableState } = useUiStore();
const pathname = usePathname();
const { const {
state: tableFilterState, state: tableFilterState,
updateFilter, updateFilter,
@@ -141,6 +130,8 @@ const WarehousesTable = () => {
areaFilter: 'area_id', areaFilter: 'area_id',
activeProjectFlockFilter: 'active_project_flock', activeProjectFlockFilter: 'active_project_flock',
}, },
persist: true,
storeName: 'warehouses-table',
}); });
// ===== FILTER MODAL STATE ===== // ===== FILTER MODAL STATE =====
@@ -149,27 +140,36 @@ const WarehousesTable = () => {
// ===== FORMIK SETUP ===== // ===== FORMIK SETUP =====
const formik = useFormik<WarehouseFilterType>({ const formik = useFormik<WarehouseFilterType>({
initialValues: { initialValues: {
area_id: null, area_id: tableFilterState.areaFilter || null,
active_project_flock: false, active_project_flock:
tableFilterState.activeProjectFlockFilter === 'true',
}, },
validationSchema: WarehouseFilterSchema, validationSchema: WarehouseFilterSchema,
onSubmit: (values, { setSubmitting }) => { onSubmit: (values, { setSubmitting }) => {
updateFilter('areaFilter', values.area_id || ''); updateFilter('areaFilter', values.area_id || '', true);
updateFilter( updateFilter(
'activeProjectFlockFilter', 'activeProjectFlockFilter',
values.active_project_flock === true ? 'true' : '' values.active_project_flock === true ? 'true' : '',
true
); );
filterModal.closeModal(); filterModal.closeModal();
setSubmitting(false); setSubmitting(false);
}, },
onReset: () => { });
updateFilter('areaFilter', '');
updateFilter('activeProjectFlockFilter', ''); const formikResetHandler = () => {
formik.setFieldValue('active_project_flock', false); updateFilter('areaFilter', '', true);
updateFilter('activeProjectFlockFilter', '', true);
formik.resetForm({
values: {
area_id: null,
active_project_flock: false,
}, },
}); });
const { setFieldValue } = formik; filterModal.closeModal();
};
// ===== AREA OPTIONS ===== // ===== AREA OPTIONS =====
const { const {
@@ -243,26 +243,6 @@ const WarehousesTable = () => {
formik.validateForm(); formik.validateForm();
}; };
useEffect(() => {
if (filterModal.open) {
const activeProjectFlockValue =
tableFilterState.activeProjectFlockFilter === 'true' ? true : false; // Default ke false (Semua Kandang)
setFieldValue('active_project_flock', activeProjectFlockValue);
}
}, [
filterModal.open,
tableFilterState.activeProjectFlockFilter,
setFieldValue,
]);
useEffect(() => {
updateFilter('search', searchValue);
}, [searchValue, updateFilter]);
useEffect(() => {
setTableState('warehouses-table', pathname);
}, [pathname, setTableState]);
const [sorting, setSorting] = useState<SortingState>([]); const [sorting, setSorting] = useState<SortingState>([]);
const { const {
@@ -281,8 +261,7 @@ const WarehousesTable = () => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setSearchValue(e.target.value); updateFilter('search', e.target.value, true);
updateFilter('search', e.target.value);
}; };
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -507,7 +486,7 @@ const WarehousesTable = () => {
<Icon icon='heroicons:x-mark' width={20} height={20} /> <Icon icon='heroicons:x-mark' width={20} height={20} />
</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-1.5'> <div className='p-4 flex flex-col gap-1.5'>
<SelectInput <SelectInput
label='Area' label='Area'
@@ -538,10 +517,7 @@ const WarehousesTable = () => {
type='button' type='button'
variant='soft' variant='soft'
className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2' className='rounded-lg text-base-content/65 bg-transparent border-none hover:bg-base-content/10 hover:text-base-content/65 transition-colors px-3 py-2'
onClick={() => { onClick={formikResetHandler}
formik.resetForm();
filterModal.closeModal();
}}
> >
Reset Filter Reset Filter
</Button> </Button>