From 88a080a7a44fbf1786761465a5da444e838131c6 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Wed, 24 Sep 2025 18:38:32 +0430 Subject: [PATCH 01/18] fix:(search) update optimistic transactions pending action in search snapshot --- src/components/MoneyReportHeader.tsx | 12 ++++++++++-- src/libs/actions/IOU.ts | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 6826b4dc6f034..d04d44c26dfa7 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -301,7 +301,7 @@ function MoneyReportHeader({ const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); const [isRejectEducationalModalVisible, setIsRejectEducationalModalVisible] = useState(false); - const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions, currentSearchQueryJSON, currentSearchKey} = useSearchContext(); + const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions, currentSearchQueryJSON, currentSearchKey, currentSearchHash} = useSearchContext(); const beginExportWithTemplate = useCallback( (templateName: string, templateType: string, transactionIDList: string[], policyID?: string) => { @@ -1306,7 +1306,15 @@ function MoneyReportHeader({ // it's deleting transaction but not the report which leads to bug (that is actually also on staging) // Money request should be deleted when interactions are done, to not show the not found page before navigating to goBackRoute InteractionManager.runAfterInteractions(() => { - deleteMoneyRequest(transaction?.transactionID, requestParentReportAction, duplicateTransactions, duplicateTransactionViolations); + deleteMoneyRequest( + transaction?.transactionID, + requestParentReportAction, + duplicateTransactions, + duplicateTransactionViolations, + undefined, + undefined, + currentSearchHash, + ); removeTransaction(transaction.transactionID); }); goBackRoute = getNavigationUrlOnMoneyRequestDelete(transaction.transactionID, requestParentReportAction, false); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index ec6d2b1458def..050764c721958 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -8175,6 +8175,7 @@ function deleteMoneyRequest( violations: OnyxCollection, isSingleTransactionView = false, transactionIDsPendingDeletion?: string[], + searchHash?: number, ) { if (!transactionID) { return; @@ -8208,6 +8209,20 @@ function deleteMoneyRequest( }, ]; + if (searchHash) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${searchHash}`, + value: { + data: { + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, + }, + }, + }); + } + optimisticData.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, From 970bc19885650d6d851280107c1ef2df10fbd343 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 25 Sep 2025 09:40:53 +0430 Subject: [PATCH 02/18] fix(search): update snapshot optimistic data when delete from search/moneyRequest page --- src/hooks/useSelectedTransactionsActions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index 818fecf5c8527..e5d0dbe39e32c 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -56,7 +56,7 @@ function useSelectedTransactionsActions({ beginExportWithTemplate: (templateName: string, templateType: string, transactionIDList: string[], policyID?: string) => void; }) { const {isOffline} = useNetworkWithOfflineStatus(); - const {selectedTransactionIDs, clearSelectedTransactions} = useSearchContext(); + const {selectedTransactionIDs, clearSelectedTransactions, currentSearchHash} = useSearchContext(); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false}); const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID, {canBeMissing: true}); @@ -107,12 +107,12 @@ function useSelectedTransactionsActions({ return; } - deleteMoneyRequest(transactionID, action, duplicateTransactions, duplicateTransactionViolations, false, deletedTransactionIDs); + deleteMoneyRequest(transactionID, action, duplicateTransactions, duplicateTransactionViolations, false, deletedTransactionIDs, currentSearchHash); deletedTransactionIDs.push(transactionID); }); clearSelectedTransactions(true); setIsDeleteModalVisible(false); - }, [duplicateTransactions, duplicateTransactionViolations, reportActions, selectedTransactionIDs, clearSelectedTransactions]); + }, [duplicateTransactions, duplicateTransactionViolations, reportActions, selectedTransactionIDs, clearSelectedTransactions, currentSearchHash]); const showDeleteModal = useCallback(() => { setIsDeleteModalVisible(true); From d77b81b3862e0415d905942bbeaced1c921e82fc Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 25 Sep 2025 11:00:14 +0430 Subject: [PATCH 03/18] fix(search): handle update optimistic data for search snapshot --- src/components/MoneyRequestHeader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 247d10dc413b4..ed8ca64b92c02 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -103,7 +103,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const isOnHold = isOnHoldTransactionUtils(transaction); const isDuplicate = isDuplicateTransactionUtils(transaction); const reportID = report?.reportID; - const {removeTransaction} = useSearchContext(); + const {removeTransaction, currentSearchHash} = useSearchContext(); const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; @@ -438,7 +438,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre isParentReportArchived, ); } else { - deleteMoneyRequest(transaction.transactionID, parentReportAction, duplicateTransactions, duplicateTransactionViolations, true); + deleteMoneyRequest(transaction.transactionID, parentReportAction, duplicateTransactions, duplicateTransactionViolations, true, undefined, currentSearchHash); removeTransaction(transaction.transactionID); } onBackButtonPress(); From 6e4c8d249dd57fe3459173de6bf91efafd007a44 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Tue, 30 Sep 2025 21:50:59 +0430 Subject: [PATCH 04/18] fix(search): update deleted transaction in all search snapshots --- src/components/MoneyReportHeader.tsx | 13 +---- src/components/MoneyRequestHeader.tsx | 13 +---- src/hooks/useSelectedTransactionsActions.ts | 6 +- src/libs/actions/IOU.ts | 64 ++++++++++++++++----- 4 files changed, 56 insertions(+), 40 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 34ffe9598b5ba..f4e177835f380 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -301,7 +301,7 @@ function MoneyReportHeader({ const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); const [isRejectEducationalModalVisible, setIsRejectEducationalModalVisible] = useState(false); - const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions, currentSearchQueryJSON, currentSearchKey, currentSearchHash} = useSearchContext(); + const {selectedTransactionIDs, removeTransaction, clearSelectedTransactions, currentSearchQueryJSON, currentSearchKey} = useSearchContext(); const beginExportWithTemplate = useCallback( (templateName: string, templateType: string, transactionIDList: string[], policyID?: string) => { @@ -1304,16 +1304,7 @@ function MoneyReportHeader({ // it's deleting transaction but not the report which leads to bug (that is actually also on staging) // Money request should be deleted when interactions are done, to not show the not found page before navigating to goBackRoute InteractionManager.runAfterInteractions(() => { - deleteMoneyRequest( - transaction?.transactionID, - requestParentReportAction, - duplicateTransactions, - duplicateTransactionViolations, - undefined, - undefined, - undefined, - currentSearchHash, - ); + deleteMoneyRequest(transaction?.transactionID, requestParentReportAction, duplicateTransactions, duplicateTransactionViolations); removeTransaction(transaction.transactionID); }); goBackRoute = getNavigationUrlOnMoneyRequestDelete(transaction.transactionID, requestParentReportAction, false); diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index e2a2f57e74f69..247d10dc413b4 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -103,7 +103,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const isOnHold = isOnHoldTransactionUtils(transaction); const isDuplicate = isDuplicateTransactionUtils(transaction); const reportID = report?.reportID; - const {removeTransaction, currentSearchHash} = useSearchContext(); + const {removeTransaction} = useSearchContext(); const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; @@ -438,16 +438,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre isParentReportArchived, ); } else { - deleteMoneyRequest( - transaction.transactionID, - parentReportAction, - duplicateTransactions, - duplicateTransactionViolations, - true, - undefined, - undefined, - currentSearchHash, - ); + deleteMoneyRequest(transaction.transactionID, parentReportAction, duplicateTransactions, duplicateTransactionViolations, true); removeTransaction(transaction.transactionID); } onBackButtonPress(); diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index 0897ee690d414..285760952b479 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -56,7 +56,7 @@ function useSelectedTransactionsActions({ beginExportWithTemplate: (templateName: string, templateType: string, transactionIDList: string[], policyID?: string) => void; }) { const {isOffline} = useNetworkWithOfflineStatus(); - const {selectedTransactionIDs, clearSelectedTransactions, currentSearchHash} = useSearchContext(); + const {selectedTransactionIDs, clearSelectedTransactions} = useSearchContext(); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false}); const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID, {canBeMissing: true}); @@ -107,12 +107,12 @@ function useSelectedTransactionsActions({ return; } - deleteMoneyRequest(transactionID, action, duplicateTransactions, duplicateTransactionViolations, false, deletedTransactionIDs, selectedTransactionIDs, currentSearchHash); + deleteMoneyRequest(transactionID, action, duplicateTransactions, duplicateTransactionViolations, false, deletedTransactionIDs, selectedTransactionIDs); deletedTransactionIDs.push(transactionID); }); clearSelectedTransactions(true); setIsDeleteModalVisible(false); - }, [duplicateTransactions, duplicateTransactionViolations, reportActions, selectedTransactionIDs, clearSelectedTransactions, currentSearchHash]); + }, [duplicateTransactions, duplicateTransactionViolations, reportActions, selectedTransactionIDs, clearSelectedTransactions]); const showDeleteModal = useCallback(() => { setIsDeleteModalVisible(true); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 20e4aaa3171d2..c70f15158f63e 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -232,6 +232,7 @@ import type {IOUAction, IOUActionParams, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxKey} from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Route} from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; @@ -786,6 +787,20 @@ Onyx.connect({ callback: (val) => (allPolicyCategories = val), }); +let allSnapshotKeys: OnyxKey[] = []; + +Onyx.connect({ + key: ONYXKEYS.COLLECTION.SNAPSHOT, + waitForCollectionCallback: true, + callback: (val) => { + if (!val) { + return; + } + + allSnapshotKeys = Object.keys(val) as OnyxKey[]; + }, +}); + const allPolicies: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, @@ -8223,7 +8238,6 @@ function deleteMoneyRequest( isSingleTransactionView = false, transactionIDsPendingDeletion?: string[], selectedTransactionIDs?: string[], - searchHash?: number, ) { if (!transactionID) { return; @@ -8257,20 +8271,6 @@ function deleteMoneyRequest( }, ]; - if (searchHash) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${searchHash}`, - value: { - data: { - [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - }, - }, - }, - }); - } - optimisticData.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, @@ -8403,6 +8403,40 @@ function deleteMoneyRequest( }, ]; + if (allSnapshotKeys?.length && allSnapshotKeys.length > 0) { + allSnapshotKeys.forEach((key) => { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key, + value: { + data: { + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, + }, + }, + }); + + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key, + value: { + data: { + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { + pendingAction: null, + }, + }, + }, + }); + + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key, + value: null, + }); + }); + } + if (reportPreviewAction?.reportActionID) { successData.push({ onyxMethod: Onyx.METHOD.MERGE, From 503d3fa9f7a6530aacc41243e9c07e5980f300f4 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Fri, 3 Oct 2025 19:41:10 +0430 Subject: [PATCH 05/18] fix(search): allow users to delete expenses and reports in offline mode --- src/pages/Search/SearchPage.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index a09bf193325a4..40f72953664af 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -408,7 +408,7 @@ function SearchPage({route}: SearchPageProps) { }); } - const shouldShowDeleteOption = !isOffline && selectedTransactionsKeys.every((id) => selectedTransactions[id].canDelete); + const shouldShowDeleteOption = selectedTransactionsKeys.every((id) => selectedTransactions[id].canDelete); if (shouldShowDeleteOption) { options.push({ @@ -417,11 +417,6 @@ function SearchPage({route}: SearchPageProps) { value: CONST.SEARCH.BULK_ACTION_TYPES.DELETE, shouldCloseModalOnSelect: true, onSelected: () => { - if (isOffline) { - setIsOfflineModalVisible(true); - return; - } - // Use InteractionManager to ensure this runs after the dropdown modal closes // eslint-disable-next-line deprecation/deprecation InteractionManager.runAfterInteractions(() => { From da5ba478cf946fca79ee86fac00d40046ea709e7 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Wed, 8 Oct 2025 10:47:58 +0430 Subject: [PATCH 06/18] fix(search): covered update optimistic data in bulk delete --- src/libs/actions/IOU.ts | 6 ---- src/libs/actions/Search.ts | 71 ++++++++++++++++++++++++++------------ 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 67565f98af532..87f07a8266231 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -8567,12 +8567,6 @@ function deleteMoneyRequest( }, }, }); - - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key, - value: null, - }); }); } diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index c35c1fa6397c2..602a3517c0c42 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -29,6 +29,7 @@ import playSound, {SOUNDS} from '@libs/Sound'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxKey} from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import {FILTER_KEYS} from '@src/types/form/SearchAdvancedFiltersForm'; import type {SearchAdvancedFiltersForm} from '@src/types/form/SearchAdvancedFiltersForm'; @@ -48,6 +49,20 @@ type OnyxSearchResponse = { }; }; +let allSnapshotKeys: OnyxKey[] = []; + +Onyx.connect({ + key: ONYXKEYS.COLLECTION.SNAPSHOT, + waitForCollectionCallback: true, + callback: (val) => { + if (!val) { + return; + } + + allSnapshotKeys = Object.keys(val) as OnyxKey[]; + }, +}); + function handleActionButtonPress( hash: number, item: TransactionListItemType | TransactionReportGroupListItemType, @@ -554,29 +569,39 @@ function unholdMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { 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, - }, - }, - ]; + const optimisticData: OnyxUpdate[] = [...loadingOptimisticData]; + const failureData: OnyxUpdate[] = []; + + if (allSnapshotKeys?.length && allSnapshotKeys.length > 0) { + allSnapshotKeys.forEach((key) => { + transactionIDList.forEach((transactionID) => { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key, + value: { + data: { + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, + }, + }, + }); + + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key, + value: { + data: { + [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { + pendingAction: null, + }, + }, + }, + }); + }); + }); + } + API.write(WRITE_COMMANDS.DELETE_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList}, {optimisticData, failureData, finallyData}); } From e7b0c61fa9eeb2dab766e99021b055716507684f Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 16 Oct 2025 08:12:30 +0430 Subject: [PATCH 07/18] delete transaction and report if needed --- src/libs/actions/Search.ts | 48 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 1405144edfe3f..df7ec925a34a9 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -58,6 +58,20 @@ type OnyxSearchResponse = { }; }; +let transactions: NonNullable> = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (value) => { + if (!value) { + transactions = {}; + return; + } + + transactions = value; + }, +}); + let allSnapshotKeys: OnyxKey[] = []; Onyx.connect({ @@ -589,8 +603,8 @@ function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { const failureData: OnyxUpdate[] = []; if (allSnapshotKeys?.length && allSnapshotKeys.length > 0) { - allSnapshotKeys.forEach((key) => { - transactionIDList.forEach((transactionID) => { + transactionIDList.forEach((transactionID) => { + allSnapshotKeys.forEach((key) => { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key, @@ -615,6 +629,36 @@ function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { }, }); }); + + const transaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + + if (transaction) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: {...transaction, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}, + }); + + failureData.push({ + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: {...transaction, pendingAction: null}, + }); + + const shouldDeleteIOUReport = getReportTransactions(transaction?.reportID).length === 1; + + if (shouldDeleteIOUReport) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`, + value: { + pendingFields: { + preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, + }, + }); + } + } }); } From 7f3d109f4fbb14b5075104e4a067745b53197a9f Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Wed, 12 Nov 2025 16:05:52 +0430 Subject: [PATCH 08/18] refactor: provided transactions and snapshotKeys through arguments instead of Onyx.connect --- src/hooks/useDeleteTransactions.ts | 15 ++++-- src/libs/actions/IOU.ts | 27 +++-------- src/libs/actions/Search.ts | 76 ++++++++++-------------------- src/pages/Search/SearchPage.tsx | 13 ++++- 4 files changed, 56 insertions(+), 75 deletions(-) diff --git a/src/hooks/useDeleteTransactions.ts b/src/hooks/useDeleteTransactions.ts index 2174391478e42..c12db30c5bcc9 100644 --- a/src/hooks/useDeleteTransactions.ts +++ b/src/hooks/useDeleteTransactions.ts @@ -1,4 +1,4 @@ -import {useCallback} from 'react'; +import {useCallback, useMemo} from 'react'; import type {OnyxCollection} from 'react-native-onyx'; import {deleteMoneyRequest, getIOUActionForTransactions, getIOURequestPolicyID, initSplitExpenseItemData, updateSplitTransactions} from '@libs/actions/IOU'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; @@ -6,7 +6,7 @@ import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils import {isArchivedReport} from '@libs/ReportUtils'; import {getChildTransactions, getOriginalTransactionWithSplitInfo} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; +import ONYXKEYS, {OnyxKey} from '@src/ONYXKEYS'; import type {Policy, Report, ReportAction, Transaction, TransactionViolations} from '@src/types/onyx'; import useArchivedReportsIdSet from './useArchivedReportsIdSet'; import useOnyx from './useOnyx'; @@ -27,12 +27,21 @@ type UseDeleteTransactionsParams = { function useDeleteTransactions({report, reportActions, policy}: UseDeleteTransactionsParams) { const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false}); const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true}); + const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT); const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${getNonEmptyStringOnyxID(report?.policyID)}`, {canBeMissing: true}); const [allPolicyRecentlyUsedCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES, {canBeMissing: true}); const [allReportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {canBeMissing: true}); const archivedReportsIdSet = useArchivedReportsIdSet(); + const allSnapshotKeys = useMemo(() => { + if (!allSnapshots) { + return []; + } + + return Object.keys(allSnapshots || {}) as OnyxKey[]; + }, [allSnapshots]); + /** * Delete transactions by IDs * @param transactionIDs - Array of transaction IDs to delete @@ -147,7 +156,7 @@ function useDeleteTransactions({report, reportActions, policy}: UseDeleteTransac iouReport, chatReport, isChatIOUReportArchived, - isSingleTransactionView, + allSnapshotKeys, deletedTransactionIDs, transactionIDs, ); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index d1f1b0e0e39f2..fc542c8e69ad6 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -769,6 +769,7 @@ type DeleteTrackExpenseParams = { isSingleTransactionView: boolean | undefined; isChatReportArchived: boolean | undefined; isChatIOUReportArchived: boolean | undefined; + allSnapshotKeys: OnyxKey[]; }; let allBetas: OnyxEntry; @@ -823,20 +824,6 @@ Onyx.connect({ }, }); -let allSnapshotKeys: OnyxKey[] = []; - -Onyx.connect({ - key: ONYXKEYS.COLLECTION.SNAPSHOT, - waitForCollectionCallback: true, - callback: (val) => { - if (!val) { - return; - } - - allSnapshotKeys = Object.keys(val) as OnyxKey[]; - }, -}); - const allPolicies: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, @@ -8626,7 +8613,6 @@ function cleanUpMoneyRequest( * * @param transactionID - The transactionID of IOU * @param reportAction - The reportAction of the transaction in the IOU report - * @param isSingleTransactionView - whether we are in the transaction thread report * @return the url to navigate back once the money request is deleted */ function deleteMoneyRequest( @@ -8637,7 +8623,7 @@ function deleteMoneyRequest( iouReport: OnyxEntry, chatReport: OnyxEntry, isChatIOUReportArchived: boolean | undefined, - isSingleTransactionView = false, + allSnapshotKeys: OnyxKey[], transactionIDsPendingDeletion?: string[], selectedTransactionIDs?: string[], ) { @@ -8659,7 +8645,7 @@ function deleteMoneyRequest( reportPreviewAction, } = prepareToCleanUpMoneyRequest(transactionID, reportAction, iouReport, chatReport, isChatIOUReportArchived, false, transactionIDsPendingDeletion, selectedTransactionIDs); - const urlToNavigateBack = getNavigationUrlOnMoneyRequestDelete(transactionID, reportAction, iouReport, chatReport, isChatIOUReportArchived, isSingleTransactionView); + const urlToNavigateBack = getNavigationUrlOnMoneyRequestDelete(transactionID, reportAction, iouReport, chatReport, isChatIOUReportArchived); // STEP 2: Build Onyx data // The logic mostly resembles the cleanUpMoneyRequest function @@ -8812,7 +8798,7 @@ function deleteMoneyRequest( }, }, }, - }); + } as unknown as OnyxUpdate); failureData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -8824,7 +8810,7 @@ function deleteMoneyRequest( }, }, }, - }); + } as unknown as OnyxUpdate); }); } @@ -8969,6 +8955,7 @@ function deleteTrackExpense({ isSingleTransactionView = false, isChatReportArchived, isChatIOUReportArchived, + allSnapshotKeys, }: DeleteTrackExpenseParams) { if (!chatReportID || !transactionID) { return; @@ -8987,7 +8974,7 @@ function deleteTrackExpense({ // STEP 1: Get all collections we're updating if (!isSelfDM(chatReport)) { - deleteMoneyRequest(transactionID, reportAction, transactions, violations, iouReport, chatIOUReport, isChatIOUReportArchived, isSingleTransactionView); + deleteMoneyRequest(transactionID, reportAction, transactions, violations, iouReport, chatIOUReport, isChatIOUReportArchived, allSnapshotKeys); return urlToNavigateBack; } diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 56e72022477f1..7df810dbd2103 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -60,34 +60,6 @@ type OnyxSearchResponse = { }; }; -let transactions: NonNullable> = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (value) => { - if (!value) { - transactions = {}; - return; - } - - transactions = value; - }, -}); - -let allSnapshotKeys: OnyxKey[] = []; - -Onyx.connect({ - key: ONYXKEYS.COLLECTION.SNAPSHOT, - waitForCollectionCallback: true, - callback: (val) => { - if (!val) { - return; - } - - allSnapshotKeys = Object.keys(val) as OnyxKey[]; - }, -}); - type TransactionPreviewData = { hasParentReport: boolean; hasParentReportAction: boolean; @@ -622,7 +594,7 @@ function unholdMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { API.write(WRITE_COMMANDS.UNHOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList}, {optimisticData, finallyData}); } -function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { +function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[], allSnapshotKeys: OnyxKey[], transactions: OnyxCollection) { const {optimisticData: loadingOptimisticData, finallyData} = getOnyxLoadingData(hash); const optimisticData: OnyxUpdate[] = [...loadingOptimisticData]; const failureData: OnyxUpdate[] = []; @@ -655,33 +627,35 @@ function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { } as unknown as OnyxUpdate); }); - const transaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + if (transactions) { + const transaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; - if (transaction) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - value: {...transaction, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}, - }); + if (transaction) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: {...transaction, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE}, + }); - failureData.push({ - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - value: {...transaction, pendingAction: null}, - }); + failureData.push({ + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, + value: {...transaction, pendingAction: null}, + }); - const shouldDeleteIOUReport = getReportTransactions(transaction?.reportID).length === 1; + const shouldDeleteIOUReport = getReportTransactions(transaction?.reportID).length === 1; - if (shouldDeleteIOUReport) { - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`, - value: { - pendingFields: { - preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + if (shouldDeleteIOUReport) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`, + value: { + pendingFields: { + preview: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + }, }, - }, - }); + }); + } } } }); diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index b9a174b8dd259..6b46b9aead681 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -79,6 +79,7 @@ import {openOldDotLink} from '@userActions/Link'; import {buildOptimisticTransactionAndCreateDraft} from '@userActions/TransactionEdit'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxKey} from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {SearchResults, Transaction} from '@src/types/onyx'; @@ -108,6 +109,8 @@ function SearchPage({route}: SearchPageProps) { const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {canBeMissing: true}); const personalPolicy = usePersonalPolicy(); const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); + const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT); + const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: true}); const [integrationsExportTemplates] = useOnyx(ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES, {canBeMissing: true}); const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS, {canBeMissing: true}); const [isOfflineModalVisible, setIsOfflineModalVisible] = useState(false); @@ -637,6 +640,14 @@ function SearchPage({route}: SearchPageProps) { isBetaBulkPayEnabled, ]); + const allSnapshotKeys = useMemo(() => { + if (!allSnapshots) { + return []; + } + + return Object.keys(allSnapshots || {}) as OnyxKey[]; + }, [allSnapshots]); + const handleDeleteExpenses = () => { if (selectedTransactionsKeys.length === 0 || !hash) { return; @@ -648,7 +659,7 @@ function SearchPage({route}: SearchPageProps) { // We need to wait for modal to fully disappear before clearing them to avoid translation flicker between singular vs plural // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { - deleteMoneyRequestOnSearch(hash, selectedTransactionsKeys); + deleteMoneyRequestOnSearch(hash, selectedTransactionsKeys, allSnapshotKeys, transactions); clearSelectedTransactions(); }); }; From a1938ce3214deb13350eb5c7ad5cc1550b89f0d6 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Wed, 12 Nov 2025 16:16:32 +0430 Subject: [PATCH 09/18] added isSingleTransactionView back as non intentional removed --- src/hooks/useDeleteTransactions.ts | 1 + src/libs/actions/IOU.ts | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hooks/useDeleteTransactions.ts b/src/hooks/useDeleteTransactions.ts index c12db30c5bcc9..ba8ccb58787c8 100644 --- a/src/hooks/useDeleteTransactions.ts +++ b/src/hooks/useDeleteTransactions.ts @@ -157,6 +157,7 @@ function useDeleteTransactions({report, reportActions, policy}: UseDeleteTransac chatReport, isChatIOUReportArchived, allSnapshotKeys, + isSingleTransactionView, deletedTransactionIDs, transactionIDs, ); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index fc542c8e69ad6..137b3864e9354 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -8613,6 +8613,7 @@ function cleanUpMoneyRequest( * * @param transactionID - The transactionID of IOU * @param reportAction - The reportAction of the transaction in the IOU report + * @param isSingleTransactionView - whether we are in the transaction thread report * @return the url to navigate back once the money request is deleted */ function deleteMoneyRequest( @@ -8624,6 +8625,7 @@ function deleteMoneyRequest( chatReport: OnyxEntry, isChatIOUReportArchived: boolean | undefined, allSnapshotKeys: OnyxKey[], + isSingleTransactionView = false, transactionIDsPendingDeletion?: string[], selectedTransactionIDs?: string[], ) { @@ -8645,7 +8647,7 @@ function deleteMoneyRequest( reportPreviewAction, } = prepareToCleanUpMoneyRequest(transactionID, reportAction, iouReport, chatReport, isChatIOUReportArchived, false, transactionIDsPendingDeletion, selectedTransactionIDs); - const urlToNavigateBack = getNavigationUrlOnMoneyRequestDelete(transactionID, reportAction, iouReport, chatReport, isChatIOUReportArchived); + const urlToNavigateBack = getNavigationUrlOnMoneyRequestDelete(transactionID, reportAction, iouReport, chatReport, isChatIOUReportArchived, isSingleTransactionView); // STEP 2: Build Onyx data // The logic mostly resembles the cleanUpMoneyRequest function @@ -8974,7 +8976,7 @@ function deleteTrackExpense({ // STEP 1: Get all collections we're updating if (!isSelfDM(chatReport)) { - deleteMoneyRequest(transactionID, reportAction, transactions, violations, iouReport, chatIOUReport, isChatIOUReportArchived, allSnapshotKeys); + deleteMoneyRequest(transactionID, reportAction, transactions, violations, iouReport, chatIOUReport, isChatIOUReportArchived, allSnapshotKeys, isSingleTransactionView); return urlToNavigateBack; } From b23d2213223882fdd1de4cff6561e12105f96151 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Mon, 17 Nov 2025 21:58:32 +0430 Subject: [PATCH 10/18] fixed type issues. --- src/components/MoneyRequestHeader.tsx | 11 +++++++++ src/pages/ReportDetailsPage.tsx | 12 ++++++++++ .../PopoverReportActionContextMenu.tsx | 17 +++++++++++++- tests/actions/IOUTest.ts | 23 ++++++++++--------- 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 1709fe77b8735..b354651ac8128 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -44,6 +44,7 @@ import {dismissRejectUseExplanation} from '@userActions/IOU'; import {markAsCash as markAsCashAction} from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxKey} from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import type {Policy, Report, ReportAction} from '@src/types/onyx'; @@ -107,6 +108,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre > | null>(null); const [dismissedRejectUseExplanation] = useOnyx(ONYXKEYS.NVP_DISMISSED_REJECT_USE_EXPLANATION, {canBeMissing: true}); const [dismissedHoldUseExplanation] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {canBeMissing: true}); + const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT); const shouldShowLoadingBar = useLoadingBarVisibility(); const styles = useThemeStyles(); const theme = useTheme(); @@ -260,6 +262,14 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre ), }; + const allSnapshotKeys = useMemo(() => { + if (!allSnapshots) { + return []; + } + + return Object.keys(allSnapshots || {}) as OnyxKey[]; + }, [allSnapshots]); + const secondaryActions = useMemo(() => { if (!transaction || !parentReportAction || !parentReport) { return []; @@ -483,6 +493,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre isSingleTransactionView: true, isChatReportArchived: isParentReportArchived, isChatIOUReportArchived, + allSnapshotKeys, }); } else { deleteTransactions([transaction.transactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash, true); diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 8b645d1ace3bf..0ee2c33c3f25e 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -117,6 +117,7 @@ import {canActionTask, canModifyTask, deleteTask, reopenTask} from '@userActions import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxKey} from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -158,6 +159,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, {canBeMissing: true}); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`, {canBeMissing: true}); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true}); + const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT); const parentReportAction = useParentReportAction(report); @@ -805,6 +807,14 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail ); + const allSnapshotKeys = useMemo(() => { + if (!allSnapshots) { + return []; + } + + return Object.keys(allSnapshots || {}) as OnyxKey[]; + }, [allSnapshots]); + const deleteTransaction = useCallback(() => { if (caseID === CASES.DEFAULT) { deleteTask(report, isReportArchived, currentUserPersonalDetails.accountID); @@ -830,6 +840,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail isSingleTransactionView, isChatReportArchived: isMoneyRequestReportArchived, isChatIOUReportArchived, + allSnapshotKeys, }); } else if (iouTransactionID) { deleteTransactions([iouTransactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash, isSingleTransactionView); @@ -853,6 +864,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail deleteTransactions, currentSearchHash, isChatIOUReportArchived, + allSnapshotKeys, ]); // Where to navigate back to after deleting the transaction and its report. diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index 9f6870c360334..6ae82782dcef4 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -1,6 +1,6 @@ /* eslint-disable react-compiler/react-compiler */ import type {ForwardedRef} from 'react'; -import React, {useCallback, useContext, useEffect, useImperativeHandle, useRef, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; /* eslint-disable no-restricted-imports */ import type {EmitterSubscription, GestureResponderEvent, NativeTouchEvent, View} from 'react-native'; @@ -27,6 +27,7 @@ import {getOriginalMessage, isMoneyRequestAction, isReportPreviewAction, isTrack import {getOriginalReportID} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxKey} from '@src/ONYXKEYS'; import type {AnchorDimensions} from '@src/styles'; import type {ReportAction} from '@src/types/onyx'; import type {Location} from '@src/types/utils/Layout'; @@ -48,6 +49,9 @@ type PopoverReportActionContextMenuProps = { function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuProps) { const {translate} = useLocalize(); + + const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT); + const reportIDRef = useRef(undefined); const typeRef = useRef(undefined); const reportActionRef = useRef> | null>(null); @@ -340,6 +344,15 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro } ancestorsRef.current = ancestors; }, [originalReport, ancestors]); + + const allSnapshotKeys = useMemo(() => { + if (!allSnapshots) { + return []; + } + + return Object.keys(allSnapshots || {}) as OnyxKey[]; + }, [allSnapshots]); + const confirmDeleteAndHideModal = useCallback(() => { callbackWhenDeleteModalHide.current = runAndResetCallback(onConfirmDeleteModal.current); const reportAction = reportActionRef.current; @@ -358,6 +371,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro isSingleTransactionView: undefined, isChatReportArchived: isReportArchived, isChatIOUReportArchived, + allSnapshotKeys, }); } else if (originalMessage?.IOUTransactionID) { deleteTransactions([originalMessage.IOUTransactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash); @@ -384,6 +398,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro deleteTransactions, currentSearchHash, isOriginalReportArchived, + allSnapshotKeys, ]); const hideDeleteModal = () => { diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 24d402ab664c4..b2d2a9393ef4b 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -3997,7 +3997,7 @@ describe('actions/IOU', () => { if (transaction && createIOUAction) { // When the expense is deleted - deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, true, undefined); + deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, true, []); } await waitForBatchedUpdates(); @@ -4076,7 +4076,7 @@ describe('actions/IOU', () => { if (transaction && createIOUAction) { // When the IOU expense is deleted - deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, true); + deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, true, []); } await waitForBatchedUpdates(); @@ -4139,7 +4139,7 @@ describe('actions/IOU', () => { // When we attempt to delete an expense from the IOU report mockFetch?.pause?.(); if (transaction && createIOUAction) { - deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined); + deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); } await waitForBatchedUpdates(); @@ -4234,7 +4234,7 @@ describe('actions/IOU', () => { if (transaction && createIOUAction) { // When Deleting an expense - deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined); + deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); } await waitForBatchedUpdates(); @@ -4359,7 +4359,7 @@ describe('actions/IOU', () => { if (transaction && createIOUAction) { // When Deleting an expense - deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined); + deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); } await waitForBatchedUpdates(); @@ -4433,7 +4433,7 @@ describe('actions/IOU', () => { if (transaction && createIOUAction) { // When deleting expense - deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined); + deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); } await waitForBatchedUpdates(); @@ -4584,7 +4584,7 @@ describe('actions/IOU', () => { mockFetch?.pause?.(); if (transaction && createIOUAction) { // When we delete the expense - deleteMoneyRequest(transaction.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined); + deleteMoneyRequest(transaction.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); } await waitForBatchedUpdates(); @@ -4677,7 +4677,7 @@ describe('actions/IOU', () => { mockFetch?.pause?.(); jest.advanceTimersByTime(10); if (transaction && createIOUAction) { - deleteMoneyRequest(transaction.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined); + deleteMoneyRequest(transaction.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); } await waitForBatchedUpdates(); @@ -4754,7 +4754,7 @@ describe('actions/IOU', () => { let navigateToAfterDelete; if (transaction && createIOUAction) { - navigateToAfterDelete = deleteMoneyRequest(transaction.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, true); + navigateToAfterDelete = deleteMoneyRequest(transaction.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, [], true); } let allReports = await new Promise>((resolve) => { @@ -4802,7 +4802,7 @@ describe('actions/IOU', () => { let navigateToAfterDelete; if (transaction && createIOUAction) { // When we delete the expense and we should delete the IOU report - navigateToAfterDelete = deleteMoneyRequest(transaction.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined); + navigateToAfterDelete = deleteMoneyRequest(transaction.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); } // Then we expect to navigate to the chat report expect(chatReport?.reportID).not.toBeUndefined(); @@ -4866,7 +4866,7 @@ describe('actions/IOU', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, expenseReport); const selectedTransactionIDs = [transaction1.transactionID, transaction2.transactionID]; - deleteMoneyRequest(transaction1.transactionID, moneyRequestAction1, {}, {}, expenseReport, expenseReport, undefined, undefined, [], selectedTransactionIDs); + deleteMoneyRequest(transaction1.transactionID, moneyRequestAction1, {}, {}, expenseReport, expenseReport, undefined, [], undefined, [], selectedTransactionIDs); deleteMoneyRequest( transaction2.transactionID, moneyRequestAction2, @@ -4875,6 +4875,7 @@ describe('actions/IOU', () => { expenseReport, expenseReport, undefined, + [], undefined, [transaction1.transactionID], selectedTransactionIDs, From abebc6e9f899725a94173fb81b810b0be6c1473e Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Mon, 17 Nov 2025 23:48:12 +0430 Subject: [PATCH 11/18] fixed eslint errors --- src/components/MoneyRequestHeader.tsx | 2 +- src/hooks/useDeleteTransactions.ts | 33 ++-- src/libs/actions/IOU.ts | 44 ++++-- src/pages/ReportDetailsPage.tsx | 2 +- src/pages/Search/SearchPage.tsx | 21 ++- .../PopoverReportActionContextMenu.tsx | 2 +- tests/actions/IOUTest.ts | 146 +++++++++++++++--- 7 files changed, 198 insertions(+), 52 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index b354651ac8128..422894c8614f8 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -108,7 +108,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre > | null>(null); const [dismissedRejectUseExplanation] = useOnyx(ONYXKEYS.NVP_DISMISSED_REJECT_USE_EXPLANATION, {canBeMissing: true}); const [dismissedHoldUseExplanation] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {canBeMissing: true}); - const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT); + const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT, {canBeMissing: true}); const shouldShowLoadingBar = useLoadingBarVisibility(); const styles = useThemeStyles(); const theme = useTheme(); diff --git a/src/hooks/useDeleteTransactions.ts b/src/hooks/useDeleteTransactions.ts index 4245929672b44..8f81650255529 100644 --- a/src/hooks/useDeleteTransactions.ts +++ b/src/hooks/useDeleteTransactions.ts @@ -5,7 +5,8 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {getChildTransactions, getOriginalTransactionWithSplitInfo} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; -import ONYXKEYS, {OnyxKey} from '@src/ONYXKEYS'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {OnyxKey} from '@src/ONYXKEYS'; import type {Policy, Report, ReportAction, Transaction, TransactionViolations} from '@src/types/onyx'; import useArchivedReportsIdSet from './useArchivedReportsIdSet'; import useOnyx from './useOnyx'; @@ -27,7 +28,7 @@ type UseDeleteTransactionsParams = { function useDeleteTransactions({report, reportActions, policy}: UseDeleteTransactionsParams) { const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false}); const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true}); - const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT); + const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT, {canBeMissing: true}); const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${getNonEmptyStringOnyxID(report?.policyID)}`, {canBeMissing: true}); const [allPolicyRecentlyUsedCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES, {canBeMissing: true}); const [allReportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {canBeMissing: true}); @@ -145,19 +146,19 @@ function useDeleteTransactions({report, reportActions, policy}: UseDeleteTransac const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`]; const chatIOUReportID = chatReport?.reportID; const isChatIOUReportArchived = archivedReportsIdSet.has(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${chatIOUReportID}`); - deleteMoneyRequest( + deleteMoneyRequest({ transactionID, - action, - duplicateTransactions, - duplicateTransactionViolations, + reportAction: action, + transactions: duplicateTransactions, + violations: duplicateTransactionViolations, iouReport, chatReport, isChatIOUReportArchived, allSnapshotKeys, isSingleTransactionView, - deletedTransactionIDs, - transactionIDs, - ); + transactionIDsPendingDeletion: deletedTransactionIDs, + selectedTransactionIDs: transactionIDs, + }); deletedTransactionIDs.push(transactionID); if (action.childReportID) { deletedTransactionThreadReportIDs.add(action.childReportID); @@ -166,7 +167,19 @@ function useDeleteTransactions({report, reportActions, policy}: UseDeleteTransac return Array.from(deletedTransactionThreadReportIDs); }, - [reportActions, allTransactions, allReports, report, allReportNameValuePairs, allPolicyRecentlyUsedCategories, policyCategories, policy, archivedReportsIdSet, isBetaEnabled], + [ + reportActions, + allTransactions, + allReports, + report, + allReportNameValuePairs, + allPolicyRecentlyUsedCategories, + policyCategories, + policy, + archivedReportsIdSet, + isBetaEnabled, + allSnapshotKeys, + ], ); return { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 7ec3aad441d4a..83b67ca51fde0 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -785,6 +785,20 @@ type DeleteTrackExpenseParams = { allSnapshotKeys: OnyxKey[]; }; +type DeleteMoneyRequestInputParams = { + transactionID: string | undefined; + reportAction: OnyxTypes.ReportAction; + transactions: OnyxCollection; + violations: OnyxCollection; + iouReport: OnyxEntry; + chatReport: OnyxEntry; + isChatIOUReportArchived: boolean | undefined; + allSnapshotKeys: OnyxKey[]; + isSingleTransactionView?: boolean; + transactionIDsPendingDeletion?: string[]; + selectedTransactionIDs?: string[]; +}; + let allTransactions: NonNullable> = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, @@ -8672,19 +8686,21 @@ function cleanUpMoneyRequest( * @param isSingleTransactionView - whether we are in the transaction thread report * @return the url to navigate back once the money request is deleted */ -function deleteMoneyRequest( - transactionID: string | undefined, - reportAction: OnyxTypes.ReportAction, - transactions: OnyxCollection, - violations: OnyxCollection, - iouReport: OnyxEntry, - chatReport: OnyxEntry, - isChatIOUReportArchived: boolean | undefined, - allSnapshotKeys: OnyxKey[], - isSingleTransactionView = false, - transactionIDsPendingDeletion?: string[], - selectedTransactionIDs?: string[], -) { +function deleteMoneyRequest(params: DeleteMoneyRequestInputParams): Route | undefined { + const { + transactionID, + reportAction, + transactions, + violations, + iouReport, + chatReport, + isChatIOUReportArchived, + allSnapshotKeys, + isSingleTransactionView = false, + transactionIDsPendingDeletion, + selectedTransactionIDs, + } = params; + if (!transactionID) { return; } @@ -9032,7 +9048,7 @@ function deleteTrackExpense({ // STEP 1: Get all collections we're updating if (!isSelfDM(chatReport)) { - deleteMoneyRequest(transactionID, reportAction, transactions, violations, iouReport, chatIOUReport, isChatIOUReportArchived, allSnapshotKeys, isSingleTransactionView); + deleteMoneyRequest({transactionID, reportAction, transactions, violations, iouReport, chatReport: chatIOUReport, isChatIOUReportArchived, allSnapshotKeys, isSingleTransactionView}); return urlToNavigateBack; } diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 0ee2c33c3f25e..72012ae974c5d 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -159,7 +159,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID}`, {canBeMissing: true}); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.chatReportID}`, {canBeMissing: true}); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true}); - const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT); + const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT, {canBeMissing: true}); const parentReportAction = useParentReportAction(report); diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 154bf221790db..7f08953c406fb 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -1,5 +1,7 @@ import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import type {FC} from 'react'; import {InteractionManager, View} from 'react-native'; +import type {SvgProps} from 'react-native-svg'; import type {ValueOf} from 'type-fest'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; @@ -8,7 +10,6 @@ import DecisionModal from '@components/DecisionModal'; import DragAndDropConsumer from '@components/DragAndDrop/Consumer'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; import DropZoneUI from '@components/DropZone/DropZoneUI'; -import * as Expensicons from '@components/Icon/Expensicons'; import type {PaymentMethodType} from '@components/KYCWall/types'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -24,6 +25,7 @@ import {usePlaybackContext} from '@components/VideoPlayerContexts/PlaybackContex import useBulkPayOptions from '@hooks/useBulkPayOptions'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useFilesValidation from '@hooks/useFilesValidation'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; import useNetwork from '@hooks/useNetwork'; @@ -297,6 +299,20 @@ function SearchPage({route}: SearchPageProps) { [clearSelectedTransactions, hash, isOffline, lastPaymentMethods, selectedReports, selectedTransactions, policies, formatPhoneNumber], ); + const Expensicons = useMemoizedLazyExpensifyIcons([ + 'ArrowRight', + 'DocumentMerge', + 'Export', + 'MoneyBag', + 'Send', + 'Table', + 'ThumbsUp', + 'Trashcan', + 'Stopwatch', + 'SmartScan', + 'Exclamation', + ]); + const headerButtonsOptions = useMemo(() => { if (selectedTransactionsKeys.length === 0 || status == null || !hash) { return CONST.EMPTY_ARRAY as unknown as Array>; @@ -373,7 +389,7 @@ function SearchPage({route}: SearchPageProps) { const exportButtonOption: DropdownOption & Pick = { icon: Expensicons.Export, - rightIcon: Expensicons.ArrowRight, + rightIcon: Expensicons.ArrowRight as FC, text: translate('common.export'), backButtonText: translate('common.export'), value: CONST.SEARCH.BULK_ACTION_TYPES.EXPORT, @@ -623,6 +639,7 @@ function SearchPage({route}: SearchPageProps) { selectedPolicyIDs, selectedReportIDs, selectedTransactionReportIDs, + Expensicons, ]); const allSnapshotKeys = useMemo(() => { diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index 6ae82782dcef4..afce969a5a205 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -50,7 +50,7 @@ type PopoverReportActionContextMenuProps = { function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuProps) { const {translate} = useLocalize(); - const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT); + const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT, {canBeMissing: true}); const reportIDRef = useRef(undefined); const typeRef = useRef(undefined); diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index b2d2a9393ef4b..7e511a77f345e 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -3997,7 +3997,16 @@ describe('actions/IOU', () => { if (transaction && createIOUAction) { // When the expense is deleted - deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, true, []); + deleteMoneyRequest({ + transactionID: transaction?.transactionID, + reportAction: createIOUAction, + transactions: {}, + violations: {}, + iouReport, + chatReport, + isChatIOUReportArchived: true, + allSnapshotKeys: [], + }); } await waitForBatchedUpdates(); @@ -4076,7 +4085,16 @@ describe('actions/IOU', () => { if (transaction && createIOUAction) { // When the IOU expense is deleted - deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, true, []); + deleteMoneyRequest({ + transactionID: transaction?.transactionID, + reportAction: createIOUAction, + transactions: {}, + violations: {}, + iouReport, + chatReport, + isChatIOUReportArchived: true, + allSnapshotKeys: [], + }); } await waitForBatchedUpdates(); @@ -4139,7 +4157,16 @@ describe('actions/IOU', () => { // When we attempt to delete an expense from the IOU report mockFetch?.pause?.(); if (transaction && createIOUAction) { - deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); + deleteMoneyRequest({ + transactionID: transaction?.transactionID, + reportAction: createIOUAction, + transactions: {}, + violations: {}, + iouReport, + chatReport, + isChatIOUReportArchived: undefined, + allSnapshotKeys: [], + }); } await waitForBatchedUpdates(); @@ -4234,7 +4261,16 @@ describe('actions/IOU', () => { if (transaction && createIOUAction) { // When Deleting an expense - deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); + deleteMoneyRequest({ + transactionID: transaction?.transactionID, + reportAction: createIOUAction, + transactions: {}, + violations: {}, + iouReport, + chatReport, + isChatIOUReportArchived: undefined, + allSnapshotKeys: [], + }); } await waitForBatchedUpdates(); @@ -4359,7 +4395,16 @@ describe('actions/IOU', () => { if (transaction && createIOUAction) { // When Deleting an expense - deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); + deleteMoneyRequest({ + transactionID: transaction?.transactionID, + reportAction: createIOUAction, + transactions: {}, + violations: {}, + iouReport, + chatReport, + isChatIOUReportArchived: undefined, + allSnapshotKeys: [], + }); } await waitForBatchedUpdates(); @@ -4433,7 +4478,16 @@ describe('actions/IOU', () => { if (transaction && createIOUAction) { // When deleting expense - deleteMoneyRequest(transaction?.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); + deleteMoneyRequest({ + transactionID: transaction?.transactionID, + reportAction: createIOUAction, + transactions: {}, + violations: {}, + iouReport, + chatReport, + isChatIOUReportArchived: undefined, + allSnapshotKeys: [], + }); } await waitForBatchedUpdates(); @@ -4584,7 +4638,16 @@ describe('actions/IOU', () => { mockFetch?.pause?.(); if (transaction && createIOUAction) { // When we delete the expense - deleteMoneyRequest(transaction.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); + deleteMoneyRequest({ + transactionID: transaction.transactionID, + reportAction: createIOUAction, + transactions: {}, + violations: {}, + iouReport, + chatReport, + isChatIOUReportArchived: undefined, + allSnapshotKeys: [], + }); } await waitForBatchedUpdates(); @@ -4677,7 +4740,16 @@ describe('actions/IOU', () => { mockFetch?.pause?.(); jest.advanceTimersByTime(10); if (transaction && createIOUAction) { - deleteMoneyRequest(transaction.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); + deleteMoneyRequest({ + transactionID: transaction.transactionID, + reportAction: createIOUAction, + transactions: {}, + violations: {}, + iouReport, + chatReport, + isChatIOUReportArchived: undefined, + allSnapshotKeys: [], + }); } await waitForBatchedUpdates(); @@ -4754,7 +4826,17 @@ describe('actions/IOU', () => { let navigateToAfterDelete; if (transaction && createIOUAction) { - navigateToAfterDelete = deleteMoneyRequest(transaction.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, [], true); + navigateToAfterDelete = deleteMoneyRequest({ + transactionID: transaction.transactionID, + reportAction: createIOUAction, + transactions: {}, + violations: {}, + iouReport, + chatReport, + isChatIOUReportArchived: undefined, + allSnapshotKeys: [], + isSingleTransactionView: true, + }); } let allReports = await new Promise>((resolve) => { @@ -4802,7 +4884,16 @@ describe('actions/IOU', () => { let navigateToAfterDelete; if (transaction && createIOUAction) { // When we delete the expense and we should delete the IOU report - navigateToAfterDelete = deleteMoneyRequest(transaction.transactionID, createIOUAction, {}, {}, iouReport, chatReport, undefined, []); + navigateToAfterDelete = deleteMoneyRequest({ + transactionID: transaction.transactionID, + reportAction: createIOUAction, + transactions: {}, + violations: {}, + iouReport, + chatReport, + isChatIOUReportArchived: undefined, + allSnapshotKeys: [], + }); } // Then we expect to navigate to the chat report expect(chatReport?.reportID).not.toBeUndefined(); @@ -4866,20 +4957,29 @@ describe('actions/IOU', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, expenseReport); const selectedTransactionIDs = [transaction1.transactionID, transaction2.transactionID]; - deleteMoneyRequest(transaction1.transactionID, moneyRequestAction1, {}, {}, expenseReport, expenseReport, undefined, [], undefined, [], selectedTransactionIDs); - deleteMoneyRequest( - transaction2.transactionID, - moneyRequestAction2, - {}, - {}, - expenseReport, - expenseReport, - undefined, - [], - undefined, - [transaction1.transactionID], + deleteMoneyRequest({ + transactionID: transaction1.transactionID, + reportAction: moneyRequestAction1, + transactions: {}, + violations: {}, + iouReport: expenseReport, + chatReport: expenseReport, + isChatIOUReportArchived: undefined, + allSnapshotKeys: [], selectedTransactionIDs, - ); + }); + deleteMoneyRequest({ + transactionID: transaction2.transactionID, + reportAction: moneyRequestAction2, + transactions: {}, + violations: {}, + iouReport: expenseReport, + chatReport: expenseReport, + isChatIOUReportArchived: undefined, + allSnapshotKeys: [], + transactionIDsPendingDeletion: [transaction1.transactionID], + selectedTransactionIDs, + }); await waitForBatchedUpdates(); From fd983969808b370709178f8f627738cb5bcf0daf Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Tue, 18 Nov 2025 00:48:20 +0430 Subject: [PATCH 12/18] fix some more eslint errors --- src/components/MoneyReportHeader.tsx | 45 +++++++++++++++++++++++++--- src/pages/Search/SearchPage.tsx | 2 +- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index ba35de3c9efbd..c1503976fdd4e 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -2,8 +2,10 @@ import {useRoute} from '@react-navigation/native'; import {isUserValidatedSelector} from '@selectors/Account'; import getArchiveReason from '@selectors/Report'; import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; +import type {FC} from 'react'; import {InteractionManager, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import type {SvgProps} from 'react-native-svg'; import type {ValueOf} from 'type-fest'; import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -127,7 +129,6 @@ import HeaderWithBackButton from './HeaderWithBackButton'; import HoldOrRejectEducationalModal from './HoldOrRejectEducationalModal'; import HoldSubmitterEducationalModal from './HoldSubmitterEducationalModal'; import Icon from './Icon'; -import * as Expensicons from './Icon/Expensicons'; import {KYCWallContext} from './KYCWall/KYCWallContext'; import type {PaymentMethod} from './KYCWall/types'; import LoadingBar from './LoadingBar'; @@ -527,6 +528,31 @@ function MoneyReportHeader({ /> ); + const Expensicons = useMemoizedLazyExpensifyIcons([ + 'Box', + 'CreditCardHourglass', + 'Flag', + 'Hourglass', + 'ReceiptScan', + 'Stopwatch', + 'Info', + 'Cash', + 'ArrowRight', + 'Plus', + 'ThumbsDown', + 'Send', + 'Document', + 'Trashcan', + 'Export', + 'ThumbsUp', + 'CircularArrowBackwards', + 'Workflows', + 'ArrowCollapse', + 'ArrowSplit', + 'Table', + 'Clear', + ]); + const getStatusBarProps: () => MoneyRequestHeaderStatusBarProps | undefined = () => { if (shouldShowMarkAsResolved) { return {icon: getStatusIcon(Expensicons.Hourglass), description: translate('iou.reject.rejectedStatus')}; @@ -737,7 +763,18 @@ function MoneyReportHeader({ } return options; - }, [translate, connectedIntegrationFallback, connectedIntegration, moneyRequestReport, isOffline, transactionIDs, isExported, beginExportWithTemplate, exportTemplates]); + }, [ + translate, + connectedIntegrationFallback, + connectedIntegration, + moneyRequestReport, + isOffline, + transactionIDs, + isExported, + beginExportWithTemplate, + exportTemplates, + Expensicons.Table, + ]); const primaryActionsImplementation = { [CONST.REPORT.PRIMARY_ACTIONS.SUBMIT]: ( @@ -941,7 +978,7 @@ function MoneyReportHeader({ text: translate('common.export'), backButtonText: translate('common.export'), icon: Expensicons.Export, - rightIcon: Expensicons.ArrowRight, + rightIcon: Expensicons.ArrowRight as FC, subMenuItems: secondaryExportActions.map((action) => exportSubmenuOptions[action as string]), }, [CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD_PDF]: { @@ -1218,7 +1255,7 @@ function MoneyReportHeader({ text: translate('iou.addExpense'), backButtonText: translate('iou.addExpense'), icon: Expensicons.Plus, - rightIcon: Expensicons.ArrowRight, + rightIcon: Expensicons.ArrowRight as FC, value: CONST.REPORT.SECONDARY_ACTIONS.ADD_EXPENSE, subMenuItems: addExpenseDropdownOptions, onSelected: () => { diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 5cfb47a6433c0..97d23cf5a4e4b 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -109,7 +109,7 @@ function SearchPage({route}: SearchPageProps) { const [activePolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`, {canBeMissing: true}); const personalPolicy = usePersonalPolicy(); const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true}); - const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT); + const [allSnapshots] = useOnyx(ONYXKEYS.COLLECTION.SNAPSHOT, {canBeMissing: true}); const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: true}); const [integrationsExportTemplates] = useOnyx(ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES, {canBeMissing: true}); const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS, {canBeMissing: true}); From ed748e8797b3a2e7e71ac9dd2e68bcc90d3d2425 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Tue, 18 Nov 2025 09:03:58 +0430 Subject: [PATCH 13/18] fixed remained eslint issues --- src/components/MoneyRequestHeader.tsx | 15 ++++++++++++++- tests/unit/DisplayNamesShouldParseHTMLTest.tsx | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index a0c7dea4398f5..a684f7d419a72 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -8,6 +8,7 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails' import useDeleteTransactions from '@hooks/useDeleteTransactions'; import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactionsAndViolations'; import useGetIOUReportFromReportAction from '@hooks/useGetIOUReportFromReportAction'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLoadingBarVisibility from '@hooks/useLoadingBarVisibility'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -60,7 +61,6 @@ import HeaderWithBackButton from './HeaderWithBackButton'; import HoldOrRejectEducationalModal from './HoldOrRejectEducationalModal'; import HoldSubmitterEducationalModal from './HoldSubmitterEducationalModal'; import Icon from './Icon'; -import * as Expensicons from './Icon/Expensicons'; import LoadingBar from './LoadingBar'; import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; @@ -155,6 +155,19 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre /> ); + const Expensicons = useMemoizedLazyExpensifyIcons([ + 'Stopwatch', + 'Hourglass', + 'Flag', + 'CreditCardHourglass', + 'ReceiptScan', + 'ArrowSplit', + 'ArrowCollapse', + 'Info', + 'Trashcan', + 'ThumbsDown', + ]); + const getStatusBarProps: () => MoneyRequestHeaderStatusBarProps | undefined = () => { if (isOnHold) { return {icon: getStatusIcon(Expensicons.Stopwatch), description: translate('iou.expenseOnHold')}; diff --git a/tests/unit/DisplayNamesShouldParseHTMLTest.tsx b/tests/unit/DisplayNamesShouldParseHTMLTest.tsx index 1103aab38677f..95c99a3ce4c22 100644 --- a/tests/unit/DisplayNamesShouldParseHTMLTest.tsx +++ b/tests/unit/DisplayNamesShouldParseHTMLTest.tsx @@ -21,7 +21,7 @@ jest.mock('@libs/Parser', () => ({ default: { htmlToText: jest.fn((html: string) => { // Simulate stripTag behavior: remove anything that looks like HTML tags - return html.replace(/(<([^>]+)>)/gi, ''); + return html.replaceAll(/(<([^>]+)>)/gi, ''); }), }, })); From 05f6d01b4909163c9c094d8a1355ebcb0b467735 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 20 Nov 2025 00:10:35 +0430 Subject: [PATCH 14/18] unified getSnapshotKeys to and helper function --- src/components/MoneyRequestHeader.tsx | 41 +++++++---------- src/hooks/useDeleteTransactions.ts | 15 ++----- src/libs/SearchUIUtils.ts | 11 +++++ src/libs/actions/IOU.ts | 24 +++++----- src/libs/actions/Search.ts | 20 ++++++--- src/pages/ReportDetailsPage.tsx | 45 +++++++++---------- src/pages/Search/SearchPage.tsx | 45 ++++++++----------- .../PopoverReportActionContextMenu.tsx | 13 +----- tests/actions/IOUTest.ts | 12 ----- 9 files changed, 96 insertions(+), 130 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 57cdbf12b2f39..73542310819bc 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -45,7 +45,6 @@ import {dismissRejectUseExplanation} from '@userActions/IOU'; import {markAsCash as markAsCashAction} from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxKey} from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import type {Policy, Report, ReportAction} from '@src/types/onyx'; @@ -156,7 +155,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre /> ); - const Expensicons = useMemoizedLazyExpensifyIcons([ + const expensifyIcons = useMemoizedLazyExpensifyIcons([ 'Stopwatch', 'Hourglass', 'Flag', @@ -171,22 +170,22 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const getStatusBarProps: () => MoneyRequestHeaderStatusBarProps | undefined = () => { if (isOnHold) { - return {icon: getStatusIcon(Expensicons.Stopwatch), description: translate('iou.expenseOnHold')}; + return {icon: getStatusIcon(expensifyIcons.Stopwatch), description: translate('iou.expenseOnHold')}; } if (isMarkAsResolvedAction(parentReport, transactionViolations, policy)) { - return {icon: getStatusIcon(Expensicons.Hourglass), description: translate('iou.reject.rejectedStatus')}; + return {icon: getStatusIcon(expensifyIcons.Hourglass), description: translate('iou.reject.rejectedStatus')}; } if (isDuplicate) { - return {icon: getStatusIcon(Expensicons.Flag), description: translate('iou.expenseDuplicate')}; + return {icon: getStatusIcon(expensifyIcons.Flag), description: translate('iou.expenseDuplicate')}; } if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { - return {icon: getStatusIcon(Expensicons.CreditCardHourglass), description: translate('iou.transactionPendingDescription')}; + return {icon: getStatusIcon(expensifyIcons.CreditCardHourglass), description: translate('iou.transactionPendingDescription')}; } if (shouldShowBrokenConnectionViolation) { return { - icon: getStatusIcon(Expensicons.Hourglass), + icon: getStatusIcon(expensifyIcons.Hourglass), description: ( { - if (!allSnapshots) { - return []; - } - - return Object.keys(allSnapshots || {}) as OnyxKey[]; - }, [allSnapshots]); - const secondaryActions = useMemo(() => { if (!transaction || !parentReportAction || !parentReport) { return []; @@ -317,7 +308,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const secondaryActionsImplementation: Record, DropdownOption>> = { [CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.HOLD]: { text: translate('iou.hold'), - icon: Expensicons.Stopwatch, + icon: expensifyIcons.Stopwatch, value: CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.HOLD, onSelected: () => { if (!parentReportAction) { @@ -342,7 +333,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre }, [CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REMOVE_HOLD]: { text: translate('iou.unhold'), - icon: Expensicons.Stopwatch, + icon: expensifyIcons.Stopwatch, value: CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REMOVE_HOLD, onSelected: () => { if (!parentReportAction) { @@ -354,7 +345,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre }, [CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.SPLIT]: { text: isExpenseSplit ? translate('iou.editSplits') : translate('iou.split'), - icon: Expensicons.ArrowSplit, + icon: expensifyIcons.ArrowSplit, value: CONST.REPORT.SECONDARY_ACTIONS.SPLIT, onSelected: () => { initSplitExpense(allTransactions, allReports, transaction); @@ -362,7 +353,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre }, [CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.MERGE]: { text: translate('common.merge'), - icon: Expensicons.ArrowCollapse, + icon: expensifyIcons.ArrowCollapse, value: CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.MERGE, onSelected: () => { if (!transaction) { @@ -376,14 +367,14 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre [CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.VIEW_DETAILS]: { value: CONST.REPORT.SECONDARY_ACTIONS.VIEW_DETAILS, text: translate('iou.viewDetails'), - icon: Expensicons.Info, + icon: expensifyIcons.Info, onSelected: () => { navigateToDetailsPage(report, Navigation.getActiveRoute()); }, }, [CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.DELETE]: { text: translate('common.delete'), - icon: Expensicons.Trashcan, + icon: expensifyIcons.Trashcan, value: CONST.REPORT.SECONDARY_ACTIONS.DELETE, onSelected: () => { setIsDeleteModalVisible(true); @@ -391,7 +382,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre }, [CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT]: { text: translate('common.reject'), - icon: Expensicons.ThumbsDown, + icon: expensifyIcons.ThumbsDown, value: CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT, onSelected: () => { if (dismissedRejectUseExplanation) { @@ -507,7 +498,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre isSingleTransactionView: true, isChatReportArchived: isParentReportArchived, isChatIOUReportArchived, - allSnapshotKeys, + allSnapshots, }); } else { deleteTransactions([transaction.transactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash, true); diff --git a/src/hooks/useDeleteTransactions.ts b/src/hooks/useDeleteTransactions.ts index 833066acbcb1b..fe086ec4b1bf5 100644 --- a/src/hooks/useDeleteTransactions.ts +++ b/src/hooks/useDeleteTransactions.ts @@ -1,4 +1,4 @@ -import {useCallback, useMemo} from 'react'; +import {useCallback} from 'react'; import type {OnyxCollection} from 'react-native-onyx'; import {deleteMoneyRequest, getIOUActionForTransactions, getIOURequestPolicyID, initSplitExpenseItemData, updateSplitTransactions} from '@libs/actions/IOU'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; @@ -6,7 +6,6 @@ import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils import {getChildTransactions, getOriginalTransactionWithSplitInfo} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxKey} from '@src/ONYXKEYS'; import type {Policy, Report, ReportAction, Transaction, TransactionViolations} from '@src/types/onyx'; import useArchivedReportsIdSet from './useArchivedReportsIdSet'; import useOnyx from './useOnyx'; @@ -36,14 +35,6 @@ function useDeleteTransactions({report, reportActions, policy}: UseDeleteTransac const {isBetaEnabled} = usePermissions(); const archivedReportsIdSet = useArchivedReportsIdSet(); - const allSnapshotKeys = useMemo(() => { - if (!allSnapshots) { - return []; - } - - return Object.keys(allSnapshots || {}) as OnyxKey[]; - }, [allSnapshots]); - /** * Delete transactions by IDs * @param transactionIDs - Array of transaction IDs to delete @@ -154,7 +145,7 @@ function useDeleteTransactions({report, reportActions, policy}: UseDeleteTransac iouReport, chatReport, isChatIOUReportArchived, - allSnapshotKeys, + allSnapshots, isSingleTransactionView, transactionIDsPendingDeletion: deletedTransactionIDs, selectedTransactionIDs: transactionIDs, @@ -178,7 +169,7 @@ function useDeleteTransactions({report, reportActions, policy}: UseDeleteTransac policy, archivedReportsIdSet, isBetaEnabled, - allSnapshotKeys, + allSnapshots, ], ); diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 30ab92ca71b35..c5f5fa0f30546 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -2491,6 +2491,16 @@ function getColumnsToShow( return columns; } +type OnyxSnapshotKey = `${typeof ONYXKEYS.COLLECTION.SNAPSHOT}${string}`; + +function getSnapshotKeys(allSnapshots: OnyxCollection) { + if (!allSnapshots) { + return []; + } + + return Object.keys(allSnapshots || {}) as OnyxSnapshotKey[]; +} + export { getSuggestedSearches, getListItem, @@ -2532,5 +2542,6 @@ export { getActionOptions, getColumnsToShow, getHasOptions, + getSnapshotKeys, }; export type {SavedSearchMenuItem, SearchTypeMenuSection, SearchTypeMenuItem, SearchDateModifier, SearchDateModifierLower, SearchKey, ArchivedReportsIDSet}; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 4cb163980565d..a500b16d1e85f 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -201,7 +201,7 @@ import { updateReportPreview, } from '@libs/ReportUtils'; import {getCurrentSearchQueryJSON} from '@libs/SearchQueryUtils'; -import {getSuggestedSearches} from '@libs/SearchUIUtils'; +import {getSnapshotKeys, getSuggestedSearches} from '@libs/SearchUIUtils'; import {getSession} from '@libs/SessionUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; @@ -238,7 +238,6 @@ import type {IOUAction, IOUActionParams, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxKey} from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; @@ -252,6 +251,7 @@ import type RecentlyUsedTags from '@src/types/onyx/RecentlyUsedTags'; 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 SearchResults from '@src/types/onyx/SearchResults'; import type {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'; @@ -782,7 +782,7 @@ type DeleteTrackExpenseParams = { isSingleTransactionView: boolean | undefined; isChatReportArchived: boolean | undefined; isChatIOUReportArchived: boolean | undefined; - allSnapshotKeys: OnyxKey[]; + allSnapshots?: OnyxCollection; }; type DeleteMoneyRequestInputParams = { @@ -793,7 +793,7 @@ type DeleteMoneyRequestInputParams = { iouReport: OnyxEntry; chatReport: OnyxEntry; isChatIOUReportArchived: boolean | undefined; - allSnapshotKeys: OnyxKey[]; + allSnapshots?: OnyxCollection; isSingleTransactionView?: boolean; transactionIDsPendingDeletion?: string[]; selectedTransactionIDs?: string[]; @@ -8703,7 +8703,7 @@ function deleteMoneyRequest(params: DeleteMoneyRequestInputParams): Route | unde iouReport, chatReport, isChatIOUReportArchived, - allSnapshotKeys, + allSnapshots, isSingleTransactionView = false, transactionIDsPendingDeletion, selectedTransactionIDs, @@ -8729,6 +8729,8 @@ function deleteMoneyRequest(params: DeleteMoneyRequestInputParams): Route | unde const urlToNavigateBack = getNavigationUrlOnMoneyRequestDelete(transactionID, reportAction, iouReport, chatReport, isChatIOUReportArchived, isSingleTransactionView); + const allSnapshotKeys = getSnapshotKeys(allSnapshots); + // STEP 2: Build Onyx data // The logic mostly resembles the cleanUpMoneyRequest function const optimisticData: OnyxUpdate[] = [ @@ -8878,9 +8880,9 @@ function deleteMoneyRequest(params: DeleteMoneyRequestInputParams): Route | unde [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }, - }, + } as Partial, }, - } as unknown as OnyxUpdate); + }); failureData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -8890,9 +8892,9 @@ function deleteMoneyRequest(params: DeleteMoneyRequestInputParams): Route | unde [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { pendingAction: null, }, - }, + } as Partial, }, - } as unknown as OnyxUpdate); + }); }); } @@ -9037,7 +9039,7 @@ function deleteTrackExpense({ isSingleTransactionView = false, isChatReportArchived, isChatIOUReportArchived, - allSnapshotKeys, + allSnapshots, }: DeleteTrackExpenseParams) { if (!chatReportID || !transactionID) { return; @@ -9056,7 +9058,7 @@ function deleteTrackExpense({ // STEP 1: Get all collections we're updating if (!isSelfDM(chatReport)) { - deleteMoneyRequest({transactionID, reportAction, transactions, violations, iouReport, chatReport: chatIOUReport, isChatIOUReportArchived, allSnapshotKeys, isSingleTransactionView}); + deleteMoneyRequest({transactionID, reportAction, transactions, violations, iouReport, chatReport: chatIOUReport, isChatIOUReportArchived, allSnapshots, isSingleTransactionView}); return urlToNavigateBack; } diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 88ee880a0205d..c5a709a74fbb8 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -33,12 +33,11 @@ import { isIOUReport as isIOUReportUtil, } from '@libs/ReportUtils'; import type {SearchKey} from '@libs/SearchUIUtils'; -import {isTransactionGroupListItemType, isTransactionListItemType} from '@libs/SearchUIUtils'; +import {getSnapshotKeys, isTransactionGroupListItemType, isTransactionListItemType} from '@libs/SearchUIUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxKey} from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import {FILTER_KEYS} from '@src/types/form/SearchAdvancedFiltersForm'; import type {SearchAdvancedFiltersForm} from '@src/types/form/SearchAdvancedFiltersForm'; @@ -667,7 +666,14 @@ function unholdMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { API.write(WRITE_COMMANDS.UNHOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList}, {optimisticData, finallyData}); } -function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[], allSnapshotKeys: OnyxKey[], transactions: OnyxCollection, currentSearchResults?: SearchResults) { +function deleteMoneyRequestOnSearch( + hash: number, + transactionIDList: string[], + allSnapshots: OnyxCollection, + transactions: OnyxCollection, + currentSearchResults?: SearchResults, +) { + const allSnapshotKeys = getSnapshotKeys(allSnapshots); const {optimisticData: loadingOptimisticData, finallyData} = getOnyxLoadingData(hash); const optimisticData: OnyxUpdate[] = [...loadingOptimisticData]; const failureData: OnyxUpdate[] = []; @@ -711,9 +717,9 @@ function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[], a [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }, - }, + } as Partial, }, - } as unknown as OnyxUpdate); + }); failureData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -723,9 +729,9 @@ function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[], a [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { pendingAction: null, }, - }, + } as Partial, }, - } as unknown as OnyxUpdate); + }); }); if (transactions) { diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 72012ae974c5d..640c69bc463c1 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -10,7 +10,6 @@ import ConfirmModal from '@components/ConfirmModal'; import DisplayNames from '@components/DisplayNames'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext'; -import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -27,6 +26,7 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails' import useDeleteTransactions from '@hooks/useDeleteTransactions'; import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactionsAndViolations'; import useGetIOUReportFromReportAction from '@hooks/useGetIOUReportFromReportAction'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -117,7 +117,6 @@ import {canActionTask, canModifyTask, deleteTask, reopenTask} from '@userActions import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxKey} from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; @@ -303,6 +302,8 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); const isWorkspaceChat = useMemo(() => isWorkspaceChatUtil(report?.chatType ?? ''), [report?.chatType]); + const expensifyIcons = useMemoizedLazyExpensifyIcons(['Users', 'Gear', 'Send', 'Folder', 'UserPlus', 'Pencil', 'Checkmark', 'Building', 'Exit', 'Camera', 'Bug', 'Trashcan']); + useEffect(() => { if (canDeleteRequest) { return; @@ -382,7 +383,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.MEMBERS, translationKey: 'common.members', - icon: Expensicons.Users, + icon: expensifyIcons.Users, subtitle: activeChatMembers.length, isAnonymousAction: false, shouldShowRightIcon: true, @@ -398,7 +399,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.INVITE, translationKey: 'common.invite', - icon: Expensicons.Users, + icon: expensifyIcons.Users, isAnonymousAction: false, shouldShowRightIcon: true, action: () => { @@ -411,7 +412,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.SETTINGS, translationKey: 'common.settings', - icon: Expensicons.Gear, + icon: expensifyIcons.Gear, isAnonymousAction: false, shouldShowRightIcon: true, action: () => { @@ -427,7 +428,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.TRACK.SUBMIT, translationKey: 'actionableMentionTrackExpense.submit', - icon: Expensicons.Send, + icon: expensifyIcons.Send, isAnonymousAction: false, shouldShowRightIcon: true, action: () => { @@ -446,7 +447,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.TRACK.CATEGORIZE, translationKey: 'actionableMentionTrackExpense.categorize', - icon: Expensicons.Folder, + icon: expensifyIcons.Folder, isAnonymousAction: false, shouldShowRightIcon: true, action: () => { @@ -456,7 +457,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.TRACK.SHARE, translationKey: 'actionableMentionTrackExpense.share', - icon: Expensicons.UserPlus, + icon: expensifyIcons.UserPlus, isAnonymousAction: false, shouldShowRightIcon: true, action: () => { @@ -471,7 +472,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.PRIVATE_NOTES, translationKey: 'privateNotes.title', - icon: Expensicons.Pencil, + icon: expensifyIcons.Pencil, isAnonymousAction: false, shouldShowRightIcon: true, action: () => navigateToPrivateNotes(report, currentUserPersonalDetails.accountID, backTo), @@ -484,7 +485,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail if (isCompletedTaskReport(report) && isTaskActionable) { items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.MARK_AS_INCOMPLETE, - icon: Expensicons.Checkmark, + icon: expensifyIcons.Checkmark, translationKey: 'task.markAsIncomplete', isAnonymousAction: false, action: callFunctionIfActionIsAllowed(() => { @@ -499,7 +500,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.GO_TO_WORKSPACE, translationKey: 'workspace.common.goToWorkspace', - icon: Expensicons.Building, + icon: expensifyIcons.Building, action: () => { if (!report?.policyID) { return; @@ -519,7 +520,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.LEAVE_ROOM, translationKey: 'common.leave', - icon: Expensicons.Exit, + icon: expensifyIcons.Exit, isAnonymousAction: true, action: () => { if (getParticipantsAccountIDsForDisplay(report, false, true).length === 1 && isRootGroupChat) { @@ -536,7 +537,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.DEBUG, translationKey: 'debug.debug', - icon: Expensicons.Bug, + icon: expensifyIcons.Bug, action: () => Navigation.navigate(ROUTES.DEBUG_REPORT.getRoute(report.reportID)), isAnonymousAction: true, shouldShowRightIcon: true, @@ -580,6 +581,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail isRestrictedToPreferredPolicy, preferredPolicyID, introSelected, + expensifyIcons, ]); const displayNamesWithTooltips = useMemo(() => { @@ -639,7 +641,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail updateGroupChatAvatar(report.reportID); }} onImageSelected={(file) => updateGroupChatAvatar(report.reportID, file)} - editIcon={Expensicons.Camera} + editIcon={expensifyIcons.Camera} editIconStyle={styles.smallEditIconAccount} pendingAction={report.pendingFields?.avatar ?? undefined} errors={report.errorFields?.avatar ?? null} @@ -662,6 +664,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail policy, participants, moneyRequestReport?.reportID, + expensifyIcons.Camera, ]); const canJoin = canJoinChat(report, parentReportAction, policy, !!reportNameValuePairs?.private_isArchived); @@ -807,14 +810,6 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail ); - const allSnapshotKeys = useMemo(() => { - if (!allSnapshots) { - return []; - } - - return Object.keys(allSnapshots || {}) as OnyxKey[]; - }, [allSnapshots]); - const deleteTransaction = useCallback(() => { if (caseID === CASES.DEFAULT) { deleteTask(report, isReportArchived, currentUserPersonalDetails.accountID); @@ -840,7 +835,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail isSingleTransactionView, isChatReportArchived: isMoneyRequestReportArchived, isChatIOUReportArchived, - allSnapshotKeys, + allSnapshots, }); } else if (iouTransactionID) { deleteTransactions([iouTransactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash, isSingleTransactionView); @@ -864,7 +859,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail deleteTransactions, currentSearchHash, isChatIOUReportArchived, - allSnapshotKeys, + allSnapshots, ]); // Where to navigate back to after deleting the transaction and its report. @@ -999,7 +994,7 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail {shouldShowDeleteButton && ( setIsDeleteModalVisible(true)} /> diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 97d23cf5a4e4b..1eb9d1b3029f1 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -80,7 +80,6 @@ import {openOldDotLink} from '@userActions/Link'; import {buildOptimisticTransactionAndCreateDraft} from '@userActions/TransactionEdit'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OnyxKey} from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {SearchResults, Transaction} from '@src/types/onyx'; @@ -299,7 +298,7 @@ function SearchPage({route}: SearchPageProps) { [clearSelectedTransactions, hash, isOffline, lastPaymentMethods, selectedReports, selectedTransactions, policies, formatPhoneNumber], ); - const Expensicons = useMemoizedLazyExpensifyIcons([ + const expensifyIcons = useMemoizedLazyExpensifyIcons([ 'ArrowRight', 'DocumentMerge', 'Export', @@ -327,7 +326,7 @@ function SearchPage({route}: SearchPageProps) { const exportOptions: PopoverMenuItem[] = [ { text: translate('export.basicExport'), - icon: Expensicons.Table, + icon: expensifyIcons.Table, onSelected: () => { if (isOffline) { setIsOfflineModalVisible(true); @@ -374,7 +373,7 @@ function SearchPage({route}: SearchPageProps) { for (const template of exportTemplates) { exportOptions.push({ text: template.name, - icon: Expensicons.Table, + icon: expensifyIcons.Table, description: template.description, onSelected: () => { beginExportWithTemplate(template.templateName, template.type, template.policyID); @@ -388,8 +387,8 @@ function SearchPage({route}: SearchPageProps) { }; const exportButtonOption: DropdownOption & Pick = { - icon: Expensicons.Export, - rightIcon: Expensicons.ArrowRight as FC, + icon: expensifyIcons.Export, + rightIcon: expensifyIcons.ArrowRight as FC, text: translate('common.export'), backButtonText: translate('common.export'), value: CONST.SEARCH.BULK_ACTION_TYPES.EXPORT, @@ -414,7 +413,7 @@ function SearchPage({route}: SearchPageProps) { if (shouldShowApproveOption) { options.push({ - icon: Expensicons.ThumbsUp, + icon: expensifyIcons.ThumbsUp, text: translate('search.bulkActions.approve'), value: CONST.SEARCH.BULK_ACTION_TYPES.APPROVE, shouldCloseModalOnSelect: true, @@ -460,7 +459,7 @@ function SearchPage({route}: SearchPageProps) { if (shouldShowSubmitOption) { options.push({ - icon: Expensicons.Send, + icon: expensifyIcons.Send, text: translate('common.submit'), value: CONST.SEARCH.BULK_ACTION_TYPES.SUBMIT, shouldCloseModalOnSelect: true, @@ -491,9 +490,9 @@ function SearchPage({route}: SearchPageProps) { if (shouldShowPayOption) { const payButtonOption = { - icon: Expensicons.MoneyBag, + icon: expensifyIcons.MoneyBag, text: translate('search.bulkActions.pay'), - rightIcon: isFirstTimePayment ? Expensicons.ArrowRight : undefined, + rightIcon: isFirstTimePayment ? expensifyIcons.ArrowRight : undefined, value: CONST.SEARCH.BULK_ACTION_TYPES.PAY, shouldCloseModalOnSelect: true, subMenuItems: isFirstTimePayment ? bulkPayButtonOptions : undefined, @@ -508,7 +507,7 @@ function SearchPage({route}: SearchPageProps) { if (shouldShowHoldOption) { options.push({ - icon: Expensicons.Stopwatch, + icon: expensifyIcons.Stopwatch, text: translate('search.bulkActions.hold'), value: CONST.SEARCH.BULK_ACTION_TYPES.HOLD, shouldCloseModalOnSelect: true, @@ -527,7 +526,7 @@ function SearchPage({route}: SearchPageProps) { if (shouldShowUnholdOption) { options.push({ - icon: Expensicons.Stopwatch, + icon: expensifyIcons.Stopwatch, text: translate('search.bulkActions.unhold'), value: CONST.SEARCH.BULK_ACTION_TYPES.UNHOLD, shouldCloseModalOnSelect: true, @@ -570,7 +569,7 @@ function SearchPage({route}: SearchPageProps) { if (canAllTransactionsBeMoved && !hasMultipleOwners) { options.push({ text: translate('iou.moveExpenses', {count: selectedTransactionsKeys.length}), - icon: Expensicons.DocumentMerge, + icon: expensifyIcons.DocumentMerge, value: CONST.SEARCH.BULK_ACTION_TYPES.CHANGE_REPORT, shouldCloseModalOnSelect: true, onSelected: () => Navigation.navigate(ROUTES.MOVE_TRANSACTIONS_SEARCH_RHP), @@ -581,7 +580,7 @@ function SearchPage({route}: SearchPageProps) { if (shouldShowDeleteOption) { options.push({ - icon: Expensicons.Trashcan, + icon: expensifyIcons.Trashcan, text: translate('search.bulkActions.delete'), value: CONST.SEARCH.BULK_ACTION_TYPES.DELETE, shouldCloseModalOnSelect: true, @@ -606,7 +605,7 @@ function SearchPage({route}: SearchPageProps) { }; options.push({ - icon: Expensicons.Exclamation, + icon: expensifyIcons.Exclamation, text: translate('search.bulkActions.noOptionsAvailable'), value: undefined, ...emptyOptionStyle, @@ -639,17 +638,9 @@ function SearchPage({route}: SearchPageProps) { selectedPolicyIDs, selectedReportIDs, selectedTransactionReportIDs, - Expensicons, + expensifyIcons, ]); - const allSnapshotKeys = useMemo(() => { - if (!allSnapshots) { - return []; - } - - return Object.keys(allSnapshots || {}) as OnyxKey[]; - }, [allSnapshots]); - const handleDeleteExpenses = () => { if (selectedTransactionsKeys.length === 0 || !hash) { return; @@ -661,7 +652,7 @@ function SearchPage({route}: SearchPageProps) { // We need to wait for modal to fully disappear before clearing them to avoid translation flicker between singular vs plural // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { - deleteMoneyRequestOnSearch(hash, selectedTransactionsKeys, allSnapshotKeys, transactions, currentSearchResults); + deleteMoneyRequestOnSearch(hash, selectedTransactionsKeys, allSnapshots, transactions, currentSearchResults); clearSelectedTransactions(); }); }; @@ -844,7 +835,7 @@ function SearchPage({route}: SearchPageProps) { /> { - if (!allSnapshots) { - return []; - } - - return Object.keys(allSnapshots || {}) as OnyxKey[]; - }, [allSnapshots]); - const confirmDeleteAndHideModal = useCallback(() => { callbackWhenDeleteModalHide.current = runAndResetCallback(onConfirmDeleteModal.current); const reportAction = reportActionRef.current; @@ -371,7 +362,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro isSingleTransactionView: undefined, isChatReportArchived: isReportArchived, isChatIOUReportArchived, - allSnapshotKeys, + allSnapshots, }); } else if (originalMessage?.IOUTransactionID) { deleteTransactions([originalMessage.IOUTransactionID], duplicateTransactions, duplicateTransactionViolations, currentSearchHash); @@ -398,7 +389,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro deleteTransactions, currentSearchHash, isOriginalReportArchived, - allSnapshotKeys, + allSnapshots, ]); const hideDeleteModal = () => { diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 4df431838eb2d..87e96b48b8eab 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -4027,7 +4027,6 @@ describe('actions/IOU', () => { iouReport, chatReport, isChatIOUReportArchived: true, - allSnapshotKeys: [], }); } await waitForBatchedUpdates(); @@ -4115,7 +4114,6 @@ describe('actions/IOU', () => { iouReport, chatReport, isChatIOUReportArchived: true, - allSnapshotKeys: [], }); } await waitForBatchedUpdates(); @@ -4187,7 +4185,6 @@ describe('actions/IOU', () => { iouReport, chatReport, isChatIOUReportArchived: undefined, - allSnapshotKeys: [], }); } await waitForBatchedUpdates(); @@ -4291,7 +4288,6 @@ describe('actions/IOU', () => { iouReport, chatReport, isChatIOUReportArchived: undefined, - allSnapshotKeys: [], }); } await waitForBatchedUpdates(); @@ -4425,7 +4421,6 @@ describe('actions/IOU', () => { iouReport, chatReport, isChatIOUReportArchived: undefined, - allSnapshotKeys: [], }); } await waitForBatchedUpdates(); @@ -4508,7 +4503,6 @@ describe('actions/IOU', () => { iouReport, chatReport, isChatIOUReportArchived: undefined, - allSnapshotKeys: [], }); } await waitForBatchedUpdates(); @@ -4668,7 +4662,6 @@ describe('actions/IOU', () => { iouReport, chatReport, isChatIOUReportArchived: undefined, - allSnapshotKeys: [], }); } await waitForBatchedUpdates(); @@ -4770,7 +4763,6 @@ describe('actions/IOU', () => { iouReport, chatReport, isChatIOUReportArchived: undefined, - allSnapshotKeys: [], }); } await waitForBatchedUpdates(); @@ -4856,7 +4848,6 @@ describe('actions/IOU', () => { iouReport, chatReport, isChatIOUReportArchived: undefined, - allSnapshotKeys: [], isSingleTransactionView: true, }); } @@ -4914,7 +4905,6 @@ describe('actions/IOU', () => { iouReport, chatReport, isChatIOUReportArchived: undefined, - allSnapshotKeys: [], }); } // Then we expect to navigate to the chat report @@ -4987,7 +4977,6 @@ describe('actions/IOU', () => { iouReport: expenseReport, chatReport: expenseReport, isChatIOUReportArchived: undefined, - allSnapshotKeys: [], selectedTransactionIDs, }); deleteMoneyRequest({ @@ -4998,7 +4987,6 @@ describe('actions/IOU', () => { iouReport: expenseReport, chatReport: expenseReport, isChatIOUReportArchived: undefined, - allSnapshotKeys: [], transactionIDsPendingDeletion: [transaction1.transactionID], selectedTransactionIDs, }); From 0c8a39742f39637ef18393507884b80b60560e79 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 20 Nov 2025 09:22:55 +0430 Subject: [PATCH 15/18] removed unused useMemo --- .../home/report/ContextMenu/PopoverReportActionContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx index f977cebd01336..c9515e040a684 100644 --- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -1,6 +1,6 @@ /* eslint-disable react-compiler/react-compiler */ import type {ForwardedRef} from 'react'; -import React, {useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useContext, useEffect, useImperativeHandle, useRef, useState} from 'react'; /* eslint-disable no-restricted-imports */ import type {EmitterSubscription, GestureResponderEvent, NativeTouchEvent, View} from 'react-native'; From 277e833be54ac918cdc14afcf7d09269d7d3cd13 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 20 Nov 2025 10:04:57 +0430 Subject: [PATCH 16/18] replaced SearchTransaction with Transaction it was deprecated --- src/libs/actions/IOU.ts | 9 ++++----- src/libs/actions/Search.ts | 10 +++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 59808f6040968..c4945b23e31d5 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -252,7 +252,6 @@ 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 SearchResults from '@src/types/onyx/SearchResults'; -import type {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'; @@ -8879,7 +8878,7 @@ function deleteMoneyRequest(params: DeleteMoneyRequestInputParams): Route | unde [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }, - } as Partial, + } as Partial, }, }); @@ -8891,7 +8890,7 @@ function deleteMoneyRequest(params: DeleteMoneyRequestInputParams): Route | unde [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { pendingAction: null, }, - } as Partial, + } as Partial, }, }); }); @@ -10178,7 +10177,7 @@ function canIOUBePaid( iouReport: OnyxTypes.OnyxInputOrEntry, chatReport: OnyxTypes.OnyxInputOrEntry, policy: OnyxTypes.OnyxInputOrEntry, - transactions?: OnyxTypes.Transaction[] | SearchTransaction[], + transactions?: OnyxTypes.Transaction[], onlyShowPayElsewhere = false, chatReportRNVP?: OnyxTypes.ReportNameValuePairs, invoiceReceiverPolicy?: OnyxTypes.Policy, @@ -10251,7 +10250,7 @@ function canCancelPayment(iouReport: OnyxEntry, session: OnyxE function canSubmitReport( report: OnyxEntry, policy: OnyxEntry, - transactions: OnyxTypes.Transaction[] | SearchTransaction[], + transactions: OnyxTypes.Transaction[], allViolations: OnyxCollection | undefined, isReportArchived: boolean, ) { diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 523f803035998..bbb966a0b52ac 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -44,7 +44,7 @@ import type {SearchAdvancedFiltersForm} from '@src/types/form/SearchAdvancedFilt import type {ExportTemplate, LastPaymentMethod, LastPaymentMethodType, Policy, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; import type {PaymentInformation} from '@src/types/onyx/LastPaymentMethod'; import type {ConnectionName} from '@src/types/onyx/Policy'; -import type {SearchReport, SearchTransaction} from '@src/types/onyx/SearchResults'; +import type {SearchReport} from '@src/types/onyx/SearchResults'; import type SearchResults from '@src/types/onyx/SearchResults'; import type Nullable from '@src/types/utils/Nullable'; import SafeString from '@src/utils/SafeString'; @@ -81,7 +81,7 @@ function handleActionButtonPress( // The transactionIDList is needed to handle actions taken on `status:""` where transactions on single expense reports can be approved/paid. // We need the transactionID to display the loading indicator for that list item's action. // eslint-disable-next-line @typescript-eslint/no-deprecated - const allReportTransactions = (isTransactionGroupListItemType(item) ? item.transactions : [item]) as SearchTransaction[]; + const allReportTransactions = (isTransactionGroupListItemType(item) ? item.transactions : [item]) as Transaction[]; const hasHeldExpense = hasHeldExpenses('', allReportTransactions); if (hasHeldExpense) { @@ -419,7 +419,7 @@ function search({ */ function updateSearchResultsWithTransactionThreadReportID(hash: number, transactionID: string, reportID: string) { // eslint-disable-next-line @typescript-eslint/no-deprecated - const onyxUpdate: Record>> = { + const onyxUpdate: Record>> = { data: { [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { transactionThreadReportID: reportID, @@ -749,7 +749,7 @@ function deleteMoneyRequestOnSearch( [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, }, - } as Partial, + } as Partial, }, }); @@ -761,7 +761,7 @@ function deleteMoneyRequestOnSearch( [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { pendingAction: null, }, - } as Partial, + } as Partial, }, }); }); From 9822a94d8f85b0f35d83f5585dc375d5de1b17b4 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 20 Nov 2025 10:16:43 +0430 Subject: [PATCH 17/18] replaced SearchTransaction for ReportUtils with Transaction --- src/libs/ReportUtils.ts | 33 ++++++++++++++++----------------- src/libs/actions/Search.ts | 4 ++-- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index cd675e90ba855..94b6211434346 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -75,7 +75,6 @@ import type {NotificationPreference, Participants, Participant as ReportParticip import type {Message, OldDotReportAction, ReportActions} from '@src/types/onyx/ReportAction'; import type {PendingChatMember} from '@src/types/onyx/ReportMetadata'; import type {OnyxData} from '@src/types/onyx/Request'; -import type {SearchTransaction} from '@src/types/onyx/SearchResults'; import type {Comment, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction'; import type {FileObject} from '@src/types/utils/Attachment'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -924,7 +923,7 @@ type GetReportNameParams = { parentReportActionParam?: OnyxInputOrEntry; personalDetails?: Partial; invoiceReceiverPolicy?: OnyxEntry; - transactions?: SearchTransaction[]; + transactions?: Transaction[]; reports?: Report[]; policies?: Policy[]; isReportArchived?: boolean; @@ -2246,7 +2245,7 @@ function findLastAccessedReport(ignoreDomainRooms: boolean, openOnAdminRoom = fa /** * Whether the provided report has expenses */ -function hasExpenses(reportID?: string, transactions?: SearchTransaction[] | Array>): boolean { +function hasExpenses(reportID?: string, transactions?: Transaction[] | Array>): boolean { if (transactions) { return !!transactions?.find((transaction) => transaction?.reportID === reportID); } @@ -2256,7 +2255,7 @@ function hasExpenses(reportID?: string, transactions?: SearchTransaction[] | Arr /** * Whether the provided report is a closed expense report with no expenses */ -function isClosedExpenseReportWithNoExpenses(report: OnyxEntry, transactions?: SearchTransaction[] | Array>): boolean { +function isClosedExpenseReportWithNoExpenses(report: OnyxEntry, transactions?: Transaction[] | Array>): boolean { if (!report?.statusNum || report.statusNum !== CONST.REPORT.STATUS_NUM.CLOSED || !isExpenseReport(report)) { return false; } @@ -4482,7 +4481,7 @@ function canEditMoneyRequest( isChatReportArchived = false, report?: OnyxInputOrEntry, policy?: OnyxEntry, - linkedTransaction?: OnyxEntry | SearchTransaction, + linkedTransaction?: OnyxEntry | Transaction, ): boolean { const isDeleted = isDeletedAction(reportAction); @@ -4624,7 +4623,7 @@ function canEditFieldOfMoneyRequest( isDeleteAction?: boolean, isChatReportArchived = false, outstandingReportsByPolicyID?: OutstandingReportsByPolicyIDDerivedValue, - linkedTransaction?: OnyxEntry | SearchTransaction, + linkedTransaction?: OnyxEntry | Transaction, report?: OnyxInputOrEntry, policy?: OnyxEntry, ): boolean { @@ -4906,7 +4905,7 @@ function areAllRequestsBeingSmartScanned(iouReportID: string | undefined, report * * NOTE: This method is only meant to be used inside this action file. Do not export and use it elsewhere. Use useOnyx instead. */ -function getLinkedTransaction(reportAction: OnyxEntry, transactions?: SearchTransaction[]): OnyxEntry | SearchTransaction { +function getLinkedTransaction(reportAction: OnyxEntry, transactions?: Transaction[]): OnyxEntry | Transaction { let transactionID: string | undefined; if (isMoneyRequestAction(reportAction)) { @@ -4961,7 +4960,7 @@ function getTransactionReportName({ reports, }: { reportAction: OnyxEntry; - transactions?: SearchTransaction[]; + transactions?: Transaction[]; reports?: Report[]; }): string { if (isReversedTransaction(reportAction)) { @@ -5594,7 +5593,7 @@ function getReportName( personalDetails?: Partial, invoiceReceiverPolicy?: OnyxEntry, reportAttributes?: ReportAttributesDerivedValue['reports'], - transactions?: SearchTransaction[], + transactions?: Transaction[], isReportArchived?: boolean, reports?: Report[], policies?: Policy[], @@ -8895,7 +8894,7 @@ function hasViolations( reportID: string | undefined, transactionViolations: OnyxCollection, shouldShowInReview?: boolean, - reportTransactions?: SearchTransaction[], + reportTransactions?: Transaction[], ): boolean { const transactions = reportTransactions ?? getReportTransactions(reportID); return transactions.some((transaction) => hasViolation(transaction, transactionViolations, shouldShowInReview)); @@ -8908,7 +8907,7 @@ function hasWarningTypeViolations( reportID: string | undefined, transactionViolations: OnyxCollection, shouldShowInReview?: boolean, - reportTransactions?: SearchTransaction[], + reportTransactions?: Transaction[], ): boolean { const transactions = reportTransactions ?? getReportTransactions(reportID); return transactions.some((transaction) => hasWarningTypeViolation(transaction, transactionViolations, shouldShowInReview)); @@ -8941,7 +8940,7 @@ function hasNoticeTypeViolations( reportID: string | undefined, transactionViolations: OnyxCollection, shouldShowInReview?: boolean, - reportTransactions?: SearchTransaction[], + reportTransactions?: Transaction[], ): boolean { const transactions = reportTransactions ?? getReportTransactions(reportID); return transactions.some((transaction) => hasNoticeTypeViolation(transaction, transactionViolations, shouldShowInReview)); @@ -8950,7 +8949,7 @@ function hasNoticeTypeViolations( /** * Checks to see if a report contains any type of violation */ -function hasAnyViolations(reportID: string | undefined, transactionViolations: OnyxCollection, reportTransactions?: SearchTransaction[]) { +function hasAnyViolations(reportID: string | undefined, transactionViolations: OnyxCollection, reportTransactions?: Transaction[]) { return ( hasViolations(reportID, transactionViolations, undefined, reportTransactions) || hasNoticeTypeViolations(reportID, transactionViolations, true, reportTransactions) || @@ -8974,12 +8973,12 @@ function shouldBlockSubmitDueToStrictPolicyRules( reportID: string | undefined, transactionViolations: OnyxCollection, areStrictPolicyRulesEnabled: boolean, - reportTransactions?: Transaction[] | SearchTransaction[], + reportTransactions?: Transaction[] | Transaction[], ) { if (!areStrictPolicyRulesEnabled) { return false; } - return hasAnyViolations(reportID, transactionViolations, reportTransactions as SearchTransaction[]); + return hasAnyViolations(reportID, transactionViolations, reportTransactions as Transaction[]); } type ReportErrorsAndReportActionThatRequiresAttention = { @@ -10332,7 +10331,7 @@ function getAllHeldTransactions(iouReportID?: string): Transaction[] { /** * Check if Report has any held expenses */ -function hasHeldExpenses(iouReportID?: string, allReportTransactions?: SearchTransaction[]): boolean { +function hasHeldExpenses(iouReportID?: string, allReportTransactions?: Transaction[]): boolean { const iouReportTransactions = getReportTransactions(iouReportID); const transactions = allReportTransactions ?? iouReportTransactions; return transactions.some((transaction) => isOnHoldTransactionUtils(transaction)); @@ -10341,7 +10340,7 @@ function hasHeldExpenses(iouReportID?: string, allReportTransactions?: SearchTra /** * Check if all expenses in the Report are on hold */ -function hasOnlyHeldExpenses(iouReportID?: string, allReportTransactions?: SearchTransaction[]): boolean { +function hasOnlyHeldExpenses(iouReportID?: string, allReportTransactions?: Transaction[]): boolean { const transactionsByIouReportID = getReportTransactions(iouReportID); const reportTransactions = allReportTransactions ?? transactionsByIouReportID; return reportTransactions.length > 0 && !reportTransactions.some((transaction) => !isOnHoldTransactionUtils(transaction)); diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index bbb966a0b52ac..a954a55804e50 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -419,12 +419,12 @@ function search({ */ function updateSearchResultsWithTransactionThreadReportID(hash: number, transactionID: string, reportID: string) { // eslint-disable-next-line @typescript-eslint/no-deprecated - const onyxUpdate: Record>> = { + const onyxUpdate = { data: { [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]: { transactionThreadReportID: reportID, }, - }, + } as Partial, }; Onyx.merge(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, onyxUpdate); } From 600ec50fed975b70e364684277f0e6cbe05b6163 Mon Sep 17 00:00:00 2001 From: Maruf Sharifi Date: Thu, 20 Nov 2025 10:52:12 +0430 Subject: [PATCH 18/18] fixed report utils type error --- src/libs/ReportUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 94b6211434346..7a96a1f6a100b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8973,12 +8973,12 @@ function shouldBlockSubmitDueToStrictPolicyRules( reportID: string | undefined, transactionViolations: OnyxCollection, areStrictPolicyRulesEnabled: boolean, - reportTransactions?: Transaction[] | Transaction[], + reportTransactions?: Transaction[], ) { if (!areStrictPolicyRulesEnabled) { return false; } - return hasAnyViolations(reportID, transactionViolations, reportTransactions as Transaction[]); + return hasAnyViolations(reportID, transactionViolations, reportTransactions); } type ReportErrorsAndReportActionThatRequiresAttention = {