From ded66a7c08444389e60d584edc707917cb3f38cd Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 28 Jul 2025 00:26:24 +0800 Subject: [PATCH 1/9] optimistically mark transaction as deleted and filter it out --- src/libs/SearchUIUtils.ts | 2 +- src/libs/actions/Search.ts | 23 +++++++++++++++++++++-- src/pages/Search/SearchPage.tsx | 2 +- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 14fe4733f01e..237d8ce84ec4 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -1473,7 +1473,7 @@ function getSortedReportActionData(data: ReportActionListItemType[]) { * 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.values(searchResults?.data).some((data) => 'transactionID' in data && (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 d2b27aff7621..00bac95ce2e3 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -441,8 +441,27 @@ 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 3f748195e886..a42b3875e030 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -381,11 +381,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(); }); }; From 679792528a6499c23b22820f836e995b4d533fc9 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Mon, 28 Jul 2025 00:37:09 +0800 Subject: [PATCH 2/9] prettier --- src/libs/actions/Search.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 00bac95ce2e3..7250d5f8e100 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -447,20 +447,24 @@ function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { { 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 + 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 + 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}); } From 9836943a29c878a85d32159855307a6696d32bb4 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 20 Aug 2025 14:28:21 +0700 Subject: [PATCH 3/9] refactor --- src/libs/SearchUIUtils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 133ad43cc83c..48ae96e01b09 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -1670,7 +1670,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.values(searchResults?.data).some((data) => 'transactionID' in data && (data as SearchTransaction)?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + 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, + ); } /** From 618f4cd85102aa5e5216e5ee65c6ac4e8ca4618d Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Wed, 20 Aug 2025 14:28:28 +0700 Subject: [PATCH 4/9] add test --- tests/unit/Search/SearchUIUtilsTest.ts | 62 +++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 08d44333a777..ebef8a745b38 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import createRandomTransaction from 'tests/utils/collections/transaction'; import ChatListItem from '@components/SelectionList/ChatListItem'; import TransactionGroupListItem from '@components/SelectionList/Search/TransactionGroupListItem'; import TransactionListItem from '@components/SelectionList/Search/TransactionListItem'; @@ -15,7 +16,7 @@ import IntlStore from '@src/languages/IntlStore'; import * as SearchUIUtils from '@src/libs/SearchUIUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; -import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; +import type {SearchDataTypes, SearchTransaction} from '@src/types/onyx/SearchResults'; import {formatPhoneNumber, localeCompare} from '../../utils/TestHelper'; import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; @@ -1691,6 +1692,65 @@ describe('SearchUIUtils', () => { }); }); + describe('Test isSearchResultsEmpty', () => { + it('should return true when all transactions have delete pending action', () => { + const results: OnyxTypes.SearchResults = { + data: { + personalDetailsList: {}, + transactions_1: { + accountID: 2074551, + amount: 0, + canDelete: false, + canHold: true, + canUnhold: false, + category: 'Employee Meals Remote (Fringe Benefit)', + action: '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; From ee976da14413451aeead334d6d464fc46d3fe027 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Thu, 21 Aug 2025 13:17:53 +0700 Subject: [PATCH 5/9] lint --- tests/unit/Search/SearchUIUtilsTest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index ebef8a745b38..c86eedc57fa3 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -1,5 +1,4 @@ import Onyx from 'react-native-onyx'; -import createRandomTransaction from 'tests/utils/collections/transaction'; import ChatListItem from '@components/SelectionList/ChatListItem'; import TransactionGroupListItem from '@components/SelectionList/Search/TransactionGroupListItem'; import TransactionListItem from '@components/SelectionList/Search/TransactionListItem'; @@ -16,7 +15,7 @@ import IntlStore from '@src/languages/IntlStore'; import * as SearchUIUtils from '@src/libs/SearchUIUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; -import type {SearchDataTypes, SearchTransaction} from '@src/types/onyx/SearchResults'; +import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import {formatPhoneNumber, localeCompare} from '../../utils/TestHelper'; import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; @@ -1697,6 +1696,7 @@ describe('SearchUIUtils', () => { const results: OnyxTypes.SearchResults = { data: { personalDetailsList: {}, + // eslint-disable-next-line @typescript-eslint/naming-convention transactions_1: { accountID: 2074551, amount: 0, From 9f2b69324b0b0c47aa059a9c162ae6bba06a7098 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 22 Aug 2025 14:37:48 +0700 Subject: [PATCH 6/9] fix test --- tests/unit/Search/SearchUIUtilsTest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index f873d597a983..7e0b7f108cf4 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -1841,6 +1841,7 @@ describe('SearchUIUtils', () => { canUnhold: false, category: 'Employee Meals Remote (Fringe Benefit)', action: 'approve', + allActions: ['approve'], comment: { comment: '', }, From d4398018d3dae48b1a4c2cc04843c974287d2c4b Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Fri, 22 Aug 2025 14:40:13 +0700 Subject: [PATCH 7/9] fix pending delete report or transaction is not hidden --- .../Search/TransactionGroupListItem.tsx | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/components/SelectionList/Search/TransactionGroupListItem.tsx b/src/components/SelectionList/Search/TransactionGroupListItem.tsx index 9db2f6da979f..46db56d2bc2c 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} - /> + + 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} + /> + )) )} From f59f6e45ea824864a7e288ae52af41e1132f8900 Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sun, 24 Aug 2025 22:24:15 +0700 Subject: [PATCH 8/9] rename --- tests/unit/Search/SearchUIUtilsTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 7e0b7f108cf4..cca50306667a 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -1833,7 +1833,7 @@ describe('SearchUIUtils', () => { data: { personalDetailsList: {}, // eslint-disable-next-line @typescript-eslint/naming-convention - transactions_1: { + transactions_1805965960759424086: { accountID: 2074551, amount: 0, canDelete: false, From 0ce4d8dd957ca071298032d5cf83fe4e1fb7283c Mon Sep 17 00:00:00 2001 From: Bernhard Owen Josephus Date: Sun, 24 Aug 2025 22:25:32 +0700 Subject: [PATCH 9/9] lint --- .../SelectionList/Search/TransactionGroupListItem.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionList/Search/TransactionGroupListItem.tsx b/src/components/SelectionList/Search/TransactionGroupListItem.tsx index 46db56d2bc2c..13629002e70a 100644 --- a/src/components/SelectionList/Search/TransactionGroupListItem.tsx +++ b/src/components/SelectionList/Search/TransactionGroupListItem.tsx @@ -209,9 +209,11 @@ function TransactionGroupListItem({ ) : ( groupItem.transactions.map((transaction) => ( - +