Repository: Expensify/App
Pull request #59433: Add selection to table on MoneyRequestReport page
Author: login: jnowakow
Created: 2025-04-01T10:32:15Z | Merged: 2025-04-07T17:34:30Z

<!-- If necessary, assign reviewers that know the area or changes well. Feel free to tag any additional reviewers you see fit. -->
@Kicu @mountiny 

### Explanation of Change
<!-- Explain what your change does and how it addresses the linked issue -->

Follow up to new MoneyRequestReport page. It adds selection to transactions table. The implementation lack support for long press selection on mobile devices, it will be added in next follow up in order to keep PRs small. 

### Fixed Issues
<!---
1. Please postfix `$` with a URL link to the GitHub issue this Pull Request is fixing. For example, `$ https://github.com/Expensify/App/issues/<issueID>`.
2. Please postfix  `PROPOSAL:` with a URL link to your GitHub comment, which contains the approved proposal (i.e. the proposal that was approved by Expensify).  For example, `PROPOSAL: https://github.com/Expensify/App/issues/<issueID>#issuecomment-1369752925`

Do NOT add the special GH keywords like `fixed` etc, we have our own process of managing the flow.
It MUST be an entire link to the github issue and your comment proposal ; otherwise, the linking and its automation will not work as expected.

Make sure this section looks similar to this (you can link multiple issues using the same formatting, just add a new line):

$ https://github.com/Expensify/App/issues/<issueID>
$ https://github.com/Expensify/App/issues/<issueID(comment)>

Do NOT only link the issue number like this: $ #<issueID>
--->
$ https://github.com/Expensify/App/issues/57508
$ https://github.com/Expensify/App/issues/59459
PROPOSAL: N/A


<!--- 
If you want to trigger adhoc build of hybrid app from specific Mobile-Expensify PR please link it like this:

MOBILE-EXPENSIFY: https://github.com/Expensify/Mobile-Expensify/pull/<PR-number>

--->

### Tests
<!---
Add a numbered list of manual tests you performed that validates your changes work on all platforms, and that there are no regressions present.
Add any additional test steps if test steps are unique to a particular platform.
Manual test steps should be written so that your reviewer can repeat and verify one or more expected outcomes in the development environment.

For example:
1. Click on the text input to bring it into focus
2. Upload an image via copy paste
3. Verify a modal appears displaying a preview of that image
--->

Selection doesn't work on small screen. It will be fixed in separate PR.

1. Go to new report view -> /search/r/report-id
2. Verify that transactions in table can be selected
3. Verify that when any transaction is selected there is `x selected` button with dropdown in header


- [x] Verify that no errors appear in the JS console

### Offline tests
<!---
Add any relevant steps that validate your changes work as expected in a variety of network states e.g. "offline", "spotty connection", "slow internet", etc. Manual test steps should be written so that your reviewer and QA testers can repeat and verify one or more expected outcomes. If you are unsure how the behavior should work ask for advice in the `#expensify-open-source` Slack channel.
--->

1. Go to new report view -> /search/r/report-id
2. Verify that transactions in table can be selected
3. Verify that when any transaction is selected there is `x selected` button with dropdown in header


### QA Steps
<!---
Add a numbered list of manual tests that can be performed by our QA engineers on the staging environment to validate that your changes work on all platforms, and that there are no regressions present.
Add any additional QA steps if test steps are unique to a particular platform.
Manual test steps should be written so that the QA engineer can repeat and verify one or more expected outcomes in the staging environment.

For example:
1. Click on the text input to bring it into focus
5. Upload an image via copy paste
6. Verify a modal appears displaying a preview of that image

It's acceptable to write "Same as tests" if the QA team is able to run the tests in the above "Tests" section.
--->

Selection doesn't work on small screen. It will be fixed in separate PR.

1. Go to new report view -> /search/r/report-id
2. Verify that transactions in table can be selected
3. Verify that when any transaction is selected there is `x selected` button with dropdown in header


- [x] Verify that no errors appear in the JS console

### PR Author Checklist
<!--
This is a checklist for PR authors. Please make sure to complete all tasks and check them off once you do, or else your PR will not be merged!
-->

