diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 9c47dd0da547..aada3e312353 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -80,6 +80,7 @@ import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar'; import type {ActionHandledType} from './ProcessMoneyReportHoldMenu'; import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu'; import ExportWithDropdownMenu from './ReportActionItem/ExportWithDropdownMenu'; +import {useSearchContext} from './Search/SearchContext'; import SettlementButton from './SettlementButton'; type MoneyReportHeaderProps = { @@ -163,6 +164,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const isPayAtEndExpense = isPayAtEndExpenseTransactionUtils(transaction); const isArchivedReport = isArchivedReportWithID(moneyRequestReport?.reportID); const [archiveReason] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport?.reportID}`, {selector: getArchiveReason}); + const {currentSearchHash, isAllStatus} = useSearchContext(); + const hash = isAllStatus ? undefined : currentSearchHash; const getCanIOUBePaid = useCallback( (onlyShowPayElsewhere = false) => canIOUBePaidAction(moneyRequestReport, chatReport, policy, transaction ? [transaction] : undefined, onlyShowPayElsewhere), @@ -236,10 +239,10 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea } else if (isInvoiceReport(moneyRequestReport)) { payInvoice(type, chatReport, moneyRequestReport, payAsBusiness); } else { - payMoneyRequest(type, chatReport, moneyRequestReport, true); + payMoneyRequest(type, chatReport, moneyRequestReport, true, hash); } }, - [chatReport, isAnyTransactionOnHold, isDelegateAccessRestricted, moneyRequestReport], + [chatReport, hash, isAnyTransactionOnHold, isDelegateAccessRestricted, moneyRequestReport], ); const confirmApproval = () => { @@ -249,7 +252,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea } else if (isAnyTransactionOnHold) { setIsHoldMenuVisible(true); } else { - approveMoneyRequest(moneyRequestReport, true); + approveMoneyRequest(moneyRequestReport, true, hash); } }; @@ -418,7 +421,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea success={isWaitingForSubmissionFromCurrentUser} text={translate('common.submit')} style={[styles.mnw120, styles.pv2, styles.pr0]} - onPress={() => submitReport(moneyRequestReport)} + onPress={() => submitReport(moneyRequestReport, hash)} isDisabled={shouldDisableSubmitButton} /> @@ -479,7 +482,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea success={isWaitingForSubmissionFromCurrentUser} text={translate('common.submit')} style={[styles.flex1, styles.pr0]} - onPress={() => submitReport(moneyRequestReport)} + onPress={() => submitReport(moneyRequestReport, hash)} isDisabled={shouldDisableSubmitButton} /> )} diff --git a/src/components/OpacityView.tsx b/src/components/OpacityView.tsx index 6c7aa26d05ba..f13d703e77e8 100644 --- a/src/components/OpacityView.tsx +++ b/src/components/OpacityView.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React, {useState} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; import type {AnimatedStyle} from 'react-native-reanimated'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import useExitingAnimation from '@hooks/useExitingAnimation'; +import useThemeStyles from '@hooks/useThemeStyles'; import shouldRenderOffscreen from '@libs/shouldRenderOffscreen'; import variables from '@styles/variables'; @@ -32,6 +34,8 @@ type OpacityViewProps = { /** Whether the view needs to be rendered offscreen (for Android only) */ needsOffscreenAlphaCompositing?: boolean; + + shouldAnimateOnRemove?: boolean; }; function OpacityView({ @@ -41,8 +45,13 @@ function OpacityView({ style = [], dimmingValue = variables.hoverDimValue, needsOffscreenAlphaCompositing = false, + shouldAnimateOnRemove, }: OpacityViewProps) { const opacity = useSharedValue(1); + const styles = useThemeStyles(); + const [height, setHeight] = useState(0); + const Exiting = useExitingAnimation(height); + const opacityStyle = useAnimatedStyle(() => ({ opacity: opacity.get(), })); @@ -53,8 +62,12 @@ function OpacityView({ return ( { + setHeight(e.nativeEvent.layout.height); + }} > {children} diff --git a/src/components/Pressable/PressableWithFeedback.tsx b/src/components/Pressable/PressableWithFeedback.tsx index 10e6ac7bbca6..8a79a9337cf4 100644 --- a/src/components/Pressable/PressableWithFeedback.tsx +++ b/src/components/Pressable/PressableWithFeedback.tsx @@ -37,6 +37,8 @@ type PressableWithFeedbackProps = PressableProps & { /** The color of the underlay that will show through when the Pressable is active. */ underlayColor?: Color; + + shouldAnimateOnRemove?: boolean; }; function PressableWithFeedback( @@ -47,6 +49,7 @@ function PressableWithFeedback( pressDimmingValue = variables.pressDimValue, hoverDimmingValue = variables.hoverDimValue, dimAnimationDuration, + shouldAnimateOnRemove, ...rest }: PressableWithFeedbackProps, ref: PressableRef, @@ -61,6 +64,7 @@ function PressableWithFeedback( dimAnimationDuration={dimAnimationDuration} style={wrapperStyle} needsOffscreenAlphaCompositing={needsOffscreenAlphaCompositing} + shouldAnimateOnRemove={shouldAnimateOnRemove} > ; @@ -60,6 +61,8 @@ function ProcessMoneyReportHoldMenu({ }: ProcessMoneyReportHoldMenuProps) { const {translate} = useLocalize(); const isApprove = requestType === CONST.IOU.REPORT_ACTION_TYPE.APPROVE; + const {currentSearchHash, isAllStatus} = useSearchContext(); + const hash = isAllStatus ? undefined : currentSearchHash; // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to apply the correct modal type // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); @@ -69,15 +72,15 @@ function ProcessMoneyReportHoldMenu({ if (startAnimation) { startAnimation(); } - IOU.approveMoneyRequest(moneyRequestReport, full); - if (!full && isLinkedTransactionHeld(Navigation.getTopmostReportActionId() ?? '-1', moneyRequestReport?.reportID ?? '')) { - Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(moneyRequestReport?.reportID ?? '')); + approveMoneyRequest(moneyRequestReport, full, hash); + if (!full && isLinkedTransactionHeld(Navigation.getTopmostReportActionId(), moneyRequestReport?.reportID)) { + Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(moneyRequestReport?.reportID)); } } else if (chatReport && paymentType) { if (startAnimation) { startAnimation(); } - IOU.payMoneyRequest(paymentType, chatReport, moneyRequestReport, full); + payMoneyRequest(paymentType, chatReport, moneyRequestReport, full, hash); } onClose(); }; diff --git a/src/components/Search/SearchContext.tsx b/src/components/Search/SearchContext.tsx index 73091abf045b..f537f562afb6 100644 --- a/src/components/Search/SearchContext.tsx +++ b/src/components/Search/SearchContext.tsx @@ -8,6 +8,7 @@ import type {SearchContext, SelectedTransactions} from './types'; const defaultSearchContext = { currentSearchHash: -1, + isAllStatus: false, shouldTurnOffSelectionMode: false, selectedTransactions: {}, selectedReports: [], @@ -34,17 +35,21 @@ function getReportsFromSelectedTransactions(data: TransactionListItemType[] | Re } function SearchContextProvider({children}: ChildrenProps) { - const [searchContextData, setSearchContextData] = useState>({ + const [searchContextData, setSearchContextData] = useState< + Pick + >({ currentSearchHash: defaultSearchContext.currentSearchHash, selectedTransactions: defaultSearchContext.selectedTransactions, shouldTurnOffSelectionMode: false, selectedReports: defaultSearchContext.selectedReports, + isAllStatus: defaultSearchContext.isAllStatus, }); - const setCurrentSearchHash = useCallback((searchHash: number) => { + const setCurrentSearchHash = useCallback((searchHash: number, status: string | string[]) => { setSearchContextData((prevState) => ({ ...prevState, currentSearchHash: searchHash, + isAllStatus: status === CONST.SEARCH.STATUS.EXPENSE.ALL, })); }, []); diff --git a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx index a3b814650a23..5d0fccecc6df 100644 --- a/src/components/Search/SearchPageHeader/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader/SearchPageHeader.tsx @@ -60,7 +60,7 @@ function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, hideS // 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 const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); - const {selectedTransactions, clearSelectedTransactions, selectedReports} = useSearchContext(); + const {selectedTransactions, clearSelectedTransactions, selectedReports, isAllStatus} = useSearchContext(); const [selectionMode] = useOnyx(ONYXKEYS.MOBILE_SELECTION_MODE); const personalDetails = usePersonalDetails(); const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); @@ -141,7 +141,7 @@ function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, hideS const reportIDList = !selectedReports.length ? Object.values(selectedTransactions).map((transaction) => transaction.reportID) : selectedReports?.filter((report) => !!report).map((report) => report.reportID) ?? []; - approveMoneyRequestOnSearch(hash, reportIDList, transactionIDList); + approveMoneyRequestOnSearch(hash, reportIDList, isAllStatus, transactionIDList); }, }); } @@ -205,7 +205,7 @@ function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, hideS })) ) as PaymentData[]; - payMoneyRequestOnSearch(hash, paymentData, transactionIDList); + payMoneyRequestOnSearch(hash, paymentData, isAllStatus, transactionIDList); }, }); } @@ -319,6 +319,7 @@ function SearchPageHeader({queryJSON, searchName, searchRouterListVisible, hideS selectedReports, translate, hash, + isAllStatus, lastPaymentMethods, status, queryJSON, diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index ea87d595b965..bb8e0204e455 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -172,8 +172,8 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo useEffect(() => { clearSelectedTransactions(hash); - setCurrentSearchHash(hash); - }, [hash, clearSelectedTransactions, setCurrentSearchHash]); + setCurrentSearchHash(hash, queryJSON.status); + }, [hash, clearSelectedTransactions, setCurrentSearchHash, queryJSON.status]); const searchResults = currentSearchResults?.data ? currentSearchResults : lastNonEmptySearchResults; const isSearchResultsEmpty = !searchResults?.data || isSearchResultsEmptyUtil(searchResults); @@ -549,6 +549,7 @@ function Search({queryJSON, onSearchListScroll, isSearchScreenFocused, contentCo shouldKeepFocusedItemAtTopOfViewableArea={type === CONST.SEARCH.DATA_TYPES.CHAT} isScreenFocused={isSearchScreenFocused} initialNumToRender={shouldUseNarrowLayout ? 5 : undefined} + shouldAnimateOnRemove /> ); } diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index 3dc408cc27c3..8cb4295e5c0c 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -61,9 +61,10 @@ type SearchStatus = ExpenseSearchStatus | InvoiceSearchStatus | TripSearchStatus type SearchContext = { currentSearchHash: number; + isAllStatus: boolean; selectedTransactions: SelectedTransactions; selectedReports: SelectedReports[]; - setCurrentSearchHash: (hash: number) => void; + setCurrentSearchHash: (hash: number, status: string | string[]) => void; setSelectedTransactions: (selectedTransactions: SelectedTransactions, data: TransactionListItemType[] | ReportListItemType[] | ReportActionListItemType[]) => void; clearSelectedTransactions: (hash?: number, shouldTurnOffSelectionMode?: boolean) => void; shouldTurnOffSelectionMode: boolean; diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx index 99b6f8d5c53e..912c77ac46f0 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.tsx @@ -38,6 +38,7 @@ function BaseListItem({ onFocus = () => {}, hoverStyle, onLongPressRow, + shouldAnimateOnRemove, }: BaseListItemProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -110,6 +111,7 @@ function BaseListItem({ onMouseLeave={handleMouseLeave} tabIndex={item.tabIndex} wrapperStyle={pressableWrapperStyle} + shouldAnimateOnRemove={shouldAnimateOnRemove} > ( listItemTitleStyles, initialNumToRender = 12, listItemTitleContainerStyles, + shouldAnimateOnRemove = false, isScreenFocused = false, shouldSubscribeToArrowKeyEvents = true, }: BaseSelectionListProps, @@ -576,6 +577,7 @@ function BaseSelectionList( shouldHighlightSelectedItem={shouldHighlightSelectedItem} singleExecution={singleExecution} titleContainerStyles={listItemTitleContainerStyles} + shouldAnimateOnRemove={shouldAnimateOnRemove} /> ); diff --git a/src/components/SelectionList/BaseSelectionListItemRenderer.tsx b/src/components/SelectionList/BaseSelectionListItemRenderer.tsx index 987a72e025c1..a7de36c18c38 100644 --- a/src/components/SelectionList/BaseSelectionListItemRenderer.tsx +++ b/src/components/SelectionList/BaseSelectionListItemRenderer.tsx @@ -15,6 +15,7 @@ type BaseSelectionListItemRendererProps = Omit['singleExecution']; titleStyles?: StyleProp; titleContainerStyles?: StyleProp; + shouldAnimateOnRemove?: boolean; }; function BaseSelectionListItemRenderer({ @@ -44,6 +45,7 @@ function BaseSelectionListItemRenderer({ titleStyles, singleExecution, titleContainerStyles, + shouldAnimateOnRemove, }: BaseSelectionListItemRendererProps) { const handleOnCheckboxPress = () => { if (isReportListItemType(item)) { @@ -94,6 +96,7 @@ function BaseSelectionListItemRenderer({ wrapperStyle={wrapperStyle} titleStyles={titleStyles} titleContainerStyles={titleContainerStyles} + shouldAnimateOnRemove={shouldAnimateOnRemove} /> {item.footerContent && item.footerContent} diff --git a/src/components/SelectionList/Search/ReportListItem.tsx b/src/components/SelectionList/Search/ReportListItem.tsx index 7a573412871b..b708a034273c 100644 --- a/src/components/SelectionList/Search/ReportListItem.tsx +++ b/src/components/SelectionList/Search/ReportListItem.tsx @@ -63,6 +63,7 @@ function ReportListItem({ onFocus, onLongPressRow, shouldSyncFocus, + shouldAnimateOnRemove, }: ReportListItemProps) { const reportItem = item as unknown as ReportListItemType; @@ -70,7 +71,7 @@ function ReportListItem({ const styles = useThemeStyles(); const {isLargeScreenWidth} = useResponsiveLayout(); const StyleUtils = useStyleUtils(); - const {currentSearchHash} = useSearchContext(); + const {currentSearchHash, isAllStatus} = useSearchContext(); const animatedHighlightStyle = useAnimatedHighlightStyle({ borderRadius: variables.componentBorderRadius, @@ -95,7 +96,7 @@ function ReportListItem({ ]; const handleOnButtonPress = () => { - handleActionButtonPress(currentSearchHash, reportItem, () => onSelectRow(item)); + handleActionButtonPress(currentSearchHash, reportItem, () => onSelectRow(item), isAllStatus); }; const openReportInRHP = (transactionItem: TransactionListItemType) => { @@ -132,6 +133,7 @@ function ReportListItem({ onLongPressRow={onLongPressRow} shouldSyncFocus={shouldSyncFocus} isLoading={reportItem.isActionLoading} + shouldAnimateOnRemove={shouldAnimateOnRemove} /> ); } @@ -155,6 +157,7 @@ function ReportListItem({ shouldSyncFocus={shouldSyncFocus} hoverStyle={item.isSelected && styles.activeComponentBG} pressableWrapperStyle={[styles.mh5, animatedHighlightStyle]} + shouldAnimateOnRemove={shouldAnimateOnRemove} > {!isLargeScreenWidth && ( diff --git a/src/components/SelectionList/Search/TransactionListItem.tsx b/src/components/SelectionList/Search/TransactionListItem.tsx index 2582f1fb23cc..c742068a8499 100644 --- a/src/components/SelectionList/Search/TransactionListItem.tsx +++ b/src/components/SelectionList/Search/TransactionListItem.tsx @@ -22,13 +22,14 @@ function TransactionListItem({ onLongPressRow, shouldSyncFocus, isLoading, + shouldAnimateOnRemove, }: TransactionListItemProps) { const transactionItem = item as unknown as TransactionListItemType; const styles = useThemeStyles(); const theme = useTheme(); const {isLargeScreenWidth} = useResponsiveLayout(); - const {currentSearchHash} = useSearchContext(); + const {currentSearchHash, isAllStatus} = useSearchContext(); const listItemPressableStyle = [ styles.selectionListPressableItemWrapper, @@ -71,12 +72,13 @@ function TransactionListItem({ shouldSyncFocus={shouldSyncFocus} hoverStyle={item.isSelected && styles.activeComponentBG} pressableWrapperStyle={[styles.mh5, animatedHighlightStyle]} + shouldAnimateOnRemove={shouldAnimateOnRemove} > { - handleActionButtonPress(currentSearchHash, transactionItem, () => onSelectRow(item)); + handleActionButtonPress(currentSearchHash, transactionItem, () => onSelectRow(item), isAllStatus); }} onCheckboxPress={() => onCheckboxPress?.(item)} isDisabled={!!isDisabled} diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 3eb63ae97242..1f82a86814c3 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -315,6 +315,8 @@ type ListItemProps = CommonListItemProps & { /** Styles applid for the title container of the list item */ titleContainerStyles?: StyleProp; + + shouldAnimateOnRemove?: boolean; }; type BaseListItemProps = CommonListItemProps & { @@ -331,6 +333,7 @@ type BaseListItemProps = CommonListItemProps & { hoverStyle?: StyleProp; /** Errors that this user may contain */ shouldDisplayRBR?: boolean; + shouldAnimateOnRemove?: boolean; }; type UserListItemProps = ListItemProps & { @@ -666,6 +669,9 @@ type BaseSelectionListProps = Partial & { /** Initial number of items to render */ initialNumToRender?: number; + /** Whether to have an animation when an item is removed from the list */ + shouldAnimateOnRemove?: boolean; + /** Whether the screen is focused or not. (useIsFocused state does not work in tab screens, e.g. SearchPageBottomTab) */ isScreenFocused?: boolean; } & TRightHandSideComponent; diff --git a/src/hooks/useExitingAnimation/index.ts b/src/hooks/useExitingAnimation/index.ts new file mode 100644 index 000000000000..b08d26eec894 --- /dev/null +++ b/src/hooks/useExitingAnimation/index.ts @@ -0,0 +1,40 @@ +import {useMemo} from 'react'; +import {Easing, Keyframe} from 'react-native-reanimated'; +import type {ReanimatedKeyframe} from 'react-native-reanimated/lib/typescript/layoutReanimation/animationBuilder/Keyframe'; +import type UseExitingAnimation from './type'; + +const easing = Easing.bezier(0.76, 0.0, 0.24, 1.0); + +/** + * Due to issues with react-native-reanimated Keyframes the easing type doesn't account for bezier functions + * and we also need to use internal .build() function to make the easing apply on each mount. + * + * This causes problems with both eslint & Typescript and is going to be fixed in react-native-reanimated 3.17 with these PRs merged: + * https://github.com/software-mansion/react-native-reanimated/pull/6960 + * https://github.com/software-mansion/react-native-reanimated/pull/6958 + * + * Once that's added we can apply our changes to files in BottomDockedModal/Backdrop/*.tsx and BottomDockedModal/Container/*.tsx + */ + +/* eslint-disable @typescript-eslint/no-unsafe-call */ +const useExitingAnimation: UseExitingAnimation = (height: number) => { + const Exiting = useMemo(() => { + const SlideOut = new Keyframe({ + from: { + height, + }, + to: { + height: '0', + // @ts-expect-error Types mismatch in reanimated, should to be fixed in 3.17 + easing, + }, + }); + + // @ts-expect-error Internal function used to fix easing issue, should to be fixed in 3.17 + return SlideOut.duration(300).build() as ReanimatedKeyframe; + }, [height]); + + return Exiting; +}; + +export default useExitingAnimation; diff --git a/src/hooks/useExitingAnimation/index.web.ts b/src/hooks/useExitingAnimation/index.web.ts new file mode 100644 index 000000000000..2c50d1585ec5 --- /dev/null +++ b/src/hooks/useExitingAnimation/index.web.ts @@ -0,0 +1,37 @@ +import {useMemo} from 'react'; +import {Easing, Keyframe} from 'react-native-reanimated'; +import type UseExitingAnimation from './type'; + +const easing = Easing.bezier(0.76, 0.0, 0.24, 1.0); + +/** + * Due to issues with react-native-reanimated Keyframes the easing type doesn't account for bezier functions + * and we also need to use internal .build() function to make the easing apply on each mount. + * + * This causes problems with both eslint & Typescript and is going to be fixed in react-native-reanimated 3.17 with these PRs merged: + * https://github.com/software-mansion/react-native-reanimated/pull/6960 + * https://github.com/software-mansion/react-native-reanimated/pull/6958 + * + * Once that's added we can apply our changes to files in BottomDockedModal/Backdrop/*.tsx and BottomDockedModal/Container/*.tsx + */ + +const useExitingAnimation: UseExitingAnimation = (height: number) => { + const Exiting = useMemo(() => { + const SlideOut = new Keyframe({ + from: { + height, + }, + to: { + height: '0', + // @ts-expect-error Types mismatch in reanimated, should to be fixed in 3.17 + easing, + }, + }); + + return SlideOut.duration(300); + }, [height]); + + return Exiting; +}; + +export default useExitingAnimation; diff --git a/src/hooks/useExitingAnimation/type.ts b/src/hooks/useExitingAnimation/type.ts new file mode 100644 index 000000000000..1f20cecd738d --- /dev/null +++ b/src/hooks/useExitingAnimation/type.ts @@ -0,0 +1,5 @@ +import type {ReanimatedKeyframe} from 'react-native-reanimated/lib/typescript/layoutReanimation/animationBuilder/Keyframe'; + +type UseExitingAnimation = (height: number) => ReanimatedKeyframe; + +export default UseExitingAnimation; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index f60a4bb2e668..afdf76d160ef 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1583,7 +1583,7 @@ function getDismissedViolationMessageText(originalMessage: ReportAction report.transactions.length); } /** diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 6f4596b0e597..73ab08bc6834 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4165,6 +4165,60 @@ function updateMoneyRequestDistanceRate( API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DISTANCE_RATE, paramsWithoutTaxUpdated, onyxData); } +function createSearchOnyxData(hash: number, reportID: string | undefined, iouTransactionID?: string) { + const transactionIDList = getReportTransactions(reportID).map((transaction) => transaction.transactionID); + const optimisticData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, + value: { + data: iouTransactionID + ? { + [`${ONYXKEYS.COLLECTION.TRANSACTION}${iouTransactionID}`]: {isActionLoading: true}, + } + : { + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: {isActionLoading: true}, + }, + }, + }, + ]; + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, + value: { + data: iouTransactionID + ? { + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: null, + } + : { + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: null, + ...(Object.fromEntries( + (transactionIDList ?? []).map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, null]), + ) as Partial), + }, + }, + }, + ]; + const failureData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, + value: { + data: iouTransactionID + ? {} + : { + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: { + errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), + isActionLoading: false, + }, + }, + }, + }, + ]; + return {optimisticData, successData, failureData}; +} + const getConvertTrackedExpenseInformation = ( transactionID: string | undefined, actionableWhisperReportActionID: string | undefined, @@ -6788,7 +6842,7 @@ function cleanUpMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repo * @param isSingleTransactionView - whether we are in the transaction thread report * @return the url to navigate back once the money request is deleted */ -function deleteMoneyRequest(transactionID: string | undefined, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false) { +function deleteMoneyRequest(transactionID: string | undefined, reportAction: OnyxTypes.ReportAction, isSingleTransactionView = false, hash?: number) { if (!transactionID) { return; } @@ -7099,6 +7153,13 @@ function deleteMoneyRequest(transactionID: string | undefined, reportAction: Ony }); } + if (hash) { + const searchOnyxData = createSearchOnyxData(hash, iouReport?.reportID, transactionID); + optimisticData.push(...searchOnyxData.optimisticData); + successData.push(...searchOnyxData.successData); + failureData.push(...searchOnyxData.failureData); + } + const parameters: DeleteMoneyRequestParams = { transactionID, reportActionID: reportAction.reportActionID, @@ -7757,6 +7818,7 @@ function getPayMoneyRequestParams( paymentMethodType: PaymentMethodType, full: boolean, payAsBusiness?: boolean, + hash?: number, ): PayMoneyRequestData { const isInvoiceReport = isInvoiceReportReportUtils(iouReport); const activePolicy = getPolicy(activePolicyID); @@ -8019,6 +8081,13 @@ function getPayMoneyRequestParams( optimisticHoldReportExpenseActionIDs = JSON.stringify(holdReportOnyxData.optimisticHoldReportExpenseActionIDs); } + if (hash) { + const searchOnyxData = createSearchOnyxData(hash, iouReport?.reportID); + optimisticData.push(...searchOnyxData.optimisticData); + successData.push(...searchOnyxData.successData); + failureData.push(...searchOnyxData.failureData); + } + return { params: { iouReportID: iouReport?.reportID, @@ -8250,7 +8319,7 @@ function getNextApproverAccountID(report: OnyxEntry, isUnappro return getAccountIDsByLogins([nextApproverEmail]).at(0); } -function approveMoneyRequest(expenseReport: OnyxEntry, full?: boolean) { +function approveMoneyRequest(expenseReport: OnyxEntry, full?: boolean, hash?: number) { if (!expenseReport) { return; } @@ -8270,9 +8339,10 @@ function approveMoneyRequest(expenseReport: OnyxEntry, full?: const approvalChain = getApprovalChain(getPolicy(expenseReport.policyID), expenseReport); - const predictedNextStatus = isLastApprover(approvalChain) ? CONST.REPORT.STATUS_NUM.APPROVED : CONST.REPORT.STATUS_NUM.SUBMITTED; - const predictedNextState = isLastApprover(approvalChain) ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED; - const managerID = isLastApprover(approvalChain) ? expenseReport.managerID : getNextApproverAccountID(expenseReport); + const predictedReportApproved = isLastApprover(approvalChain); + const predictedNextStatus = predictedReportApproved ? CONST.REPORT.STATUS_NUM.APPROVED : CONST.REPORT.STATUS_NUM.SUBMITTED; + const predictedNextState = predictedReportApproved ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED; + const managerID = predictedReportApproved ? expenseReport.managerID : getNextApproverAccountID(expenseReport); const optimisticNextStep = buildNextStep(expenseReport, predictedNextStatus); const chatReport = getReportOrDraftReport(expenseReport.chatReportID); @@ -8405,6 +8475,13 @@ function approveMoneyRequest(expenseReport: OnyxEntry, full?: optimisticHoldReportExpenseActionIDs = JSON.stringify(holdReportOnyxData.optimisticHoldReportExpenseActionIDs); } + if (hash && predictedReportApproved) { + const searchOnyxData = createSearchOnyxData(hash, expenseReport.reportID); + optimisticData.push(...searchOnyxData.optimisticData); + successData.push(...searchOnyxData.successData); + failureData.push(...searchOnyxData.failureData); + } + const parameters: ApproveMoneyRequestParams = { reportID: expenseReport.reportID, approvedReportActionID: optimisticApprovedReportAction.reportActionID, @@ -8417,7 +8494,7 @@ function approveMoneyRequest(expenseReport: OnyxEntry, full?: API.write(WRITE_COMMANDS.APPROVE_MONEY_REQUEST, parameters, {optimisticData, successData, failureData}); } -function unapproveExpenseReport(expenseReport: OnyxEntry) { +function unapproveExpenseReport(expenseReport: OnyxEntry, hash?: number) { if (isEmptyObject(expenseReport)) { return; } @@ -8522,6 +8599,13 @@ function unapproveExpenseReport(expenseReport: OnyxEntry) { }); } + if (hash) { + const searchOnyxData = createSearchOnyxData(hash, expenseReport.reportID); + optimisticData.push(...searchOnyxData.optimisticData); + successData.push(...searchOnyxData.successData); + failureData.push(...searchOnyxData.failureData); + } + const parameters: UnapproveExpenseReportParams = { reportID: expenseReport.reportID, reportActionID: optimisticUnapprovedReportAction.reportActionID, @@ -8530,7 +8614,7 @@ function unapproveExpenseReport(expenseReport: OnyxEntry) { API.write(WRITE_COMMANDS.UNAPPROVE_EXPENSE_REPORT, parameters, {optimisticData, successData, failureData}); } -function submitReport(expenseReport: OnyxTypes.Report) { +function submitReport(expenseReport: OnyxTypes.Report, hash?: number) { if (expenseReport.policyID && shouldRestrictUserBillableActions(expenseReport.policyID)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(expenseReport.policyID)); return; @@ -8654,6 +8738,13 @@ function submitReport(expenseReport: OnyxTypes.Report) { }); } + if (hash) { + const searchOnyxData = createSearchOnyxData(hash, expenseReport.reportID); + optimisticData.push(...searchOnyxData.optimisticData); + successData.push(...searchOnyxData.successData); + failureData.push(...searchOnyxData.failureData); + } + const parameters: SubmitReportParams = { reportID: expenseReport.reportID, managerAccountID: getSubmitToAccountID(policy, expenseReport) ?? expenseReport.managerID, @@ -8663,7 +8754,7 @@ function submitReport(expenseReport: OnyxTypes.Report) { API.write(WRITE_COMMANDS.SUBMIT_REPORT, parameters, {optimisticData, successData, failureData}); } -function cancelPayment(expenseReport: OnyxEntry, chatReport: OnyxTypes.Report, backTo?: Route) { +function cancelPayment(expenseReport: OnyxEntry, chatReport: OnyxTypes.Report, backTo?: Route, hash?: number) { if (isEmptyObject(expenseReport)) { return; } @@ -8796,6 +8887,13 @@ function cancelPayment(expenseReport: OnyxEntry, chatReport: O value: buildNextStep(expenseReport, CONST.REPORT.STATUS_NUM.REIMBURSED), }); + if (hash) { + const searchOnyxData = createSearchOnyxData(hash, expenseReport?.reportID); + optimisticData.push(...searchOnyxData.optimisticData); + successData.push(...searchOnyxData.successData); + failureData.push(...searchOnyxData.failureData); + } + API.write( WRITE_COMMANDS.CANCEL_PAYMENT, { @@ -8849,7 +8947,7 @@ function completePaymentOnboarding(paymentSelected: ValueOf, full = true) { +function payMoneyRequest(paymentType: PaymentMethodType, chatReport: OnyxTypes.Report, iouReport: OnyxEntry, full = true, hash?: number) { if (chatReport.policyID && shouldRestrictUserBillableActions(chatReport.policyID)) { Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(chatReport.policyID)); return; @@ -8859,7 +8957,7 @@ function payMoneyRequest(paymentType: PaymentMethodType, chatReport: OnyxTypes.R completePaymentOnboarding(paymentSelected); const recipient = {accountID: iouReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID}; - const {params, optimisticData, successData, failureData} = getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentType, full); + const {params, optimisticData, successData, failureData} = getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentType, full, undefined, hash); // For now, we need to call the PayMoneyRequestWithWallet API since PayMoneyRequest was not updated to work with // Expensify Wallets. diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index ef1ddb998f63..395d5b096834 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -48,7 +48,7 @@ Onyx.connect({ waitForCollectionCallback: true, }); -function handleActionButtonPress(hash: number, item: TransactionListItemType | ReportListItemType, goToItem: () => void) { +function handleActionButtonPress(hash: number, item: TransactionListItemType | ReportListItemType, goToItem: () => void, isAllStatus: boolean) { // The transactionIDList is needed to handle actions taken on `status:all` where transactions on single expense reports can be approved/paid. // We need the transactionID to display the loading indicator for that list item's action. const transactionID = isTransactionListItemType(item) ? [item.transactionID] : undefined; @@ -62,14 +62,14 @@ function handleActionButtonPress(hash: number, item: TransactionListItemType | R switch (item.action) { case CONST.SEARCH.ACTION_TYPES.PAY: - getPayActionCallback(hash, item, goToItem); + getPayActionCallback(hash, item, goToItem, isAllStatus); return; case CONST.SEARCH.ACTION_TYPES.APPROVE: - approveMoneyRequestOnSearch(hash, [item.reportID], transactionID); + approveMoneyRequestOnSearch(hash, [item.reportID], isAllStatus, transactionID); return; case CONST.SEARCH.ACTION_TYPES.SUBMIT: { const policy = (allSnapshots?.[`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`]?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${item.policyID}`] ?? {}) as SearchPolicy; - submitMoneyRequestOnSearch(hash, [item], [policy], transactionID); + submitMoneyRequestOnSearch(hash, [item], [policy], isAllStatus, transactionID); return; } default: @@ -91,7 +91,7 @@ function getLastPolicyPaymentMethod(policyID: string | undefined, lastPaymentMet return lastPolicyPaymentMethod; } -function getPayActionCallback(hash: number, item: TransactionListItemType | ReportListItemType, goToItem: () => void) { +function getPayActionCallback(hash: number, item: TransactionListItemType | ReportListItemType, goToItem: () => void, isAllStatus: boolean) { const lastPolicyPaymentMethod = getLastPolicyPaymentMethod(item.policyID, lastPaymentMethod); if (!lastPolicyPaymentMethod) { @@ -104,13 +104,13 @@ function getPayActionCallback(hash: number, item: TransactionListItemType | Repo const transactionID = isTransactionListItemType(item) ? [item.transactionID] : undefined; if (lastPolicyPaymentMethod === CONST.IOU.PAYMENT_TYPE.ELSEWHERE) { - payMoneyRequestOnSearch(hash, [{reportID: item.reportID, amount, paymentType: lastPolicyPaymentMethod}], transactionID); + payMoneyRequestOnSearch(hash, [{reportID: item.reportID, amount, paymentType: lastPolicyPaymentMethod}], isAllStatus, transactionID); return; } const hasVBBA = !!allSnapshots?.[`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`]?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${item.policyID}`]?.achAccount?.bankAccountID; if (hasVBBA) { - payMoneyRequestOnSearch(hash, [{reportID: item.reportID, amount, paymentType: lastPolicyPaymentMethod}], transactionID); + payMoneyRequestOnSearch(hash, [{reportID: item.reportID, amount, paymentType: lastPolicyPaymentMethod}], isAllStatus, transactionID); return; } @@ -281,7 +281,7 @@ function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], com API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList, comment}, {optimisticData, finallyData}); } -function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], policy: SearchPolicy[], transactionIDList?: string[]) { +function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], policy: SearchPolicy[], isAllStatus: boolean, transactionIDList?: string[]) { const createActionLoadingData = (isLoading: boolean): OnyxUpdate[] => [ { onyxMethod: Onyx.METHOD.MERGE, @@ -296,7 +296,23 @@ function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], po }, ]; const optimisticData: OnyxUpdate[] = createActionLoadingData(true); - const finallyData: OnyxUpdate[] = createActionLoadingData(false); + const successData: OnyxUpdate[] = isAllStatus + ? createActionLoadingData(false) + : [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, + value: { + data: { + ...(Object.fromEntries(reportList.map((item) => [`${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, null])) as Partial), + ...(Object.fromEntries( + (transactionIDList ?? []).map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, null]), + ) as Partial), + }, + }, + }, + ]; + const failureData: OnyxUpdate[] = createActionLoadingData(false); const report = (reportList.at(0) ?? {}) as SearchReport; const parameters: SubmitReportParams = { @@ -307,10 +323,10 @@ function submitMoneyRequestOnSearch(hash: number, reportList: SearchReport[], po // The SubmitReport command is not 1:1:1 yet, which means creating a separate SubmitMoneyRequestOnSearch command is not feasible until https://github.com/Expensify/Expensify/issues/451223 is done. // In the meantime, we'll call SubmitReport which works for a single expense only, so not bulk actions are possible. - API.write(WRITE_COMMANDS.SUBMIT_REPORT, parameters, {optimisticData, finallyData}); + API.write(WRITE_COMMANDS.SUBMIT_REPORT, parameters, {optimisticData, successData, failureData}); } -function approveMoneyRequestOnSearch(hash: number, reportIDList: string[], transactionIDList?: string[]) { +function approveMoneyRequestOnSearch(hash: number, reportIDList: string[], isAllStatus: boolean, transactionIDList?: string[]) { const createOnyxData = (update: Partial | Partial): OnyxUpdate[] => [ { onyxMethod: Onyx.METHOD.MERGE, @@ -323,13 +339,28 @@ function approveMoneyRequestOnSearch(hash: number, reportIDList: string[], trans }, ]; const optimisticData: OnyxUpdate[] = createOnyxData({isActionLoading: true}); - const failureData: OnyxUpdate[] = createOnyxData({errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}); - const finallyData: OnyxUpdate[] = createOnyxData({isActionLoading: false}); - - API.write(WRITE_COMMANDS.APPROVE_MONEY_REQUEST_ON_SEARCH, {hash, reportIDList}, {optimisticData, failureData, finallyData}); + const successData: OnyxUpdate[] = isAllStatus + ? createOnyxData({isActionLoading: false}) + : [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, + value: { + data: { + ...(Object.fromEntries(reportIDList.map((reportID) => [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, null])) as Partial), + ...(Object.fromEntries( + (transactionIDList ?? []).map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, null]), + ) as Partial), + }, + }, + }, + ]; + const failureData: OnyxUpdate[] = createOnyxData({errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), isActionLoading: false}); + + API.write(WRITE_COMMANDS.APPROVE_MONEY_REQUEST_ON_SEARCH, {hash, reportIDList}, {optimisticData, failureData, successData}); } -function payMoneyRequestOnSearch(hash: number, paymentData: PaymentData[], transactionIDList?: string[]) { +function payMoneyRequestOnSearch(hash: number, paymentData: PaymentData[], isAllStatus: boolean, transactionIDList?: string[]) { const createOnyxData = (update: Partial | Partial): OnyxUpdate[] => [ { onyxMethod: Onyx.METHOD.MERGE, @@ -343,14 +374,29 @@ function payMoneyRequestOnSearch(hash: number, paymentData: PaymentData[], trans ]; const optimisticData: OnyxUpdate[] = createOnyxData({isActionLoading: true}); - const failureData: OnyxUpdate[] = createOnyxData({errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')}); - const finallyData: OnyxUpdate[] = createOnyxData({isActionLoading: false}); + const successData: OnyxUpdate[] = isAllStatus + ? createOnyxData({isActionLoading: false}) + : [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, + value: { + data: { + ...(Object.fromEntries(paymentData.map((item) => [`${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, null])) as Partial), + ...(Object.fromEntries( + (transactionIDList ?? []).map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, null]), + ) as Partial), + }, + }, + }, + ]; + const failureData: OnyxUpdate[] = createOnyxData({errors: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage'), isActionLoading: false}); // eslint-disable-next-line rulesdir/no-api-side-effects-method API.makeRequestWithSideEffects( SIDE_EFFECT_REQUEST_COMMANDS.PAY_MONEY_REQUEST_ON_SEARCH, {hash, paymentData: JSON.stringify(paymentData)}, - {optimisticData, failureData, finallyData}, + {optimisticData, failureData, successData}, ).then((response) => { if (response?.jsonCode !== CONST.JSON_CODE.SUCCESS) { return; @@ -367,7 +413,18 @@ function unholdMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[]) { const {optimisticData, finallyData} = getOnyxLoadingData(hash); - API.write(WRITE_COMMANDS.DELETE_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList}, {optimisticData, finallyData}); + const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, + value: { + data: { + ...(Object.fromEntries(transactionIDList.map((transactionID) => [`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, null])) as Partial), + }, + }, + }, + ]; + API.write(WRITE_COMMANDS.DELETE_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList}, {optimisticData, successData, finallyData}); } type Params = Record; diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index f348445e3c91..6f96811b52d7 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -173,7 +173,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const [parentReportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.parentReportID || CONST.DEFAULT_NUMBER_ID}`); /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */ const {reportActions} = usePaginatedReportActions(report.reportID); - const {currentSearchHash} = useSearchContext(); + const {currentSearchHash, isAllStatus} = 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 @@ -348,8 +348,8 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } Navigation.dismissModal(); - unapproveExpenseReport(moneyRequestReport); - }, [isMoneyRequestExported, moneyRequestReport, isDelegateAccessRestricted]); + unapproveExpenseReport(moneyRequestReport, isAllStatus ? undefined : currentSearchHash); + }, [isDelegateAccessRestricted, isMoneyRequestExported, moneyRequestReport, isAllStatus, currentSearchHash]); const shouldShowLeaveButton = canLeaveChat(report, policy); const shouldShowGoToWorkspace = shouldShowPolicy(policy, false, session?.email) && !policy?.isJoinRequestPending; @@ -387,9 +387,9 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } - cancelPaymentAction(moneyRequestReport, chatReport, backTo); + cancelPaymentAction(moneyRequestReport, chatReport, backTo, isAllStatus ? undefined : currentSearchHash); setIsConfirmModalVisible(false); - }, [moneyRequestReport, chatReport, backTo]); + }, [moneyRequestReport, chatReport, backTo, isAllStatus, currentSearchHash]); const menuItems: ReportDetailsPageMenuItem[] = useMemo(() => { const items: ReportDetailsPageMenuItem[] = []; @@ -922,9 +922,9 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta if (isTrackExpense) { deleteTrackExpense(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); } else { - deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); + deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView, currentSearchHash); } - }, [caseID, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, report, requestParentReportAction]); + }, [caseID, currentSearchHash, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, report, requestParentReportAction]); // A flag to indicate whether the user chose to delete the transaction or not const isTransactionDeleted = useRef(false); diff --git a/tests/unit/Search/handleActionButtonPressTest.ts b/tests/unit/Search/handleActionButtonPressTest.ts index 69af0e83849a..28552ae03aab 100644 --- a/tests/unit/Search/handleActionButtonPressTest.ts +++ b/tests/unit/Search/handleActionButtonPressTest.ts @@ -205,13 +205,13 @@ describe('handleActionButtonPress', () => { const searchHash = 1; test('Should navigate to item when report has one transaction on hold', () => { const goToItem = jest.fn(() => {}); - handleActionButtonPress(searchHash, mockReportItemWithHold, goToItem); + handleActionButtonPress(searchHash, mockReportItemWithHold, goToItem, false); expect(goToItem).toHaveBeenCalledTimes(1); }); test('Should not navigate to item when the hold is removed', () => { const goToItem = jest.fn(() => {}); - handleActionButtonPress(searchHash, updatedMockReportItem, goToItem); + handleActionButtonPress(searchHash, updatedMockReportItem, goToItem, false); expect(goToItem).toHaveBeenCalledTimes(0); }); });