Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import isEmpty from 'lodash/isEmpty';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {InteractionManager, View} from 'react-native';
import type {NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import FlatList from '@components/FlatList';
import {AUTOSCROLL_TO_TOP_THRESHOLD} from '@components/InvertedFlatList/BaseInvertedFlatList';
Expand All @@ -17,7 +17,6 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import {isActionVisibleOnMoneyRequestReport} from '@libs/MoneyRequestReportUtils';
import {
getFirstVisibleReportActionID,
getIOUActionForTransactionID,
getMostRecentIOURequestActionID,
getOneTransactionThreadReportID,
hasNextActionMadeBySameActor,
Expand All @@ -39,7 +38,6 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import type Transaction from '@src/types/onyx/Transaction';
import MoneyRequestReportTransactionList from './MoneyRequestReportTransactionList';
import SearchMoneyRequestReportEmptyState from './SearchMoneyRequestReportEmptyState';

Expand All @@ -57,6 +55,9 @@ type MoneyRequestReportListProps = {
/** Array of report actions for this report */
reportActions?: OnyxTypes.ReportAction[];

/** List of transactions belonging to this report */
transactions: OnyxTypes.Transaction[];

/** If the report has newer actions to load */
hasNewerActions: boolean;

Expand All @@ -71,21 +72,11 @@ function getParentReportAction(parentReportActions: OnyxEntry<OnyxTypes.ReportAc
return parentReportActions[parentReportActionID];
}

function selectTransactionsForReportID(transactions: OnyxCollection<OnyxTypes.Transaction>, reportID: string, reportActions: OnyxTypes.ReportAction[]) {
return Object.values(transactions ?? {}).filter((transaction): transaction is Transaction => {
if (!transaction) {
return false;
}
const action = getIOUActionForTransactionID(reportActions, transaction.transactionID);
return transaction.reportID === reportID && !isDeletedParentAction(action);
});
}

/**
* TODO make this component have the same functionalities as `ReportActionsList`
* - shouldDisplayNewMarker
*/
function MoneyRequestReportActionsList({report, reportActions = [], hasNewerActions, hasOlderActions}: MoneyRequestReportListProps) {
function MoneyRequestReportActionsList({report, reportActions = [], transactions = [], hasNewerActions, hasOlderActions}: MoneyRequestReportListProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {preferredLocale} = useLocalize();
Expand All @@ -101,9 +92,6 @@ function MoneyRequestReportActionsList({report, reportActions = [], hasNewerActi
const mostRecentIOUReportActionID = useMemo(() => getMostRecentIOURequestActionID(reportActions), [reportActions]);
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, reportActions),
});
const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID ?? CONST.DEFAULT_NUMBER_ID}`);
const [currentUserAccountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID});

Expand Down
85 changes: 66 additions & 19 deletions src/components/MoneyRequestReportView/MoneyRequestReportView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {useCallback} from 'react';
import {InteractionManager, View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import HeaderGap from '@components/HeaderGap';
import MoneyReportHeader from '@components/MoneyReportHeader';
Expand All @@ -15,10 +15,11 @@ import {removeFailedReport} from '@libs/actions/Report';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import Log from '@libs/Log';
import navigationRef from '@libs/Navigation/navigationRef';
import {isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {getIOUActionForTransactionID, getOneTransactionThreadReportID, isDeletedParentAction, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {canEditReportAction, getReportOfflinePendingActionAndErrors} from '@libs/ReportUtils';
import {buildCannedSearchQuery} from '@libs/SearchQueryUtils';
import Navigation from '@navigation/Navigation';
import ReportActionsView from '@pages/home/report/ReportActionsView';
import ReportFooter from '@pages/home/report/ReportFooter';
import NAVIGATORS from '@src/NAVIGATORS';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down Expand Up @@ -81,6 +82,20 @@ function getParentReportAction(parentReportActions: OnyxEntry<OnyxTypes.ReportAc
return parentReportActions[parentReportActionID];
}

function selectTransactionsForReportID(transactions: OnyxCollection<OnyxTypes.Transaction>, reportID: string | undefined, reportActions: OnyxTypes.ReportAction[]) {
if (!reportID) {
return [];
}

return Object.values(transactions ?? {}).filter((transaction): transaction is OnyxTypes.Transaction => {
if (!transaction) {
return false;
}
const action = getIOUActionForTransactionID(reportActions, transaction.transactionID);
return transaction.reportID === reportID && !isDeletedParentAction(action);
});
}

function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayReportFooter, backToRoute}: MoneyRequestReportViewProps) {
const styles = useThemeStyles();
const {isOffline} = useNetwork();
Expand All @@ -92,6 +107,12 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe
const {reportPendingAction, reportErrors} = getReportOfflinePendingActionAndErrors(report);

const {reportActions, hasNewerActions, hasOlderActions} = usePaginatedReportActions(reportID);
const transactionThreadReportID = getOneTransactionThreadReportID(reportID, reportActions ?? [], isOffline);

const [transactions = []] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {
selector: (allTransactions): OnyxTypes.Transaction[] => selectTransactionsForReportID(allTransactions, reportID, reportActions),
});
const shouldUseSingleTransactionView = transactions.length === 1;

const [parentReportAction] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(report?.parentReportID)}`, {
canEvict: false,
Expand All @@ -118,6 +139,26 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe
return;
}

if (isLoadingApp) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's great that you used an early return here instead of a ternary — it really improves readability.

return (
<View style={styles.flex1}>
<HeaderGap />
<ReportHeaderSkeletonView />
<ReportActionsSkeletonView />
{shouldDisplayReportFooter ? (
<ReportFooter
report={report}
reportMetadata={reportMetadata}
policy={policy}
pendingAction={reportPendingAction}
isComposerFullSize={!!isComposerFullSize}
lastReportAction={lastReportAction}
/>
) : null}
</View>
);
}

return (
<View style={styles.flex1}>
<OfflineWithFeedback
Expand All @@ -130,33 +171,39 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe
errorRowStyles={[styles.ph5, styles.mv2]}
>
<HeaderGap />
{!isLoadingApp ? (
<MoneyReportHeader
<MoneyReportHeader
report={report}
policy={policy}
reportActions={reportActions}
transactionThreadReportID={undefined}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SzymczakJ @allgandalf Is there any reason why we pass undefined here? Or this is a mistake

Coming from: #60079

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is a mistake. In fact, this is probably something that we forgot to add, because this transactionThreadReportID has been undefined, since the creation of this file.
Adding transactionThreadReportID might solve the issue.

shouldDisplayBackButton
onBackButtonPress={() => {
if (!backToRoute) {
goBackFromSearchMoneyRequest(activeWorkspaceID);
return;
}
Navigation.goBack(backToRoute);
}}
/>
{shouldUseSingleTransactionView ? (
// This component originally lives in ReportScreen, it is used here to handle the case when the report has a single transaction. Any other case will be handled by MoneyRequestReportActionsList
<ReportActionsView
report={report}
policy={policy}
reportActions={reportActions}
transactionThreadReportID={undefined}
shouldDisplayBackButton
onBackButtonPress={() => {
if (!backToRoute) {
goBackFromSearchMoneyRequest(activeWorkspaceID);
return;
}
Navigation.goBack(backToRoute);
}}
isLoadingInitialReportActions={reportMetadata?.isLoadingInitialReportActions}
hasNewerActions={hasNewerActions}
hasOlderActions={hasOlderActions}
parentReportAction={parentReportAction}
transactionThreadReportID={transactionThreadReportID}
/>
) : (
<ReportHeaderSkeletonView />
)}
{!isLoadingApp ? (
<MoneyRequestReportActionsList
report={report}
transactions={transactions}
reportActions={reportActions}
hasOlderActions={hasOlderActions}
hasNewerActions={hasNewerActions}
/>
) : (
<ReportActionsSkeletonView />
)}
{shouldDisplayReportFooter ? (
<ReportFooter
Expand Down
7 changes: 4 additions & 3 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5486,7 +5486,7 @@ function buildOptimisticExpenseReport(
return expenseReport;
}

function buildOptimisticEmptyReport(reportID: string, accountID: number, parentReportID: string | undefined, policy: OnyxEntry<Policy>, timeOfCreation: string) {
function buildOptimisticEmptyReport(reportID: string, accountID: number, parentReport: OnyxEntry<Report>, policy: OnyxEntry<Policy>, timeOfCreation: string) {
const {stateNum, statusNum} = getExpenseReportStateAndStatus(policy);
const titleReportField = getTitleReportField(getReportFieldsByPolicyID(policy?.id) ?? {});
const optimisticEmptyReport: OptimisticNewReport = {
Expand All @@ -5503,8 +5503,9 @@ function buildOptimisticEmptyReport(reportID: string, accountID: number, parentR
participants: {},
lastVisibleActionCreated: timeOfCreation,
pendingFields: {createReport: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD},
parentReportID,
chatReportID: parentReportID,
parentReportID: parentReport?.reportID,
chatReportID: parentReport?.reportID,
managerID: getSubmitToAccountID(policy, undefined),
};

const optimisticReportName = populateOptimisticReportFormula(titleReportField?.defaultValue ?? CONST.POLICY.DEFAULT_REPORT_NAME_PATTERN, optimisticEmptyReport, policy);
Expand Down
2 changes: 1 addition & 1 deletion src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2509,7 +2509,7 @@ function buildNewReportOptimisticData(policy: OnyxEntry<Policy>, reportID: strin
const {accountID, login} = creatorPersonalDetails;
const timeOfCreation = DateUtils.getDBTime();
const parentReport = getPolicyExpenseChat(accountID, policy?.id);
const optimisticReportData = buildOptimisticEmptyReport(reportID, accountID, parentReport?.reportID, policy, timeOfCreation);
const optimisticReportData = buildOptimisticEmptyReport(reportID, accountID, parentReport, policy, timeOfCreation);

const optimisticCreateAction = {
action: CONST.REPORT.ACTIONS.TYPE.CREATED,
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/NextStepUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('libs/NextStepUtils', () => {

describe('it generates and optimistic nextStep once a report has been created', () => {
test('Correct next steps message', () => {
const emptyReport = buildOptimisticEmptyReport('fake-empty-report-id-2', currentUserAccountID, 'fake-parent-report-id-3', policy, '2025-03-31 13:23:11');
const emptyReport = buildOptimisticEmptyReport('fake-empty-report-id-2', currentUserAccountID, {reportID: 'fake-parent-report-id-3'}, policy, '2025-03-31 13:23:11');

optimisticNextStep.message = [
{
Expand Down