diff --git a/src/hooks/useSearchSelector.base.ts b/src/hooks/useSearchSelector.base.ts index 6d14bf56b8e16..de20556fb29e7 100644 --- a/src/hooks/useSearchSelector.base.ts +++ b/src/hooks/useSearchSelector.base.ts @@ -84,6 +84,9 @@ type UseSearchSelectorReturn = { /** Current search term */ searchTerm: string; + /** Debounced search term */ + debouncedSearchTerm: string; + /** Function to update search term */ setSearchTerm: (value: string) => void; @@ -201,7 +204,7 @@ function useSearchSelectorBase({ maxElements: maxResults, maxRecentReportElements: maxRecentReportsToShow, includeUserToInvite, - loginsToExclude: excludeLogins, + excludeLogins, }); case CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_SHARE_DESTINATION: return getValidOptions(optionsWithContacts, draftComments, { @@ -328,6 +331,7 @@ function useSearchSelectorBase({ return { searchTerm, + debouncedSearchTerm, setSearchTerm, searchOptions, availableOptions, diff --git a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx index eaa5020f188af..bfa230156d98d 100644 --- a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx +++ b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx @@ -1,35 +1,27 @@ -import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import React, {useEffect, useMemo} from 'react'; import {View} from 'react-native'; import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import {useBetas} from '@components/OnyxListItemProvider'; -import {useOptionsList} from '@components/OptionListContextProvider'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionListWithSections'; import UserListItem from '@components/SelectionListWithSections/UserListItem'; -import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import useSearchSelector from '@hooks/useSearchSelector'; import useThemeStyles from '@hooks/useThemeStyles'; import {searchInServer} from '@libs/actions/Report'; -import memoize from '@libs/memoize'; import Navigation from '@libs/Navigation/Navigation'; -import {filterAndOrderOptions, getHeaderMessage, getValidOptions} from '@libs/OptionsListUtils'; +import {getHeaderMessage} from '@libs/OptionsListUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {Participant} from '@src/types/onyx/IOU'; -const memoizedGetValidOptions = memoize(getValidOptions, {maxSize: 5, monitoringName: 'AddDelegatePage.getValidOptions'}); - -function useOptions() { - const betas = useBetas(); - const [isLoading, setIsLoading] = useState(true); - const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState(''); - const {options: optionsList, areOptionsInitialized} = useOptionsList(); +function AddDelegatePage() { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true}); - const [countryCode] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); - const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); + const existingDelegates = useMemo( () => account?.delegatedAccess?.delegates?.reduce( @@ -39,85 +31,45 @@ function useOptions() { return prev; }, {} as Record, - ), + ) ?? {}, [account?.delegatedAccess?.delegates], ); - const defaultOptions = useMemo(() => { - const {recentReports, personalDetails, userToInvite, currentUserOption} = memoizedGetValidOptions( - { - reports: optionsList.reports, - personalDetails: optionsList.personalDetails, - }, - draftComments, - { - betas, - excludeLogins: {...CONST.EXPENSIFY_EMAILS_OBJECT, ...existingDelegates}, - }, - countryCode, - ); - - const headerMessage = getHeaderMessage((recentReports?.length || 0) + (personalDetails?.length || 0) !== 0, !!userToInvite, '', false, countryCode); - - if (isLoading) { - // eslint-disable-next-line react-compiler/react-compiler - setIsLoading(false); - } - - return { - userToInvite, - recentReports, - personalDetails, - currentUserOption, - headerMessage, - }; - }, [optionsList.reports, optionsList.personalDetails, draftComments, betas, existingDelegates, isLoading, countryCode]); - - const options = useMemo(() => { - const filteredOptions = filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), countryCode, { - excludeLogins: {...CONST.EXPENSIFY_EMAILS_OBJECT, ...existingDelegates}, - maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, - }); - const headerMessage = getHeaderMessage( - (filteredOptions.recentReports?.length || 0) + (filteredOptions.personalDetails?.length || 0) !== 0, - !!filteredOptions.userToInvite, - debouncedSearchValue, - ); - - return { - ...filteredOptions, - headerMessage, - }; - }, [debouncedSearchValue, defaultOptions, existingDelegates, countryCode]); - - return {...options, searchValue, debouncedSearchValue, setSearchValue, areOptionsInitialized}; -} -function AddDelegatePage() { - const {translate} = useLocalize(); - const styles = useThemeStyles(); - const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); - const {userToInvite, recentReports, personalDetails, searchValue, debouncedSearchValue, setSearchValue, headerMessage, areOptionsInitialized} = useOptions(); - const [selectedOption, setSelectedOption] = useState(undefined); + const {searchTerm, debouncedSearchTerm, setSearchTerm, availableOptions, areOptionsInitialized, toggleSelection} = useSearchSelector({ + selectionMode: CONST.SEARCH_SELECTOR.SELECTION_MODE_SINGLE, + searchContext: CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_GENERAL, + includeUserToInvite: true, + excludeLogins: {...CONST.EXPENSIFY_EMAILS_OBJECT, ...existingDelegates}, + includeRecentReports: true, + maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, + onSingleSelect: (option) => { + Navigation.navigate(ROUTES.SETTINGS_DELEGATE_ROLE.getRoute(option.login ?? '')); + }, + }); + + const headerMessage = useMemo(() => { + return getHeaderMessage((availableOptions.recentReports?.length || 0) + (availableOptions.personalDetails?.length || 0) !== 0, !!availableOptions.userToInvite, debouncedSearchTerm); + }, [availableOptions, debouncedSearchTerm]); const sections = useMemo(() => { const sectionsList = []; sectionsList.push({ title: translate('common.recents'), - data: recentReports, - shouldShow: recentReports?.length > 0, + data: availableOptions.recentReports, + shouldShow: availableOptions.recentReports?.length > 0, }); sectionsList.push({ title: translate('common.contacts'), - data: personalDetails, - shouldShow: personalDetails?.length > 0, + data: availableOptions.personalDetails, + shouldShow: availableOptions.personalDetails?.length > 0, }); - if (userToInvite) { + if (availableOptions.userToInvite) { sectionsList.push({ title: undefined, - data: [userToInvite], + data: [availableOptions.userToInvite], shouldShow: true, }); } @@ -132,19 +84,13 @@ function AddDelegatePage() { isDisabled: option.isDisabled ?? undefined, login: option.login ?? undefined, shouldShowSubscript: option.shouldShowSubscript ?? undefined, - isSelected: option.login === selectedOption, })), })); - }, [personalDetails, recentReports, translate, userToInvite, selectedOption]); - - const onSelectRow = useCallback((option: Participant) => { - setSelectedOption(option?.login); - Navigation.navigate(ROUTES.SETTINGS_DELEGATE_ROLE.getRoute(option?.login ?? '')); - }, []); + }, [availableOptions, translate]); useEffect(() => { - searchInServer(debouncedSearchValue); - }, [debouncedSearchValue]); + searchInServer(debouncedSearchTerm); + }, [debouncedSearchTerm]); return ( >(); + const {translate} = useLocalize(); const session = useSession(); + const backTo = route.params?.backTo; + const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false}); + const [task] = useOnyx(ONYXKEYS.TASK, {canBeMissing: false}); + const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); const [countryCode] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); - const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true}); - - const defaultOptions = useMemo(() => { - const {recentReports, personalDetails, userToInvite, currentUserOption} = memoizedGetValidOptions( - { - reports: optionsList.reports, - personalDetails: optionsList.personalDetails, - }, - draftComments, - { - betas, - excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, - includeCurrentUser: true, - }, - countryCode, - ); - - const headerMessage = getHeaderMessage((recentReports?.length || 0) + (personalDetails?.length || 0) !== 0 || !!currentUserOption, !!userToInvite, '', false, countryCode); - - if (isLoading) { - // eslint-disable-next-line react-compiler/react-compiler - setIsLoading(false); - } + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - return { - userToInvite, - recentReports, - personalDetails, - currentUserOption, - headerMessage, - }; - }, [optionsList.reports, optionsList.personalDetails, draftComments, betas, isLoading, countryCode]); + const {searchTerm, debouncedSearchTerm, setSearchTerm, availableOptions, areOptionsInitialized} = useSearchSelector({ + selectionMode: CONST.SEARCH_SELECTOR.SELECTION_MODE_SINGLE, + searchContext: CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_GENERAL, + includeUserToInvite: true, + excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, + maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, + getValidOptionsConfig: { + includeCurrentUser: true, + }, + }); const optionsWithoutCurrentUser = useMemo(() => { if (!session?.accountID) { - return defaultOptions; + return availableOptions; } return { - ...defaultOptions, - personalDetails: defaultOptions.personalDetails.filter((detail) => detail.accountID !== session.accountID), - recentReports: defaultOptions.recentReports.filter((report) => report.accountID !== session.accountID), + ...availableOptions, + personalDetails: availableOptions.personalDetails.filter((detail) => detail.accountID !== session.accountID), + recentReports: availableOptions.recentReports.filter((report) => report.accountID !== session.accountID), }; - }, [defaultOptions, session?.accountID]); + }, [availableOptions, session?.accountID]); - const options = useMemo(() => { - const filteredOptions = filterAndOrderOptions(optionsWithoutCurrentUser, debouncedSearchValue.trim(), countryCode, { - excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, - maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, - }); - const headerMessage = getHeaderMessage( - (filteredOptions.recentReports?.length || 0) + (filteredOptions.personalDetails?.length || 0) !== 0 || !!filteredOptions.currentUserOption, - !!filteredOptions.userToInvite, - debouncedSearchValue, + const headerMessage = useMemo(() => { + return getHeaderMessage( + (optionsWithoutCurrentUser.recentReports?.length || 0) + (optionsWithoutCurrentUser.personalDetails?.length || 0) !== 0 || !!optionsWithoutCurrentUser.currentUserOption, + !!optionsWithoutCurrentUser.userToInvite, + debouncedSearchTerm, false, countryCode, ); - - return { - ...filteredOptions, - headerMessage, - }; - }, [debouncedSearchValue, optionsWithoutCurrentUser, countryCode]); - - return {...options, searchValue, debouncedSearchValue, setSearchValue, areOptionsInitialized}; -} - -function TaskAssigneeSelectorModal() { - const styles = useThemeStyles(); - const route = useRoute>(); - const {translate} = useLocalize(); - const session = useSession(); - const backTo = route.params?.backTo; - const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: false}); - const [task] = useOnyx(ONYXKEYS.TASK, {canBeMissing: false}); - const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); - const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const {userToInvite, recentReports, personalDetails, currentUserOption, searchValue, debouncedSearchValue, setSearchValue, headerMessage, areOptionsInitialized} = useOptions(); + }, [optionsWithoutCurrentUser, debouncedSearchTerm, countryCode]); const report: OnyxEntry = useMemo(() => { if (!route.params?.reportID) { @@ -139,30 +94,30 @@ function TaskAssigneeSelectorModal() { const sections = useMemo(() => { const sectionsList = []; - if (currentUserOption) { + if (optionsWithoutCurrentUser.currentUserOption) { sectionsList.push({ title: translate('newTaskPage.assignMe'), - data: [currentUserOption], + data: [optionsWithoutCurrentUser.currentUserOption], shouldShow: true, }); } sectionsList.push({ title: translate('common.recents'), - data: recentReports, - shouldShow: recentReports?.length > 0, + data: optionsWithoutCurrentUser.recentReports, + shouldShow: optionsWithoutCurrentUser.recentReports?.length > 0, }); sectionsList.push({ title: translate('common.contacts'), - data: personalDetails, - shouldShow: personalDetails?.length > 0, + data: optionsWithoutCurrentUser.personalDetails, + shouldShow: optionsWithoutCurrentUser.personalDetails?.length > 0, }); - if (userToInvite) { + if (optionsWithoutCurrentUser.userToInvite) { sectionsList.push({ title: '', - data: [userToInvite], + data: [optionsWithoutCurrentUser.userToInvite], shouldShow: true, }); } @@ -179,7 +134,7 @@ function TaskAssigneeSelectorModal() { shouldShowSubscript: option.shouldShowSubscript ?? undefined, })), })); - }, [currentUserOption, personalDetails, recentReports, translate, userToInvite]); + }, [optionsWithoutCurrentUser, translate]); const selectReport = useCallback( (option: ListItem) => { @@ -231,8 +186,8 @@ function TaskAssigneeSelectorModal() { const isTaskNonEditable = isTaskReport(report) && (!isTaskModifiable || !isOpen); useEffect(() => { - searchInServer(debouncedSearchValue); - }, [debouncedSearchValue]); + searchInServer(debouncedSearchTerm); + }, [debouncedSearchTerm]); return (