Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Better Expense Report View] Add new navigator to allow for new SearchMoneyRequestReport screen #57607

Merged
1 change: 1 addition & 0 deletions src/NAVIGATORS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export default {
REPORTS_SPLIT_NAVIGATOR: 'ReportsSplitNavigator',
SETTINGS_SPLIT_NAVIGATOR: 'SettingsSplitNavigator',
WORKSPACE_SPLIT_NAVIGATOR: 'WorkspaceSplitNavigator',
SEARCH_FULLSCREEN_NAVIGATOR: 'SearchFullscreenNavigator',
} as const;
7 changes: 7 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ const ROUTES = {
return getUrlWithBackToParam(baseRoute, backTo);
},
},
SEARCH_MONEY_REQUEST_REPORT: {
route: 'search/report/:reportID',
getRoute: ({reportID, backTo}: {reportID: string; backTo?: string}) => {
const baseRoute = `search/view/${reportID}` as const;
return getUrlWithBackToParam(baseRoute, backTo);
},
},
TRANSACTION_HOLD_REASON_RHP: 'search/hold',

// This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const SCREENS = {
},
SEARCH: {
ROOT: 'Search_Root',
MONEY_REQUEST_REPORT: 'Search_Money_Request_Report',
REPORT_RHP: 'Search_Report_RHP',
ADVANCED_FILTERS_RHP: 'Search_Advanced_Filters_RHP',
ADVANCED_FILTERS_DATE_RHP: 'Search_Advanced_Filters_Date_RHP',
Expand Down
50 changes: 2 additions & 48 deletions src/components/Navigation/BottomTabBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import * as Expensicons from '@components/Icon/Expensicons';
import DebugTabView from '@components/Navigation/DebugTabView';
import {PressableWithFeedback} from '@components/Pressable';
import {useProductTrainingContext} from '@components/ProductTrainingContext';
import type {SearchQueryString} from '@components/Search/types';
import Text from '@components/Text';
import EducationalTooltip from '@components/Tooltip/EducationalTooltip';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
Expand All @@ -21,15 +20,14 @@ import useThemeStyles from '@hooks/useThemeStyles';
import clearSelectedText from '@libs/clearSelectedText/clearSelectedText';
import getPlatform from '@libs/getPlatform';
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
import {getPolicy} from '@libs/PolicyUtils';
import {buildCannedSearchQuery, buildSearchQueryJSON, buildSearchQueryString} from '@libs/SearchQueryUtils';
import {buildCannedSearchQuery} from '@libs/SearchQueryUtils';
import type {BrickRoad} from '@libs/WorkspacesSettingsUtils';
import {getChatTabBrickRoad} from '@libs/WorkspacesSettingsUtils';
import {getPreservedSplitNavigatorState} from '@navigation/AppNavigator/createSplitNavigator/usePreserveSplitNavigatorState';
import {isFullScreenName} from '@navigation/helpers/isNavigatorName';
import Navigation from '@navigation/Navigation';
import navigationRef from '@navigation/navigationRef';
import type {AuthScreensParamList, RootNavigatorParamList, State, WorkspaceSplitNavigatorParamList} from '@navigation/types';
import type {WorkspaceSplitNavigatorParamList} from '@navigation/types';
import BottomTabAvatar from '@pages/home/sidebar/BottomTabAvatar';
import variables from '@styles/variables';
import CONST from '@src/CONST';
Expand All @@ -44,34 +42,6 @@ type BottomTabBarProps = {
isTooltipAllowed?: boolean;
};

/**
* Returns SearchQueryString that has policyID correctly set.
*
* When we're coming back to Search Screen we might have pre-existing policyID inside SearchQuery.
* There are 2 cases when we might want to remove this `policyID`:
* - if Policy was removed in another screen
* - if WorkspaceSwitcher was used to globally unset a policyID
* Otherwise policyID will be inserted into query
*/
function handleQueryWithPolicyID(query: SearchQueryString, activePolicyID?: string): SearchQueryString {
const queryJSON = buildSearchQueryJSON(query);
if (!queryJSON) {
return query;
}

const policyID = activePolicyID ?? queryJSON.policyID;
const policy = getPolicy(policyID);

// In case policy is missing or there is no policy currently selected via WorkspaceSwitcher we remove it
if (!activePolicyID || !policy) {
delete queryJSON.policyID;
} else {
queryJSON.policyID = policyID;
}

return buildSearchQueryString(queryJSON);
}

function BottomTabBar({selectedTab, isTooltipAllowed = false}: BottomTabBarProps) {
const {isCreateMenuActive, toggleCreateMenu, fabRef} = useContext(FABPopoverContext);
const theme = useTheme();
Expand Down Expand Up @@ -110,22 +80,6 @@ function BottomTabBar({selectedTab, isTooltipAllowed = false}: BottomTabBarProps
}
clearSelectedText();
interceptAnonymousUser(() => {
const rootState = navigationRef.getRootState() as State<RootNavigatorParamList>;
const lastSearchRoute = rootState.routes.findLast((route) => route.name === SCREENS.SEARCH.ROOT);

if (lastSearchRoute) {
const {q, ...rest} = lastSearchRoute.params as AuthScreensParamList[typeof SCREENS.SEARCH.ROOT];
const cleanedQuery = handleQueryWithPolicyID(q, activeWorkspaceID);

Navigation.navigate(
ROUTES.SEARCH_ROOT.getRoute({
query: cleanedQuery,
...rest,
}),
);
return;
}

const defaultCannedQuery = 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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {SIDEBAR_TO_SPLIT} from '@libs/Navigation/linkingConfig/RELATIONS';
import NAVIGATORS from '@src/NAVIGATORS';
import SCREENS from '@src/SCREENS';

const SCREENS_WITH_BOTTOM_TAB_BAR = [...Object.keys(SIDEBAR_TO_SPLIT), SCREENS.SEARCH.ROOT, SCREENS.SETTINGS.WORKSPACES];
const SCREENS_WITH_BOTTOM_TAB_BAR = [...Object.keys(SIDEBAR_TO_SPLIT), NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR, SCREENS.SETTINGS.WORKSPACES];

export default SCREENS_WITH_BOTTOM_TAB_BAR;
4 changes: 2 additions & 2 deletions src/components/Search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
} from '@libs/SearchUIUtils';
import {isOnHold} from '@libs/TransactionUtils';
import Navigation from '@navigation/Navigation';
import type {AuthScreensParamList} from '@navigation/types';
import type {SearchFullscreenNavigatorParamList} from '@navigation/types';
import EmptySearchView from '@pages/Search/EmptySearchView';
import variables from '@styles/variables';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -133,7 +133,7 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo
// We need to use isSmallScreenWidth instead of shouldUseNarrowLayout for enabling the selection mode on small screens only
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth, isLargeScreenWidth} = useResponsiveLayout();
const navigation = useNavigation<PlatformStackNavigationProp<AuthScreensParamList>>();
const navigation = useNavigation<PlatformStackNavigationProp<SearchFullscreenNavigatorParamList>>();
const isFocused = useIsFocused();
const [lastNonEmptySearchResults, setLastNonEmptySearchResults] = useState<SearchResults | undefined>(undefined);
const {
Expand Down
10 changes: 4 additions & 6 deletions src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import NetworkConnection from '@libs/NetworkConnection';
import onyxSubscribe from '@libs/onyxSubscribe';
import Pusher from '@libs/Pusher';
import PusherConnectionManager from '@libs/PusherConnectionManager';
import * as SearchQueryUtils from '@libs/SearchQueryUtils';
import * as SessionUtils from '@libs/SessionUtils';
import ConnectionCompletePage from '@pages/ConnectionCompletePage';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
Expand Down Expand Up @@ -97,7 +96,7 @@ const loadWorkspaceJoinUser = () => require<ReactComponentModule>('@pages/worksp
const loadReportSplitNavigator = () => require<ReactComponentModule>('./Navigators/ReportsSplitNavigator').default;
const loadSettingsSplitNavigator = () => require<ReactComponentModule>('./Navigators/SettingsSplitNavigator').default;
const loadWorkspaceSplitNavigator = () => require<ReactComponentModule>('./Navigators/WorkspaceSplitNavigator').default;
const loadSearchPage = () => require<ReactComponentModule>('@pages/Search/SearchPage').default;
const loadSearchNavigator = () => require<ReactComponentModule>('./Navigators/SearchFullscreenNavigator').default;

function initializePusher() {
return Pusher.init({
Expand Down Expand Up @@ -455,7 +454,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
return (
<ComposeProviders components={[OptionsListContextProvider, ActiveWorkspaceContextProvider, ReportIDsContextProvider, SearchContextProvider]}>
<RootStack.Navigator
persistentScreens={[NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, SCREENS.SEARCH.ROOT]}
persistentScreens={[NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR]}
// @ts-expect-error SidePane is a custom screen option that was added in a patch (when we migrate to react-navigation v7 we can use screenLayout instead)
screenOptions={{sidePane: SidePaneWithOverlay}}
>
Expand All @@ -471,10 +470,9 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
getComponent={loadSettingsSplitNavigator}
/>
<RootStack.Screen
name={SCREENS.SEARCH.ROOT}
name={NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR}
options={rootNavigatorScreenOptions.fullScreen}
getComponent={loadSearchPage}
initialParams={{q: SearchQueryUtils.buildSearchQueryString()}}
getComponent={loadSearchNavigator}
/>
<RootStack.Screen
name={NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import type {SearchFullscreenNavigatorParamList} from '@libs/Navigation/types';
import * as SearchQueryUtils from '@libs/SearchQueryUtils';
import createSearchFullscreenNavigator from '@navigation/AppNavigator/createSearchFullscreenNavigator';
import FreezeWrapper from '@navigation/AppNavigator/FreezeWrapper';
import useRootNavigatorScreenOptions from '@navigation/AppNavigator/useRootNavigatorScreenOptions';
import SCREENS from '@src/SCREENS';
import type ReactComponentModule from '@src/types/utils/ReactComponentModule';

const loadSearchPage = () => require<ReactComponentModule>('@pages/Search/SearchPage').default;
const loadSearchMoneyReportPage = () => require<ReactComponentModule>('@pages/Search/SearchMoneyRequestReportPage').default;

const Stack = createSearchFullscreenNavigator<SearchFullscreenNavigatorParamList>();

function SearchFullscreenNavigator() {
const rootNavigatorScreenOptions = useRootNavigatorScreenOptions();

return (
<FreezeWrapper>
<Stack.Navigator
screenOptions={rootNavigatorScreenOptions.fullScreen}
defaultCentralScreen={SCREENS.SEARCH.ROOT}
>
<Stack.Screen
name={SCREENS.SEARCH.ROOT}
getComponent={loadSearchPage}
initialParams={{q: SearchQueryUtils.buildSearchQueryString()}}
/>
<Stack.Screen
name={SCREENS.SEARCH.MONEY_REQUEST_REPORT}
getComponent={loadSearchMoneyReportPage}
/>
</Stack.Navigator>
</FreezeWrapper>
);
}

SearchFullscreenNavigator.displayName = 'SearchFullscreenNavigator';

export default SearchFullscreenNavigator;
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ function handleOpenWorkspaceSplitAction(
}

/**
* Handles the SWITCH_POLICY_ID action.
* Information about the currently selected policy can be found in the last ReportsSplitNavigator or Search_Root.
* As the policy can only be changed from Search or Inbox Tab, after changing the policy a new ReportsSplitNavigator or Search_Root with the changed policy has to be pushed to the navigation state.
* Handles the SWITCH_POLICY_ID action for `SearchFullscreenNavigator`.
* Information about the currently selected policy can be found in the last Search_Root.
* After user changes the policy while on Search, a new Search_Root with the changed policy inside query param has to be pushed to the navigation state.
*/
function handleSwitchPolicyID(
function handleSwitchPolicyIDFromSearchAction(
state: StackNavigationState<ParamListBase>,
action: SwitchPolicyIdActionType,
configOptions: RouterConfigOptions,
Expand Down Expand Up @@ -110,6 +110,23 @@ function handleSwitchPolicyID(
setActiveWorkspaceID(action.payload.policyID);
return stackRouter.getStateForAction(state, newAction, configOptions);
}
// We don't have other navigators that should handle switch policy action.
return null;
}

/**
* Handles the SWITCH_POLICY_ID action for `ReportsSplitNavigator`.
* Information about the currently selected policy can be found in the last ReportsSplitNavigator.
* After user changes the policy while on Inbox, a new ReportsSplitNavigator with the changed policy has to be pushed to the navigation state.
*/
function handleSwitchPolicyIDAction(
state: StackNavigationState<ParamListBase>,
action: SwitchPolicyIdActionType,
configOptions: RouterConfigOptions,
stackRouter: Router<StackNavigationState<ParamListBase>, CommonActions.Action | StackActionType>,
setActiveWorkspaceID: (workspaceID: string | undefined) => void,
) {
const lastRoute = state.routes.at(-1);
if (lastRoute?.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR) {
const newAction = StackActions.push(NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, {policyID: action.payload.policyID});

Expand Down Expand Up @@ -182,37 +199,42 @@ function handlePushSearchPageAction(
stackRouter: Router<StackNavigationState<ParamListBase>, CommonActions.Action | StackActionType>,
setActiveWorkspaceID: (workspaceID: string | undefined) => void,
) {
const currentParams = action.payload.params as RootNavigatorParamList[typeof SCREENS.SEARCH.ROOT];
const queryJSON = SearchQueryUtils.buildSearchQueryJSON(currentParams.q);

if (!queryJSON) {
return null;
}
let updatedAction = action;
const currentParams = action.payload.params as RootNavigatorParamList[typeof NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR];
if (currentParams?.screen === SCREENS.SEARCH.ROOT) {
const searchParams = currentParams?.params;
const queryJSON = SearchQueryUtils.buildSearchQueryJSON(searchParams.q);
if (!queryJSON) {
return null;
}

if (!queryJSON.policyID) {
const policyID = getPolicyIDFromState(state as State<RootNavigatorParamList>);
if (!queryJSON.policyID) {
const policyID = getPolicyIDFromState(state as State<RootNavigatorParamList>);

if (policyID) {
queryJSON.policyID = policyID;
if (policyID) {
queryJSON.policyID = policyID;
} else {
delete queryJSON.policyID;
}
} else {
delete queryJSON.policyID;
setActiveWorkspaceID(queryJSON.policyID);
}
} else {
setActiveWorkspaceID(queryJSON.policyID);
}

const modifiedAction = {
...action,
payload: {
...action.payload,
params: {
...action.payload.params,
q: SearchQueryUtils.buildSearchQueryString(queryJSON),
updatedAction = {
...action,
payload: {
...action.payload,
params: {
...action.payload.params,
params: {
q: SearchQueryUtils.buildSearchQueryString(queryJSON),
},
},
},
},
};
};
}

return stackRouter.getStateForAction(state, modifiedAction, configOptions);
return stackRouter.getStateForAction(state, updatedAction, configOptions);
}

/**
Expand Down Expand Up @@ -253,7 +275,8 @@ export {
handleDismissModalAction,
handlePushReportSplitAction,
handlePushSearchPageAction,
handleSwitchPolicyID,
handleSwitchPolicyIDAction,
handleSwitchPolicyIDFromSearchAction,
handleNavigatingToModalFromModal,
workspaceSplitsWithoutEnteringAnimation,
reportsSplitsWithEnteringAnimation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ import isSideModalNavigator from '@libs/Navigation/helpers/isSideModalNavigator'
import * as Welcome from '@userActions/Welcome';
import CONST from '@src/CONST';
import NAVIGATORS from '@src/NAVIGATORS';
import SCREENS from '@src/SCREENS';
import * as GetStateForActionHandlers from './GetStateForActionHandlers';
import {
handleDismissModalAction,
handleNavigatingToModalFromModal,
handleOpenWorkspaceSplitAction,
handlePushReportSplitAction,
handlePushSearchPageAction,
handleSwitchPolicyIDAction,
} from './GetStateForActionHandlers';
import syncBrowserHistory from './syncBrowserHistory';
import type {DismissModalActionType, OpenWorkspaceSplitActionType, PushActionType, RootStackNavigatorAction, RootStackNavigatorRouterOptions, SwitchPolicyIdActionType} from './types';

Expand Down Expand Up @@ -65,24 +71,24 @@ function RootStackRouter(options: RootStackNavigatorRouterOptions) {
...stackRouter,
getStateForAction(state: StackNavigationState<ParamListBase>, action: RootStackNavigatorAction, configOptions: RouterConfigOptions) {
if (isOpenWorkspaceSplitAction(action)) {
return GetStateForActionHandlers.handleOpenWorkspaceSplitAction(state, action, configOptions, stackRouter);
return handleOpenWorkspaceSplitAction(state, action, configOptions, stackRouter);
}

if (isSwitchPolicyIdAction(action)) {
return GetStateForActionHandlers.handleSwitchPolicyID(state, action, configOptions, stackRouter, setActiveWorkspaceID);
return handleSwitchPolicyIDAction(state, action, configOptions, stackRouter, setActiveWorkspaceID);
}

if (isDismissModalAction(action)) {
return GetStateForActionHandlers.handleDismissModalAction(state, configOptions, stackRouter);
return handleDismissModalAction(state, configOptions, stackRouter);
}

if (isPushAction(action)) {
if (action.payload.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR) {
return GetStateForActionHandlers.handlePushReportSplitAction(state, action, configOptions, stackRouter, setActiveWorkspaceID);
return handlePushReportSplitAction(state, action, configOptions, stackRouter, setActiveWorkspaceID);
}

if (action.payload.name === SCREENS.SEARCH.ROOT) {
return GetStateForActionHandlers.handlePushSearchPageAction(state, action, configOptions, stackRouter, setActiveWorkspaceID);
if (action.payload.name === NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR) {
return handlePushSearchPageAction(state, action, configOptions, stackRouter, setActiveWorkspaceID);
}
}

Expand All @@ -93,7 +99,7 @@ function RootStackRouter(options: RootStackNavigatorRouterOptions) {
}

if (isNavigatingToModalFromModal(state, action)) {
return GetStateForActionHandlers.handleNavigatingToModalFromModal(state, action, configOptions, stackRouter);
return handleNavigatingToModalFromModal(state, action, configOptions, stackRouter);
}

return stackRouter.getStateForAction(state, action, configOptions);
Expand Down
Loading
Loading