diff --git a/src/libs/actions/IOU/PerDiem.ts b/src/libs/actions/IOU/PerDiem.ts index 3eb3d4983d25..ad4042fd3d41 100644 --- a/src/libs/actions/IOU/PerDiem.ts +++ b/src/libs/actions/IOU/PerDiem.ts @@ -58,6 +58,7 @@ import { getReportPreviewAction, getUserAccountID, handleNavigateAfterExpenseCreate, + highlightTransactionOnSearchRouteIfNeeded, mergePolicyRecentlyUsedCategories, mergePolicyRecentlyUsedCurrencies, } from '.'; @@ -999,6 +1000,9 @@ function submitPerDiemExpense(submitPerDiemExpenseInformation: PerDiemExpenseInf // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID)); + + highlightTransactionOnSearchRouteIfNeeded(isFromGlobalCreate, transaction.transactionID, CONST.SEARCH.DATA_TYPES.EXPENSE); + handleNavigateAfterExpenseCreate({activeReportID, transactionID: transaction.transactionID, isFromGlobalCreate, shouldHandleNavigation}); if (activeReportID) { diff --git a/src/libs/actions/IOU/SendInvoice.ts b/src/libs/actions/IOU/SendInvoice.ts index 19ce3984f710..1af58b437347 100644 --- a/src/libs/actions/IOU/SendInvoice.ts +++ b/src/libs/actions/IOU/SendInvoice.ts @@ -38,6 +38,7 @@ import { getReceiptError, getSearchOnyxUpdate, handleNavigateAfterExpenseCreate, + highlightTransactionOnSearchRouteIfNeeded, mergePolicyRecentlyUsedCategories, mergePolicyRecentlyUsedCurrencies, } from '.'; @@ -818,6 +819,8 @@ function sendInvoice({ // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID)); + highlightTransactionOnSearchRouteIfNeeded(isFromGlobalCreate, transactionID, CONST.SEARCH.DATA_TYPES.INVOICE); + handleNavigateAfterExpenseCreate({ activeReportID: invoiceRoom.reportID, transactionID, diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index d6a63ac7636b..456471662176 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -258,6 +258,7 @@ import type RecentlyUsedTags from '@src/types/onyx/RecentlyUsedTags'; import type {ReportNextStep} from '@src/types/onyx/Report'; import type ReportAction from '@src/types/onyx/ReportAction'; import type {OnyxData} from '@src/types/onyx/Request'; +import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import type {Comment, Receipt, ReceiptSource, Routes, SplitShares, TransactionChanges, TransactionCustomUnit, WaypointCollection} from '@src/types/onyx/Transaction'; import type {FileObject} from '@src/types/utils/Attachment'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -1142,6 +1143,17 @@ function dismissModalAndOpenReportInInboxTab(reportID?: string, isInvoice?: bool } } +/** + * Marks a transaction for highlight on the Search page when the expense was created + * from the global create button and the user is not on the Inbox tab. + */ +function highlightTransactionOnSearchRouteIfNeeded(isFromGlobalCreate: boolean | undefined, transactionID: string | undefined, dataType: SearchDataTypes) { + if (!isFromGlobalCreate || isReportTopmostSplitNavigator() || !transactionID) { + return; + } + mergeTransactionIdsHighlightOnSearchRoute(dataType, {[transactionID]: true}); +} + /** * Helper to navigate after an expense is created in order to standardize the post‑creation experience * when creating an expense from the global create button. @@ -1174,15 +1186,12 @@ function handleNavigateAfterExpenseCreate({ return; } - const type = isInvoice ? CONST.SEARCH.DATA_TYPES.INVOICE : CONST.SEARCH.DATA_TYPES.EXPENSE; - - // We mark this transaction to be highlighted when opening the expense search route page - mergeTransactionIdsHighlightOnSearchRoute(type, {[transactionID]: true}); - if (!shouldHandleNavigation) { return; } + const type = isInvoice ? CONST.SEARCH.DATA_TYPES.INVOICE : CONST.SEARCH.DATA_TYPES.EXPENSE; + // When already on Search ROOT with the same type (expense vs invoice), we navigate to the same screen (no-op or refresh); record as dismiss_modal_only. // When on another Search sub-tab (e.g. Chats), or on Search with a different type (e.g. on Invoice, submitting expense), record as navigate_to_search. const rootState = navigationRef.getRootState(); @@ -6607,6 +6616,8 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation): {iouRep } if (!requestMoneyInformation.isRetry) { + highlightTransactionOnSearchRouteIfNeeded(isFromGlobalCreate, transaction.transactionID, CONST.SEARCH.DATA_TYPES.EXPENSE); + handleNavigateAfterExpenseCreate({ activeReportID: backToReport ?? activeReportID, transactionID: transaction.transactionID, @@ -6991,6 +7002,8 @@ function trackExpense(params: CreateTrackExpenseParams) { } if (!params.isRetry) { + highlightTransactionOnSearchRouteIfNeeded(isFromGlobalCreate, transaction?.transactionID, CONST.SEARCH.DATA_TYPES.EXPENSE); + handleNavigateAfterExpenseCreate({ activeReportID, transactionID: transaction?.transactionID, @@ -7851,6 +7864,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest const activeReportID = isMoneyRequestReport && report?.reportID ? report.reportID : parameters.chatReportID; if (shouldHandleNavigation) { + highlightTransactionOnSearchRouteIfNeeded(isFromGlobalCreate, parameters.transactionID, CONST.SEARCH.DATA_TYPES.EXPENSE); handleNavigateAfterExpenseCreate({activeReportID: backToReport ?? activeReportID, isFromGlobalCreate, transactionID: parameters.transactionID}); } @@ -13279,6 +13293,7 @@ export { setMoneyRequestTimeCount, getCleanUpTransactionThreadReportOnyxData, handleNavigateAfterExpenseCreate, + highlightTransactionOnSearchRouteIfNeeded, buildMinimalTransactionForFormula, buildOnyxDataForMoneyRequest, createSplitsAndOnyxData, diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 5054f2febac2..14875f767187 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -14989,9 +14989,6 @@ describe('actions/IOU', () => { const transactionID = '1'; mockedIsReportTopmostSplitNavigator.mockReturnValue(false); - // When on the Inbox tab, or NOT from the "global create" button, or without a transactionID, - // the function dismissModalAndOpenReportInInboxTab will always be called to handle it, - // so mergeTransactionIdsHighlightOnSearchRoute will never be invoked. handleNavigateAfterExpenseCreate({activeReportID, isFromGlobalCreate: false}); expect(spyOnMergeTransactionIdsHighlightOnSearchRoute).toHaveBeenCalledTimes(0); @@ -15002,20 +14999,13 @@ describe('actions/IOU', () => { handleNavigateAfterExpenseCreate({activeReportID, isFromGlobalCreate: true, transactionID}); expect(spyOnMergeTransactionIdsHighlightOnSearchRoute).toHaveBeenCalledTimes(0); - // When NOT on the Inbox tab mockedIsReportTopmostSplitNavigator.mockReturnValue(false); handleNavigateAfterExpenseCreate({activeReportID, isFromGlobalCreate: true, transactionID}); + expect(spyOnMergeTransactionIdsHighlightOnSearchRoute).toHaveBeenCalledTimes(0); - // then mergeTransactionIdsHighlightOnSearchRoute will be called - expect(spyOnMergeTransactionIdsHighlightOnSearchRoute).toHaveBeenCalledTimes(1); - expect(spyOnMergeTransactionIdsHighlightOnSearchRoute).toHaveBeenCalledWith(CONST.SEARCH.DATA_TYPES.EXPENSE, {[transactionID]: true}); - spyOnMergeTransactionIdsHighlightOnSearchRoute.mockClear(); - - // If expense is an invoice handleNavigateAfterExpenseCreate({activeReportID, isFromGlobalCreate: true, transactionID, isInvoice: true}); + expect(spyOnMergeTransactionIdsHighlightOnSearchRoute).toHaveBeenCalledTimes(0); - expect(spyOnMergeTransactionIdsHighlightOnSearchRoute).toHaveBeenCalledTimes(1); - expect(spyOnMergeTransactionIdsHighlightOnSearchRoute).toHaveBeenCalledWith(CONST.SEARCH.DATA_TYPES.INVOICE, {[transactionID]: true}); spyOnMergeTransactionIdsHighlightOnSearchRoute.mockReset(); });