From b3befd82dda7f8eb40bd313aa39428be312b8490 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Thu, 22 May 2025 13:32:32 +0200 Subject: [PATCH 01/11] 61777 follow-up --- .../TransactionPreview/TransactionPreviewContent.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx index 16e33d7f551c..7215cf8b5959 100644 --- a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx +++ b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx @@ -15,12 +15,13 @@ import useOnyx from '@hooks/useOnyx'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {convertToDisplayString} from '@libs/CurrencyUtils'; +import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {calculateAmount} from '@libs/IOUUtils'; import {getAvatarsForAccountIDs} from '@libs/OptionsListUtils'; import Parser from '@libs/Parser'; import {getCleanedTagName} from '@libs/PolicyUtils'; import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils'; -import {getOriginalMessage, getReportActions, isMoneyRequestAction} from '@libs/ReportActionsUtils'; +import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import type {TransactionDetails} from '@libs/ReportUtils'; import {canEditMoneyRequest, getTransactionDetails, getWorkspaceIcon, isIOUReport, isPolicyExpenseChat, isReportApproved, isSettled} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; @@ -63,7 +64,7 @@ function TransactionPreviewContent({ const ownerAccountID = iouReport?.ownerAccountID ?? reportPreviewAction?.childOwnerAccountID ?? CONST.DEFAULT_NUMBER_ID; const isReportAPolicyExpenseChat = isPolicyExpenseChat(chatReport); const {amount: requestAmount, comment: requestComment, merchant, tag, category, currency: requestCurrency} = transactionDetails; - const reportActions = useMemo(() => (iouReport ? getReportActions(iouReport) ?? {} : {}), [iouReport]); + const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(iouReport?.reportID)}`, {canBeMissing: true}); const transactionPreviewCommonArguments = useMemo( () => ({ From 903702beed3c199b8932e90ac0d5079c4615374a Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Thu, 22 May 2025 16:06:07 +0200 Subject: [PATCH 02/11] WIP prepare files for context merge --- .storybook/preview.tsx | 13 +--- src/CONST.ts | 2 + src/components/MoneyReportHeader.tsx | 4 +- .../MoneyRequestReportActionsList.tsx | 4 +- .../MoneyRequestReportContext.tsx | 69 ----------------- .../MoneyRequestReportTransactionList.tsx | 4 +- src/components/Search/SearchContext.tsx | 77 ++++++++++++++++++- src/components/Search/types.ts | 9 +++ src/hooks/useSelectedTransactionsActions.ts | 4 +- .../Navigation/AppNavigator/AuthScreens.tsx | 9 ++- .../ModalStackNavigators/index.tsx | 2 +- src/pages/ReportDetailsPage.tsx | 3 +- src/pages/Search/SearchHoldReasonPage.tsx | 44 ++++++----- ...SearchMoneyRequestReportHoldReasonPage.tsx | 58 -------------- .../iou/request/step/IOURequestEditReport.tsx | 5 +- 15 files changed, 130 insertions(+), 177 deletions(-) delete mode 100644 src/components/MoneyRequestReportView/MoneyRequestReportContext.tsx delete mode 100644 src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index e29fe39c0e0a..5093417812d2 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -3,7 +3,7 @@ import React from 'react'; import Onyx from 'react-native-onyx'; import {SafeAreaProvider} from 'react-native-safe-area-context'; import type {Parameters} from 'storybook/internal/types'; -import {MoneyRequestReportContextProvider} from '@components/MoneyRequestReportView/MoneyRequestReportContext'; +import {SearchContextProvider} from '@components/Search/SearchContext'; import ComposeProviders from '@src/components/ComposeProviders'; import HTMLEngineProvider from '@src/components/HTMLEngineProvider'; import {LocaleContextProvider} from '@src/components/LocaleContextProvider'; @@ -23,16 +23,7 @@ Onyx.init({ const decorators = [ (Story: React.ElementType) => ( diff --git a/src/CONST.ts b/src/CONST.ts index 0e6c66c297fb..edbda8b2a6bc 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -7114,6 +7114,8 @@ const CONST = { RESCHEDULED: 'rescheduled', CANCELLED: 'cancelled', }, + + TEMPORARY_MONEY_REQUEST_REPORT_CONTEXT: true, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 194ead6f241a..502df42209e0 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -100,9 +100,9 @@ import Modal from './Modal'; import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar'; import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar'; import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; -import {useMoneyRequestReportContext} from './MoneyRequestReportView/MoneyRequestReportContext'; import type {ActionHandledType} from './ProcessMoneyReportHoldMenu'; import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu'; +import {useSearchContext} from './Search/SearchContext'; import AnimatedSettlementButton from './SettlementButton/AnimatedSettlementButton'; import Text from './Text'; @@ -238,7 +238,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); - const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(); + const {selectedTransactionsID, setSelectedTransactionsID} = useSearchContext(CONST.TEMPORARY_MONEY_REQUEST_REPORT_CONTEXT); const { options: selectedTransactionsOptions, diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index cd2846451d8a..df28841a3876 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -13,6 +13,7 @@ import DecisionModal from '@components/DecisionModal'; import FlatList from '@components/FlatList'; import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/InvertedFlatList/BaseInvertedFlatList'; import {PressableWithFeedback} from '@components/Pressable'; +import {useSearchContext} from '@components/Search/SearchContext'; import Text from '@components/Text'; import useLoadReportActions from '@hooks/useLoadReportActions'; import useLocalize from '@hooks/useLocalize'; @@ -50,7 +51,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; -import {useMoneyRequestReportContext} from './MoneyRequestReportContext'; import MoneyRequestReportTransactionList from './MoneyRequestReportTransactionList'; import MoneyRequestViewReportFields from './MoneyRequestViewReportFields'; import SearchMoneyRequestReportEmptyState from './SearchMoneyRequestReportEmptyState'; @@ -121,7 +121,7 @@ function MoneyRequestReportActionsList({report, policy, reportActions = [], tran const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false}); const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); - const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(); + const {selectedTransactionsID, setSelectedTransactionsID} = useSearchContext(CONST.TEMPORARY_MONEY_REQUEST_REPORT_CONTEXT); const {selectionMode} = useMobileSelectionMode(); const { diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportContext.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportContext.tsx deleted file mode 100644 index 417e87c751e7..000000000000 --- a/src/components/MoneyRequestReportView/MoneyRequestReportContext.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, {useCallback, useContext, useMemo, useState} from 'react'; -import type ChildrenProps from '@src/types/utils/ChildrenProps'; - -type TMoneyRequestReportContext = { - selectedTransactionsID: string[]; - setSelectedTransactionsID: (transactionsID: string[]) => void; - toggleTransaction: (transactionID: string) => void; - removeTransaction: (transactionID?: string) => void; - isTransactionSelected: (transactionID: string) => boolean; -}; - -const defaultMoneyRequestReportContext = { - selectedTransactionsID: [], - setSelectedTransactionsID: () => {}, - toggleTransaction: () => {}, - removeTransaction: () => {}, - isTransactionSelected: () => false, -}; - -const Context = React.createContext(defaultMoneyRequestReportContext); - -// TODO merge it with SearchContext in follow-up - https://github.com/Expensify/App/issues/59431 -function MoneyRequestReportContextProvider({children}: ChildrenProps) { - const [selectedTransactions, setSelectedTransactions] = useState([]); - - const setSelectedTransactionsID = useCallback((transactionsID: string[]) => { - setSelectedTransactions(transactionsID); - }, []); - - const toggleTransaction = useCallback((transactionID: string) => { - setSelectedTransactions((prev) => { - if (prev.includes(transactionID)) { - return prev.filter((t) => t !== transactionID); - } - return [...prev, transactionID]; - }); - }, []); - - const removeTransaction = useCallback((transactionID?: string) => { - setSelectedTransactions((prev) => { - return prev.filter((t) => t !== transactionID); - }); - }, []); - - const isTransactionSelected = useCallback((transactionID: string) => selectedTransactions.includes(transactionID), [selectedTransactions]); - - const context = useMemo( - () => ({ - selectedTransactionsID: selectedTransactions, - setSelectedTransactionsID, - toggleTransaction, - isTransactionSelected, - removeTransaction, - }), - [isTransactionSelected, removeTransaction, selectedTransactions, setSelectedTransactionsID, toggleTransaction], - ); - - return {children}; -} - -function useMoneyRequestReportContext() { - const context = useContext(Context); - - return context; -} - -MoneyRequestReportContextProvider.displayName = 'MoneyRequestReportContextProvider'; - -export {MoneyRequestReportContextProvider, useMoneyRequestReportContext}; diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index bfc4d36cb078..f62ab90a3342 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -9,6 +9,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import Modal from '@components/Modal'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; +import {useSearchContext} from '@components/Search/SearchContext'; import type {SortOrder} from '@components/Search/types'; import Text from '@components/Text'; import TransactionItemRow from '@components/TransactionItemRow'; @@ -36,7 +37,6 @@ import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; -import {useMoneyRequestReportContext} from './MoneyRequestReportContext'; import MoneyRequestReportTableHeader from './MoneyRequestReportTableHeader'; import SearchMoneyRequestReportEmptyState from './SearchMoneyRequestReportEmptyState'; import {setActiveTransactionThreadIDs} from './TransactionThreadReportIDRepository'; @@ -108,7 +108,7 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions, const {bind} = useHover(); const {isMouseDownOnInput, setMouseUp} = useMouseContext(); - const {selectedTransactionsID, setSelectedTransactionsID, toggleTransaction, isTransactionSelected} = useMoneyRequestReportContext(); + const {selectedTransactionsID, setSelectedTransactionsID, toggleTransaction, isTransactionSelected} = useSearchContext(CONST.TEMPORARY_MONEY_REQUEST_REPORT_CONTEXT); const {selectionMode} = useMobileSelectionMode(); useFocusEffect( diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index e593e8dc8d3f..bce3cb8777f9 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -4,7 +4,7 @@ import {isMoneyRequestReport} from '@libs/ReportUtils'; import {isReportListItemType, isTransactionListItemType} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import type {SearchContext, SelectedTransactions} from './types'; +import type {SearchContext, SelectedTransactions, TMoneyRequestReportContext} from './types'; const defaultSearchContext: SearchContext = { currentSearchHash: -1, @@ -25,7 +25,16 @@ const defaultSearchContext: SearchContext = { isOnSearch: false, }; +const defaultMoneyRequestReportContext: TMoneyRequestReportContext = { + selectedTransactionsID: [], + setSelectedTransactionsID: () => {}, + toggleTransaction: () => {}, + removeTransaction: () => {}, + isTransactionSelected: () => false, +}; + const Context = React.createContext(defaultSearchContext); +const MoneyRequestReportContext = React.createContext(defaultMoneyRequestReportContext); function getReportsFromSelectedTransactions( data: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[] | TaskListItemType[], @@ -63,7 +72,7 @@ function getReportsFromSelectedTransactions( return []; } -function SearchContextProvider({children}: ChildrenProps) { +function ContextProvider({children}: ChildrenProps) { const [shouldShowExportModeOption, setShouldShowExportModeOption] = useState(false); const [isExportMode, setExportMode] = useState(false); @@ -151,10 +160,70 @@ function SearchContextProvider({children}: ChildrenProps) { return {children}; } -function useSearchContext() { - return useContext(Context); +// TODO merge it with SearchContext in follow-up - https://github.com/Expensify/App/issues/59431 +function MoneyRequestReportContextProvider({children}: ChildrenProps) { + const [selectedTransactions, setSelectedTransactions] = useState([]); + + const setSelectedTransactionsID = useCallback((transactionsID: string[]) => { + setSelectedTransactions(transactionsID); + }, []); + + const toggleTransaction = useCallback((transactionID: string) => { + setSelectedTransactions((prev) => { + if (prev.includes(transactionID)) { + return prev.filter((t) => t !== transactionID); + } + return [...prev, transactionID]; + }); + }, []); + + const removeTransaction = useCallback((transactionID?: string) => { + setSelectedTransactions((prev) => { + return prev.filter((t) => t !== transactionID); + }); + }, []); + + const isTransactionSelected = useCallback((transactionID: string) => selectedTransactions.includes(transactionID), [selectedTransactions]); + + const context = useMemo( + () => ({ + selectedTransactionsID: selectedTransactions, + setSelectedTransactionsID, + toggleTransaction, + isTransactionSelected, + removeTransaction, + }), + [isTransactionSelected, removeTransaction, selectedTransactions, setSelectedTransactionsID, toggleTransaction], + ); + + return {children}; +} + +type ExtractContextType = T extends true ? TMoneyRequestReportContext : SearchContext; + +function useSearchContext(shouldUseMoneyRequestContext?: false): SearchContext; +function useSearchContext(shouldUseMoneyRequestContext: true): TMoneyRequestReportContext; +function useSearchContext(shouldUseMoneyRequestContext: boolean): ExtractContextType; + +function useSearchContext(shouldUseMoneyRequestContext = false) { + const moneyRequestContext = useContext(MoneyRequestReportContext); + const context = useContext(Context); + + if (shouldUseMoneyRequestContext) { + return moneyRequestContext; + } + + return context; +} + +function SearchContextProvider(shouldUseMoneyRequestContext = false) { + if (shouldUseMoneyRequestContext) { + return MoneyRequestReportContextProvider; + } + return ContextProvider; } +MoneyRequestReportContextProvider.displayName = 'MoneyRequestReportContextProvider'; SearchContextProvider.displayName = 'SearchContextProvider'; export {SearchContextProvider, useSearchContext, Context}; diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 2fee07cf0a2c..22cff5ceae83 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -90,6 +90,14 @@ type SearchContext = { isOnSearch: boolean; }; +type TMoneyRequestReportContext = { + selectedTransactionsID: string[]; + setSelectedTransactionsID: (transactionsID: string[]) => void; + toggleTransaction: (transactionID: string) => void; + removeTransaction: (transactionID?: string) => void; + isTransactionSelected: (transactionID: string) => boolean; +}; + type ASTNode = { operator: ValueOf; left: ValueOf | ASTNode; @@ -173,6 +181,7 @@ export type { SearchQueryString, SortOrder, SearchContext, + TMoneyRequestReportContext, ASTNode, QueryFilter, QueryFilters, diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index 02bdf3529da6..f903d27cd8e5 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -1,7 +1,7 @@ import {useCallback, useMemo, useState} from 'react'; import {useOnyx} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; -import {useMoneyRequestReportContext} from '@components/MoneyRequestReportView/MoneyRequestReportContext'; +import {useSearchContext} from '@components/Search/SearchContext'; import {deleteMoneyRequest, unholdRequest} from '@libs/actions/IOU'; import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import {exportReportToCSV} from '@libs/actions/Report'; @@ -42,7 +42,7 @@ function useSelectedTransactionsActions({ session?: Session; onExportFailed?: () => void; }) { - const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(); + const {selectedTransactionsID, setSelectedTransactionsID} = useSearchContext(CONST.TEMPORARY_MONEY_REQUEST_REPORT_CONTEXT); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false}); const selectedTransactions = useMemo( () => diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 2484439efb5c..b457030be2b5 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -6,7 +6,6 @@ import Onyx, {useOnyx, withOnyx} from 'react-native-onyx'; import ActiveWorkspaceContextProvider from '@components/ActiveWorkspaceProvider'; import ComposeProviders from '@components/ComposeProviders'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import {MoneyRequestReportContextProvider} from '@components/MoneyRequestReportView/MoneyRequestReportContext'; import OptionsListContextProvider from '@components/OptionListContextProvider'; import {SearchContextProvider} from '@components/Search/SearchContext'; import {useSearchRouterContext} from '@components/Search/SearchRouter/SearchRouterContext'; @@ -526,7 +525,13 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie return ( {/* This has to be the first navigator in auth screens. */} diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index f5d1b6ee8c2c..e5264ac33a0c 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -698,7 +698,7 @@ const TransactionDuplicateStackNavigator = createModalStackNavigator( { [SCREENS.SEARCH.REPORT_RHP]: () => require('../../../../pages/home/ReportScreen').default, - [SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS]: () => require('../../../../pages/Search/SearchMoneyRequestReportHoldReasonPage').default, + [SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS]: () => require('../../../../pages/Search/SearchHoldReasonPage').default, [SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP]: () => require('../../../../pages/Search/SearchHoldReasonPage').default, [SCREENS.SEARCH.TRANSACTIONS_CHANGE_REPORT_SEARCH_RHP]: () => require('../../../../pages/Search/SearchTransactionsChangeReport').default, }, diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 8b1b366afb75..88cee79f4772 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -16,7 +16,6 @@ import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/M import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import {useMoneyRequestReportContext} from '@components/MoneyRequestReportView/MoneyRequestReportContext'; import MultipleAvatars from '@components/MultipleAvatars'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ParentNavigationSubtitle from '@components/ParentNavigationSubtitle'; @@ -194,7 +193,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta canBeMissing: true, }); - const {removeTransaction} = useMoneyRequestReportContext(); + const {removeTransaction} = useSearchContext(CONST.TEMPORARY_MONEY_REQUEST_REPORT_CONTEXT); const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); diff --git a/src/pages/Search/SearchHoldReasonPage.tsx b/src/pages/Search/SearchHoldReasonPage.tsx index 3bcd3a84de91..915d5682ee32 100644 --- a/src/pages/Search/SearchHoldReasonPage.tsx +++ b/src/pages/Search/SearchHoldReasonPage.tsx @@ -1,39 +1,43 @@ import React, {useCallback, useEffect} from 'react'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import {useSearchContext} from '@components/Search/SearchContext'; +import type {SearchContext, TMoneyRequestReportContext} from '@components/Search/types'; import useLocalize from '@hooks/useLocalize'; import {clearErrorFields, clearErrors} from '@libs/actions/FormActions'; import {holdMoneyRequestOnSearch} from '@libs/actions/Search'; import Navigation from '@libs/Navigation/Navigation'; -import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import {getFieldRequiredErrors} from '@libs/ValidationUtils'; +import type {SearchReportParamList} from '@navigation/types'; import HoldReasonFormView from '@pages/iou/HoldReasonFormView'; +import {putOnHold} from '@userActions/IOU'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Route} from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm'; -type SearchHoldReasonPageRouteParams = { - /** Link to previous page */ - backTo: Route; -}; +type Props = PlatformStackScreenProps; -type SearchHoldReasonPageProps = { - /** Navigation route context info provided by react navigation */ - route: PlatformStackRouteProp<{params?: SearchHoldReasonPageRouteParams}>; -}; - -function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) { +function SearchHoldReasonPage({route}: Props) { const {translate} = useLocalize(); - - const {currentSearchHash, selectedTransactions, clearSelectedTransactions} = useSearchContext(); const {backTo = ''} = route.params ?? {}; + const shouldUseMoneyRequestContext = route.name !== SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP; + const contextValue = useSearchContext(shouldUseMoneyRequestContext); - const selectedTransactionIDs = Object.keys(selectedTransactions); - const onSubmit = (values: FormOnyxValues) => { - holdMoneyRequestOnSearch(currentSearchHash, selectedTransactionIDs, values.comment); - clearSelectedTransactions(); - Navigation.goBack(); - }; + const onSubmit = useCallback( + (values: FormOnyxValues) => { + if (shouldUseMoneyRequestContext) { + const {selectedTransactionsID, setSelectedTransactionsID} = contextValue as TMoneyRequestReportContext; + selectedTransactionsID.forEach((transactionID) => putOnHold(transactionID, values.comment, route.params.reportID)); + setSelectedTransactionsID([]); + } else { + const {currentSearchHash, selectedTransactions, clearSelectedTransactions} = contextValue as SearchContext; + holdMoneyRequestOnSearch(currentSearchHash, Object.keys(selectedTransactions), values.comment); + clearSelectedTransactions(); + } + Navigation.goBack(); + }, + [contextValue, route.params?.reportID, shouldUseMoneyRequestContext], + ); const validate = useCallback( (values: FormOnyxValues) => { diff --git a/src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx b/src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx deleted file mode 100644 index 3eb2a415f5d3..000000000000 --- a/src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, {useCallback, useEffect} from 'react'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; -import {useMoneyRequestReportContext} from '@components/MoneyRequestReportView/MoneyRequestReportContext'; -import useLocalize from '@hooks/useLocalize'; -import {putOnHold} from '@libs/actions/IOU'; -import Navigation from '@libs/Navigation/Navigation'; -import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {SearchReportParamList} from '@libs/Navigation/types'; -import {getFieldRequiredErrors} from '@libs/ValidationUtils'; -import HoldReasonFormView from '@pages/iou/HoldReasonFormView'; -import {clearErrorFields, clearErrors} from '@userActions/FormActions'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type SCREENS from '@src/SCREENS'; -import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm'; - -function SearchMoneyRequestReportHoldReasonPage({route}: PlatformStackScreenProps) { - const {translate} = useLocalize(); - - const {backTo, reportID} = route.params; - const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(); - - const onSubmit = (values: FormOnyxValues) => { - selectedTransactionsID.forEach((transactionID) => putOnHold(transactionID, values.comment, reportID)); - - setSelectedTransactionsID([]); - Navigation.goBack(); - }; - - const validate = useCallback( - (values: FormOnyxValues) => { - const errors: FormInputErrors = getFieldRequiredErrors(values, [INPUT_IDS.COMMENT]); - - if (!values.comment) { - errors.comment = translate('common.error.fieldRequired'); - } - - return errors; - }, - [translate], - ); - - useEffect(() => { - clearErrors(ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM); - clearErrorFields(ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM); - }, []); - - return ( - - ); -} - -SearchMoneyRequestReportHoldReasonPage.displayName = 'SearchMoneyRequestReportHoldReasonPage'; - -export default SearchMoneyRequestReportHoldReasonPage; diff --git a/src/pages/iou/request/step/IOURequestEditReport.tsx b/src/pages/iou/request/step/IOURequestEditReport.tsx index 43a796ab1d8c..b36326db6390 100644 --- a/src/pages/iou/request/step/IOURequestEditReport.tsx +++ b/src/pages/iou/request/step/IOURequestEditReport.tsx @@ -1,9 +1,10 @@ import React from 'react'; import {useOnyx} from 'react-native-onyx'; -import {useMoneyRequestReportContext} from '@components/MoneyRequestReportView/MoneyRequestReportContext'; +import {useSearchContext} from '@components/Search/SearchContext'; import type {ListItem} from '@components/SelectionList/types'; import {changeTransactionsReport} from '@libs/actions/Transaction'; import Navigation from '@libs/Navigation/Navigation'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import IOURequestEditReportCommon from './IOURequestEditReportCommon'; @@ -20,7 +21,7 @@ type IOURequestEditReportProps = WithWritableReportOrNotFoundProps Date: Tue, 27 May 2025 14:53:37 +0200 Subject: [PATCH 03/11] 'Stupid' merge of contexts --- .storybook/preview.tsx | 2 +- src/CONST.ts | 2 - src/components/MoneyReportHeader.tsx | 2 +- .../MoneyRequestReportActionsList.tsx | 2 +- .../MoneyRequestReportTransactionList.tsx | 2 +- src/components/Search/SearchContext.tsx | 141 +++++------------- .../SearchPageHeader/SearchStatusBar.tsx | 3 +- src/hooks/useSelectedTransactionsActions.ts | 2 +- .../Navigation/AppNavigator/AuthScreens.tsx | 10 +- src/libs/ReportUtils.ts | 40 +++++ src/pages/ReportDetailsPage.tsx | 4 +- src/pages/Search/SearchHoldReasonPage.tsx | 13 +- .../iou/request/step/IOURequestEditReport.tsx | 2 +- 13 files changed, 91 insertions(+), 134 deletions(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 5093417812d2..9a5e13b7b380 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -23,7 +23,7 @@ Onyx.init({ const decorators = [ (Story: React.ElementType) => ( diff --git a/src/CONST.ts b/src/CONST.ts index a913a364898c..2736c8c423c3 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -7150,8 +7150,6 @@ const CONST = { RESCHEDULED: 'rescheduled', CANCELLED: 'cancelled', }, - - TEMPORARY_MONEY_REQUEST_REPORT_CONTEXT: true, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 9c65c98f9cbe..4b6d6c04e6ce 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -259,7 +259,7 @@ function MoneyReportHeader({ const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); - const {selectedTransactionsID, setSelectedTransactionsID} = useSearchContext(CONST.TEMPORARY_MONEY_REQUEST_REPORT_CONTEXT); + const {selectedTransactionsID, setSelectedTransactionsID} = useSearchContext(); const { options: selectedTransactionsOptions, diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index 799fe9551c6f..4c60bb1cbe4c 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -138,7 +138,7 @@ function MoneyRequestReportActionsList({ const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false}); const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); - const {selectedTransactionsID, setSelectedTransactionsID} = useSearchContext(CONST.TEMPORARY_MONEY_REQUEST_REPORT_CONTEXT); + const {selectedTransactionsID, setSelectedTransactionsID} = useSearchContext(); const {selectionMode} = useMobileSelectionMode(); const { diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index f655ad637729..d943035465e2 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -125,7 +125,7 @@ function MoneyRequestReportTransactionList({ const {bind} = useHover(); const {isMouseDownOnInput, setMouseUp} = useMouseContext(); - const {selectedTransactionsID, setSelectedTransactionsID, toggleTransaction, isTransactionSelected} = useSearchContext(CONST.TEMPORARY_MONEY_REQUEST_REPORT_CONTEXT); + const {selectedTransactionsID, setSelectedTransactionsID, toggleTransaction, isTransactionSelected} = useSearchContext(); const {selectionMode} = useMobileSelectionMode(); useFocusEffect( diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index bce3cb8777f9..291f2e64a668 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -1,10 +1,10 @@ import React, {useCallback, useContext, useMemo, useState} from 'react'; import type {ReportActionListItemType, ReportListItemType, TaskListItemType, TransactionListItemType} from '@components/SelectionList/types'; -import {isMoneyRequestReport} from '@libs/ReportUtils'; -import {isReportListItemType, isTransactionListItemType} from '@libs/SearchUIUtils'; -import CONST from '@src/CONST'; +import {getReportsFromSelectedTransactions} from '@libs/ReportUtils'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import type {SearchContext, SelectedTransactions, TMoneyRequestReportContext} from './types'; +import type {SearchContext as SearchContextOld, SelectedTransactions, TMoneyRequestReportContext} from './types'; + +type SearchContext = TMoneyRequestReportContext & SearchContextOld; const defaultSearchContext: SearchContext = { currentSearchHash: -1, @@ -23,9 +23,7 @@ const defaultSearchContext: SearchContext = { isExportMode: false, setExportMode: () => {}, isOnSearch: false, -}; - -const defaultMoneyRequestReportContext: TMoneyRequestReportContext = { + /** **************** */ selectedTransactionsID: [], setSelectedTransactionsID: () => {}, toggleTransaction: () => {}, @@ -34,47 +32,11 @@ const defaultMoneyRequestReportContext: TMoneyRequestReportContext = { }; const Context = React.createContext(defaultSearchContext); -const MoneyRequestReportContext = React.createContext(defaultMoneyRequestReportContext); - -function getReportsFromSelectedTransactions( - data: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[] | TaskListItemType[], - selectedTransactions: SelectedTransactions, -) { - if (data.length === 0) { - return []; - } - - if (isReportListItemType(data[0]) || isMoneyRequestReport(data[0])) { - return data - .filter( - (item): item is ReportListItemType => - isReportListItemType(item) && isMoneyRequestReport(item) && item.transactions?.every((transaction) => selectedTransactions[transaction.keyForList]?.isSelected), - ) - .map((item) => ({ - reportID: item.reportID, - action: item.action ?? CONST.SEARCH.ACTION_TYPES.VIEW, - total: item.total ?? CONST.DEFAULT_NUMBER_ID, - policyID: item.policyID, - })); - } - - if (isTransactionListItemType(data[0])) { - return data - .filter((transaction) => transaction.keyForList != null && selectedTransactions[transaction.keyForList]?.isSelected) - .map((transaction) => ({ - reportID: transaction.reportID, - action: 'action' in transaction ? transaction.action ?? CONST.SEARCH.ACTION_TYPES.VIEW : CONST.SEARCH.ACTION_TYPES.VIEW, - total: 'amount' in transaction ? transaction.amount ?? CONST.DEFAULT_NUMBER_ID : CONST.DEFAULT_NUMBER_ID, - policyID: transaction.policyID, - })); - } - - return []; -} -function ContextProvider({children}: ChildrenProps) { +function SearchContextProvider({children}: ChildrenProps) { const [shouldShowExportModeOption, setShouldShowExportModeOption] = useState(false); const [isExportMode, setExportMode] = useState(false); + const [selectedTransactionsID, setSelectedTransactionsID] = useState([]); const [searchContextData, setSearchContextData] = useState< Pick @@ -93,6 +55,23 @@ function ContextProvider({children}: ChildrenProps) { })); }, []); + const toggleTransaction = useCallback((transactionID: string) => { + setSelectedTransactionsID((prev) => { + if (prev.includes(transactionID)) { + return prev.filter((t) => t !== transactionID); + } + return [...prev, transactionID]; + }); + }, []); + + const removeTransaction = useCallback((transactionID?: string) => { + setSelectedTransactionsID((prev) => { + return prev.filter((t) => t !== transactionID); + }); + }, []); + + const isTransactionSelected = useCallback((transactionID: string) => selectedTransactionsID.includes(transactionID), [selectedTransactionsID]); + const setSelectedTransactions = useCallback( (selectedTransactions: SelectedTransactions, data: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[] | TaskListItemType[]) => { // When selecting transactions, we also need to manage the reports to which these transactions belong. This is done to ensure proper exporting to CSV. @@ -142,6 +121,12 @@ function ContextProvider({children}: ChildrenProps) { setShouldShowExportModeOption, isExportMode, setExportMode, + /** **************** */ + selectedTransactionsID, + setSelectedTransactionsID, + toggleTransaction, + isTransactionSelected, + removeTransaction, }), [ searchContextData, @@ -154,76 +139,22 @@ function ContextProvider({children}: ChildrenProps) { setShouldShowExportModeOption, isExportMode, setExportMode, - ], - ); - - return {children}; -} - -// TODO merge it with SearchContext in follow-up - https://github.com/Expensify/App/issues/59431 -function MoneyRequestReportContextProvider({children}: ChildrenProps) { - const [selectedTransactions, setSelectedTransactions] = useState([]); - - const setSelectedTransactionsID = useCallback((transactionsID: string[]) => { - setSelectedTransactions(transactionsID); - }, []); - - const toggleTransaction = useCallback((transactionID: string) => { - setSelectedTransactions((prev) => { - if (prev.includes(transactionID)) { - return prev.filter((t) => t !== transactionID); - } - return [...prev, transactionID]; - }); - }, []); - - const removeTransaction = useCallback((transactionID?: string) => { - setSelectedTransactions((prev) => { - return prev.filter((t) => t !== transactionID); - }); - }, []); - - const isTransactionSelected = useCallback((transactionID: string) => selectedTransactions.includes(transactionID), [selectedTransactions]); - - const context = useMemo( - () => ({ - selectedTransactionsID: selectedTransactions, + /** **************** */ + selectedTransactionsID, setSelectedTransactionsID, toggleTransaction, isTransactionSelected, removeTransaction, - }), - [isTransactionSelected, removeTransaction, selectedTransactions, setSelectedTransactionsID, toggleTransaction], + ], ); - return {children}; -} - -type ExtractContextType = T extends true ? TMoneyRequestReportContext : SearchContext; - -function useSearchContext(shouldUseMoneyRequestContext?: false): SearchContext; -function useSearchContext(shouldUseMoneyRequestContext: true): TMoneyRequestReportContext; -function useSearchContext(shouldUseMoneyRequestContext: boolean): ExtractContextType; - -function useSearchContext(shouldUseMoneyRequestContext = false) { - const moneyRequestContext = useContext(MoneyRequestReportContext); - const context = useContext(Context); - - if (shouldUseMoneyRequestContext) { - return moneyRequestContext; - } - - return context; + return {children}; } -function SearchContextProvider(shouldUseMoneyRequestContext = false) { - if (shouldUseMoneyRequestContext) { - return MoneyRequestReportContextProvider; - } - return ContextProvider; +function useSearchContext() { + return useContext(Context); } -MoneyRequestReportContextProvider.displayName = 'MoneyRequestReportContextProvider'; SearchContextProvider.displayName = 'SearchContextProvider'; export {SearchContextProvider, useSearchContext, Context}; diff --git a/src/components/Search/SearchPageHeader/SearchStatusBar.tsx b/src/components/Search/SearchPageHeader/SearchStatusBar.tsx index 13e08139a656..c959e26c0716 100644 --- a/src/components/Search/SearchPageHeader/SearchStatusBar.tsx +++ b/src/components/Search/SearchPageHeader/SearchStatusBar.tsx @@ -247,12 +247,11 @@ function SearchStatusBar({queryJSON, onStatusChange, headerButtonsOptions}: Sear const theme = useTheme(); const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); - const {selectedTransactions, setExportMode, isExportMode, shouldShowExportModeOption} = useSearchContext(); + const {selectedTransactions, setExportMode, isExportMode, shouldShowExportModeOption, shouldShowStatusBarLoading} = useSearchContext(); const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE, {canBeMissing: true}); const options = getOptions(queryJSON.type, queryJSON.groupBy); const scrollRef = useRef(null); const isScrolledRef = useRef(false); - const {shouldShowStatusBarLoading} = useSearchContext(); const {hash} = queryJSON; const [currentSearchResults] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, {canBeMissing: true}); const {isOffline} = useNetwork(); diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index 56e78733836a..9c6880be5b4b 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -42,7 +42,7 @@ function useSelectedTransactionsActions({ session?: Session; onExportFailed?: () => void; }) { - const {selectedTransactionsID, setSelectedTransactionsID} = useSearchContext(CONST.TEMPORARY_MONEY_REQUEST_REPORT_CONTEXT); + const {selectedTransactionsID, setSelectedTransactionsID} = useSearchContext(); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false}); const selectedTransactions = useMemo( () => diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index f4ccc99cc6c1..f6d3bd63036a 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -554,15 +554,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie } return ( - + {/* This has to be the first navigator in auth screens. */} + isReportListItemType(item) && isMoneyRequestReport(item) && item.transactions?.every((transaction) => selectedTransactions[transaction.keyForList]?.isSelected), + ) + .map((item) => ({ + reportID: item.reportID, + action: item.action ?? CONST.SEARCH.ACTION_TYPES.VIEW, + total: item.total ?? CONST.DEFAULT_NUMBER_ID, + policyID: item.policyID, + })); + } + + if (isTransactionListItemType(data[0])) { + return data + .filter((transaction) => transaction.keyForList != null && selectedTransactions[transaction.keyForList]?.isSelected) + .map((transaction) => ({ + reportID: transaction.reportID, + action: 'action' in transaction ? transaction.action ?? CONST.SEARCH.ACTION_TYPES.VIEW : CONST.SEARCH.ACTION_TYPES.VIEW, + total: 'amount' in transaction ? transaction.amount ?? CONST.DEFAULT_NUMBER_ID : CONST.DEFAULT_NUMBER_ID, + policyID: transaction.policyID, + })); + } + + return []; +} + export { addDomainToShortMention, completeShortMention, @@ -10933,6 +10972,7 @@ export { getReimbursementDeQueuedOrCanceledActionMessage, getReimbursementQueuedActionMessage, getReportActionActorAccountID, + getReportsFromSelectedTransactions, getReportDescription, getReportFieldKey, getReportIDFromLink, diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index aed88c358585..f9e96bd6a330 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -174,7 +174,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const [parentReportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.parentReportID}`, {canBeMissing: true}); const {reportActions} = usePaginatedReportActions(report.reportID); - const {currentSearchHash} = useSearchContext(); + const {currentSearchHash, removeTransaction} = useSearchContext(); // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to apply the correct modal type for the decision modal // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth @@ -193,8 +193,6 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta canBeMissing: true, }); - const {removeTransaction} = useSearchContext(CONST.TEMPORARY_MONEY_REQUEST_REPORT_CONTEXT); - const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const [isUnapproveModalVisible, setIsUnapproveModalVisible] = useState(false); diff --git a/src/pages/Search/SearchHoldReasonPage.tsx b/src/pages/Search/SearchHoldReasonPage.tsx index 915d5682ee32..f04c10ec4bb4 100644 --- a/src/pages/Search/SearchHoldReasonPage.tsx +++ b/src/pages/Search/SearchHoldReasonPage.tsx @@ -1,7 +1,6 @@ import React, {useCallback, useEffect} from 'react'; import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import {useSearchContext} from '@components/Search/SearchContext'; -import type {SearchContext, TMoneyRequestReportContext} from '@components/Search/types'; import useLocalize from '@hooks/useLocalize'; import {clearErrorFields, clearErrors} from '@libs/actions/FormActions'; import {holdMoneyRequestOnSearch} from '@libs/actions/Search'; @@ -20,23 +19,23 @@ type Props = PlatformStackScreenProps) => { - if (shouldUseMoneyRequestContext) { - const {selectedTransactionsID, setSelectedTransactionsID} = contextValue as TMoneyRequestReportContext; + if (isOnSearchHoldReason) { + const {selectedTransactionsID, setSelectedTransactionsID} = contextValue; selectedTransactionsID.forEach((transactionID) => putOnHold(transactionID, values.comment, route.params.reportID)); setSelectedTransactionsID([]); } else { - const {currentSearchHash, selectedTransactions, clearSelectedTransactions} = contextValue as SearchContext; + const {currentSearchHash, selectedTransactions, clearSelectedTransactions} = contextValue; holdMoneyRequestOnSearch(currentSearchHash, Object.keys(selectedTransactions), values.comment); clearSelectedTransactions(); } Navigation.goBack(); }, - [contextValue, route.params?.reportID, shouldUseMoneyRequestContext], + [contextValue, route.params?.reportID, isOnSearchHoldReason], ); const validate = useCallback( diff --git a/src/pages/iou/request/step/IOURequestEditReport.tsx b/src/pages/iou/request/step/IOURequestEditReport.tsx index b36326db6390..517eb5dbad49 100644 --- a/src/pages/iou/request/step/IOURequestEditReport.tsx +++ b/src/pages/iou/request/step/IOURequestEditReport.tsx @@ -21,7 +21,7 @@ type IOURequestEditReportProps = WithWritableReportOrNotFoundProps Date: Wed, 28 May 2025 08:13:57 +0200 Subject: [PATCH 04/11] Fix ESLint checks --- src/libs/ReportUtils.ts | 2 +- src/pages/iou/request/step/IOURequestEditReport.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 46421ea1fcc6..6a0ef34deb06 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -26,7 +26,6 @@ import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvata import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput'; import type {SelectedTransactions} from '@components/Search/types'; import type {ReportActionListItemType, ReportListItemType, TaskListItemType, TransactionListItemType} from '@components/SelectionList/types'; -import {isReportListItemType, isTransactionListItemType} from '@libs/SearchUIUtils'; import type {IOUAction, IOUType, OnboardingAccounting, OnboardingCompanySize, OnboardingPurpose, OnboardingTaskLinks} from '@src/CONST'; import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams} from '@src/languages/params'; @@ -210,6 +209,7 @@ import { wasActionTakenByCurrentUser, } from './ReportActionsUtils'; import type {LastVisibleMessage} from './ReportActionsUtils'; +import {isReportListItemType, isTransactionListItemType} from './SearchUIUtils'; import {shouldRestrictUserBillableActions} from './SubscriptionUtils'; import {getNavatticURL} from './TourUtils'; import { diff --git a/src/pages/iou/request/step/IOURequestEditReport.tsx b/src/pages/iou/request/step/IOURequestEditReport.tsx index 517eb5dbad49..97aa5019bd84 100644 --- a/src/pages/iou/request/step/IOURequestEditReport.tsx +++ b/src/pages/iou/request/step/IOURequestEditReport.tsx @@ -4,7 +4,6 @@ import {useSearchContext} from '@components/Search/SearchContext'; import type {ListItem} from '@components/SelectionList/types'; import {changeTransactionsReport} from '@libs/actions/Transaction'; import Navigation from '@libs/Navigation/Navigation'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import IOURequestEditReportCommon from './IOURequestEditReportCommon'; From 466408ba9bf3eb35ed8b34983b9e6a769b5b40d0 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Wed, 28 May 2025 10:53:35 +0200 Subject: [PATCH 05/11] Fix circular import --- src/components/Search/SearchContext.tsx | 2 +- src/libs/ReportUtils.ts | 40 ------------------------- src/libs/SearchUIUtils.ts | 39 +++++++++++++++++++++++- 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 291f2e64a668..6120374dafbe 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -1,6 +1,6 @@ import React, {useCallback, useContext, useMemo, useState} from 'react'; import type {ReportActionListItemType, ReportListItemType, TaskListItemType, TransactionListItemType} from '@components/SelectionList/types'; -import {getReportsFromSelectedTransactions} from '@libs/ReportUtils'; +import {getReportsFromSelectedTransactions} from '@libs/SearchUIUtils'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type {SearchContext as SearchContextOld, SelectedTransactions, TMoneyRequestReportContext} from './types'; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b56fb46183c8..c81f62e099cf 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -24,8 +24,6 @@ import {FallbackAvatar, IntacctSquare, NetSuiteSquare, QBOSquare, XeroSquare} fr import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput'; -import type {SelectedTransactions} from '@components/Search/types'; -import type {ReportActionListItemType, ReportListItemType, TaskListItemType, TransactionListItemType} from '@components/SelectionList/types'; import type {IOUAction, IOUType, OnboardingAccounting, OnboardingCompanySize, OnboardingPurpose, OnboardingTaskLinks} from '@src/CONST'; import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams} from '@src/languages/params'; @@ -210,7 +208,6 @@ import { wasActionTakenByCurrentUser, } from './ReportActionsUtils'; import type {LastVisibleMessage} from './ReportActionsUtils'; -import {isReportListItemType, isTransactionListItemType} from './SearchUIUtils'; import {shouldRestrictUserBillableActions} from './SubscriptionUtils'; import {getNavatticURL} from './TourUtils'; import { @@ -10829,42 +10826,6 @@ function findReportIDForAction(action?: ReportAction): string | undefined { ?.replace(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}`, ''); } -function getReportsFromSelectedTransactions( - data: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[] | TaskListItemType[], - selectedTransactions: SelectedTransactions, -) { - if (data.length === 0) { - return []; - } - - if (isReportListItemType(data[0]) || isMoneyRequestReport(data[0])) { - return data - .filter( - (item): item is ReportListItemType => - isReportListItemType(item) && isMoneyRequestReport(item) && item.transactions?.every((transaction) => selectedTransactions[transaction.keyForList]?.isSelected), - ) - .map((item) => ({ - reportID: item.reportID, - action: item.action ?? CONST.SEARCH.ACTION_TYPES.VIEW, - total: item.total ?? CONST.DEFAULT_NUMBER_ID, - policyID: item.policyID, - })); - } - - if (isTransactionListItemType(data[0])) { - return data - .filter((transaction) => transaction.keyForList != null && selectedTransactions[transaction.keyForList]?.isSelected) - .map((transaction) => ({ - reportID: transaction.reportID, - action: 'action' in transaction ? transaction.action ?? CONST.SEARCH.ACTION_TYPES.VIEW : CONST.SEARCH.ACTION_TYPES.VIEW, - total: 'amount' in transaction ? transaction.amount ?? CONST.DEFAULT_NUMBER_ID : CONST.DEFAULT_NUMBER_ID, - policyID: transaction.policyID, - })); - } - - return []; -} - export { addDomainToShortMention, completeShortMention, @@ -11004,7 +10965,6 @@ export { getReimbursementDeQueuedOrCanceledActionMessage, getReimbursementQueuedActionMessage, getReportActionActorAccountID, - getReportsFromSelectedTransactions, getReportDescription, getReportFieldKey, getReportIDFromLink, diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index fd6c691364a8..7922f5344487 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -3,7 +3,7 @@ import Onyx from 'react-native-onyx'; import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {MenuItemWithLink} from '@components/MenuItemList'; -import type {SearchColumnType, SearchQueryJSON, SearchStatus, SortOrder} from '@components/Search/types'; +import type {SearchColumnType, SearchQueryJSON, SearchStatus, SelectedTransactions, SortOrder} from '@components/Search/types'; import ChatListItem from '@components/SelectionList/ChatListItem'; import ReportListItem from '@components/SelectionList/Search/ReportListItem'; import TaskListItem from '@components/SelectionList/Search/TaskListItem'; @@ -636,6 +636,42 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx return Object.values(reportIDToTransactions); } +function getReportsFromSelectedTransactions( + data: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[] | TaskListItemType[], + selectedTransactions: SelectedTransactions, +) { + if (data.length === 0) { + return []; + } + + if (isReportListItemType(data[0]) || isMoneyRequestReport(data[0])) { + return data + .filter( + (item): item is ReportListItemType => + isReportListItemType(item) && isMoneyRequestReport(item) && item.transactions?.every((transaction) => selectedTransactions[transaction.keyForList]?.isSelected), + ) + .map((item) => ({ + reportID: item.reportID, + action: item.action ?? CONST.SEARCH.ACTION_TYPES.VIEW, + total: item.total ?? CONST.DEFAULT_NUMBER_ID, + policyID: item.policyID, + })); + } + + if (isTransactionListItemType(data[0])) { + return data + .filter((transaction) => transaction.keyForList != null && selectedTransactions[transaction.keyForList]?.isSelected) + .map((transaction) => ({ + reportID: transaction.reportID, + action: 'action' in transaction ? transaction.action ?? CONST.SEARCH.ACTION_TYPES.VIEW : CONST.SEARCH.ACTION_TYPES.VIEW, + total: 'amount' in transaction ? transaction.amount ?? CONST.DEFAULT_NUMBER_ID : CONST.DEFAULT_NUMBER_ID, + policyID: transaction.policyID, + })); + } + + return []; +} + /** * Returns the appropriate list item component based on the type and status of the search data. */ @@ -979,5 +1015,6 @@ export { shouldShowEmptyState, compareValues, isSearchDataLoaded, + getReportsFromSelectedTransactions, }; export type {SavedSearchMenuItem, SearchTypeMenuItem}; From 04ed3b26b9ef284700723180c9a7a4a127909f41 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Thu, 29 May 2025 09:49:52 +0200 Subject: [PATCH 06/11] Clean-up after merge & add optimization tweaks --- src/components/MoneyReportHeader.tsx | 6 +- .../MoneyRequestReportActionsList.tsx | 10 +-- .../MoneyRequestReportTransactionList.tsx | 18 ++++-- src/components/Search/SearchContext.tsx | 63 +++++-------------- src/components/Search/types.ts | 15 ++--- src/hooks/useSelectedTransactionsActions.ts | 12 ++-- src/pages/ReportDetailsPage.tsx | 9 ++- src/pages/Search/SearchHoldReasonPage.tsx | 4 +- .../iou/request/step/IOURequestEditReport.tsx | 4 +- 9 files changed, 61 insertions(+), 80 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 64aaae5ac6df..7d3e72a56d64 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -260,7 +260,7 @@ function MoneyReportHeader({ const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); - const {selectedTransactionsID, setSelectedTransactionsID} = useSearchContext(); + const {selectedTransactionsID, setSelectedTransactions} = useSearchContext(); const { options: selectedTransactionsOptions, @@ -841,7 +841,7 @@ function MoneyReportHeader({ if (!transactionThreadReportID) { return; } - setSelectedTransactionsID([]); + setSelectedTransactions([]); // We don't need to run the effect on change of setSelectedTransactionsID since it can cause the infinite loop. // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps @@ -869,7 +869,7 @@ function MoneyReportHeader({ { - setSelectedTransactionsID([]); + setSelectedTransactions([]); turnOffMobileSelectionMode(); }} /> diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index 2263a31ca342..db0fc1e98f2e 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -147,7 +147,7 @@ function MoneyRequestReportActionsList({ const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false}); const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); - const {selectedTransactionsID, setSelectedTransactionsID} = useSearchContext(); + const {selectedTransactionsID, setSelectedTransactions} = useSearchContext(); const {selectionMode} = useMobileSelectionMode(); const { @@ -548,9 +548,9 @@ function MoneyRequestReportActionsList({ isIndeterminate={selectedTransactionsID.length > 0 && selectedTransactionsID.length !== transactions.length} onPress={() => { if (selectedTransactionsID.length !== 0) { - setSelectedTransactionsID([]); + setSelectedTransactions([]); } else { - setSelectedTransactionsID(transactions.map((t) => t.transactionID)); + setSelectedTransactions(transactions.map((t) => t.transactionID)); } }} /> @@ -558,9 +558,9 @@ function MoneyRequestReportActionsList({ style={[styles.userSelectNone, styles.alignItemsCenter]} onPress={() => { if (selectedTransactionsID.length === transactions.length) { - setSelectedTransactionsID([]); + setSelectedTransactions([]); } else { - setSelectedTransactionsID(transactions.map((t) => t.transactionID)); + setSelectedTransactions(transactions.map((t) => t.transactionID)); } }} accessibilityLabel={translate('workspace.people.selectAll')} diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index 31037b2bdd57..05d23ef9571e 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -126,18 +126,26 @@ function MoneyRequestReportTransactionList({ const {bind} = useHover(); const {isMouseDownOnInput, setMouseUp} = useMouseContext(); - const {selectedTransactionsID, setSelectedTransactionsID, toggleTransaction, isTransactionSelected} = useSearchContext(); + const {selectedTransactionsID, setSelectedTransactions} = useSearchContext(); const {selectionMode} = useMobileSelectionMode(); + const toggleTransaction = useCallback( + (transactionID: string) => + setSelectedTransactions(selectedTransactionsID.includes(transactionID) ? selectedTransactionsID.filter((t) => t !== transactionID) : [...selectedTransactionsID, transactionID]), + [setSelectedTransactions, selectedTransactionsID], + ); + + const isTransactionSelected = useCallback((transactionID: string) => selectedTransactionsID.includes(transactionID), [selectedTransactionsID]); + useFocusEffect( useCallback(() => { return () => { if (navigationRef?.getRootState()?.routes.at(-1)?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR) { return; } - setSelectedTransactionsID([]); + setSelectedTransactions([]); }; - }, [setSelectedTransactionsID]), + }, [setSelectedTransactions]), ); const handleMouseLeave = (e: React.MouseEvent) => { @@ -198,9 +206,9 @@ function MoneyRequestReportTransactionList({ { if (selectedTransactionsID.length !== 0) { - setSelectedTransactionsID([]); + setSelectedTransactions([]); } else { - setSelectedTransactionsID(transactions.map((t) => t.transactionID)); + setSelectedTransactions(transactions.map((t) => t.transactionID)); } }} accessibilityLabel={CONST.ROLE.CHECKBOX} diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 6120374dafbe..7795d4bd3e19 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -1,15 +1,13 @@ import React, {useCallback, useContext, useMemo, useState} from 'react'; -import type {ReportActionListItemType, ReportListItemType, TaskListItemType, TransactionListItemType} from '@components/SelectionList/types'; import {getReportsFromSelectedTransactions} from '@libs/SearchUIUtils'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import type {SearchContext as SearchContextOld, SelectedTransactions, TMoneyRequestReportContext} from './types'; - -type SearchContext = TMoneyRequestReportContext & SearchContextOld; +import type {SearchContext} from './types'; const defaultSearchContext: SearchContext = { currentSearchHash: -1, shouldTurnOffSelectionMode: false, selectedTransactions: {}, + selectedTransactionsID: [], selectedReports: [], setCurrentSearchHash: () => {}, setSelectedTransactions: () => {}, @@ -23,12 +21,6 @@ const defaultSearchContext: SearchContext = { isExportMode: false, setExportMode: () => {}, isOnSearch: false, - /** **************** */ - selectedTransactionsID: [], - setSelectedTransactionsID: () => {}, - toggleTransaction: () => {}, - removeTransaction: () => {}, - isTransactionSelected: () => false, }; const Context = React.createContext(defaultSearchContext); @@ -36,13 +28,14 @@ const Context = React.createContext(defaultSearchContext); function SearchContextProvider({children}: ChildrenProps) { const [shouldShowExportModeOption, setShouldShowExportModeOption] = useState(false); const [isExportMode, setExportMode] = useState(false); - const [selectedTransactionsID, setSelectedTransactionsID] = useState([]); - + const [shouldShowStatusBarLoading, setShouldShowStatusBarLoading] = useState(false); + const [lastSearchType, setLastSearchType] = useState(undefined); const [searchContextData, setSearchContextData] = useState< - Pick + Pick >({ currentSearchHash: defaultSearchContext.currentSearchHash, selectedTransactions: defaultSearchContext.selectedTransactions, + selectedTransactionsID: defaultSearchContext.selectedTransactionsID, shouldTurnOffSelectionMode: false, selectedReports: defaultSearchContext.selectedReports, isOnSearch: false, @@ -55,25 +48,18 @@ function SearchContextProvider({children}: ChildrenProps) { })); }, []); - const toggleTransaction = useCallback((transactionID: string) => { - setSelectedTransactionsID((prev) => { - if (prev.includes(transactionID)) { - return prev.filter((t) => t !== transactionID); + const setSelectedTransactions: SearchContext['setSelectedTransactions'] = useCallback( + (selectedTransactions, data = []) => { + if (selectedTransactions instanceof Array) { + if (!selectedTransactions.length && !searchContextData.selectedTransactionsID.length) { + return; + } + return setSearchContextData((prevState) => ({ + ...prevState, + selectedTransactionsID: selectedTransactions, + })); } - return [...prev, transactionID]; - }); - }, []); - const removeTransaction = useCallback((transactionID?: string) => { - setSelectedTransactionsID((prev) => { - return prev.filter((t) => t !== transactionID); - }); - }, []); - - const isTransactionSelected = useCallback((transactionID: string) => selectedTransactionsID.includes(transactionID), [selectedTransactionsID]); - - const setSelectedTransactions = useCallback( - (selectedTransactions: SelectedTransactions, data: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[] | TaskListItemType[]) => { // When selecting transactions, we also need to manage the reports to which these transactions belong. This is done to ensure proper exporting to CSV. const selectedReports = getReportsFromSelectedTransactions(data, selectedTransactions); @@ -84,7 +70,7 @@ function SearchContextProvider({children}: ChildrenProps) { selectedReports, })); }, - [], + [searchContextData.selectedTransactionsID.length], ); const clearSelectedTransactions = useCallback( @@ -104,9 +90,6 @@ function SearchContextProvider({children}: ChildrenProps) { [searchContextData.currentSearchHash], ); - const [shouldShowStatusBarLoading, setShouldShowStatusBarLoading] = useState(false); - const [lastSearchType, setLastSearchType] = useState(undefined); - const searchContext = useMemo( () => ({ ...searchContextData, @@ -121,12 +104,6 @@ function SearchContextProvider({children}: ChildrenProps) { setShouldShowExportModeOption, isExportMode, setExportMode, - /** **************** */ - selectedTransactionsID, - setSelectedTransactionsID, - toggleTransaction, - isTransactionSelected, - removeTransaction, }), [ searchContextData, @@ -139,12 +116,6 @@ function SearchContextProvider({children}: ChildrenProps) { setShouldShowExportModeOption, isExportMode, setExportMode, - /** **************** */ - selectedTransactionsID, - setSelectedTransactionsID, - toggleTransaction, - isTransactionSelected, - removeTransaction, ], ); diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 22cff5ceae83..969ecd7c0afe 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -74,9 +74,13 @@ type TableColumnSize = ValueOf; type SearchContext = { currentSearchHash: number; selectedTransactions: SelectedTransactions; + selectedTransactionsID: string[]; selectedReports: SelectedReports[]; setCurrentSearchHash: (hash: number) => void; - setSelectedTransactions: (selectedTransactions: SelectedTransactions, data: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[] | TaskListItemType[]) => void; + setSelectedTransactions: { + (selectedTransactions: string[], data?: []): void; + (selectedTransactions: SelectedTransactions, data: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[] | TaskListItemType[]): void; + }; clearSelectedTransactions: (hash?: number, shouldTurnOffSelectionMode?: boolean) => void; shouldTurnOffSelectionMode: boolean; shouldShowStatusBarLoading: boolean; @@ -90,14 +94,6 @@ type SearchContext = { isOnSearch: boolean; }; -type TMoneyRequestReportContext = { - selectedTransactionsID: string[]; - setSelectedTransactionsID: (transactionsID: string[]) => void; - toggleTransaction: (transactionID: string) => void; - removeTransaction: (transactionID?: string) => void; - isTransactionSelected: (transactionID: string) => boolean; -}; - type ASTNode = { operator: ValueOf; left: ValueOf | ASTNode; @@ -181,7 +177,6 @@ export type { SearchQueryString, SortOrder, SearchContext, - TMoneyRequestReportContext, ASTNode, QueryFilter, QueryFilters, diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index 9c6880be5b4b..19864d3fe613 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -42,7 +42,7 @@ function useSelectedTransactionsActions({ session?: Session; onExportFailed?: () => void; }) { - const {selectedTransactionsID, setSelectedTransactionsID} = useSearchContext(); + const {selectedTransactionsID, setSelectedTransactions} = useSearchContext(); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false}); const selectedTransactions = useMemo( () => @@ -81,12 +81,12 @@ function useSelectedTransactionsActions({ })); transactionsWithActions.forEach(({transactionID, action}) => action && deleteMoneyRequest(transactionID, action)); - setSelectedTransactionsID([]); + setSelectedTransactions([]); if (allTransactionsLength - transactionsWithActions.length <= 1) { turnOffMobileSelectionMode(); } setIsDeleteModalVisible(false); - }, [allTransactionsLength, reportActions, selectedTransactionsID, setSelectedTransactionsID]); + }, [allTransactionsLength, reportActions, selectedTransactionsID, setSelectedTransactions]); const showDeleteModal = useCallback(() => { setIsDeleteModalVisible(true); @@ -150,7 +150,7 @@ function useSelectedTransactionsActions({ } unholdRequest(transactionID, action?.childReportID); }); - setSelectedTransactionsID([]); + setSelectedTransactions([]); }, }); } @@ -166,7 +166,7 @@ function useSelectedTransactionsActions({ exportReportToCSV({reportID: report.reportID, transactionIDList: selectedTransactionsID}, () => { onExportFailed?.(); }); - setSelectedTransactionsID([]); + setSelectedTransactions([]); }, }); @@ -213,7 +213,7 @@ function useSelectedTransactionsActions({ }); } return options; - }, [selectedTransactionsID, report, selectedTransactions, translate, reportActions, setSelectedTransactionsID, onExportFailed, iouType, session?.accountID, showDeleteModal]); + }, [selectedTransactionsID, report, selectedTransactions, translate, reportActions, setSelectedTransactions, onExportFailed, iouType, session?.accountID, showDeleteModal]); return { options: computedOptions, diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index b06c28a3b1c6..d90776c67801 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -174,7 +174,14 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const [parentReportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.parentReportID}`, {canBeMissing: true}); const {reportActions} = usePaginatedReportActions(report.reportID); - const {currentSearchHash, removeTransaction} = useSearchContext(); + const {currentSearchHash, setSelectedTransactions, selectedTransactionsID} = useSearchContext(); + + const removeTransaction = useCallback( + (transactionID?: string) => { + setSelectedTransactions(selectedTransactionsID.filter((t) => t !== transactionID)); + }, + [setSelectedTransactions, selectedTransactionsID], + ); // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to apply the correct modal type for the decision modal // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth diff --git a/src/pages/Search/SearchHoldReasonPage.tsx b/src/pages/Search/SearchHoldReasonPage.tsx index f04c10ec4bb4..e6fefd95f57c 100644 --- a/src/pages/Search/SearchHoldReasonPage.tsx +++ b/src/pages/Search/SearchHoldReasonPage.tsx @@ -25,9 +25,9 @@ function SearchHoldReasonPage({route}: Props) { const onSubmit = useCallback( (values: FormOnyxValues) => { if (isOnSearchHoldReason) { - const {selectedTransactionsID, setSelectedTransactionsID} = contextValue; + const {selectedTransactionsID, setSelectedTransactions} = contextValue; selectedTransactionsID.forEach((transactionID) => putOnHold(transactionID, values.comment, route.params.reportID)); - setSelectedTransactionsID([]); + setSelectedTransactions([]); } else { const {currentSearchHash, selectedTransactions, clearSelectedTransactions} = contextValue; holdMoneyRequestOnSearch(currentSearchHash, Object.keys(selectedTransactions), values.comment); diff --git a/src/pages/iou/request/step/IOURequestEditReport.tsx b/src/pages/iou/request/step/IOURequestEditReport.tsx index 97aa5019bd84..3653b67eba2e 100644 --- a/src/pages/iou/request/step/IOURequestEditReport.tsx +++ b/src/pages/iou/request/step/IOURequestEditReport.tsx @@ -20,7 +20,7 @@ type IOURequestEditReportProps = WithWritableReportOrNotFoundProps Date: Thu, 29 May 2025 15:32:10 +0200 Subject: [PATCH 07/11] Clean up code a little more --- src/components/Search/SearchContext.tsx | 18 +++++++++-- src/libs/SearchUIUtils.ts | 39 +---------------------- src/libs/actions/IOU.ts | 9 ++++++ src/pages/Search/SearchHoldReasonPage.tsx | 31 +++++++----------- 4 files changed, 37 insertions(+), 60 deletions(-) diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 7795d4bd3e19..d72bcef69426 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -1,5 +1,7 @@ import React, {useCallback, useContext, useMemo, useState} from 'react'; -import {getReportsFromSelectedTransactions} from '@libs/SearchUIUtils'; +import {isMoneyRequestReport} from '@libs/ReportUtils'; +import {isReportListItemType, isTransactionListItemType} from '@libs/SearchUIUtils'; +import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type {SearchContext} from './types'; @@ -61,7 +63,19 @@ function SearchContextProvider({children}: ChildrenProps) { } // When selecting transactions, we also need to manage the reports to which these transactions belong. This is done to ensure proper exporting to CSV. - const selectedReports = getReportsFromSelectedTransactions(data, selectedTransactions); + let selectedReports: SearchContext['selectedReports'] = []; + + if (data.length && data.every(isReportListItemType)) { + selectedReports = data + .filter((item) => isMoneyRequestReport(item) && item.transactions.every(({keyForList}) => selectedTransactions[keyForList]?.isSelected)) + .map(({reportID, action = CONST.SEARCH.ACTION_TYPES.VIEW, total = CONST.DEFAULT_NUMBER_ID, policyID}) => ({reportID, action, total, policyID})); + } + + if (data.length && data.every(isTransactionListItemType)) { + selectedReports = data + .filter(({keyForList}) => !!keyForList && selectedTransactions[keyForList]?.isSelected) + .map(({reportID, action, amount: total, policyID}) => ({reportID, action, total, policyID})); + } setSearchContextData((prevState) => ({ ...prevState, diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index f089044ded5e..b294cb79715b 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -3,7 +3,7 @@ import Onyx from 'react-native-onyx'; import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {MenuItemWithLink} from '@components/MenuItemList'; -import type {SearchColumnType, SearchQueryJSON, SearchStatus, SelectedTransactions, SortOrder} from '@components/Search/types'; +import type {SearchColumnType, SearchQueryJSON, SearchStatus, SortOrder} from '@components/Search/types'; import ChatListItem from '@components/SelectionList/ChatListItem'; import ReportListItem from '@components/SelectionList/Search/ReportListItem'; import TaskListItem from '@components/SelectionList/Search/TaskListItem'; @@ -636,42 +636,6 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx return Object.values(reportIDToTransactions); } -function getReportsFromSelectedTransactions( - data: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[] | TaskListItemType[], - selectedTransactions: SelectedTransactions, -) { - if (data.length === 0) { - return []; - } - - if (isReportListItemType(data[0]) || isMoneyRequestReport(data[0])) { - return data - .filter( - (item): item is ReportListItemType => - isReportListItemType(item) && isMoneyRequestReport(item) && item.transactions?.every((transaction) => selectedTransactions[transaction.keyForList]?.isSelected), - ) - .map((item) => ({ - reportID: item.reportID, - action: item.action ?? CONST.SEARCH.ACTION_TYPES.VIEW, - total: item.total ?? CONST.DEFAULT_NUMBER_ID, - policyID: item.policyID, - })); - } - - if (isTransactionListItemType(data[0])) { - return data - .filter((transaction) => transaction.keyForList != null && selectedTransactions[transaction.keyForList]?.isSelected) - .map((transaction) => ({ - reportID: transaction.reportID, - action: 'action' in transaction ? transaction.action ?? CONST.SEARCH.ACTION_TYPES.VIEW : CONST.SEARCH.ACTION_TYPES.VIEW, - total: 'amount' in transaction ? transaction.amount ?? CONST.DEFAULT_NUMBER_ID : CONST.DEFAULT_NUMBER_ID, - policyID: transaction.policyID, - })); - } - - return []; -} - /** * Returns the appropriate list item component based on the type and status of the search data. */ @@ -1015,6 +979,5 @@ export { shouldShowEmptyState, compareValues, isSearchDataLoaded, - getReportsFromSelectedTransactions, }; export type {SavedSearchMenuItem, SearchTypeMenuItem}; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 9e16ca95775e..50be05aac91d 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -79,6 +79,7 @@ import { } from '@libs/PolicyUtils'; import { getAllReportActions, + getIOUActionForReportID, getIOUReportIDFromReportActionPreview, getLastVisibleAction, getLastVisibleMessage, @@ -10659,6 +10660,13 @@ function putOnHold(transactionID: string, comment: string, reportID: string, sea Navigation.setNavigationActionToMicrotaskQueue(() => notifyNewAction(currentReportID, userAccountID)); } +function putTransactionsOnHold(transactionsID: string[], comment: string, reportID: string) { + transactionsID.forEach((transactionID) => { + const {childReportID} = getIOUActionForReportID(reportID, transactionID) ?? {}; + return childReportID && putOnHold(transactionID, comment, childReportID); + }); +} + /** * Remove expense from HOLD */ @@ -11289,6 +11297,7 @@ export { payInvoice, payMoneyRequest, putOnHold, + putTransactionsOnHold, replaceReceipt, requestMoney, resetSplitShares, diff --git a/src/pages/Search/SearchHoldReasonPage.tsx b/src/pages/Search/SearchHoldReasonPage.tsx index 806252731718..c012ad9e3ac4 100644 --- a/src/pages/Search/SearchHoldReasonPage.tsx +++ b/src/pages/Search/SearchHoldReasonPage.tsx @@ -6,40 +6,31 @@ import {clearErrorFields, clearErrors} from '@libs/actions/FormActions'; import {holdMoneyRequestOnSearch} from '@libs/actions/Search'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import {getIOUActionForReportID} from '@libs/ReportActionsUtils'; import {getFieldRequiredErrors} from '@libs/ValidationUtils'; import type {SearchReportParamList} from '@navigation/types'; import HoldReasonFormView from '@pages/iou/HoldReasonFormView'; -import {putOnHold} from '@userActions/IOU'; +import {putTransactionsOnHold} from '@userActions/IOU'; import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm'; -type Props = PlatformStackScreenProps; - -function SearchHoldReasonPage({route}: Props) { +function SearchHoldReasonPage({route}: PlatformStackScreenProps>) { const {translate} = useLocalize(); - const {backTo = ''} = route.params ?? {}; - const isOnSearchHoldReason = route.name !== SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP; - const contextValue = useSearchContext(); + const {backTo = '', reportID} = route.params; + const context = useSearchContext(); const onSubmit = useCallback( - (values: FormOnyxValues) => { - if (isOnSearchHoldReason) { - const {selectedTransactionsID, setSelectedTransactions} = contextValue; - selectedTransactionsID.forEach((transactionID) => { - const {childReportID} = getIOUActionForReportID(route.params.reportID, transactionID) ?? {}; - return childReportID && putOnHold(transactionID, values.comment, childReportID); - }); - setSelectedTransactions([]); + ({comment}: FormOnyxValues) => { + if (route.name === SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS) { + putTransactionsOnHold(context.selectedTransactionsID, comment, reportID); + context.setSelectedTransactions([]); } else { - const {currentSearchHash, selectedTransactions, clearSelectedTransactions} = contextValue; - holdMoneyRequestOnSearch(currentSearchHash, Object.keys(selectedTransactions), values.comment); - clearSelectedTransactions(); + holdMoneyRequestOnSearch(context.currentSearchHash, Object.keys(context.selectedTransactions), comment); + context.clearSelectedTransactions(); } Navigation.goBack(); }, - [contextValue, route.params?.reportID, isOnSearchHoldReason], + [route.name, context, reportID], ); const validate = useCallback( From b13151057c914b5e01925e783e9b6b8064bffaa7 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Fri, 30 May 2025 11:01:21 +0200 Subject: [PATCH 08/11] Make clearSelectedTransactions work for IDs --- src/components/MoneyReportHeader.tsx | 6 +++--- .../MoneyRequestReportActionsList.tsx | 6 +++--- .../MoneyRequestReportTransactionList.tsx | 8 ++++---- src/components/Search/SearchContext.tsx | 13 ++++++++++--- src/components/Search/types.ts | 7 +++++-- src/hooks/useSelectedTransactionsActions.ts | 12 ++++++------ src/pages/Search/SearchHoldReasonPage.tsx | 9 +++++---- src/pages/iou/request/step/IOURequestEditReport.tsx | 4 ++-- 8 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 8f9e1b0d715a..5d403979c65f 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -261,7 +261,7 @@ function MoneyReportHeader({ const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); - const {selectedTransactionsID, setSelectedTransactions} = useSearchContext(); + const {selectedTransactionsID, clearSelectedTransactions} = useSearchContext(); const { options: selectedTransactionsOptions, @@ -866,7 +866,7 @@ function MoneyReportHeader({ if (!transactionThreadReportID) { return; } - setSelectedTransactions([]); + clearSelectedTransactions(true); // We don't need to run the effect on change of setSelectedTransactionsID since it can cause the infinite loop. // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps @@ -894,7 +894,7 @@ function MoneyReportHeader({ { - setSelectedTransactions([]); + clearSelectedTransactions(true); turnOffMobileSelectionMode(); }} /> diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx index 73d33d304d6b..2e81cbf846de 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx @@ -145,7 +145,7 @@ function MoneyRequestReportActionsList({ const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false}); const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); - const {selectedTransactionsID, setSelectedTransactions} = useSearchContext(); + const {selectedTransactionsID, setSelectedTransactions, clearSelectedTransactions} = useSearchContext(); const {selectionMode} = useMobileSelectionMode(); const { @@ -545,7 +545,7 @@ function MoneyRequestReportActionsList({ isIndeterminate={selectedTransactionsID.length > 0 && selectedTransactionsID.length !== transactions.length} onPress={() => { if (selectedTransactionsID.length !== 0) { - setSelectedTransactions([]); + clearSelectedTransactions(true); } else { setSelectedTransactions(transactions.map((t) => t.transactionID)); } @@ -555,7 +555,7 @@ function MoneyRequestReportActionsList({ style={[styles.userSelectNone, styles.alignItemsCenter]} onPress={() => { if (selectedTransactionsID.length === transactions.length) { - setSelectedTransactions([]); + clearSelectedTransactions(true); } else { setSelectedTransactions(transactions.map((t) => t.transactionID)); } diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index 0899db6c206a..819df56cd680 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -132,7 +132,7 @@ function MoneyRequestReportTransactionList({ const {bind} = useHover(); const {isMouseDownOnInput, setMouseUp} = useMouseContext(); - const {selectedTransactionsID, setSelectedTransactions} = useSearchContext(); + const {selectedTransactionsID, setSelectedTransactions, clearSelectedTransactions} = useSearchContext(); const {selectionMode} = useMobileSelectionMode(); const toggleTransaction = useCallback( @@ -149,9 +149,9 @@ function MoneyRequestReportTransactionList({ if (navigationRef?.getRootState()?.routes.at(-1)?.name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR) { return; } - setSelectedTransactions([]); + clearSelectedTransactions(true); }; - }, [setSelectedTransactions]), + }, [clearSelectedTransactions]), ); const handleMouseLeave = (e: React.MouseEvent) => { @@ -212,7 +212,7 @@ function MoneyRequestReportTransactionList({ { if (selectedTransactionsID.length !== 0) { - setSelectedTransactions([]); + clearSelectedTransactions(true); } else { setSelectedTransactions(transactions.map((t) => t.transactionID)); } diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index d72bcef69426..26dbe41d8e4c 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -87,8 +87,15 @@ function SearchContextProvider({children}: ChildrenProps) { [searchContextData.selectedTransactionsID.length], ); - const clearSelectedTransactions = useCallback( - (searchHash?: number, shouldTurnOffSelectionMode = false) => { + const clearSelectedTransactions: SearchContext['clearSelectedTransactions'] = useCallback( + (searchHashOrClearIDsFlag, shouldTurnOffSelectionMode = false) => { + if (typeof searchHashOrClearIDsFlag === 'boolean' && searchHashOrClearIDsFlag) { + setSelectedTransactions([]); + return; + } + + const searchHash = searchHashOrClearIDsFlag === false ? undefined : searchHashOrClearIDsFlag; + if (searchHash === searchContextData.currentSearchHash) { return; } @@ -101,7 +108,7 @@ function SearchContextProvider({children}: ChildrenProps) { setShouldShowExportModeOption(false); setExportMode(false); }, - [searchContextData.currentSearchHash], + [searchContextData.currentSearchHash, setSelectedTransactions], ); const searchContext = useMemo( diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 969ecd7c0afe..1be58e9e2f9d 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -78,10 +78,13 @@ type SearchContext = { selectedReports: SelectedReports[]; setCurrentSearchHash: (hash: number) => void; setSelectedTransactions: { - (selectedTransactions: string[], data?: []): void; + (selectedTransactions: string[], unused?: undefined): void; (selectedTransactions: SelectedTransactions, data: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[] | TaskListItemType[]): void; }; - clearSelectedTransactions: (hash?: number, shouldTurnOffSelectionMode?: boolean) => void; + clearSelectedTransactions: { + (hash?: number, shouldTurnOffSelectionMode?: boolean): void; + (clearIDs: boolean, unused?: undefined): void; + }; shouldTurnOffSelectionMode: boolean; shouldShowStatusBarLoading: boolean; setShouldShowStatusBarLoading: (shouldShow: boolean) => void; diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index 19864d3fe613..4652253fe302 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -42,7 +42,7 @@ function useSelectedTransactionsActions({ session?: Session; onExportFailed?: () => void; }) { - const {selectedTransactionsID, setSelectedTransactions} = useSearchContext(); + const {selectedTransactionsID, clearSelectedTransactions} = useSearchContext(); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false}); const selectedTransactions = useMemo( () => @@ -81,12 +81,12 @@ function useSelectedTransactionsActions({ })); transactionsWithActions.forEach(({transactionID, action}) => action && deleteMoneyRequest(transactionID, action)); - setSelectedTransactions([]); + clearSelectedTransactions(true); if (allTransactionsLength - transactionsWithActions.length <= 1) { turnOffMobileSelectionMode(); } setIsDeleteModalVisible(false); - }, [allTransactionsLength, reportActions, selectedTransactionsID, setSelectedTransactions]); + }, [allTransactionsLength, reportActions, selectedTransactionsID, clearSelectedTransactions]); const showDeleteModal = useCallback(() => { setIsDeleteModalVisible(true); @@ -150,7 +150,7 @@ function useSelectedTransactionsActions({ } unholdRequest(transactionID, action?.childReportID); }); - setSelectedTransactions([]); + clearSelectedTransactions(true); }, }); } @@ -166,7 +166,7 @@ function useSelectedTransactionsActions({ exportReportToCSV({reportID: report.reportID, transactionIDList: selectedTransactionsID}, () => { onExportFailed?.(); }); - setSelectedTransactions([]); + clearSelectedTransactions(true); }, }); @@ -213,7 +213,7 @@ function useSelectedTransactionsActions({ }); } return options; - }, [selectedTransactionsID, report, selectedTransactions, translate, reportActions, setSelectedTransactions, onExportFailed, iouType, session?.accountID, showDeleteModal]); + }, [selectedTransactionsID, report, selectedTransactions, translate, reportActions, clearSelectedTransactions, onExportFailed, iouType, session?.accountID, showDeleteModal]); return { options: computedOptions, diff --git a/src/pages/Search/SearchHoldReasonPage.tsx b/src/pages/Search/SearchHoldReasonPage.tsx index c012ad9e3ac4..ba1e3d9bf695 100644 --- a/src/pages/Search/SearchHoldReasonPage.tsx +++ b/src/pages/Search/SearchHoldReasonPage.tsx @@ -18,19 +18,20 @@ function SearchHoldReasonPage({route}: PlatformStackScreenProps) => { - if (route.name === SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS) { + if (isIOUHold) { putTransactionsOnHold(context.selectedTransactionsID, comment, reportID); - context.setSelectedTransactions([]); } else { holdMoneyRequestOnSearch(context.currentSearchHash, Object.keys(context.selectedTransactions), comment); - context.clearSelectedTransactions(); } + + context.clearSelectedTransactions(isIOUHold); Navigation.goBack(); }, - [route.name, context, reportID], + [isIOUHold, context, reportID], ); const validate = useCallback( diff --git a/src/pages/iou/request/step/IOURequestEditReport.tsx b/src/pages/iou/request/step/IOURequestEditReport.tsx index 3653b67eba2e..11510bfc31b6 100644 --- a/src/pages/iou/request/step/IOURequestEditReport.tsx +++ b/src/pages/iou/request/step/IOURequestEditReport.tsx @@ -20,7 +20,7 @@ type IOURequestEditReportProps = WithWritableReportOrNotFoundProps Date: Fri, 30 May 2025 11:49:55 +0200 Subject: [PATCH 09/11] Add docs & clean up code a little more --- src/components/MoneyReportHeader.tsx | 12 ++--- .../MoneyRequestReportActionsList.tsx | 18 +++---- .../MoneyRequestReportTransactionList.tsx | 14 ++--- src/components/Search/SearchContext.tsx | 52 +++++++++---------- src/components/Search/types.ts | 18 ++++--- src/hooks/useSelectedTransactionsActions.ts | 22 ++++---- src/pages/ReportDetailsPage.tsx | 6 +-- src/pages/Search/SearchHoldReasonPage.tsx | 7 +-- ...SearchMoneyRequestReportHoldReasonPage.tsx | 0 .../iou/request/step/IOURequestEditReport.tsx | 6 +-- tests/ui/ReportListItemHeaderTest.tsx | 1 - 11 files changed, 80 insertions(+), 76 deletions(-) delete mode 100644 src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 5d403979c65f..ce76fc3f8b36 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -261,7 +261,7 @@ function MoneyReportHeader({ const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false); - const {selectedTransactionsID, clearSelectedTransactions} = useSearchContext(); + const {selectedTransactionIDs, clearSelectedTransactions} = useSearchContext(); const { options: selectedTransactionsOptions, @@ -867,7 +867,7 @@ function MoneyReportHeader({ return; } clearSelectedTransactions(true); - // We don't need to run the effect on change of setSelectedTransactionsID since it can cause the infinite loop. + // We don't need to run the effect on change of clearSelectedTransactions since it can cause the infinite loop. // eslint-disable-next-line react-compiler/react-compiler // eslint-disable-next-line react-hooks/exhaustive-deps }, [transactionThreadReportID]); @@ -967,7 +967,7 @@ function MoneyReportHeader({ null} options={selectedTransactionsOptions} - customText={translate('workspace.common.selected', {count: selectedTransactionsID.length})} + customText={translate('workspace.common.selected', {count: selectedTransactionIDs.length})} isSplitButton={false} shouldAlwaysShowDropdownMenu /> @@ -989,7 +989,7 @@ function MoneyReportHeader({ null} options={selectedTransactionsOptions} - customText={translate('workspace.common.selected', {count: selectedTransactionsID.length})} + customText={translate('workspace.common.selected', {count: selectedTransactionIDs.length})} isSplitButton={false} shouldAlwaysShowDropdownMenu wrapperStyle={styles.w100} @@ -1085,11 +1085,11 @@ function MoneyReportHeader({ shouldEnableNewFocusManagement /> null} options={selectedTransactionsOptions} - customText={translate('workspace.common.selected', {count: selectedTransactionsID.length})} + customText={translate('workspace.common.selected', {count: selectedTransactionIDs.length})} isSplitButton={false} shouldAlwaysShowDropdownMenu wrapperStyle={[styles.w100, styles.ph5]} @@ -541,10 +541,10 @@ function MoneyRequestReportActionsList({ 0 && selectedTransactionsID.length !== transactions.length} + isChecked={selectedTransactionIDs.length === transactions.length} + isIndeterminate={selectedTransactionIDs.length > 0 && selectedTransactionIDs.length !== transactions.length} onPress={() => { - if (selectedTransactionsID.length !== 0) { + if (selectedTransactionIDs.length !== 0) { clearSelectedTransactions(true); } else { setSelectedTransactions(transactions.map((t) => t.transactionID)); @@ -554,7 +554,7 @@ function MoneyRequestReportActionsList({ { - if (selectedTransactionsID.length === transactions.length) { + if (selectedTransactionIDs.length === transactions.length) { clearSelectedTransactions(true); } else { setSelectedTransactions(transactions.map((t) => t.transactionID)); @@ -562,18 +562,18 @@ function MoneyRequestReportActionsList({ }} accessibilityLabel={translate('workspace.people.selectAll')} role="button" - accessibilityState={{checked: selectedTransactionsID.length === transactions.length}} + accessibilityState={{checked: selectedTransactionIDs.length === transactions.length}} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} > {translate('workspace.people.selectAll')} - setSelectedTransactions(selectedTransactionsID.includes(transactionID) ? selectedTransactionsID.filter((t) => t !== transactionID) : [...selectedTransactionsID, transactionID]), - [setSelectedTransactions, selectedTransactionsID], + setSelectedTransactions(selectedTransactionIDs.includes(transactionID) ? selectedTransactionIDs.filter((t) => t !== transactionID) : [...selectedTransactionIDs, transactionID]), + [setSelectedTransactions, selectedTransactionIDs], ); - const isTransactionSelected = useCallback((transactionID: string) => selectedTransactionsID.includes(transactionID), [selectedTransactionsID]); + const isTransactionSelected = useCallback((transactionID: string) => selectedTransactionIDs.includes(transactionID), [selectedTransactionIDs]); useFocusEffect( useCallback(() => { @@ -211,15 +211,15 @@ function MoneyRequestReportTransactionList({ { - if (selectedTransactionsID.length !== 0) { + if (selectedTransactionIDs.length !== 0) { clearSelectedTransactions(true); } else { setSelectedTransactions(transactions.map((t) => t.transactionID)); } }} accessibilityLabel={CONST.ROLE.CHECKBOX} - isIndeterminate={selectedTransactionsID.length > 0 && selectedTransactionsID.length !== transactions.length} - isChecked={selectedTransactionsID.length === transactions.length} + isIndeterminate={selectedTransactionIDs.length > 0 && selectedTransactionIDs.length !== transactions.length} + isChecked={selectedTransactionIDs.length === transactions.length} /> {isMediumScreenWidth && {translate('workspace.people.selectAll')}} diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 26dbe41d8e4c..b0328b21577d 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -3,26 +3,30 @@ import {isMoneyRequestReport} from '@libs/ReportUtils'; import {isReportListItemType, isTransactionListItemType} from '@libs/SearchUIUtils'; import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import type {SearchContext} from './types'; +import type {SearchContext, SearchContextData} from './types'; -const defaultSearchContext: SearchContext = { +const defaultSearchContextData: SearchContextData = { currentSearchHash: -1, - shouldTurnOffSelectionMode: false, selectedTransactions: {}, - selectedTransactionsID: [], + selectedTransactionIDs: [], selectedReports: [], + isOnSearch: false, + shouldTurnOffSelectionMode: false, +}; + +const defaultSearchContext: SearchContext = { + ...defaultSearchContextData, + lastSearchType: undefined, + isExportMode: false, + shouldShowStatusBarLoading: false, + shouldShowExportModeOption: false, + setLastSearchType: () => {}, setCurrentSearchHash: () => {}, setSelectedTransactions: () => {}, clearSelectedTransactions: () => {}, - shouldShowStatusBarLoading: false, setShouldShowStatusBarLoading: () => {}, - lastSearchType: undefined, - setLastSearchType: () => {}, - shouldShowExportModeOption: false, setShouldShowExportModeOption: () => {}, - isExportMode: false, setExportMode: () => {}, - isOnSearch: false, }; const Context = React.createContext(defaultSearchContext); @@ -32,16 +36,7 @@ function SearchContextProvider({children}: ChildrenProps) { const [isExportMode, setExportMode] = useState(false); const [shouldShowStatusBarLoading, setShouldShowStatusBarLoading] = useState(false); const [lastSearchType, setLastSearchType] = useState(undefined); - const [searchContextData, setSearchContextData] = useState< - Pick - >({ - currentSearchHash: defaultSearchContext.currentSearchHash, - selectedTransactions: defaultSearchContext.selectedTransactions, - selectedTransactionsID: defaultSearchContext.selectedTransactionsID, - shouldTurnOffSelectionMode: false, - selectedReports: defaultSearchContext.selectedReports, - isOnSearch: false, - }); + const [searchContextData, setSearchContextData] = useState(defaultSearchContextData); const setCurrentSearchHash = useCallback((searchHash: number) => { setSearchContextData((prevState) => ({ @@ -53,12 +48,12 @@ function SearchContextProvider({children}: ChildrenProps) { const setSelectedTransactions: SearchContext['setSelectedTransactions'] = useCallback( (selectedTransactions, data = []) => { if (selectedTransactions instanceof Array) { - if (!selectedTransactions.length && !searchContextData.selectedTransactionsID.length) { + if (!selectedTransactions.length && !searchContextData.selectedTransactionIDs.length) { return; } return setSearchContextData((prevState) => ({ ...prevState, - selectedTransactionsID: selectedTransactions, + selectedTransactionIDs: selectedTransactions, })); } @@ -84,19 +79,17 @@ function SearchContextProvider({children}: ChildrenProps) { selectedReports, })); }, - [searchContextData.selectedTransactionsID.length], + [searchContextData.selectedTransactionIDs.length], ); const clearSelectedTransactions: SearchContext['clearSelectedTransactions'] = useCallback( (searchHashOrClearIDsFlag, shouldTurnOffSelectionMode = false) => { - if (typeof searchHashOrClearIDsFlag === 'boolean' && searchHashOrClearIDsFlag) { + if (typeof searchHashOrClearIDsFlag === 'boolean') { setSelectedTransactions([]); return; } - const searchHash = searchHashOrClearIDsFlag === false ? undefined : searchHashOrClearIDsFlag; - - if (searchHash === searchContextData.currentSearchHash) { + if (searchHashOrClearIDsFlag === searchContextData.currentSearchHash) { return; } setSearchContextData((prevState) => ({ @@ -143,6 +136,11 @@ function SearchContextProvider({children}: ChildrenProps) { return {children}; } +/** + * Note: `selectedTransactionIDs` and `selectedTransactions` are two separate properties. + * Setting or clearing one of them does not influence the other. + * IDs should be used if transaction details are not required. + */ function useSearchContext() { return useContext(Context); } diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 1be58e9e2f9d..5214fd251fe6 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -71,21 +71,27 @@ type SearchStatus = type SearchGroupBy = ValueOf; type TableColumnSize = ValueOf; -type SearchContext = { +type SearchContextData = { currentSearchHash: number; selectedTransactions: SelectedTransactions; - selectedTransactionsID: string[]; + selectedTransactionIDs: string[]; selectedReports: SelectedReports[]; + isOnSearch: boolean; + shouldTurnOffSelectionMode: boolean; +}; + +type SearchContext = SearchContextData & { setCurrentSearchHash: (hash: number) => void; + /** If you want to set `selectedTransactionIDs`, pass an array as the first argument, object/record otherwise */ setSelectedTransactions: { - (selectedTransactions: string[], unused?: undefined): void; + (selectedTransactionIDs: string[], unused?: undefined): void; (selectedTransactions: SelectedTransactions, data: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[] | TaskListItemType[]): void; }; + /** If you want to clear `selectedTransactionIDs`, pass `true` as the first argument */ clearSelectedTransactions: { (hash?: number, shouldTurnOffSelectionMode?: boolean): void; - (clearIDs: boolean, unused?: undefined): void; + (clearIDs: true, unused?: undefined): void; }; - shouldTurnOffSelectionMode: boolean; shouldShowStatusBarLoading: boolean; setShouldShowStatusBarLoading: (shouldShow: boolean) => void; setLastSearchType: (type: string | undefined) => void; @@ -94,7 +100,6 @@ type SearchContext = { setShouldShowExportModeOption: (shouldShow: boolean) => void; isExportMode: boolean; setExportMode: (on: boolean) => void; - isOnSearch: boolean; }; type ASTNode = { @@ -180,6 +185,7 @@ export type { SearchQueryString, SortOrder, SearchContext, + SearchContextData, ASTNode, QueryFilter, QueryFilters, diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index 4652253fe302..7a742185c5e1 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -42,18 +42,18 @@ function useSelectedTransactionsActions({ session?: Session; onExportFailed?: () => void; }) { - const {selectedTransactionsID, clearSelectedTransactions} = useSearchContext(); + const {selectedTransactionIDs, clearSelectedTransactions} = useSearchContext(); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: false}); const selectedTransactions = useMemo( () => - selectedTransactionsID.reduce((acc, transactionID) => { + selectedTransactionIDs.reduce((acc, transactionID) => { const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; if (transaction) { acc.push(transaction); } return acc; }, [] as Transaction[]), - [allTransactions, selectedTransactionsID], + [allTransactions, selectedTransactionIDs], ); const {translate} = useLocalize(); @@ -72,7 +72,7 @@ function useSelectedTransactionsActions({ const handleDeleteTransactions = useCallback(() => { const iouActions = reportActions.filter((action) => isMoneyRequestAction(action)); - const transactionsWithActions = selectedTransactionsID.map((transactionID) => ({ + const transactionsWithActions = selectedTransactionIDs.map((transactionID) => ({ transactionID, action: iouActions.find((action) => { const IOUTransactionID = (getOriginalMessage(action) as OriginalMessageIOU)?.IOUTransactionID; @@ -86,7 +86,7 @@ function useSelectedTransactionsActions({ turnOffMobileSelectionMode(); } setIsDeleteModalVisible(false); - }, [allTransactionsLength, reportActions, selectedTransactionsID, clearSelectedTransactions]); + }, [allTransactionsLength, reportActions, selectedTransactionIDs, clearSelectedTransactions]); const showDeleteModal = useCallback(() => { setIsDeleteModalVisible(true); @@ -97,7 +97,7 @@ function useSelectedTransactionsActions({ }, []); const computedOptions = useMemo(() => { - if (!selectedTransactionsID.length) { + if (!selectedTransactionIDs.length) { return []; } const options = []; @@ -143,7 +143,7 @@ function useSelectedTransactionsActions({ icon: Expensicons.Stopwatch, value: UNHOLD, onSelected: () => { - selectedTransactionsID.forEach((transactionID) => { + selectedTransactionIDs.forEach((transactionID) => { const action = getIOUActionForTransactionID(reportActions, transactionID); if (!action?.childReportID) { return; @@ -163,7 +163,7 @@ function useSelectedTransactionsActions({ if (!report) { return; } - exportReportToCSV({reportID: report.reportID, transactionIDList: selectedTransactionsID}, () => { + exportReportToCSV({reportID: report.reportID, transactionIDList: selectedTransactionIDs}, () => { onExportFailed?.(); }); clearSelectedTransactions(true); @@ -183,7 +183,7 @@ function useSelectedTransactionsActions({ const canUserPerformWriteAction = canUserPerformWriteActionReportUtils(report); if (canSelectedExpensesBeMoved && canUserPerformWriteAction) { options.push({ - text: translate('iou.moveExpenses', {count: selectedTransactionsID.length}), + text: translate('iou.moveExpenses', {count: selectedTransactionIDs.length}), icon: Expensicons.DocumentMerge, value: MOVE, onSelected: () => { @@ -193,7 +193,7 @@ function useSelectedTransactionsActions({ }); } - const canAllSelectedTransactionsBeRemoved = selectedTransactionsID.every((transactionID) => { + const canAllSelectedTransactionsBeRemoved = selectedTransactionIDs.every((transactionID) => { const canRemoveTransaction = canDeleteCardTransactionByLiabilityType(transactionID); const action = getIOUActionForTransactionID(reportActions, transactionID); const isActionDeleted = isDeletedAction(action); @@ -213,7 +213,7 @@ function useSelectedTransactionsActions({ }); } return options; - }, [selectedTransactionsID, report, selectedTransactions, translate, reportActions, clearSelectedTransactions, onExportFailed, iouType, session?.accountID, showDeleteModal]); + }, [selectedTransactionIDs, report, selectedTransactions, translate, reportActions, clearSelectedTransactions, onExportFailed, iouType, session?.accountID, showDeleteModal]); return { options: computedOptions, diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 5a6ab5f9d172..48cab5a672f7 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -157,13 +157,13 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const {reportActions} = usePaginatedReportActions(report.reportID); - const {setSelectedTransactions, selectedTransactionsID} = useSearchContext(); + const {setSelectedTransactions, selectedTransactionIDs} = useSearchContext(); const removeTransaction = useCallback( (transactionID?: string) => { - setSelectedTransactions(selectedTransactionsID.filter((t) => t !== transactionID)); + setSelectedTransactions(selectedTransactionIDs.filter((t) => t !== transactionID)); }, - [setSelectedTransactions, selectedTransactionsID], + [setSelectedTransactions, selectedTransactionIDs], ); const transactionThreadReportID = useMemo(() => getOneTransactionThreadReportID(report.reportID, reportActions ?? [], isOffline), [report.reportID, reportActions, isOffline]); diff --git a/src/pages/Search/SearchHoldReasonPage.tsx b/src/pages/Search/SearchHoldReasonPage.tsx index ba1e3d9bf695..40faa2523333 100644 --- a/src/pages/Search/SearchHoldReasonPage.tsx +++ b/src/pages/Search/SearchHoldReasonPage.tsx @@ -16,19 +16,20 @@ import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm'; function SearchHoldReasonPage({route}: PlatformStackScreenProps>) { const {translate} = useLocalize(); - const {backTo = '', reportID} = route.params; + const {backTo = '', reportID} = route.params ?? {}; const context = useSearchContext(); const isIOUHold = route.name === SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS; const onSubmit = useCallback( ({comment}: FormOnyxValues) => { if (isIOUHold) { - putTransactionsOnHold(context.selectedTransactionsID, comment, reportID); + putTransactionsOnHold(context.selectedTransactionIDs, comment, reportID); + context.clearSelectedTransactions(true); } else { holdMoneyRequestOnSearch(context.currentSearchHash, Object.keys(context.selectedTransactions), comment); + context.clearSelectedTransactions(); } - context.clearSelectedTransactions(isIOUHold); Navigation.goBack(); }, [isIOUHold, context, reportID], diff --git a/src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx b/src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/pages/iou/request/step/IOURequestEditReport.tsx b/src/pages/iou/request/step/IOURequestEditReport.tsx index 11510bfc31b6..2d4b0c240212 100644 --- a/src/pages/iou/request/step/IOURequestEditReport.tsx +++ b/src/pages/iou/request/step/IOURequestEditReport.tsx @@ -20,16 +20,16 @@ type IOURequestEditReportProps = WithWritableReportOrNotFoundProps { - if (selectedTransactionsID.length === 0) { + if (selectedTransactionIDs.length === 0) { return; } if (item.value !== transactionReport?.reportID) { - changeTransactionsReport(selectedTransactionsID, item.value); + changeTransactionsReport(selectedTransactionIDs, item.value); clearSelectedTransactions(true); } Navigation.dismissModalWithReport({reportID: item.value}); diff --git a/tests/ui/ReportListItemHeaderTest.tsx b/tests/ui/ReportListItemHeaderTest.tsx index a2998d60d6ff..5762ada2ec9d 100644 --- a/tests/ui/ReportListItemHeaderTest.tsx +++ b/tests/ui/ReportListItemHeaderTest.tsx @@ -25,7 +25,6 @@ const mockSearchContext = { selectedReports: {}, setSelectedReports: jest.fn(), selectedTransactionIDs: [], - setSelectedTransactionIDs: jest.fn(), clearSelectedItems: jest.fn(), }; From 93d543f11becbe627b163615466b69c33a82dc09 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Tue, 3 Jun 2025 11:23:25 +0200 Subject: [PATCH 10/11] Address Dylan's comments --- .../MoneyRequestReportTransactionList.tsx | 16 +++++++++++++--- src/libs/actions/IOU.ts | 5 ++++- src/pages/Search/SearchHoldReasonPage.tsx | 5 ++--- tests/ui/ReportListItemHeaderTest.tsx | 13 +++++++++++-- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index a9a3403ff384..7b338fb1d6ca 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -141,8 +141,15 @@ function MoneyRequestReportTransactionList({ const {selectionMode} = useMobileSelectionMode(); const toggleTransaction = useCallback( - (transactionID: string) => - setSelectedTransactions(selectedTransactionIDs.includes(transactionID) ? selectedTransactionIDs.filter((t) => t !== transactionID) : [...selectedTransactionIDs, transactionID]), + (transactionID: string) => { + let newSelectedTransactionIDs = selectedTransactionIDs; + if (selectedTransactionIDs.includes(transactionID)) { + newSelectedTransactionIDs = selectedTransactionIDs.filter((t) => t !== transactionID); + } else { + newSelectedTransactionIDs = [...selectedTransactionIDs, transactionID]; + } + setSelectedTransactions(newSelectedTransactionIDs); + }, [setSelectedTransactions, selectedTransactionIDs], ); @@ -156,7 +163,10 @@ function MoneyRequestReportTransactionList({ } clearSelectedTransactions(true); }; - }, [clearSelectedTransactions]), + // We don't need to run the effect on change of clearSelectedTransactions on every focus. + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []), ); const handleMouseLeave = (e: React.MouseEvent) => { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index d6b3a32851d2..451cfd13477d 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -10837,7 +10837,10 @@ function putOnHold(transactionID: string, comment: string, initialReportID: stri function putTransactionsOnHold(transactionsID: string[], comment: string, reportID: string) { transactionsID.forEach((transactionID) => { const {childReportID} = getIOUActionForReportID(reportID, transactionID) ?? {}; - return childReportID && putOnHold(transactionID, comment, childReportID); + if (!childReportID) { + return; + } + putOnHold(transactionID, comment, childReportID); }); } diff --git a/src/pages/Search/SearchHoldReasonPage.tsx b/src/pages/Search/SearchHoldReasonPage.tsx index 40faa2523333..8182f089e3f9 100644 --- a/src/pages/Search/SearchHoldReasonPage.tsx +++ b/src/pages/Search/SearchHoldReasonPage.tsx @@ -18,11 +18,10 @@ function SearchHoldReasonPage({route}: PlatformStackScreenProps) => { - if (isIOUHold) { + if (route.name === SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS) { putTransactionsOnHold(context.selectedTransactionIDs, comment, reportID); context.clearSelectedTransactions(true); } else { @@ -32,7 +31,7 @@ function SearchHoldReasonPage({route}: PlatformStackScreenProps = { From e1a2296c03ab5653353341182d3e1408931de24c Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Wed, 4 Jun 2025 11:50:20 +0200 Subject: [PATCH 11/11] Add default values for Transaction's in SearchContext --- src/components/Search/SearchContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 2b38dc7590a7..234aca31ffad 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -69,7 +69,7 @@ function SearchContextProvider({children}: ChildrenProps) { if (data.length && data.every(isTransactionListItemType)) { selectedReports = data .filter(({keyForList}) => !!keyForList && selectedTransactions[keyForList]?.isSelected) - .map(({reportID, action, amount: total, policyID}) => ({reportID, action, total, policyID})); + .map(({reportID, action = CONST.SEARCH.ACTION_TYPES.VIEW, amount: total = CONST.DEFAULT_NUMBER_ID, policyID}) => ({reportID, action, total, policyID})); } setSearchContextData((prevState) => ({