diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 6e33591d3d47..bf7310522657 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -3,7 +3,9 @@ import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {ASTNode, QueryFilter, QueryFilters, SearchDateFilterKeys, SearchFilterKey, SearchQueryJSON, SearchQueryString, SearchStatus, UserFriendlyKey} from '@components/Search/types'; import CONST from '@src/CONST'; +import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; +import SCREENS from '@src/SCREENS'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; import FILTER_KEYS, {DATE_FILTER_KEYS} from '@src/types/form/SearchAdvancedFiltersForm'; import type * as OnyxTypes from '@src/types/onyx'; @@ -15,6 +17,8 @@ import {convertToBackendAmount, convertToFrontendAmountAsInteger} from './Curren import localeCompare from './LocaleCompare'; import Log from './Log'; import {validateAmount} from './MoneyRequestUtils'; +import navigationRef from './Navigation/navigationRef'; +import type {SearchFullscreenNavigatorParamList} from './Navigation/types'; import {getPersonalDetailByEmail} from './PersonalDetailsUtils'; import {getCleanedTagName, getTagNamesFromTagsLists} from './PolicyUtils'; import {getReportName} from './ReportUtils'; @@ -852,6 +856,28 @@ function getQueryWithUpdatedValues(query: string, policyID?: string) { return buildSearchQueryString(standardizedQuery); } +function getCurrentSearchQueryJSON() { + const rootState = navigationRef.getRootState(); + const lastPolicyRoute = rootState?.routes?.findLast((route) => route.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR || route.name === NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR); + + if (!lastPolicyRoute) { + return; + } + + const lastSearchRoute = lastPolicyRoute.state?.routes.findLast((route) => route.name === SCREENS.SEARCH.ROOT); + if (!lastSearchRoute || !lastSearchRoute.params) { + return; + } + + const {q: searchParams} = lastSearchRoute.params as SearchFullscreenNavigatorParamList[typeof SCREENS.SEARCH.ROOT]; + const queryJSON = buildSearchQueryJSON(searchParams); + if (!queryJSON) { + return; + } + + return queryJSON; +} + /** * Converts a filter key from old naming (camelCase) to user friendly naming (kebab-case). * @@ -895,6 +921,7 @@ export { isCannedSearchQueryWithPolicyIDCheck, sanitizeSearchValue, getQueryWithUpdatedValues, + getCurrentSearchQueryJSON, getUserFriendlyKey, isDefaultExpensesQuery, shouldHighlight, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 7ca76b600752..305da947860c 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -157,6 +157,7 @@ import { shouldCreateNewMoneyRequestReport as shouldCreateNewMoneyRequestReportReportUtils, updateReportPreview, } from '@libs/ReportUtils'; +import {getCurrentSearchQueryJSON} from '@libs/SearchQueryUtils'; import {getSession} from '@libs/SessionUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; @@ -202,7 +203,7 @@ import type {QuickActionName} from '@src/types/onyx/QuickAction'; import type {InvoiceReceiver, InvoiceReceiverType} from '@src/types/onyx/Report'; import type ReportAction from '@src/types/onyx/ReportAction'; import type {OnyxData} from '@src/types/onyx/Request'; -import type {SearchPolicy, SearchReport, SearchTransaction} from '@src/types/onyx/SearchResults'; +import type {SearchDataTypes, SearchPolicy, SearchReport, SearchTransaction} from '@src/types/onyx/SearchResults'; import type {Comment, Receipt, ReceiptSource, Routes, SplitShares, TransactionChanges, TransactionCustomUnit, WaypointCollection} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import {clearByKey as clearPdfByOnyxKey} from './CachedPDFPaths'; @@ -446,6 +447,7 @@ type BuildOnyxDataForMoneyRequestParams = { policyParams?: BasePolicyParams; optimisticParams: MoneyRequestOptimisticParams; retryParams?: StartSplitBilActionParams | CreateTrackExpenseParams | RequestMoneyInformation | ReplaceReceipt; + participant?: Participant; }; type DistanceRequestTransactionParams = BaseTransactionParams & { @@ -533,6 +535,7 @@ type BuildOnyxDataForInvoiceParams = { }; companyName?: string; companyWebsite?: string; + participant?: Participant; }; type GetTrackExpenseInformationTransactionParams = { @@ -595,6 +598,11 @@ type ReplaceReceipt = { source: string; }; +type GetSearchOnyxUpdateParams = { + transaction: OnyxTypes.Transaction; + participant?: Participant; +}; + let allTransactions: NonNullable> = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, @@ -1184,6 +1192,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR policyParams = {}, optimisticParams, retryParams, + participant, } = moneyRequestParams; const {policy, policyCategories, policyTagList} = policyParams; const { @@ -1200,6 +1209,7 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR const outstandingChildRequest = getOutstandingChildRequest(iou.report); const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null])); const isMoneyRequestToManagerMcTest = isTestTransactionReport(iou.report); + const optimisticData: OnyxUpdate[] = []; const successData: OnyxUpdate[] = []; let newQuickAction: ValueOf; @@ -1689,6 +1699,15 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR }); } + const searchUpdate = getSearchOnyxUpdate({ + transaction, + participant, + }); + + if (searchUpdate) { + optimisticData.push({...searchUpdate}); + } + // We don't need to compute violations unless we're on a paid policy if (!policy || !isPaidGroupPolicy(policy)) { return [optimisticData, successData, failureData]; @@ -1724,7 +1743,8 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR /** Builds the Onyx data for an invoice */ function buildOnyxDataForInvoice(invoiceParams: BuildOnyxDataForInvoiceParams): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] { - const {chat, iou, transactionParams, policyParams, optimisticData: optimisticDataParams, companyName, companyWebsite} = invoiceParams; + const {chat, iou, transactionParams, policyParams, optimisticData: optimisticDataParams, companyName, companyWebsite, participant} = invoiceParams; + const transaction = transactionParams.transaction; const clearedPendingFields = Object.fromEntries(Object.keys(transactionParams.transaction.pendingFields ?? {}).map((key) => [key, null])); const optimisticData: OnyxUpdate[] = [ @@ -2104,6 +2124,15 @@ function buildOnyxDataForInvoice(invoiceParams: BuildOnyxDataForInvoiceParams): }); } + const searchUpdate = getSearchOnyxUpdate({ + transaction, + participant, + }); + + if (searchUpdate) { + optimisticData.push({...searchUpdate}); + } + // We don't need to compute violations unless we're on a paid policy if (!policyParams.policy || !isPaidGroupPolicy(policyParams.policy)) { return [optimisticData, successData, failureData]; @@ -2140,6 +2169,7 @@ type BuildOnyxDataForTrackExpenseParams = { existingTransactionThreadReportID?: string; actionableTrackExpenseWhisper?: OnyxInputValue; retryParams?: StartSplitBilActionParams | CreateTrackExpenseParams | RequestMoneyInformation | ReplaceReceipt; + participant?: Participant; }; /** Builds the Onyx data for track expense */ @@ -2152,6 +2182,7 @@ function buildOnyxDataForTrackExpense({ existingTransactionThreadReportID, actionableTrackExpenseWhisper, retryParams, + participant, }: BuildOnyxDataForTrackExpenseParams): [OnyxUpdate[], OnyxUpdate[], OnyxUpdate[]] { const {report: chatReport, previewAction: reportPreviewAction} = chat; const {report: iouReport, createdAction: iouCreatedAction, action: iouAction} = iou; @@ -2161,6 +2192,7 @@ function buildOnyxDataForTrackExpense({ const isScanRequest = isScanRequestTransactionUtils(transaction); const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null])); + const optimisticData: OnyxUpdate[] = []; const successData: OnyxUpdate[] = []; const failureData: OnyxUpdate[] = []; @@ -2550,6 +2582,15 @@ function buildOnyxDataForTrackExpense({ }); } + const searchUpdate = getSearchOnyxUpdate({ + transaction, + participant, + }); + + if (searchUpdate) { + optimisticData.push({...searchUpdate}); + } + // We don't need to compute violations unless we're on a paid policy if (!policy || !isPaidGroupPolicy(policy)) { return [optimisticData, successData, failureData]; @@ -2929,6 +2970,7 @@ function getSendInvoiceInformation( policyRecentlyUsedCategories: optimisticPolicyRecentlyUsedCategories, policyRecentlyUsedTags: optimisticPolicyRecentlyUsedTags, }, + participant: receiver, companyName, companyWebsite, }); @@ -3084,7 +3126,6 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma participants: [participant], transactionID: optimisticTransaction.transactionID, paymentType: isSelectedManagerMcTest(participant.login) ? CONST.IOU.PAYMENT_TYPE.ELSEWHERE : undefined, - existingTransactionThreadReportID: linkedTrackedExpenseReportAction?.childReportID, linkedTrackedExpenseReportAction, }); @@ -3122,6 +3163,7 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma // STEP 5: Build Onyx Data const [optimisticData, successData, failureData] = buildOnyxDataForMoneyRequest({ + participant, isNewChatReport, shouldCreateNewMoneyRequestReport, policyParams: { @@ -3577,6 +3619,7 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T // STEP 5: Build Onyx Data const trackExpenseOnyxData = buildOnyxDataForTrackExpense({ + participant, chat: {report: chatReport, previewAction: reportPreviewAction}, iou: {report: iouReport, action: iouAction, createdAction: optimisticCreatedActionForIOUReport}, transactionParams: { @@ -10513,6 +10556,47 @@ function resolveDuplicates(params: MergeDuplicatesParams) { API.write(WRITE_COMMANDS.RESOLVE_DUPLICATES, parameters, {optimisticData, failureData}); } +function getSearchOnyxUpdate({participant, transaction}: GetSearchOnyxUpdateParams) { + const toAccountID = participant?.accountID; + const fromAccountID = currentUserPersonalDetails?.accountID; + const currentSearchQueryJSON = getCurrentSearchQueryJSON(); + + if (currentSearchQueryJSON && toAccountID != null && fromAccountID != null) { + const validSearchTypes: SearchDataTypes[] = [CONST.SEARCH.DATA_TYPES.EXPENSE, CONST.SEARCH.DATA_TYPES.INVOICE]; + const shouldOptimisticallyUpdate = + currentSearchQueryJSON.status === CONST.SEARCH.STATUS.EXPENSE.ALL && validSearchTypes.includes(currentSearchQueryJSON.type) && currentSearchQueryJSON.flatFilters.length === 0; + + if (shouldOptimisticallyUpdate) { + return { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${currentSearchQueryJSON.hash}` as const, + value: { + data: { + [ONYXKEYS.PERSONAL_DETAILS_LIST]: { + [toAccountID]: { + accountID: toAccountID, + displayName: participant?.displayName, + login: participant?.login, + }, + [fromAccountID]: { + accountID: fromAccountID, + avatar: currentUserPersonalDetails?.avatar, + displayName: currentUserPersonalDetails?.displayName, + login: currentUserPersonalDetails?.login, + }, + }, + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: { + accountID: fromAccountID, + managerID: toAccountID, + ...transaction, + }, + }, + }, + }; + } + } +} + export { adjustRemainingSplitShares, getNextApproverAccountID,