From cb9d6199d87b96dd341f21d4ed00c98c9e8c6838 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 18 Jul 2024 18:18:56 +0200 Subject: [PATCH] tmp --- src/CONST.ts | 21 +++++++ src/ROUTES.ts | 18 ++---- src/components/PromotedActionsBar.tsx | 6 +- .../Search/SearchListWithHeader.tsx | 10 ++-- src/components/Search/SearchPageHeader.tsx | 13 ++--- src/components/Search/index.tsx | 40 +++++++------ src/components/Search/types.ts | 36 +++++++++++- .../SelectionList/Search/ActionCell.tsx | 2 +- .../SelectionList/Search/ReportListItem.tsx | 4 +- .../Navigation/AppNavigator/AuthScreens.tsx | 3 +- .../BottomTabBar/index.tsx | 2 +- .../BottomTabBar/index.website.tsx | 2 +- src/libs/Navigation/switchPolicyID.ts | 2 +- src/libs/Navigation/types.ts | 20 ++----- src/libs/SearchUtils.ts | 58 +++++++++++-------- src/libs/actions/Search.ts | 18 ++++++ src/pages/Search/SearchFilters.tsx | 8 +-- src/pages/Search/SearchPage.tsx | 21 +++---- src/pages/Search/SearchPageBottomTab.tsx | 44 ++++---------- .../Subscription/CardSection/CardSection.tsx | 2 +- src/types/onyx/SearchResults.ts | 15 +++-- 21 files changed, 202 insertions(+), 143 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 3f141905e84c..3e1c1b780ab0 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5202,6 +5202,16 @@ const CONST = { ASC: 'asc', DESC: 'desc', }, + + // TODO_SEARCH: change TABv2 to TAB after current TAB is safely removed. + TABv2: { + EXPENSE: { + ALL: 'type:expense status:all', + SHARED: 'type:expense status:shared', + DRAFTS: 'type:expense status:drafts', + FINISHED: 'type:expense status:finished', + }, + }, TAB: { ALL: 'all', SHARED: 'shared', @@ -5222,6 +5232,12 @@ const CONST = { ACTION: 'action', TAX_AMOUNT: 'taxAmount', }, + STATUS: { + ALL: 'all', + SHARED: 'shared', + DRAFTS: 'drafts', + FINISHED: 'finished', + }, BULK_ACTION_TYPES: { DELETE: 'delete', HOLD: 'hold', @@ -5263,6 +5279,11 @@ const CONST = { REPORT_ID: 'reportID', KEYWORD: 'keyword', }, + QUERY_KIND: { + CANNED_QUERY: 'cannedQuery', + CUSTOM_QUERY: 'customQuery', + }, + DEFAULT_QUERY: 'type:all sortBy:date sortOrder:desc', }, REFERRER: { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index f13724bf4322..3d26f0f28b28 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1,10 +1,9 @@ import type {TupleToUnion, ValueOf} from 'type-fest'; -import type CONST from './CONST'; +import type {QueryKind, SearchQueryString} from './components/Search/types'; +import CONST from './CONST'; import type {IOUAction, IOUType} from './CONST'; import type {IOURequestType} from './libs/actions/IOU'; -import type {AuthScreensParamList} from './libs/Navigation/types'; import type {ConnectionName, SageIntacctMappingName} from './types/onyx/Policy'; -import type {SearchQuery} from './types/onyx/SearchResults'; import type AssertTypesNotEqual from './types/utils/AssertTypesNotEqual'; // This is a file containing constants for all the routes we want to be able to go to @@ -37,16 +36,9 @@ const ROUTES = { ALL_SETTINGS: 'all-settings', SEARCH: { - route: '/search/:query', - getRoute: (searchQuery: SearchQuery, queryParams?: AuthScreensParamList['Search_Central_Pane']) => { - const {sortBy, sortOrder} = queryParams ?? {}; - - if (!sortBy && !sortOrder) { - return `search/${searchQuery}` as const; - } - - return `search/${searchQuery}?sortBy=${sortBy}&sortOrder=${sortOrder}` as const; - }, + route: '/search', + getRoute: ({query, queryKind = CONST.SEARCH.QUERY_KIND.CANNED_QUERY, policyIDs}: {query: SearchQueryString; queryKind?: QueryKind; policyIDs?: string}) => + `search?${queryKind === CONST.SEARCH.QUERY_KIND.CANNED_QUERY ? 'q' : 'cq'}=${query}${policyIDs ? `&policyIDs=${policyIDs}` : ''}` as const, }, SEARCH_REPORT: { diff --git a/src/components/PromotedActionsBar.tsx b/src/components/PromotedActionsBar.tsx index 1aefe941011b..347fa6f822d5 100644 --- a/src/components/PromotedActionsBar.tsx +++ b/src/components/PromotedActionsBar.tsx @@ -9,6 +9,7 @@ import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRo import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; import type {AuthScreensParamList, RootStackParamList, State} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; +import {buildSearchQueryJSON, getQueryFromSearchParams} from '@libs/SearchUtils'; import * as ReportActions from '@userActions/Report'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; @@ -80,8 +81,9 @@ const PromotedActions = { return; } - const currentQuery = topmostCentralPaneRoute?.params as AuthScreensParamList['Search_Central_Pane']; - ReportUtils.changeMoneyRequestHoldStatus(reportAction, ROUTES.SEARCH_REPORT.getRoute(currentQuery?.query ?? CONST.SEARCH.TAB.ALL, reportAction?.childReportID ?? '')); + const currentQuery = getQueryFromSearchParams(topmostCentralPaneRoute?.params as AuthScreensParamList['Search_Central_Pane']); + const currentStatus = buildSearchQueryJSON(currentQuery)?.status; + ReportUtils.changeMoneyRequestHoldStatus(reportAction, ROUTES.SEARCH_REPORT.getRoute(currentStatus ?? CONST.SEARCH.STATUS.ALL, reportAction?.childReportID ?? '')); }, }), } satisfies PromotedActionsType; diff --git a/src/components/Search/SearchListWithHeader.tsx b/src/components/Search/SearchListWithHeader.tsx index 283b68bf17af..c0c40d8181ec 100644 --- a/src/components/Search/SearchListWithHeader.tsx +++ b/src/components/Search/SearchListWithHeader.tsx @@ -11,12 +11,12 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as SearchActions from '@libs/actions/Search'; import * as SearchUtils from '@libs/SearchUtils'; import CONST from '@src/CONST'; -import type {SearchDataTypes, SearchQuery} from '@src/types/onyx/SearchResults'; +import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import SearchPageHeader from './SearchPageHeader'; -import type {SelectedTransactionInfo, SelectedTransactions} from './types'; +import type {SearchStatus, SelectedTransactionInfo, SelectedTransactions} from './types'; type SearchListWithHeaderProps = Omit, 'onSelectAll' | 'onCheckboxPress' | 'sections'> & { - query: SearchQuery; + status: SearchStatus; hash: number; data: TransactionListItemType[] | ReportListItemType[]; searchType: SearchDataTypes; @@ -43,7 +43,7 @@ function mapToItemWithSelectionInfo(item: TransactionListItemType | ReportListIt } function SearchListWithHeader( - {ListItem, onSelectRow, query, hash, data, searchType, isMobileSelectionModeActive, setIsMobileSelectionModeActive, ...props}: SearchListWithHeaderProps, + {ListItem, onSelectRow, status, hash, data, searchType, isMobileSelectionModeActive, setIsMobileSelectionModeActive, ...props}: SearchListWithHeaderProps, ref: ForwardedRef, ) { const {isSmallScreenWidth} = useWindowDimensions(); @@ -173,7 +173,7 @@ function SearchListWithHeader( void; hash: number; @@ -30,13 +29,13 @@ type SearchPageHeaderProps = { type SearchHeaderOptionValue = DeepValueOf | undefined; -function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems, onSelectDeleteOption, isMobileSelectionModeActive, setIsMobileSelectionModeActive}: SearchPageHeaderProps) { +function SearchPageHeader({status, selectedItems = {}, hash, clearSelectedItems, onSelectDeleteOption, isMobileSelectionModeActive, setIsMobileSelectionModeActive}: SearchPageHeaderProps) { const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); const {isSmallScreenWidth} = useResponsiveLayout(); - const headerContent: {[key in SearchQuery]: {icon: IconAsset; title: string}} = { + const headerContent: {[key in SearchStatus]: {icon: IconAsset; title: string}} = { all: {icon: Illustrations.MoneyReceipts, title: translate('common.expenses')}, shared: {icon: Illustrations.SendMoney, title: translate('common.shared')}, drafts: {icon: Illustrations.Pencil, title: translate('common.drafts')}, @@ -146,8 +145,8 @@ function SearchPageHeader({query, selectedItems = {}, hash, clearSelectedItems, return ( {headerButtonsOptions.length > 0 && ( diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 78992496f031..a4a9d0f10cc6 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -23,27 +23,25 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SearchResults from '@src/types/onyx/SearchResults'; -import type {SearchDataTypes, SearchQuery} from '@src/types/onyx/SearchResults'; +import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import {useSearchContext} from './SearchContext'; import SearchListWithHeader from './SearchListWithHeader'; import SearchPageHeader from './SearchPageHeader'; -import type {SearchColumnType, SortOrder} from './types'; +import type {SearchColumnType, SearchQueryJSON, SearchStatus, SortOrder} from './types'; type SearchProps = { - query: SearchQuery; + queryJSON: SearchQueryJSON; policyIDs?: string; - sortBy?: SearchColumnType; - sortOrder?: SortOrder; isMobileSelectionModeActive?: boolean; setIsMobileSelectionModeActive?: (isMobileSelectionModeActive: boolean) => void; }; -const sortableSearchTabs: SearchQuery[] = [CONST.SEARCH.TAB.ALL]; +const sortableSearchTabs: SearchStatus[] = [CONST.SEARCH.STATUS.ALL]; const transactionItemMobileHeight = 100; const reportItemTransactionHeight = 52; const listItemPadding = 12; // this is equivalent to 'mb3' on every transaction/report list item const searchHeaderHeight = 54; -function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActive, setIsMobileSelectionModeActive}: SearchProps) { +function Search({queryJSON, policyIDs, isMobileSelectionModeActive, setIsMobileSelectionModeActive}: SearchProps) { const {isOffline} = useNetwork(); const styles = useThemeStyles(); const {isLargeScreenWidth, isSmallScreenWidth} = useWindowDimensions(); @@ -51,7 +49,8 @@ function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActiv const lastSearchResultsRef = useRef>(); const {setCurrentSearchHash} = useSearchContext(); - const hash = SearchUtils.getQueryHash(query, policyIDs, sortBy, sortOrder); + const {status, sortBy, sortOrder, hash} = queryJSON; + const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`); const getItemHeight = useCallback( @@ -97,7 +96,9 @@ function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActiv } setCurrentSearchHash(hash); - SearchActions.search({hash, query, policyIDs, offset: 0, sortBy, sortOrder}); + + // TODO_SEARCH: Function below will be deprecated soon. No point in refactoring to use status instead of query. + SearchActions.search({hash, query: status, policyIDs, offset: 0, sortBy, sortOrder}); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [hash, isOffline]); @@ -108,7 +109,7 @@ function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActiv return ( <> @@ -122,7 +123,7 @@ function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActiv return ( <> @@ -143,7 +144,7 @@ function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActiv SearchActions.createTransactionThread(hash, item.transactionID, reportID, item.moneyRequestReportActionID); } - Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(query, reportID)); + Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(status, reportID)); }; const fetchMoreResults = () => { @@ -151,7 +152,9 @@ function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActiv return; } const currentOffset = searchResults?.search?.offset ?? 0; - SearchActions.search({hash, query, offset: currentOffset + CONST.SEARCH.RESULTS_PAGE_SIZE, sortBy, sortOrder}); + + // TODO_SEARCH: Function below will be deprecated soon. No point in refactoring to use status instead of query. + SearchActions.search({hash, query: status, offset: currentOffset + CONST.SEARCH.RESULTS_PAGE_SIZE, sortBy, sortOrder}); }; const type = SearchUtils.getSearchType(searchResults?.search); @@ -167,13 +170,16 @@ function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActiv const sortedData = SearchUtils.getSortedSections(type, data, sortBy, sortOrder); const onSortPress = (column: SearchColumnType, order: SortOrder) => { + const tmp = SearchUtils.buildSearchQueryJSON(undefined); + console.log({tmp}); + + const newQuery = SearchUtils.buildSearchQueryString({sortBy: column, sortOrder: order}); navigation.setParams({ - sortBy: column, - sortOrder: order, + q: newQuery, }); }; - const isSortingAllowed = sortableSearchTabs.includes(query); + const isSortingAllowed = sortableSearchTabs.includes(status); const shouldShowYear = SearchUtils.shouldShowYear(searchResults?.data); @@ -181,7 +187,7 @@ function Search({query, policyIDs, sortBy, sortOrder, isMobileSelectionModeActiv return ( ; type SortOrder = ValueOf; type SearchColumnType = ValueOf; +type SearchStatus = ValueOf; type SearchContext = { currentSearchHash: number; @@ -43,4 +44,37 @@ type QueryFilters = { [K in AllFieldKeys]: QueryFilter | QueryFilter[]; }; -export type {SelectedTransactionInfo, SelectedTransactions, SearchColumnType, SortOrder, SearchContext, ASTNode, QueryFilter, QueryFilters, AllFieldKeys}; +type QueryKind = ValueOf; + +type SearchQueryString = string; + +type SearchQueryAST = { + type: string; + status: SearchStatus; + sortBy: SearchColumnType; + sortOrder: SortOrder; + offset: number; + filters: ASTNode; +}; + +type SearchQueryJSON = { + input: string; + hash: number; +} & SearchQueryAST; + +export type { + QueryKind, + SelectedTransactionInfo, + SelectedTransactions, + SearchColumnType, + SearchStatus, + SearchQueryAST, + SearchQueryJSON, + SearchQueryString, + SortOrder, + SearchContext, + ASTNode, + QueryFilter, + QueryFilters, + AllFieldKeys, +}; diff --git a/src/components/SelectionList/Search/ActionCell.tsx b/src/components/SelectionList/Search/ActionCell.tsx index 7888a8b26114..8ca0f69116cd 100644 --- a/src/components/SelectionList/Search/ActionCell.tsx +++ b/src/components/SelectionList/Search/ActionCell.tsx @@ -57,7 +57,7 @@ function ActionCell({ } if (action === CONST.SEARCH.ACTION_TYPES.HOLD) { - Navigation.navigate(ROUTES.TRANSACTION_HOLD_REASON_RHP.getRoute(CONST.SEARCH.TAB.ALL, transactionID)); + Navigation.navigate(ROUTES.TRANSACTION_HOLD_REASON_RHP.getRoute(CONST.SEARCH.STATUS.ALL, transactionID)); } else if (action === CONST.SEARCH.ACTION_TYPES.UNHOLD) { SearchActions.unholdMoneyRequestOnSearch(currentSearchHash, [transactionID]); } diff --git a/src/components/SelectionList/Search/ReportListItem.tsx b/src/components/SelectionList/Search/ReportListItem.tsx index e6358698d414..d8573fd45be2 100644 --- a/src/components/SelectionList/Search/ReportListItem.tsx +++ b/src/components/SelectionList/Search/ReportListItem.tsx @@ -10,7 +10,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {getSearchParams} from '@libs/SearchUtils'; +import {getQueryFromSearchParams, getSearchParams} from '@libs/SearchUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import ActionCell from './ActionCell'; @@ -79,7 +79,7 @@ function ReportListItem({ const openReportInRHP = (transactionItem: TransactionListItemType) => { const searchParams = getSearchParams(); - const currentQuery = searchParams?.query ?? CONST.SEARCH.TAB.ALL; + const currentQuery = getQueryFromSearchParams(searchParams); Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(currentQuery, transactionItem.transactionThreadReportID)); }; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 0e40fa4d4037..19a88faab094 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -84,7 +84,8 @@ function shouldOpenOnAdminRoom() { function getCentralPaneScreenInitialParams(screenName: CentralPaneName): Partial> { if (screenName === SCREENS.SEARCH.CENTRAL_PANE) { - return {sortBy: CONST.SEARCH.TABLE_COLUMNS.DATE, sortOrder: CONST.SEARCH.SORT_ORDER.DESC}; + // TODO_SEARCH: normalize this query + return {q: CONST.SEARCH.DEFAULT_QUERY}; } if (screenName === SCREENS.REPORT) { diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx index f244342c28ae..6515812fe825 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx @@ -112,7 +112,7 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps if (currentTabName === SCREENS.SEARCH.BOTTOM_TAB || currentTabName === SCREENS.SEARCH.CENTRAL_PANE) { return; } - interceptAnonymousUser(() => Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.ALL))); + interceptAnonymousUser(() => Navigation.navigate(ROUTES.SEARCH.getRoute({query: CONST.SEARCH.TABv2.EXPENSE.ALL}))); }} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('common.search')} diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx index 556365b473c3..c552cd66a588 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx @@ -112,7 +112,7 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps if (isSearchTabName(activeBottomTabRoute?.name)) { return; } - interceptAnonymousUser(() => Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.ALL))); + interceptAnonymousUser(() => Navigation.navigate(ROUTES.SEARCH.getRoute({query: CONST.SEARCH.TABv2.EXPENSE.ALL}))); }} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('common.search')} diff --git a/src/libs/Navigation/switchPolicyID.ts b/src/libs/Navigation/switchPolicyID.ts index 19626a400b9d..0c259cecbeb4 100644 --- a/src/libs/Navigation/switchPolicyID.ts +++ b/src/libs/Navigation/switchPolicyID.ts @@ -84,7 +84,7 @@ export default function switchPolicyID(navigation: NavigationContainerRef>; const action: StackNavigationAction = getActionFromState(stateFromPath, linkingConfig.config); diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index c273b2db6a10..086a1a5af57f 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -11,7 +11,7 @@ import type { Route, } from '@react-navigation/native'; import type {TupleToUnion, ValueOf} from 'type-fest'; -import type {SearchColumnType, SortOrder} from '@components/Search/types'; +import type {SearchQueryString} from '@components/Search/types'; import type {IOURequestType} from '@libs/actions/IOU'; import type CONST from '@src/CONST'; import type {Country, IOUAction, IOUType} from '@src/CONST'; @@ -64,13 +64,9 @@ type CentralPaneScreensParamList = { [SCREENS.SETTINGS.ABOUT]: undefined; [SCREENS.SETTINGS.TROUBLESHOOT]: undefined; [SCREENS.SETTINGS.WORKSPACES]: undefined; - [SCREENS.SEARCH.CENTRAL_PANE]: { - query: string; - policyIDs?: string; - offset?: number; - sortBy?: SearchColumnType; - sortOrder?: SortOrder; - }; + + // Param types of the search central pane are also used for the search bottom tab screen. + [SCREENS.SEARCH.CENTRAL_PANE]: ({cq: SearchQueryString; q?: never} | {q: SearchQueryString; cq?: never}) & {policyIDs?: string}; [SCREENS.SETTINGS.SAVE_THE_WORLD]: undefined; [SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: undefined; }; @@ -1164,13 +1160,7 @@ type BottomTabScreensParamList = {[SCREENS.HOME]: undefined; [SCREENS.REPORT]: u type BottomTabNavigatorParamList = { [SCREENS.HOME]: {policyID?: string}; - [SCREENS.SEARCH.BOTTOM_TAB]: { - query: string; - policyID?: string; - offset?: number; - sortBy?: SearchColumnType; - sortOrder?: SortOrder; - }; + [SCREENS.SEARCH.BOTTOM_TAB]: CentralPaneScreensParamList[typeof SCREENS.SEARCH.CENTRAL_PANE]; [SCREENS.SETTINGS.ROOT]: {policyID?: string}; }; diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index 4ec3fa9fb314..352489d9b39c 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -1,10 +1,11 @@ import type {ValueOf} from 'type-fest'; -import type {AllFieldKeys, ASTNode, QueryFilter, QueryFilters, SearchColumnType, SortOrder} from '@components/Search/types'; +import type {AllFieldKeys, ASTNode, QueryFilter, QueryFilters, SearchColumnType, SearchQueryAST, SearchQueryJSON, SearchQueryString, SortOrder} from '@components/Search/types'; import ReportListItem from '@components/SelectionList/Search/ReportListItem'; import TransactionListItem from '@components/SelectionList/Search/TransactionListItem'; import type {ListItem, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import type {SearchAccountDetails, SearchDataTypes, SearchPersonalDetails, SearchTransaction, SearchTypeToItemMap, SectionsType} from '@src/types/onyx/SearchResults'; import type SearchResults from '@src/types/onyx/SearchResults'; @@ -304,29 +305,23 @@ function getSearchParams() { return topmostCentralPaneRoute?.params as AuthScreensParamList['Search_Central_Pane']; } +// Query may be in the q or cq parameter +function getQueryFromSearchParams(params: AuthScreensParamList[typeof SCREENS.SEARCH.CENTRAL_PANE]) { + return params.q ?? params.cq; +} + function isSearchResultsEmpty(searchResults: SearchResults) { return !Object.keys(searchResults?.data).some((key) => key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION)); } -function getQueryHashFromString(query: string): number { +function getQueryHashFromString(query: SearchQueryString): number { return UserUtils.hashText(query, 2 ** 32); } -type JSONQuery = { - input: string; - hash: number; - type: string; - status: string; - sortBy: string; - sortOrder: string; - offset: number; - filters: ASTNode; -}; - -function buildJSONQuery(query: string) { +function buildSearchQueryJSON(query: SearchQueryString) { try { // Add the full input and hash to the results - const result = searchParser.parse(query) as JSONQuery; + const result = searchParser.parse(query) as SearchQueryJSON; result.input = query; result.hash = getQueryHashFromString(query); return result; @@ -335,10 +330,25 @@ function buildJSONQuery(query: string) { } } -function getFilters(query: string, fields: Array>) { - let jsonQuery; +// TODO_SEARCH: this function may need improvement's to handle more complex queries. For now it will handle canned queries. +function buildSearchQueryString(partialQueryAST: Partial>) { + const queryParts: string[] = []; + + // For this const values are lowercase version of the keys. We are using lowercase for ast keys. + for (const [, value] of Object.entries(CONST.SEARCH.SYNTAX_ROOT_KEYS)) { + if (partialQueryAST[value]) { + queryParts.push(`${value}:${partialQueryAST[value]}`); + } + } + + return queryParts.join(' '); +} + +function getFilters(query: SearchQueryString, fields: Array>) { + let queryAST; + try { - jsonQuery = searchParser.parse(query) as JSONQuery; + queryAST = searchParser.parse(query) as SearchQueryJSON; } catch (e) { console.error(e); return; @@ -348,13 +358,13 @@ function getFilters(query: string, fields: Array>) { fields.forEach((field) => { const rootFieldKey = field as ValueOf; - if (jsonQuery[rootFieldKey] === undefined) { + if (queryAST[rootFieldKey] === undefined) { return; } filters[field] = { operator: 'eq', - value: jsonQuery[rootFieldKey], + value: queryAST[rootFieldKey], }; }); @@ -387,15 +397,17 @@ function getFilters(query: string, fields: Array>) { }); } - if (jsonQuery.filters) { - traverse(jsonQuery.filters); + if (queryAST.filters) { + traverse(queryAST.filters); } return filters; } export { - buildJSONQuery, + getQueryFromSearchParams, + buildSearchQueryJSON, + buildSearchQueryString, getListItem, getQueryHash, getSections, diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 4ce82a027a12..a45890a190b2 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -1,8 +1,10 @@ import Onyx from 'react-native-onyx'; import type {OnyxUpdate} from 'react-native-onyx'; +import type {SearchQueryString} from '@components/Search/types'; import * as API from '@libs/API'; import type {SearchParams} from '@libs/API/parameters'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; +import {buildSearchQueryJSON} from '@libs/SearchUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SearchTransaction} from '@src/types/onyx/SearchResults'; import * as Report from './Report'; @@ -49,6 +51,22 @@ function search({hash, query, policyIDs, offset, sortBy, sortOrder}: SearchParam API.read(READ_COMMANDS.SEARCH, {hash, query, offset, policyIDs, sortBy, sortOrder}, {optimisticData, finallyData}); } +// TODO_SEARCH: use this function after backend changes. +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function searchV2(queryString: SearchQueryString) { + const queryJSON = buildSearchQueryJSON(queryString); + + if (!queryJSON) { + return; + } + + const {optimisticData, finallyData} = getOnyxLoadingData(queryJSON.hash); + + // TODO_SEARCH: uncomment this line after backend changes + // @ts-expect-error waiting for backend changes + API.read(READ_COMMANDS.SEARCH, queryJSON, {optimisticData, finallyData}); +} + /** * It's possible that we return legacy transactions that don't have a transaction thread created yet. * In that case, when users select the search result row, we need to create the transaction thread on the fly and update the search result with the new transactionThreadReport diff --git a/src/pages/Search/SearchFilters.tsx b/src/pages/Search/SearchFilters.tsx index bbb861364f00..1d04bcfc272b 100644 --- a/src/pages/Search/SearchFilters.tsx +++ b/src/pages/Search/SearchFilters.tsx @@ -36,25 +36,25 @@ function SearchFilters({query}: SearchFiltersProps) { title: translate('common.expenses'), query: CONST.SEARCH.TAB.ALL, icon: Expensicons.Receipt, - route: ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.ALL), + route: ROUTES.SEARCH.getRoute({query: CONST.SEARCH.TABv2.EXPENSE.ALL}), }, { title: translate('common.shared'), query: CONST.SEARCH.TAB.SHARED, icon: Expensicons.Send, - route: ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.SHARED), + route: ROUTES.SEARCH.getRoute({query: CONST.SEARCH.TABv2.EXPENSE.SHARED}), }, { title: translate('common.drafts'), query: CONST.SEARCH.TAB.DRAFTS, icon: Expensicons.Pencil, - route: ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.DRAFTS), + route: ROUTES.SEARCH.getRoute({query: CONST.SEARCH.TABv2.EXPENSE.DRAFTS}), }, { title: translate('common.finished'), query: CONST.SEARCH.TAB.FINISHED, icon: Expensicons.CheckCircle, - route: ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.FINISHED), + route: ROUTES.SEARCH.getRoute({query: CONST.SEARCH.TABv2.EXPENSE.FINISHED}), }, ]; const activeItemIndex = filterItems.findIndex((item) => item.query === query); diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 6e734fd835d2..c77657f47b2f 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -1,5 +1,5 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; +import React, {useMemo} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ScreenWrapper from '@components/ScreenWrapper'; import Search from '@components/Search'; @@ -7,10 +7,10 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; +import {buildSearchQueryJSON} from '@libs/SearchUtils'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {SearchQuery} from '@src/types/onyx/SearchResults'; type SearchPageProps = StackScreenProps; @@ -18,12 +18,9 @@ function SearchPage({route}: SearchPageProps) { const {isSmallScreenWidth} = useWindowDimensions(); const styles = useThemeStyles(); - const {query: rawQuery, policyIDs, sortBy, sortOrder} = route?.params ?? {}; + const queryJSON = useMemo(() => buildSearchQueryJSON(route.params.cq ?? route.params.q), [route.params]); - const query = rawQuery as SearchQuery; - const isValidQuery = Object.values(CONST.SEARCH.TAB).includes(query); - - const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.ALL)); + const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute({query: CONST.SEARCH.TABv2.EXPENSE.ALL})); // 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. @@ -39,15 +36,15 @@ function SearchPage({route}: SearchPageProps) { > diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx index 9ae6423a6cc1..36aa411fae9a 100644 --- a/src/pages/Search/SearchPageBottomTab.tsx +++ b/src/pages/Search/SearchPageBottomTab.tsx @@ -1,4 +1,3 @@ -import type {StackScreenProps} from '@react-navigation/stack'; import React, {useMemo, useState} from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -9,22 +8,12 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; -import type {CentralPaneScreensParamList} from '@libs/Navigation/types'; +import {buildSearchQueryJSON} from '@libs/SearchUtils'; import TopBar from '@navigation/AppNavigator/createCustomBottomTabNavigator/TopBar'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; -import type {SearchQuery} from '@src/types/onyx/SearchResults'; import SearchFilters from './SearchFilters'; -type SearchPageProps = StackScreenProps; - -const defaultSearchProps = { - query: '' as SearchQuery, - policyIDs: undefined, - sortBy: CONST.SEARCH.TABLE_COLUMNS.DATE, - sortOrder: CONST.SEARCH.SORT_ORDER.DESC, -}; function SearchPageBottomTab() { const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -32,23 +21,16 @@ function SearchPageBottomTab() { const styles = useThemeStyles(); const [isMobileSelectionModeActive, setIsMobileSelectionModeActive] = useState(false); - const { - query: rawQuery, - policyIDs, - sortBy, - sortOrder, - } = useMemo(() => { - if (activeBottomTabRoute?.name !== SCREENS.SEARCH.CENTRAL_PANE || !activeBottomTabRoute.params) { - return defaultSearchProps; - } - return {...defaultSearchProps, ...activeBottomTabRoute.params} as SearchPageProps['route']['params']; - }, [activeBottomTabRoute]); - - const query = rawQuery as SearchQuery; + // TODO_SEARCH: tpyes for the activeBottomTabRoute are broken. + const queryJSON = useMemo(() => buildSearchQueryJSON(activeBottomTabRoute.params.cq ?? activeBottomTabRoute.params.q), [activeBottomTabRoute.params]); + const policyIDs = activeBottomTabRoute.params.policyIDs as string | undefined; - const isValidQuery = Object.values(CONST.SEARCH.TAB).includes(query); + const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute({query: CONST.SEARCH.TABv2.EXPENSE.ALL})); - const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.ALL)); + // TODO_SEARCH: not sure how we should handle possible undefined for queryJSON. + if (!queryJSON) { + return null; + } return ( @@ -68,7 +50,7 @@ function SearchPageBottomTab() { breadcrumbLabel={translate('common.search')} shouldDisplaySearch={false} /> - + ) : ( diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 4cc160fc13b2..8eaddf28396d 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -148,7 +148,7 @@ function CardSection() { title={translate('subscription.cardSection.viewPaymentHistory')} titleStyle={styles.textStrong} style={styles.mt5} - onPress={() => Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.ALL))} + onPress={() => Navigation.navigate(ROUTES.SEARCH.getRoute({query: CONST.SEARCH.TABv2.EXPENSE.ALL}))} hoverAndPressStyle={styles.hoveredComponentBG} /> )} diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index 97a8b459ff0f..fb787903dd8a 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -64,6 +64,17 @@ type SearchResultsInfo = { /** The optional columns that should be shown according to policy settings */ columnsToShow: ColumnsToShow; + + /** The status of the search results to show */ + statusToShow: { + // eslint-disable-next-line jsdoc/require-jsdoc + expense: { + // eslint-disable-next-line jsdoc/require-jsdoc + drafts: boolean; + // eslint-disable-next-line jsdoc/require-jsdoc + approved: boolean; + }; + }; }; /** Model of personal details search result */ @@ -237,9 +248,6 @@ type SearchAccountDetails = Partial /** Types of searchable transactions */ type SearchTransactionType = ValueOf; -/** Types of search queries */ -type SearchQuery = ValueOf; - /** Model of search results */ type SearchResults = { /** Current search results state */ @@ -255,7 +263,6 @@ type SearchResults = { export default SearchResults; export type { - SearchQuery, SearchTransaction, SearchTransactionType, SearchTransactionAction,