diff --git a/src/CONST.ts b/src/CONST.ts index 9ee9ec4d9147..0fc0b67565ce 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5578,6 +5578,11 @@ const CONST = { KEYWORD: 'keyword', IN: 'in', }, + ROUTER_LIST_ITEM_TYPE: { + REPORT: 'report', + SEARCH: 'search', + CONTEXTUAL_SUGGESTION: 'contextualSuggestion', + }, }, REFERRER: { diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index d73c3e8265c2..94b5c75a626c 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -31,6 +31,7 @@ function SearchRouter() { const {isSmallScreenWidth} = useResponsiveLayout(); const {isSearchRouterDisplayed, closeSearchRouter} = useSearchRouterContext(); + const [textInputValue, setTextInputValue] = useState(''); const [userSearchQuery, setUserSearchQuery] = useState(undefined); const contextualReportID = useNavigationState, string | undefined>((state) => { return state.routes.at(-1)?.params?.reportID; @@ -56,26 +57,35 @@ function SearchRouter() { const contextualReportData = contextualReportID ? searchOptions.recentReports?.find((option) => option.reportID === contextualReportID) : undefined; const clearUserQuery = () => { + setTextInputValue(''); setUserSearchQuery(undefined); }; - const onSearchChange = debounce((userQuery: string) => { - if (!userQuery) { - clearUserQuery(); - return; - } + const onSearchChange = useCallback( + debounce((userQuery: string) => { + if (!userQuery) { + clearUserQuery(); + return; + } - const queryJSON = SearchUtils.buildSearchQueryJSON(userQuery); + const queryJSON = SearchUtils.buildSearchQueryJSON(userQuery); - if (queryJSON) { - // eslint-disable-next-line - console.log('parsedQuery', queryJSON); + if (queryJSON) { + // eslint-disable-next-line + console.log('parsedQuery', queryJSON); - setUserSearchQuery(queryJSON); - } else { - // Handle query parsing error - } - }, SEARCH_DEBOUNCE_DELAY); + setUserSearchQuery(queryJSON); + } else { + // Handle query parsing error + } + }, SEARCH_DEBOUNCE_DELAY), + [], + ); + + const updateUserSearchQuery = (newSearchQuery: string) => { + setTextInputValue(newSearchQuery); + onSearchChange(newSearchQuery); + }; const closeAndClearRouter = useCallback(() => { closeSearchRouter(); @@ -114,7 +124,9 @@ function SearchRouter() { { onSearchSubmit(userSearchQuery); }} @@ -125,7 +137,8 @@ function SearchRouter() { reportForContextualSearch={contextualReportData} recentSearches={sortedRecentSearches} recentReports={searchOptions?.recentReports?.slice(0, 5)} - onRecentSearchSelect={onSearchSubmit} + onSearchSubmit={onSearchSubmit} + updateUserSearchQuery={updateUserSearchQuery} closeAndClearRouter={closeAndClearRouter} /> diff --git a/src/components/Search/SearchRouter/SearchRouterInput.tsx b/src/components/Search/SearchRouter/SearchRouterInput.tsx index b1405f2c9701..1a067d22f1d9 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput.tsx @@ -4,23 +4,23 @@ import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; type SearchRouterInputProps = { - onChange: (searchTerm: string) => void; + text: string; + setText: (searchTerm: string) => void; + updateSearch: (searchTerm: string) => void; onSubmit: () => void; }; -function SearchRouterInput({onChange, onSubmit}: SearchRouterInputProps) { +function SearchRouterInput({text, setText, updateSearch, onSubmit}: SearchRouterInputProps) { const styles = useThemeStyles(); - const [value, setValue] = useState(''); - const onChangeText = (text: string) => { - setValue(text); - onChange(text); + setText(text); + updateSearch(text); }; return ( void; + onSearchSubmit: (query: SearchQueryJSON | undefined) => void; + updateUserSearchQuery: (newSearchQuery: string) => void; closeAndClearRouter: () => void; }; -function SearchRouterItem(props: UserListItemProps | SingleIconListItemProps) { +function SearchRouterItem(props: UserListItemProps | SingleIconListItemProps) { const styles = useThemeStyles(); - - if ('item' in props && props.item.reportID) { + if (props.item.itemType === CONST.SEARCH.ROUTER_LIST_ITEM_TYPE.REPORT) { return ( )} /> @@ -42,10 +44,10 @@ function SearchRouterItem(props: UserListItemProps | SingleIconListI return )} />; } -function SearchRouterList({currentQuery, reportForContextualSearch, recentSearches, recentReports, onRecentSearchSelect, closeAndClearRouter}: SearchRouterListProps) { +function SearchRouterList({currentQuery, reportForContextualSearch, recentSearches, recentReports, onSearchSubmit, updateUserSearchQuery, closeAndClearRouter}: SearchRouterListProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const sections: Array> = []; + const sections: Array> = []; if (currentQuery?.inputQuery) { sections.push({ @@ -56,6 +58,7 @@ function SearchRouterList({currentQuery, reportForContextualSearch, recentSearch query: currentQuery?.inputQuery, itemStyle: styles.activeComponentBG, keyForList: 'findItem', + itemType: CONST.SEARCH.ROUTER_LIST_ITEM_TYPE.SEARCH, }, ], }); @@ -67,10 +70,10 @@ function SearchRouterList({currentQuery, reportForContextualSearch, recentSearch { text: `${translate('search.searchIn')}${reportForContextualSearch.text ?? reportForContextualSearch.alternateText}`, singleIcon: Expensicons.MagnifyingGlass, - // We will change it to different behaviour when Search 2.5 autocomplete will be implemented query: `in:${reportForContextualSearch.reportID}`, itemStyle: styles.activeComponentBG, keyForList: 'contextualSearch', + itemType: CONST.SEARCH.ROUTER_LIST_ITEM_TYPE.CONTEXTUAL_SUGGESTION, }, ], }); @@ -81,37 +84,50 @@ function SearchRouterList({currentQuery, reportForContextualSearch, recentSearch singleIcon: Expensicons.History, query, keyForList: query, + itemType: CONST.SEARCH.ROUTER_LIST_ITEM_TYPE.SEARCH, })); if (recentSearchesData && recentSearchesData.length > 0) { sections.push({title: translate('search.recentSearches'), data: recentSearchesData}); } - const styledRecentReports = recentReports.map((item) => ({...item, pressableStyle: styles.br2})); - sections.push({title: translate('search.recentChats'), data: styledRecentReports}); + const recentReportsData = recentReports.map((item) => ({...item, pressableStyle: styles.br2, itemType: CONST.SEARCH.ROUTER_LIST_ITEM_TYPE.REPORT})); + sections.push({title: translate('search.recentChats'), data: recentReportsData}); const onSelectRow = useCallback( - (item: OptionData | ItemWithQuery) => { - // Handle selection of "Recent search" - if ('query' in item && item?.query) { - const queryJSON = SearchUtils.buildSearchQueryJSON(item?.query); - onRecentSearchSelect(queryJSON, true); - return; - } - - // Handle selection of "Recent chat" - closeAndClearRouter(); - if ('reportID' in item && item?.reportID) { - Navigation.closeAndNavigate(ROUTES.REPORT_WITH_ID.getRoute(item?.reportID)); - } else if ('login' in item) { - Report.navigateToAndOpenReport(item?.login ? [item.login] : []); + (item: SearchRouterListItem) => { + switch (item.itemType) { + case CONST.SEARCH.ROUTER_LIST_ITEM_TYPE.SEARCH: + // Handle selection of "Recent search" + if (!('query' in item) || !item?.query) { + return; + } + const queryJSON = SearchUtils.buildSearchQueryJSON(item?.query); + onSearchSubmit(queryJSON); + return; + case CONST.SEARCH.ROUTER_LIST_ITEM_TYPE.CONTEXTUAL_SUGGESTION: + // Handle selection of "Contextual search suggestion" + if (!('query' in item) || !item?.query || currentQuery?.inputQuery.includes(item?.query)) { + return; + } + updateUserSearchQuery(`${item?.query} ${currentQuery?.inputQuery ?? ''}`); + return; + case CONST.SEARCH.ROUTER_LIST_ITEM_TYPE.REPORT: + // Handle selection of "Recent chat" + closeAndClearRouter(); + if ('reportID' in item && item?.reportID) { + Navigation.closeAndNavigate(ROUTES.REPORT_WITH_ID.getRoute(item?.reportID)); + } else if ('login' in item) { + Report.navigateToAndOpenReport(item?.login ? [item.login] : []); + } + return; } }, - [closeAndClearRouter, onRecentSearchSelect], + [closeAndClearRouter, onSearchSubmit, currentQuery], ); return ( - + sections={sections} onSelectRow={onSelectRow} ListItem={SearchRouterItem} @@ -122,3 +138,4 @@ function SearchRouterList({currentQuery, reportForContextualSearch, recentSearch export default SearchRouterList; export {SearchRouterItem}; +export type {ItemWithQuery}; diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 195073f8b89f..c76f8959533e 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -1,6 +1,10 @@ import type {ValueOf} from 'react-native-gesture-handler/lib/typescript/typeUtils'; +import type {ListItemWithSingleIcon, SingleIconListItemProps} from '@components/SelectionList/Search/SingleIconListItem'; +import type {ListItemProps, UserListItemProps} from '@components/SelectionList/types'; +import type {OptionData} from '@libs/ReportUtils'; import type CONST from '@src/CONST'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; +import type {ItemWithQuery} from './SearchRouter/SearchRouterList'; /** Model of the selected transaction */ type SelectedTransactionInfo = { @@ -73,6 +77,8 @@ type SearchQueryJSON = { flatFilters: QueryFilters; } & SearchQueryAST; +type SearchRouterListItem = (OptionData | (ListItemWithSingleIcon & ItemWithQuery)) & {itemType?: ValueOf}; + export type { SelectedTransactionInfo, SelectedTransactions, @@ -91,4 +97,5 @@ export type { InvoiceSearchStatus, TripSearchStatus, ChatSearchStatus, + SearchRouterListItem, }; diff --git a/src/components/SelectionList/Search/SingleIconListItem.tsx b/src/components/SelectionList/Search/SingleIconListItem.tsx index 784aa4822335..9aa3274d3686 100644 --- a/src/components/SelectionList/Search/SingleIconListItem.tsx +++ b/src/components/SelectionList/Search/SingleIconListItem.tsx @@ -8,7 +8,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import type IconAsset from '@src/types/utils/IconAsset'; -type ListItemWithSingleIcon = {singleIcon: IconAsset} & ListItem; +type ListItemWithSingleIcon = {singleIcon?: IconAsset} & ListItem; type SingleIconListItemProps = { item: TItem;