diff --git a/src/components/SelectionList/Search/TransactionGroupListItem.tsx b/src/components/SelectionList/Search/TransactionGroupListItem.tsx index 9db2f6da979f..13629002e70a 100644 --- a/src/components/SelectionList/Search/TransactionGroupListItem.tsx +++ b/src/components/SelectionList/Search/TransactionGroupListItem.tsx @@ -170,8 +170,13 @@ function TransactionGroupListItem({ useSyncFocus(pressableRef, !!isFocused, shouldSyncFocus); + const pendingAction = + (item.pendingAction ?? groupItem.transactions.every((transaction) => transaction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)) + ? CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE + : undefined; + return ( - + ({ ) : ( groupItem.transactions.map((transaction) => ( - onCheckboxPress?.(transaction as unknown as TItem)} - columns={columns} - onButtonPress={() => { - openReportInRHP(transaction); - }} - style={[styles.noBorderRadius, shouldUseNarrowLayout ? [styles.p3, styles.pt2] : [styles.ph3, styles.pv1Half]]} - isReportItemChild - isInSingleTransactionReport={groupItem.transactions.length === 1} - /> + pendingAction={transaction.pendingAction} + > + onCheckboxPress?.(transaction as unknown as TItem)} + columns={columns} + onButtonPress={() => { + openReportInRHP(transaction); + }} + style={[styles.noBorderRadius, shouldUseNarrowLayout ? [styles.p3, styles.pt2] : [styles.ph3, styles.pv1Half]]} + isReportItemChild + isInSingleTransactionReport={groupItem.transactions.length === 1} + /> + )) )} diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 379fa082c0d2..6048e08e0995 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -1691,7 +1691,11 @@ function getSortedReportActionData(data: ReportActionListItemType[], localeCompa * Checks if the search results contain any data, useful for determining if the search results are empty. */ function isSearchResultsEmpty(searchResults: SearchResults) { - return !Object.keys(searchResults?.data).some((key) => key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION)); + return !Object.keys(searchResults?.data).some( + (key) => + key.startsWith(ONYXKEYS.COLLECTION.TRANSACTION) && + (searchResults?.data[key as keyof typeof searchResults.data] as SearchTransaction)?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + ); } /** diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index e17e00eff0ef..df964f2e89ab 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -543,8 +543,31 @@ function unholdMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { } function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { - const {optimisticData, finallyData} = getOnyxLoadingData(hash); - API.write(WRITE_COMMANDS.DELETE_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList}, {optimisticData, finallyData}); + const {optimisticData: loadingOptimisticData, finallyData} = getOnyxLoadingData(hash); + const optimisticData: OnyxUpdate[] = [ + ...loadingOptimisticData, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, + value: { + data: Object.fromEntries( + transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}]), + ) as Partial, + }, + }, + ]; + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, + value: { + data: Object.fromEntries( + transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {pendingAction: null}]), + ) as Partial, + }, + }, + ]; + API.write(WRITE_COMMANDS.DELETE_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList}, {optimisticData, failureData, finallyData}); } type Params = Record; diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index fa7be311fe30..f05b07944b7a 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -532,11 +532,11 @@ function SearchPage({route}: SearchPageProps) { } setIsDeleteExpensesConfirmModalVisible(false); - deleteMoneyRequestOnSearch(hash, selectedTransactionsKeys); // Translations copy for delete modal depends on amount of selected items, // We need to wait for modal to fully disappear before clearing them to avoid translation flicker between singular vs plural InteractionManager.runAfterInteractions(() => { + deleteMoneyRequestOnSearch(hash, selectedTransactionsKeys); clearSelectedTransactions(); }); }; diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 884df50ec043..cca50306667a 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -1827,6 +1827,67 @@ describe('SearchUIUtils', () => { }); }); + describe('Test isSearchResultsEmpty', () => { + it('should return true when all transactions have delete pending action', () => { + const results: OnyxTypes.SearchResults = { + data: { + personalDetailsList: {}, + // eslint-disable-next-line @typescript-eslint/naming-convention + transactions_1805965960759424086: { + accountID: 2074551, + amount: 0, + canDelete: false, + canHold: true, + canUnhold: false, + category: 'Employee Meals Remote (Fringe Benefit)', + action: 'approve', + allActions: ['approve'], + comment: { + comment: '', + }, + created: '2025-05-26', + currency: 'USD', + hasEReceipt: false, + isFromOneTransactionReport: true, + managerID: adminAccountID, + merchant: '(none)', + modifiedAmount: -1000, + modifiedCreated: '2025-05-22', + modifiedCurrency: 'USD', + modifiedMerchant: 'Costco Wholesale', + parentTransactionID: '', + policyID: '137DA25D273F2423', + receipt: { + source: 'https://www.expensify.com/receipts/fake.jpg', + state: CONST.IOU.RECEIPT_STATE.SCAN_COMPLETE, + }, + reportID: '6523565988285061', + reportType: 'expense', + tag: '', + transactionID: '1805965960759424086', + transactionThreadReportID: '4139222832581831', + transactionType: 'cash', + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, + }, + search: { + type: 'expense', + status: CONST.SEARCH.STATUS.EXPENSE.ALL, + offset: 0, + hasMoreResults: false, + hasResults: true, + isLoading: false, + columnsToShow: { + shouldShowCategoryColumn: true, + shouldShowTagColumn: true, + shouldShowTaxColumn: true, + }, + }, + }; + expect(SearchUIUtils.isSearchResultsEmpty(results)).toBe(true); + }); + }); + test('Should show `View` to overlimit approver', () => { Onyx.merge(ONYXKEYS.SESSION, {accountID: overlimitApproverAccountID}); searchResults.data[`policy_${policyID}`].role = CONST.POLICY.ROLE.USER;