chore(env): update application version to 2.3.1 in production environment
feat(customers): enhance customer list filters with lazy-loaded company options - Added InputGroup components for searching by full name and email in CustomerListFilters. - Integrated Select component for company selection with lazy-loading capabilities. - Implemented company search and load more functionality using useCompaniesInfiniteQuery. - Updated useCustomerListPresenter to manage company search state and loading indicators. - Enhanced customer list filtering experience by allowing dynamic company selection.
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
NEXT_PUBLIC_APP_VERSION=2.3.0
|
NEXT_PUBLIC_APP_VERSION=2.3.1
|
||||||
NEXT_PUBLIC_TOAST_AUTO_CLOSE_DURATION=2500
|
NEXT_PUBLIC_TOAST_AUTO_CLOSE_DURATION=2500
|
||||||
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSyCTjdsk2jE34IfnvSKP1JMTIf_Abd7tbt0
|
NEXT_PUBLIC_FIREBASE_API_KEY=AIzaSyCTjdsk2jE34IfnvSKP1JMTIf_Abd7tbt0
|
||||||
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=opplens-270d1.firebaseapp.com
|
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=opplens-270d1.firebaseapp.com
|
||||||
|
|||||||
@@ -133,6 +133,20 @@ type PropsType<T extends FieldValues> = {
|
|||||||
searchQueryKey?: readonly unknown[];
|
searchQueryKey?: readonly unknown[];
|
||||||
/** Loads options for the debounced search term. Required when `searchMode` is `"server"`. */
|
/** Loads options for the debounced search term. Required when `searchMode` is `"server"`. */
|
||||||
searchFn?: (query: string) => Promise<ILabelValue[]>;
|
searchFn?: (query: string) => Promise<ILabelValue[]>;
|
||||||
|
/**
|
||||||
|
* External lazy-load mode: the parent owns `items`, searching and pagination.
|
||||||
|
* Providing `onSearch` and/or `onLoadMore` opts in — the dropdown renders a search
|
||||||
|
* box, streams `items` as-is and requests more on scroll. Pairs with an infinite query.
|
||||||
|
*/
|
||||||
|
onSearch?: (query: string) => void;
|
||||||
|
/** Fetch the next page; called when the option list is scrolled near its end. */
|
||||||
|
onLoadMore?: () => void;
|
||||||
|
/** Whether more pages are available to lazy-load. */
|
||||||
|
hasMore?: boolean;
|
||||||
|
/** Initial / search fetch in progress (external mode). */
|
||||||
|
isLoading?: boolean;
|
||||||
|
/** Next-page fetch in progress (external mode). */
|
||||||
|
isLoadingMore?: boolean;
|
||||||
} & (
|
} & (
|
||||||
| { placeholder?: string; defaultValue?: string }
|
| { placeholder?: string; defaultValue?: string }
|
||||||
| { placeholder: string; defaultValue?: string }
|
| { placeholder: string; defaultValue?: string }
|
||||||
@@ -163,11 +177,22 @@ export function Select<T extends FieldValues>({
|
|||||||
searchDebounceMs = 300,
|
searchDebounceMs = 300,
|
||||||
searchQueryKey,
|
searchQueryKey,
|
||||||
searchFn,
|
searchFn,
|
||||||
|
onSearch,
|
||||||
|
onLoadMore,
|
||||||
|
hasMore = false,
|
||||||
|
isLoading = false,
|
||||||
|
isLoadingMore = false,
|
||||||
register: _register,
|
register: _register,
|
||||||
...props
|
...props
|
||||||
}: PropsType<T>) {
|
}: PropsType<T>) {
|
||||||
void _register;
|
void _register;
|
||||||
|
|
||||||
|
/** Parent-controlled list: search + pagination handled outside, `items` consumed as-is. */
|
||||||
|
const isExternal =
|
||||||
|
typeof onSearch === "function" || typeof onLoadMore === "function";
|
||||||
|
/** Both `searchable` and external mode render the in-dropdown search box. */
|
||||||
|
const showSearch = searchable || isExternal;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
process.env.NODE_ENV !== "production" &&
|
process.env.NODE_ENV !== "production" &&
|
||||||
searchable &&
|
searchable &&
|
||||||
@@ -186,6 +211,7 @@ export function Select<T extends FieldValues>({
|
|||||||
const rootRef = useRef<HTMLDivElement>(null);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const listRef = useRef<HTMLUListElement>(null);
|
||||||
|
|
||||||
const [isOptionSelected, setIsOptionSelected] = useState(!!defaultValue);
|
const [isOptionSelected, setIsOptionSelected] = useState(!!defaultValue);
|
||||||
const [value, setValue] = useState(defaultValue || "");
|
const [value, setValue] = useState(defaultValue || "");
|
||||||
@@ -221,10 +247,21 @@ export function Select<T extends FieldValues>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const listItems = useMemo(() => {
|
const listItems = useMemo(() => {
|
||||||
|
if (isExternal) return items;
|
||||||
if (!searchable) return items;
|
if (!searchable) return items;
|
||||||
if (isServerMode && serverDisplayItems) return serverDisplayItems;
|
if (isServerMode && serverDisplayItems) return serverDisplayItems;
|
||||||
return filterSelectItems(items, query);
|
return filterSelectItems(items, query);
|
||||||
}, [searchable, isServerMode, serverDisplayItems, items, query]);
|
}, [isExternal, searchable, isServerMode, serverDisplayItems, items, query]);
|
||||||
|
|
||||||
|
/** Lazy-load the next page once the option list nears its bottom (external mode). */
|
||||||
|
const handleListScroll = useCallback(() => {
|
||||||
|
if (!isExternal || !onLoadMore || !hasMore || isLoadingMore) return;
|
||||||
|
const el = listRef.current;
|
||||||
|
if (!el) return;
|
||||||
|
if (el.scrollHeight - el.scrollTop - el.clientHeight < 40) {
|
||||||
|
onLoadMore();
|
||||||
|
}
|
||||||
|
}, [isExternal, onLoadMore, hasMore, isLoadingMore]);
|
||||||
|
|
||||||
const selectedLabel = useMemo(() => {
|
const selectedLabel = useMemo(() => {
|
||||||
const hit = listItems.find((i) => String(i.value) === String(value));
|
const hit = listItems.find((i) => String(i.value) === String(value));
|
||||||
@@ -287,13 +324,13 @@ export function Select<T extends FieldValues>({
|
|||||||
}, [open, listboxId]);
|
}, [open, listboxId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!searchable || !open) return;
|
if (!showSearch || !open) return;
|
||||||
const idFrame = window.requestAnimationFrame(() => {
|
const idFrame = window.requestAnimationFrame(() => {
|
||||||
searchInputRef.current?.focus();
|
searchInputRef.current?.focus();
|
||||||
searchInputRef.current?.select();
|
searchInputRef.current?.select();
|
||||||
});
|
});
|
||||||
return () => window.cancelAnimationFrame(idFrame);
|
return () => window.cancelAnimationFrame(idFrame);
|
||||||
}, [open, searchable]);
|
}, [open, showSearch]);
|
||||||
|
|
||||||
const emitChange = (next: string) => {
|
const emitChange = (next: string) => {
|
||||||
if (controlledValue === undefined) {
|
if (controlledValue === undefined) {
|
||||||
@@ -323,7 +360,9 @@ export function Select<T extends FieldValues>({
|
|||||||
triggerRef.current?.focus();
|
triggerRef.current?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
const dropdownItems = searchable ? listItems : items;
|
const dropdownItems = showSearch ? listItems : items;
|
||||||
|
/** Mono value chip next to labels only helps the client/server search modes, not external lists. */
|
||||||
|
const showItemValue = searchable && !isExternal;
|
||||||
|
|
||||||
const dropdown =
|
const dropdown =
|
||||||
mounted &&
|
mounted &&
|
||||||
@@ -345,7 +384,7 @@ export function Select<T extends FieldValues>({
|
|||||||
panelRect.placement === "above" ? "translateY(-100%)" : undefined,
|
panelRect.placement === "above" ? "translateY(-100%)" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{searchable ? (
|
{showSearch ? (
|
||||||
<div className="border-b border-stroke/50 p-2 dark:border-white/[0.08]">
|
<div className="border-b border-stroke/50 p-2 dark:border-white/[0.08]">
|
||||||
<label htmlFor={`${listboxId}-search`} className="sr-only">
|
<label htmlFor={`${listboxId}-search`} className="sr-only">
|
||||||
{searchPlaceholder}
|
{searchPlaceholder}
|
||||||
@@ -355,7 +394,10 @@ export function Select<T extends FieldValues>({
|
|||||||
ref={searchInputRef}
|
ref={searchInputRef}
|
||||||
type="search"
|
type="search"
|
||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setQuery(e.target.value);
|
||||||
|
if (isExternal) onSearch?.(e.target.value);
|
||||||
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -372,51 +414,62 @@ export function Select<T extends FieldValues>({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<ul className="max-h-[min(18rem,calc(100vh-10rem))] overflow-y-auto overscroll-contain p-1.5">
|
<ul
|
||||||
{searchable && isSearchLoading ? (
|
ref={listRef}
|
||||||
|
onScroll={isExternal ? handleListScroll : undefined}
|
||||||
|
className="max-h-[min(18rem,calc(100vh-10rem))] overflow-y-auto overscroll-contain p-1.5"
|
||||||
|
>
|
||||||
|
{(isExternal ? isLoading : searchable && isSearchLoading) ? (
|
||||||
<li className="flex items-center justify-center gap-2 px-3 py-6 text-sm text-dark-5 dark:text-dark-6">
|
<li className="flex items-center justify-center gap-2 px-3 py-6 text-sm text-dark-5 dark:text-dark-6">
|
||||||
<span className="inline-flex h-4 w-4 animate-spin rounded-full border-2 border-primary/25 border-t-primary" />
|
<span className="inline-flex h-4 w-4 animate-spin rounded-full border-2 border-primary/25 border-t-primary" />
|
||||||
Searching…
|
{isExternal ? "Loading…" : "Searching…"}
|
||||||
</li>
|
</li>
|
||||||
) : searchable && isSearchError ? (
|
) : !isExternal && searchable && isSearchError ? (
|
||||||
<li className="px-3 py-6 text-center text-sm text-error">
|
<li className="px-3 py-6 text-center text-sm text-error">
|
||||||
Could not load options. Try again.
|
Could not load options. Try again.
|
||||||
</li>
|
</li>
|
||||||
) : dropdownItems.length === 0 ? (
|
) : dropdownItems.length === 0 ? (
|
||||||
<li className="px-3 py-6 text-center text-sm text-dark-5 dark:text-dark-6">
|
<li className="px-3 py-6 text-center text-sm text-dark-5 dark:text-dark-6">
|
||||||
{searchable ? "No matches." : "No options."}
|
{showSearch ? "No matches." : "No options."}
|
||||||
</li>
|
</li>
|
||||||
) : (
|
) : (
|
||||||
dropdownItems.map((item) => {
|
<>
|
||||||
const v = String(item.value);
|
{dropdownItems.map((item) => {
|
||||||
const isActive = v === String(value);
|
const v = String(item.value);
|
||||||
return (
|
const isActive = v === String(value);
|
||||||
<li key={v}>
|
return (
|
||||||
<button
|
<li key={v}>
|
||||||
type="button"
|
<button
|
||||||
role="option"
|
type="button"
|
||||||
aria-selected={isActive}
|
role="option"
|
||||||
data-cy={dataCy ? `${dataCy}-option` : undefined}
|
aria-selected={isActive}
|
||||||
data-value={v}
|
data-cy={dataCy ? `${dataCy}-option` : undefined}
|
||||||
onClick={() => pickItem(v)}
|
data-value={v}
|
||||||
className={cn(
|
onClick={() => pickItem(v)}
|
||||||
"flex w-full items-center gap-2 rounded-lg px-3 py-2 text-start text-sm transition",
|
className={cn(
|
||||||
searchable ? "justify-between" : "justify-start",
|
"flex w-full items-center gap-2 rounded-lg px-3 py-2 text-start text-sm transition",
|
||||||
isActive
|
showItemValue ? "justify-between" : "justify-start",
|
||||||
? "bg-primary/12 font-medium text-primary dark:bg-primary/20 dark:text-white"
|
isActive
|
||||||
: "text-dark hover:bg-stroke/30 dark:text-white dark:hover:bg-white/[0.06]",
|
? "bg-primary/12 font-medium text-primary dark:bg-primary/20 dark:text-white"
|
||||||
)}
|
: "text-dark hover:bg-stroke/30 dark:text-white dark:hover:bg-white/[0.06]",
|
||||||
>
|
)}
|
||||||
<span className="min-w-0 truncate">{item.label}</span>
|
>
|
||||||
{searchable ? (
|
<span className="min-w-0 truncate">{item.label}</span>
|
||||||
<span className="shrink-0 font-mono text-xs text-dark-5 dark:text-dark-6">
|
{showItemValue ? (
|
||||||
{v}
|
<span className="shrink-0 font-mono text-xs text-dark-5 dark:text-dark-6">
|
||||||
</span>
|
{v}
|
||||||
) : null}
|
</span>
|
||||||
</button>
|
) : null}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{isExternal && isLoadingMore ? (
|
||||||
|
<li className="flex items-center justify-center py-2">
|
||||||
|
<span className="inline-flex h-4 w-4 animate-spin rounded-full border-2 border-primary/25 border-t-primary" />
|
||||||
</li>
|
</li>
|
||||||
);
|
) : null}
|
||||||
})
|
</>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</div>,
|
</div>,
|
||||||
@@ -508,7 +561,7 @@ export function Select<T extends FieldValues>({
|
|||||||
>
|
>
|
||||||
<span className="min-w-0 flex-1 truncate">
|
<span className="min-w-0 flex-1 truncate">
|
||||||
{value ? (
|
{value ? (
|
||||||
searchable ? (
|
showItemValue ? (
|
||||||
<>
|
<>
|
||||||
<span className="text-dark dark:text-white">
|
<span className="text-dark dark:text-white">
|
||||||
{selectedLabel}
|
{selectedLabel}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import InputGroup from "@/components/FormElements/InputGroup";
|
||||||
import { Select } from "@/components/FormElements/select";
|
import { Select } from "@/components/FormElements/select";
|
||||||
import { Button } from "@/components/ui-elements/button";
|
import { Button } from "@/components/ui-elements/button";
|
||||||
import InlineListFiltersPanel from "@/components/ui/InlineListFiltersPanel";
|
import InlineListFiltersPanel from "@/components/ui/InlineListFiltersPanel";
|
||||||
import { AdminRoles, CustomerStatus, CustomerType } from "@/constants/enums";
|
import { AdminRoles, CustomerStatus, CustomerType } from "@/constants/enums";
|
||||||
|
import { ILabelValue } from "@/types/shared";
|
||||||
import { getEnumAsArray } from "@/utils/shared";
|
import { getEnumAsArray } from "@/utils/shared";
|
||||||
import {
|
import {
|
||||||
FieldValues,
|
FieldValues,
|
||||||
@@ -22,11 +24,25 @@ interface CustomerListFiltersProps {
|
|||||||
watch: UseFormWatch<FieldValues>;
|
watch: UseFormWatch<FieldValues>;
|
||||||
setValue: UseFormSetValue<any>;
|
setValue: UseFormSetValue<any>;
|
||||||
onClearAll: () => void;
|
onClearAll: () => void;
|
||||||
|
/** Lazy-loaded company options for the company dropdown. */
|
||||||
|
companyItems: ILabelValue[];
|
||||||
|
onCompanySearch: (query: string) => void;
|
||||||
|
onCompanyLoadMore: () => void;
|
||||||
|
companyHasMore?: boolean;
|
||||||
|
companyIsLoading: boolean;
|
||||||
|
companyIsLoadingMore: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function countActive(values: Record<string, unknown>): number {
|
function countActive(values: Record<string, unknown>): number {
|
||||||
let n = 0;
|
let n = 0;
|
||||||
for (const key of ["status", "role", "type"] as const) {
|
for (const key of [
|
||||||
|
"status",
|
||||||
|
"role",
|
||||||
|
"type",
|
||||||
|
"full_name",
|
||||||
|
"email",
|
||||||
|
"company_id",
|
||||||
|
] as const) {
|
||||||
const v = values[key];
|
const v = values[key];
|
||||||
if (v !== undefined && v !== null && v !== "" && String(v).trim()) n++;
|
if (v !== undefined && v !== null && v !== "" && String(v).trim()) n++;
|
||||||
}
|
}
|
||||||
@@ -41,6 +57,12 @@ const CustomerListFilters = ({
|
|||||||
watch,
|
watch,
|
||||||
setValue,
|
setValue,
|
||||||
onClearAll,
|
onClearAll,
|
||||||
|
companyItems,
|
||||||
|
onCompanySearch,
|
||||||
|
onCompanyLoadMore,
|
||||||
|
companyHasMore,
|
||||||
|
companyIsLoading,
|
||||||
|
companyIsLoadingMore,
|
||||||
}: CustomerListFiltersProps) => {
|
}: CustomerListFiltersProps) => {
|
||||||
const watched = watch();
|
const watched = watch();
|
||||||
const activeCount = useMemo(
|
const activeCount = useMemo(
|
||||||
@@ -63,6 +85,36 @@ const CustomerListFilters = ({
|
|||||||
data-cy="customers-filter-form"
|
data-cy="customers-filter-form"
|
||||||
>
|
>
|
||||||
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
||||||
|
<InputGroup
|
||||||
|
{...filterRegister("full_name")}
|
||||||
|
name="full_name"
|
||||||
|
label="Full name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Search by full name"
|
||||||
|
/>
|
||||||
|
<InputGroup
|
||||||
|
{...filterRegister("email")}
|
||||||
|
name="email"
|
||||||
|
label="Email"
|
||||||
|
type="text"
|
||||||
|
placeholder="Search by email"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
{...filterRegister("company_id")}
|
||||||
|
items={companyItems}
|
||||||
|
label="Company"
|
||||||
|
name="company_id"
|
||||||
|
placeholder="Select company"
|
||||||
|
clearable
|
||||||
|
value={watch("company_id")}
|
||||||
|
onClear={() => setValue("company_id", undefined)}
|
||||||
|
searchPlaceholder="Search companies…"
|
||||||
|
onSearch={onCompanySearch}
|
||||||
|
onLoadMore={onCompanyLoadMore}
|
||||||
|
hasMore={companyHasMore}
|
||||||
|
isLoading={companyIsLoading}
|
||||||
|
isLoadingMore={companyIsLoadingMore}
|
||||||
|
/>
|
||||||
<Select
|
<Select
|
||||||
{...filterRegister("status")}
|
{...filterRegister("status")}
|
||||||
items={getEnumAsArray(CustomerStatus)}
|
items={getEnumAsArray(CustomerStatus)}
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ const CustomersTable = () => {
|
|||||||
openResetPasswordModalForCustomer,
|
openResetPasswordModalForCustomer,
|
||||||
generatedPassword,
|
generatedPassword,
|
||||||
isResettingPassword,
|
isResettingPassword,
|
||||||
|
companyItems,
|
||||||
|
onCompanySearch,
|
||||||
|
onCompanyLoadMore,
|
||||||
|
companyHasMore,
|
||||||
|
companyIsLoading,
|
||||||
|
companyIsLoadingMore,
|
||||||
} = useCustomerListPresenter();
|
} = useCustomerListPresenter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -88,6 +94,12 @@ const CustomersTable = () => {
|
|||||||
search={search}
|
search={search}
|
||||||
watch={watch}
|
watch={watch}
|
||||||
onClearAll={clearAllFilters}
|
onClearAll={clearAllFilters}
|
||||||
|
companyItems={companyItems}
|
||||||
|
onCompanySearch={onCompanySearch}
|
||||||
|
onCompanyLoadMore={onCompanyLoadMore}
|
||||||
|
companyHasMore={companyHasMore}
|
||||||
|
companyIsLoading={companyIsLoading}
|
||||||
|
companyIsLoadingMore={companyIsLoadingMore}
|
||||||
/>
|
/>
|
||||||
<div ref={tableSectionRef}>
|
<div ref={tableSectionRef}>
|
||||||
<TableSortProvider
|
<TableSortProvider
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
import { SortDirection } from "@/components/ui/tableSortContext";
|
import { SortDirection } from "@/components/ui/tableSortContext";
|
||||||
import {
|
import {
|
||||||
useAssignCompany,
|
useAssignCompany,
|
||||||
|
useCompaniesInfiniteQuery,
|
||||||
useCompanyFullList,
|
useCompanyFullList,
|
||||||
useCustomersQuery,
|
useCustomersQuery,
|
||||||
useDeleteCustomerQuery,
|
useDeleteCustomerQuery,
|
||||||
useResetCustomerPassword,
|
useResetCustomerPassword,
|
||||||
} from "@/hooks/queries";
|
} from "@/hooks/queries";
|
||||||
|
import useDebounce from "@/hooks/useDebounce";
|
||||||
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||||
import { TAssignCustomerToCompanyCredentials, TCustomer } from "@/lib/api";
|
import { TAssignCustomerToCompanyCredentials, TCustomer } from "@/lib/api";
|
||||||
@@ -50,6 +52,35 @@ const useCustomerListPresenter = () => {
|
|||||||
setValue: setFilterValue,
|
setValue: setFilterValue,
|
||||||
} = useForm();
|
} = useForm();
|
||||||
|
|
||||||
|
// Lazy-loaded company options for the inline filter dropdown.
|
||||||
|
const [companySearch, setCompanySearch] = useState("");
|
||||||
|
const debouncedCompanySearch = useDebounce(companySearch, 400);
|
||||||
|
const companiesQuery = useCompaniesInfiniteQuery(
|
||||||
|
debouncedCompanySearch ? { search: debouncedCompanySearch } : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const companyItems = useMemo(
|
||||||
|
() =>
|
||||||
|
companiesQuery.data?.pages.flatMap(
|
||||||
|
(page) =>
|
||||||
|
page.data.companies?.map((company) => ({
|
||||||
|
label: company.name,
|
||||||
|
value: company.id,
|
||||||
|
})) ?? [],
|
||||||
|
) ?? [],
|
||||||
|
[companiesQuery.data],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onCompanySearch = useCallback((query: string) => {
|
||||||
|
setCompanySearch(query);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onCompanyLoadMore = useCallback(() => {
|
||||||
|
if (companiesQuery.hasNextPage && !companiesQuery.isFetchingNextPage) {
|
||||||
|
companiesQuery.fetchNextPage();
|
||||||
|
}
|
||||||
|
}, [companiesQuery]);
|
||||||
|
|
||||||
const tableSectionRef = useRef<HTMLDivElement>(null);
|
const tableSectionRef = useRef<HTMLDivElement>(null);
|
||||||
const skeletonTbodyRef = useRef<HTMLTableSectionElement>(null);
|
const skeletonTbodyRef = useRef<HTMLTableSectionElement>(null);
|
||||||
const dataTbodyRef = useRef<HTMLTableSectionElement>(null);
|
const dataTbodyRef = useRef<HTMLTableSectionElement>(null);
|
||||||
@@ -125,7 +156,15 @@ const useCustomerListPresenter = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const clearAllFilters = useCallback(() => {
|
const clearAllFilters = useCallback(() => {
|
||||||
filterFormReset({ status: undefined, role: undefined, type: undefined });
|
filterFormReset({
|
||||||
|
status: undefined,
|
||||||
|
role: undefined,
|
||||||
|
type: undefined,
|
||||||
|
full_name: undefined,
|
||||||
|
email: undefined,
|
||||||
|
company_id: undefined,
|
||||||
|
});
|
||||||
|
setCompanySearch("");
|
||||||
router.push(pathName);
|
router.push(pathName);
|
||||||
}, [pathName, router, filterFormReset]);
|
}, [pathName, router, filterFormReset]);
|
||||||
|
|
||||||
@@ -228,6 +267,13 @@ const useCustomerListPresenter = () => {
|
|||||||
openResetPasswordModalForCustomer,
|
openResetPasswordModalForCustomer,
|
||||||
generatedPassword,
|
generatedPassword,
|
||||||
isResettingPassword,
|
isResettingPassword,
|
||||||
|
companyItems,
|
||||||
|
onCompanySearch,
|
||||||
|
onCompanyLoadMore,
|
||||||
|
companyHasMore: companiesQuery.hasNextPage,
|
||||||
|
companyIsLoading:
|
||||||
|
companiesQuery.isFetching && !companiesQuery.isFetchingNextPage,
|
||||||
|
companyIsLoadingMore: companiesQuery.isFetchingNextPage,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user