Skip to content
2 changes: 2 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4747,6 +4747,8 @@ const CONST = {
DISTANCE: 'distance',
},

SEARCH_BOTTOM_TAB_URL: '/Search_Bottom_Tab',

SEARCH_DATA_TYPES: {
TRANSACTION: 'transaction',
},
Expand Down
10 changes: 6 additions & 4 deletions src/components/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,25 @@ import TableListItemSkeleton from './Skeletons/TableListItemSkeleton';

type SearchProps = {
query: string;
policyIDs?: string;
};

function Search({query}: SearchProps) {
function Search({query, policyIDs}: SearchProps) {
const {isOffline} = useNetwork();
const styles = useThemeStyles();
useCustomBackHandler();

const hash = SearchUtils.getQueryHash(query);
const hash = SearchUtils.getQueryHash(query, policyIDs);
const [searchResults, searchResultsMeta] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`);

useEffect(() => {
if (isOffline) {
return;
}

SearchActions.search(query);
}, [query, isOffline]);
SearchActions.search(hash, query, policyIDs);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hash, isOffline]);

const isLoading = (!isOffline && isLoadingOnyxValue(searchResultsMeta)) || searchResults?.data === undefined;
const shouldShowEmptyState = !isLoading && isEmptyObject(searchResults?.data);
Expand Down
1 change: 1 addition & 0 deletions src/libs/API/parameters/Search.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
type SearchParams = {
query: string;
policyIDs?: string;
hash: number;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function TopBar({policy, session, breadcrumbLabel, shouldDisplaySearch = true}:
return (
<View style={styles.w100}>
<View
style={[styles.flexRow, styles.gap4, styles.mh3, styles.mv5, styles.alignItemsCenter, {justifyContent: 'space-between'}]}
style={[styles.flexRow, styles.gap4, styles.mh3, styles.mv5, styles.alignItemsCenter, styles.justifyContentBetween]}
dataSet={{dragArea: true}}
>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.ml2]}>
Expand Down
10 changes: 7 additions & 3 deletions src/libs/Navigation/linkingConfig/customGetPathFromState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import {getPathFromState} from '@react-navigation/native';
import _ from 'lodash';
import getPolicyIDFromState from '@libs/Navigation/getPolicyIDFromState';
import getTopmostBottomTabRoute from '@libs/Navigation/getTopmostBottomTabRoute';
import type {RootStackParamList, State} from '@libs/Navigation/types';
import type {BottomTabName, RootStackParamList, State} from '@libs/Navigation/types';
import SCREENS from '@src/SCREENS';

// The policy ID parameter should be included in the URL when any of these pages is opened in the bottom tab.
const SCREENS_WITH_POLICY_ID_IN_URL: BottomTabName[] = [SCREENS.HOME, SCREENS.SEARCH.BOTTOM_TAB] as const;

const removePolicyIDParamFromState = (state: State<RootStackParamList>) => {
const stateCopy = _.cloneDeep(state);
const bottomTabRoute = getTopmostBottomTabRoute(stateCopy);
Expand All @@ -19,8 +22,9 @@ const customGetPathFromState: typeof getPathFromState = (state, options) => {
const stateWithoutPolicyID = removePolicyIDParamFromState(state as State<RootStackParamList>);
const path = getPathFromState(stateWithoutPolicyID, options);
const policyIDFromState = getPolicyIDFromState(state as State<RootStackParamList>);
const isHomeOpened = getTopmostBottomTabRoute(state as State<RootStackParamList>)?.name === SCREENS.HOME;
return `${policyIDFromState && isHomeOpened ? `/w/${policyIDFromState}` : ''}${path}`;
const topmostBottomTabRouteName = getTopmostBottomTabRoute(state as State<RootStackParamList>)?.name;
const shouldAddPolicyID = !!topmostBottomTabRouteName && SCREENS_WITH_POLICY_ID_IN_URL.includes(topmostBottomTabRouteName);
return `${policyIDFromState && shouldAddPolicyID ? `/w/${policyIDFromState}` : ''}${path}`;
};

export default customGetPathFromState;
89 changes: 35 additions & 54 deletions src/libs/Navigation/switchPolicyID.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {getActionFromState} from '@react-navigation/core';
import type {NavigationAction, NavigationContainerRef, NavigationState, PartialState} from '@react-navigation/native';
import {getPathFromState} from '@react-navigation/native';
import type {ValueOf, Writable} from 'type-fest';
import type {Writable} from 'type-fest';
import getIsNarrowLayout from '@libs/getIsNarrowLayout';
import CONST from '@src/CONST';
import NAVIGATORS from '@src/NAVIGATORS';
import type {Route} from '@src/ROUTES';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import getStateFromPath from './getStateFromPath';
import getTopmostCentralPaneRoute from './getTopmostCentralPaneRoute';
Expand Down Expand Up @@ -35,14 +36,8 @@ function getActionForBottomTabNavigator(action: StackNavigationAction, state: Na
let payloadParams = params?.params as Record<string, string | undefined>;
let screen = params.screen;

// Case when the user is on the AllSettingsScreen and selects the specific workspace. The user is redirected then to the specific workspace settings.
if (screen === SCREENS.ALL_SETTINGS && policyID) {
screen = SCREENS.WORKSPACE.INITIAL;
}

// Alternative case when the user is on the specific workspace settings screen and selects "All" workspace.
else if (!policyID && screen === SCREENS.WORKSPACE.INITIAL) {
screen = SCREENS.ALL_SETTINGS;
Comment on lines -38 to -45

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we remove this?

if (screen === SCREENS.SEARCH.CENTRAL_PANE) {
screen = SCREENS.SEARCH.BOTTOM_TAB;
}

if (!payloadParams) {
Expand All @@ -65,7 +60,6 @@ export default function switchPolicyID(navigation: NavigationContainerRef<RootSt
if (!navigation) {
throw new Error("Couldn't find a navigation object. Is your component inside a screen in a navigator?");
}

let root: NavigationRoot = navigation;
let current: NavigationRoot | undefined;

Expand All @@ -76,7 +70,14 @@ export default function switchPolicyID(navigation: NavigationContainerRef<RootSt
}

const rootState = navigation.getRootState() as NavigationState<RootStackParamList>;
const newPath = route ?? getPathFromState({routes: rootState.routes} as State, linkingConfig.config);
let newPath = route ?? getPathFromState({routes: rootState.routes} as State, linkingConfig.config);

// Currently, the search page displayed in the bottom tab has the same URL as the page in the central pane, so we need to redirect to the correct search route.
// Here's the configuration: src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx
const isOpeningSearchFromBottomTab = newPath.startsWith(CONST.SEARCH_BOTTOM_TAB_URL);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the screen name instead of creating a new const for it?

if (isOpeningSearchFromBottomTab) {
newPath = ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL);
}
const stateFromPath = getStateFromPath(newPath as Route) as PartialState<NavigationState<RootStackParamList>>;
const action: StackNavigationAction = getActionFromState(stateFromPath, linkingConfig.config);

Expand All @@ -93,51 +94,31 @@ export default function switchPolicyID(navigation: NavigationContainerRef<RootSt
return;
}

// The correct route for SearchPage is located in the CentralPane
const shouldAddToCentralPane = !getIsNarrowLayout() || isOpeningSearchFromBottomTab;

// If the layout is wide we need to push matching central pane route to the stack.
if (!getIsNarrowLayout()) {
// Case when the user selects "All" workspace from the specific workspace settings
if (checkIfActionPayloadNameIsEqual(actionForBottomTabNavigator, SCREENS.ALL_SETTINGS) && !policyID) {
root.dispatch({
type: CONST.NAVIGATION.ACTION_TYPE.PUSH,
payload: {
name: NAVIGATORS.CENTRAL_PANE_NAVIGATOR,
params: {
screen: SCREENS.SETTINGS.WORKSPACES,
params: undefined,
},
},
});
} else {
const topmostCentralPaneRoute = getTopmostCentralPaneRoute(rootState);
const screen = topmostCentralPaneRoute?.name;
const params: CentralPaneRouteParams = {...topmostCentralPaneRoute?.params};
const isWorkspaceScreen = screen && Object.values(SCREENS.WORKSPACE).includes(screen as ValueOf<typeof SCREENS.WORKSPACE>);

// Only workspace settings screens have to store the policyID in the params.
// In other case, the policyID is read from the BottomTab params.
if (!isWorkspaceScreen) {
delete params.policyID;
} else {
params.policyID = policyID;
}

// If the user is on the home page and changes the current workspace, then should be displayed a report from the selected workspace.
// To achieve that, it's necessary to navigate without the reportID param.
if (checkIfActionPayloadNameIsEqual(actionForBottomTabNavigator, SCREENS.HOME)) {
delete params.reportID;
}

root.dispatch({
type: CONST.NAVIGATION.ACTION_TYPE.PUSH,
payload: {
name: NAVIGATORS.CENTRAL_PANE_NAVIGATOR,
params: {
screen,
params,
},
},
});
Comment on lines -97 to -139

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that I understand this diff. Would you mind explaining why we can replace this code?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is related to the pages that have been already refactored. Previously Workspace Switcher was available on Account Settings and Workspace Initial Page pages, we don't need this anymore since these pages don't display WS :)

if (shouldAddToCentralPane) {
const topmostCentralPaneRoute = getTopmostCentralPaneRoute(rootState);
const screen = topmostCentralPaneRoute?.name;
const params: CentralPaneRouteParams = {...topmostCentralPaneRoute?.params};

// If the user is on the home page and changes the current workspace, then should be displayed a report from the selected workspace.
// To achieve that, it's necessary to navigate without the reportID param.
if (checkIfActionPayloadNameIsEqual(actionForBottomTabNavigator, SCREENS.HOME)) {
delete params.reportID;
}

root.dispatch({
type: CONST.NAVIGATION.ACTION_TYPE.PUSH,
payload: {
name: NAVIGATORS.CENTRAL_PANE_NAVIGATOR,
params: {
screen,
params,
},
},
});
} else {
// If the layout is small we need to pop everything from the central pane so the bottom tab navigator is visible.
root.dispatch({
Expand Down
5 changes: 3 additions & 2 deletions src/libs/SearchUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ function getSections<K extends keyof SearchTypeToItemMap>(data: OnyxTypes.Search
return searchTypeToItemMap[type].getSections(data) as ReturnType<SearchTypeToItemMap[K]['getSections']>;
}

function getQueryHash(query: string): number {
return UserUtils.hashText(query, 2 ** 32);
function getQueryHash(query: string, policyID?: string): number {
const textToHash = [query, policyID].filter(Boolean).join('_');
return UserUtils.hashText(textToHash, 2 ** 32);
}

export {getListItem, getQueryHash, getSections, getShouldShowMerchant, getSearchType};
6 changes: 2 additions & 4 deletions src/libs/actions/Search.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import * as API from '@libs/API';
import {READ_COMMANDS} from '@libs/API/types';
import * as SearchUtils from '@libs/SearchUtils';

function search(query: string) {
const hash = SearchUtils.getQueryHash(query);
API.read(READ_COMMANDS.SEARCH, {query, hash});
function search(hash: number, query: string, policyIDs?: string) {
API.read(READ_COMMANDS.SEARCH, {hash, query, policyIDs});
}

export {
Expand Down
16 changes: 15 additions & 1 deletion src/pages/Search/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Illustrations from '@components/Icon/Illustrations';
import ScreenWrapper from '@components/ScreenWrapper';
import Search from '@components/Search';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useLocalize from '@hooks/useLocalize';
import useWindowDimensions from '@hooks/useWindowDimensions';
import Navigation from '@libs/Navigation/Navigation';
import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types';
import CONST from '@src/CONST';
Expand All @@ -18,6 +20,7 @@ type SearchPageProps = StackScreenProps<CentralPaneNavigatorParamList, typeof SC

function SearchPage({route}: SearchPageProps) {
const {translate} = useLocalize();
const {isSmallScreenWidth} = useWindowDimensions();
const currentQuery = route?.params && 'query' in route.params ? route?.params?.query : '';
const query = currentQuery as SearchQuery;
const isValidQuery = Object.values(CONST.TAB_SEARCH).includes(query);
Expand All @@ -28,6 +31,14 @@ function SearchPage({route}: SearchPageProps) {

const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL));

const {activeWorkspaceID} = useActiveWorkspace();

// 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.
if (isSmallScreenWidth) {
return null;
}

return (
<ScreenWrapper testID={Search.displayName}>
<FullPageNotFoundView
Expand All @@ -41,7 +52,10 @@ function SearchPage({route}: SearchPageProps) {
icon={headerContent[query]?.icon}
shouldShowBackButton={false}
/>
<Search query={query} />
<Search
policyIDs={activeWorkspaceID}
query={query}
/>
</FullPageNotFoundView>
</ScreenWrapper>
);
Expand Down
11 changes: 9 additions & 2 deletions src/pages/Search/SearchPageBottomTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView
import ScreenWrapper from '@components/ScreenWrapper';
import Search from '@components/Search';
import useActiveRoute from '@hooks/useActiveRoute';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
Expand All @@ -18,7 +19,7 @@ function SearchPageBottomTab() {
const {isSmallScreenWidth} = useWindowDimensions();
const activeRoute = useActiveRoute();
const styles = useThemeStyles();

const {activeWorkspaceID} = useActiveWorkspace();
const currentQuery = activeRoute?.params && 'query' in activeRoute.params ? activeRoute?.params?.query : '';
const query = currentQuery as SearchQuery;
const isValidQuery = Object.values(CONST.TAB_SEARCH).includes(query);
Expand All @@ -36,11 +37,17 @@ function SearchPageBottomTab() {
shouldShowLink={false}
>
<TopBar
activeWorkspaceID={activeWorkspaceID}
breadcrumbLabel={translate('common.search')}
shouldDisplaySearch={false}
/>
<SearchFilters query={query} />
{isSmallScreenWidth && <Search query={query} />}
{isSmallScreenWidth && (
<Search
policyIDs={activeWorkspaceID}
query={query}
/>
)}
</FullPageNotFoundView>
</ScreenWrapper>
);
Expand Down