mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 21:41:57 +00:00
Merge branch 'feat/select-input-lazy-loading' into 'development'
[FEAT/FE] Implement SelectInput lazy loading See merge request mbugroup/lti-web-client!173
This commit is contained in:
@@ -9,15 +9,20 @@ import Select, {
|
||||
SingleValue,
|
||||
components as ReactSelectComponents,
|
||||
ControlProps,
|
||||
MenuListProps,
|
||||
} from 'react-select';
|
||||
import CreatableSelect from 'react-select/creatable';
|
||||
import makeAnimated from 'react-select/animated';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
import { cn, getByPath } from '@/lib/helper';
|
||||
import useSWR from 'swr';
|
||||
import useSWRInfinite from 'swr/infinite';
|
||||
import { httpClientFetcher } from '@/services/http/client';
|
||||
import { BaseApiResponse } from '@/types/api/api-general';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import {
|
||||
BaseApiResponse,
|
||||
ErrorApiResponse,
|
||||
SuccessApiResponse,
|
||||
} from '@/types/api/api-general';
|
||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||
|
||||
export interface OptionType {
|
||||
value: string | number;
|
||||
@@ -56,6 +61,7 @@ interface SelectInputBaseProps<T = OptionType> {
|
||||
onInputChange?: (search: string) => void;
|
||||
startAdornment?: ReactNode;
|
||||
menuPortalTarget?: HTMLElement | null;
|
||||
onMenuScrollToBottom?: ((event: WheelEvent | TouchEvent) => void) | undefined;
|
||||
}
|
||||
|
||||
interface SelectInputProps<T = OptionType> extends SelectInputBaseProps<T> {
|
||||
@@ -93,6 +99,29 @@ const CustomControl = <
|
||||
);
|
||||
};
|
||||
|
||||
const CustomMenuList = <
|
||||
Option,
|
||||
IsMulti extends boolean,
|
||||
Group extends GroupBase<Option>,
|
||||
>(
|
||||
props: MenuListProps<Option, IsMulti, Group>
|
||||
) => {
|
||||
const { children, selectProps, options } = props;
|
||||
const { isLoading } = selectProps;
|
||||
|
||||
return (
|
||||
<ReactSelectComponents.MenuList {...props}>
|
||||
{children}
|
||||
|
||||
{options.length > 0 && isLoading && (
|
||||
<div className='mt-1 px-3 py-2 rounded-md text-center text-gray-400'>
|
||||
<span className='loading loading-spinner loading-md' />
|
||||
</div>
|
||||
)}
|
||||
</ReactSelectComponents.MenuList>
|
||||
);
|
||||
};
|
||||
|
||||
const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
||||
const {
|
||||
label,
|
||||
@@ -119,6 +148,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
||||
onInputChange,
|
||||
startAdornment,
|
||||
menuPortalTarget,
|
||||
onMenuScrollToBottom,
|
||||
} = props;
|
||||
|
||||
const [internalInputValue, setInternalInputValue] = useState('');
|
||||
@@ -256,6 +286,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
||||
components={{
|
||||
...components,
|
||||
...(optionComponent ? { Option: optionComponent } : {}),
|
||||
MenuList: CustomMenuList,
|
||||
}}
|
||||
{...(startAdornment && {
|
||||
shouldShowAdornment,
|
||||
@@ -269,6 +300,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
||||
styles={{
|
||||
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
|
||||
}}
|
||||
onMenuScrollToBottom={onMenuScrollToBottom}
|
||||
/>
|
||||
|
||||
{isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
|
||||
@@ -288,34 +320,96 @@ const useSelect = <T,>(
|
||||
) => {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
const optionsUrlParams = useMemo(() => {
|
||||
return new URLSearchParams({
|
||||
const pageKey = 'page';
|
||||
const limitKey = 'limit';
|
||||
const limit = params?.['limit'] ?? 10;
|
||||
|
||||
const getKey = (
|
||||
pageIndex: number,
|
||||
previousPageData?: BaseApiResponse<T[]>
|
||||
) => {
|
||||
// stop when backend says no more pages
|
||||
if (previousPageData && isResponseSuccess(previousPageData)) {
|
||||
const meta = previousPageData.meta;
|
||||
if (meta && meta.page >= meta.total_pages) return null;
|
||||
}
|
||||
|
||||
const qs = new URLSearchParams({
|
||||
...(params ?? {}),
|
||||
[searchKey]: inputValue ?? '',
|
||||
...params,
|
||||
[pageKey]: String(pageIndex + 1),
|
||||
[limitKey]: String(limit),
|
||||
}).toString();
|
||||
}, [inputValue, searchKey, params]);
|
||||
|
||||
const optionsUrl = `${basePath}?${optionsUrlParams}`;
|
||||
return `${basePath}?${qs}`;
|
||||
};
|
||||
|
||||
const { data, isLoading } = useSWR(optionsUrl, async (url) => {
|
||||
return await httpClientFetcher<BaseApiResponse<T[]>>(url);
|
||||
});
|
||||
const {
|
||||
data: pages,
|
||||
isLoading,
|
||||
isValidating,
|
||||
size,
|
||||
setSize,
|
||||
} = useSWRInfinite<BaseApiResponse<T[]>>(getKey, (url) =>
|
||||
httpClientFetcher<BaseApiResponse<T[]>>(url)
|
||||
);
|
||||
|
||||
const options = isResponseSuccess(data)
|
||||
? data.data.map((item) => {
|
||||
return {
|
||||
value: getByPath<T, number>(item, valueKey as string),
|
||||
label: getByPath<T, string>(item, labelKey as string),
|
||||
};
|
||||
})
|
||||
: [];
|
||||
const options = useMemo(() => {
|
||||
if (!pages) return [];
|
||||
|
||||
return pages.flatMap((page) =>
|
||||
isResponseSuccess(page)
|
||||
? page.data.map((item) => ({
|
||||
value: getByPath<T, number>(item, valueKey as string),
|
||||
label: getByPath<T, string>(item, labelKey as string),
|
||||
}))
|
||||
: []
|
||||
);
|
||||
}, [pages, valueKey, labelKey]);
|
||||
|
||||
const lastPage = pages?.[pages.length - 1];
|
||||
const hasMore =
|
||||
!!lastPage &&
|
||||
isResponseSuccess(lastPage) &&
|
||||
!!lastPage.meta &&
|
||||
lastPage.meta.page < lastPage.meta.total_pages;
|
||||
|
||||
const loadMore = () => {
|
||||
if (!hasMore) return;
|
||||
setSize(size + 1);
|
||||
};
|
||||
|
||||
let formattedSuccessRawData: SuccessApiResponse<T[]> | undefined = undefined;
|
||||
let formattedErrorRawData: ErrorApiResponse | undefined = undefined;
|
||||
|
||||
const latestPagesIndex = pages?.length ? pages.length - 1 : 0;
|
||||
|
||||
if (isResponseSuccess(pages?.[latestPagesIndex])) {
|
||||
formattedSuccessRawData = {
|
||||
...pages?.[latestPagesIndex],
|
||||
data:
|
||||
pages?.flatMap((page) => (isResponseSuccess(page) ? page.data : [])) ??
|
||||
[],
|
||||
};
|
||||
}
|
||||
|
||||
if (isResponseError(pages?.[latestPagesIndex])) {
|
||||
formattedErrorRawData = pages?.[latestPagesIndex];
|
||||
}
|
||||
|
||||
return {
|
||||
inputValue,
|
||||
setInputValue,
|
||||
|
||||
options,
|
||||
isLoadingOptions: isLoading,
|
||||
rawData: data,
|
||||
rawData: isResponseSuccess(pages?.[latestPagesIndex])
|
||||
? formattedSuccessRawData
|
||||
: formattedErrorRawData,
|
||||
|
||||
isLoadingOptions: isLoading || isValidating,
|
||||
isLoadingMore: isValidating && size > 1,
|
||||
hasMore,
|
||||
loadMore,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -23,13 +23,17 @@ import TextInput from '@/components/input/TextInput';
|
||||
import { cn } from '@/lib/helper';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import TextArea from '@/components/input/TextArea';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import SelectInput, {
|
||||
OptionType,
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
import useSWR from 'swr';
|
||||
import { UserApi } from '@/services/api/user';
|
||||
import { TYPE_OPTIONS } from '@/config/constant';
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { User } from '@/types/api/api-general';
|
||||
|
||||
interface CustomerFormProps {
|
||||
formType?: 'add' | 'edit' | 'detail';
|
||||
@@ -47,25 +51,15 @@ const CustomerForm = ({
|
||||
// Setup State
|
||||
const [customerFormErrorMessage, setCustomerFormErrorMessage] = useState('');
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
const [picSelectInputValue, setPicSelectInputValue] = useState('');
|
||||
|
||||
// Fetch Data
|
||||
const picUrl = `${UserApi.basePath}?${new URLSearchParams({
|
||||
search: picSelectInputValue ?? '',
|
||||
})}`;
|
||||
|
||||
const { data: pic, isLoading: isLoadingPic } = useSWR(
|
||||
picUrl,
|
||||
UserApi.getAllFetcher
|
||||
);
|
||||
const {
|
||||
setInputValue: setPicSelectInputValue,
|
||||
options: picOptions,
|
||||
isLoadingOptions: isLoadingPicOptions,
|
||||
loadMore: loadMorePic,
|
||||
} = useSelect<User>(UserApi.basePath, 'id', 'name');
|
||||
|
||||
// -- Options data mapping
|
||||
const picOptions = isResponseSuccess(pic)
|
||||
? pic?.data.map((area) => ({
|
||||
value: area.id,
|
||||
label: area.name,
|
||||
}))
|
||||
: [];
|
||||
const typeOptions = TYPE_OPTIONS;
|
||||
|
||||
// Handler Event
|
||||
@@ -240,11 +234,12 @@ const CustomerForm = ({
|
||||
required
|
||||
placeholder='Pilih PIC'
|
||||
label='PIC'
|
||||
value={formik.values.pic ?? undefined}
|
||||
value={formik.values.pic?.value ? formik.values.pic : undefined}
|
||||
onChange={picChangeHandler}
|
||||
options={picOptions}
|
||||
onInputChange={setPicSelectInputValue}
|
||||
isLoading={isLoadingPic}
|
||||
onMenuScrollToBottom={loadMorePic}
|
||||
isLoading={isLoadingPicOptions}
|
||||
isError={formik.touched.picId && Boolean(formik.errors.picId)}
|
||||
errorMessage={formik.errors.picId as string}
|
||||
isDisabled={formType === 'detail'}
|
||||
|
||||
@@ -9,7 +9,10 @@ import useSWR from 'swr';
|
||||
import { Icon } from '@iconify/react';
|
||||
import Button from '@/components/Button';
|
||||
import TextInput from '@/components/input/TextInput';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import SelectInput, {
|
||||
OptionType,
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
@@ -31,6 +34,7 @@ import { UserApi } from '@/services/api/user';
|
||||
import NumberInput from '@/components/input/NumberInput';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { User } from '@/types/api/api-general';
|
||||
|
||||
interface KandangFormProps {
|
||||
type?: 'add' | 'edit' | 'detail';
|
||||
@@ -128,23 +132,12 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
|
||||
const { setValues: formikSetValues } = formik;
|
||||
|
||||
// location
|
||||
const [locationSelectInputValue, setLocationSelectInputValue] = useState('');
|
||||
|
||||
const locationsUrl = `${LocationApi.basePath}?${new URLSearchParams({
|
||||
search: locationSelectInputValue ?? '',
|
||||
}).toString()}`;
|
||||
|
||||
const { data: locations, isLoading: isLoadingLocations } = useSWR(
|
||||
locationsUrl,
|
||||
LocationApi.getAllFetcher
|
||||
);
|
||||
|
||||
const locationOptions = isResponseSuccess(locations)
|
||||
? locations?.data.map((location) => ({
|
||||
value: location.id,
|
||||
label: location.name,
|
||||
}))
|
||||
: [];
|
||||
const {
|
||||
setInputValue: setLocationSelectInputValue,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocationOptions,
|
||||
loadMore: loadMoreLocations,
|
||||
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||
|
||||
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
formik.setFieldTouched('location', true);
|
||||
@@ -155,23 +148,12 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
|
||||
};
|
||||
|
||||
// PIC
|
||||
const [picSelectInputValue, setPicSelectInputValue] = useState('');
|
||||
|
||||
const picsUrl = `${UserApi.basePath}?${new URLSearchParams({
|
||||
search: picSelectInputValue ?? '',
|
||||
}).toString()}`;
|
||||
|
||||
const { data: pics, isLoading: isLoadingPics } = useSWR(
|
||||
picsUrl,
|
||||
LocationApi.getAllFetcher
|
||||
);
|
||||
|
||||
const picOptions = isResponseSuccess(pics)
|
||||
? pics?.data.map((pic) => ({
|
||||
value: pic.id,
|
||||
label: pic.name,
|
||||
}))
|
||||
: [];
|
||||
const {
|
||||
setInputValue: setPicSelectInputValue,
|
||||
options: picOptions,
|
||||
isLoadingOptions: isLoadingPicOptions,
|
||||
loadMore: loadMorePics,
|
||||
} = useSelect<User>(UserApi.basePath, 'id', 'name');
|
||||
|
||||
const picChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
formik.setFieldTouched('pic', true);
|
||||
@@ -249,7 +231,8 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
|
||||
onChange={locationChangeHandler}
|
||||
options={locationOptions}
|
||||
onInputChange={setLocationSelectInputValue}
|
||||
isLoading={isLoadingLocations}
|
||||
onMenuScrollToBottom={loadMoreLocations}
|
||||
isLoading={isLoadingLocationOptions}
|
||||
isError={
|
||||
formik.touched.locationId && Boolean(formik.errors.locationId)
|
||||
}
|
||||
@@ -280,7 +263,8 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
|
||||
onChange={picChangeHandler}
|
||||
options={picOptions}
|
||||
onInputChange={setPicSelectInputValue}
|
||||
isLoading={isLoadingPics}
|
||||
onMenuScrollToBottom={loadMorePics}
|
||||
isLoading={isLoadingPicOptions}
|
||||
isError={formik.touched.picId && Boolean(formik.errors.picId)}
|
||||
errorMessage={formik.errors.picId as string}
|
||||
isDisabled={type === 'detail'}
|
||||
|
||||
@@ -9,7 +9,10 @@ import useSWR from 'swr';
|
||||
import { Icon } from '@iconify/react';
|
||||
import Button from '@/components/Button';
|
||||
import TextInput from '@/components/input/TextInput';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import SelectInput, {
|
||||
OptionType,
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
@@ -29,6 +32,7 @@ import { AreaApi, LocationApi } from '@/services/api/master-data';
|
||||
import { cn } from '@/lib/helper';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { Area } from '@/types/api/master-data/area';
|
||||
|
||||
interface LocationFormProps {
|
||||
type?: 'add' | 'edit' | 'detail';
|
||||
@@ -117,23 +121,12 @@ const LocationForm = ({ type = 'add', initialValues }: LocationFormProps) => {
|
||||
|
||||
const { setValues: formikSetValues } = formik;
|
||||
|
||||
const [areaSelectInputValue, setAreaSelectInputValue] = useState('');
|
||||
|
||||
const areasUrl = `${AreaApi.basePath}?${new URLSearchParams({
|
||||
search: areaSelectInputValue ?? '',
|
||||
}).toString()}`;
|
||||
|
||||
const { data: areas, isLoading: isLoadingAreas } = useSWR(
|
||||
areasUrl,
|
||||
AreaApi.getAllFetcher
|
||||
);
|
||||
|
||||
const areaOptions = isResponseSuccess(areas)
|
||||
? areas?.data.map((area) => ({
|
||||
value: area.id,
|
||||
label: area.name,
|
||||
}))
|
||||
: [];
|
||||
const {
|
||||
setInputValue: setAreaSelectInputValue,
|
||||
options: areaOptions,
|
||||
isLoadingOptions: isLoadingAreaOptions,
|
||||
loadMore: loadMoreAreas,
|
||||
} = useSelect<Area>(AreaApi.basePath, 'id', 'name');
|
||||
|
||||
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
formik.setFieldTouched('area', true);
|
||||
@@ -224,7 +217,8 @@ const LocationForm = ({ type = 'add', initialValues }: LocationFormProps) => {
|
||||
onChange={areaChangeHandler}
|
||||
options={areaOptions}
|
||||
onInputChange={setAreaSelectInputValue}
|
||||
isLoading={isLoadingAreas}
|
||||
onMenuScrollToBottom={loadMoreAreas}
|
||||
isLoading={isLoadingAreaOptions}
|
||||
isError={formik.touched.areaId && Boolean(formik.errors.areaId)}
|
||||
errorMessage={formik.errors.areaId as string}
|
||||
isDisabled={type === 'detail'}
|
||||
|
||||
@@ -9,7 +9,10 @@ import useSWR from 'swr';
|
||||
import { Icon } from '@iconify/react';
|
||||
import Button from '@/components/Button';
|
||||
import TextInput from '@/components/input/TextInput';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import SelectInput, {
|
||||
OptionType,
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
@@ -31,6 +34,8 @@ import { flags } from '@/types/api/api-general';
|
||||
import { SUPPLIER_FLAG_OPTIONS } from '@/config/constant';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { Supplier } from '@/types/api/master-data/supplier';
|
||||
import { Uom } from '@/types/api/master-data/uom';
|
||||
|
||||
interface NonstockFormProps {
|
||||
type?: 'add' | 'edit' | 'detail';
|
||||
@@ -129,23 +134,12 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
|
||||
const { setValues: formikSetValues } = formik;
|
||||
|
||||
// UOM
|
||||
const [uomSelectInputValue, setUomSelectInputValue] = useState('');
|
||||
|
||||
const uomsUrl = `${UomApi.basePath}?${new URLSearchParams({
|
||||
search: uomSelectInputValue ?? '',
|
||||
}).toString()}`;
|
||||
|
||||
const { data: uoms, isLoading: isLoadingUoms } = useSWR(
|
||||
uomsUrl,
|
||||
UomApi.getAllFetcher
|
||||
);
|
||||
|
||||
const uomOptions = isResponseSuccess(uoms)
|
||||
? uoms?.data.map((uom) => ({
|
||||
value: uom.id,
|
||||
label: uom.name,
|
||||
}))
|
||||
: [];
|
||||
const {
|
||||
setInputValue: setUomSelectInputValue,
|
||||
options: uomOptions,
|
||||
isLoadingOptions: isLoadingUomOptions,
|
||||
loadMore: loadMoreUoms,
|
||||
} = useSelect<Uom>(UomApi.basePath, 'id', 'name');
|
||||
|
||||
const uomChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
formik.setFieldTouched('uom', true);
|
||||
@@ -156,25 +150,12 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
|
||||
};
|
||||
|
||||
// supplier
|
||||
const [supplierSelectInputValue, setSupplierSelectInputValue] = useState('');
|
||||
|
||||
const suppliersUrl = `${SupplierApi.basePath}?${new URLSearchParams({
|
||||
search: supplierSelectInputValue ?? '',
|
||||
}).toString()}`;
|
||||
|
||||
const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR(
|
||||
suppliersUrl,
|
||||
SupplierApi.getAllFetcher
|
||||
);
|
||||
|
||||
const supplierOptions = isResponseSuccess(suppliers)
|
||||
? suppliers?.data
|
||||
.filter((sup) => sup.category === 'BOP')
|
||||
.map((supplier) => ({
|
||||
value: supplier.id,
|
||||
label: supplier.name,
|
||||
}))
|
||||
: [];
|
||||
const {
|
||||
setInputValue: setSupplierSelectInputValue,
|
||||
options: supplierOptions,
|
||||
isLoadingOptions: isLoadingSupplierOptions,
|
||||
loadMore: loadMoreSuppliers,
|
||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
||||
|
||||
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
formik.setFieldTouched('suppliers', true);
|
||||
@@ -264,7 +245,8 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
|
||||
onChange={uomChangeHandler}
|
||||
options={uomOptions}
|
||||
onInputChange={setUomSelectInputValue}
|
||||
isLoading={isLoadingUoms}
|
||||
isLoading={isLoadingUomOptions}
|
||||
onMenuScrollToBottom={loadMoreUoms}
|
||||
isError={formik.touched.uomId && Boolean(formik.errors.uomId)}
|
||||
errorMessage={formik.errors.uomId as string}
|
||||
isDisabled={type === 'detail'}
|
||||
@@ -278,7 +260,8 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
|
||||
onChange={supplierChangeHandler}
|
||||
options={supplierOptions ?? []}
|
||||
onInputChange={setSupplierSelectInputValue}
|
||||
isLoading={isLoadingSuppliers}
|
||||
onMenuScrollToBottom={loadMoreSuppliers}
|
||||
isLoading={isLoadingSupplierOptions}
|
||||
isError={
|
||||
formik.touched.suppliers && Boolean(formik.errors.suppliers)
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
import { cn } from '@/lib/helper';
|
||||
import { PRODUCT_FLAG_OPTIONS } from '@/config/constant';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import { Supplier } from '@/types/api/master-data/supplier';
|
||||
|
||||
interface ProductFormProps {
|
||||
type?: 'add' | 'edit' | 'detail';
|
||||
@@ -145,6 +146,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
setInputValue: setUomSelectInputValue,
|
||||
options: uomOptions,
|
||||
isLoadingOptions: isLoadingUoms,
|
||||
loadMore: loadMoreUoms,
|
||||
} = useSelect(UomApi.basePath, 'id', 'name');
|
||||
const uomChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
formik.setFieldTouched('uom', true);
|
||||
@@ -158,6 +160,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
setInputValue: setCategorySelectInputValue,
|
||||
options: categoryOptions,
|
||||
isLoadingOptions: isLoadingCategories,
|
||||
loadMore: loadMoreCategories,
|
||||
} = useSelect(ProductCategoryApi.basePath, 'id', 'name');
|
||||
const categoryChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
formik.setFieldTouched('product_category', true);
|
||||
@@ -167,17 +170,15 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
};
|
||||
|
||||
// Supplier (multi select) - using SWR to filter by category
|
||||
const [supplierSelectInputValue, setSupplierSelectInputValue] = useState('');
|
||||
const suppliersUrl = `${SupplierApi.basePath}?${new URLSearchParams({ search: supplierSelectInputValue ?? '' }).toString()}`;
|
||||
const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR(
|
||||
suppliersUrl,
|
||||
SupplierApi.getAllFetcher
|
||||
);
|
||||
const supplierOptions = isResponseSuccess(suppliers)
|
||||
? suppliers?.data
|
||||
.filter((sup) => sup.category === 'SAPRONAK')
|
||||
.map((sup) => ({ value: sup.id, label: sup.name }))
|
||||
: [];
|
||||
const {
|
||||
setInputValue: setSupplierSelectInputValue,
|
||||
options: supplierOptions,
|
||||
isLoadingOptions: isLoadingSuppliers,
|
||||
loadMore: loadMoreSuppliers,
|
||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search', {
|
||||
category: 'SAPRONAK',
|
||||
});
|
||||
|
||||
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
const arr = Array.isArray(val) ? val : val ? [val] : [];
|
||||
formik.setFieldTouched('supplier_ids', true);
|
||||
@@ -291,6 +292,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
onChange={uomChangeHandler}
|
||||
options={uomOptions}
|
||||
onInputChange={setUomSelectInputValue}
|
||||
onMenuScrollToBottom={loadMoreUoms}
|
||||
isLoading={isLoadingUoms}
|
||||
isError={
|
||||
(formik.touched.uom || formik.touched.uom_id) &&
|
||||
@@ -308,6 +310,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
onChange={categoryChangeHandler}
|
||||
options={categoryOptions}
|
||||
onInputChange={setCategorySelectInputValue}
|
||||
onMenuScrollToBottom={loadMoreCategories}
|
||||
isLoading={isLoadingCategories}
|
||||
isError={
|
||||
(formik.touched.product_category ||
|
||||
@@ -412,6 +415,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
onChange={supplierChangeHandler}
|
||||
options={supplierOptions}
|
||||
onInputChange={setSupplierSelectInputValue}
|
||||
onMenuScrollToBottom={loadMoreSuppliers}
|
||||
isLoading={isLoadingSuppliers}
|
||||
isError={
|
||||
formik.touched.supplier_ids &&
|
||||
|
||||
@@ -9,7 +9,10 @@ import useSWR from 'swr';
|
||||
import { Icon } from '@iconify/react';
|
||||
import Button from '@/components/Button';
|
||||
import TextInput from '@/components/input/TextInput';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import SelectInput, {
|
||||
OptionType,
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
import { useModal } from '@/components/Modal';
|
||||
import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
@@ -35,6 +38,8 @@ import { cn } from '@/lib/helper';
|
||||
import { WAREHOUSE_TYPE_OPTIONS } from '@/config/constant';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { Area } from '@/types/api/master-data/area';
|
||||
import { Kandang } from '@/types/api/master-data/kandang';
|
||||
|
||||
interface WarehouseFormProps {
|
||||
type?: 'add' | 'edit' | 'detail';
|
||||
@@ -221,61 +226,28 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
|
||||
const { setValues: formikSetValues } = formik;
|
||||
|
||||
// Area
|
||||
const [areaSelectInputValue, setAreaSelectInputValue] = useState('');
|
||||
|
||||
const areasUrl = `${AreaApi.basePath}?${new URLSearchParams({
|
||||
search: areaSelectInputValue ?? '',
|
||||
}).toString()}`;
|
||||
|
||||
const { data: areas, isLoading: isLoadingAreas } = useSWR(
|
||||
areasUrl,
|
||||
AreaApi.getAllFetcher
|
||||
);
|
||||
|
||||
const areaOptions = isResponseSuccess(areas)
|
||||
? areas?.data.map((area) => ({
|
||||
value: area.id,
|
||||
label: area.name,
|
||||
}))
|
||||
: [];
|
||||
const {
|
||||
setInputValue: setAreaSelectInputValue,
|
||||
options: areaOptions,
|
||||
isLoadingOptions: isLoadingAreaOptions,
|
||||
loadMore: loadMoreAreas,
|
||||
} = useSelect<Area>(AreaApi.basePath, 'id', 'name');
|
||||
|
||||
// Location
|
||||
const [locationSelectInputValue, setLocationSelectInputValue] = useState('');
|
||||
|
||||
const locationsUrl = `${LocationApi.basePath}?${new URLSearchParams({
|
||||
search: locationSelectInputValue ?? '',
|
||||
}).toString()}`;
|
||||
|
||||
const { data: locations, isLoading: isLoadingLocations } = useSWR(
|
||||
locationsUrl,
|
||||
LocationApi.getAllFetcher
|
||||
);
|
||||
|
||||
const locationOptions = isResponseSuccess(locations)
|
||||
? locations?.data.map((location) => ({
|
||||
value: location.id,
|
||||
label: location.name,
|
||||
}))
|
||||
: [];
|
||||
const {
|
||||
setInputValue: setLocationSelectInputValue,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocationOptions,
|
||||
loadMore: loadMoreLocations,
|
||||
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||
|
||||
// Kandang
|
||||
const [kandangSelectInputValue, setKandangSelectInputValue] = useState('');
|
||||
|
||||
const kandangsUrl = `${KandangApi.basePath}?${new URLSearchParams({
|
||||
search: kandangSelectInputValue ?? '',
|
||||
}).toString()}`;
|
||||
|
||||
const { data: kandangs, isLoading: isLoadingKandangs } = useSWR(
|
||||
kandangsUrl,
|
||||
KandangApi.getAllFetcher
|
||||
);
|
||||
|
||||
const kandangOptions = isResponseSuccess(kandangs)
|
||||
? kandangs?.data.map((kandang) => ({
|
||||
value: kandang.id,
|
||||
label: kandang.name,
|
||||
}))
|
||||
: [];
|
||||
const {
|
||||
setInputValue: setKandangSelectInputValue,
|
||||
options: kandangOptions,
|
||||
isLoadingOptions: isLoadingKandangOptions,
|
||||
loadMore: loadMoreKandangs,
|
||||
} = useSelect<Kandang>(KandangApi.basePath, 'id', 'name');
|
||||
|
||||
const typeChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
formik.setFieldTouched('type', true);
|
||||
@@ -393,7 +365,8 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
|
||||
onChange={areaChangeHandler}
|
||||
options={areaOptions}
|
||||
onInputChange={setAreaSelectInputValue}
|
||||
isLoading={isLoadingAreas}
|
||||
onMenuScrollToBottom={loadMoreAreas}
|
||||
isLoading={isLoadingAreaOptions}
|
||||
isError={formik.touched.areaId && Boolean(formik.errors.areaId)}
|
||||
errorMessage={formik.errors.areaId as string}
|
||||
isDisabled={type === 'detail'}
|
||||
@@ -409,7 +382,8 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
|
||||
onChange={locationChangeHandler}
|
||||
options={locationOptions}
|
||||
onInputChange={setLocationSelectInputValue}
|
||||
isLoading={isLoadingLocations}
|
||||
onMenuScrollToBottom={loadMoreLocations}
|
||||
isLoading={isLoadingLocationOptions}
|
||||
isError={
|
||||
formik.touched.locationId && Boolean(formik.errors.locationId)
|
||||
}
|
||||
@@ -427,7 +401,8 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
|
||||
onChange={kandangChangeHandler}
|
||||
options={kandangOptions}
|
||||
onInputChange={setKandangSelectInputValue}
|
||||
isLoading={isLoadingKandangs}
|
||||
onMenuScrollToBottom={loadMoreKandangs}
|
||||
isLoading={isLoadingKandangOptions}
|
||||
isError={
|
||||
formik.touched.kandangId && Boolean(formik.errors.kandangId)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user