From 8ab23f4922ae142b9495251acfaa00830e278efa Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Fri, 16 Jan 2026 18:02:19 +0100 Subject: [PATCH 01/16] Allow creating time expenses from global menu --- src/ROUTES.ts | 5 + src/SCREENS.ts | 1 + .../MoneyRequestConfirmationListFooter.tsx | 2 +- src/libs/IOUUtils.ts | 3 + .../ModalStackNavigators/index.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 1 + src/libs/Navigation/types.ts | 7 + src/libs/PolicyUtils.ts | 5 + src/libs/actions/IOU/index.ts | 3 +- src/pages/iou/request/IOURequestStartPage.tsx | 27 +++- .../step/IOURequestStepConfirmation.tsx | 4 - .../iou/request/step/IOURequestStepHours.tsx | 55 +++++-- .../step/IOURequestStepTimeWorkspace.tsx | 142 ++++++++++++++++++ .../step/withFullTransactionOrNotFound.tsx | 3 +- .../step/withWritableReportOrNotFound.tsx | 3 +- 15 files changed, 239 insertions(+), 23 deletions(-) create mode 100644 src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 1799b9078b2ba..63d7c652f920c 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1318,6 +1318,11 @@ const ROUTES = { getRoute: (action: IOUAction, iouType: IOUType, transactionID: string | undefined, reportID: string | undefined, reportActionID?: string) => `${action as string}/${iouType as string}/hours/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}` as const, }, + MONEY_REQUEST_STEP_HOURS_EDIT: { + route: ':action/:iouType/hours-edit/:transactionID/:reportID/:reportActionID?', + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string | undefined, reportID: string | undefined, reportActionID?: string) => + `${action as string}/${iouType as string}/hours-edit/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}/edit` as const, + }, DISTANCE_REQUEST_CREATE: { route: ':action/:iouType/start/:transactionID/:reportID/distance-new/:backToReport?', getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backToReport?: string) => diff --git a/src/SCREENS.ts b/src/SCREENS.ts index c6f861e0666db..c29aca43251f8 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -334,6 +334,7 @@ const SCREENS = { RECEIPT_PREVIEW: 'Money_Request_Receipt_preview', STEP_TIME_RATE: 'Money_Request_Step_Time_Rate', STEP_HOURS: 'Money_Request_Step_Hours', + STEP_HOURS_EDIT: 'Money_Request_Step_Hours_Edit', }, TRANSACTION_DUPLICATE: { diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index f59abf501e258..ae970c800a490 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -610,7 +610,7 @@ function MoneyRequestConfirmationListFooter({ if (!transactionID) { return; } - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, transactionID, reportID, reportActionID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS_EDIT.getRoute(action, iouType, transactionID, reportID, reportActionID)); }} disabled={didConfirm} interactive={!isReadOnly} diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index 04d6f7baebf2c..7a3cfc99be989 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -38,6 +38,9 @@ function navigateToStartMoneyRequestStep(requestType: IOURequestType, iouType: I case CONST.IOU.REQUEST_TYPE.SCAN: Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID), {compareParams: false}); break; + case CONST.IOU.REQUEST_TYPE.TIME: + Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_TIME.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID), {compareParams: false}); + break; default: Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_MANUAL.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID), {compareParams: false}); break; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 5041594252a02..a4cd0ab0fb4ea 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -187,6 +187,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../../pages/SetDefaultWorkspacePage').default, [SCREENS.MONEY_REQUEST.STEP_TIME_RATE]: () => require('../../../../pages/iou/request/step/IOURequestStepTimeRate').default, [SCREENS.MONEY_REQUEST.STEP_HOURS]: () => require('../../../../pages/iou/request/step/IOURequestStepHours').default, + [SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT]: () => require('../../../../pages/iou/request/step/IOURequestStepHours').default, }); const TravelModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index caf20777d7be9..43d06bb24618e 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1525,6 +1525,7 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.STEP_SUBRATE_EDIT]: ROUTES.MONEY_REQUEST_STEP_SUBRATE_EDIT.route, [SCREENS.MONEY_REQUEST.STEP_TIME_RATE]: ROUTES.MONEY_REQUEST_STEP_TIME_RATE.route, [SCREENS.MONEY_REQUEST.STEP_HOURS]: ROUTES.MONEY_REQUEST_STEP_HOURS.route, + [SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT]: ROUTES.MONEY_REQUEST_STEP_HOURS_EDIT.route, [SCREENS.IOU_SEND.ENABLE_PAYMENTS]: ROUTES.IOU_SEND_ENABLE_PAYMENTS, [SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: ROUTES.IOU_SEND_ADD_BANK_ACCOUNT, [SCREENS.IOU_SEND.ADD_DEBIT_CARD]: ROUTES.IOU_SEND_ADD_DEBIT_CARD, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 04fa0a4037898..d264fbfcd0ffb 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1958,6 +1958,13 @@ type MoneyRequestNavigatorParamList = { reportID: string; reportActionID: string; }; + [SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT]: { + action: IOUAction; + iouType: Exclude; + transactionID: string; + reportID: string; + reportActionID: string; + }; }; type WorkspaceConfirmationNavigatorParamList = { diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 203735d086d57..8a580fdb00e6f 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -107,6 +107,10 @@ function getActivePoliciesWithExpenseChatAndPerDiemEnabledAndHasRates(policies: }); } +function getActivePoliciesWithExpenseChatAndTimeEnabled(policies: OnyxCollection | null, currentUserLogin: string | undefined): Policy[] { + return getActivePoliciesWithExpenseChat(policies, currentUserLogin).filter((policy) => isTimeTrackingEnabled(policy)); +} + /** * Checks if the current user is an admin of the policy. */ @@ -1846,6 +1850,7 @@ export { isDefaultTagName, isTimeTrackingEnabled, getDefaultTimeTrackingRate, + getActivePoliciesWithExpenseChatAndTimeEnabled, }; export type {MemberEmailsToAccountIDs}; diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 1a1d6e797eb15..faecdc9f4fdcb 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -1329,12 +1329,13 @@ function setMoneyRequestReimbursable(transactionID: string, reimbursable: boolea Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {reimbursable}); } -function setMoneyRequestParticipants(transactionID: string, participants: Participant[] = [], isTestTransaction = false) { +function setMoneyRequestParticipants(transactionID: string, participants: Participant[] = [], isTestTransaction = false, participantsAutoAssigned?: boolean) { // We should change the reportID and isFromGlobalCreate of the test transaction since this flow can start inside an existing report return Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, { participants, isFromGlobalCreate: isTestTransaction ? true : undefined, reportID: isTestTransaction ? participants?.at(0)?.reportID : undefined, + participantsAutoAssigned, }); } diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index 5e067196a4fde..97e53ec738834 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -29,6 +29,7 @@ import {getIsUserSubmittedExpenseOrScannedReceipt} from '@libs/OptionsListUtils' import Performance from '@libs/Performance'; import { getActivePoliciesWithExpenseChatAndPerDiemEnabledAndHasRates, + getActivePoliciesWithExpenseChatAndTimeEnabled, getPerDiemCustomUnit, hasOnlyPersonalPolicies as hasOnlyPersonalPoliciesUtil, isTimeTrackingEnabled, @@ -50,6 +51,7 @@ import IOURequestStepDistance from './step/IOURequestStepDistance'; import IOURequestStepHours from './step/IOURequestStepHours'; import IOURequestStepPerDiemWorkspace from './step/IOURequestStepPerDiemWorkspace'; import IOURequestStepScan from './step/IOURequestStepScan'; +import IOURequestStepTimeWorkspace from './step/IOURequestStepTimeWorkspace'; import type {WithWritableReportOrNotFoundProps} from './step/withWritableReportOrNotFound'; type IOURequestStartPageProps = WithWritableReportOrNotFoundProps & { @@ -111,6 +113,10 @@ function IOURequestStartPage({ () => getActivePoliciesWithExpenseChatAndPerDiemEnabledAndHasRates(allPolicies, currentUserPersonalDetails.login), [allPolicies, currentUserPersonalDetails.login], ); + const policiesWithTimeEnabled = useMemo( + () => getActivePoliciesWithExpenseChatAndTimeEnabled(allPolicies, currentUserPersonalDetails.login), + [allPolicies, currentUserPersonalDetails.login], + ); const doesPerDiemPolicyExist = policiesWithPerDiemEnabledAndHasRates.length > 0; const moreThanOnePerDiemExist = policiesWithPerDiemEnabledAndHasRates.length > 1; const hasCurrentPolicyPerDiemEnabled = !!policy?.arePerDiemRatesEnabled; @@ -247,7 +253,10 @@ function IOURequestStartPage({ }, }, ); - const shouldShowTimeOption = isBetaEnabled(CONST.BETAS.TIME_TRACKING) && iouType === CONST.IOU.TYPE.SUBMIT && !isFromGlobalCreate && hasCurrentPolicyTimeTrackingEnabled; + const shouldShowTimeOption = + isBetaEnabled(CONST.BETAS.TIME_TRACKING) && + (iouType === CONST.IOU.TYPE.SUBMIT || iouType === CONST.IOU.TYPE.CREATE) && + ((!isFromGlobalCreate && hasCurrentPolicyTimeTrackingEnabled) || (isFromGlobalCreate && !!policiesWithTimeEnabled.length)); const onBackButtonPress = () => { navigateBack(); @@ -360,10 +369,18 @@ function IOURequestStartPage({ {() => ( - + {isFromGlobalCreate && policiesWithTimeEnabled.length > 1 ? ( + + ) : ( + 1 ? undefined : policiesWithTimeEnabled.at(0)?.id} + /> + )} )} diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index a7baca130b30e..fd63ef7e5229b 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -434,9 +434,6 @@ function IOURequestStepConfirmation({ Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_SUBRATE.getRoute(action, iouType, initialTransactionID, reportID)); return; } - if (isTimeRequest) { - return Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_TIME.getRoute(action, iouType, initialTransactionID, reportID)); - } if (transaction?.isFromGlobalCreate && !transaction.receipt?.isTestReceipt) { // If the participants weren't automatically added to the transaction, then we should go back to the IOURequestStepParticipants. @@ -478,7 +475,6 @@ function IOURequestStepConfirmation({ isMovingTransactionFromTrackExpense, participantsAutoAssignedFromRoute, backTo, - isTimeRequest, ]); // When the component mounts, if there is a receipt, see if the image can be read from the disk. If not, redirect the user to the starting step of the flow. diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index 9b99eb3ccb0fc..70e5d87049f39 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -14,9 +14,17 @@ import {canUseTouchScreen as canUseTouchScreenUtil} from '@libs/DeviceCapabiliti import {shouldUseTransactionDraft} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; +import {getPolicyExpenseChat} from '@libs/ReportUtils'; import {computeTimeAmount, formatTimeMerchant} from '@libs/TimeTrackingUtils'; import variables from '@styles/variables'; -import {setMoneyRequestAmount, setMoneyRequestMerchant, setMoneyRequestParticipantsFromReport, setMoneyRequestTimeCount, setMoneyRequestTimeRate} from '@userActions/IOU'; +import { + setMoneyRequestAmount, + setMoneyRequestMerchant, + setMoneyRequestParticipants, + setMoneyRequestParticipantsFromReport, + setMoneyRequestTimeCount, + setMoneyRequestTimeRate, +} from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -27,8 +35,12 @@ import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; -type IOURequestStepHoursProps = WithWritableReportOrNotFoundProps & - WithFullTransactionOrNotFoundProps; +type IOURequestStepHoursProps = WithWritableReportOrNotFoundProps< + typeof SCREENS.MONEY_REQUEST.CREATE | typeof SCREENS.MONEY_REQUEST.STEP_HOURS | typeof SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT +> & + WithFullTransactionOrNotFoundProps & { + explicitPolicyID?: string; + }; function IOURequestStepHours({ report, @@ -37,9 +49,11 @@ function IOURequestStepHours({ name: routeName, }, transaction, + explicitPolicyID, }: IOURequestStepHoursProps) { - const isEditingConfirmation = routeName === SCREENS.MONEY_REQUEST.STEP_HOURS; - const policyID = report?.policyID; + const isEditingConfirmation = routeName === SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT; + const isEmbeddedInStartPage = routeName === SCREENS.MONEY_REQUEST.CREATE; + const policyID = explicitPolicyID ?? report?.policyID; const isTransactionDraft = shouldUseTransactionDraft(action); const [selectedTab] = useOnyx(`${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.IOU_REQUEST_TYPE}`, {canBeMissing: true}); const {accountID} = useCurrentUserPersonalDetails(); @@ -77,7 +91,7 @@ function IOURequestStepHours({ const navigateBack = () => Navigation.goBack(isEditingConfirmation ? ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(action, iouType, transactionID, reportID) : undefined); - const saveTime = () => { + const saveTime = async () => { if (rate === undefined) { return; } @@ -96,10 +110,31 @@ function IOURequestStepHours({ navigateBack(); return; } + setMoneyRequestTimeRate(transactionID, rate, isTransactionDraft); - setMoneyRequestParticipantsFromReport(transactionID, report, accountID).then(() => - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)), - ); + + if (isEmbeddedInStartPage) { + if (explicitPolicyID) { + await setMoneyRequestParticipants( + transactionID, + [ + { + selected: true, + accountID: 0, + isPolicyExpenseChat: true, + reportID: getPolicyExpenseChat(accountID, explicitPolicyID)?.reportID, + policyID: explicitPolicyID, + }, + ], + false, + true, + ); + } else { + await setMoneyRequestParticipantsFromReport(transactionID, report, accountID); + } + } + + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); }; return ( @@ -107,7 +142,7 @@ function IOURequestStepHours({ headerTitle={translate('iou.time')} onBackButtonPress={navigateBack} testID="IOURequestStepHours" - shouldShowWrapper={isEditingConfirmation} + shouldShowWrapper={!isEmbeddedInStartPage} includeSafeAreaPaddingBottom shouldShowNotFoundPage={shouldShowNotFoundPage} > diff --git a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx new file mode 100644 index 0000000000000..e48d5a5cabff1 --- /dev/null +++ b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx @@ -0,0 +1,142 @@ +import React from 'react'; +import {View} from 'react-native'; +import SearchBar from '@components/SearchBar'; +import SelectionList from '@components/SelectionList'; +import type {ListItem} from '@components/SelectionList/ListItem/types'; +import UserListItem from '@components/SelectionList/ListItem/UserListItem'; +import Text from '@components/Text'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import useSearchResults from '@hooks/useSearchResults'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {shouldUseTransactionDraft} from '@libs/IOUUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import {getActivePoliciesWithExpenseChatAndTimeEnabled, getDefaultTimeTrackingRate, sortWorkspacesBySelected} from '@libs/PolicyUtils'; +import {getDefaultWorkspaceAvatar, getPolicyExpenseChat} from '@libs/ReportUtils'; +import tokenizedSearch from '@libs/tokenizedSearch'; +import {setMoneyRequestParticipants, setMoneyRequestTimeRate} from '@userActions/IOU'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; +import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound'; +import withWritableReportOrNotFound from './withWritableReportOrNotFound'; +import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; + +type WorkspaceListItem = ListItem & { + value: string; +}; + +type IOURequestStepTimeWorkspaceProps = WithWritableReportOrNotFoundProps & WithFullTransactionOrNotFoundProps; + +function IOURequestStepTimeWorkspace({ + route: { + params: {action, iouType, transactionID, reportID, reportActionID}, + }, + transaction, +}: IOURequestStepTimeWorkspaceProps) { + const icons = useMemoizedLazyExpensifyIcons(['FallbackWorkspaceAvatar']); + const styles = useThemeStyles(); + const {translate, localeCompare} = useLocalize(); + const isTransactionDraft = shouldUseTransactionDraft(action); + + const {login: currentUserLogin, accountID} = useCurrentUserPersonalDetails(); + const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); + const selectedWorkspace = transaction?.participants?.[0]; + const availableWorkspaces = getActivePoliciesWithExpenseChatAndTimeEnabled(allPolicies, currentUserLogin); + const workspaceOptions: WorkspaceListItem[] = availableWorkspaces + .sort((policy1, policy2) => + sortWorkspacesBySelected( + {policyID: policy1.id, name: policy1.name}, + {policyID: policy2.id, name: policy2.name}, + selectedWorkspace?.policyID ? [selectedWorkspace?.policyID] : [], + localeCompare, + ), + ) + .map((policy) => ({ + text: policy.name, + value: policy.id, + keyForList: policy.id, + icons: [ + { + id: policy.id, + source: policy?.avatarURL ? policy.avatarURL : getDefaultWorkspaceAvatar(policy.name), + fallbackIcon: icons.FallbackWorkspaceAvatar, + name: policy.name, + type: CONST.ICON_TYPE_WORKSPACE, + }, + ], + isSelected: selectedWorkspace?.policyID === policy.id, + })); + + const filterWorkspace = (workspaceOption: WorkspaceListItem, searchInput: string) => { + const results = tokenizedSearch([workspaceOption], searchInput, (option) => [option.text ?? '']); + return results.length > 0; + }; + const sortWorkspaces = (data: WorkspaceListItem[]) => data.sort((a, b) => localeCompare(a.text ?? '', b?.text ?? '')); + const [inputValue, setInputValue, filteredWorkspaceOptions] = useSearchResults(workspaceOptions, filterWorkspace, sortWorkspaces); + + const selectWorkspace = (item: WorkspaceListItem) => { + const policyExpenseReportID = getPolicyExpenseChat(accountID, item.value)?.reportID; + if (!policyExpenseReportID) { + return; + } + setMoneyRequestParticipants( + transactionID, + [ + { + selected: true, + accountID: 0, + isPolicyExpenseChat: true, + reportID: policyExpenseReportID, + policyID: item.value, + }, + ], + false, + true, + ); + + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.value}`]; + const defaultRate = policy ? getDefaultTimeTrackingRate(policy) : undefined; + if (defaultRate) { + setMoneyRequestTimeRate(transactionID, defaultRate, isTransactionDraft); + } + + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, transactionID, reportID, reportActionID)); + }; + + return ( + <> + {workspaceOptions.length > CONST.SEARCH_ITEM_LIMIT ? ( + 0 && filteredWorkspaceOptions.length === 0} + /> + ) : ( + + {translate('iou.chooseWorkspace')} + + )} + + + ); +} + +// eslint-disable-next-line rulesdir/no-negated-variables +const IOURequestStepTimeWorkspaceWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepTimeWorkspace); +// eslint-disable-next-line rulesdir/no-negated-variables +const IOURequestStepTimeWorkspaceWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepTimeWorkspaceWithWritableReportOrNotFound); + +export default IOURequestStepTimeWorkspaceWithFullTransactionOrNotFound; diff --git a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx index 1344153a0ae2f..4e5057b3307f5 100644 --- a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx +++ b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx @@ -53,7 +53,8 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_MANUAL | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_ODOMETER | typeof SCREENS.MONEY_REQUEST.STEP_TIME_RATE - | typeof SCREENS.MONEY_REQUEST.STEP_HOURS; + | typeof SCREENS.MONEY_REQUEST.STEP_HOURS + | typeof SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT; type WithFullTransactionOrNotFoundProps = WithFullTransactionOrNotFoundOnyxProps & PlatformStackScreenProps; diff --git a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx index 2c5c19a1ca847..7deb78ef5fcf7 100644 --- a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx +++ b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx @@ -57,7 +57,8 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_ODOMETER | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_MANUAL | typeof SCREENS.MONEY_REQUEST.STEP_TIME_RATE - | typeof SCREENS.MONEY_REQUEST.STEP_HOURS; + | typeof SCREENS.MONEY_REQUEST.STEP_HOURS + | typeof SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT; type WithWritableReportOrNotFoundProps = WithWritableReportOrNotFoundOnyxProps & PlatformStackScreenProps; From 34a3e84b3d1e3f53bfdf3c84ee1e9e45ccdecc32 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Mon, 19 Jan 2026 13:26:43 +0100 Subject: [PATCH 02/16] Refactor and fixes --- src/libs/PolicyUtils.ts | 2 +- src/libs/actions/IOU/index.ts | 28 +++ src/pages/iou/request/IOURequestStartPage.tsx | 2 +- .../request/step/BaseRequestStepWorkspace.tsx | 108 ++++++++++++ .../iou/request/step/IOURequestStepHours.tsx | 31 +--- .../step/IOURequestStepPerDiemWorkspace.tsx | 160 ++++-------------- .../step/IOURequestStepTimeWorkspace.tsx | 154 +++-------------- 7 files changed, 208 insertions(+), 277 deletions(-) create mode 100644 src/pages/iou/request/step/BaseRequestStepWorkspace.tsx diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 8a580fdb00e6f..3f5b6490e9f73 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -108,7 +108,7 @@ function getActivePoliciesWithExpenseChatAndPerDiemEnabledAndHasRates(policies: } function getActivePoliciesWithExpenseChatAndTimeEnabled(policies: OnyxCollection | null, currentUserLogin: string | undefined): Policy[] { - return getActivePoliciesWithExpenseChat(policies, currentUserLogin).filter((policy) => isTimeTrackingEnabled(policy)); + return getActivePoliciesWithExpenseChat(policies, currentUserLogin).filter(isTimeTrackingEnabled); } /** diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index faecdc9f4fdcb..4ac9a8e004877 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -11,6 +11,7 @@ import type {SetRequired, ValueOf} from 'type-fest'; import ReceiptGeneric from '@assets/images/receipt-generic.png'; import type {PaymentMethod} from '@components/KYCWall/types'; import type {SearchContextProps, SearchQueryJSON} from '@components/Search/types'; +import {setTransactionReport} from '@libs/actions/Transaction'; import * as API from '@libs/API'; import type { AddReportApproverParams, @@ -150,6 +151,7 @@ import { getOutstandingChildRequest, getParsedComment, getPersonalDetailsForAccountID, + getPolicyExpenseChat, getReportNotificationPreference, getReportOrDraftReport, getReportRecipientAccountIDs, @@ -11830,6 +11832,31 @@ function setMoneyRequestParticipantsFromReport(transactionID: string, report: On }); } +/** + * Sets transaction's participant and reportID to the policy's expense chat. + */ +function setMoneyRequestParticipantAsPolicyExpenseChat(transactionID: string, policyID: string, currentUserAccountID: number, isDraft: boolean) { + const policyExpenseReportID = getPolicyExpenseChat(currentUserAccountID, policyID)?.reportID; + if (!policyExpenseReportID) { + return; + } + setTransactionReport(transactionID, {reportID: policyExpenseReportID}, isDraft); + return setMoneyRequestParticipants( + transactionID, + [ + { + selected: true, + accountID: 0, + isPolicyExpenseChat: true, + reportID: policyExpenseReportID, + policyID, + }, + ], + false, + true, + ); +} + function setMoneyRequestTaxRate(transactionID: string, taxCode: string | null) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {taxCode}); } @@ -14317,6 +14344,7 @@ export { getSearchOnyxUpdate, setMoneyRequestTimeRate, setMoneyRequestTimeCount, + setMoneyRequestParticipantAsPolicyExpenseChat, }; export type { GPSPoint as GpsPoint, diff --git a/src/pages/iou/request/IOURequestStartPage.tsx b/src/pages/iou/request/IOURequestStartPage.tsx index 97e53ec738834..5c42ae78cad88 100644 --- a/src/pages/iou/request/IOURequestStartPage.tsx +++ b/src/pages/iou/request/IOURequestStartPage.tsx @@ -378,7 +378,7 @@ function IOURequestStartPage({ 1 ? undefined : policiesWithTimeEnabled.at(0)?.id} + explicitPolicyID={isFromGlobalCreate ? policiesWithTimeEnabled.at(0)?.id : undefined} /> )} diff --git a/src/pages/iou/request/step/BaseRequestStepWorkspace.tsx b/src/pages/iou/request/step/BaseRequestStepWorkspace.tsx new file mode 100644 index 0000000000000..5299478bfa1f4 --- /dev/null +++ b/src/pages/iou/request/step/BaseRequestStepWorkspace.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import {View} from 'react-native'; +import type {OnyxCollection} from 'react-native-onyx'; +import SearchBar from '@components/SearchBar'; +import SelectionList from '@components/SelectionList'; +import type {ListItem} from '@components/SelectionList/ListItem/types'; +import UserListItem from '@components/SelectionList/ListItem/UserListItem'; +import Text from '@components/Text'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import useSearchResults from '@hooks/useSearchResults'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {sortWorkspacesBySelected} from '@libs/PolicyUtils'; +import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; +import tokenizedSearch from '@libs/tokenizedSearch'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import type {Policy} from '@src/types/onyx'; +import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound'; +import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; + +type WorkspaceListItem = ListItem & { + value: string; +}; + +type BaseRequestStepWorkspaceProps = WithFullTransactionOrNotFoundProps & { + /** Function returning available workspaces for the list. */ + getPolicies: (allPolicies: OnyxCollection, currentUserLogin: string | undefined) => Policy[]; + + /** Function to run after selecting a workspace. */ + onSelectWorkspace: (item: WorkspaceListItem, allPolicies: OnyxCollection) => void; +}; + +function BaseRequestStepWorkspace({transaction, getPolicies, onSelectWorkspace}: BaseRequestStepWorkspaceProps) { + const icons = useMemoizedLazyExpensifyIcons(['FallbackWorkspaceAvatar']); + const styles = useThemeStyles(); + const {translate, localeCompare} = useLocalize(); + + const {login: currentUserLogin} = useCurrentUserPersonalDetails(); + const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); + const selectedWorkspace = transaction?.participants?.[0]; + const availableWorkspaces = getPolicies(allPolicies, currentUserLogin); + const workspaceOptions: WorkspaceListItem[] = availableWorkspaces + .sort((policy1, policy2) => + sortWorkspacesBySelected( + {policyID: policy1.id, name: policy1.name}, + {policyID: policy2.id, name: policy2.name}, + selectedWorkspace?.policyID ? [selectedWorkspace?.policyID] : [], + localeCompare, + ), + ) + .map((policy) => ({ + text: policy.name, + value: policy.id, + keyForList: policy.id, + icons: [ + { + id: policy.id, + source: policy?.avatarURL ? policy.avatarURL : getDefaultWorkspaceAvatar(policy.name), + fallbackIcon: icons.FallbackWorkspaceAvatar, + name: policy.name, + type: CONST.ICON_TYPE_WORKSPACE, + }, + ], + isSelected: selectedWorkspace?.policyID === policy.id, + })); + + const filterWorkspace = (workspaceOption: WorkspaceListItem, searchInput: string) => { + const results = tokenizedSearch([workspaceOption], searchInput, (option) => [option.text ?? '']); + return results.length > 0; + }; + const sortWorkspaces = (data: WorkspaceListItem[]) => data.sort((a, b) => localeCompare(a.text ?? '', b?.text ?? '')); + const [inputValue, setInputValue, filteredWorkspaceOptions] = useSearchResults(workspaceOptions, filterWorkspace, sortWorkspaces); + + const selectWorkspace = (item: WorkspaceListItem) => { + onSelectWorkspace(item, allPolicies); + }; + + return ( + <> + {workspaceOptions.length > CONST.SEARCH_ITEM_LIMIT ? ( + 0 && filteredWorkspaceOptions.length === 0} + /> + ) : ( + + {translate('iou.chooseWorkspace')} + + )} + + + ); +} + +export default withFullTransactionOrNotFound(BaseRequestStepWorkspace); diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index 70e5d87049f39..f8d77d92fc80a 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -14,13 +14,12 @@ import {canUseTouchScreen as canUseTouchScreenUtil} from '@libs/DeviceCapabiliti import {shouldUseTransactionDraft} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; -import {getPolicyExpenseChat} from '@libs/ReportUtils'; import {computeTimeAmount, formatTimeMerchant} from '@libs/TimeTrackingUtils'; import variables from '@styles/variables'; import { setMoneyRequestAmount, setMoneyRequestMerchant, - setMoneyRequestParticipants, + setMoneyRequestParticipantAsPolicyExpenseChat, setMoneyRequestParticipantsFromReport, setMoneyRequestTimeCount, setMoneyRequestTimeRate, @@ -114,24 +113,9 @@ function IOURequestStepHours({ setMoneyRequestTimeRate(transactionID, rate, isTransactionDraft); if (isEmbeddedInStartPage) { - if (explicitPolicyID) { - await setMoneyRequestParticipants( - transactionID, - [ - { - selected: true, - accountID: 0, - isPolicyExpenseChat: true, - reportID: getPolicyExpenseChat(accountID, explicitPolicyID)?.reportID, - policyID: explicitPolicyID, - }, - ], - false, - true, - ); - } else { - await setMoneyRequestParticipantsFromReport(transactionID, report, accountID); - } + await (explicitPolicyID + ? setMoneyRequestParticipantAsPolicyExpenseChat(transactionID, explicitPolicyID, accountID, isTransactionDraft) + : setMoneyRequestParticipantsFromReport(transactionID, report, accountID)); } Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); @@ -180,9 +164,4 @@ function IOURequestStepHours({ ); } -// eslint-disable-next-line rulesdir/no-negated-variables -const IOURequestStepHoursWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepHours); -// eslint-disable-next-line rulesdir/no-negated-variables -const IOURequestStepHoursWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepHoursWithWritableReportOrNotFound); - -export default IOURequestStepHoursWithFullTransactionOrNotFound; +export default withFullTransactionOrNotFound(withWritableReportOrNotFound(IOURequestStepHours)); diff --git a/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx index 0f46578736485..7eb0a8b46bda8 100644 --- a/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx +++ b/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx @@ -1,138 +1,52 @@ -import React, {useCallback, useMemo} from 'react'; -import {View} from 'react-native'; -import SearchBar from '@components/SearchBar'; -import SelectionList from '@components/SelectionList'; -import type {ListItem} from '@components/SelectionList/ListItem/types'; -import UserListItem from '@components/SelectionList/ListItem/UserListItem'; -import Text from '@components/Text'; +import React from 'react'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; -import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; -import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; -import useSearchResults from '@hooks/useSearchResults'; -import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import {getActivePoliciesWithExpenseChatAndPerDiemEnabled, getPerDiemCustomUnit, sortWorkspacesBySelected} from '@libs/PolicyUtils'; -import {getDefaultWorkspaceAvatar, getPolicyExpenseChat} from '@libs/ReportUtils'; -import tokenizedSearch from '@libs/tokenizedSearch'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; +import {getActivePoliciesWithExpenseChatAndPerDiemEnabled, getPerDiemCustomUnit} from '@libs/PolicyUtils'; +import {getPolicyExpenseChat} from '@libs/ReportUtils'; import {setCustomUnitID, setMoneyRequestCategory, setMoneyRequestParticipants} from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; -import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound'; -import withWritableReportOrNotFound from './withWritableReportOrNotFound'; -import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; +import BaseRequestStepWorkspace from './BaseRequestStepWorkspace'; -type WorkspaceListItem = ListItem & { - value: string; -}; +type IOURequestStepPerDiemWorkspaceProps = PlatformStackScreenProps; -type IOURequestStepPerDiemWorkspaceProps = WithWritableReportOrNotFoundProps & WithFullTransactionOrNotFoundProps; +function IOURequestStepPerDiemWorkspace({route, navigation}: IOURequestStepPerDiemWorkspaceProps) { + const { + params: {action, iouType, transactionID}, + } = route; + const {accountID} = useCurrentUserPersonalDetails(); -function IOURequestStepPerDiemWorkspace({ - route: { - params: {transactionID, action, iouType}, - }, - transaction, -}: IOURequestStepPerDiemWorkspaceProps) { - const icons = useMemoizedLazyExpensifyIcons(['FallbackWorkspaceAvatar']); - const styles = useThemeStyles(); - const {translate, localeCompare} = useLocalize(); - const {login: currentUserLogin, accountID} = useCurrentUserPersonalDetails(); - const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); - - const selectedWorkspace = useMemo(() => transaction?.participants?.[0], [transaction]); - - const workspaceOptions: WorkspaceListItem[] = useMemo(() => { - const availableWorkspaces = getActivePoliciesWithExpenseChatAndPerDiemEnabled(allPolicies, currentUserLogin); - - return availableWorkspaces - .sort((policy1, policy2) => - sortWorkspacesBySelected( - {policyID: policy1.id, name: policy1.name}, - {policyID: policy2.id, name: policy2.name}, - selectedWorkspace?.policyID ? [selectedWorkspace?.policyID] : [], - localeCompare, - ), - ) - .map((policy) => ({ - text: policy.name, - value: policy.id, - keyForList: policy.id, - icons: [ + return ( + { + const policyExpenseReportID = getPolicyExpenseChat(accountID, item.value)?.reportID; + if (!policyExpenseReportID) { + return; + } + const selectedPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.value}`]; + const perDiemUnit = getPerDiemCustomUnit(selectedPolicy); + setMoneyRequestParticipants(transactionID, [ { - id: policy.id, - source: policy?.avatarURL ? policy.avatarURL : getDefaultWorkspaceAvatar(policy.name), - fallbackIcon: icons.FallbackWorkspaceAvatar, - name: policy.name, - type: CONST.ICON_TYPE_WORKSPACE, + selected: true, + accountID: 0, + isPolicyExpenseChat: true, + reportID: policyExpenseReportID, + policyID: item.value, }, - ], - isSelected: selectedWorkspace?.policyID === policy.id, - })); - }, [allPolicies, currentUserLogin, selectedWorkspace?.policyID, localeCompare, icons.FallbackWorkspaceAvatar]); - - const filterWorkspace = useCallback((workspaceOption: WorkspaceListItem, searchInput: string) => { - const results = tokenizedSearch([workspaceOption], searchInput, (option) => [option.text ?? '']); - return results.length > 0; - }, []); - - const sortWorkspaces = useCallback( - (data: WorkspaceListItem[]) => { - return data.sort((a, b) => localeCompare(a.text ?? '', b?.text ?? '')); - }, - [localeCompare], - ); - - const [inputValue, setInputValue, filteredWorkspaceOptions] = useSearchResults(workspaceOptions, filterWorkspace, sortWorkspaces); - - const selectWorkspace = (item: WorkspaceListItem) => { - const policyExpenseReportID = getPolicyExpenseChat(accountID, item.value)?.reportID; - if (!policyExpenseReportID) { - return; - } - const selectedPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.value}`]; - const perDiemUnit = getPerDiemCustomUnit(selectedPolicy); - setMoneyRequestParticipants(transactionID, [ - { - selected: true, - accountID: 0, - isPolicyExpenseChat: true, - reportID: policyExpenseReportID, - policyID: item.value, - }, - ]); - setCustomUnitID(transactionID, perDiemUnit?.customUnitID ?? CONST.CUSTOM_UNITS.FAKE_P2P_ID); - setMoneyRequestCategory(transactionID, perDiemUnit?.defaultCategory ?? '', undefined); - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DESTINATION.getRoute(action, iouType, transactionID, policyExpenseReportID)); - }; - - return ( - <> - {workspaceOptions.length > CONST.SEARCH_ITEM_LIMIT ? ( - 0 && filteredWorkspaceOptions.length === 0} - /> - ) : ( - - {translate('iou.chooseWorkspace')} - - )} - - + ]); + setCustomUnitID(transactionID, perDiemUnit?.customUnitID ?? CONST.CUSTOM_UNITS.FAKE_P2P_ID); + setMoneyRequestCategory(transactionID, perDiemUnit?.defaultCategory ?? '', undefined); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DESTINATION.getRoute(action, iouType, transactionID, policyExpenseReportID)); + }} + /> ); } -export default withWritableReportOrNotFound(withFullTransactionOrNotFound(IOURequestStepPerDiemWorkspace)); +export default IOURequestStepPerDiemWorkspace; diff --git a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx index e48d5a5cabff1..214cc1431ed06 100644 --- a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx +++ b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx @@ -1,142 +1,44 @@ import React from 'react'; -import {View} from 'react-native'; -import SearchBar from '@components/SearchBar'; -import SelectionList from '@components/SelectionList'; -import type {ListItem} from '@components/SelectionList/ListItem/types'; -import UserListItem from '@components/SelectionList/ListItem/UserListItem'; -import Text from '@components/Text'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; -import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; -import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; -import useSearchResults from '@hooks/useSearchResults'; -import useThemeStyles from '@hooks/useThemeStyles'; import {shouldUseTransactionDraft} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {getActivePoliciesWithExpenseChatAndTimeEnabled, getDefaultTimeTrackingRate, sortWorkspacesBySelected} from '@libs/PolicyUtils'; -import {getDefaultWorkspaceAvatar, getPolicyExpenseChat} from '@libs/ReportUtils'; -import tokenizedSearch from '@libs/tokenizedSearch'; -import {setMoneyRequestParticipants, setMoneyRequestTimeRate} from '@userActions/IOU'; -import CONST from '@src/CONST'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; +import {getActivePoliciesWithExpenseChatAndTimeEnabled, getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; +import {setMoneyRequestParticipantAsPolicyExpenseChat, setMoneyRequestTimeRate} from '@userActions/IOU'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; -import type {WithFullTransactionOrNotFoundProps} from './withFullTransactionOrNotFound'; -import withWritableReportOrNotFound from './withWritableReportOrNotFound'; -import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; +import BaseRequestStepWorkspace from './BaseRequestStepWorkspace'; -type WorkspaceListItem = ListItem & { - value: string; -}; +type IOURequestStepTimeWorkspaceProps = PlatformStackScreenProps; -type IOURequestStepTimeWorkspaceProps = WithWritableReportOrNotFoundProps & WithFullTransactionOrNotFoundProps; - -function IOURequestStepTimeWorkspace({ - route: { +function IOURequestStepTimeWorkspace({route, navigation}: IOURequestStepTimeWorkspaceProps) { + const { params: {action, iouType, transactionID, reportID, reportActionID}, - }, - transaction, -}: IOURequestStepTimeWorkspaceProps) { - const icons = useMemoizedLazyExpensifyIcons(['FallbackWorkspaceAvatar']); - const styles = useThemeStyles(); - const {translate, localeCompare} = useLocalize(); - const isTransactionDraft = shouldUseTransactionDraft(action); - - const {login: currentUserLogin, accountID} = useCurrentUserPersonalDetails(); - const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); - const selectedWorkspace = transaction?.participants?.[0]; - const availableWorkspaces = getActivePoliciesWithExpenseChatAndTimeEnabled(allPolicies, currentUserLogin); - const workspaceOptions: WorkspaceListItem[] = availableWorkspaces - .sort((policy1, policy2) => - sortWorkspacesBySelected( - {policyID: policy1.id, name: policy1.name}, - {policyID: policy2.id, name: policy2.name}, - selectedWorkspace?.policyID ? [selectedWorkspace?.policyID] : [], - localeCompare, - ), - ) - .map((policy) => ({ - text: policy.name, - value: policy.id, - keyForList: policy.id, - icons: [ - { - id: policy.id, - source: policy?.avatarURL ? policy.avatarURL : getDefaultWorkspaceAvatar(policy.name), - fallbackIcon: icons.FallbackWorkspaceAvatar, - name: policy.name, - type: CONST.ICON_TYPE_WORKSPACE, - }, - ], - isSelected: selectedWorkspace?.policyID === policy.id, - })); - - const filterWorkspace = (workspaceOption: WorkspaceListItem, searchInput: string) => { - const results = tokenizedSearch([workspaceOption], searchInput, (option) => [option.text ?? '']); - return results.length > 0; - }; - const sortWorkspaces = (data: WorkspaceListItem[]) => data.sort((a, b) => localeCompare(a.text ?? '', b?.text ?? '')); - const [inputValue, setInputValue, filteredWorkspaceOptions] = useSearchResults(workspaceOptions, filterWorkspace, sortWorkspaces); + } = route; - const selectWorkspace = (item: WorkspaceListItem) => { - const policyExpenseReportID = getPolicyExpenseChat(accountID, item.value)?.reportID; - if (!policyExpenseReportID) { - return; - } - setMoneyRequestParticipants( - transactionID, - [ - { - selected: true, - accountID: 0, - isPolicyExpenseChat: true, - reportID: policyExpenseReportID, - policyID: item.value, - }, - ], - false, - true, - ); - - const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.value}`]; - const defaultRate = policy ? getDefaultTimeTrackingRate(policy) : undefined; - if (defaultRate) { - setMoneyRequestTimeRate(transactionID, defaultRate, isTransactionDraft); - } - - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, transactionID, reportID, reportActionID)); - }; + const {accountID} = useCurrentUserPersonalDetails(); + const isTransactionDraft = shouldUseTransactionDraft(action); return ( - <> - {workspaceOptions.length > CONST.SEARCH_ITEM_LIMIT ? ( - 0 && filteredWorkspaceOptions.length === 0} - /> - ) : ( - - {translate('iou.chooseWorkspace')} - - )} - - + { + setMoneyRequestParticipantAsPolicyExpenseChat(transactionID, item.value, accountID, isTransactionDraft); + + const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.value}`]; + const defaultRate = policy ? getDefaultTimeTrackingRate(policy) : undefined; + if (defaultRate) { + setMoneyRequestTimeRate(transactionID, defaultRate, isTransactionDraft); + } + + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, transactionID, reportID, reportActionID)); + }} + /> ); } -// eslint-disable-next-line rulesdir/no-negated-variables -const IOURequestStepTimeWorkspaceWithWritableReportOrNotFound = withWritableReportOrNotFound(IOURequestStepTimeWorkspace); -// eslint-disable-next-line rulesdir/no-negated-variables -const IOURequestStepTimeWorkspaceWithFullTransactionOrNotFound = withFullTransactionOrNotFound(IOURequestStepTimeWorkspaceWithWritableReportOrNotFound); - -export default IOURequestStepTimeWorkspaceWithFullTransactionOrNotFound; +export default IOURequestStepTimeWorkspace; From 5eb620526ee4628bbc17fb614fa63afa0a2347b2 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Mon, 19 Jan 2026 15:18:35 +0100 Subject: [PATCH 03/16] Fix eslint --- src/libs/actions/IOU/index.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 147f38a8d56c6..4810838139f97 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -11,7 +11,6 @@ import type {SetRequired, ValueOf} from 'type-fest'; import ReceiptGeneric from '@assets/images/receipt-generic.png'; import type {PaymentMethod} from '@components/KYCWall/types'; import type {SearchContextProps, SearchQueryJSON} from '@components/Search/types'; -import {setTransactionReport} from '@libs/actions/Transaction'; import * as API from '@libs/API'; import type { AddReportApproverParams, @@ -1323,13 +1322,12 @@ function setMoneyRequestReimbursable(transactionID: string, reimbursable: boolea Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {reimbursable}); } -function setMoneyRequestParticipants(transactionID: string, participants: Participant[] = [], isTestTransaction = false, participantsAutoAssigned?: boolean) { +function setMoneyRequestParticipants(transactionID: string, participants: Participant[] = [], isTestTransaction = false) { // We should change the reportID and isFromGlobalCreate of the test transaction since this flow can start inside an existing report return Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, { participants, isFromGlobalCreate: isTestTransaction ? true : undefined, reportID: isTestTransaction ? participants?.at(0)?.reportID : undefined, - participantsAutoAssigned, }); } @@ -11929,10 +11927,9 @@ function setMoneyRequestParticipantAsPolicyExpenseChat(transactionID: string, po if (!policyExpenseReportID) { return; } - setTransactionReport(transactionID, {reportID: policyExpenseReportID}, isDraft); - return setMoneyRequestParticipants( - transactionID, - [ + + return Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { + participants: [ { selected: true, accountID: 0, @@ -11941,9 +11938,8 @@ function setMoneyRequestParticipantAsPolicyExpenseChat(transactionID: string, po policyID, }, ], - false, - true, - ); + participantsAutoAssigned: true, + }); } function setMoneyRequestTaxRate(transactionID: string, taxCode: string | null) { From 52d9c445c7c60123759109e3d072e9c880694e5a Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Mon, 19 Jan 2026 16:12:34 +0100 Subject: [PATCH 04/16] Fix setting reportID on transaction in setMoneyRequestParticipantAsPolicyExpenseChat --- src/libs/actions/IOU/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 4810838139f97..5ca37d4311b26 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -11929,6 +11929,7 @@ function setMoneyRequestParticipantAsPolicyExpenseChat(transactionID: string, po } return Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { + reportID: policyExpenseReportID, participants: [ { selected: true, From eb00d067f73867b8e0d448f870da544203b56306 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 20 Jan 2026 11:38:36 +0100 Subject: [PATCH 05/16] Implement review suggestions --- src/ROUTES.ts | 2 +- src/libs/actions/IOU/index.ts | 18 +++++++++++++++--- .../iou/request/step/IOURequestStepHours.tsx | 14 +++++++++++--- .../step/IOURequestStepTimeWorkspace.tsx | 8 +++++++- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 63d7c652f920c..1b196467a71dc 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1321,7 +1321,7 @@ const ROUTES = { MONEY_REQUEST_STEP_HOURS_EDIT: { route: ':action/:iouType/hours-edit/:transactionID/:reportID/:reportActionID?', getRoute: (action: IOUAction, iouType: IOUType, transactionID: string | undefined, reportID: string | undefined, reportActionID?: string) => - `${action as string}/${iouType as string}/hours-edit/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}/edit` as const, + `${action as string}/${iouType as string}/hours-edit/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}` as const, }, DISTANCE_REQUEST_CREATE: { route: ':action/:iouType/start/:transactionID/:reportID/distance-new/:backToReport?', diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 26d3702a1b579..5602c07d77477 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -11922,13 +11922,25 @@ function setMoneyRequestParticipantsFromReport(transactionID: string, report: On /** * Sets transaction's participant and reportID to the policy's expense chat. */ -function setMoneyRequestParticipantAsPolicyExpenseChat(transactionID: string, policyID: string, currentUserAccountID: number, isDraft: boolean) { +function setMoneyRequestParticipantAsPolicyExpenseChat({ + transactionID, + policyID, + currentUserAccountID, + isDraft, + participantsAutoAssigned, +}: { + transactionID: string; + policyID: string; + currentUserAccountID: number; + isDraft: boolean; + participantsAutoAssigned?: boolean; +}) { const policyExpenseReportID = getPolicyExpenseChat(currentUserAccountID, policyID)?.reportID; if (!policyExpenseReportID) { return; } - return Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { + Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { reportID: policyExpenseReportID, participants: [ { @@ -11939,7 +11951,7 @@ function setMoneyRequestParticipantAsPolicyExpenseChat(transactionID: string, po policyID, }, ], - participantsAutoAssigned: true, + participantsAutoAssigned, }); } diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index f8d77d92fc80a..0f23331132475 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -113,9 +113,17 @@ function IOURequestStepHours({ setMoneyRequestTimeRate(transactionID, rate, isTransactionDraft); if (isEmbeddedInStartPage) { - await (explicitPolicyID - ? setMoneyRequestParticipantAsPolicyExpenseChat(transactionID, explicitPolicyID, accountID, isTransactionDraft) - : setMoneyRequestParticipantsFromReport(transactionID, report, accountID)); + if (explicitPolicyID) { + setMoneyRequestParticipantAsPolicyExpenseChat({ + transactionID, + policyID: explicitPolicyID, + currentUserAccountID: accountID, + isDraft: isTransactionDraft, + participantsAutoAssigned: true, + }); + } else { + setMoneyRequestParticipantsFromReport(transactionID, report, accountID); + } } Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); diff --git a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx index 214cc1431ed06..f5c253c0aa9c9 100644 --- a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx +++ b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx @@ -27,7 +27,13 @@ function IOURequestStepTimeWorkspace({route, navigation}: IOURequestStepTimeWork navigation={navigation} getPolicies={getActivePoliciesWithExpenseChatAndTimeEnabled} onSelectWorkspace={(item, allPolicies) => { - setMoneyRequestParticipantAsPolicyExpenseChat(transactionID, item.value, accountID, isTransactionDraft); + setMoneyRequestParticipantAsPolicyExpenseChat({ + transactionID, + policyID: item.value, + currentUserAccountID: accountID, + isDraft: isTransactionDraft, + participantsAutoAssigned: true, + }); const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.value}`]; const defaultRate = policy ? getDefaultTimeTrackingRate(policy) : undefined; From f94caa3913428e77120aa23083eda07c98ba8288 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 20 Jan 2026 11:43:00 +0100 Subject: [PATCH 06/16] Remove async from saveTime declaration --- src/pages/iou/request/step/IOURequestStepHours.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index 0f23331132475..a6f8618ea5b67 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -90,7 +90,7 @@ function IOURequestStepHours({ const navigateBack = () => Navigation.goBack(isEditingConfirmation ? ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(action, iouType, transactionID, reportID) : undefined); - const saveTime = async () => { + const saveTime = () => { if (rate === undefined) { return; } From 97a75f2b761b4d37ddaf41ded167e9d888abce69 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 20 Jan 2026 12:15:42 +0100 Subject: [PATCH 07/16] Fix reportID --- src/libs/actions/IOU/index.ts | 13 ++++++------- src/pages/iou/request/step/IOURequestStepHours.tsx | 6 +++--- .../request/step/IOURequestStepTimeWorkspace.tsx | 6 +++--- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 5602c07d77477..88de24b3eea60 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -11921,6 +11921,7 @@ function setMoneyRequestParticipantsFromReport(transactionID: string, report: On /** * Sets transaction's participant and reportID to the policy's expense chat. + * Returns the policy expense chat reportID. */ function setMoneyRequestParticipantAsPolicyExpenseChat({ transactionID, @@ -11935,24 +11936,22 @@ function setMoneyRequestParticipantAsPolicyExpenseChat({ isDraft: boolean; participantsAutoAssigned?: boolean; }) { - const policyExpenseReportID = getPolicyExpenseChat(currentUserAccountID, policyID)?.reportID; - if (!policyExpenseReportID) { - return; - } - + const policyExpenseChatReportID = getPolicyExpenseChat(currentUserAccountID, policyID)?.reportID; Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { - reportID: policyExpenseReportID, + reportID: policyExpenseChatReportID, participants: [ { selected: true, accountID: 0, isPolicyExpenseChat: true, - reportID: policyExpenseReportID, + reportID: policyExpenseChatReportID, policyID, }, ], participantsAutoAssigned, }); + + return policyExpenseChatReportID; } function setMoneyRequestTaxRate(transactionID: string, taxCode: string | null) { diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index a6f8618ea5b67..146c747d60909 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -114,16 +114,16 @@ function IOURequestStepHours({ if (isEmbeddedInStartPage) { if (explicitPolicyID) { - setMoneyRequestParticipantAsPolicyExpenseChat({ + const policyExpenseChatReportID = setMoneyRequestParticipantAsPolicyExpenseChat({ transactionID, policyID: explicitPolicyID, currentUserAccountID: accountID, isDraft: isTransactionDraft, participantsAutoAssigned: true, }); - } else { - setMoneyRequestParticipantsFromReport(transactionID, report, accountID); + return Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, policyExpenseChatReportID)); } + setMoneyRequestParticipantsFromReport(transactionID, report, accountID); } Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)); diff --git a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx index f5c253c0aa9c9..1f76c205bc7e1 100644 --- a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx +++ b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx @@ -15,7 +15,7 @@ type IOURequestStepTimeWorkspaceProps = PlatformStackScreenProps { - setMoneyRequestParticipantAsPolicyExpenseChat({ + const policyExpenseChatReportID = setMoneyRequestParticipantAsPolicyExpenseChat({ transactionID, policyID: item.value, currentUserAccountID: accountID, @@ -41,7 +41,7 @@ function IOURequestStepTimeWorkspace({route, navigation}: IOURequestStepTimeWork setMoneyRequestTimeRate(transactionID, defaultRate, isTransactionDraft); } - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, transactionID, reportID, reportActionID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, transactionID, policyExpenseChatReportID)); }} /> ); From f35e93e3dc2fd903f46f9bfd1be3fbd9ef27e49e Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 20 Jan 2026 13:03:22 +0100 Subject: [PATCH 08/16] Fix navigation on iOS when one workspace available in global time tab --- src/pages/iou/request/step/IOURequestStepHours.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index 146c747d60909..c7508cc3b4b02 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -121,7 +121,9 @@ function IOURequestStepHours({ isDraft: isTransactionDraft, participantsAutoAssigned: true, }); - return Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, policyExpenseChatReportID)); + return Navigation.setNavigationActionToMicrotaskQueue(() => + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, policyExpenseChatReportID)), + ); } setMoneyRequestParticipantsFromReport(transactionID, report, accountID); } From a2579fa7296e873112290567970bf0b1cc7cb172 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 20 Jan 2026 14:46:42 +0100 Subject: [PATCH 09/16] Implement AI reviewer's suggestions --- src/libs/actions/IOU/index.ts | 4 ++++ src/pages/iou/request/step/IOURequestStepHours.tsx | 2 +- src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 88de24b3eea60..7cfcd8480f776 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -11937,6 +11937,10 @@ function setMoneyRequestParticipantAsPolicyExpenseChat({ participantsAutoAssigned?: boolean; }) { const policyExpenseChatReportID = getPolicyExpenseChat(currentUserAccountID, policyID)?.reportID; + if (!policyExpenseChatReportID) { + return; + } + Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { reportID: policyExpenseChatReportID, participants: [ diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index c7508cc3b4b02..f46a544ce67c0 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -122,7 +122,7 @@ function IOURequestStepHours({ participantsAutoAssigned: true, }); return Navigation.setNavigationActionToMicrotaskQueue(() => - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, policyExpenseChatReportID)), + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, policyExpenseChatReportID ?? reportID)), ); } setMoneyRequestParticipantsFromReport(transactionID, report, accountID); diff --git a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx index 1f76c205bc7e1..e06addb5a099d 100644 --- a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx +++ b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx @@ -15,7 +15,7 @@ type IOURequestStepTimeWorkspaceProps = PlatformStackScreenProps ); From 3f9b62dca2a24db7d3a6010d35c56e90378ed3b3 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 20 Jan 2026 14:55:14 +0100 Subject: [PATCH 10/16] Fix prettier --- src/libs/actions/IOU/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 7cfcd8480f776..9cb41c70dcc7f 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -11940,7 +11940,7 @@ function setMoneyRequestParticipantAsPolicyExpenseChat({ if (!policyExpenseChatReportID) { return; } - + Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { reportID: policyExpenseChatReportID, participants: [ From 74a112fda6df9e8b47beea8653cdde93bbba16b5 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 20 Jan 2026 14:58:10 +0100 Subject: [PATCH 11/16] Change IOURequestStepHours headerTitle --- src/pages/iou/request/step/IOURequestStepHours.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index f46a544ce67c0..ebf6ce4c078ed 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -133,7 +133,7 @@ function IOURequestStepHours({ return ( Date: Tue, 20 Jan 2026 18:39:31 +0100 Subject: [PATCH 12/16] Remove setMoneyRequestParticipantAsPolicyExpenseChat and refactor --- src/libs/actions/IOU/index.ts | 41 ------------------- .../request/step/BaseRequestStepWorkspace.tsx | 8 ++-- .../iou/request/step/IOURequestStepHours.tsx | 29 ++++++------- .../step/IOURequestStepPerDiemWorkspace.tsx | 10 ++--- .../step/IOURequestStepTimeWorkspace.tsx | 29 ++++++------- 5 files changed, 35 insertions(+), 82 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 9cb41c70dcc7f..9220fa03725ef 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -150,7 +150,6 @@ import { getOutstandingChildRequest, getParsedComment, getPersonalDetailsForAccountID, - getPolicyExpenseChat, getReportNotificationPreference, getReportOrDraftReport, getReportRecipientAccountIDs, @@ -11919,45 +11918,6 @@ function setMoneyRequestParticipantsFromReport(transactionID: string, report: On }); } -/** - * Sets transaction's participant and reportID to the policy's expense chat. - * Returns the policy expense chat reportID. - */ -function setMoneyRequestParticipantAsPolicyExpenseChat({ - transactionID, - policyID, - currentUserAccountID, - isDraft, - participantsAutoAssigned, -}: { - transactionID: string; - policyID: string; - currentUserAccountID: number; - isDraft: boolean; - participantsAutoAssigned?: boolean; -}) { - const policyExpenseChatReportID = getPolicyExpenseChat(currentUserAccountID, policyID)?.reportID; - if (!policyExpenseChatReportID) { - return; - } - - Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { - reportID: policyExpenseChatReportID, - participants: [ - { - selected: true, - accountID: 0, - isPolicyExpenseChat: true, - reportID: policyExpenseChatReportID, - policyID, - }, - ], - participantsAutoAssigned, - }); - - return policyExpenseChatReportID; -} - function setMoneyRequestTaxRate(transactionID: string, taxCode: string | null) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {taxCode}); } @@ -14450,7 +14410,6 @@ export { getSearchOnyxUpdate, setMoneyRequestTimeRate, setMoneyRequestTimeCount, - setMoneyRequestParticipantAsPolicyExpenseChat, }; export type { GPSPoint as GpsPoint, diff --git a/src/pages/iou/request/step/BaseRequestStepWorkspace.tsx b/src/pages/iou/request/step/BaseRequestStepWorkspace.tsx index 5299478bfa1f4..fbb74d2aa9bb0 100644 --- a/src/pages/iou/request/step/BaseRequestStepWorkspace.tsx +++ b/src/pages/iou/request/step/BaseRequestStepWorkspace.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {View} from 'react-native'; -import type {OnyxCollection} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import SearchBar from '@components/SearchBar'; import SelectionList from '@components/SelectionList'; import type {ListItem} from '@components/SelectionList/ListItem/types'; @@ -31,7 +31,7 @@ type BaseRequestStepWorkspaceProps = WithFullTransactionOrNotFoundProps, currentUserLogin: string | undefined) => Policy[]; /** Function to run after selecting a workspace. */ - onSelectWorkspace: (item: WorkspaceListItem, allPolicies: OnyxCollection) => void; + onSelectWorkspace: (policy: OnyxEntry) => void; }; function BaseRequestStepWorkspace({transaction, getPolicies, onSelectWorkspace}: BaseRequestStepWorkspaceProps) { @@ -75,9 +75,7 @@ function BaseRequestStepWorkspace({transaction, getPolicies, onSelectWorkspace}: const sortWorkspaces = (data: WorkspaceListItem[]) => data.sort((a, b) => localeCompare(a.text ?? '', b?.text ?? '')); const [inputValue, setInputValue, filteredWorkspaceOptions] = useSearchResults(workspaceOptions, filterWorkspace, sortWorkspaces); - const selectWorkspace = (item: WorkspaceListItem) => { - onSelectWorkspace(item, allPolicies); - }; + const selectWorkspace = (item: WorkspaceListItem) => onSelectWorkspace(allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.value}`]); return ( <> diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index ebf6ce4c078ed..b292927efc9fd 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -10,20 +10,15 @@ import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep'; import useThemeStyles from '@hooks/useThemeStyles'; +import {setTransactionReport} from '@libs/actions/Transaction'; import {canUseTouchScreen as canUseTouchScreenUtil} from '@libs/DeviceCapabilities'; import {shouldUseTransactionDraft} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; +import {getPolicyExpenseChat} from '@libs/ReportUtils'; import {computeTimeAmount, formatTimeMerchant} from '@libs/TimeTrackingUtils'; import variables from '@styles/variables'; -import { - setMoneyRequestAmount, - setMoneyRequestMerchant, - setMoneyRequestParticipantAsPolicyExpenseChat, - setMoneyRequestParticipantsFromReport, - setMoneyRequestTimeCount, - setMoneyRequestTimeRate, -} from '@userActions/IOU'; +import {setMoneyRequestAmount, setMoneyRequestMerchant, setMoneyRequestParticipantsFromReport, setMoneyRequestTimeCount, setMoneyRequestTimeRate} from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -114,15 +109,17 @@ function IOURequestStepHours({ if (isEmbeddedInStartPage) { if (explicitPolicyID) { - const policyExpenseChatReportID = setMoneyRequestParticipantAsPolicyExpenseChat({ - transactionID, - policyID: explicitPolicyID, - currentUserAccountID: accountID, - isDraft: isTransactionDraft, - participantsAutoAssigned: true, - }); + const policyExpenseChat = getPolicyExpenseChat(accountID, policyID); + if (!policyExpenseChat) { + console.error(`Couldn't find policy expense chat for policyID: ${policyID}`); + return; + } + + setTransactionReport(transactionID, {reportID: policyExpenseChat.reportID}, isTransactionDraft); + setMoneyRequestParticipantsFromReport(transactionID, policyExpenseChat, accountID); + return Navigation.setNavigationActionToMicrotaskQueue(() => - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, policyExpenseChatReportID ?? reportID)), + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, policyExpenseChat.reportID)), ); } setMoneyRequestParticipantsFromReport(transactionID, report, accountID); diff --git a/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx index 7eb0a8b46bda8..d1f298404f95a 100644 --- a/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx +++ b/src/pages/iou/request/step/IOURequestStepPerDiemWorkspace.tsx @@ -7,7 +7,6 @@ import {getActivePoliciesWithExpenseChatAndPerDiemEnabled, getPerDiemCustomUnit} import {getPolicyExpenseChat} from '@libs/ReportUtils'; import {setCustomUnitID, setMoneyRequestCategory, setMoneyRequestParticipants} from '@userActions/IOU'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import BaseRequestStepWorkspace from './BaseRequestStepWorkspace'; @@ -25,20 +24,19 @@ function IOURequestStepPerDiemWorkspace({route, navigation}: IOURequestStepPerDi route={route} navigation={navigation} getPolicies={getActivePoliciesWithExpenseChatAndPerDiemEnabled} - onSelectWorkspace={(item, allPolicies) => { - const policyExpenseReportID = getPolicyExpenseChat(accountID, item.value)?.reportID; + onSelectWorkspace={(policy) => { + const policyExpenseReportID = getPolicyExpenseChat(accountID, policy?.id)?.reportID; if (!policyExpenseReportID) { return; } - const selectedPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.value}`]; - const perDiemUnit = getPerDiemCustomUnit(selectedPolicy); + const perDiemUnit = getPerDiemCustomUnit(policy); setMoneyRequestParticipants(transactionID, [ { selected: true, accountID: 0, isPolicyExpenseChat: true, reportID: policyExpenseReportID, - policyID: item.value, + policyID: policy?.id, }, ]); setCustomUnitID(transactionID, perDiemUnit?.customUnitID ?? CONST.CUSTOM_UNITS.FAKE_P2P_ID); diff --git a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx index e06addb5a099d..4d726c798ab72 100644 --- a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx +++ b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx @@ -1,12 +1,13 @@ import React from 'react'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import {setTransactionReport} from '@libs/actions/Transaction'; import {shouldUseTransactionDraft} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import {getActivePoliciesWithExpenseChatAndTimeEnabled, getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; -import {setMoneyRequestParticipantAsPolicyExpenseChat, setMoneyRequestTimeRate} from '@userActions/IOU'; -import ONYXKEYS from '@src/ONYXKEYS'; +import {getPolicyExpenseChat} from '@libs/ReportUtils'; +import {setMoneyRequestParticipantsFromReport, setMoneyRequestTimeRate} from '@userActions/IOU'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import BaseRequestStepWorkspace from './BaseRequestStepWorkspace'; @@ -15,7 +16,7 @@ type IOURequestStepTimeWorkspaceProps = PlatformStackScreenProps { - const policyExpenseChatReportID = setMoneyRequestParticipantAsPolicyExpenseChat({ - transactionID, - policyID: item.value, - currentUserAccountID: accountID, - isDraft: isTransactionDraft, - participantsAutoAssigned: true, - }); - - const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.value}`]; + onSelectWorkspace={(policy) => { + const policyExpenseChat = getPolicyExpenseChat(accountID, policy?.id); + if (!policyExpenseChat) { + console.error(`Couldn't find policy expense chat for policyID: ${policy?.id}`); + return; + } + + setTransactionReport(transactionID, {reportID: policyExpenseChat.reportID}, isTransactionDraft); + setMoneyRequestParticipantsFromReport(transactionID, policyExpenseChat, accountID); + const defaultRate = policy ? getDefaultTimeTrackingRate(policy) : undefined; if (defaultRate) { setMoneyRequestTimeRate(transactionID, defaultRate, isTransactionDraft); } - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, transactionID, policyExpenseChatReportID ?? reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, transactionID, policyExpenseChat.reportID)); }} /> ); From 9638050ccf04fa0dc0f50c5ee8d08bcbfb0e13a8 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Wed, 21 Jan 2026 13:00:39 +0100 Subject: [PATCH 13/16] Fix polish translation for "rate" --- src/languages/pl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 4c45f4dd1f0b4..faefc4d7b9645 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -501,7 +501,7 @@ const translations: TranslationDeepObject = { role: 'Rola', currency: 'Waluta', groupCurrency: 'Waluta grupy', - rate: 'Oceń', + rate: 'Stawka', emptyLHN: { title: 'Juhu! Wszystko nadrobione.', subtitleText1: 'Znajdź czat za pomocą', From c4d53294817b3cb6cfe4bbdb7740dc729855e83e Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Thu, 22 Jan 2026 11:53:40 +0100 Subject: [PATCH 14/16] Replace two hours step routes with one route with a url parameter --- src/CONST/index.ts | 4 ++++ src/ROUTES.ts | 17 ++++++++-------- src/SCREENS.ts | 1 - .../MoneyRequestConfirmationListFooter.tsx | 2 +- .../ModalStackNavigators/index.tsx | 1 - src/libs/Navigation/linkingConfig/config.ts | 1 - src/libs/Navigation/types.ts | 13 ++++++------ .../iou/request/step/IOURequestStepHours.tsx | 20 ++++++------------- .../step/IOURequestStepTimeWorkspace.tsx | 3 ++- .../step/withFullTransactionOrNotFound.tsx | 3 +-- .../step/withWritableReportOrNotFound.tsx | 3 +-- 11 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 7aa056864273c..e63a95d931a02 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -3001,6 +3001,10 @@ const CONST = { MONEY_REQUEST: 'moneyRequest', REPLACE_RECEIPT: 'replaceReceipt', }, + HOURS_STEP_ORIGIN: { + CONFIRM: 'confirm', + START: 'start', + }, }, CATEGORY_SOURCE: { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 1fa7a5e905326..7b00ef16bcb55 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1318,14 +1318,15 @@ const ROUTES = { `${action as string}/${iouType as string}/rate/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}` as const, }, MONEY_REQUEST_STEP_HOURS: { - route: ':action/:iouType/hours/:transactionID/:reportID/:reportActionID?', - getRoute: (action: IOUAction, iouType: IOUType, transactionID: string | undefined, reportID: string | undefined, reportActionID?: string) => - `${action as string}/${iouType as string}/hours/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}` as const, - }, - MONEY_REQUEST_STEP_HOURS_EDIT: { - route: ':action/:iouType/hours-edit/:transactionID/:reportID/:reportActionID?', - getRoute: (action: IOUAction, iouType: IOUType, transactionID: string | undefined, reportID: string | undefined, reportActionID?: string) => - `${action as string}/${iouType as string}/hours-edit/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}` as const, + route: ':action/:iouType/hours/:origin/:transactionID/:reportID/:reportActionID?', + getRoute: ( + action: IOUAction, + iouType: IOUType, + origin: ValueOf, + transactionID: string | undefined, + reportID: string | undefined, + reportActionID?: string, + ) => `${action as string}/${iouType as string}/hours/${origin}/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}` as const, }, DISTANCE_REQUEST_CREATE: { route: ':action/:iouType/start/:transactionID/:reportID/distance-new/:backToReport?', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 05639da43c40f..9744c56eac725 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -335,7 +335,6 @@ const SCREENS = { RECEIPT_PREVIEW: 'Money_Request_Receipt_preview', STEP_TIME_RATE: 'Money_Request_Step_Time_Rate', STEP_HOURS: 'Money_Request_Step_Hours', - STEP_HOURS_EDIT: 'Money_Request_Step_Hours_Edit', }, TRANSACTION_DUPLICATE: { diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index 40602eea12696..fe53907b699f5 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -624,7 +624,7 @@ function MoneyRequestConfirmationListFooter({ if (!transactionID) { return; } - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS_EDIT.getRoute(action, iouType, transactionID, reportID, reportActionID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, CONST.IOU.HOURS_STEP_ORIGIN.CONFIRM, transactionID, reportID, reportActionID)); }} disabled={didConfirm} interactive={!isReadOnly} diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 24a0bd920d255..7bdfeca882ef5 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -188,7 +188,6 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../../pages/SetDefaultWorkspacePage').default, [SCREENS.MONEY_REQUEST.STEP_TIME_RATE]: () => require('../../../../pages/iou/request/step/IOURequestStepTimeRate').default, [SCREENS.MONEY_REQUEST.STEP_HOURS]: () => require('../../../../pages/iou/request/step/IOURequestStepHours').default, - [SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT]: () => require('../../../../pages/iou/request/step/IOURequestStepHours').default, }); const TravelModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index cde68cfe329d2..b074d45f234a0 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1524,7 +1524,6 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.STEP_SUBRATE_EDIT]: ROUTES.MONEY_REQUEST_STEP_SUBRATE_EDIT.route, [SCREENS.MONEY_REQUEST.STEP_TIME_RATE]: ROUTES.MONEY_REQUEST_STEP_TIME_RATE.route, [SCREENS.MONEY_REQUEST.STEP_HOURS]: ROUTES.MONEY_REQUEST_STEP_HOURS.route, - [SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT]: ROUTES.MONEY_REQUEST_STEP_HOURS_EDIT.route, [SCREENS.IOU_SEND.ENABLE_PAYMENTS]: ROUTES.IOU_SEND_ENABLE_PAYMENTS, [SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: ROUTES.IOU_SEND_ADD_BANK_ACCOUNT, [SCREENS.IOU_SEND.ADD_DEBIT_CARD]: ROUTES.IOU_SEND_ADD_DEBIT_CARD, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 4d502cd55e512..113b51cedf2a6 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1954,13 +1954,12 @@ type MoneyRequestNavigatorParamList = { [SCREENS.MONEY_REQUEST.STEP_HOURS]: { action: IOUAction; iouType: Exclude; - transactionID: string; - reportID: string; - reportActionID: string; - }; - [SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT]: { - action: IOUAction; - iouType: Exclude; + /** + * Where the hours step was accessed from. + * confirm: from the confirmation page + * start: after selecting a workspace in the 'time' tab + */ + origin: ValueOf; transactionID: string; reportID: string; reportActionID: string; diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index df9a527a9f2b3..2eae79443ada3 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -14,6 +14,7 @@ import {setTransactionReport} from '@libs/actions/Transaction'; import {canUseTouchScreen as canUseTouchScreenUtil} from '@libs/DeviceCapabilities'; import {shouldUseTransactionDraft} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; +import {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; import {getPolicyExpenseChat} from '@libs/ReportUtils'; import {computeTimeAmount, formatTimeMerchant} from '@libs/TimeTrackingUtils'; @@ -29,23 +30,14 @@ import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; -type IOURequestStepHoursProps = WithWritableReportOrNotFoundProps< - typeof SCREENS.MONEY_REQUEST.CREATE | typeof SCREENS.MONEY_REQUEST.STEP_HOURS | typeof SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT -> & - WithFullTransactionOrNotFoundProps & { +type IOURequestStepHoursProps = WithWritableReportOrNotFoundProps & + WithFullTransactionOrNotFoundProps & { explicitPolicyID?: string; }; -function IOURequestStepHours({ - report, - route: { - params: {iouType, reportID, transactionID = '-1', action, reportActionID}, - name: routeName, - }, - transaction, - explicitPolicyID, -}: IOURequestStepHoursProps) { - const isEditingConfirmation = routeName === SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT; +function IOURequestStepHours({report, route: {params, name: routeName}, transaction, explicitPolicyID}: IOURequestStepHoursProps) { + const {iouType, reportID, transactionID = '-1', action, reportActionID} = params; + const isEditingConfirmation = (params as MoneyRequestNavigatorParamList[typeof SCREENS.MONEY_REQUEST.STEP_HOURS])?.origin === CONST.IOU.HOURS_STEP_ORIGIN.CONFIRM; const isEmbeddedInStartPage = routeName === SCREENS.MONEY_REQUEST.CREATE; const policyID = explicitPolicyID ?? report?.policyID; const isTransactionDraft = shouldUseTransactionDraft(action); diff --git a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx index 4d726c798ab72..30c65d5125061 100644 --- a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx +++ b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx @@ -8,6 +8,7 @@ import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import {getActivePoliciesWithExpenseChatAndTimeEnabled, getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; import {getPolicyExpenseChat} from '@libs/ReportUtils'; import {setMoneyRequestParticipantsFromReport, setMoneyRequestTimeRate} from '@userActions/IOU'; +import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import BaseRequestStepWorkspace from './BaseRequestStepWorkspace'; @@ -42,7 +43,7 @@ function IOURequestStepTimeWorkspace({route, navigation}: IOURequestStepTimeWork setMoneyRequestTimeRate(transactionID, defaultRate, isTransactionDraft); } - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, transactionID, policyExpenseChat.reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, CONST.IOU.HOURS_STEP_ORIGIN.START, transactionID, policyExpenseChat.reportID)); }} /> ); diff --git a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx index 4e5057b3307f5..1344153a0ae2f 100644 --- a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx +++ b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx @@ -53,8 +53,7 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_MANUAL | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_ODOMETER | typeof SCREENS.MONEY_REQUEST.STEP_TIME_RATE - | typeof SCREENS.MONEY_REQUEST.STEP_HOURS - | typeof SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT; + | typeof SCREENS.MONEY_REQUEST.STEP_HOURS; type WithFullTransactionOrNotFoundProps = WithFullTransactionOrNotFoundOnyxProps & PlatformStackScreenProps; diff --git a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx index 7deb78ef5fcf7..2c5c19a1ca847 100644 --- a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx +++ b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx @@ -57,8 +57,7 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_ODOMETER | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_MANUAL | typeof SCREENS.MONEY_REQUEST.STEP_TIME_RATE - | typeof SCREENS.MONEY_REQUEST.STEP_HOURS - | typeof SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT; + | typeof SCREENS.MONEY_REQUEST.STEP_HOURS; type WithWritableReportOrNotFoundProps = WithWritableReportOrNotFoundOnyxProps & PlatformStackScreenProps; From 9a8f9604f7e8c56faf59c041be9d45631ea7a3f5 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Thu, 22 Jan 2026 11:59:31 +0100 Subject: [PATCH 15/16] Fix eslint --- src/pages/iou/request/step/IOURequestStepHours.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index 2eae79443ada3..ebac6a5cb3cbb 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -14,7 +14,7 @@ import {setTransactionReport} from '@libs/actions/Transaction'; import {canUseTouchScreen as canUseTouchScreenUtil} from '@libs/DeviceCapabilities'; import {shouldUseTransactionDraft} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; +import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; import {getPolicyExpenseChat} from '@libs/ReportUtils'; import {computeTimeAmount, formatTimeMerchant} from '@libs/TimeTrackingUtils'; From 27b3ef286213c945eebd34445ce9b60924acd1dc Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Fri, 23 Jan 2026 10:37:22 +0100 Subject: [PATCH 16/16] Revert "Replace two hours step routes with one route with a url parameter" This reverts commit c4d53294817b3cb6cfe4bbdb7740dc729855e83e. --- src/CONST/index.ts | 4 ---- src/ROUTES.ts | 17 ++++++++-------- src/SCREENS.ts | 1 + .../MoneyRequestConfirmationListFooter.tsx | 2 +- .../ModalStackNavigators/index.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 1 + src/libs/Navigation/types.ts | 13 ++++++------ .../iou/request/step/IOURequestStepHours.tsx | 20 +++++++++++++------ .../step/IOURequestStepTimeWorkspace.tsx | 3 +-- .../step/withFullTransactionOrNotFound.tsx | 3 ++- .../step/withWritableReportOrNotFound.tsx | 3 ++- 11 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index e63a95d931a02..7aa056864273c 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -3001,10 +3001,6 @@ const CONST = { MONEY_REQUEST: 'moneyRequest', REPLACE_RECEIPT: 'replaceReceipt', }, - HOURS_STEP_ORIGIN: { - CONFIRM: 'confirm', - START: 'start', - }, }, CATEGORY_SOURCE: { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 7b00ef16bcb55..1fa7a5e905326 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1318,15 +1318,14 @@ const ROUTES = { `${action as string}/${iouType as string}/rate/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}` as const, }, MONEY_REQUEST_STEP_HOURS: { - route: ':action/:iouType/hours/:origin/:transactionID/:reportID/:reportActionID?', - getRoute: ( - action: IOUAction, - iouType: IOUType, - origin: ValueOf, - transactionID: string | undefined, - reportID: string | undefined, - reportActionID?: string, - ) => `${action as string}/${iouType as string}/hours/${origin}/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}` as const, + route: ':action/:iouType/hours/:transactionID/:reportID/:reportActionID?', + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string | undefined, reportID: string | undefined, reportActionID?: string) => + `${action as string}/${iouType as string}/hours/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}` as const, + }, + MONEY_REQUEST_STEP_HOURS_EDIT: { + route: ':action/:iouType/hours-edit/:transactionID/:reportID/:reportActionID?', + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string | undefined, reportID: string | undefined, reportActionID?: string) => + `${action as string}/${iouType as string}/hours-edit/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}` as const, }, DISTANCE_REQUEST_CREATE: { route: ':action/:iouType/start/:transactionID/:reportID/distance-new/:backToReport?', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 9744c56eac725..05639da43c40f 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -335,6 +335,7 @@ const SCREENS = { RECEIPT_PREVIEW: 'Money_Request_Receipt_preview', STEP_TIME_RATE: 'Money_Request_Step_Time_Rate', STEP_HOURS: 'Money_Request_Step_Hours', + STEP_HOURS_EDIT: 'Money_Request_Step_Hours_Edit', }, TRANSACTION_DUPLICATE: { diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index fe53907b699f5..40602eea12696 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -624,7 +624,7 @@ function MoneyRequestConfirmationListFooter({ if (!transactionID) { return; } - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, CONST.IOU.HOURS_STEP_ORIGIN.CONFIRM, transactionID, reportID, reportActionID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS_EDIT.getRoute(action, iouType, transactionID, reportID, reportActionID)); }} disabled={didConfirm} interactive={!isReadOnly} diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 7bdfeca882ef5..24a0bd920d255 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -188,6 +188,7 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../../pages/SetDefaultWorkspacePage').default, [SCREENS.MONEY_REQUEST.STEP_TIME_RATE]: () => require('../../../../pages/iou/request/step/IOURequestStepTimeRate').default, [SCREENS.MONEY_REQUEST.STEP_HOURS]: () => require('../../../../pages/iou/request/step/IOURequestStepHours').default, + [SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT]: () => require('../../../../pages/iou/request/step/IOURequestStepHours').default, }); const TravelModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index b074d45f234a0..cde68cfe329d2 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1524,6 +1524,7 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.STEP_SUBRATE_EDIT]: ROUTES.MONEY_REQUEST_STEP_SUBRATE_EDIT.route, [SCREENS.MONEY_REQUEST.STEP_TIME_RATE]: ROUTES.MONEY_REQUEST_STEP_TIME_RATE.route, [SCREENS.MONEY_REQUEST.STEP_HOURS]: ROUTES.MONEY_REQUEST_STEP_HOURS.route, + [SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT]: ROUTES.MONEY_REQUEST_STEP_HOURS_EDIT.route, [SCREENS.IOU_SEND.ENABLE_PAYMENTS]: ROUTES.IOU_SEND_ENABLE_PAYMENTS, [SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: ROUTES.IOU_SEND_ADD_BANK_ACCOUNT, [SCREENS.IOU_SEND.ADD_DEBIT_CARD]: ROUTES.IOU_SEND_ADD_DEBIT_CARD, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 113b51cedf2a6..4d502cd55e512 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1954,12 +1954,13 @@ type MoneyRequestNavigatorParamList = { [SCREENS.MONEY_REQUEST.STEP_HOURS]: { action: IOUAction; iouType: Exclude; - /** - * Where the hours step was accessed from. - * confirm: from the confirmation page - * start: after selecting a workspace in the 'time' tab - */ - origin: ValueOf; + transactionID: string; + reportID: string; + reportActionID: string; + }; + [SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT]: { + action: IOUAction; + iouType: Exclude; transactionID: string; reportID: string; reportActionID: string; diff --git a/src/pages/iou/request/step/IOURequestStepHours.tsx b/src/pages/iou/request/step/IOURequestStepHours.tsx index ebac6a5cb3cbb..df9a527a9f2b3 100644 --- a/src/pages/iou/request/step/IOURequestStepHours.tsx +++ b/src/pages/iou/request/step/IOURequestStepHours.tsx @@ -14,7 +14,6 @@ import {setTransactionReport} from '@libs/actions/Transaction'; import {canUseTouchScreen as canUseTouchScreenUtil} from '@libs/DeviceCapabilities'; import {shouldUseTransactionDraft} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; -import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import {getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; import {getPolicyExpenseChat} from '@libs/ReportUtils'; import {computeTimeAmount, formatTimeMerchant} from '@libs/TimeTrackingUtils'; @@ -30,14 +29,23 @@ import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; import withWritableReportOrNotFound from './withWritableReportOrNotFound'; -type IOURequestStepHoursProps = WithWritableReportOrNotFoundProps & - WithFullTransactionOrNotFoundProps & { +type IOURequestStepHoursProps = WithWritableReportOrNotFoundProps< + typeof SCREENS.MONEY_REQUEST.CREATE | typeof SCREENS.MONEY_REQUEST.STEP_HOURS | typeof SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT +> & + WithFullTransactionOrNotFoundProps & { explicitPolicyID?: string; }; -function IOURequestStepHours({report, route: {params, name: routeName}, transaction, explicitPolicyID}: IOURequestStepHoursProps) { - const {iouType, reportID, transactionID = '-1', action, reportActionID} = params; - const isEditingConfirmation = (params as MoneyRequestNavigatorParamList[typeof SCREENS.MONEY_REQUEST.STEP_HOURS])?.origin === CONST.IOU.HOURS_STEP_ORIGIN.CONFIRM; +function IOURequestStepHours({ + report, + route: { + params: {iouType, reportID, transactionID = '-1', action, reportActionID}, + name: routeName, + }, + transaction, + explicitPolicyID, +}: IOURequestStepHoursProps) { + const isEditingConfirmation = routeName === SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT; const isEmbeddedInStartPage = routeName === SCREENS.MONEY_REQUEST.CREATE; const policyID = explicitPolicyID ?? report?.policyID; const isTransactionDraft = shouldUseTransactionDraft(action); diff --git a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx index 30c65d5125061..4d726c798ab72 100644 --- a/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx +++ b/src/pages/iou/request/step/IOURequestStepTimeWorkspace.tsx @@ -8,7 +8,6 @@ import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import {getActivePoliciesWithExpenseChatAndTimeEnabled, getDefaultTimeTrackingRate} from '@libs/PolicyUtils'; import {getPolicyExpenseChat} from '@libs/ReportUtils'; import {setMoneyRequestParticipantsFromReport, setMoneyRequestTimeRate} from '@userActions/IOU'; -import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import BaseRequestStepWorkspace from './BaseRequestStepWorkspace'; @@ -43,7 +42,7 @@ function IOURequestStepTimeWorkspace({route, navigation}: IOURequestStepTimeWork setMoneyRequestTimeRate(transactionID, defaultRate, isTransactionDraft); } - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, CONST.IOU.HOURS_STEP_ORIGIN.START, transactionID, policyExpenseChat.reportID)); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_HOURS.getRoute(action, iouType, transactionID, policyExpenseChat.reportID)); }} /> ); diff --git a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx index 1344153a0ae2f..4e5057b3307f5 100644 --- a/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx +++ b/src/pages/iou/request/step/withFullTransactionOrNotFound.tsx @@ -53,7 +53,8 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_MANUAL | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_ODOMETER | typeof SCREENS.MONEY_REQUEST.STEP_TIME_RATE - | typeof SCREENS.MONEY_REQUEST.STEP_HOURS; + | typeof SCREENS.MONEY_REQUEST.STEP_HOURS + | typeof SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT; type WithFullTransactionOrNotFoundProps = WithFullTransactionOrNotFoundOnyxProps & PlatformStackScreenProps; diff --git a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx index 2c5c19a1ca847..7deb78ef5fcf7 100644 --- a/src/pages/iou/request/step/withWritableReportOrNotFound.tsx +++ b/src/pages/iou/request/step/withWritableReportOrNotFound.tsx @@ -57,7 +57,8 @@ type MoneyRequestRouteName = | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_ODOMETER | typeof SCREENS.MONEY_REQUEST.STEP_DISTANCE_MANUAL | typeof SCREENS.MONEY_REQUEST.STEP_TIME_RATE - | typeof SCREENS.MONEY_REQUEST.STEP_HOURS; + | typeof SCREENS.MONEY_REQUEST.STEP_HOURS + | typeof SCREENS.MONEY_REQUEST.STEP_HOURS_EDIT; type WithWritableReportOrNotFoundProps = WithWritableReportOrNotFoundOnyxProps & PlatformStackScreenProps;