feat(multi-select): enhance multi-select component with search and load more functionality

- Added onSearch and onLoadMore props to the MultiSelect component for improved user interaction.
- Refactored state management to handle selected items and search queries more effectively.
- Updated the CreateNotificationForm to integrate the new multi-select features for recipient selection.
- Implemented infinite scrolling logic in the useCompaniesInfiniteQuery, useCustomersInfiniteQuery, and useAdminListInfiniteQuery hooks to support dynamic data loading.
- Enhanced the useCreateNotificationFormPresenter to manage recipient search and loading states.
This commit is contained in:
AmirReza Jamali
2026-05-19 12:43:53 +03:30
parent d34d405cd3
commit 99c7e26894
6 changed files with 318 additions and 129 deletions
+148 -92
View File
@@ -1,7 +1,7 @@
"use client"; "use client";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import React, { useEffect, useId, useRef, useState } from "react"; import React, { useCallback, useEffect, useId, useRef, useState } from "react";
import { import {
type FieldErrors, type FieldErrors,
type FieldValues, type FieldValues,
@@ -12,7 +12,6 @@ import {
interface Option { interface Option {
value: string; value: string;
label: string; label: string;
selected: boolean;
} }
type MultiSelectProps<T extends FieldValues> = { type MultiSelectProps<T extends FieldValues> = {
@@ -26,6 +25,11 @@ type MultiSelectProps<T extends FieldValues> = {
disabled?: boolean; disabled?: boolean;
active?: boolean; active?: boolean;
height?: "sm" | "default"; height?: "sm" | "default";
onSearch?: (query: string) => void;
onLoadMore?: () => void;
hasMore?: boolean;
isLoading?: boolean;
isLoadingMore?: boolean;
} & UseFormRegisterReturn<Path<T>>; } & UseFormRegisterReturn<Path<T>>;
function MultiSelect<T extends FieldValues>({ function MultiSelect<T extends FieldValues>({
@@ -43,84 +47,112 @@ function MultiSelect<T extends FieldValues>({
ref, ref,
value, value,
required, required,
onSearch,
onLoadMore,
hasMore,
isLoading,
isLoadingMore,
}: MultiSelectProps<T>) { }: MultiSelectProps<T>) {
const id = useId(); const id = useId();
const error = errors && errors[name]; const error = errors && errors[name];
const [options, setOptions] = useState<Option[]>([]); const [selectedItems, setSelectedItems] = useState<Option[]>([]);
const [selected, setSelected] = useState<number[]>([]); const [searchQuery, setSearchQuery] = useState("");
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
const listRef = useRef<HTMLDivElement>(null);
const trigger = useRef<HTMLDivElement | null>(null); const trigger = useRef<HTMLDivElement | null>(null);
const initializedRef = useRef(false);
// Initialize selectedItems from external value + items (edit mode)
useEffect(() => { useEffect(() => {
if (initializedRef.current) return;
if (!value || value.length === 0) return;
const safeItems = Array.isArray(items) ? items : []; const safeItems = Array.isArray(items) ? items : [];
const found = safeItems.filter((item) => value.includes(item.value));
const newOptions = safeItems.map((item) => ({ if (found.length > 0) {
...item, setSelectedItems(found);
selected: value?.includes(item.value) ?? false, initializedRef.current = true;
})); }
setOptions(newOptions);
const newSelectedIndices = safeItems
.map((item, index) => (value?.includes(item.value) ? index : -1))
.filter((index) => index !== -1);
setSelected(newSelectedIndices);
}, [items, value]); }, [items, value]);
const open = () => { // Reset when value is cleared externally (form reset)
if (!disabled) { useEffect(() => {
setShow(true); if (!value || value.length === 0) {
setSelectedItems([]);
initializedRef.current = false;
} }
}; }, [value]);
const isOpen = () => { // Update labels for selected items as new pages load
return show === true; useEffect(() => {
const safeItems = Array.isArray(items) ? items : [];
if (!safeItems.length) return;
setSelectedItems((prev) => {
const needsUpdate = prev.some((s) => {
const found = safeItems.find((item) => item.value === s.value);
return found && found.label !== s.label;
});
if (!needsUpdate) return prev;
return prev.map((s) => {
const found = safeItems.find((item) => item.value === s.value);
return found ? { value: found.value, label: found.label } : s;
});
});
}, [items]);
const selectedValueSet = new Set(selectedItems.map((i) => i.value));
const options = (Array.isArray(items) ? items : []).map((item) => ({
...item,
selected: selectedValueSet.has(item.value),
}));
const open = () => {
if (!disabled) setShow(true);
}; };
const combinedRef = (el: HTMLDivElement) => { const combinedRef = (el: HTMLDivElement) => {
trigger.current = el; trigger.current = el;
if (typeof ref === "function") { if (typeof ref === "function") ref(el);
ref(el);
}
}; };
const handleSelect = (index: number) => { const handleSelect = (option: Option) => {
if (disabled) return; if (disabled) return;
const newOptions = [...options]; let newSelected: Option[];
let newSelected = [...selected]; if (selectedValueSet.has(option.value)) {
newSelected = selectedItems.filter((i) => i.value !== option.value);
if (!newOptions[index].selected) {
newOptions[index].selected = true;
newSelected.push(index);
} else { } else {
newOptions[index].selected = false; newSelected = [...selectedItems, option];
newSelected = newSelected.filter((i) => i !== index);
} }
setOptions(newOptions); setSelectedItems(newSelected);
setSelected(newSelected); onChange?.({ target: { name, value: newSelected.map((i) => i.value) } });
const selectedValues = newSelected.map((i) => newOptions[i].value);
onChange?.({ target: { name, value: selectedValues } });
}; };
const remove = (index: number, event: React.MouseEvent) => { const remove = (itemValue: string, event: React.MouseEvent) => {
if (disabled) return; if (disabled) return;
event.stopPropagation(); event.stopPropagation();
const newOptions = [...options]; const newSelected = selectedItems.filter((i) => i.value !== itemValue);
const newSelected = selected.filter((i) => i !== index); setSelectedItems(newSelected);
newOptions[index].selected = false; onChange?.({ target: { name, value: newSelected.map((i) => i.value) } });
setOptions(newOptions);
setSelected(newSelected);
const selectedValues = newSelected.map((i) => newOptions[i].value);
onChange?.({ target: { name, value: selectedValues } });
}; };
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
const q = e.target.value;
setSearchQuery(q);
onSearch?.(q);
};
const handleScroll = useCallback(() => {
if (!listRef.current || !onLoadMore || !hasMore || isLoadingMore) return;
const { scrollTop, scrollHeight, clientHeight } = listRef.current;
if (scrollHeight - scrollTop - clientHeight < 40) {
onLoadMore();
}
}, [onLoadMore, hasMore, isLoadingMore]);
// Close on outside click
useEffect(() => { useEffect(() => {
const clickHandler = ({ target }: MouseEvent) => { const clickHandler = ({ target }: MouseEvent) => {
if (!dropdownRef.current) return; if (!dropdownRef.current) return;
@@ -132,7 +164,6 @@ function MultiSelect<T extends FieldValues>({
return; return;
setShow(false); setShow(false);
}; };
document?.addEventListener("click", clickHandler); document?.addEventListener("click", clickHandler);
return () => document?.removeEventListener("click", clickHandler); return () => document?.removeEventListener("click", clickHandler);
}, [show]); }, [show]);
@@ -163,16 +194,16 @@ function MultiSelect<T extends FieldValues>({
data-active={active} data-active={active}
> >
<div className="flex flex-1 flex-wrap items-center gap-2"> <div className="flex flex-1 flex-wrap items-center gap-2">
{selected.map((index) => ( {selectedItems.map((item) => (
<span <span
key={index} key={item.value}
className="inline-flex items-center gap-1 rounded-md bg-primary/10 px-2.5 py-1 text-sm font-medium text-primary dark:bg-primary/20" className="inline-flex items-center gap-1 rounded-md bg-primary/10 px-2.5 py-1 text-sm font-medium text-primary dark:bg-primary/20"
> >
{options[index]?.label} {item.label}
{!disabled && ( {!disabled && (
<button <button
type="button" type="button"
onClick={(e) => remove(index, e)} onClick={(e) => remove(item.value, e)}
className="ml-1 flex h-4 w-4 items-center justify-center rounded-full text-xs hover:bg-primary/20" className="ml-1 flex h-4 w-4 items-center justify-center rounded-full text-xs hover:bg-primary/20"
> >
× ×
@@ -180,10 +211,8 @@ function MultiSelect<T extends FieldValues>({
)} )}
</span> </span>
))} ))}
{selected.length === 0 && ( {selectedItems.length === 0 && (
<span className="text-dark-6 dark:text-dark-6"> <span className="text-dark-6 dark:text-dark-6">{placeholder}</span>
{placeholder}
</span>
)} )}
</div> </div>
@@ -191,7 +220,7 @@ function MultiSelect<T extends FieldValues>({
<svg <svg
className={cn( className={cn(
"fill-current text-dark-4 transition-transform duration-200 dark:text-dark-6", "fill-current text-dark-4 transition-transform duration-200 dark:text-dark-6",
isOpen() && "rotate-180", show && "rotate-180",
)} )}
width="20" width="20"
height="20" height="20"
@@ -211,51 +240,78 @@ function MultiSelect<T extends FieldValues>({
<div <div
className={cn( className={cn(
"absolute left-0 top-full z-40 mt-1 max-h-60 w-full overflow-y-auto rounded-lg border border-stroke bg-white shadow-lg dark:border-dark-3 dark:bg-dark-2", "absolute left-0 top-full z-40 mt-1 w-full rounded-lg border border-stroke bg-white shadow-lg dark:border-dark-3 dark:bg-dark-2",
!isOpen() && "hidden", !show && "hidden",
)} )}
ref={dropdownRef} ref={dropdownRef}
> >
<div className="py-1"> {onSearch && (
{options.length === 0 ? ( <div className="border-b border-stroke p-2 dark:border-dark-3">
<input
type="text"
value={searchQuery}
onChange={handleSearch}
onClick={(e) => e.stopPropagation()}
placeholder="Search..."
className="w-full rounded-md border border-stroke bg-transparent px-3 py-1.5 text-sm outline-none focus:border-primary dark:border-dark-3 dark:text-white dark:focus:border-primary"
/>
</div>
)}
<div
ref={listRef}
onScroll={handleScroll}
className="max-h-52 overflow-y-auto py-1"
>
{isLoading ? (
<div className="flex items-center justify-center py-4">
<div className="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent" />
</div>
) : options.length === 0 && !isLoadingMore ? (
<div className="px-3 py-2 text-sm text-dark-6 dark:text-dark-6"> <div className="px-3 py-2 text-sm text-dark-6 dark:text-dark-6">
No items are available No items are available
</div> </div>
) : ( ) : (
options.map((option, index) => ( <>
<div {options.map((option) => (
key={index}
className={cn(
"flex cursor-pointer items-center px-3 py-2 text-sm text-dark hover:bg-primary/5 dark:text-white dark:hover:bg-primary/5",
option.selected && "bg-primary/10 dark:bg-primary/20",
)}
onClick={() => handleSelect(index)}
>
<div <div
key={option.value}
className={cn( className={cn(
"mr-3 h-4 w-4 rounded border-2", "flex cursor-pointer items-center px-3 py-2 text-sm text-dark hover:bg-primary/5 dark:text-white dark:hover:bg-primary/5",
option.selected option.selected && "bg-primary/10 dark:bg-primary/20",
? "border-primary bg-primary"
: "border-stroke dark:border-dark-3",
)} )}
onClick={() => handleSelect(option)}
> >
{option.selected && ( <div
<svg className={cn(
className="h-full w-full text-white" "mr-3 h-4 w-4 rounded border-2",
fill="currentColor" option.selected
viewBox="0 0 20 20" ? "border-primary bg-primary"
> : "border-stroke dark:border-dark-3",
<path )}
fillRule="evenodd" >
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" {option.selected && (
clipRule="evenodd" <svg
/> className="h-full w-full text-white"
</svg> fill="currentColor"
)} viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
)}
</div>
{option.label}
</div> </div>
{option.label} ))}
</div> {isLoadingMore && (
)) <div className="flex items-center justify-center py-2">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
</div>
)}
</>
)} )}
</div> </div>
</div> </div>
@@ -19,6 +19,11 @@ const CreateNotificationForm = () => {
recipient, recipient,
onSubmit, onSubmit,
onSelectTarget, onSelectTarget,
onSearchRecipient,
onLoadMoreRecipients,
hasMoreRecipients,
isLoadingMoreRecipients,
isLoadingRecipients,
priorities, priorities,
targets, targets,
types, types,
@@ -85,6 +90,11 @@ const CreateNotificationForm = () => {
recipient && recipient.length > 0 ? "Choose recipients" : "" recipient && recipient.length > 0 ? "Choose recipients" : ""
} }
name="recipient" name="recipient"
onSearch={recipient ? onSearchRecipient : undefined}
onLoadMore={onLoadMoreRecipients}
hasMore={hasMoreRecipients}
isLoading={isLoadingRecipients}
isLoadingMore={isLoadingMoreRecipients}
/> />
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
@@ -6,20 +6,29 @@ import {
NotificationTypes, NotificationTypes,
} from "@/constants/enums"; } from "@/constants/enums";
import { import {
useCompanyFullList, useAdminListInfiniteQuery,
useGetAllCustomers, useCompaniesInfiniteQuery,
useGetUsersQuery, useCustomersInfiniteQuery,
} from "@/hooks/queries"; } from "@/hooks/queries";
import useDebounce from "@/hooks/useDebounce";
import { useCreateNotificationQuery } from "@/hooks/queries/useNotificationQueries"; import { useCreateNotificationQuery } from "@/hooks/queries/useNotificationQueries";
import { TCreateNotificationCredentials } from "@/lib/api/types/NotificationHistory"; import { TCreateNotificationCredentials } from "@/lib/api/types/NotificationHistory";
import { ILabelValue } from "@/types/shared"; import { ILabelValue } from "@/types/shared";
import { getEnumAsArray } from "@/utils/shared"; import { getEnumAsArray } from "@/utils/shared";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { ChangeEvent, useState } from "react"; import { ChangeEvent, useCallback, useMemo, useState } from "react";
import { SubmitHandler, useForm } from "react-hook-form"; import { SubmitHandler, useForm } from "react-hook-form";
const ROLE_ITEMS: ILabelValue[] = [
{ label: "Admin", value: "admin" },
{ label: "Analyst", value: "analyst" },
];
const useCreateNotificationFormPresenter = () => { const useCreateNotificationFormPresenter = () => {
const [recipient, setRecipient] = useState<ILabelValue[] | null>(null); const [target, setTarget] = useState<NotificationTargetGroups | null>(null);
const [recipientSearch, setRecipientSearch] = useState("");
const debouncedSearch = useDebounce(recipientSearch, 400);
const { const {
register, register,
formState: { errors }, formState: { errors },
@@ -30,13 +39,128 @@ const useCreateNotificationFormPresenter = () => {
control, control,
} = useForm<TCreateNotificationCredentials>(); } = useForm<TCreateNotificationCredentials>();
const router = useRouter(); const router = useRouter();
const { data: companies } = useCompanyFullList();
const { data: users } = useGetUsersQuery(); const isCompany = target === NotificationTargetGroups.SPECIFIC_COMPANY;
const { data: customers } = useGetAllCustomers(); const isUser = target === NotificationTargetGroups.SPECIFIC_USERS;
const isCustomer = target === NotificationTargetGroups.SPECIFIC_CUSTOMER;
const searchParam = debouncedSearch ? { search: debouncedSearch } : undefined;
const companiesQuery = useCompaniesInfiniteQuery(searchParam, {
enabled: isCompany,
});
const usersQuery = useAdminListInfiniteQuery(searchParam, {
enabled: isUser,
});
const customersQuery = useCustomersInfiniteQuery(searchParam, {
enabled: isCustomer,
});
const companiesItems = useMemo(() => {
if (!companiesQuery.data) return null;
return companiesQuery.data.pages.flatMap(
(page) =>
page.data.companies?.map((c) => ({ label: c.name, value: c.id })) ?? [],
);
}, [companiesQuery.data]);
const usersItems = useMemo(() => {
if (!usersQuery.data) return null;
return usersQuery.data.pages.flatMap(
(page) =>
page.data.users?.map((u) => ({
label: u.full_name ?? u.username ?? u.id,
value: u.id,
})) ?? [],
);
}, [usersQuery.data]);
const customersItems = useMemo(() => {
if (!customersQuery.data) return null;
return customersQuery.data.pages.flatMap(
(page) =>
page.data.customers?.map((c) => ({
label: c.full_name,
value: c.id,
})) ?? [],
);
}, [customersQuery.data]);
const recipient = useMemo((): ILabelValue[] | null => {
if (!target) return null;
const map: Partial<Record<NotificationTargetGroups, ILabelValue[] | null>> =
{
[NotificationTargetGroups.SPECIFIC_COMPANY]: companiesItems,
[NotificationTargetGroups.SPECIFIC_USERS]: usersItems,
[NotificationTargetGroups.SPECIFIC_CUSTOMER]: customersItems,
[NotificationTargetGroups.SPECIFIC_ROLE]: ROLE_ITEMS,
};
return map[target] ?? null;
}, [target, companiesItems, usersItems, customersItems]);
const hasMoreRecipients = useMemo(() => {
if (isCompany) return companiesQuery.hasNextPage;
if (isUser) return usersQuery.hasNextPage;
if (isCustomer) return customersQuery.hasNextPage;
return false;
}, [
isCompany,
isUser,
isCustomer,
companiesQuery.hasNextPage,
usersQuery.hasNextPage,
customersQuery.hasNextPage,
]);
const isLoadingMoreRecipients = useMemo(() => {
if (isCompany) return companiesQuery.isFetchingNextPage;
if (isUser) return usersQuery.isFetchingNextPage;
if (isCustomer) return customersQuery.isFetchingNextPage;
return false;
}, [
isCompany,
isUser,
isCustomer,
companiesQuery.isFetchingNextPage,
usersQuery.isFetchingNextPage,
customersQuery.isFetchingNextPage,
]);
// True during initial/search fetches (not load-more)
const isLoadingRecipients = useMemo(() => {
if (isCompany)
return companiesQuery.isFetching && !companiesQuery.isFetchingNextPage;
if (isUser) return usersQuery.isFetching && !usersQuery.isFetchingNextPage;
if (isCustomer)
return customersQuery.isFetching && !customersQuery.isFetchingNextPage;
return false;
}, [
isCompany,
isUser,
isCustomer,
companiesQuery.isFetching,
companiesQuery.isFetchingNextPage,
usersQuery.isFetching,
usersQuery.isFetchingNextPage,
customersQuery.isFetching,
customersQuery.isFetchingNextPage,
]);
const onLoadMoreRecipients = useCallback(() => {
if (isCompany) companiesQuery.fetchNextPage();
else if (isUser) usersQuery.fetchNextPage();
else if (isCustomer) customersQuery.fetchNextPage();
}, [isCompany, isUser, isCustomer, companiesQuery, usersQuery, customersQuery]);
const onSearchRecipient = useCallback((query: string) => {
setRecipientSearch(query);
}, []);
const { mutate } = useCreateNotificationQuery(() => { const { mutate } = useCreateNotificationQuery(() => {
router.back(); router.back();
reset(); reset();
}); });
const onSubmit: SubmitHandler<TCreateNotificationCredentials> = (data) => { const onSubmit: SubmitHandler<TCreateNotificationCredentials> = (data) => {
const credentials = { const credentials = {
...data, ...data,
@@ -44,6 +168,7 @@ const useCreateNotificationFormPresenter = () => {
}; };
mutate({ credentials }); mutate({ credentials });
}; };
const handleCancel = () => { const handleCancel = () => {
reset(); reset();
router.back(); router.back();
@@ -51,33 +176,11 @@ const useCreateNotificationFormPresenter = () => {
const onSelectTarget = (event: ChangeEvent<HTMLSelectElement>): void => { const onSelectTarget = (event: ChangeEvent<HTMLSelectElement>): void => {
const type = event.target.value as NotificationTargetGroups; const type = event.target.value as NotificationTargetGroups;
setTarget(type);
const recipientMaps: Partial< setRecipientSearch("");
Record<NotificationTargetGroups, Array<ILabelValue> | null> setValue("recipient", undefined);
> = {
[NotificationTargetGroups.SPECIFIC_COMPANY]:
companies?.data.companies?.map((company) => ({
label: company.name,
value: company.id,
})) ?? null,
[NotificationTargetGroups.SPECIFIC_ROLE]: [
{ label: "Admin", value: "admin" },
{ label: "Analyst", value: "analyst" },
],
[NotificationTargetGroups.SPECIFIC_USERS]:
users?.data.users?.map((user) => ({
label: user.full_name ?? "",
value: user.id,
})) ?? null,
[NotificationTargetGroups.SPECIFIC_CUSTOMER]:
customers?.data.customers?.map((customer) => ({
label: customer.full_name,
value: customer.id,
})) ?? null,
};
setRecipient(recipientMaps[type] ?? null);
}; };
return { return {
handleSubmit, handleSubmit,
watch, watch,
@@ -92,6 +195,11 @@ const useCreateNotificationFormPresenter = () => {
onSubmit, onSubmit,
control, control,
recipient, recipient,
onSearchRecipient,
onLoadMoreRecipients,
hasMoreRecipients,
isLoadingMoreRecipients,
isLoadingRecipients,
targets: getEnumAsArray(NotificationTargetGroups), targets: getEnumAsArray(NotificationTargetGroups),
types: getEnumAsArray(NotificationTypes), types: getEnumAsArray(NotificationTypes),
channels: getEnumAsArray(NotificationChannels), channels: getEnumAsArray(NotificationChannels),
+6 -1
View File
@@ -103,7 +103,11 @@ export const useCompaniesQuery = (params?: Record<string, any>) => {
}); });
}; };
export const useCompaniesInfiniteQuery = (params?: Record<string, any>) => { export const useCompaniesInfiniteQuery = (
params?: Record<string, any>,
options?: { enabled?: boolean },
) => {
const enabled = options?.enabled ?? true;
const queryKey = useMemo( const queryKey = useMemo(
() => [API_ENDPOINTS.COMPANIES.READ_ALL, params], () => [API_ENDPOINTS.COMPANIES.READ_ALL, params],
[params], [params],
@@ -116,6 +120,7 @@ export const useCompaniesInfiniteQuery = (params?: Record<string, any>) => {
companiesService.getCompanies({ ...params, offset: pageParam }), companiesService.getCompanies({ ...params, offset: pageParam }),
initialPageParam: 0, initialPageParam: 0,
enabled,
getNextPageParam: (lastPage: any) => { getNextPageParam: (lastPage: any) => {
const { offset, limit, total } = lastPage.meta; const { offset, limit, total } = lastPage.meta;
+6 -1
View File
@@ -12,7 +12,11 @@ import { toast } from "react-toastify";
import { API_ENDPOINTS } from "@/lib/api"; import { API_ENDPOINTS } from "@/lib/api";
import { customersService } from "@/lib/api/services/customers-service"; import { customersService } from "@/lib/api/services/customers-service";
export const useCustomersInfiniteQuery = (params?: Record<string, any>) => { export const useCustomersInfiniteQuery = (
params?: Record<string, any>,
options?: { enabled?: boolean },
) => {
const enabled = options?.enabled ?? true;
const queryKey = useMemo( const queryKey = useMemo(
() => [API_ENDPOINTS.CUSTOMERS.READ_ALL, params], () => [API_ENDPOINTS.CUSTOMERS.READ_ALL, params],
[params], [params],
@@ -25,6 +29,7 @@ export const useCustomersInfiniteQuery = (params?: Record<string, any>) => {
customersService.getCustomers({ ...params, offset: pageParam }), customersService.getCustomers({ ...params, offset: pageParam }),
initialPageParam: 0, initialPageParam: 0,
enabled,
getNextPageParam: (lastPage) => { getNextPageParam: (lastPage) => {
if (!lastPage.meta) return; if (!lastPage.meta) return;
+6 -1
View File
@@ -100,7 +100,11 @@ export const useAdminDetails = (id: string) => {
}); });
}; };
export const useAdminListInfiniteQuery = (params?: Record<string, any>) => { export const useAdminListInfiniteQuery = (
params?: Record<string, any>,
options?: { enabled?: boolean },
) => {
const enabled = options?.enabled ?? true;
const queryKey = useMemo(() => [API_ENDPOINTS.USER.ADMINS, params], [params]); const queryKey = useMemo(() => [API_ENDPOINTS.USER.ADMINS, params], [params]);
return useInfiniteQuery({ return useInfiniteQuery({
@@ -110,6 +114,7 @@ export const useAdminListInfiniteQuery = (params?: Record<string, any>) => {
userService.adminsList({ ...params, offset: pageParam }), userService.adminsList({ ...params, offset: pageParam }),
initialPageParam: 0, initialPageParam: 0,
enabled,
getNextPageParam: (lastPage) => { getNextPageParam: (lastPage) => {
if (!lastPage.meta) { if (!lastPage.meta) {