- [x] I linked the correct issue in the `### Fixed Issues` section above
- [x] I wrote clear testing steps that cover the changes made in this PR
    - [x] I added steps for local testing in the `Tests` section
    - [x] I added steps for the expected offline behavior in the `Offline steps` section
    - [x] I added steps for Staging and/or Production testing in the `QA steps` section
    - [x] I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    - [x] I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    - [x] I tested this PR with a [High Traffic account](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#high-traffic-accounts) against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
- [x] I included screenshots or videos for tests on [all platforms](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#make-sure-you-can-test-on-all-platforms)
- [x] I ran the tests on **all platforms** & verified they passed on:
    - [x] Android: Native
    - [x] Android: mWeb Chrome
    - [x] iOS: Native
    - [x] iOS: mWeb Safari
    - [x] MacOS: Chrome / Safari
    - [x] MacOS: Desktop
- [x] I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
- [x] I followed proper code patterns (see [Reviewing the code](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md#reviewing-the-code))
    - [x] I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. `toggleReport` and not `onIconClick`)
    - [x] I verified that comments were added to code that is not self explanatory
    - [x] I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    - [x] I verified any copy / text shown in the product is localized by adding it to `src/languages/*` files and using the [translation method](https://github.com/Expensify/App/blob/4bd99402cebdf4d7394e0d1f260879ea238197eb/src/components/withLocalize.js#L60)
      - [x] If any non-english text was added/modified, I used [JaimeGPT](https://chatgpt.com/g/g-2dgOQl5VM-english-to-spanish-translator-aka-jaimegpt) to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    - [x] I verified all numbers, amounts, dates and phone numbers shown in the product are using the [localization methods](https://github.com/Expensify/App/blob/4bd99402cebdf4d7394e0d1f260879ea238197eb/src/components/withLocalize.js#L60-L68)
    - [x] I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    - [x] I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    - [x] I verified the JSDocs style guidelines (in [`STYLE.md`](https://github.com/Expensify/App/blob/main/contributingGuides/STYLE.md#jsdocs)) were followed
- [x] If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
- [x] I followed the guidelines as stated in the [Review Guidelines](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md)
- [x] I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like `Avatar`, I verified the components using `Avatar` are working as expected)
- [x] I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
- [x] I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
- [x] I verified that if a function's arguments changed that all usages have also been updated correctly
- [x] If any new file was added I verified that:
    - [x] The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
- [x] If a new CSS style is added I verified that:
    - [x] A similar style doesn't already exist
    - [x] The style can't be created with an existing [StyleUtils](https://github.com/Expensify/App/blob/main/src/styles/utils/index.ts) function (i.e. `StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)`)
- [x] If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
- [x] If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like `Avatar` is modified, I verified that `Avatar` is working as expected in all cases)
- [x] If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
- [x] If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
- [x] If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    - [x] I verified that all the inputs inside a form are aligned with each other.
    - [x] I added `Design` label and/or tagged `@Expensify/design` so the design team can review the changes.
- [x] If a new page is added, I verified it's using the `ScrollView` component to make it scrollable when more elements are added to the page.
- [x] I added [unit tests](https://github.com/Expensify/App/blob/main/tests/README.md) for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
- [x] If the `main` branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the `Test` steps.

### Screenshots/Videos
<details>
<summary>Android: Native</summary>

<!-- add screenshots or videos here -->

![image](https://github.com/user-attachments/assets/79b8d3da-febb-4a61-89ce-919b6e6c781f)

</details>

<details>
<summary>Android: mWeb Chrome</summary>

<!-- add screenshots or videos here -->

![image](https://github.com/user-attachments/assets/f1ce270d-a4c6-4b6a-a338-4e3a54cdbb73)


</details>

<details>
<summary>iOS: Native</summary>

<!-- add screenshots or videos here -->

</details>

<details>
<summary>iOS: mWeb Safari</summary>

<!-- add screenshots or videos here -->

<img width="361" alt="image" src="https://github.com/user-attachments/assets/af33c975-904c-4d8e-95e3-bcd33d080f22" />


</details>

<details>
<summary>MacOS: Chrome / Safari</summary>

<!-- add screenshots or videos here -->

</details>

<details>
<summary>MacOS: Desktop</summary>

<!-- add screenshots or videos here -->

<img width="1197" alt="image" src="https://github.com/user-attachments/assets/b2991c85-0c69-4b34-8b30-9b4dc70ae4b7" />


</details>



Diff:
diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
index 1330093dffd1..e29fe39c0e0a 100644
--- a/.storybook/preview.tsx
+++ b/.storybook/preview.tsx
@@ -3,6 +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 ComposeProviders from '@src/components/ComposeProviders';
 import HTMLEngineProvider from '@src/components/HTMLEngineProvider';
 import {LocaleContextProvider} from '@src/components/LocaleContextProvider';
@@ -21,7 +22,18 @@ Onyx.init({
 
 const decorators = [
     (Story: React.ElementType) => (
-        <ComposeProviders components={[OnyxProvider, LocaleContextProvider, HTMLEngineProvider, SafeAreaProvider, PortalProvider, EnvironmentProvider, KeyboardStateProvider]}>
+        <ComposeProviders
+            components={[
+                OnyxProvider,
+                LocaleContextProvider,
+                HTMLEngineProvider,
+                SafeAreaProvider,
+                PortalProvider,
+                EnvironmentProvider,
+                KeyboardStateProvider,
+                MoneyRequestReportContextProvider,
+            ]}
+        >
             <Story />
         </ComposeProviders>
     ),
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 92c9a3650b5a..95412591690b 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -108,6 +108,13 @@ const ROUTES = {
             return getUrlWithBackToParam(baseRoute, backTo);
         },
     },
+    SEARCH_MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS: {
+        route: 'search/r/:reportID/hold',
+        getRoute: ({reportID, backTo}: {reportID: string; backTo?: string}) => {
+            const baseRoute = `search/r/${reportID}/hold` as const;
+            return getUrlWithBackToParam(baseRoute, backTo);
+        },
+    },
     TRANSACTION_HOLD_REASON_RHP: 'search/hold',
 
     // This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 01a83f520507..f6241a6b487f 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -39,6 +39,7 @@ const SCREENS = {
     SEARCH: {
         ROOT: 'Search_Root',
         MONEY_REQUEST_REPORT: 'Search_Money_Request_Report',
+        MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS: 'Search_Money_Request_Report_Hold_Transactions',
         REPORT_RHP: 'Search_Report_RHP',
         ADVANCED_FILTERS_RHP: 'Search_Advanced_Filters_RHP',
         ADVANCED_FILTERS_DATE_RHP: 'Search_Advanced_Filters_Date_RHP',
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 6567d2e92ae0..6acb9c867b02 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -9,13 +9,15 @@ import usePaymentAnimations from '@hooks/usePaymentAnimations';
 import useResponsiveLayout from '@hooks/useResponsiveLayout';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
+import {exportReportToCSV} from '@libs/actions/Report';
 import {convertToDisplayString} from '@libs/CurrencyUtils';
 import Navigation from '@libs/Navigation/Navigation';
 import {buildOptimisticNextStepForPreventSelfApprovalsEnabled} from '@libs/NextStepUtils';
 import {getConnectedIntegration} from '@libs/PolicyUtils';
-import {getOriginalMessage, isDeletedAction, isMoneyRequestAction, isTrackExpenseAction} from '@libs/ReportActionsUtils';
+import {getIOUActionForTransactionID, getOriginalMessage, isDeletedAction, isMoneyRequestAction, isTrackExpenseAction} from '@libs/ReportActionsUtils';
 import {
     canBeExported,
+    canDeleteCardTransactionByLiabilityType,
     canDeleteTransaction,
     getArchiveReason,
     getBankAccountRoute,
@@ -38,6 +40,7 @@ import {
 import {
     allHavePendingRTERViolation,
     checkIfShouldShowMarkAsCashButton,
+    getTransaction,
     hasDuplicateTransactions,
     isDuplicate as isDuplicateTransactionUtils,
     isExpensifyCardTransaction,
@@ -60,6 +63,7 @@ import {
     payInvoice,
     payMoneyRequest,
     submitReport,
+    unholdRequest,
 } from '@userActions/IOU';
 import {markAsCash as markAsCashAction} from '@userActions/Transaction';
 import CONST from '@src/CONST';
@@ -74,7 +78,9 @@ import type IconAsset from '@src/types/utils/IconAsset';
 import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
 import BrokenConnectionDescription from './BrokenConnectionDescription';
 import Button from './Button';
+import ButtonWithDropdownMenu from './ButtonWithDropdownMenu';
 import ConfirmModal from './ConfirmModal';
+import DecisionModal from './DecisionModal';
 import DelegateNoAccessModal from './DelegateNoAccessModal';
 import HeaderWithBackButton from './HeaderWithBackButton';
 import Icon from './Icon';
@@ -84,6 +90,7 @@ import LoadingBar from './LoadingBar';
 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 ExportWithDropdownMenu from './ReportActionItem/ExportWithDropdownMenu';
@@ -182,6 +189,105 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
+
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(moneyRequestReport?.reportID);
+
+    const selectedTransactionsOptions = useMemo(() => {
+        if (!selectedTransactionsID.length) {
+            return [];
+        }
+        const options = [];
+        const selectedTransactions = selectedTransactionsID.map((transactionID) => getTransaction(transactionID)).filter((t) => !!t);
+
+        const anyTransactionOnHold = selectedTransactions.some(isOnHoldTransactionUtils);
+        const allTransactionOnHold = selectedTransactions.every(isOnHoldTransactionUtils);
+
+        if (!anyTransactionOnHold && selectedTransactions.length === 1) {
+            options.push({
+                text: translate('iou.hold'),
+                icon: Expensicons.Stopwatch,
+                value: CONST.REPORT.SECONDARY_ACTIONS.HOLD,
+                onSelected: () => {
+                    if (!moneyRequestReport?.reportID) {
+                        return;
+                    }
+                    Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS.getRoute({reportID: moneyRequestReport.reportID}));
+                },
+            });
+        }
+
+        if (allTransactionOnHold && selectedTransactions.length === 1) {
+            options.push({
+                text: translate('iou.unhold'),
+                icon: Expensicons.Stopwatch,
+                value: 'UNHOLD',
+                onSelected: () => {
+                    selectedTransactionsID.forEach((transactionID) => {
+                        const action = getIOUActionForTransactionID(reportActions, transactionID);
+                        if (!action?.childReportID) {
+                            return;
+                        }
+                        unholdRequest(transactionID, action?.childReportID);
+                    });
+                    // it's needed in order to recalculate options
+                    setSelectedTransactionsID([...selectedTransactionsID]);
+                },
+            });
+        }
+
+        options.push({
+            value: CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD,
+            text: translate('common.download'),
+            icon: Expensicons.Download,
+            onSelected: () => {
+                if (!moneyRequestReport) {
+                    return;
+                }
+                exportReportToCSV({reportID: moneyRequestReport.reportID, transactionIDList: selectedTransactionsID}, () => {
+                    setIsDownloadErrorModalVisible(true);
+                });
+            },
+        });
+
+        const canAllSelectedTransactionsBeRemoved = selectedTransactionsID.every((transactionID) => {
+            const canRemoveTransaction = canDeleteCardTransactionByLiabilityType(transactionID);
+            const action = getIOUActionForTransactionID(reportActions, transactionID);
+            const isActionDeleted = isDeletedAction(action);
+            const isIOUActionOwner = typeof action?.actorAccountID === 'number' && typeof session?.accountID === 'number' && action.actorAccountID === session?.accountID;
+
+            return canRemoveTransaction && isIOUActionOwner && !isActionDeleted;
+        });
+
+        const canRemoveReportTransaction = canDeleteTransaction(moneyRequestReport);
+
+        if (canRemoveReportTransaction && canAllSelectedTransactionsBeRemoved) {
+            options.push({
+                text: translate('common.delete'),
+                icon: Expensicons.Trashcan,
+                value: CONST.REPORT.SECONDARY_ACTIONS.DELETE,
+                onSelected: () => {
+                    const iouActions = reportActions.filter((action) => isMoneyRequestAction(action));
+
+                    const transactionsWithActions = selectedTransactions.map((t) => ({
+                        transactionID: t?.transactionID,
+                        action: iouActions.find((action) => {
+                            const IOUTransactionID = (getOriginalMessage(action) as OnyxTypes.OriginalMessageIOU)?.IOUTransactionID;
+
+                            return t?.transactionID === IOUTransactionID;
+                        }),
+                    }));
+
+                    transactionsWithActions.forEach(({transactionID, action}) => action && deleteMoneyRequest(transactionID, action));
+                    setSelectedTransactionsID([]);
+                },
+            });
+        }
+        return options;
+    }, [moneyRequestReport, reportActions, selectedTransactionsID, session?.accountID, setSelectedTransactionsID, translate]);
+
+    const shouldShowSelectedTransactionsButton = !!selectedTransactionsOptions.length;
+
     const canIOUBePaid = useMemo(() => getCanIOUBePaid(), [getCanIOUBePaid]);
     const canIOUBePaidAndApproved = useMemo(() => getCanIOUBePaid(false, false), [getCanIOUBePaid]);
     const onlyShowPayElsewhere = useMemo(() => !canIOUBePaid && getCanIOUBePaid(true), [canIOUBePaid, getCanIOUBePaid]);
@@ -206,6 +312,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
     const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && connectedIntegration && isAdmin && canBeExported(moneyRequestReport);
 
     const shouldShowSettlementButton =
+        !shouldShowSelectedTransactionsButton &&
         !shouldShowSubmitButton &&
         (shouldShowPayButton || shouldShowApproveButton) &&
         !shouldShowRTERViolationMessage(transactions) &&
@@ -228,6 +335,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
 
     const shouldShowNextStep = isFromPaidPolicy && !!optimisticNextStep?.message?.length && !shouldShowStatusBar;
     const shouldShowAnyButton =
+        shouldShowSelectedTransactionsButton ||
         isDuplicate ||
         shouldShowSettlementButton ||
         shouldShowApproveButton ||
@@ -402,7 +510,18 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
                 onBackButtonPress={onBackButtonPress}
                 shouldShowBorderBottom={false}
             >
-                {isDuplicate && !shouldUseNarrowLayout && (
+                {shouldShowSelectedTransactionsButton && !shouldUseNarrowLayout && (
+                    <View>
+                        <ButtonWithDropdownMenu
+                            onPress={() => null}
+                            options={selectedTransactionsOptions}
+                            customText={translate('workspace.common.selected', {count: selectedTransactionsID.length})}
+                            isSplitButton={false}
+                            shouldAlwaysShowDropdownMenu
+                        />
+                    </View>
+                )}
+                {!shouldShowSelectedTransactionsButton && isDuplicate && !shouldUseNarrowLayout && (
                     <View style={[shouldDuplicateButtonBeSuccess ? styles.ml2 : styles.mh2]}>
                         <Button
                             success={shouldDuplicateButtonBeSuccess}
@@ -414,7 +533,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
                         />
                     </View>
                 )}
-                {shouldShowSettlementButton && !shouldUseNarrowLayout && (
+                {!shouldShowSelectedTransactionsButton && shouldShowSettlementButton && !shouldUseNarrowLayout && (
                     <View style={styles.pv2}>
                         <AnimatedSettlementButton
                             isPaidAnimationRunning={isPaidAnimationRunning}
@@ -440,7 +559,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
                         />
                     </View>
                 )}
-                {!!shouldShowExportIntegrationButton && !shouldUseNarrowLayout && (
+                {!shouldShowSelectedTransactionsButton && !!shouldShowExportIntegrationButton && !shouldUseNarrowLayout && (
                     <View style={[styles.pv2]}>
                         <ExportWithDropdownMenu
                             policy={policy}
@@ -449,7 +568,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
                         />
                     </View>
                 )}
-                {!!moneyRequestReport && shouldShowSubmitButton && !shouldUseNarrowLayout && (
+                {!shouldShowSelectedTransactionsButton && !!moneyRequestReport && shouldShowSubmitButton && !shouldUseNarrowLayout && (
                     <View style={styles.pv2}>
                         <Button
                             success={isWaitingForSubmissionFromCurrentUser}
@@ -460,7 +579,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
                         />
                     </View>
                 )}
-                {shouldShowMarkAsCashButton && !shouldUseNarrowLayout && (
+                {!shouldShowSelectedTransactionsButton && shouldShowMarkAsCashButton && !shouldUseNarrowLayout && (
                     <View style={[styles.pv2]}>
                         <Button
                             success
@@ -474,7 +593,18 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
             {!!isMoreContentShown && (
                 <View style={[styles.dFlex, styles.flexColumn, shouldAddGapToContents && styles.gap3, styles.pb3, styles.ph5]}>
                     <View style={[styles.dFlex, styles.w100, styles.flexRow, styles.gap3]}>
-                        {isDuplicate && shouldUseNarrowLayout && (
+                        {shouldShowSelectedTransactionsButton && shouldUseNarrowLayout && (
+                            <ButtonWithDropdownMenu
+                                onPress={() => null}
+                                options={selectedTransactionsOptions}
+                                customText={translate('workspace.common.selected', {count: selectedTransactionsID.length})}
+                                isSplitButton={false}
+                                shouldAlwaysShowDropdownMenu
+                                wrapperStyle={styles.w100}
+                            />
+                        )}
+
+                        {!shouldShowSelectedTransactionsButton && isDuplicate && shouldUseNarrowLayout && (
                             <Button
                                 success={shouldDuplicateButtonBeSuccess}
                                 text={translate('iou.reviewDuplicates')}
@@ -484,7 +614,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
                                 }}
                             />
                         )}
-                        {shouldShowSettlementButton && shouldUseNarrowLayout && (
+                        {!shouldShowSelectedTransactionsButton && shouldShowSettlementButton && shouldUseNarrowLayout && (
                             <AnimatedSettlementButton
                                 isPaidAnimationRunning={isPaidAnimationRunning}
                                 isApprovedAnimationRunning={isApprovedAnimationRunning}
@@ -508,14 +638,14 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
                                 isLoading={!isOffline && !canAllowSettlement}
                             />
                         )}
-                        {!!shouldShowExportIntegrationButton && shouldUseNarrowLayout && (
+                        {!shouldShowSelectedTransactionsButton && !!shouldShowExportIntegrationButton && shouldUseNarrowLayout && (
                             <ExportWithDropdownMenu
                                 policy={policy}
                                 report={moneyRequestReport}
                                 connectionName={connectedIntegration}
                             />
                         )}
-                        {!!moneyRequestReport && shouldShowSubmitButton && shouldUseNarrowLayout && (
+                        {!shouldShowSelectedTransactionsButton && !!moneyRequestReport && shouldShowSubmitButton && shouldUseNarrowLayout && (
                             <Button
                                 success={isWaitingForSubmissionFromCurrentUser}
                                 text={translate('common.submit')}
@@ -524,7 +654,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
                                 isDisabled={shouldDisableSubmitButton}
                             />
                         )}
-                        {shouldShowMarkAsCashButton && shouldUseNarrowLayout && (
+                        {!shouldShowSelectedTransactionsButton && shouldShowMarkAsCashButton && shouldUseNarrowLayout && (
                             <Button
                                 success
                                 text={translate('iou.markAsCash')}
@@ -580,6 +710,15 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
                 danger
                 shouldEnableNewFocusManagement
             />
+            <DecisionModal
+                title={translate('common.downloadFailedTitle')}
+                prompt={translate('common.downloadFailedDescription')}
+                isSmallScreenWidth={isSmallScreenWidth}
+                onSecondOptionSubmit={() => setIsDownloadErrorModalVisible(false)}
+                secondOptionText={translate('common.buttonConfirm')}
+                isVisible={isDownloadErrorModalVisible}
+                onClose={() => setIsDownloadErrorModalVisible(false)}
+            />
         </View>
     );
 }
diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx
index d1eae9ee8f02..7c5825f598d6 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx
@@ -17,6 +17,7 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
 import {isActionVisibleOnMoneyRequestReport} from '@libs/MoneyRequestReportUtils';
 import {
     getFirstVisibleReportActionID,
+    getIOUActionForTransactionID,
     getMostRecentIOURequestActionID,
     getOneTransactionThreadReportID,
     hasNextActionMadeBySameActor,
@@ -70,9 +71,13 @@ function getParentReportAction(parentReportActions: OnyxEntry<OnyxTypes.ReportAc
     return parentReportActions[parentReportActionID];
 }
 
-function selectTransactionsForReportID(transactions: OnyxCollection<OnyxTypes.Transaction>, reportID: string) {
+function selectTransactionsForReportID(transactions: OnyxCollection<OnyxTypes.Transaction>, reportID: string, reportActions: OnyxTypes.ReportAction[]) {
     return Object.values(transactions ?? {}).filter((transaction): transaction is Transaction => {
-        return transaction?.reportID === reportID;
+        if (!transaction) {
+            return false;
+        }
+        const action = getIOUActionForTransactionID(reportActions, transaction.transactionID);
+        return transaction.reportID === reportID && !isDeletedParentAction(action);
     });
 }
 
@@ -97,7 +102,7 @@ function MoneyRequestReportActionsList({report, reportActions = [], hasNewerActi
     const transactionThreadReportID = getOneTransactionThreadReportID(reportID, reportActions ?? [], false);
     const firstVisibleReportActionID = useMemo(() => getFirstVisibleReportActionID(reportActions, isOffline), [reportActions, isOffline]);
     const [transactions = []] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {
-        selector: (allTransactions): OnyxTypes.Transaction[] => selectTransactionsForReportID(allTransactions, reportID),
+        selector: (allTransactions): OnyxTypes.Transaction[] => selectTransactionsForReportID(allTransactions, reportID, reportActions),
     });
     const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID ?? CONST.DEFAULT_NUMBER_ID}`);
     const [currentUserAccountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID});
diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportContext.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportContext.tsx
new file mode 100644
index 000000000000..5537d3c41bf7
--- /dev/null
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportContext.tsx
@@ -0,0 +1,92 @@
+import React, {useCallback, useContext, useMemo, useState} from 'react';
+import type ChildrenProps from '@src/types/utils/ChildrenProps';
+
+type TMoneyRequestReportContext = {
+    selectedTransactionsID: Record<string, string[]>;
+    setSelectedTransactionsID: (reportID: string) => (transactionsID: string[]) => void;
+    toggleTransaction: (reportID: string) => (transactionID: string) => void;
+    removeTransaction: (reportID: string) => (transactionID?: string) => void;
+    isTransactionSelected: (reportID: string) => (transactionID: string) => boolean;
+};
+
+const defaultMoneyRequestReportContext = {
+    selectedTransactionsID: {},
+    setSelectedTransactionsID: () => () => {},
+    toggleTransaction: () => () => {},
+    removeTransaction: () => () => {},
+    isTransactionSelected: () => () => false,
+};
+
+const Context = React.createContext<TMoneyRequestReportContext>(defaultMoneyRequestReportContext);
+
+// TODO merge it with SearchContext in follow-up - https://github.com/Expensify/App/issues/59431
+function MoneyRequestReportContextProvider({children}: ChildrenProps) {
+    const [selectedTransactionsForReport, setSelectedTransactionsForReport] = useState<Record<string, string[]>>({});
+
+    const setSelectedTransactionsID = useCallback(
+        (reportID: string) => (transactionsID: string[]) => {
+            setSelectedTransactionsForReport((prev) => ({...prev, [reportID]: transactionsID}));
+        },
+        [],
+    );
+
+    const toggleTransaction = useCallback(
+        (reportID: string) => (transactionID: string) => {
+            setSelectedTransactionsForReport((prev) => {
+                const prevTransactions = prev[reportID] ?? [];
+                if (prevTransactions.includes(transactionID)) {
+                    return {...prev, [reportID]: prevTransactions.filter((t) => t !== transactionID)};
+                }
+                return {...prev, [reportID]: [...prevTransactions, transactionID]};
+            });
+        },
+        [],
+    );
+
+    const removeTransaction = useCallback(
+        (reportID: string) => (transactionID?: string) => {
+            setSelectedTransactionsForReport((prev) => {
+                const prevTransactions = prev[reportID] ?? [];
+                return {...prev, [reportID]: prevTransactions.filter((t) => t !== transactionID)};
+            });
+        },
+        [],
+    );
+
+    const isTransactionSelected = useCallback(
+        (reportID: string) => (transactionID: string) => (selectedTransactionsForReport[reportID] ?? []).includes(transactionID),
+        [selectedTransactionsForReport],
+    );
+
+    const context = useMemo(
+        () => ({
+            selectedTransactionsID: selectedTransactionsForReport,
+            setSelectedTransactionsID,
+            toggleTransaction,
+            isTransactionSelected,
+            removeTransaction,
+        }),
+        [isTransactionSelected, removeTransaction, selectedTransactionsForReport, setSelectedTransactionsID, toggleTransaction],
+    );
+
+    return <Context.Provider value={context}>{children}</Context.Provider>;
+}
+
+function useMoneyRequestReportContext(reportID?: string) {
+    const context = useContext(Context);
+
+    return useMemo(
+        () => ({
+            selectedTransactionsID: reportID ? context.selectedTransactionsID[reportID] ?? [] : [],
+            setSelectedTransactionsID: reportID ? context.setSelectedTransactionsID(reportID) : () => {},
+            toggleTransaction: reportID ? context.toggleTransaction(reportID) : () => {},
+            isTransactionSelected: reportID ? context.isTransactionSelected(reportID) : () => false,
+            removeTransaction: reportID ? context.removeTransaction(reportID) : () => {},
+        }),
+        [context, reportID],
+    );
+}
+
+MoneyRequestReportContextProvider.displayName = 'MoneyRequestReportContextProvider';
+
+export {MoneyRequestReportContextProvider, useMoneyRequestReportContext};
diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTableHeader.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTableHeader.tsx
index 68b7073501e9..fba37c182a71 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportTableHeader.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportTableHeader.tsx
@@ -66,7 +66,7 @@ function MoneyRequestReportTableHeader({sortBy, sortOrder, onSortPress, dateColu
     const styles = useThemeStyles();
 
     return (
-        <View style={[styles.peopleRow, styles.listTableHeader, styles.pt4]}>
+        <View style={[styles.dFlex, styles.flex5]}>
             <SortableTableHeader
                 columns={columnConfig}
                 shouldShowColumn={shouldShowColumn}
diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
index 676a4bfb28b9..f901c11ea25f 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
@@ -1,8 +1,9 @@
 import isEmpty from 'lodash/isEmpty';
-import React, {useCallback, useEffect, useMemo, useState} from 'react';
+import React, {memo, useCallback, useMemo, useState} from 'react';
 import {View} from 'react-native';
 import type {TupleToUnion} from 'type-fest';
 import {getButtonRole} from '@components/Button/utils';
+import Checkbox from '@components/Checkbox';
 import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
 import type {SortOrder} from '@components/Search/types';
 import Text from '@components/Text';
@@ -11,6 +12,7 @@ import useHover from '@hooks/useHover';
 import useLocalize from '@hooks/useLocalize';
 import {useMouseContext} from '@hooks/useMouseContext';
 import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useStyleUtils from '@hooks/useStyleUtils';
 import useThemeStyles from '@hooks/useThemeStyles';
 import {convertToDisplayString} from '@libs/CurrencyUtils';
 import {getThreadReportIDsForTransactions} from '@libs/MoneyRequestReportUtils';
@@ -19,9 +21,11 @@ import {getMoneyRequestSpendBreakdown} from '@libs/ReportUtils';
 import {compareValues} from '@libs/SearchUIUtils';
 import shouldShowTransactionYear from '@libs/TransactionUtils/shouldShowTransactionYear';
 import Navigation from '@navigation/Navigation';
+import variables from '@styles/variables';
 import CONST from '@src/CONST';
 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 {setActiveTransactionReportIDs} from './TransactionReportIDRepository';
@@ -50,7 +54,6 @@ const sortableColumnNames = [
 type SortableColumnName = TupleToUnion<typeof sortableColumnNames>;
 
 type SortedTransactions = {
-    transactions: OnyxTypes.Transaction[];
     sortBy: SortableColumnName;
     sortOrder: SortOrder;
 };
@@ -62,18 +65,9 @@ const getTransactionKey = (transaction: OnyxTypes.Transaction, key: SortableColu
     return key === CONST.SEARCH.TABLE_COLUMNS.DATE ? dateKey : key;
 };
 
-const areTransactionValuesEqual = (transactions: OnyxTypes.Transaction[], key: SortableColumnName) => {
-    const firstValidTransaction = transactions.find((transaction) => transaction !== undefined);
-    if (!firstValidTransaction) {
-        return true;
-    }
-
-    const keyOfFirstValidTransaction = getTransactionKey(firstValidTransaction, key);
-    return transactions.every((transaction) => transaction[getTransactionKey(transaction, key)] === firstValidTransaction[keyOfFirstValidTransaction]);
-};
-
 function MoneyRequestReportTransactionList({report, transactions, reportActions, hasComments}: MoneyRequestReportTransactionListProps) {
     const styles = useThemeStyles();
+    const StyleUtils = useStyleUtils();
     const {translate} = useLocalize();
     const {shouldUseNarrowLayout, isMediumScreenWidth} = useResponsiveLayout();
     const displayNarrowVersion = isMediumScreenWidth || shouldUseNarrowLayout;
@@ -86,29 +80,23 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions,
     const {bind} = useHover();
     const {isMouseDownOnInput, setMouseUp} = useMouseContext();
 
+    const {selectedTransactionsID, setSelectedTransactionsID, toggleTransaction, isTransactionSelected} = useMoneyRequestReportContext(report.reportID);
+
     const handleMouseLeave = (e: React.MouseEvent<Element, MouseEvent>) => {
         bind.onMouseLeave();
         e.stopPropagation();
         setMouseUp();
     };
 
-    const [sortedData, setSortedData] = useState<SortedTransactions>({
-        transactions,
+    const [sortConfig, setSortConfig] = useState<SortedTransactions>({
         sortBy: CONST.SEARCH.TABLE_COLUMNS.DATE,
         sortOrder: CONST.SEARCH.SORT_ORDER.ASC,
     });
 
-    const {sortBy, sortOrder} = sortedData;
+    const {sortBy, sortOrder} = sortConfig;
 
-    useEffect(() => {
-        if (areTransactionValuesEqual(transactions, sortBy)) {
-            return;
-        }
-
-        setSortedData((prevState) => ({
-            ...prevState,
-            transactions: [...transactions].sort((a, b) => compareValues(a[getTransactionKey(a, sortBy)], b[getTransactionKey(b, sortBy)], sortOrder, sortBy)),
-        }));
+    const sortedTransactions = useMemo(() => {
+        return [...transactions].sort((a, b) => compareValues(a[getTransactionKey(a, sortBy)], b[getTransactionKey(b, sortBy)], sortOrder, sortBy));
     }, [sortBy, sortOrder, transactions]);
 
     const navigateToTransaction = useCallback(
@@ -123,12 +111,12 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions,
 
             // Single transaction report will open in RHP, and we need to find every other report ID for the rest of transactions
             // to display prev/next arrows in RHP for navigating between transactions
-            const sortedSiblingTransactionReportIDs = getThreadReportIDsForTransactions(reportActions, sortedData.transactions);
+            const sortedSiblingTransactionReportIDs = getThreadReportIDsForTransactions(reportActions, sortedTransactions);
             setActiveTransactionReportIDs(sortedSiblingTransactionReportIDs);
 
             Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: reportIDToNavigate, backTo}));
         },
-        [reportActions, sortedData.transactions],
+        [reportActions, sortedTransactions],
     );
 
     const dateColumnSize = useMemo(() => {
@@ -143,22 +131,37 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions,
     return !isEmpty(transactions) ? (
         <>
             {!displayNarrowVersion && (
-                <MoneyRequestReportTableHeader
-                    shouldShowSorting
-                    sortBy={sortBy}
-                    sortOrder={sortOrder}
-                    dateColumnSize={dateColumnSize}
-                    onSortPress={(selectedSortBy, selectedSortOrder) => {
-                        if (!isSortableColumnName(selectedSortBy)) {
-                            return;
-                        }
-
-                        setSortedData((prevState) => ({...prevState, sortBy: selectedSortBy, sortOrder: selectedSortOrder}));
-                    }}
-                />
+                <View style={[styles.dFlex, styles.flexRow, styles.ph5, styles.pv3, StyleUtils.getPaddingBottom(6), styles.justifyContentCenter, styles.alignItemsCenter]}>
+                    <View style={[styles.pv2, styles.pr4, StyleUtils.getPaddingLeft(variables.w12)]}>
+                        <Checkbox
+                            onPress={() => {
+                                if (selectedTransactionsID.length === transactions.length) {
+                                    setSelectedTransactionsID([]);
+                                } else {
+                                    setSelectedTransactionsID(transactions.map((t) => t.transactionID));
+                                }
+                            }}
+                            accessibilityLabel={CONST.ROLE.CHECKBOX}
+                            isChecked={selectedTransactionsID.length === transactions.length}
+                        />
+                    </View>
+                    <MoneyRequestReportTableHeader
+                        shouldShowSorting
+                        sortBy={sortBy}
+                        sortOrder={sortOrder}
+                        dateColumnSize={dateColumnSize}
+                        onSortPress={(selectedSortBy, selectedSortOrder) => {
+                            if (!isSortableColumnName(selectedSortBy)) {
+                                return;
+                            }
+
+                            setSortConfig((prevState) => ({...prevState, sortBy: selectedSortBy, sortOrder: selectedSortOrder}));
+                        }}
+                    />
+                </View>
             )}
             <View style={[listHorizontalPadding, styles.gap2, styles.pb4, displayNarrowVersion && styles.pt4]}>
-                {sortedData.transactions.map((transaction) => {
+                {sortedTransactions.map((transaction) => {
                     return (
                         <PressableWithFeedback
                             onPress={(e) => {
@@ -180,11 +183,12 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions,
                         >
                             <TransactionItemRow
                                 transactionItem={transaction}
-                                isSelected={false}
+                                isSelected={isTransactionSelected(transaction.transactionID)}
                                 shouldShowTooltip
                                 dateColumnSize={dateColumnSize}
                                 shouldUseNarrowLayout={displayNarrowVersion}
                                 shouldShowChatBubbleComponent
+                                onCheckboxPress={toggleTransaction}
                             />
                         </PressableWithFeedback>
                     );
@@ -230,4 +234,4 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions,
 
 MoneyRequestReportTransactionList.displayName = 'MoneyRequestReportTransactionList';
 
-export default MoneyRequestReportTransactionList;
+export default memo(MoneyRequestReportTransactionList);
diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx
index 4667de4c4cb7..4d57e972cac0 100644
--- a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx
+++ b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx
@@ -134,7 +134,7 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe
                     <MoneyReportHeader
                         report={report}
                         policy={policy}
-                        reportActions={[]}
+                        reportActions={reportActions}
                         transactionThreadReportID={undefined}
                         shouldDisplayBackButton
                         onBackButtonPress={() => {
diff --git a/src/components/TransactionItemRow/DataCells/ChatBubbleCell.tsx b/src/components/TransactionItemRow/DataCells/ChatBubbleCell.tsx
index a5f178375765..22ae7f0d9daa 100644
--- a/src/components/TransactionItemRow/DataCells/ChatBubbleCell.tsx
+++ b/src/components/TransactionItemRow/DataCells/ChatBubbleCell.tsx
@@ -1,5 +1,6 @@
 import React, {useMemo} from 'react';
 import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
 import Icon from '@components/Icon';
 import {ChatBubbleCounter} from '@components/Icon/Expensicons';
 import Text from '@components/Text';
@@ -7,34 +8,34 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
 import useStyleUtils from '@hooks/useStyleUtils';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
-import {getIOUActionForReportID} from '@libs/ReportActionsUtils';
-import {getReportOrDraftReport, isChatThread} from '@libs/ReportUtils';
+import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
+import {isChatThread} from '@libs/ReportUtils';
 import variables from '@styles/variables';
+import ONYXKEYS from '@src/ONYXKEYS';
 import type {Report} from '@src/types/onyx';
 import type Transaction from '@src/types/onyx/Transaction';
 
 const isReportUnread = ({lastReadTime = '', lastVisibleActionCreated = '', lastMentionedTime = ''}: Report): boolean =>
     lastReadTime < lastVisibleActionCreated || lastReadTime < (lastMentionedTime ?? '');
 
-/*
- * Child is retrieved because we want to get the unread status & messages count of the expense, not the report itself.
- * We can get the child's message count from the report, but we cannot get the child's unread info from there.
- */
-function getTransactionMessagesCountAndUnreadInfo(transaction: Transaction) {
-    const iouReportAction = getIOUActionForReportID(transaction.reportID, transaction.transactionID);
-    const childReport = iouReportAction ? getReportOrDraftReport(iouReportAction.childReportID) : undefined;
-
-    return {
-        count: (iouReportAction && iouReportAction.childVisibleActionCount) ?? 0,
-        isUnread: isChatThread(childReport) && isReportUnread(childReport),
-    };
-}
-
 function ChatBubbleCell({transaction}: {transaction: Transaction}) {
     const theme = useTheme();
     const styles = useThemeStyles();
     const {shouldUseNarrowLayout} = useResponsiveLayout();
-    const threadMessages = useMemo(() => getTransactionMessagesCountAndUnreadInfo(transaction), [transaction]);
+    const [iouReportAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction.reportID}`, {
+        selector: (reportActions) => getIOUActionForTransactionID(Object.values(reportActions ?? {}), transaction.transactionID),
+    });
+
+    const [transactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportAction?.childReportID}`);
+
+    const threadMessages = useMemo(
+        () => ({
+            count: (iouReportAction && iouReportAction?.childVisibleActionCount) ?? 0,
+            isUnread: isChatThread(transactionReport) && isReportUnread(transactionReport),
+        }),
+        [iouReportAction, transactionReport],
+    );
+
     const StyleUtils = useStyleUtils();
 
     const elementSize = shouldUseNarrowLayout ? variables.iconSizeSmall : variables.iconSizeNormal;
diff --git a/src/components/TransactionItemRow/DataCells/MerchantCell.tsx b/src/components/TransactionItemRow/DataCells/MerchantCell.tsx
index ec75a5828cf0..bfe0154cb167 100644
--- a/src/components/TransactionItemRow/DataCells/MerchantCell.tsx
+++ b/src/components/TransactionItemRow/DataCells/MerchantCell.tsx
@@ -1,13 +1,16 @@
 import React from 'react';
 import TextWithTooltip from '@components/TextWithTooltip';
 import useThemeStyles from '@hooks/useThemeStyles';
+import {getMerchant} from '@libs/TransactionUtils';
 import CONST from '@src/CONST';
 import type TransactionDataCellProps from './TransactionDataCellProps';
 
 function MerchantCell({transactionItem, shouldShowTooltip}: TransactionDataCellProps) {
     const styles = useThemeStyles();
 
-    const merchantToDisplay = !transactionItem?.merchant || transactionItem?.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT ? '' : transactionItem.merchant;
+    const merchantName = getMerchant(transactionItem);
+    const merchantToDisplay = merchantName === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT ? '' : merchantName;
+
     return (
         <TextWithTooltip
             shouldShowTooltip={shouldShowTooltip}
diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx
index b29fbaa9a274..2df91c359600 100644
--- a/src/components/TransactionItemRow/index.tsx
+++ b/src/components/TransactionItemRow/index.tsx
@@ -1,5 +1,6 @@
 import React from 'react';
 import {View} from 'react-native';
+import Checkbox from '@components/Checkbox';
 import Hoverable from '@components/Hoverable';
 import type {TableColumnSize} from '@components/Search/types';
 import Text from '@components/Text';
@@ -24,6 +25,7 @@ function TransactionItemRow({
     shouldShowTooltip,
     dateColumnSize,
     shouldShowChatBubbleComponent = false,
+    onCheckboxPress,
 }: {
     transactionItem: Transaction;
     shouldUseNarrowLayout: boolean;
@@ -31,6 +33,7 @@ function TransactionItemRow({
     shouldShowTooltip: boolean;
     dateColumnSize: TableColumnSize;
     shouldShowChatBubbleComponent?: boolean;
+    onCheckboxPress: (transactionID: string) => void;
 }) {
     const styles = useThemeStyles();
     const StyleUtils = useStyleUtils();
@@ -50,7 +53,7 @@ function TransactionItemRow({
                                 <View style={[styles.mr3]}>
                                     <ReceiptCell
                                         transactionItem={transactionItem}
-                                        isSelected
+                                        isSelected={isSelected}
                                     />
                                 </View>
                                 <View style={[styles.flex2, styles.flexColumn, styles.justifyContentEvenly]}>
@@ -109,10 +112,19 @@ function TransactionItemRow({
                     {(hovered) => (
                         <View style={[hovered ? styles.hoveredComponentBG : backgroundColor, styles.p3, styles.expenseWidgetRadius, styles.gap2]}>
                             <View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.gap3]}>
+                                <View style={[styles.mr1]}>
+                                    <Checkbox
+                                        onPress={() => {
+                                            onCheckboxPress(transactionItem.transactionID);
+                                        }}
+                                        accessibilityLabel={CONST.ROLE.CHECKBOX}
+                                        isChecked={isSelected}
+                                    />
+                                </View>
                                 <View style={[StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.RECEIPT)]}>
                                     <ReceiptCell
                                         transactionItem={transactionItem}
-                                        isSelected
+                                        isSelected={isSelected}
                                     />
                                 </View>
                                 <View style={[StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.TYPE)]}>
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index 218c2c5e3c71..2c11d308cc19 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -6,6 +6,7 @@ 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';
@@ -478,7 +479,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
     }
 
     return (
-        <ComposeProviders components={[OptionsListContextProvider, ActiveWorkspaceContextProvider, ReportIDsContextProvider, SearchContextProvider]}>
+        <ComposeProviders components={[OptionsListContextProvider, ActiveWorkspaceContextProvider, ReportIDsContextProvider, SearchContextProvider, MoneyRequestReportContextProvider]}>
             <RootStack.Navigator persistentScreens={[NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, SCREENS.SEARCH.ROOT]}>
                 {/* This has to be the first navigator in auth screens. */}
                 <RootStack.Screen
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index 8e4dd26974f4..8eacc21f53f8 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -684,6 +684,7 @@ const TransactionDuplicateStackNavigator = createModalStackNavigator<Transaction
 const SearchReportModalStackNavigator = createModalStackNavigator<SearchReportParamList>(
     {
         [SCREENS.SEARCH.REPORT_RHP]: () => require<ReactComponentModule>('../../../../pages/home/ReportScreen').default,
+        [SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS]: () => require<ReactComponentModule>('../../../../pages/Search/SearchMoneyRequestReportHoldReasonPage').default,
         [SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchHoldReasonPage').default,
     },
     () => ({
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 4c2ec248eb81..f7142cf632d0 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -1388,6 +1388,7 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
                 [SCREENS.RIGHT_MODAL.SEARCH_REPORT]: {
                     screens: {
                         [SCREENS.SEARCH.REPORT_RHP]: ROUTES.SEARCH_REPORT.route,
+                        [SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS]: ROUTES.SEARCH_MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS.route,
                         [SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP]: ROUTES.TRANSACTION_HOLD_REASON_RHP,
                     },
                 },
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 160a88797e25..f20a30d92c78 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -1857,6 +1857,12 @@ type SearchReportParamList = {
         /** Hash that includes info about what is searched for */
         searchHash?: number;
     };
+    [SCREENS.SEARCH.MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS]: {
+        /** Link to previous page */
+        backTo: Routes;
+        /** Selected transactions' report ID  */
+        reportID: string;
+    };
 };
 
 type SearchFullscreenNavigatorParamList = {
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index fa688d37007c..2f015350dc36 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -1691,8 +1691,7 @@ function getIOUActionForReportID(reportID: string | undefined, transactionID: st
     if (!reportID || !transactionID) {
         return undefined;
     }
-    const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
-    const reportActions = getAllReportActions(report?.reportID);
+    const reportActions = getAllReportActions(reportID);
 
     return getIOUActionForTransactionID(Object.values(reportActions ?? {}), transactionID);
 }
diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx
index 782bec1b00b5..999ec3483960 100644
--- a/src/pages/ReportDetailsPage.tsx
+++ b/src/pages/ReportDetailsPage.tsx
@@ -19,6 +19,7 @@ import * as Expensicons from '@components/Icon/Expensicons';
 import MenuItem from '@components/MenuItem';
 import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
 import Modal from '@components/Modal';
+import {useMoneyRequestReportContext} from '@components/MoneyRequestReportView/MoneyRequestReportContext';
 import MultipleAvatars from '@components/MultipleAvatars';
 import OfflineWithFeedback from '@components/OfflineWithFeedback';
 import ParentNavigationSubtitle from '@components/ParentNavigationSubtitle';
@@ -201,6 +202,8 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta
         initialValue: [],
     });
 
+    const {removeTransaction} = useMoneyRequestReportContext(report.parentReportID);
+
     const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false);
     const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
     const [isUnapproveModalVisible, setIsUnapproveModalVisible] = useState(false);
@@ -959,8 +962,9 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta
             deleteTrackExpense(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView);
         } else {
             deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView);
+            removeTransaction(iouTransactionID);
         }
-    }, [caseID, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, report, requestParentReportAction]);
+    }, [caseID, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, removeTransaction, report, requestParentReportAction]);
 
     // A flag to indicate whether the user chose to delete the transaction or not
     const isTransactionDeleted = useRef<boolean>(false);
diff --git a/src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx b/src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx
new file mode 100644
index 000000000000..1f4eef726161
--- /dev/null
+++ b/src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx
@@ -0,0 +1,64 @@
+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<SearchReportParamList, typeof SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP>) {
+    const {translate} = useLocalize();
+
+    const {backTo, reportID} = route.params;
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(reportID);
+
+    const onSubmit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM>) => {
+        const firstTransactionID = selectedTransactionsID.at(0);
+        if (!firstTransactionID) {
+            return;
+        }
+
+        putOnHold(firstTransactionID, values.comment, reportID);
+
+        // We need to do this so the actions in header are correctly updated
+        setSelectedTransactionsID([...selectedTransactionsID]);
+        Navigation.goBack();
+    };
+
+    const validate = useCallback(
+        (values: FormOnyxValues<typeof ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM>) => {
+            const errors: FormInputErrors<typeof ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM> = 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 (
+        <HoldReasonFormView
+            onSubmit={onSubmit}
+            validate={validate}
+            backTo={backTo}
+        />
+    );
+}
+
+SearchMoneyRequestReportHoldReasonPage.displayName = 'SearchMoneyRequestReportHoldReasonPage';
+
+export default SearchMoneyRequestReportHoldReasonPage;
diff --git a/src/stories/TransactionItemRow.stories.tsx b/src/stories/TransactionItemRow.stories.tsx
index 4b7da0d0441d..123617b85fed 100644
--- a/src/stories/TransactionItemRow.stories.tsx
+++ b/src/stories/TransactionItemRow.stories.tsx
@@ -57,6 +57,7 @@ function Template({transactionItem, shouldUseNarrowLayout, isSelected, shouldSho
                     shouldShowTooltip={shouldShowTooltip}
                     shouldShowChatBubbleComponent
                     dateColumnSize={CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL}
+                    onCheckboxPress={() => {}}
                 />
             </ThemeStylesProvider>
         </ThemeProvider>
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index 1d8faeb41cc7..3177366b179d 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -704,6 +704,15 @@ function getPaddingRight(paddingRight: number): ViewStyle {
     };
 }
 
+/**
+ * Get variable padding-bottom as style
+ */
+function getPaddingBottom(paddingBottom: number): ViewStyle {
+    return {
+        paddingBottom,
+    };
+}
+
 /**
  * Checks to see if the iOS device has safe areas or not
  */
@@ -1201,6 +1210,7 @@ const staticStyleUtils = {
     getBackgroundColorWithOpacityStyle,
     getPaddingLeft,
     getPaddingRight,
+    getPaddingBottom,
     hasSafeAreas,
     getHeight,
     getMinimumHeight,
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 84f41dba46f4..2dbd4cf8ec11 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -295,6 +295,7 @@ export default {
     h70: 70,
     h112: 112,
     h172: 172,
+    w12: 12,
     w20: 20,
     w28: 28,
     w36: 36,


Reviews:
user: login: allgandalf
body: Code is almost ready, please update the comments and we should be good with testing this PR
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T17:07:54Z
user: login: mountiny
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T17:55:46Z
user: login: mountiny
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T17:56:50Z
user: login: mountiny
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T17:57:14Z
user: login: mountiny
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T17:57:35Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T10:22:18Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T10:28:02Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T10:37:58Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T10:39:47Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T10:43:28Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T11:14:38Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T11:17:11Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T11:19:17Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T10:23:58Z
user: login: luacmartins
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T17:21:06Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T09:30:02Z
user: login: allgandalf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T10:34:17Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T10:45:45Z
user: login: allgandalf
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T10:47:55Z
user: login: allgandalf
body: ^ bug is minor and can be dealt with in a follow up
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T16:06:21Z
user: login: luacmartins
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T16:23:58Z
user: login: luacmartins
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T15:53:50Z
user: login: mountiny
body: Tests well for me and changes look good to me, but I would still wait for the design feedback from @shawnborton to be addressed unless he is happy to move on. This PR does hold some other changes so unless its some biggest design issues, I would recommend merging 🙌 

<img width="1431" alt="image" src="https://github.com/user-attachments/assets/74947cf7-b8ad-4e8b-a5c9-5fafed655053" />

state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T16:21:02Z
user: login: mountiny
body: Those seem like minor checks, since we are super close to another deploy, I would merge it now and add those 2 remarks to a follow up tracker
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T17:30:29Z
user: login: allroundexperts
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T22:21:54Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T13:36:27Z
user: login: luacmartins
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T15:43:47Z
user: login: jnowakow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T07:06:23Z
user: login: luacmartins
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T20:32:26Z
user: login: hoangzinh
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-06-10T22:35:08Z
user: login: DylanDylann
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-06-22T04:39:25Z

Review comments:
diff_hunk: @@ -0,0 +1,69 @@
+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 Navigation from '@libs/Navigation/Navigation';
+import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
+import {getFieldRequiredErrors} from '@libs/ValidationUtils';
+import HoldReasonFormView from '@pages/iou/HoldReasonFormView';
+import {clearErrorFields, clearErrors} from '@userActions/FormActions';
+import {holdMoneyRequestOnMoneyRequestReport} from '@src/libs/actions/Search';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm';
+
+type SearchMoneyRequestReportHoldReasonPageRouteParams = {
+    /** Link to previous page */
+    backTo: Route;
+
+    reportID: string;
path: src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx
user: login: allgandalf
body: Please add comment here
created_at: 2025-04-01T16:45:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,69 @@
+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 Navigation from '@libs/Navigation/Navigation';
+import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
+import {getFieldRequiredErrors} from '@libs/ValidationUtils';
+import HoldReasonFormView from '@pages/iou/HoldReasonFormView';
+import {clearErrorFields, clearErrors} from '@userActions/FormActions';
+import {holdMoneyRequestOnMoneyRequestReport} from '@src/libs/actions/Search';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm';
+
+type SearchMoneyRequestReportHoldReasonPageRouteParams = {
+    /** Link to previous page */
+    backTo: Route;
+
+    reportID: string;
+};
+
+type SearchMoneyRequestReportHoldReasonPageProps = {
+    /** Navigation route context info provided by react navigation */
+    route: PlatformStackRouteProp<{params?: SearchMoneyRequestReportHoldReasonPageRouteParams}>;
+};
+
+function SearchMoneyRequestReportHoldReasonPage({route}: SearchMoneyRequestReportHoldReasonPageProps) {
+    const {translate} = useLocalize();
+
+    const {backTo = '', reportID = ''} = route.params ?? {};
path: src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx
user: login: allgandalf
body: This is fancy, we don't initialise values like this AFAIK, we keep it undefined if not present, @mountiny thoughts ?
created_at: 2025-04-01T16:49:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,69 @@
+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 Navigation from '@libs/Navigation/Navigation';
+import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
+import {getFieldRequiredErrors} from '@libs/ValidationUtils';
+import HoldReasonFormView from '@pages/iou/HoldReasonFormView';
+import {clearErrorFields, clearErrors} from '@userActions/FormActions';
+import {holdMoneyRequestOnMoneyRequestReport} from '@src/libs/actions/Search';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm';
+
+type SearchMoneyRequestReportHoldReasonPageRouteParams = {
+    /** Link to previous page */
+    backTo: Route;
+
+    reportID: string;
+};
+
+type SearchMoneyRequestReportHoldReasonPageProps = {
+    /** Navigation route context info provided by react navigation */
+    route: PlatformStackRouteProp<{params?: SearchMoneyRequestReportHoldReasonPageRouteParams}>;
+};
+
+function SearchMoneyRequestReportHoldReasonPage({route}: SearchMoneyRequestReportHoldReasonPageProps) {
+    const {translate} = useLocalize();
+
+    const {backTo = '', reportID = ''} = route.params ?? {};
path: src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx
user: login: allgandalf
body: ```suggestion
    const {backTo, reportID} = route.params;
```

Something like this
created_at: 2025-04-01T16:49:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,69 @@
+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 Navigation from '@libs/Navigation/Navigation';
+import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
+import {getFieldRequiredErrors} from '@libs/ValidationUtils';
+import HoldReasonFormView from '@pages/iou/HoldReasonFormView';
+import {clearErrorFields, clearErrors} from '@userActions/FormActions';
+import {holdMoneyRequestOnMoneyRequestReport} from '@src/libs/actions/Search';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm';
+
+type SearchMoneyRequestReportHoldReasonPageRouteParams = {
+    /** Link to previous page */
+    backTo: Route;
+
+    reportID: string;
+};
+
+type SearchMoneyRequestReportHoldReasonPageProps = {
+    /** Navigation route context info provided by react navigation */
+    route: PlatformStackRouteProp<{params?: SearchMoneyRequestReportHoldReasonPageRouteParams}>;
+};
+
+function SearchMoneyRequestReportHoldReasonPage({route}: SearchMoneyRequestReportHoldReasonPageProps) {
+    const {translate} = useLocalize();
+
+    const {backTo = '', reportID = ''} = route.params ?? {};
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(reportID);
+
+    const onSubmit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM>) => {
+        holdMoneyRequestOnMoneyRequestReport(selectedTransactionsID, values.comment);
+        setSelectedTransactionsID([...selectedTransactionsID]); // It's needed so the actions in header are recalculated
+
+        Navigation.goBack();
+    };
+
+    const validate = useCallback(
+        (values: FormOnyxValues<typeof ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM>) => {
+            const errors: FormInputErrors<typeof ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM> = 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);
+    }, []);
path: src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx
user: login: allgandalf
body: won't there be any other dependency to clear this error?
created_at: 2025-04-01T16:50:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,69 @@
+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 Navigation from '@libs/Navigation/Navigation';
+import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
+import {getFieldRequiredErrors} from '@libs/ValidationUtils';
+import HoldReasonFormView from '@pages/iou/HoldReasonFormView';
+import {clearErrorFields, clearErrors} from '@userActions/FormActions';
+import {holdMoneyRequestOnMoneyRequestReport} from '@src/libs/actions/Search';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm';
+
+type SearchMoneyRequestReportHoldReasonPageRouteParams = {
+    /** Link to previous page */
+    backTo: Route;
+
+    reportID: string;
+};
+
+type SearchMoneyRequestReportHoldReasonPageProps = {
+    /** Navigation route context info provided by react navigation */
+    route: PlatformStackRouteProp<{params?: SearchMoneyRequestReportHoldReasonPageRouteParams}>;
+};
+
+function SearchMoneyRequestReportHoldReasonPage({route}: SearchMoneyRequestReportHoldReasonPageProps) {
+    const {translate} = useLocalize();
+
+    const {backTo = '', reportID = ''} = route.params ?? {};
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(reportID);
+
+    const onSubmit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM>) => {
+        holdMoneyRequestOnMoneyRequestReport(selectedTransactionsID, values.comment);
+        setSelectedTransactionsID([...selectedTransactionsID]); // It's needed so the actions in header are recalculated
path: src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx
user: login: allgandalf
body: Please add this comment on the top of the line
created_at: 2025-04-01T16:51:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,69 @@
+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 Navigation from '@libs/Navigation/Navigation';
+import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
+import {getFieldRequiredErrors} from '@libs/ValidationUtils';
+import HoldReasonFormView from '@pages/iou/HoldReasonFormView';
+import {clearErrorFields, clearErrors} from '@userActions/FormActions';
+import {holdMoneyRequestOnMoneyRequestReport} from '@src/libs/actions/Search';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm';
+
+type SearchMoneyRequestReportHoldReasonPageRouteParams = {
+    /** Link to previous page */
+    backTo: Route;
+
+    reportID: string;
+};
+
+type SearchMoneyRequestReportHoldReasonPageProps = {
+    /** Navigation route context info provided by react navigation */
+    route: PlatformStackRouteProp<{params?: SearchMoneyRequestReportHoldReasonPageRouteParams}>;
+};
path: src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx
user: login: allgandalf
body: I don't think this is how we extract values, can you check with other pages and path it, sorry if it already does
created_at: 2025-04-01T16:57:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,6 +270,10 @@ function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], com
     API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList, comment}, {optimisticData, finallyData});
 }
 
+function holdMoneyRequestOnMoneyRequestReport(transactionIDList: string[], comment: string) {
+    API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash: -1, transactionIDList, comment}, {});
path: src/libs/actions/Search.ts
user: login: allgandalf
body: what's this `hash: -1` for?
created_at: 2025-04-01T16:57:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -455,7 +456,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
     }, [currentUserPersonalDetails.status?.clearAfter]);
 
     return (
-        <ComposeProviders components={[OptionsListContextProvider, ActiveWorkspaceContextProvider, ReportIDsContextProvider, SearchContextProvider]}>
+        <ComposeProviders components={[OptionsListContextProvider, ActiveWorkspaceContextProvider, ReportIDsContextProvider, SearchContextProvider, MoneyRequestReportContextProvider]}>
path: src/libs/Navigation/AppNavigator/AuthScreens.tsx
user: login: allgandalf
body: can you explain this change please 
created_at: 2025-04-01T16:58:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -230,4 +269,4 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions,
 
 MoneyRequestReportTransactionList.displayName = 'MoneyRequestReportTransactionList';
 
-export default MoneyRequestReportTransactionList;
path: src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
user: login: allgandalf
body: why do we need to use `memo` here?
created_at: 2025-04-01T16:59:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -178,14 +214,17 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions,
                             style={[pressableStyle]}
                             onMouseLeave={handleMouseLeave}
                         >
-                            <TransactionItemRow
-                                transactionItem={transaction}
-                                isSelected={false}
-                                shouldShowTooltip
-                                dateColumnSize={dateColumnSize}
-                                shouldUseNarrowLayout={displayNarrowVersion}
-                                shouldShowChatBubbleComponent
-                            />
+                            <View style={[styles.mb2]}>
+                                <TransactionItemRow
+                                    transactionItem={transaction}
+                                    isSelected={false}
+                                    shouldShowTooltip
+                                    dateColumnSize={dateColumnSize}
+                                    shouldUseNarrowLayout={displayNarrowVersion}
+                                    shouldShowChatBubbleComponent
+                                    reportID={report.reportID}
path: src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
user: login: allgandalf
body: ```suggestion
                                    reportID={report?.reportID}
```

just to be extra safe, if there is no report ID that would mean a crash right?
created_at: 2025-04-01T16:59:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -178,14 +214,17 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions,
                             style={[pressableStyle]}
                             onMouseLeave={handleMouseLeave}
                         >
-                            <TransactionItemRow
-                                transactionItem={transaction}
-                                isSelected={false}
-                                shouldShowTooltip
-                                dateColumnSize={dateColumnSize}
-                                shouldUseNarrowLayout={displayNarrowVersion}
-                                shouldShowChatBubbleComponent
-                            />
+                            <View style={[styles.mb2]}>
+                                <TransactionItemRow
+                                    transactionItem={transaction}
+                                    isSelected={false}
path: src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
user: login: allgandalf
body: would this value always stay as false?
created_at: 2025-04-01T17:00:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -143,19 +164,34 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions,
     return !isEmpty(transactions) ? (
         <>
             {!displayNarrowVersion && (
-                <MoneyRequestReportTableHeader
-                    shouldShowSorting
-                    sortBy={sortBy}
-                    sortOrder={sortOrder}
-                    dateColumnSize={dateColumnSize}
-                    onSortPress={(selectedSortBy, selectedSortOrder) => {
-                        if (!isSortableColumnName(selectedSortBy)) {
-                            return;
-                        }
-
-                        setSortedData((prevState) => ({...prevState, sortBy: selectedSortBy, sortOrder: selectedSortOrder}));
-                    }}
-                />
+                <View style={[styles.dFlex, styles.flexRow, styles.ph5]}>
+                    <View style={[styles.p2, StyleUtils.getPaddingLeft(variables.w12)]}>
+                        <Checkbox
+                            onPress={() => {
+                                if (selectedTransactionsID.length === transactions.length) {
path: src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
user: login: allgandalf
body: ```suggestion
                                if (selectedTransactionsID?.length === transactions?.length) {
```

Safe assertions are better, if we have a reason to think otherwise 
created_at: 2025-04-01T17:00:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -74,7 +78,9 @@ const areTransactionValuesEqual = (transactions: OnyxTypes.Transaction[], key: S
 
 function MoneyRequestReportTransactionList({report, transactions, reportActions, hasComments}: MoneyRequestReportTransactionListProps) {
     const styles = useThemeStyles();
+    const StyleUtils = useStyleUtils();
     const {translate} = useLocalize();
+
path: src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
user: login: allgandalf
body: ```suggestion
```
created_at: 2025-04-01T17:01:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,92 @@
+import React, {useCallback, useContext, useMemo, useState} from 'react';
+import type ChildrenProps from '@src/types/utils/ChildrenProps';
+
+type TMoneyRequestReportContext = {
+    selectedTransactionsID: Record<string, string[]>;
+    setSelectedTransactionsID: (reportID: string) => (transactionsID: string[]) => void;
+    toggleTransaction: (reportID: string) => (transactionID: string) => void;
+    removeTransaction: (reportID: string) => (transactionID?: string) => void;
+    isTransactionSelected: (reportID: string) => (transactionID: string) => boolean;
+};
+
+const defaultMoneyRequestReportContext = {
+    selectedTransactionsID: {},
+    setSelectedTransactionsID: () => () => {},
+    toggleTransaction: () => () => {},
+    removeTransaction: () => () => {},
+    isTransactionSelected: () => () => false,
+};
+
+const Context = React.createContext<TMoneyRequestReportContext>(defaultMoneyRequestReportContext);
+
+// TODO merge it with SearchContext in follow-up - https://github.com/Expensify/App/issues/59431
+function MoneyRequestReportContextProvider({children}: ChildrenProps) {
+    const [selectedTransactionsForReport, setSelectedTransactionsForReport] = useState<Record<string, string[]>>({});
+
+    const setSelectedTransactionsID = useCallback(
+        (reportID: string) => (transactionsID: string[]) => {
+            setSelectedTransactionsForReport((prev) => ({...prev, [reportID]: transactionsID}));
+        },
+        [],
+    );
+
+    const toggleTransaction = useCallback(
+        (reportID: string) => (transactionID: string) => {
+            setSelectedTransactionsForReport((prev) => {
+                const prevTransactions = prev[reportID] ?? [];
+                if (prevTransactions.includes(transactionID)) {
+                    return {...prev, [reportID]: prevTransactions.filter((t) => t !== transactionID)};
+                }
+                return {...prev, [reportID]: [...prevTransactions, transactionID]};
+            });
+        },
+        [],
+    );
+
+    const removeTransaction = useCallback(
+        (reportID: string) => (transactionID?: string) => {
+            setSelectedTransactionsForReport((prev) => {
+                const prevTransactions = prev[reportID] ?? [];
+                return {...prev, [reportID]: prevTransactions.filter((t) => t !== transactionID)};
+            });
+        },
+        [],
+    );
+
+    const isTransactionSelected = useCallback(
+        (reportID: string) => (transactionID: string) => (selectedTransactionsForReport[reportID] ?? []).includes(transactionID),
+        [selectedTransactionsForReport],
+    );
+
+    const context = useMemo(
+        () => ({
+            selectedTransactionsID: selectedTransactionsForReport,
+            setSelectedTransactionsID,
+            toggleTransaction,
+            isTransactionSelected,
+            removeTransaction,
+        }),
+        [isTransactionSelected, removeTransaction, selectedTransactionsForReport, setSelectedTransactionsID, toggleTransaction],
+    );
+
+    return <Context.Provider value={context}>{children}</Context.Provider>;
+}
+
+function useMoneyRequestReportContext(reportID = '') {
path: src/components/MoneyRequestReportView/MoneyRequestReportContext.tsx
user: login: allgandalf
body: I feel we shouldn't initialize the value to empty string, let it be undefined please, this is what we follow in the app, @mountiny thoughts ?
created_at: 2025-04-01T17:02:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,92 @@
+import React, {useCallback, useContext, useMemo, useState} from 'react';
+import type ChildrenProps from '@src/types/utils/ChildrenProps';
+
+type TMoneyRequestReportContext = {
+    selectedTransactionsID: Record<string, string[]>;
+    setSelectedTransactionsID: (reportID: string) => (transactionsID: string[]) => void;
+    toggleTransaction: (reportID: string) => (transactionID: string) => void;
+    removeTransaction: (reportID: string) => (transactionID?: string) => void;
+    isTransactionSelected: (reportID: string) => (transactionID: string) => boolean;
+};
+
+const defaultMoneyRequestReportContext = {
+    selectedTransactionsID: {},
+    setSelectedTransactionsID: () => () => {},
+    toggleTransaction: () => () => {},
+    removeTransaction: () => () => {},
+    isTransactionSelected: () => () => false,
+};
+
+const Context = React.createContext<TMoneyRequestReportContext>(defaultMoneyRequestReportContext);
+
+// TODO merge it with SearchContext in follow-up - https://github.com/Expensify/App/issues/59431
path: src/components/MoneyRequestReportView/MoneyRequestReportContext.tsx
user: login: allgandalf
body: alright!!!
created_at: 2025-04-01T17:03:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -70,9 +71,13 @@ function getParentReportAction(parentReportActions: OnyxEntry<OnyxTypes.ReportAc
     return parentReportActions[parentReportActionID];
 }
 
-function selectTransactionsForReportID(transactions: OnyxCollection<OnyxTypes.Transaction>, reportID: string) {
+function selectTransactionsForReportID(transactions: OnyxCollection<OnyxTypes.Transaction>, reportID: string, reportActions: OnyxTypes.ReportAction[]) {
     return Object.values(transactions ?? {}).filter((transaction): transaction is Transaction => {
-        return transaction?.reportID === reportID;
+        if (!transaction) {
+            return false;
+        }
+        const action = getIOUActionForTransactionID(reportActions, transaction.transactionID);
+        return transaction.reportID === reportID && !isDeletedParentAction(action);
path: src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx
user: login: allgandalf
body: ```suggestion
        return transaction?.reportID === reportID && !isDeletedParentAction(action);
```

😸 just a habit, but let me know if you think otherwise 
created_at: 2025-04-01T17:04:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -181,6 +188,106 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false);
path: src/components/MoneyReportHeader.tsx
user: login: allgandalf
body: ```suggestion
    const [isdownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
```

And please update below too
created_at: 2025-04-01T17:05:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -181,6 +188,106 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false);
+
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(moneyRequestReport?.reportID);
+
+    const selectedTransactionsOptions = useMemo(() => {
+        if (!selectedTransactionsID.length) {
+            return [];
+        }
+        const options = [];
+        const selectedTransactions = selectedTransactionsID.map((transactionID) => getTransaction(transactionID)).filter((t) => !!t);
+
+        const anyTransactionOnHold = selectedTransactions.some(isOnHoldTransactionUtils);
+        const allTransactionOnHold = selectedTransactions.every(isOnHoldTransactionUtils);
+
+        if (!anyTransactionOnHold) {
+            options.push({
+                text: translate('iou.hold'),
+                icon: Expensicons.Stopwatch,
+                value: CONST.REPORT.SECONDARY_ACTIONS.HOLD,
+                onSelected: () => {
+                    if (!moneyRequestReport?.reportID) {
+                        return;
+                    }
+                    Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS.getRoute({reportID: moneyRequestReport.reportID}));
+                },
+            });
+        }
+
+        if (allTransactionOnHold) {
+            options.push({
+                text: translate('iou.unhold'),
+                icon: Expensicons.Stopwatch,
+                value: 'UNHOLD',
+                onSelected: () => {
+                    const iouActions = reportActions.filter((action) => isMoneyRequestAction(action));
+
+                    const selectedIOUActions = iouActions.filter((action) => {
+                        // I have no idea why this it's unsafe assignment
path: src/components/MoneyReportHeader.tsx
user: login: allgandalf
body: 😅 , what should we do about this one 😆 
created_at: 2025-04-01T17:06:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -181,6 +188,106 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false);
+
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(moneyRequestReport?.reportID);
+
+    const selectedTransactionsOptions = useMemo(() => {
+        if (!selectedTransactionsID.length) {
+            return [];
+        }
+        const options = [];
+        const selectedTransactions = selectedTransactionsID.map((transactionID) => getTransaction(transactionID)).filter((t) => !!t);
+
+        const anyTransactionOnHold = selectedTransactions.some(isOnHoldTransactionUtils);
+        const allTransactionOnHold = selectedTransactions.every(isOnHoldTransactionUtils);
+
+        if (!anyTransactionOnHold) {
+            options.push({
+                text: translate('iou.hold'),
+                icon: Expensicons.Stopwatch,
+                value: CONST.REPORT.SECONDARY_ACTIONS.HOLD,
+                onSelected: () => {
+                    if (!moneyRequestReport?.reportID) {
+                        return;
+                    }
+                    Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS.getRoute({reportID: moneyRequestReport.reportID}));
+                },
+            });
+        }
+
+        if (allTransactionOnHold) {
+            options.push({
+                text: translate('iou.unhold'),
+                icon: Expensicons.Stopwatch,
+                value: 'UNHOLD',
+                onSelected: () => {
+                    const iouActions = reportActions.filter((action) => isMoneyRequestAction(action));
+
+                    const selectedIOUActions = iouActions.filter((action) => {
+                        // I have no idea why this it's unsafe assignment
+                        const IOUTransactionID = (getOriginalMessage(action) as OnyxTypes.OriginalMessageIOU)?.IOUTransactionID;
+
+                        return IOUTransactionID && selectedTransactionsID.includes(IOUTransactionID);
+                    });
+                    selectedIOUActions.forEach((action) => changeMoneyRequestHoldStatus(action));
+                    setSelectedTransactionsID([...selectedTransactionsID]); // it's needed in order to recalculate options
path: src/components/MoneyReportHeader.tsx
user: login: allgandalf
body: please add comment above this line
created_at: 2025-04-01T17:06:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -181,6 +188,106 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false);
+
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(moneyRequestReport?.reportID);
+
+    const selectedTransactionsOptions = useMemo(() => {
+        if (!selectedTransactionsID.length) {
+            return [];
+        }
+        const options = [];
+        const selectedTransactions = selectedTransactionsID.map((transactionID) => getTransaction(transactionID)).filter((t) => !!t);
+
+        const anyTransactionOnHold = selectedTransactions.some(isOnHoldTransactionUtils);
+        const allTransactionOnHold = selectedTransactions.every(isOnHoldTransactionUtils);
+
+        if (!anyTransactionOnHold) {
+            options.push({
+                text: translate('iou.hold'),
+                icon: Expensicons.Stopwatch,
+                value: CONST.REPORT.SECONDARY_ACTIONS.HOLD,
+                onSelected: () => {
+                    if (!moneyRequestReport?.reportID) {
+                        return;
+                    }
+                    Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS.getRoute({reportID: moneyRequestReport.reportID}));
+                },
+            });
+        }
+
+        if (allTransactionOnHold) {
+            options.push({
+                text: translate('iou.unhold'),
+                icon: Expensicons.Stopwatch,
+                value: 'UNHOLD',
+                onSelected: () => {
+                    const iouActions = reportActions.filter((action) => isMoneyRequestAction(action));
+
+                    const selectedIOUActions = iouActions.filter((action) => {
+                        // I have no idea why this it's unsafe assignment
+                        const IOUTransactionID = (getOriginalMessage(action) as OnyxTypes.OriginalMessageIOU)?.IOUTransactionID;
+
+                        return IOUTransactionID && selectedTransactionsID.includes(IOUTransactionID);
+                    });
+                    selectedIOUActions.forEach((action) => changeMoneyRequestHoldStatus(action));
+                    setSelectedTransactionsID([...selectedTransactionsID]); // it's needed in order to recalculate options
+                },
+            });
+        }
+
+        options.push({
+            value: CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD,
+            text: translate('common.download'),
+            icon: Expensicons.Download,
+            onSelected: () => {
+                if (!moneyRequestReport) {
+                    return;
+                }
+                exportReportToCSV({reportID: moneyRequestReport.reportID, transactionIDList: selectedTransactionsID}, () => {
+                    setDownloadErrorModalVisible(true);
+                });
+            },
+        });
+
+        const canAllSelectedTransactionsBeRemoved = selectedTransactionsID.every((transactionID) => {
+            const canRemoveTransaction = canDeleteCardTransactionByLiabilityType(transactionID);
+            const action = getIOUActionForTransactionID(reportActions, transactionID);
+            const isActionDeleted = isDeletedAction(action);
+            const isIOUActionOwner = typeof action?.actorAccountID === 'number' && typeof session?.accountID === 'number' && action.actorAccountID === session?.accountID;
+
+            return canRemoveTransaction && isIOUActionOwner && !isActionDeleted;
+        });
+
+        const canRemoveReportTransaction = canDeleteTransaction(moneyRequestReport);
+
+        if (canRemoveReportTransaction && canAllSelectedTransactionsBeRemoved) {
+            options.push({
+                text: translate('common.delete'),
+                icon: Expensicons.Trashcan,
+                value: CONST.REPORT.SECONDARY_ACTIONS.DELETE,
+                onSelected: () => {
+                    const iouActions = reportActions.filter((action) => isMoneyRequestAction(action));
+
+                    const transactionsWithActions = selectedTransactions.map((t) => ({
+                        transactionID: t?.transactionID,
+                        action: iouActions.find((action) => {
+                            const IOUTransactionID = (getOriginalMessage(action) as OnyxTypes.OriginalMessageIOU)?.IOUTransactionID;
+
+                            return t?.transactionID === IOUTransactionID;
path: src/components/MoneyReportHeader.tsx
user: login: allgandalf
body: There you gooooo! `?` ::relieved:
created_at: 2025-04-01T17:07:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,69 @@
+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 Navigation from '@libs/Navigation/Navigation';
+import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
+import {getFieldRequiredErrors} from '@libs/ValidationUtils';
+import HoldReasonFormView from '@pages/iou/HoldReasonFormView';
+import {clearErrorFields, clearErrors} from '@userActions/FormActions';
+import {holdMoneyRequestOnMoneyRequestReport} from '@src/libs/actions/Search';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm';
+
+type SearchMoneyRequestReportHoldReasonPageRouteParams = {
+    /** Link to previous page */
+    backTo: Route;
+
+    reportID: string;
+};
+
+type SearchMoneyRequestReportHoldReasonPageProps = {
+    /** Navigation route context info provided by react navigation */
+    route: PlatformStackRouteProp<{params?: SearchMoneyRequestReportHoldReasonPageRouteParams}>;
+};
+
+function SearchMoneyRequestReportHoldReasonPage({route}: SearchMoneyRequestReportHoldReasonPageProps) {
+    const {translate} = useLocalize();
+
+    const {backTo = '', reportID = ''} = route.params ?? {};
path: src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx
user: login: mountiny
body: Yeah I think the suggestion is more common in the app
created_at: 2025-04-01T17:55:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,92 @@
+import React, {useCallback, useContext, useMemo, useState} from 'react';
+import type ChildrenProps from '@src/types/utils/ChildrenProps';
+
+type TMoneyRequestReportContext = {
+    selectedTransactionsID: Record<string, string[]>;
+    setSelectedTransactionsID: (reportID: string) => (transactionsID: string[]) => void;
+    toggleTransaction: (reportID: string) => (transactionID: string) => void;
+    removeTransaction: (reportID: string) => (transactionID?: string) => void;
+    isTransactionSelected: (reportID: string) => (transactionID: string) => boolean;
+};
+
+const defaultMoneyRequestReportContext = {
+    selectedTransactionsID: {},
+    setSelectedTransactionsID: () => () => {},
+    toggleTransaction: () => () => {},
+    removeTransaction: () => () => {},
+    isTransactionSelected: () => () => false,
+};
+
+const Context = React.createContext<TMoneyRequestReportContext>(defaultMoneyRequestReportContext);
+
+// TODO merge it with SearchContext in follow-up - https://github.com/Expensify/App/issues/59431
+function MoneyRequestReportContextProvider({children}: ChildrenProps) {
+    const [selectedTransactionsForReport, setSelectedTransactionsForReport] = useState<Record<string, string[]>>({});
+
+    const setSelectedTransactionsID = useCallback(
+        (reportID: string) => (transactionsID: string[]) => {
+            setSelectedTransactionsForReport((prev) => ({...prev, [reportID]: transactionsID}));
+        },
+        [],
+    );
+
+    const toggleTransaction = useCallback(
+        (reportID: string) => (transactionID: string) => {
+            setSelectedTransactionsForReport((prev) => {
+                const prevTransactions = prev[reportID] ?? [];
+                if (prevTransactions.includes(transactionID)) {
+                    return {...prev, [reportID]: prevTransactions.filter((t) => t !== transactionID)};
+                }
+                return {...prev, [reportID]: [...prevTransactions, transactionID]};
+            });
+        },
+        [],
+    );
+
+    const removeTransaction = useCallback(
+        (reportID: string) => (transactionID?: string) => {
+            setSelectedTransactionsForReport((prev) => {
+                const prevTransactions = prev[reportID] ?? [];
+                return {...prev, [reportID]: prevTransactions.filter((t) => t !== transactionID)};
+            });
+        },
+        [],
+    );
+
+    const isTransactionSelected = useCallback(
+        (reportID: string) => (transactionID: string) => (selectedTransactionsForReport[reportID] ?? []).includes(transactionID),
+        [selectedTransactionsForReport],
+    );
+
+    const context = useMemo(
+        () => ({
+            selectedTransactionsID: selectedTransactionsForReport,
+            setSelectedTransactionsID,
+            toggleTransaction,
+            isTransactionSelected,
+            removeTransaction,
+        }),
+        [isTransactionSelected, removeTransaction, selectedTransactionsForReport, setSelectedTransactionsID, toggleTransaction],
+    );
+
+    return <Context.Provider value={context}>{children}</Context.Provider>;
+}
+
+function useMoneyRequestReportContext(reportID = '') {
path: src/components/MoneyRequestReportView/MoneyRequestReportContext.tsx
user: login: mountiny
body: yeah undefined is good
created_at: 2025-04-01T17:56:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -181,6 +188,106 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false);
path: src/components/MoneyReportHeader.tsx
user: login: mountiny
body: isDownloadErrorModalVisible
created_at: 2025-04-01T17:57:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -181,6 +188,106 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false);
+
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(moneyRequestReport?.reportID);
+
+    const selectedTransactionsOptions = useMemo(() => {
+        if (!selectedTransactionsID.length) {
+            return [];
+        }
+        const options = [];
+        const selectedTransactions = selectedTransactionsID.map((transactionID) => getTransaction(transactionID)).filter((t) => !!t);
+
+        const anyTransactionOnHold = selectedTransactions.some(isOnHoldTransactionUtils);
+        const allTransactionOnHold = selectedTransactions.every(isOnHoldTransactionUtils);
+
+        if (!anyTransactionOnHold) {
+            options.push({
+                text: translate('iou.hold'),
+                icon: Expensicons.Stopwatch,
+                value: CONST.REPORT.SECONDARY_ACTIONS.HOLD,
+                onSelected: () => {
+                    if (!moneyRequestReport?.reportID) {
+                        return;
+                    }
+                    Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS.getRoute({reportID: moneyRequestReport.reportID}));
+                },
+            });
+        }
+
+        if (allTransactionOnHold) {
+            options.push({
+                text: translate('iou.unhold'),
+                icon: Expensicons.Stopwatch,
+                value: 'UNHOLD',
+                onSelected: () => {
+                    const iouActions = reportActions.filter((action) => isMoneyRequestAction(action));
+
+                    const selectedIOUActions = iouActions.filter((action) => {
+                        // I have no idea why this it's unsafe assignment
path: src/components/MoneyReportHeader.tsx
user: login: mountiny
body: Yeah let's not leave comments like this haha
created_at: 2025-04-01T17:57:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -181,6 +188,106 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false);
+
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(moneyRequestReport?.reportID);
+
+    const selectedTransactionsOptions = useMemo(() => {
+        if (!selectedTransactionsID.length) {
+            return [];
+        }
+        const options = [];
+        const selectedTransactions = selectedTransactionsID.map((transactionID) => getTransaction(transactionID)).filter((t) => !!t);
+
+        const anyTransactionOnHold = selectedTransactions.some(isOnHoldTransactionUtils);
+        const allTransactionOnHold = selectedTransactions.every(isOnHoldTransactionUtils);
+
+        if (!anyTransactionOnHold) {
+            options.push({
+                text: translate('iou.hold'),
+                icon: Expensicons.Stopwatch,
+                value: CONST.REPORT.SECONDARY_ACTIONS.HOLD,
+                onSelected: () => {
+                    if (!moneyRequestReport?.reportID) {
+                        return;
+                    }
+                    Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS.getRoute({reportID: moneyRequestReport.reportID}));
+                },
+            });
+        }
+
+        if (allTransactionOnHold) {
+            options.push({
+                text: translate('iou.unhold'),
+                icon: Expensicons.Stopwatch,
+                value: 'UNHOLD',
+                onSelected: () => {
+                    const iouActions = reportActions.filter((action) => isMoneyRequestAction(action));
+
+                    const selectedIOUActions = iouActions.filter((action) => {
+                        // I have no idea why this it's unsafe assignment
path: src/components/MoneyReportHeader.tsx
user: login: jnowakow
body: Sorry, should be added on github 😅 

created_at: 2025-04-02T10:22:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -70,9 +71,13 @@ function getParentReportAction(parentReportActions: OnyxEntry<OnyxTypes.ReportAc
     return parentReportActions[parentReportActionID];
 }
 
-function selectTransactionsForReportID(transactions: OnyxCollection<OnyxTypes.Transaction>, reportID: string) {
+function selectTransactionsForReportID(transactions: OnyxCollection<OnyxTypes.Transaction>, reportID: string, reportActions: OnyxTypes.ReportAction[]) {
     return Object.values(transactions ?? {}).filter((transaction): transaction is Transaction => {
-        return transaction?.reportID === reportID;
+        if (!transaction) {
+            return false;
+        }
+        const action = getIOUActionForTransactionID(reportActions, transaction.transactionID);
+        return transaction.reportID === reportID && !isDeletedParentAction(action);
path: src/components/MoneyRequestReportView/MoneyRequestReportActionsList.tsx
user: login: jnowakow
body: At the beginning of the function there is early return if transaction is not defined 
created_at: 2025-04-02T10:28:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -178,14 +214,17 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions,
                             style={[pressableStyle]}
                             onMouseLeave={handleMouseLeave}
                         >
-                            <TransactionItemRow
-                                transactionItem={transaction}
-                                isSelected={false}
-                                shouldShowTooltip
-                                dateColumnSize={dateColumnSize}
-                                shouldUseNarrowLayout={displayNarrowVersion}
-                                shouldShowChatBubbleComponent
-                            />
+                            <View style={[styles.mb2]}>
+                                <TransactionItemRow
+                                    transactionItem={transaction}
+                                    isSelected={false}
path: src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
user: login: jnowakow
body: Nope, I forgot to update it 
created_at: 2025-04-02T10:37:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -455,7 +456,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
     }, [currentUserPersonalDetails.status?.clearAfter]);
 
     return (
-        <ComposeProviders components={[OptionsListContextProvider, ActiveWorkspaceContextProvider, ReportIDsContextProvider, SearchContextProvider]}>
+        <ComposeProviders components={[OptionsListContextProvider, ActiveWorkspaceContextProvider, ReportIDsContextProvider, SearchContextProvider, MoneyRequestReportContextProvider]}>
path: src/libs/Navigation/AppNavigator/AuthScreens.tsx
user: login: jnowakow
body: I've created new context so I have to add Provider somewhere to tree. I decided it's right place to do so
created_at: 2025-04-02T10:39:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,6 +270,10 @@ function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], com
     API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList, comment}, {optimisticData, finallyData});
 }
 
+function holdMoneyRequestOnMoneyRequestReport(transactionIDList: string[], comment: string) {
+    API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash: -1, transactionIDList, comment}, {});
path: src/libs/actions/Search.ts
user: login: jnowakow
body: I had two options here. Iterate over all selected transactions and make a request for each one. I decided to reuse Search's API as it allows to hold/unhold multiple transactions at once. But it requires `hash` param which I don't have here. So I hardcoded `-1` so it doesn't interfere with existing hashes. From frontend perspective it seems to work fine. @mountiny are you able to confirm it doesn't break anything on backend? 
created_at: 2025-04-02T10:43:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,69 @@
+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 Navigation from '@libs/Navigation/Navigation';
+import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types';
+import {getFieldRequiredErrors} from '@libs/ValidationUtils';
+import HoldReasonFormView from '@pages/iou/HoldReasonFormView';
+import {clearErrorFields, clearErrors} from '@userActions/FormActions';
+import {holdMoneyRequestOnMoneyRequestReport} from '@src/libs/actions/Search';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm';
+
+type SearchMoneyRequestReportHoldReasonPageRouteParams = {
+    /** Link to previous page */
+    backTo: Route;
+
+    reportID: string;
+};
+
+type SearchMoneyRequestReportHoldReasonPageProps = {
+    /** Navigation route context info provided by react navigation */
+    route: PlatformStackRouteProp<{params?: SearchMoneyRequestReportHoldReasonPageRouteParams}>;
+};
+
+function SearchMoneyRequestReportHoldReasonPage({route}: SearchMoneyRequestReportHoldReasonPageProps) {
+    const {translate} = useLocalize();
+
+    const {backTo = '', reportID = ''} = route.params ?? {};
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(reportID);
+
+    const onSubmit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM>) => {
+        holdMoneyRequestOnMoneyRequestReport(selectedTransactionsID, values.comment);
+        setSelectedTransactionsID([...selectedTransactionsID]); // It's needed so the actions in header are recalculated
+
+        Navigation.goBack();
+    };
+
+    const validate = useCallback(
+        (values: FormOnyxValues<typeof ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM>) => {
+            const errors: FormInputErrors<typeof ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM> = 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);
+    }, []);
path: src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx
user: login: jnowakow
body: We want to clear them only when leaving page that page. It's handled that way on `HoldReasonPage` as well. 
created_at: 2025-04-02T11:14:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -230,4 +269,4 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions,
 
 MoneyRequestReportTransactionList.displayName = 'MoneyRequestReportTransactionList';
 
-export default MoneyRequestReportTransactionList;
path: src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
user: login: jnowakow
body: The logic behind sorting is quite complicated and as it's done in `useEffect` we want to minimise numer of unnecessary re-renders of this component. 
created_at: 2025-04-02T11:17:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -143,19 +164,34 @@ function MoneyRequestReportTransactionList({report, transactions, reportActions,
     return !isEmpty(transactions) ? (
         <>
             {!displayNarrowVersion && (
-                <MoneyRequestReportTableHeader
-                    shouldShowSorting
-                    sortBy={sortBy}
-                    sortOrder={sortOrder}
-                    dateColumnSize={dateColumnSize}
-                    onSortPress={(selectedSortBy, selectedSortOrder) => {
-                        if (!isSortableColumnName(selectedSortBy)) {
-                            return;
-                        }
-
-                        setSortedData((prevState) => ({...prevState, sortBy: selectedSortBy, sortOrder: selectedSortOrder}));
-                    }}
-                />
+                <View style={[styles.dFlex, styles.flexRow, styles.ph5]}>
+                    <View style={[styles.p2, StyleUtils.getPaddingLeft(variables.w12)]}>
+                        <Checkbox
+                            onPress={() => {
+                                if (selectedTransactionsID.length === transactions.length) {
path: src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx
user: login: jnowakow
body: Both `selectedTransactionsID` and `transactions` are arrays so they will always have property `length`
created_at: 2025-04-02T11:19:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -355,6 +364,13 @@ function unholdMoneyRequestOnSearch(hash: number, transactionIDList: string[]) {
     API.write(WRITE_COMMANDS.UNHOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList}, {optimisticData, finallyData});
 }
 
+function unholdMoneyRequestOnMoneyRequestReport(transactionIDList: string[], reportActions: ReportAction[]) {
+    transactionIDList.forEach((transactionID) => {
+        const action = getIOUActionForTransactionID(reportActions, transactionID);
+        unholdRequest(transactionID, action?.childReportID ?? '');
+    });
path: src/libs/actions/Search.ts
user: login: jnowakow
body: I had to change to this for loop because with `Search` specific hold/unhold wasn't reflected in list
created_at: 2025-04-03T10:23:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,6 +270,10 @@ function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], com
     API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList, comment}, {optimisticData, finallyData});
 }
 
+function holdMoneyRequestOnMoneyRequestReport(transactionIDList: string[], comment: string) {
+    API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash: -1, transactionIDList, comment}, {});
path: src/libs/actions/Search.ts
user: login: luacmartins
body: Hmm I don't think we should do this, the *Search commands are specific to search and may not send the response we need elsewhere in App. I think in this case we might want to introduce a new BE API command to process a batch hold
created_at: 2025-04-03T17:21:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,6 +270,10 @@ function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], com
     API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList, comment}, {optimisticData, finallyData});
 }
 
+function holdMoneyRequestOnMoneyRequestReport(transactionIDList: string[], comment: string) {
+    API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash: -1, transactionIDList, comment}, {});
path: src/libs/actions/Search.ts
user: login: jnowakow
body: I changed that because it caused other problems with transactions not being refreshed. Now I'm iterating over transactions and for each one I'm sending request so introducing API to handle batch hold/unhold would be best solution
created_at: 2025-04-04T09:30:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,6 +270,10 @@ function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], com
     API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList, comment}, {optimisticData, finallyData});
 }
 
+function holdMoneyRequestOnMoneyRequestReport(transactionIDList: string[], comment: string) {
+    API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash: -1, transactionIDList, comment}, {});
path: src/libs/actions/Search.ts
user: login: allgandalf
body: @jnowakow can you please read this slack convo, we came to conclusion that we will create a New API request, can you remove this action for now and put a comment saying we will put this up later, context here:

https://expensify.slack.com/archives/C07NMDKEFMH/p1743701465577059?thread_ts=1743532385.835069&cid=C07NMDKEFMH

c.c. @mountiny 
created_at: 2025-04-04T10:34:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,6 +270,10 @@ function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], com
     API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList, comment}, {optimisticData, finallyData});
 }
 
+function holdMoneyRequestOnMoneyRequestReport(transactionIDList: string[], comment: string) {
+    API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash: -1, transactionIDList, comment}, {});
path: src/libs/actions/Search.ts
user: login: jnowakow
body: Isn't it better to keep version using old api and improve performance when new API is in place?
created_at: 2025-04-04T10:45:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -270,6 +270,10 @@ function holdMoneyRequestOnSearch(hash: number, transactionIDList: string[], com
     API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash, transactionIDList, comment}, {optimisticData, finallyData});
 }
 
+function holdMoneyRequestOnMoneyRequestReport(transactionIDList: string[], comment: string) {
+    API.write(WRITE_COMMANDS.HOLD_MONEY_REQUEST_ON_SEARCH, {hash: -1, transactionIDList, comment}, {});
path: src/libs/actions/Search.ts
user: login: allgandalf
body: Lets take this discussion to slack
created_at: 2025-04-04T10:47:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+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<SearchReportParamList, typeof SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP>) {
+    const {translate} = useLocalize();
+
+    const {backTo, reportID} = route.params;
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(reportID);
+
+    const onSubmit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM>) => {
+        const firstTransactionID = selectedTransactionsID.at(0);
+        if (!firstTransactionID) {
+            return;
+        }
+
+        putOnHold(firstTransactionID, values.comment, reportID);
+        // It's needed so the actions in header are recalculated
+        setSelectedTransactionsID([...selectedTransactionsID]);
+
+        Navigation.goBack();
path: src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx
user: login: mountiny
body: ```suggestion

        // We need to do this so the actions in header are correctly updated
        setSelectedTransactionsID([...selectedTransactionsID]);
        Navigation.goBack();
```
created_at: 2025-04-07T16:20:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -182,6 +189,105 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
+
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(moneyRequestReport?.reportID);
+
+    const selectedTransactionsOptions = useMemo(() => {
+        if (!selectedTransactionsID.length) {
+            return [];
+        }
+        const options = [];
+        const selectedTransactions = selectedTransactionsID.map((transactionID) => getTransaction(transactionID)).filter((t) => !!t);
+
+        const anyTransactionOnHold = selectedTransactions.some(isOnHoldTransactionUtils);
+        const allTransactionOnHold = selectedTransactions.every(isOnHoldTransactionUtils);
+
+        if (!anyTransactionOnHold && selectedTransactions.length === 1) {
path: src/components/MoneyReportHeader.tsx
user: login: allroundexperts
body: Why was not the hold option shown when bulk selecting? Adding this line caused https://github.com/Expensify/App/issues/60111
created_at: 2025-05-14T22:21:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -182,6 +189,105 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
+
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(moneyRequestReport?.reportID);
+
+    const selectedTransactionsOptions = useMemo(() => {
+        if (!selectedTransactionsID.length) {
+            return [];
+        }
+        const options = [];
+        const selectedTransactions = selectedTransactionsID.map((transactionID) => getTransaction(transactionID)).filter((t) => !!t);
+
+        const anyTransactionOnHold = selectedTransactions.some(isOnHoldTransactionUtils);
+        const allTransactionOnHold = selectedTransactions.every(isOnHoldTransactionUtils);
+
+        if (!anyTransactionOnHold && selectedTransactions.length === 1) {
path: src/components/MoneyReportHeader.tsx
user: login: jnowakow
body: I can't find conversation on slack because it was discussed in long thread but it was agreed that we don't want to iterate over selected issues and send many requests. The go to solution was to temporarily disable that option if many transactions are selected and add it when there's api to hold many transaction in one request.
created_at: 2025-05-15T13:36:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -182,6 +189,105 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
+
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(moneyRequestReport?.reportID);
+
+    const selectedTransactionsOptions = useMemo(() => {
+        if (!selectedTransactionsID.length) {
+            return [];
+        }
+        const options = [];
+        const selectedTransactions = selectedTransactionsID.map((transactionID) => getTransaction(transactionID)).filter((t) => !!t);
+
+        const anyTransactionOnHold = selectedTransactions.some(isOnHoldTransactionUtils);
+        const allTransactionOnHold = selectedTransactions.every(isOnHoldTransactionUtils);
+
+        if (!anyTransactionOnHold && selectedTransactions.length === 1) {
path: src/components/MoneyReportHeader.tsx
user: login: luacmartins
body: We have a `BulkHoldRequest` API in place, so we can remove this limitation and start working on a fix for that.
created_at: 2025-05-15T15:43:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -182,6 +189,105 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
+
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(moneyRequestReport?.reportID);
+
+    const selectedTransactionsOptions = useMemo(() => {
+        if (!selectedTransactionsID.length) {
+            return [];
+        }
+        const options = [];
+        const selectedTransactions = selectedTransactionsID.map((transactionID) => getTransaction(transactionID)).filter((t) => !!t);
+
+        const anyTransactionOnHold = selectedTransactions.some(isOnHoldTransactionUtils);
+        const allTransactionOnHold = selectedTransactions.every(isOnHoldTransactionUtils);
+
+        if (!anyTransactionOnHold && selectedTransactions.length === 1) {
path: src/components/MoneyReportHeader.tsx
user: login: jnowakow
body: @luacmartins great! Is there issue for it? I can replace it 
created_at: 2025-05-16T07:06:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -182,6 +189,105 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         [moneyRequestReport, chatReport, policy, transaction],
     );
 
+    const [isDownloadErrorModalVisible, setIsDownloadErrorModalVisible] = useState(false);
+
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(moneyRequestReport?.reportID);
+
+    const selectedTransactionsOptions = useMemo(() => {
+        if (!selectedTransactionsID.length) {
+            return [];
+        }
+        const options = [];
+        const selectedTransactions = selectedTransactionsID.map((transactionID) => getTransaction(transactionID)).filter((t) => !!t);
+
+        const anyTransactionOnHold = selectedTransactions.some(isOnHoldTransactionUtils);
+        const allTransactionOnHold = selectedTransactions.every(isOnHoldTransactionUtils);
+
+        if (!anyTransactionOnHold && selectedTransactions.length === 1) {
path: src/components/MoneyReportHeader.tsx
user: login: luacmartins
body: I don't think we have an issue yet, but I think @Tony-MK might be working on that
created_at: 2025-05-16T20:32:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,64 @@
+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<SearchReportParamList, typeof SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP>) {
+    const {translate} = useLocalize();
+
+    const {backTo, reportID} = route.params;
+    const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext(reportID);
+
+    const onSubmit = (values: FormOnyxValues<typeof ONYXKEYS.FORMS.MONEY_REQUEST_HOLD_FORM>) => {
+        const firstTransactionID = selectedTransactionsID.at(0);
+        if (!firstTransactionID) {
+            return;
+        }
+
+        putOnHold(firstTransactionID, values.comment, reportID);
path: src/pages/Search/SearchMoneyRequestReportHoldReasonPage.tsx
user: login: hoangzinh
body: `putOnHold` expects the report id of the transaction thread id (not the expense report) so we should pass the transaction thread report id here. More details [here](https://github.com/Expensify/App/issues/61522#issuecomment-2855981734)
created_at: 2025-06-10T22:35:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,40 +1,41 @@
 import React, {useMemo} from 'react';
 import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
 import Icon from '@components/Icon';
 import {ChatBubbleCounter} from '@components/Icon/Expensicons';
 import Text from '@components/Text';
 import useResponsiveLayout from '@hooks/useResponsiveLayout';
 import useStyleUtils from '@hooks/useStyleUtils';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
-import {getIOUActionForReportID} from '@libs/ReportActionsUtils';
-import {getReportOrDraftReport, isChatThread} from '@libs/ReportUtils';
+import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils';
+import {isChatThread} from '@libs/ReportUtils';
 import variables from '@styles/variables';
+import ONYXKEYS from '@src/ONYXKEYS';
 import type {Report} from '@src/types/onyx';
 import type Transaction from '@src/types/onyx/Transaction';
 
 const isReportUnread = ({lastReadTime = '', lastVisibleActionCreated = '', lastMentionedTime = ''}: Report): boolean =>
     lastReadTime < lastVisibleActionCreated || lastReadTime < (lastMentionedTime ?? '');
 
-/*
- * Child is retrieved because we want to get the unread status & messages count of the expense, not the report itself.
- * We can get the child's message count from the report, but we cannot get the child's unread info from there.
- */
-function getTransactionMessagesCountAndUnreadInfo(transaction: Transaction) {
-    const iouReportAction = getIOUActionForReportID(transaction.reportID, transaction.transactionID);
-    const childReport = iouReportAction ? getReportOrDraftReport(iouReportAction.childReportID) : undefined;
-
-    return {
-        count: (iouReportAction && iouReportAction.childVisibleActionCount) ?? 0,
-        isUnread: isChatThread(childReport) && isReportUnread(childReport),
-    };
-}
-
 function ChatBubbleCell({transaction}: {transaction: Transaction}) {
     const theme = useTheme();
     const styles = useThemeStyles();
     const {shouldUseNarrowLayout} = useResponsiveLayout();
-    const threadMessages = useMemo(() => getTransactionMessagesCountAndUnreadInfo(transaction), [transaction]);
+    const [iouReportAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction.reportID}`, {
+        selector: (reportActions) => getIOUActionForTransactionID(Object.values(reportActions ?? {}), transaction.transactionID),
+    });
+
+    const [transactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportAction?.childReportID}`);
path: src/components/TransactionItemRow/DataCells/ChatBubbleCell.tsx
user: login: DylanDylann
body: It caused this bug https://github.com/Expensify/App/issues/62579

>  we didn’t distinguish between the regular view and the single transaction view. The single transaction view displays not only messages from the thread but also from the entire report, which is why the timestamp of the last read report action was missing. 
created_at: 2025-06-22T04:39:21Z
author_association: CONTRIBUTOR

PR comments:
user: login: melvin-bot[bot]
created_at: 2025-04-01T10:32:21Z
body: @DylanDylann Please copy/paste the Reviewer Checklist from [here](https://raw.githubusercontent.com/Expensify/App/main/contributingGuides/REVIEWER_CHECKLIST.md) into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-01T15:03:31Z
body: 🚧 @mountiny has triggered a test app build. You can view the [workflow run here](https://github.com/Expensify/App/actions/runs/14199111772).
author_association: CONTRIBUTOR
user: login: allgandalf
created_at: 2025-04-01T15:43:47Z
body: @jnowakow please add test steps

author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-01T15:45:44Z
body: :test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! :test_tube::test_tube:
| Android :robot:  | iOS :apple: |
| ------------- | ------------- |
| Android :robot::arrows_counterclockwise:  | iOS :apple::arrows_counterclockwise: |
| https://ad-hoc-expensify-cash.s3.amazonaws.com/android/59433-hybrid/index.html  | https://ad-hoc-expensify-cash.s3.amazonaws.com/ios/59433-hybrid/index.html  |
| ![Android](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/android/59433-hybrid/index.html)  | ![iOS](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/ios/59433-hybrid/index.html)  |
| Desktop :computer: | Web :spider_web: |
| https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/59433/NewExpensify.dmg  | https://59433.pr-testing.expensify.com  |
| ![Desktop](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/59433/NewExpensify.dmg)  | ![Web](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://59433.pr-testing.expensify.com)  |

---

:eyes: [View the workflow run that generated this build](https://github.com/Expensify/App/actions/runs/14199111772) :eyes:

author_association: CONTRIBUTOR
user: login: shawnborton
created_at: 2025-04-01T19:05:56Z
body: I think the gap between checkbox and receipt icon is too large:
![CleanShot 2025-04-01 at 21 04 50@2x](https://github.com/user-attachments/assets/3cf3bffb-a44c-4f4e-bbc2-5f83fe0e7443)

This is what the reports page looks like for reference:
![CleanShot 2025-04-01 at 21 05 23@2x](https://github.com/user-attachments/assets/df604a39-02df-48db-a0e5-303d7904eb97)

Is there any way to harmonize styles between these components? My fear is that we need to maintain two separate row components that basically need to look exactly the same. 
author_association: CONTRIBUTOR
user: login: shawnborton
created_at: 2025-04-01T19:06:52Z
body: The gaps between rows is now too large:
![CleanShot 2025-04-01 at 21 06 05@2x](https://github.com/user-attachments/assets/823b10f4-903d-4f32-8748-f56f3b40f2a1)

From staging, I see this which is correct:
![CleanShot 2025-04-01 at 21 06 38@2x](https://github.com/user-attachments/assets/c05aba6a-ec31-434e-a4e9-ba04233403e5)

author_association: CONTRIBUTOR
user: login: shawnborton
created_at: 2025-04-01T19:07:36Z
body: The table header is too close to the top of the view as well:
![CleanShot 2025-04-01 at 21 06 59@2x](https://github.com/user-attachments/assets/06d2fb8c-edd3-4b78-acd4-1f5323206de0)


When is the last time we merged in main? Maybe these changes are available and we just need to merge them in? 
author_association: CONTRIBUTOR
user: login: Kicu
created_at: 2025-04-02T09:25:53Z
body: @jnowakow a reminder so that we do not forget to fix the receipt default background color.
https://github.com/Expensify/App/pull/58903#pullrequestreview-2729624984
author_association: CONTRIBUTOR
user: login: mountiny
created_at: 2025-04-03T11:49:16Z
body: @DylanDylann are you able to review this one too today
author_association: CONTRIBUTOR
user: login: allgandalf
created_at: 2025-04-04T06:46:51Z
body: @jnowakow please checkout this conversation https://expensify.slack.com/archives/C07NMDKEFMH/p1743700645488649?thread_ts=1743532385.835069&cid=C07NMDKEFMH
author_association: CONTRIBUTOR
user: login: DylanDylann
created_at: 2025-04-04T08:49:53Z
body: @mountiny could you please remove my assignment here? @allgandalf will take care this one
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-04T11:12:42Z
body: 🚧 @mountiny has triggered a test app build. You can view the [workflow run here](https://github.com/Expensify/App/actions/runs/14264081834).
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-04T11:51:15Z
body: :test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! :test_tube::test_tube:
| Android :robot:  | iOS :apple: |
| ------------- | ------------- |
| Android :robot::arrows_counterclockwise:  | iOS :apple::arrows_counterclockwise: |
| https://ad-hoc-expensify-cash.s3.amazonaws.com/android/59433-hybrid/index.html  | https://ad-hoc-expensify-cash.s3.amazonaws.com/ios/59433-hybrid/index.html  |
| ![Android](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/android/59433-hybrid/index.html)  | ![iOS](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/ios/59433-hybrid/index.html)  |
| Desktop :computer: | Web :spider_web: |
| https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/59433/NewExpensify.dmg  | https://59433.pr-testing.expensify.com  |
| ![Desktop](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/59433/NewExpensify.dmg)  | ![Web](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://59433.pr-testing.expensify.com)  |

---

:eyes: [View the workflow run that generated this build](https://github.com/Expensify/App/actions/runs/14264081834) :eyes:

author_association: CONTRIBUTOR
user: login: allgandalf
created_at: 2025-04-04T15:28:15Z
body: ## Reviewer Checklist

- [x] I have verified the author checklist is complete (all boxes are checked off).
- [x] I verified the correct issue is linked in the `### Fixed Issues` section above
- [x] I verified testing steps are clear and they cover the changes made in this PR
    - [x] I verified the steps for local testing are in the `Tests` section
    - [x] I verified the steps for Staging and/or Production testing are in the `QA steps` section
    - [x] I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    - [x] I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
- [x] I checked that screenshots or videos are included for tests on [all platforms](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#make-sure-you-can-test-on-all-platforms)
- [x] I included screenshots or videos for tests on [all platforms](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#make-sure-you-can-test-on-all-platforms)
- [x] I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
- [x] I verified tests pass on **all platforms** & I tested again on:
    - [x] Android: Native
    - [x] Android: mWeb Chrome
    - [x] iOS: Native
    - [x] iOS: mWeb Safari
    - [x] MacOS: Chrome / Safari
    - [x] MacOS: Desktop
- [x] If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
- [x] I verified proper code patterns were followed (see [Reviewing the code](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md#reviewing-the-code))
    - [x] I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. `toggleReport` and not `onIconClick`).
    - [x] I verified that comments were added to code that is not self explanatory
    - [x] I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    - [x] I verified any copy / text shown in the product is localized by adding it to `src/languages/*` files and using the [translation method](https://github.com/Expensify/App/blob/4bd99402cebdf4d7394e0d1f260879ea238197eb/src/components/withLocalize.js#L60)
    - [x] I verified all numbers, amounts, dates and phone numbers shown in the product are using the [localization methods](https://github.com/Expensify/App/blob/4bd99402cebdf4d7394e0d1f260879ea238197eb/src/components/withLocalize.js#L60-L68)
    - [x] I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    - [x] I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    - [x] I verified the JSDocs style guidelines (in [`STYLE.md`](https://github.com/Expensify/App/blob/main/contributingGuides/STYLE.md#jsdocs)) were followed
- [x] If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
- [x] I verified that this PR follows the guidelines as stated in the [Review Guidelines](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md)
- [x] I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like `Avatar`, I verified the components using `Avatar` have been tested & I retested again)
- [x] I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
- [x] I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
- [x] If a new component is created I verified that:
    - [x] A similar component doesn't exist in the codebase
    - [x] All props are defined accurately and each prop has a `/** comment above it */`
    - [x] The file is named correctly
    - [x] The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    - [x] The only data being stored in the state is data necessary for rendering and nothing else
    - [x] For Class Components, any internal methods passed to components event handlers are bound to `this` properly so there are no scoping issues (i.e. for `onClick={this.submit}` the method `this.submit` should be bound to `this` in the constructor)
    - [x] Any internal methods bound to `this` are necessary to be bound (i.e. avoid `this.submit = this.submit.bind(this);` if `this.submit` is never passed to a component event handler like `onClick`)
    - [x] All JSX used for rendering exists in the render method
    - [x] The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
- [x] If any new file was added I verified that:
    - [x] The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
- [x] If a new CSS style is added I verified that:
    - [x] A similar style doesn't already exist
    - [x] The style can't be created with an existing [StyleUtils](https://github.com/Expensify/App/blob/main/src/utils/index.ts) function (i.e. `StyleUtils.getBackgroundAndBorderStyle(theme.componentBG`)
- [x] If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
- [x] If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like `Avatar` is modified, I verified that `Avatar` is working as expected in all cases)
- [x] If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
- [x] If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
- [x] If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    - [x] I verified that all the inputs inside a form are aligned with each other.
    - [x] I added `Design` label and/or tagged `@Expensify/design` so the design team can review the changes.
- [x] If a new page is added, I verified it's using the `ScrollView` component to make it scrollable when more elements are added to the page.
- [x] For any bug fix or new feature in this PR, I verified that sufficient [unit tests](https://github.com/Expensify/App/blob/main/tests/README.md) are included to prevent regressions in this flow.
- [x] If the `main` branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the `Test` steps.
- [x] I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

### Screenshots/Videos
<details>
<summary>Android: Native</summary>

<img width="344" alt="Screenshot 2025-04-04 at 9 35 19 PM" src="https://github.com/user-attachments/assets/66b5357f-87d9-471b-8268-62e0b2aca969" />

<!-- add screenshots or videos here -->

</details>

<details>
<summary>Android: mWeb Chrome</summary>

<img width="324" alt="Screenshot 2025-04-04 at 9 25 36 PM" src="https://github.com/user-attachments/assets/aa10a9c6-1114-4cea-bffe-801f0d7b2cda" />

<!-- add screenshots or videos here -->

</details>

<details>
<summary>iOS: Native</summary>

<!-- add screenshots or videos here -->

</details>

<details>
<summary>iOS: mWeb Safari</summary>

<img width="424" alt="Screenshot 2025-04-04 at 9 19 58 PM" src="https://github.com/user-attachments/assets/07d4dee0-3912-43ee-9b00-f791c7a4c84c" />

<!-- add screenshots or videos here -->

</details>

<details>
<summary>MacOS: Chrome / Safari</summary>

https://github.com/user-attachments/assets/1d7561d2-f1b5-4137-9a8a-7a0ed7686262


<!-- add screenshots or videos here -->

</details>

<details>
<summary>MacOS: Desktop</summary>

https://github.com/user-attachments/assets/bff175a8-fb92-45da-a7e9-034d6ee22326


<!-- add screenshots or videos here -->

</details>

author_association: CONTRIBUTOR
user: login: allgandalf
created_at: 2025-04-04T15:49:11Z
body: `Bug:` Selected items persist even when i go back and come again, i think they should get unselected once the screen looses focus:

https://github.com/user-attachments/assets/d30755a5-e84e-4755-9d74-2d89ee25a6fa


author_association: CONTRIBUTOR
user: login: allgandalf
created_at: 2025-04-04T15:50:38Z
body: @jnowakow please update the QA steps to indicate that the tests won't work on small screens and should be skipped
author_association: CONTRIBUTOR
user: login: allgandalf
created_at: 2025-04-06T07:14:49Z
body: @jnowakow if you find time then can you also fix the bug ^ with fixing the conflicts 
author_association: CONTRIBUTOR
user: login: jnowakow
created_at: 2025-04-07T07:10:43Z
body: > ^ bug is minor and can be dealt with in a follow up

Actually it was done on purpose but if it's considered a bug then it should be fixed in follow up because it won't be that easy. 
author_association: CONTRIBUTOR
user: login: shawnborton
created_at: 2025-04-07T07:12:04Z
body: Going to run another test build quickly, will report back with some design feedback once they are ready. 
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-07T07:12:35Z
body: 🚧 @shawnborton has triggered a test app build. You can view the [workflow run here](https://github.com/Expensify/App/actions/runs/14303067718).
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-07T07:54:35Z
body: :test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! :test_tube::test_tube:
| Android :robot:  | iOS :apple: |
| ------------- | ------------- |
| Android :robot::arrows_counterclockwise:  | iOS :apple::arrows_counterclockwise: |
| https://ad-hoc-expensify-cash.s3.amazonaws.com/android/59433-hybrid/index.html  | https://ad-hoc-expensify-cash.s3.amazonaws.com/ios/59433-hybrid/index.html  |
| ![Android](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/android/59433-hybrid/index.html)  | ![iOS](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/ios/59433-hybrid/index.html)  |
| Desktop :computer: | Web :spider_web: |
| https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/59433/NewExpensify.dmg  | https://59433.pr-testing.expensify.com  |
| ![Desktop](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/59433/NewExpensify.dmg)  | ![Web](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://59433.pr-testing.expensify.com)  |

---

:eyes: [View the workflow run that generated this build](https://github.com/Expensify/App/actions/runs/14303067718) :eyes:

author_association: CONTRIBUTOR
user: login: shawnborton
created_at: 2025-04-07T08:02:53Z
body: It looks like we have good spacing, we are consistent between the reports page and the report view itself:
![CleanShot 2025-04-07 at 10 00 44@2x](https://github.com/user-attachments/assets/102c89d5-6c91-4134-9eee-fb2bbf9169a5)

However, minor thing is that the table header on a report is further away than it is on the reports page:
![CleanShot 2025-04-07 at 10 02 09@2x](https://github.com/user-attachments/assets/18c0bb00-88e9-436b-8db3-a72de2fc9b4d)

We need to reduce the bottom margin/padding of the table header on the report by 8px. If we remove 8px of space, they should be consistent between Reports and a Report itself. 
author_association: CONTRIBUTOR
user: login: allgandalf
created_at: 2025-04-07T09:58:35Z
body: we also have a failing jest
author_association: CONTRIBUTOR
user: login: jnowakow
created_at: 2025-04-07T10:46:23Z
body: @shawnborton header padding on header is fixed 
author_association: CONTRIBUTOR
user: login: jnowakow
created_at: 2025-04-07T10:58:49Z
body: @allgandalf tests work again. Merging main does the trick 😌 
author_association: CONTRIBUTOR
user: login: shawnborton
created_at: 2025-04-07T15:10:19Z
body: Will rebuild 👍 
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-07T15:10:56Z
body: 🚧 @shawnborton has triggered a test app build. You can view the [workflow run here](https://github.com/Expensify/App/actions/runs/14312820507).
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-07T15:50:05Z
body: :test_tube::test_tube: Use the links below to test this adhoc build on Android, iOS, Desktop, and Web. Happy testing! :test_tube::test_tube:
| Android :robot:  | iOS :apple: |
| ------------- | ------------- |
| Android :robot::arrows_counterclockwise:  | iOS :apple::arrows_counterclockwise: |
| https://ad-hoc-expensify-cash.s3.amazonaws.com/android/59433-hybrid/index.html  | https://ad-hoc-expensify-cash.s3.amazonaws.com/ios/59433-hybrid/index.html  |
| ![Android](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/android/59433-hybrid/index.html)  | ![iOS](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/ios/59433-hybrid/index.html)  |
| Desktop :computer: | Web :spider_web: |
| https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/59433/NewExpensify.dmg  | https://59433.pr-testing.expensify.com  |
| ![Desktop](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://ad-hoc-expensify-cash.s3.amazonaws.com/desktop/59433/NewExpensify.dmg)  | ![Web](https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=https://59433.pr-testing.expensify.com)  |

---

:eyes: [View the workflow run that generated this build](https://github.com/Expensify/App/actions/runs/14312820507) :eyes:

author_association: CONTRIBUTOR
user: login: shawnborton
created_at: 2025-04-07T15:53:13Z
body: Hmm looks like we're still off by 2px:
![CleanShot 2025-04-07 at 17 52 42@2x](https://github.com/user-attachments/assets/05a9cad9-9472-41cf-b17b-3be7effe3837)

The report itself has more padding than the reports page. Any idea what's going on? 
author_association: CONTRIBUTOR
user: login: shawnborton
created_at: 2025-04-07T16:32:37Z
body: Looking good outside of my comment above about the header being slightly off, and only other comment is that if something is selected, it should not have any :hover styles - it should just keep the selectedBG color on hover.


https://github.com/user-attachments/assets/c5bd6446-5c01-4afc-8d61-20c3f1d747c8


author_association: CONTRIBUTOR
user: login: jnowakow
created_at: 2025-04-07T16:38:12Z
body: This feature is actively worked on so I would said it's good to merge this PR and fix those two UI issues in one of coming PRs
author_association: CONTRIBUTOR
user: login: shawnborton
created_at: 2025-04-07T17:31:47Z
body: Fine by me! 
author_association: CONTRIBUTOR
user: login: mountiny
created_at: 2025-04-07T17:33:56Z
body: @shawnborton thank you! Added them here in case you could just add a bit more detail so its easier for folks to squash quickly 🚀 https://github.com/Expensify/App/issues/59452#issuecomment-2784087351 thank you!
author_association: CONTRIBUTOR
user: login: OSBotify
created_at: 2025-04-07T17:42:42Z
body: :hand: This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-07T23:09:13Z
body: 🚀 [Deployed](https://github.com/Expensify/App/actions/runs/14320813743) to staging by https://github.com/mountiny in version: 9.1.24-2 🚀

platform | result
---|---
🖥 desktop 🖥|success ✅
🕸 web 🕸|success ✅
🤖 android 🤖|success ✅
🍎 iOS 🍎|success ✅
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-09T02:14:29Z
body: 🚀 [Deployed](https://github.com/Expensify/App/actions/runs/14347298096) to production by https://github.com/jasperhuangg in version: 9.1.24-10 🚀

platform | result
---|---
🖥 desktop 🖥|success ✅
🕸 web 🕸|success ✅
🤖 android 🤖|failure ❌
🍎 iOS 🍎|success ✅
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-04-09T04:32:50Z
body: 🚀 [Deployed](https://github.com/Expensify/App/actions/runs/14347298096) to production by https://github.com/jasperhuangg in version: 9.1.24-10 🚀

platform | result
---|---
🖥 desktop 🖥|success ✅
🕸 web 🕸|success ✅
🤖 android 🤖|failure ❌
🍎 iOS 🍎|success ✅
author_association: CONTRIBUTOR
user: login: jnowakow
created_at: 2025-04-16T11:26:42Z
body: @shawnborton I've wanted to create follow up and add selection to mobile but I can't find designs. It should look like this: 
<img width="407" alt="image" src="https://github.com/user-attachments/assets/a13d316a-b11a-465e-a455-302ed835a7fe" />
but with checkboxes on items? Or like on the reports page it should go into selection mode and Report header should be replaced with something like this:
<img width="382" alt="image" src="https://github.com/user-attachments/assets/4385f64c-f722-4c67-b8b2-11a5df515ca8" />


author_association: CONTRIBUTOR
user: login: shawnborton
created_at: 2025-04-16T15:01:27Z
body: > Or like on the reports page it should go into selection mode and Report header should be replaced with something like this:

I think it makes sense to follow what we have for the reports page, where the whole header is replaced when we enter into select mode. cc @Expensify/design for the gut check there though! 
author_association: CONTRIBUTOR
user: login: shawnborton
created_at: 2025-04-16T15:01:54Z
body: And yes, we need to add the checkboxes to the mobile expense rows. Again, use the Reports page as your guiding light! 
author_association: CONTRIBUTOR
user: login: dannymcclain
created_at: 2025-04-16T15:11:57Z
body: Agree with Shawn above!
author_association: CONTRIBUTOR