From 1b9196e6af8e57eecc08f98a2b71e72a8a364ff7 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Tue, 15 Oct 2024 12:30:10 +0200 Subject: [PATCH] Split SearchUtils into two files --- src/components/Search/SearchContext.tsx | 6 +- src/components/Search/SearchPageHeader.tsx | 14 +- .../Search/SearchRouter/SearchRouter.tsx | 8 +- .../Search/SearchRouter/SearchRouterList.tsx | 10 +- src/components/Search/SearchStatusBar.tsx | 4 +- src/components/Search/index.tsx | 33 +- .../SelectionList/BaseSelectionList.tsx | 4 +- .../Search/ExpenseItemHeaderNarrow.tsx | 4 +- .../SelectionList/Search/ReportListItem.tsx | 2 +- .../SelectionList/Search/UserInfoCell.tsx | 4 +- .../SelectionList/SearchTableHeader.tsx | 6 +- src/hooks/useDeleteSavedSearch.tsx | 4 +- .../Navigation/AppNavigator/AuthScreens.tsx | 4 +- .../BottomTabBar.tsx | 8 +- .../createCustomBottomTabNavigator/TopBar.tsx | 4 +- .../BottomTabBar.tsx | 8 +- .../TopBar.tsx | 4 +- .../Navigation/extractPolicyIDFromQuery.ts | 6 +- src/libs/Navigation/switchPolicyID.ts | 10 +- .../{SearchUtils.ts => SearchQueryUtils.ts} | 469 +----------------- src/libs/SearchUIUtils.ts | 440 ++++++++++++++++ src/pages/Search/AdvancedSearchFilters.tsx | 11 +- src/pages/Search/SavedSearchRenamePage.tsx | 4 +- .../SearchFiltersExpenseTypePage.tsx | 2 +- src/pages/Search/SearchPage.tsx | 6 +- src/pages/Search/SearchPageBottomTab.tsx | 10 +- src/pages/Search/SearchTypeMenu.tsx | 21 +- src/pages/Search/SearchTypeMenuNarrow.tsx | 7 +- .../Subscription/CardSection/CardSection.tsx | 4 +- 29 files changed, 560 insertions(+), 557 deletions(-) rename src/libs/{SearchUtils.ts => SearchQueryUtils.ts} (54%) create mode 100644 src/libs/SearchUIUtils.ts diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 30825ed3bfba..f3206868d556 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -1,6 +1,6 @@ import React, {useCallback, useContext, useMemo, useState} from 'react'; import type {ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchUIUtils from '@libs/SearchUIUtils'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type {SearchContext, SelectedTransactions} from './types'; @@ -23,8 +23,8 @@ function getReportsFromSelectedTransactions(data: TransactionListItemType[] | Re return (data ?? []) .filter( (item) => - !SearchUtils.isTransactionListItemType(item) && - !SearchUtils.isReportActionListItemType(item) && + !SearchUIUtils.isTransactionListItemType(item) && + !SearchUIUtils.isReportActionListItemType(item) && item.reportID && item?.transactions?.every((transaction: {keyForList: string | number}) => selectedTransactions[transaction.keyForList]?.isSelected), ) diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index 4c383021645f..51f6c68c11ae 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -23,7 +23,7 @@ import * as SearchActions from '@libs/actions/Search'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import {getAllTaxRates} from '@libs/PolicyUtils'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import SearchSelectedNarrow from '@pages/Search/SearchSelectedNarrow'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -136,8 +136,8 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) { const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); const {status, type} = queryJSON; - const isCannedQuery = SearchUtils.isCannedSearchQuery(queryJSON); - const headerText = isCannedQuery ? translate(getHeaderContent(type).titleText) : SearchUtils.getSearchHeaderTitle(queryJSON, personalDetails, cardList, reports, taxRates); + const isCannedQuery = SearchQueryUtils.isCannedSearchQuery(queryJSON); + const headerText = isCannedQuery ? translate(getHeaderContent(type).titleText) : SearchQueryUtils.getSearchHeaderTitle(queryJSON, personalDetails, cardList, reports, taxRates); const [inputValue, setInputValue] = useState(headerText); useEffect(() => { @@ -327,7 +327,7 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) { } const onPress = () => { - const filterFormValues = SearchUtils.buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, cardList, reports, taxRates); + const filterFormValues = SearchQueryUtils.buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, cardList, reports, taxRates); SearchActions.updateAdvancedFilters(filterFormValues); Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS); @@ -337,10 +337,10 @@ function SearchPageHeader({queryJSON, hash}: SearchPageHeaderProps) { if (!inputValue) { return; } - const inputQueryJSON = SearchUtils.buildSearchQueryJSON(inputValue); + const inputQueryJSON = SearchQueryUtils.buildSearchQueryJSON(inputValue); if (inputQueryJSON) { - const standardizedQuery = SearchUtils.standardizeQueryJSON(inputQueryJSON, cardList, taxRates); - const query = SearchUtils.buildSearchQueryString(standardizedQuery); + const standardizedQuery = SearchQueryUtils.standardizeQueryJSON(inputQueryJSON, cardList, taxRates); + const query = SearchQueryUtils.buildSearchQueryString(standardizedQuery); SearchActions.clearAllFilters(); Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query})); } else { diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 924cf366415a..43709e4f8a13 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -16,7 +16,7 @@ import Log from '@libs/Log'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import {getAllTaxRates} from '@libs/PolicyUtils'; import type {OptionData} from '@libs/ReportUtils'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import Navigation from '@navigation/Navigation'; import variables from '@styles/variables'; import * as Report from '@userActions/Report'; @@ -117,7 +117,7 @@ function SearchRouter() { return; } listRef.current?.updateAndScrollToFocusedIndex(0); - const queryJSON = SearchUtils.buildSearchQueryJSON(userQuery); + const queryJSON = SearchQueryUtils.buildSearchQueryJSON(userQuery); if (queryJSON) { setUserSearchQuery(queryJSON); @@ -148,8 +148,8 @@ function SearchRouter() { return; } closeSearchRouter(); - const standardizedQuery = SearchUtils.standardizeQueryJSON(query, cardList, taxRates); - const queryString = SearchUtils.buildSearchQueryString(standardizedQuery); + const standardizedQuery = SearchQueryUtils.standardizeQueryJSON(query, cardList, taxRates); + const queryString = SearchQueryUtils.buildSearchQueryString(standardizedQuery); Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: queryString})); clearUserQuery(); }, diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index 20ade90843d7..89a59f23647b 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -15,7 +15,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import {getAllTaxRates} from '@libs/PolicyUtils'; import type {OptionData} from '@libs/ReportUtils'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import * as Report from '@userActions/Report'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -113,7 +113,7 @@ function SearchRouterList( { text: `${translate('search.searchIn')} ${reportForContextualSearch.text ?? reportForContextualSearch.alternateText}`, singleIcon: Expensicons.MagnifyingGlass, - query: SearchUtils.getContextualSuggestionQuery(reportForContextualSearch.reportID), + query: SearchQueryUtils.getContextualSuggestionQuery(reportForContextualSearch.reportID), itemStyle: styles.activeComponentBG, keyForList: 'contextualSearch', isContextualSearchItem: true, @@ -123,9 +123,9 @@ function SearchRouterList( } const recentSearchesData = recentSearches?.map(({query}) => { - const searchQueryJSON = SearchUtils.buildSearchQueryJSON(query); + const searchQueryJSON = SearchQueryUtils.buildSearchQueryJSON(query); return { - text: searchQueryJSON ? SearchUtils.getSearchHeaderTitle(searchQueryJSON, personalDetails, cardList, reports, taxRates) : query, + text: searchQueryJSON ? SearchQueryUtils.getSearchHeaderTitle(searchQueryJSON, personalDetails, cardList, reports, taxRates) : query, singleIcon: Expensicons.History, query, keyForList: query, @@ -152,7 +152,7 @@ function SearchRouterList( if (!item?.query) { return; } - onSearchSubmit(SearchUtils.buildSearchQueryJSON(item?.query)); + onSearchSubmit(SearchQueryUtils.buildSearchQueryJSON(item?.query)); } // Handle selection of "Recent chat" diff --git a/src/components/Search/SearchStatusBar.tsx b/src/components/Search/SearchStatusBar.tsx index afba2acc415c..07b57f8acab8 100644 --- a/src/components/Search/SearchStatusBar.tsx +++ b/src/components/Search/SearchStatusBar.tsx @@ -11,7 +11,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; @@ -177,7 +177,7 @@ function SearchStatusBar({queryJSON, onStatusChange}: SearchStatusBarProps) { {options.map((item, index) => { const onPress = singleExecution(() => { onStatusChange?.(); - const query = SearchUtils.buildSearchQueryString({...queryJSON, status: item.status}); + const query = SearchQueryUtils.buildSearchQueryString({...queryJSON, status: item.status}); Navigation.setParams({q: query}); }); const isActive = queryJSON.status === item.status; diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 670cfef54df8..a360d116a345 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -22,7 +22,8 @@ import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Log from '@libs/Log'; import memoize from '@libs/memoize'; import * as ReportUtils from '@libs/ReportUtils'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; +import * as SearchUIUtils from '@libs/SearchUIUtils'; import Navigation from '@navigation/Navigation'; import type {AuthScreensParamList} from '@navigation/types'; import EmptySearchView from '@pages/Search/EmptySearchView'; @@ -60,11 +61,11 @@ function mapToItemWithSelectionInfo( canSelectMultiple: boolean, shouldAnimateInHighlight: boolean, ) { - if (SearchUtils.isReportActionListItemType(item)) { + if (SearchUIUtils.isReportActionListItemType(item)) { return item; } - return SearchUtils.isTransactionListItemType(item) + return SearchUIUtils.isTransactionListItemType(item) ? mapToTransactionItemWithSelectionInfo(item, selectedTransactions, canSelectMultiple, shouldAnimateInHighlight) : { ...item, @@ -139,7 +140,7 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr const getItemHeight = useCallback( (item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => { - if (SearchUtils.isTransactionListItemType(item) || SearchUtils.isReportActionListItemType(item)) { + if (SearchUIUtils.isTransactionListItemType(item) || SearchUIUtils.isReportActionListItemType(item)) { return isLargeScreenWidth ? variables.optionRowHeight + listItemPadding : transactionItemMobileHeight + listItemPadding; } @@ -186,9 +187,9 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr const isDataLoaded = searchResults?.data !== undefined && searchResults?.search?.type === type && searchResults?.search?.status === status; const shouldShowLoadingState = !isOffline && !isDataLoaded; const shouldShowLoadingMoreItems = !shouldShowLoadingState && searchResults?.search?.isLoading && searchResults?.search?.offset > 0; - const isSearchResultsEmpty = !searchResults?.data || SearchUtils.isSearchResultsEmpty(searchResults); + const isSearchResultsEmpty = !searchResults?.data || SearchUIUtils.isSearchResultsEmpty(searchResults); const prevIsSearchResultEmpty = usePrevious(isSearchResultsEmpty); - const data = searchResults === undefined ? [] : SearchUtils.getSections(type, status, searchResults.data, searchResults.search); + const data = searchResults === undefined ? [] : SearchUIUtils.getSections(type, status, searchResults.data, searchResults.search); useEffect(() => { /** We only want to display the skeleton for the status filters the first time we load them for a specific data type */ @@ -260,8 +261,8 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr return {null}; } - const ListItem = SearchUtils.getListItem(type, status); - const sortedData = SearchUtils.getSortedSections(type, status, data, sortBy, sortOrder); + const ListItem = SearchUIUtils.getListItem(type, status); + const sortedData = SearchUIUtils.getSortedSections(type, status, data, sortBy, sortOrder); const sortedSelectedData = sortedData.map((item) => { const baseKey = `${ONYXKEYS.COLLECTION.TRANSACTION}${(item as TransactionListItemType).transactionID}`; // Check if the base key matches the newSearchResultKey (TransactionListItemType) @@ -288,10 +289,10 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr } const toggleTransaction = (item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => { - if (SearchUtils.isReportActionListItemType(item)) { + if (SearchUIUtils.isReportActionListItemType(item)) { return; } - if (SearchUtils.isTransactionListItemType(item)) { + if (SearchUIUtils.isTransactionListItemType(item)) { if (!item.keyForList) { return; } @@ -322,21 +323,21 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr const openReport = (item: TransactionListItemType | ReportListItemType | ReportActionListItemType) => { const isFromSelfDM = item.reportID === CONST.REPORT.UNREPORTED_REPORTID; - let reportID = SearchUtils.isTransactionListItemType(item) && (!item.isFromOneTransactionReport || isFromSelfDM) ? item.transactionThreadReportID : item.reportID; + let reportID = SearchUIUtils.isTransactionListItemType(item) && (!item.isFromOneTransactionReport || isFromSelfDM) ? item.transactionThreadReportID : item.reportID; if (!reportID) { return; } // If we're trying to open a legacy transaction without a transaction thread, let's create the thread and navigate the user - if (SearchUtils.isTransactionListItemType(item) && reportID === '0' && item.moneyRequestReportActionID) { + if (SearchUIUtils.isTransactionListItemType(item) && reportID === '0' && item.moneyRequestReportActionID) { reportID = ReportUtils.generateReportID(); SearchActions.createTransactionThread(hash, item.transactionID, reportID, item.moneyRequestReportActionID); } const backTo = Navigation.getActiveRoute(); - if (SearchUtils.isReportActionListItemType(item)) { + if (SearchUIUtils.isReportActionListItemType(item)) { const reportActionID = item.reportActionID; Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, reportActionID, backTo})); return; @@ -372,11 +373,11 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr }; const onSortPress = (column: SearchColumnType, order: SortOrder) => { - const newQuery = SearchUtils.buildSearchQueryString({...queryJSON, sortBy: column, sortOrder: order}); + const newQuery = SearchQueryUtils.buildSearchQueryString({...queryJSON, sortBy: column, sortOrder: order}); navigation.setParams({q: newQuery}); }; - const shouldShowYear = SearchUtils.shouldShowYear(searchResults?.data); + const shouldShowYear = SearchUIUtils.shouldShowYear(searchResults?.data); const shouldShowSorting = sortableSearchStatuses.includes(status); return ( @@ -401,7 +402,7 @@ function Search({queryJSON, onSearchListScroll, contentContainerStyle}: SearchPr ) } isSelected={(item) => - status !== CONST.SEARCH.STATUS.EXPENSE.ALL && SearchUtils.isReportListItemType(item) + status !== CONST.SEARCH.STATUS.EXPENSE.ALL && SearchUIUtils.isReportListItemType(item) ? item.transactions.some((transaction) => selectedTransactions[transaction.keyForList]?.isSelected) : !!item.isSelected } diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 06bf8eb6434a..892be8eca96c 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -24,7 +24,7 @@ import useSingleExecution from '@hooks/useSingleExecution'; import useThemeStyles from '@hooks/useThemeStyles'; import getSectionsWithIndexOffset from '@libs/getSectionsWithIndexOffset'; import Log from '@libs/Log'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchUIUtils from '@libs/SearchUIUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -437,7 +437,7 @@ function BaseSelectionList( const showTooltip = shouldShowTooltips && normalizedIndex < 10; const handleOnCheckboxPress = () => { - if (SearchUtils.isReportListItemType(item)) { + if (SearchUIUtils.isReportListItemType(item)) { return onCheckboxPress; } return onCheckboxPress ? () => onCheckboxPress(item) : undefined; diff --git a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx index 424bbd50d7b2..5b643c148731 100644 --- a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx +++ b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx @@ -7,7 +7,7 @@ import {PressableWithFeedback} from '@components/Pressable'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchUIUtils from '@libs/SearchUIUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import type {SearchPersonalDetails, SearchTransactionAction} from '@src/types/onyx/SearchResults'; @@ -50,7 +50,7 @@ function ExpenseItemHeaderNarrow({ const theme = useTheme(); // It might happen that we are missing display names for `From` or `To`, we only display arrow icon if both names exist - const shouldDisplayArrowIcon = SearchUtils.isCorrectSearchUserName(participantFromDisplayName) && SearchUtils.isCorrectSearchUserName(participantToDisplayName); + const shouldDisplayArrowIcon = SearchUIUtils.isCorrectSearchUserName(participantFromDisplayName) && SearchUIUtils.isCorrectSearchUserName(participantToDisplayName); return ( diff --git a/src/components/SelectionList/Search/ReportListItem.tsx b/src/components/SelectionList/Search/ReportListItem.tsx index 147e1686be5b..7e283557819a 100644 --- a/src/components/SelectionList/Search/ReportListItem.tsx +++ b/src/components/SelectionList/Search/ReportListItem.tsx @@ -110,7 +110,7 @@ function ReportListItem({ const participantFrom = reportItem.from; const participantTo = reportItem.to; - // These values should come as part of the item via SearchUtils.getSections() but ReportListItem is not yet 100% handled + // These values should come as part of the item via SearchUIUtils.getSections() but ReportListItem is not yet 100% handled // This will be simplified in future once sorting of ReportListItem is done const participantFromDisplayName = participantFrom?.displayName ?? participantFrom?.login ?? ''; const participantToDisplayName = participantTo?.displayName ?? participantTo?.login ?? ''; diff --git a/src/components/SelectionList/Search/UserInfoCell.tsx b/src/components/SelectionList/Search/UserInfoCell.tsx index 3a6c98178a3b..6a653471683a 100644 --- a/src/components/SelectionList/Search/UserInfoCell.tsx +++ b/src/components/SelectionList/Search/UserInfoCell.tsx @@ -4,7 +4,7 @@ import Avatar from '@components/Avatar'; import Text from '@components/Text'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchUIUtils from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import type {SearchPersonalDetails} from '@src/types/onyx/SearchResults'; @@ -18,7 +18,7 @@ function UserInfoCell({participant, displayName}: UserInfoCellProps) { const {isLargeScreenWidth} = useResponsiveLayout(); const avatarURL = participant?.avatar; - if (!SearchUtils.isCorrectSearchUserName(displayName)) { + if (!SearchUIUtils.isCorrectSearchUserName(displayName)) { return null; } diff --git a/src/components/SelectionList/SearchTableHeader.tsx b/src/components/SelectionList/SearchTableHeader.tsx index f54532a7f318..5cae23eb9ab5 100644 --- a/src/components/SelectionList/SearchTableHeader.tsx +++ b/src/components/SelectionList/SearchTableHeader.tsx @@ -5,7 +5,7 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchUIUtils from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import type * as OnyxTypes from '@src/types/onyx'; @@ -39,12 +39,12 @@ const expenseHeaders: SearchColumnConfig[] = [ { columnName: CONST.SEARCH.TABLE_COLUMNS.MERCHANT, translationKey: 'common.merchant', - shouldShow: (data: OnyxTypes.SearchResults['data']) => SearchUtils.getShouldShowMerchant(data), + shouldShow: (data: OnyxTypes.SearchResults['data']) => SearchUIUtils.getShouldShowMerchant(data), }, { columnName: CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION, translationKey: 'common.description', - shouldShow: (data: OnyxTypes.SearchResults['data']) => !SearchUtils.getShouldShowMerchant(data), + shouldShow: (data: OnyxTypes.SearchResults['data']) => !SearchUIUtils.getShouldShowMerchant(data), }, { columnName: CONST.SEARCH.TABLE_COLUMNS.FROM, diff --git a/src/hooks/useDeleteSavedSearch.tsx b/src/hooks/useDeleteSavedSearch.tsx index 668f9048e7fb..19e5def4601d 100644 --- a/src/hooks/useDeleteSavedSearch.tsx +++ b/src/hooks/useDeleteSavedSearch.tsx @@ -1,7 +1,7 @@ import React, {useState} from 'react'; import ConfirmModal from '@components/ConfirmModal'; import Navigation from '@libs/Navigation/Navigation'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import * as SearchActions from '@userActions/Search'; import ROUTES from '@src/ROUTES'; import useLocalize from './useLocalize'; @@ -22,7 +22,7 @@ export default function useDeleteSavedSearch() { SearchActions.clearAdvancedFilters(); Navigation.navigate( ROUTES.SEARCH_CENTRAL_PANE.getRoute({ - query: SearchUtils.buildCannedSearchQuery(), + query: SearchQueryUtils.buildCannedSearchQuery(), }), ); }; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index f103504cbd86..40910014faa9 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -28,7 +28,7 @@ import onyxSubscribe from '@libs/onyxSubscribe'; import * as Pusher from '@libs/Pusher/pusher'; import PusherConnectionManager from '@libs/PusherConnectionManager'; import * as ReportUtils from '@libs/ReportUtils'; -import {buildSearchQueryString} from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import * as SessionUtils from '@libs/SessionUtils'; import ConnectionCompletePage from '@pages/ConnectionCompletePage'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; @@ -93,7 +93,7 @@ const loadWorkspaceJoinUser = () => require('@pages/worksp function getCentralPaneScreenInitialParams(screenName: CentralPaneName, initialReportID?: string): Partial> { if (screenName === SCREENS.SEARCH.CENTRAL_PANE) { // Generate default query string with buildSearchQueryString without argument. - return {q: buildSearchQueryString()}; + return {q: SearchQueryUtils.buildSearchQueryString()}; } if (screenName === SCREENS.REPORT) { diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index 16a3e5e098f6..0aa3a343b71d 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -15,7 +15,7 @@ import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList, RootStackParamList, State} from '@libs/Navigation/types'; import * as PolicyUtils from '@libs/PolicyUtils'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import type {BrickRoad} from '@libs/WorkspacesSettingsUtils'; import {getChatTabBrickRoad} from '@libs/WorkspacesSettingsUtils'; import navigationRef from '@navigation/navigationRef'; @@ -42,7 +42,7 @@ type BottomTabBarProps = { * Otherwise policyID will be inserted into query */ function handleQueryWithPolicyID(query: SearchQueryString, activePolicyID?: string): SearchQueryString { - const queryJSON = SearchUtils.buildSearchQueryJSON(query); + const queryJSON = SearchQueryUtils.buildSearchQueryJSON(query); if (!queryJSON) { return query; } @@ -57,7 +57,7 @@ function handleQueryWithPolicyID(query: SearchQueryString, activePolicyID?: stri queryJSON.policyID = policyID; } - return SearchUtils.buildSearchQueryString(queryJSON); + return SearchQueryUtils.buildSearchQueryString(queryJSON); } function BottomTabBar({selectedTab}: BottomTabBarProps) { @@ -101,7 +101,7 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) { return; } - const defaultCannedQuery = SearchUtils.buildCannedSearchQuery(); + const defaultCannedQuery = SearchQueryUtils.buildCannedSearchQuery(); // when navigating to search we might have an activePolicyID set from workspace switcher const query = activeWorkspaceID ? `${defaultCannedQuery} ${CONST.SEARCH.SYNTAX_ROOT_KEYS.POLICY_ID}:${activeWorkspaceID}` : defaultCannedQuery; Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query})); diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx index 4684eb9637be..3b4879839ae0 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx @@ -15,7 +15,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import SignInButton from '@pages/home/sidebar/SignInButton'; import * as Session from '@userActions/Session'; import Timing from '@userActions/Timing'; @@ -68,7 +68,7 @@ function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, accessibilityLabel={translate('common.cancel')} style={[styles.textBlue]} onPress={() => { - Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchUtils.buildCannedSearchQuery()})); + Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchQueryUtils.buildCannedSearchQuery()})); }} > {translate('common.cancel')} diff --git a/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/BottomTabBar.tsx index cbcfa4b84677..3bf029012b36 100644 --- a/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/BottomTabBar.tsx @@ -17,7 +17,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; import {isCentralPaneName} from '@libs/NavigationUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import type {BrickRoad} from '@libs/WorkspacesSettingsUtils'; import {getChatTabBrickRoad} from '@libs/WorkspacesSettingsUtils'; import navigationRef from '@navigation/navigationRef'; @@ -47,7 +47,7 @@ type BottomTabBarProps = { * Otherwise policyID will be inserted into query */ function handleQueryWithPolicyID(query: SearchQueryString, activePolicyID?: string): SearchQueryString { - const queryJSON = SearchUtils.buildSearchQueryJSON(query); + const queryJSON = SearchQueryUtils.buildSearchQueryJSON(query); if (!queryJSON) { return query; } @@ -62,7 +62,7 @@ function handleQueryWithPolicyID(query: SearchQueryString, activePolicyID?: stri queryJSON.policyID = policyID; } - return SearchUtils.buildSearchQueryString(queryJSON); + return SearchQueryUtils.buildSearchQueryString(queryJSON); } function BottomTabBar({selectedTab}: BottomTabBarProps) { @@ -130,7 +130,7 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) { return; } - const defaultCannedQuery = SearchUtils.buildCannedSearchQuery(); + const defaultCannedQuery = SearchQueryUtils.buildCannedSearchQuery(); // when navigating to search we might have an activePolicyID set from workspace switcher const query = activeWorkspaceID ? `${defaultCannedQuery} ${CONST.SEARCH.SYNTAX_ROOT_KEYS.POLICY_ID}:${activeWorkspaceID}` : defaultCannedQuery; Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query})); diff --git a/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/TopBar.tsx b/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/TopBar.tsx index 4684eb9637be..3b4879839ae0 100644 --- a/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/TopBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomPlatformStackBottomTabNavigator/TopBar.tsx @@ -15,7 +15,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import Performance from '@libs/Performance'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import SignInButton from '@pages/home/sidebar/SignInButton'; import * as Session from '@userActions/Session'; import Timing from '@userActions/Timing'; @@ -68,7 +68,7 @@ function TopBar({breadcrumbLabel, activeWorkspaceID, shouldDisplaySearch = true, accessibilityLabel={translate('common.cancel')} style={[styles.textBlue]} onPress={() => { - Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchUtils.buildCannedSearchQuery()})); + Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchQueryUtils.buildCannedSearchQuery()})); }} > {translate('common.cancel')} diff --git a/src/libs/Navigation/extractPolicyIDFromQuery.ts b/src/libs/Navigation/extractPolicyIDFromQuery.ts index bd0464f4aab6..f091690c16f2 100644 --- a/src/libs/Navigation/extractPolicyIDFromQuery.ts +++ b/src/libs/Navigation/extractPolicyIDFromQuery.ts @@ -1,4 +1,4 @@ -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import type {NavigationPartialRoute} from './types'; function extractPolicyIDFromQuery(route?: NavigationPartialRoute) { @@ -11,12 +11,12 @@ function extractPolicyIDFromQuery(route?: NavigationPartialRoute) { } const queryString = route.params.q as string; - const queryJSON = SearchUtils.buildSearchQueryJSON(queryString); + const queryJSON = SearchQueryUtils.buildSearchQueryJSON(queryString); if (!queryJSON) { return undefined; } - return SearchUtils.getPolicyIDFromSearchQuery(queryJSON); + return SearchQueryUtils.getPolicyIDFromSearchQuery(queryJSON); } export default extractPolicyIDFromQuery; diff --git a/src/libs/Navigation/switchPolicyID.ts b/src/libs/Navigation/switchPolicyID.ts index 5ccc2da54418..16e705258e58 100644 --- a/src/libs/Navigation/switchPolicyID.ts +++ b/src/libs/Navigation/switchPolicyID.ts @@ -4,7 +4,7 @@ import {getPathFromState} from '@react-navigation/native'; import type {Writable} from 'type-fest'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import {isCentralPaneName} from '@libs/NavigationUtils'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import CONST from '@src/CONST'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; @@ -83,7 +83,7 @@ export default function switchPolicyID(navigation: NavigationContainerRef>; const action: StackNavigationAction = getActionFromState(stateFromPath, linkingConfig.config); @@ -110,16 +110,16 @@ export default function switchPolicyID(navigation: NavigationContainerRef { - const isExpenseReport = transactionItem.reportType === CONST.REPORT.TYPE.EXPENSE; - - const formattedFrom = from?.displayName ?? from?.login ?? ''; - const formattedTo = to?.displayName ?? to?.login ?? ''; - const formattedTotal = TransactionUtils.getAmount(transactionItem, isExpenseReport); - const date = transactionItem?.modifiedCreated ? transactionItem.modifiedCreated : transactionItem?.created; - const merchant = TransactionUtils.getMerchant(transactionItem); - const formattedMerchant = merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || merchant === CONST.TRANSACTION.DEFAULT_MERCHANT ? '' : merchant; - - return { - formattedFrom, - formattedTo, - date, - formattedTotal, - formattedMerchant, - }; -} - -type ReportKey = `${typeof ONYXKEYS.COLLECTION.REPORT}${string}`; - -type TransactionKey = `${typeof ONYXKEYS.COLLECTION.TRANSACTION}${string}`; - -type ReportActionKey = `${typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS}${string}`; - -function isReportEntry(key: string): key is ReportKey { - return key.startsWith(ONYXKEYS.COLLECTION.REPORT); -} - -function isTransactionEntry(key: string): key is TransactionKey { - return key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION); -} - -function isReportActionEntry(key: string): key is ReportActionKey { - return key.startsWith(ONYXKEYS.COLLECTION.REPORT_ACTIONS); -} - -function getShouldShowMerchant(data: OnyxTypes.SearchResults['data']): boolean { - return Object.keys(data).some((key) => { - if (isTransactionEntry(key)) { - const item = data[key]; - const merchant = item.modifiedMerchant ? item.modifiedMerchant : item.merchant ?? ''; - return merchant !== '' && merchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && merchant !== CONST.TRANSACTION.DEFAULT_MERCHANT; - } - return false; - }); -} - -const currentYear = new Date().getFullYear(); - -function isReportListItemType(item: ListItem): item is ReportListItemType { - return 'transactions' in item; -} - -function isTransactionListItemType(item: TransactionListItemType | ReportListItemType | ReportActionListItemType): item is TransactionListItemType { - const transactionListItem = item as TransactionListItemType; - return transactionListItem.transactionID !== undefined; -} - -function isReportActionListItemType(item: TransactionListItemType | ReportListItemType | ReportActionListItemType): item is ReportActionListItemType { - const reportActionListItem = item as ReportActionListItemType; - return reportActionListItem.reportActionID !== undefined; -} - -function shouldShowYear(data: TransactionListItemType[] | ReportListItemType[] | OnyxTypes.SearchResults['data']): boolean { - if (Array.isArray(data)) { - return data.some((item: TransactionListItemType | ReportListItemType) => { - if (isReportListItemType(item)) { - // If the item is a ReportListItemType, iterate over its transactions and check them - return item.transactions.some((transaction) => { - const transactionYear = new Date(TransactionUtils.getCreated(transaction)).getFullYear(); - return transactionYear !== currentYear; - }); - } - - const createdYear = new Date(item?.modifiedCreated ? item.modifiedCreated : item?.created || '').getFullYear(); - return createdYear !== currentYear; - }); - } - - for (const key in data) { - if (isTransactionEntry(key)) { - const item = data[key]; - const date = TransactionUtils.getCreated(item); - - if (DateUtils.doesDateBelongToAPastYear(date)) { - return true; - } - } else if (isReportActionEntry(key)) { - const item = data[key]; - for (const action of Object.values(item)) { - const date = action.created; - - if (DateUtils.doesDateBelongToAPastYear(date)) { - return true; - } - } - } - } - return false; -} - -function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']): TransactionListItemType[] { - const shouldShowMerchant = getShouldShowMerchant(data); - - const doesDataContainAPastYearTransaction = shouldShowYear(data); - - return Object.keys(data) - .filter(isTransactionEntry) - .map((key) => { - const transactionItem = data[key]; - const from = data.personalDetailsList?.[transactionItem.accountID]; - const to = transactionItem.managerID ? data.personalDetailsList?.[transactionItem.managerID] : emptyPersonalDetails; - - const {formattedFrom, formattedTo, formattedTotal, formattedMerchant, date} = getTransactionItemCommonFormattedProperties(transactionItem, from, to); - - return { - ...transactionItem, - from, - to, - formattedFrom, - formattedTo, - formattedTotal, - formattedMerchant, - date, - shouldShowMerchant, - shouldShowCategory: metadata?.columnsToShow?.shouldShowCategoryColumn, - shouldShowTag: metadata?.columnsToShow?.shouldShowTagColumn, - shouldShowTax: metadata?.columnsToShow?.shouldShowTaxColumn, - keyForList: transactionItem.transactionID, - shouldShowYear: doesDataContainAPastYearTransaction, - }; - }); -} - -function getReportActionsSections(data: OnyxTypes.SearchResults['data']): ReportActionListItemType[] { - const reportActionItems: ReportActionListItemType[] = []; - for (const key in data) { - if (isReportActionEntry(key)) { - const reportActions = data[key]; - for (const reportAction of Object.values(reportActions)) { - const from = data.personalDetailsList?.[reportAction.accountID]; - if (ReportActionsUtils.isDeletedAction(reportAction)) { - // eslint-disable-next-line no-continue - continue; - } - reportActionItems.push({ - ...reportAction, - from, - formattedFrom: from?.displayName ?? from?.login ?? '', - date: reportAction.created, - keyForList: reportAction.reportActionID, - }); - } - } - } - return reportActionItems; -} - -function getIOUReportName(data: OnyxTypes.SearchResults['data'], reportItem: SearchReport) { - const payerPersonalDetails = reportItem.managerID ? data.personalDetailsList?.[reportItem.managerID] : emptyPersonalDetails; - const payerName = payerPersonalDetails?.displayName ?? payerPersonalDetails?.login ?? translateLocal('common.hidden'); - const formattedAmount = CurrencyUtils.convertToDisplayString(reportItem.total ?? 0, reportItem.currency ?? CONST.CURRENCY.USD); - if (reportItem.action === CONST.SEARCH.ACTION_TYPES.VIEW) { - return translateLocal('iou.payerOwesAmount', { - payer: payerName, - amount: formattedAmount, - }); - } - - if (reportItem.action === CONST.SEARCH.ACTION_TYPES.PAID) { - return translateLocal('iou.payerPaidAmount', { - payer: payerName, - amount: formattedAmount, - }); - } - - return reportItem.reportName; -} - -function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']): ReportListItemType[] { - const shouldShowMerchant = getShouldShowMerchant(data); - - const doesDataContainAPastYearTransaction = shouldShowYear(data); - - const reportIDToTransactions: Record = {}; - for (const key in data) { - if (isReportEntry(key)) { - const reportItem = {...data[key]}; - const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${reportItem.reportID}`; - const transactions = reportIDToTransactions[reportKey]?.transactions ?? []; - const isIOUReport = reportItem.type === CONST.REPORT.TYPE.IOU; - - reportIDToTransactions[reportKey] = { - ...reportItem, - keyForList: reportItem.reportID, - from: data.personalDetailsList?.[reportItem.accountID ?? -1], - to: reportItem.managerID ? data.personalDetailsList?.[reportItem.managerID] : emptyPersonalDetails, - transactions, - reportName: isIOUReport ? getIOUReportName(data, reportItem) : reportItem.reportName, - }; - } else if (isTransactionEntry(key)) { - const transactionItem = {...data[key]}; - const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${transactionItem.reportID}`; - - const from = data.personalDetailsList?.[transactionItem.accountID]; - const to = transactionItem.managerID ? data.personalDetailsList?.[transactionItem.managerID] : emptyPersonalDetails; - - const {formattedFrom, formattedTo, formattedTotal, formattedMerchant, date} = getTransactionItemCommonFormattedProperties(transactionItem, from, to); - - const transaction = { - ...transactionItem, - from, - to, - formattedFrom, - formattedTo, - formattedTotal, - formattedMerchant, - date, - shouldShowMerchant, - shouldShowCategory: metadata?.columnsToShow?.shouldShowCategoryColumn, - shouldShowTag: metadata?.columnsToShow?.shouldShowTagColumn, - shouldShowTax: metadata?.columnsToShow?.shouldShowTaxColumn, - keyForList: transactionItem.transactionID, - shouldShowYear: doesDataContainAPastYearTransaction, - }; - if (reportIDToTransactions[reportKey]?.transactions) { - reportIDToTransactions[reportKey].transactions.push(transaction); - } else if (reportIDToTransactions[reportKey]) { - reportIDToTransactions[reportKey].transactions = [transaction]; - } - } - } - - return Object.values(reportIDToTransactions); -} - -function getListItem(type: SearchDataTypes, status: SearchStatus): ListItemType { - if (type === CONST.SEARCH.DATA_TYPES.CHAT) { - return ChatListItem; - } - if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) { - return TransactionListItem; - } - return ReportListItem; -} - -function getSections(type: SearchDataTypes, status: SearchStatus, data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']) { - if (type === CONST.SEARCH.DATA_TYPES.CHAT) { - return getReportActionsSections(data); - } - if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) { - return getTransactionsSections(data, metadata); - } - return getReportSections(data, metadata); -} - -function getSortedSections(type: SearchDataTypes, status: SearchStatus, data: ListItemDataType, sortBy?: SearchColumnType, sortOrder?: SortOrder) { - if (type === CONST.SEARCH.DATA_TYPES.CHAT) { - return getSortedReportActionData(data as ReportActionListItemType[]); - } - if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) { - return getSortedTransactionData(data as TransactionListItemType[], sortBy, sortOrder); - } - return getSortedReportData(data as ReportListItemType[]); -} - -function getSortedTransactionData(data: TransactionListItemType[], sortBy?: SearchColumnType, sortOrder?: SortOrder) { - if (!sortBy || !sortOrder) { - return data; - } - - const sortingProperty = columnNamesToSortingProperty[sortBy]; - - if (!sortingProperty) { - return data; - } - - return data.sort((a, b) => { - const aValue = sortingProperty === 'comment' ? a.comment?.comment : a[sortingProperty]; - const bValue = sortingProperty === 'comment' ? b.comment?.comment : b[sortingProperty]; - - if (aValue === undefined || bValue === undefined) { - return 0; - } - - // We are guaranteed that both a and b will be string or number at the same time - if (typeof aValue === 'string' && typeof bValue === 'string') { - return sortOrder === CONST.SEARCH.SORT_ORDER.ASC ? aValue.toLowerCase().localeCompare(bValue) : bValue.toLowerCase().localeCompare(aValue); - } - - const aNum = aValue as number; - const bNum = bValue as number; - - return sortOrder === CONST.SEARCH.SORT_ORDER.ASC ? aNum - bNum : bNum - aNum; - }); -} - -function getReportNewestTransactionDate(report: ReportListItemType) { - return report.transactions?.reduce((max, curr) => (curr.modifiedCreated ?? curr.created > (max?.created ?? '') ? curr : max), report.transactions.at(0))?.created; -} - -function getSortedReportData(data: ReportListItemType[]) { - return data.sort((a, b) => { - const aNewestTransaction = getReportNewestTransactionDate(a); - const bNewestTransaction = getReportNewestTransactionDate(b); - - if (!aNewestTransaction || !bNewestTransaction) { - return 0; - } - - return bNewestTransaction.toLowerCase().localeCompare(aNewestTransaction); - }); -} - -function getSortedReportActionData(data: ReportActionListItemType[]) { - return data.sort((a, b) => { - const aValue = a?.created; - const bValue = b?.created; - - if (aValue === undefined || bValue === undefined) { - return 0; - } - - return bValue.toLowerCase().localeCompare(aValue); - }); -} - -function isSearchResultsEmpty(searchResults: SearchResults) { - return !Object.keys(searchResults?.data).some((key) => key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION)); -} - -function getQueryHashFromString(query: SearchQueryString): number { - return UserUtils.hashText(query, 2 ** 32); -} - -function getExpenseTypeTranslationKey(expenseType: ValueOf): TranslationPaths { - // eslint-disable-next-line default-case - switch (expenseType) { - case CONST.SEARCH.TRANSACTION_TYPE.DISTANCE: - return 'common.distance'; - case CONST.SEARCH.TRANSACTION_TYPE.CARD: - return 'common.card'; - case CONST.SEARCH.TRANSACTION_TYPE.CASH: - return 'iou.cash'; - } -} - -/* Search query related */ - -/** - * Update string query with all the default params that are set by parser - */ -function normalizeQuery(query: string) { - const normalizedQueryJSON = buildSearchQueryJSON(query); - return buildSearchQueryString(normalizedQueryJSON); -} - /** * @private * returns Date filter query string part, which needs special logic @@ -544,9 +142,12 @@ function buildSearchQueryJSON(query: SearchQueryString) { const flatFilters = getFilters(result); // Add the full input and hash to the results + const queryHash = UserUtils.hashText(query, 2 ** 32); + + result.hash = queryHash; result.inputQuery = query; - result.hash = getQueryHashFromString(query); result.flatFilters = flatFilters; + return result; } catch (e) { console.error(`Error when parsing SearchQuery: "${query}"`, e); @@ -795,7 +396,7 @@ function buildFilterString(filterName: string, queryFilters: QueryFilter[]) { } else if (filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD) { filterValueString += `${delimiter}${sanitizeString(queryFilter.value.toString())}`; } else { - filterValueString += ` ${filterName}${operatorToSignMap[queryFilter.operator]}${sanitizeString(queryFilter.value.toString())}`; + filterValueString += ` ${filterName}${operatorToCharMap[queryFilter.operator]}${sanitizeString(queryFilter.value.toString())}`; } }); @@ -855,34 +456,9 @@ function buildCannedSearchQuery({ } = {}): SearchQueryString { const queryString = policyID ? `type:${type} status:${status} policyID:${policyID}` : `type:${type} status:${status}`; - return normalizeQuery(queryString); -} - -function getOverflowMenu(itemName: string, hash: number, inputQuery: string, showDeleteModal: (hash: number) => void, isMobileMenu?: boolean, closeMenu?: () => void) { - return [ - { - text: translateLocal('common.rename'), - onSelected: () => { - if (isMobileMenu && closeMenu) { - closeMenu(); - } - Navigation.navigate(ROUTES.SEARCH_SAVED_SEARCH_RENAME.getRoute({name: encodeURIComponent(itemName), jsonQuery: inputQuery})); - }, - icon: Expensicons.Pencil, - shouldShowRightIcon: false, - shouldShowRightComponent: false, - shouldCallAfterModalHide: true, - }, - { - text: translateLocal('common.delete'), - onSelected: () => showDeleteModal(hash), - icon: Expensicons.Trashcan, - shouldShowRightIcon: false, - shouldShowRightComponent: false, - shouldCallAfterModalHide: true, - shouldCloseAllModals: true, - }, - ]; + // Parse the query to fill all default query fields with values + const normalizedQueryJSON = buildSearchQueryJSON(queryString); + return buildSearchQueryString(normalizedQueryJSON); } /** @@ -969,32 +545,15 @@ function getContextualSuggestionQuery(reportID: string) { return `type:chat in:${reportID}`; } -function isCorrectSearchUserName(displayName?: string) { - return displayName && displayName.toUpperCase() !== CONST.REPORT.OWNER_EMAIL_FAKE; -} - export { - getContextualSuggestionQuery, - buildQueryStringFromFilterFormValues, buildSearchQueryJSON, buildSearchQueryString, + buildQueryStringFromFilterFormValues, buildFilterFormValuesFromQuery, getPolicyIDFromSearchQuery, - getListItem, - getSections, - getShouldShowMerchant, - getSortedSections, - isReportListItemType, - isSearchResultsEmpty, - isTransactionListItemType, - isReportActionListItemType, getSearchHeaderTitle, - normalizeQuery, - shouldShowYear, buildCannedSearchQuery, isCannedSearchQuery, - getExpenseTypeTranslationKey, - getOverflowMenu, - isCorrectSearchUserName, standardizeQueryJSON, + getContextualSuggestionQuery, }; diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts new file mode 100644 index 000000000000..89c28eb6c39f --- /dev/null +++ b/src/libs/SearchUIUtils.ts @@ -0,0 +1,440 @@ +import type {ValueOf} from 'type-fest'; +import type {SearchColumnType, SearchStatus, SortOrder} from '@components/Search/types'; +import ChatListItem from '@components/SelectionList/ChatListItem'; +import ReportListItem from '@components/SelectionList/Search/ReportListItem'; +import TransactionListItem from '@components/SelectionList/Search/TransactionListItem'; +import type {ListItem, ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; +import * as Expensicons from '@src/components/Icon/Expensicons'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; +import type SearchResults from '@src/types/onyx/SearchResults'; +import type {ListItemDataType, ListItemType, SearchDataTypes, SearchPersonalDetails, SearchReport, SearchTransaction} from '@src/types/onyx/SearchResults'; +import * as CurrencyUtils from './CurrencyUtils'; +import DateUtils from './DateUtils'; +import {translateLocal} from './Localize'; +import Navigation from './Navigation/Navigation'; +import * as ReportActionsUtils from './ReportActionsUtils'; +import * as TransactionUtils from './TransactionUtils'; + +const columnNamesToSortingProperty = { + [CONST.SEARCH.TABLE_COLUMNS.TO]: 'formattedTo' as const, + [CONST.SEARCH.TABLE_COLUMNS.FROM]: 'formattedFrom' as const, + [CONST.SEARCH.TABLE_COLUMNS.DATE]: 'date' as const, + [CONST.SEARCH.TABLE_COLUMNS.TAG]: 'tag' as const, + [CONST.SEARCH.TABLE_COLUMNS.MERCHANT]: 'formattedMerchant' as const, + [CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT]: 'formattedTotal' as const, + [CONST.SEARCH.TABLE_COLUMNS.CATEGORY]: 'category' as const, + [CONST.SEARCH.TABLE_COLUMNS.TYPE]: 'transactionType' as const, + [CONST.SEARCH.TABLE_COLUMNS.ACTION]: 'action' as const, + [CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION]: 'comment' as const, + [CONST.SEARCH.TABLE_COLUMNS.TAX_AMOUNT]: null, + [CONST.SEARCH.TABLE_COLUMNS.RECEIPT]: null, +}; + +const emptyPersonalDetails = { + accountID: CONST.REPORT.OWNER_ACCOUNT_ID_FAKE, + avatar: '', + displayName: undefined, + login: undefined, +}; +/* Search list and results related */ + +/** + * @private + */ +function getTransactionItemCommonFormattedProperties( + transactionItem: SearchTransaction, + from: SearchPersonalDetails, + to: SearchPersonalDetails, +): Pick { + const isExpenseReport = transactionItem.reportType === CONST.REPORT.TYPE.EXPENSE; + + const formattedFrom = from?.displayName ?? from?.login ?? ''; + const formattedTo = to?.displayName ?? to?.login ?? ''; + const formattedTotal = TransactionUtils.getAmount(transactionItem, isExpenseReport); + const date = transactionItem?.modifiedCreated ? transactionItem.modifiedCreated : transactionItem?.created; + const merchant = TransactionUtils.getMerchant(transactionItem); + const formattedMerchant = merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || merchant === CONST.TRANSACTION.DEFAULT_MERCHANT ? '' : merchant; + + return { + formattedFrom, + formattedTo, + date, + formattedTotal, + formattedMerchant, + }; +} + +type ReportKey = `${typeof ONYXKEYS.COLLECTION.REPORT}${string}`; + +type TransactionKey = `${typeof ONYXKEYS.COLLECTION.TRANSACTION}${string}`; + +type ReportActionKey = `${typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS}${string}`; + +function isReportEntry(key: string): key is ReportKey { + return key.startsWith(ONYXKEYS.COLLECTION.REPORT); +} + +function isTransactionEntry(key: string): key is TransactionKey { + return key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION); +} + +function isReportActionEntry(key: string): key is ReportActionKey { + return key.startsWith(ONYXKEYS.COLLECTION.REPORT_ACTIONS); +} + +function getShouldShowMerchant(data: OnyxTypes.SearchResults['data']): boolean { + return Object.keys(data).some((key) => { + if (isTransactionEntry(key)) { + const item = data[key]; + const merchant = item.modifiedMerchant ? item.modifiedMerchant : item.merchant ?? ''; + return merchant !== '' && merchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && merchant !== CONST.TRANSACTION.DEFAULT_MERCHANT; + } + return false; + }); +} + +const currentYear = new Date().getFullYear(); + +function isReportListItemType(item: ListItem): item is ReportListItemType { + return 'transactions' in item; +} + +function isTransactionListItemType(item: TransactionListItemType | ReportListItemType | ReportActionListItemType): item is TransactionListItemType { + const transactionListItem = item as TransactionListItemType; + return transactionListItem.transactionID !== undefined; +} + +function isReportActionListItemType(item: TransactionListItemType | ReportListItemType | ReportActionListItemType): item is ReportActionListItemType { + const reportActionListItem = item as ReportActionListItemType; + return reportActionListItem.reportActionID !== undefined; +} + +function shouldShowYear(data: TransactionListItemType[] | ReportListItemType[] | OnyxTypes.SearchResults['data']): boolean { + if (Array.isArray(data)) { + return data.some((item: TransactionListItemType | ReportListItemType) => { + if (isReportListItemType(item)) { + // If the item is a ReportListItemType, iterate over its transactions and check them + return item.transactions.some((transaction) => { + const transactionYear = new Date(TransactionUtils.getCreated(transaction)).getFullYear(); + return transactionYear !== currentYear; + }); + } + + const createdYear = new Date(item?.modifiedCreated ? item.modifiedCreated : item?.created || '').getFullYear(); + return createdYear !== currentYear; + }); + } + + for (const key in data) { + if (isTransactionEntry(key)) { + const item = data[key]; + const date = TransactionUtils.getCreated(item); + + if (DateUtils.doesDateBelongToAPastYear(date)) { + return true; + } + } else if (isReportActionEntry(key)) { + const item = data[key]; + for (const action of Object.values(item)) { + const date = action.created; + + if (DateUtils.doesDateBelongToAPastYear(date)) { + return true; + } + } + } + } + return false; +} + +function getTransactionsSections(data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']): TransactionListItemType[] { + const shouldShowMerchant = getShouldShowMerchant(data); + + const doesDataContainAPastYearTransaction = shouldShowYear(data); + + return Object.keys(data) + .filter(isTransactionEntry) + .map((key) => { + const transactionItem = data[key]; + const from = data.personalDetailsList?.[transactionItem.accountID]; + const to = transactionItem.managerID ? data.personalDetailsList?.[transactionItem.managerID] : emptyPersonalDetails; + + const {formattedFrom, formattedTo, formattedTotal, formattedMerchant, date} = getTransactionItemCommonFormattedProperties(transactionItem, from, to); + + return { + ...transactionItem, + from, + to, + formattedFrom, + formattedTo, + formattedTotal, + formattedMerchant, + date, + shouldShowMerchant, + shouldShowCategory: metadata?.columnsToShow?.shouldShowCategoryColumn, + shouldShowTag: metadata?.columnsToShow?.shouldShowTagColumn, + shouldShowTax: metadata?.columnsToShow?.shouldShowTaxColumn, + keyForList: transactionItem.transactionID, + shouldShowYear: doesDataContainAPastYearTransaction, + }; + }); +} + +function getReportActionsSections(data: OnyxTypes.SearchResults['data']): ReportActionListItemType[] { + const reportActionItems: ReportActionListItemType[] = []; + for (const key in data) { + if (isReportActionEntry(key)) { + const reportActions = data[key]; + for (const reportAction of Object.values(reportActions)) { + const from = data.personalDetailsList?.[reportAction.accountID]; + if (ReportActionsUtils.isDeletedAction(reportAction)) { + // eslint-disable-next-line no-continue + continue; + } + reportActionItems.push({ + ...reportAction, + from, + formattedFrom: from?.displayName ?? from?.login ?? '', + date: reportAction.created, + keyForList: reportAction.reportActionID, + }); + } + } + } + return reportActionItems; +} + +function getIOUReportName(data: OnyxTypes.SearchResults['data'], reportItem: SearchReport) { + const payerPersonalDetails = reportItem.managerID ? data.personalDetailsList?.[reportItem.managerID] : emptyPersonalDetails; + const payerName = payerPersonalDetails?.displayName ?? payerPersonalDetails?.login ?? translateLocal('common.hidden'); + const formattedAmount = CurrencyUtils.convertToDisplayString(reportItem.total ?? 0, reportItem.currency ?? CONST.CURRENCY.USD); + if (reportItem.action === CONST.SEARCH.ACTION_TYPES.VIEW) { + return translateLocal('iou.payerOwesAmount', { + payer: payerName, + amount: formattedAmount, + }); + } + + if (reportItem.action === CONST.SEARCH.ACTION_TYPES.PAID) { + return translateLocal('iou.payerPaidAmount', { + payer: payerName, + amount: formattedAmount, + }); + } + + return reportItem.reportName; +} + +function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']): ReportListItemType[] { + const shouldShowMerchant = getShouldShowMerchant(data); + + const doesDataContainAPastYearTransaction = shouldShowYear(data); + + const reportIDToTransactions: Record = {}; + for (const key in data) { + if (isReportEntry(key)) { + const reportItem = {...data[key]}; + const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${reportItem.reportID}`; + const transactions = reportIDToTransactions[reportKey]?.transactions ?? []; + const isIOUReport = reportItem.type === CONST.REPORT.TYPE.IOU; + + reportIDToTransactions[reportKey] = { + ...reportItem, + keyForList: reportItem.reportID, + from: data.personalDetailsList?.[reportItem.accountID ?? -1], + to: reportItem.managerID ? data.personalDetailsList?.[reportItem.managerID] : emptyPersonalDetails, + transactions, + reportName: isIOUReport ? getIOUReportName(data, reportItem) : reportItem.reportName, + }; + } else if (isTransactionEntry(key)) { + const transactionItem = {...data[key]}; + const reportKey = `${ONYXKEYS.COLLECTION.REPORT}${transactionItem.reportID}`; + + const from = data.personalDetailsList?.[transactionItem.accountID]; + const to = transactionItem.managerID ? data.personalDetailsList?.[transactionItem.managerID] : emptyPersonalDetails; + + const {formattedFrom, formattedTo, formattedTotal, formattedMerchant, date} = getTransactionItemCommonFormattedProperties(transactionItem, from, to); + + const transaction = { + ...transactionItem, + from, + to, + formattedFrom, + formattedTo, + formattedTotal, + formattedMerchant, + date, + shouldShowMerchant, + shouldShowCategory: metadata?.columnsToShow?.shouldShowCategoryColumn, + shouldShowTag: metadata?.columnsToShow?.shouldShowTagColumn, + shouldShowTax: metadata?.columnsToShow?.shouldShowTaxColumn, + keyForList: transactionItem.transactionID, + shouldShowYear: doesDataContainAPastYearTransaction, + }; + if (reportIDToTransactions[reportKey]?.transactions) { + reportIDToTransactions[reportKey].transactions.push(transaction); + } else if (reportIDToTransactions[reportKey]) { + reportIDToTransactions[reportKey].transactions = [transaction]; + } + } + } + + return Object.values(reportIDToTransactions); +} + +function getListItem(type: SearchDataTypes, status: SearchStatus): ListItemType { + if (type === CONST.SEARCH.DATA_TYPES.CHAT) { + return ChatListItem; + } + if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) { + return TransactionListItem; + } + return ReportListItem; +} + +function getSections(type: SearchDataTypes, status: SearchStatus, data: OnyxTypes.SearchResults['data'], metadata: OnyxTypes.SearchResults['search']) { + if (type === CONST.SEARCH.DATA_TYPES.CHAT) { + return getReportActionsSections(data); + } + if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) { + return getTransactionsSections(data, metadata); + } + return getReportSections(data, metadata); +} + +function getSortedSections(type: SearchDataTypes, status: SearchStatus, data: ListItemDataType, sortBy?: SearchColumnType, sortOrder?: SortOrder) { + if (type === CONST.SEARCH.DATA_TYPES.CHAT) { + return getSortedReportActionData(data as ReportActionListItemType[]); + } + if (status === CONST.SEARCH.STATUS.EXPENSE.ALL) { + return getSortedTransactionData(data as TransactionListItemType[], sortBy, sortOrder); + } + return getSortedReportData(data as ReportListItemType[]); +} + +function getSortedTransactionData(data: TransactionListItemType[], sortBy?: SearchColumnType, sortOrder?: SortOrder) { + if (!sortBy || !sortOrder) { + return data; + } + + const sortingProperty = columnNamesToSortingProperty[sortBy]; + + if (!sortingProperty) { + return data; + } + + return data.sort((a, b) => { + const aValue = sortingProperty === 'comment' ? a.comment?.comment : a[sortingProperty]; + const bValue = sortingProperty === 'comment' ? b.comment?.comment : b[sortingProperty]; + + if (aValue === undefined || bValue === undefined) { + return 0; + } + + // We are guaranteed that both a and b will be string or number at the same time + if (typeof aValue === 'string' && typeof bValue === 'string') { + return sortOrder === CONST.SEARCH.SORT_ORDER.ASC ? aValue.toLowerCase().localeCompare(bValue) : bValue.toLowerCase().localeCompare(aValue); + } + + const aNum = aValue as number; + const bNum = bValue as number; + + return sortOrder === CONST.SEARCH.SORT_ORDER.ASC ? aNum - bNum : bNum - aNum; + }); +} + +function getReportNewestTransactionDate(report: ReportListItemType) { + return report.transactions?.reduce((max, curr) => (curr.modifiedCreated ?? curr.created > (max?.created ?? '') ? curr : max), report.transactions.at(0))?.created; +} + +function getSortedReportData(data: ReportListItemType[]) { + return data.sort((a, b) => { + const aNewestTransaction = getReportNewestTransactionDate(a); + const bNewestTransaction = getReportNewestTransactionDate(b); + + if (!aNewestTransaction || !bNewestTransaction) { + return 0; + } + + return bNewestTransaction.toLowerCase().localeCompare(aNewestTransaction); + }); +} + +function getSortedReportActionData(data: ReportActionListItemType[]) { + return data.sort((a, b) => { + const aValue = a?.created; + const bValue = b?.created; + + if (aValue === undefined || bValue === undefined) { + return 0; + } + + return bValue.toLowerCase().localeCompare(aValue); + }); +} + +function isSearchResultsEmpty(searchResults: SearchResults) { + return !Object.keys(searchResults?.data).some((key) => key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION)); +} + +function getExpenseTypeTranslationKey(expenseType: ValueOf): TranslationPaths { + // eslint-disable-next-line default-case + switch (expenseType) { + case CONST.SEARCH.TRANSACTION_TYPE.DISTANCE: + return 'common.distance'; + case CONST.SEARCH.TRANSACTION_TYPE.CARD: + return 'common.card'; + case CONST.SEARCH.TRANSACTION_TYPE.CASH: + return 'iou.cash'; + } +} + +function getOverflowMenu(itemName: string, hash: number, inputQuery: string, showDeleteModal: (hash: number) => void, isMobileMenu?: boolean, closeMenu?: () => void) { + return [ + { + text: translateLocal('common.rename'), + onSelected: () => { + if (isMobileMenu && closeMenu) { + closeMenu(); + } + Navigation.navigate(ROUTES.SEARCH_SAVED_SEARCH_RENAME.getRoute({name: encodeURIComponent(itemName), jsonQuery: inputQuery})); + }, + icon: Expensicons.Pencil, + shouldShowRightIcon: false, + shouldShowRightComponent: false, + shouldCallAfterModalHide: true, + }, + { + text: translateLocal('common.delete'), + onSelected: () => showDeleteModal(hash), + icon: Expensicons.Trashcan, + shouldShowRightIcon: false, + shouldShowRightComponent: false, + shouldCallAfterModalHide: true, + shouldCloseAllModals: true, + }, + ]; +} + +function isCorrectSearchUserName(displayName?: string) { + return displayName && displayName.toUpperCase() !== CONST.REPORT.OWNER_EMAIL_FAKE; +} + +export { + getListItem, + getSections, + getShouldShowMerchant, + getSortedSections, + isReportListItemType, + isSearchResultsEmpty, + isTransactionListItemType, + isReportActionListItemType, + shouldShowYear, + getExpenseTypeTranslationKey, + getOverflowMenu, + isCorrectSearchUserName, +}; diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 6e117a8baaab..14b6bb37179a 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -20,7 +20,8 @@ import Navigation from '@libs/Navigation/Navigation'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import {getAllTaxRates} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; +import * as SearchUIUtils from '@libs/SearchUIUtils'; import * as SearchActions from '@userActions/Search'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -237,7 +238,7 @@ function getFilterExpenseDisplayTitle(filters: Partial filterValue.includes(expenseType)) - .map((expenseType) => translate(SearchUtils.getExpenseTypeTranslationKey(expenseType))) + .map((expenseType) => translate(SearchUIUtils.getExpenseTypeTranslationKey(expenseType))) .join(', ') : undefined; } @@ -266,8 +267,8 @@ function AdvancedSearchFilters() { currentType = CONST.SEARCH.DATA_TYPES.EXPENSE; } - const queryString = useMemo(() => SearchUtils.buildQueryStringFromFilterFormValues(searchAdvancedFilters), [searchAdvancedFilters]); - const queryJSON = useMemo(() => SearchUtils.buildSearchQueryJSON(queryString || SearchUtils.buildCannedSearchQuery()), [queryString]); + const queryString = useMemo(() => SearchQueryUtils.buildQueryStringFromFilterFormValues(searchAdvancedFilters), [searchAdvancedFilters]); + const queryJSON = useMemo(() => SearchQueryUtils.buildSearchQueryJSON(queryString || SearchQueryUtils.buildCannedSearchQuery()), [queryString]); const applyFiltersAndNavigate = () => { SearchActions.clearAllFilters(); @@ -337,7 +338,7 @@ function AdvancedSearchFilters() { }) .sort((a, b) => (a?.description ?? '')?.localeCompare(b?.description ?? '')); - const displaySearchButton = queryJSON && !SearchUtils.isCannedSearchQuery(queryJSON); + const displaySearchButton = queryJSON && !SearchQueryUtils.isCannedSearchQuery(queryJSON); return ( <> diff --git a/src/pages/Search/SavedSearchRenamePage.tsx b/src/pages/Search/SavedSearchRenamePage.tsx index 00180cd5de7f..98a33759682c 100644 --- a/src/pages/Search/SavedSearchRenamePage.tsx +++ b/src/pages/Search/SavedSearchRenamePage.tsx @@ -10,7 +10,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as SearchActions from '@libs/actions/Search'; import Navigation from '@libs/Navigation/Navigation'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -34,7 +34,7 @@ function SavedSearchRenamePage({route}: {route: {params: {q: string; name: strin }; const onSaveSearch = () => { - const queryJSON = SearchUtils.buildSearchQueryJSON(q || SearchUtils.buildCannedSearchQuery()) ?? ({} as SearchQueryJSON); + const queryJSON = SearchQueryUtils.buildSearchQueryJSON(q || SearchQueryUtils.buildCannedSearchQuery()) ?? ({} as SearchQueryJSON); SearchActions.saveSearch({ queryJSON, diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage.tsx index ea97340fed14..f0e90912302d 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage.tsx @@ -8,7 +8,7 @@ import SearchMultipleSelectionPicker from '@components/Search/SearchMultipleSele import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import {getExpenseTypeTranslationKey} from '@libs/SearchUtils'; +import {getExpenseTypeTranslationKey} from '@libs/SearchUIUtils'; import * as SearchActions from '@userActions/Search'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 82c502febaf8..2289eb7b0a85 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -9,7 +9,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -20,8 +20,8 @@ function SearchPage({route}: SearchPageProps) { const styles = useThemeStyles(); const {q} = route.params; - const queryJSON = useMemo(() => SearchUtils.buildSearchQueryJSON(q), [q]); - const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchUtils.buildCannedSearchQuery()})); + const queryJSON = useMemo(() => SearchQueryUtils.buildSearchQueryJSON(q), [q]); + const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchQueryUtils.buildCannedSearchQuery()})); // On small screens this page is not displayed, the configuration is in the file: src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx // To avoid calling hooks in the Search component when this page isn't visible, we return null here. diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx index 5bda7cd0056e..dfb671da7af1 100644 --- a/src/pages/Search/SearchPageBottomTab.tsx +++ b/src/pages/Search/SearchPageBottomTab.tsx @@ -13,7 +13,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import TopBar from '@navigation/AppNavigator/createCustomBottomTabNavigator/TopBar'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -60,15 +60,15 @@ function SearchPageBottomTab() { }); const searchParams = activeCentralPaneRoute?.params as AuthScreensParamList[typeof SCREENS.SEARCH.CENTRAL_PANE]; - const parsedQuery = SearchUtils.buildSearchQueryJSON(searchParams?.q); + const parsedQuery = SearchQueryUtils.buildSearchQueryJSON(searchParams?.q); const isSearchNameModified = searchParams?.name === searchParams?.q; const searchName = isSearchNameModified ? undefined : searchParams?.name; - const policyIDFromSearchQuery = parsedQuery && SearchUtils.getPolicyIDFromSearchQuery(parsedQuery); + const policyIDFromSearchQuery = parsedQuery && SearchQueryUtils.getPolicyIDFromSearchQuery(parsedQuery); const isActiveCentralPaneRoute = activeCentralPaneRoute?.name === SCREENS.SEARCH.CENTRAL_PANE; const queryJSON = isActiveCentralPaneRoute ? parsedQuery : undefined; const policyID = isActiveCentralPaneRoute ? policyIDFromSearchQuery : undefined; - const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchUtils.buildCannedSearchQuery()})); + const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query: SearchQueryUtils.buildCannedSearchQuery()})); if (!queryJSON) { return ( @@ -100,7 +100,7 @@ function SearchPageBottomTab() { breadcrumbLabel={translate('common.search')} shouldDisplaySearch={false} shouldDisplaySearchRouter={shouldUseNarrowLayout} - isCustomSearchQuery={shouldUseNarrowLayout && !SearchUtils.isCannedSearchQuery(queryJSON)} + isCustomSearchQuery={shouldUseNarrowLayout && !SearchQueryUtils.isCannedSearchQuery(queryJSON)} /> {shouldUseNarrowLayout ? ( diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index 70e7c20fee13..4aec50986f84 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -20,7 +20,8 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as SearchActions from '@libs/actions/Search'; import Navigation from '@libs/Navigation/Navigation'; import {getAllTaxRates} from '@libs/PolicyUtils'; -import * as SearchUtils from '@libs/SearchUtils'; +import * as SearchQueryUtils from '@libs/SearchQueryUtils'; +import * as SearchUIUtils from '@libs/SearchUIUtils'; import variables from '@styles/variables'; import * as Expensicons from '@src/components/Icon/Expensicons'; import CONST from '@src/CONST'; @@ -73,7 +74,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { type: CONST.SEARCH.DATA_TYPES.EXPENSE, icon: Expensicons.Receipt, getRoute: (policyID?: string) => { - const query = SearchUtils.buildCannedSearchQuery({policyID}); + const query = SearchQueryUtils.buildCannedSearchQuery({policyID}); return ROUTES.SEARCH_CENTRAL_PANE.getRoute({query}); }, }, @@ -82,7 +83,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { type: CONST.SEARCH.DATA_TYPES.CHAT, icon: Expensicons.ChatBubbles, getRoute: (policyID?: string) => { - const query = SearchUtils.buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.CHAT, status: CONST.SEARCH.STATUS.CHAT.ALL, policyID}); + const query = SearchQueryUtils.buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.CHAT, status: CONST.SEARCH.STATUS.CHAT.ALL, policyID}); return ROUTES.SEARCH_CENTRAL_PANE.getRoute({query}); }, }, @@ -91,7 +92,7 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { type: CONST.SEARCH.DATA_TYPES.INVOICE, icon: Expensicons.InvoiceGeneric, getRoute: (policyID?: string) => { - const query = SearchUtils.buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.INVOICE, status: CONST.SEARCH.STATUS.INVOICE.ALL, policyID}); + const query = SearchQueryUtils.buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.INVOICE, status: CONST.SEARCH.STATUS.INVOICE.ALL, policyID}); return ROUTES.SEARCH_CENTRAL_PANE.getRoute({query}); }, }, @@ -100,22 +101,22 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { type: CONST.SEARCH.DATA_TYPES.TRIP, icon: Expensicons.Suitcase, getRoute: (policyID?: string) => { - const query = SearchUtils.buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.TRIP, status: CONST.SEARCH.STATUS.TRIP.ALL, policyID}); + const query = SearchQueryUtils.buildCannedSearchQuery({type: CONST.SEARCH.DATA_TYPES.TRIP, status: CONST.SEARCH.STATUS.TRIP.ALL, policyID}); return ROUTES.SEARCH_CENTRAL_PANE.getRoute({query}); }, }, ]; const getOverflowMenu = useCallback( - (itemName: string, itemHash: number, itemQuery: string) => SearchUtils.getOverflowMenu(itemName, itemHash, itemQuery, showDeleteModal), + (itemName: string, itemHash: number, itemQuery: string) => SearchUIUtils.getOverflowMenu(itemName, itemHash, itemQuery, showDeleteModal), [showDeleteModal], ); const createSavedSearchMenuItem = (item: SaveSearchItem, key: string, isNarrow: boolean, index: number) => { let title = item.name; if (title === item.query) { - const jsonQuery = SearchUtils.buildSearchQueryJSON(item.query) ?? ({} as SearchQueryJSON); - title = SearchUtils.getSearchHeaderTitle(jsonQuery, personalDetails, cardList, reports, taxRates); + const jsonQuery = SearchQueryUtils.buildSearchQueryJSON(item.query) ?? ({} as SearchQueryJSON); + title = SearchQueryUtils.getSearchHeaderTitle(jsonQuery, personalDetails, cardList, reports, taxRates); } const baseMenuItem: SavedSearchMenuItem = { @@ -216,11 +217,11 @@ function SearchTypeMenu({queryJSON, searchName}: SearchTypeMenuProps) { [styles], ); - const isCannedQuery = SearchUtils.isCannedSearchQuery(queryJSON); + const isCannedQuery = SearchQueryUtils.isCannedSearchQuery(queryJSON); const activeItemIndex = isCannedQuery ? typeMenuItems.findIndex((item) => item.type === type) : -1; if (shouldUseNarrowLayout) { - const title = searchName ?? (isCannedQuery ? undefined : SearchUtils.getSearchHeaderTitle(queryJSON, personalDetails, cardList, reports, taxRates)); + const title = searchName ?? (isCannedQuery ? undefined : SearchQueryUtils.getSearchHeaderTitle(queryJSON, personalDetails, cardList, reports, taxRates)); return ( setIsPopoverVisible(true), []); const closeMenu = useCallback(() => setIsPopoverVisible(false), []); const onPress = () => { - const values = SearchUtils.buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, cardList, reports, taxRates); + const values = SearchQueryUtils.buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, cardList, reports, taxRates); SearchActions.updateAdvancedFilters(values); Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS); }; @@ -140,7 +141,7 @@ function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, queryJSON, title, shouldShowRightComponent: true, rightComponent: ( { - const query = SearchUtils.buildQueryStringFromFilterFormValues({merchant: CONST.EXPENSIFY_MERCHANT}); + const query = SearchQueryUtils.buildQueryStringFromFilterFormValues({merchant: CONST.EXPENSIFY_MERCHANT}); Navigation.navigate(ROUTES.SEARCH_CENTRAL_PANE.getRoute({query})); }, []);