Skip to content

Commit 8b15ab7

Browse files
authored
Merge pull request #86732 from callstack-internal/perf/decompose-MoneyRequestHeaderStatusBar
Extract MoneyReportHeader status bar logic into hook and component
2 parents 5635046 + 519545f commit 8b15ab7

4 files changed

Lines changed: 305 additions & 141 deletions

File tree

src/CONST/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,17 @@ const CONST = {
13251325
MARK_AS_CASH: 'markAsCash',
13261326
MARK_AS_RESOLVED: 'markAsResolved',
13271327
},
1328+
STATUS_BAR_TYPE: {
1329+
MARK_AS_RESOLVED: 'markAsResolved',
1330+
BOOKING_PENDING: 'bookingPending',
1331+
BOOKING_ARCHIVED: 'bookingArchived',
1332+
ON_HOLD: 'onHold',
1333+
DUPLICATES: 'duplicates',
1334+
BROKEN_CONNECTION: 'brokenConnection',
1335+
PENDING_RTER: 'pendingRTER',
1336+
PENDING_TRANSACTIONS: 'pendingTransactions',
1337+
SCANNING_RECEIPT: 'scanningReceipt',
1338+
},
13281339
REPORT_PREVIEW_ACTIONS: {
13291340
VIEW: 'view',
13301341
ADD_EXPENSE: 'addExpense',

src/components/MoneyReportHeader.tsx

Lines changed: 10 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {isUserValidatedSelector} from '@selectors/Account';
33
import {shouldFailAllRequestsSelector} from '@selectors/Network';
44
import {hasSeenTourSelector} from '@selectors/Onboarding';
55
import passthroughPolicyTagListSelector from '@selectors/PolicyTagList';
6-
import {getArchiveReason} from '@selectors/Report';
76
import {validTransactionDraftsSelector} from '@selectors/TransactionDraft';
87
import truncate from 'lodash/truncate';
98
import React, {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
@@ -23,6 +22,7 @@ import useGetIOUReportFromReportAction from '@hooks/useGetIOUReportFromReportAct
2322
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
2423
import useLocalize from '@hooks/useLocalize';
2524
import useMobileSelectionMode from '@hooks/useMobileSelectionMode';
25+
import useMoneyReportHeaderStatusBar from '@hooks/useMoneyReportHeaderStatusBar';
2626
import useNetwork from '@hooks/useNetwork';
2727
import useNonReimbursablePaymentModal from '@hooks/useNonReimbursablePaymentModal';
2828
import useOnyx from '@hooks/useOnyx';
@@ -53,7 +53,6 @@ import {deleteAppReport, downloadReportPDF, exportReportToCSV, exportReportToPDF
5353
import {getExportTemplates, queueExportSearchWithTemplate, search} from '@libs/actions/Search';
5454
import initSplitExpense from '@libs/actions/SplitExpenses';
5555
import {setNameValuePair} from '@libs/actions/User';
56-
import {isPersonalCard} from '@libs/CardUtils';
5756
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
5857
import getPlatform from '@libs/getPlatform';
5958
import {getExistingTransactionID} from '@libs/IOUUtils';
@@ -82,7 +81,7 @@ import {
8281
hasRequestFromCurrentAccount,
8382
isMoneyRequestAction,
8483
} from '@libs/ReportActionsUtils';
85-
import {getReportPrimaryAction, isMarkAsResolvedAction} from '@libs/ReportPrimaryActionUtils';
84+
import {getReportPrimaryAction} from '@libs/ReportPrimaryActionUtils';
8685
import {getSecondaryExportReportActions, getSecondaryReportActions} from '@libs/ReportSecondaryActionUtils';
8786
import {
8887
canEditFieldOfMoneyRequest,
@@ -97,7 +96,6 @@ import {
9796
getNonHeldAndFullAmount,
9897
getPolicyExpenseChat,
9998
getReasonAndReportActionThatRequiresAttention,
100-
getTransactionsWithReceipts,
10199
hasHeldExpenses as hasHeldExpensesReportUtils,
102100
hasOnlyHeldExpenses as hasOnlyHeldExpensesReportUtils,
103101
hasOnlyNonReimbursableTransactions,
@@ -111,7 +109,6 @@ import {
111109
isIOUReport as isIOUReportUtil,
112110
isOpenExpenseReport,
113111
isOpenReport,
114-
isProcessingReport,
115112
isReportOwner,
116113
isSelfDM,
117114
navigateOnDeleteExpense,
@@ -123,23 +120,17 @@ import shouldPopoverUseScrollView from '@libs/shouldPopoverUseScrollView';
123120
import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
124121
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
125122
import {
126-
allHavePendingRTERViolation,
127123
getChildTransactions,
128124
getOriginalTransactionWithSplitInfo,
129125
hasAnyPendingRTERViolation as hasAnyPendingRTERViolationTransactionUtils,
130126
hasCustomUnitOutOfPolicyViolation as hasCustomUnitOutOfPolicyViolationTransactionUtils,
131-
hasDuplicateTransactions,
132127
isDistanceRequest,
133128
isExpensifyCardTransaction,
134-
isPayAtEndExpense as isPayAtEndExpenseTransactionUtils,
135129
isPending,
136130
isPerDiemRequest,
137-
isScanning,
138131
isTransactionPendingDelete,
139-
shouldShowBrokenConnectionViolationForMultipleTransactions,
140132
} from '@libs/TransactionUtils';
141133
import type {ExportType} from '@pages/inbox/report/ReportDetailsExportPage';
142-
import variables from '@styles/variables';
143134
import {
144135
approveMoneyRequest,
145136
canApproveIOU,
@@ -163,9 +154,7 @@ import ROUTES from '@src/ROUTES';
163154
import SCREENS from '@src/SCREENS';
164155
import type * as OnyxTypes from '@src/types/onyx';
165156
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
166-
import type IconAsset from '@src/types/utils/IconAsset';
167157
import ActivityIndicator from './ActivityIndicator';
168-
import BrokenConnectionDescription from './BrokenConnectionDescription';
169158
import Button from './Button';
170159
import ButtonWithDropdownMenu from './ButtonWithDropdownMenu';
171160
import type {ButtonWithDropdownMenuRef, DropdownOption} from './ButtonWithDropdownMenu/types';
@@ -185,9 +174,8 @@ import {ModalActions} from './Modal/Global/ModalContext';
185174
import MoneyReportHeaderKYCDropdown from './MoneyReportHeaderKYCDropdown';
186175
import MoneyReportHeaderPrimaryAction from './MoneyReportHeaderPrimaryAction';
187176
import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar';
177+
import MoneyReportHeaderStatusBarSection from './MoneyReportHeaderStatusBarSection';
188178
import MoneyReportHeaderStatusBarSkeleton from './MoneyReportHeaderStatusBarSkeleton';
189-
import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar';
190-
import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
191179
import MoneyRequestReportNavigation from './MoneyRequestReportView/MoneyRequestReportNavigation';
192180
import {usePersonalDetails} from './OnyxListItemProvider';
193181
import type {PopoverMenuItem} from './PopoverMenu';
@@ -264,15 +252,10 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
264252
'Building',
265253
'Buildings',
266254
'Plus',
267-
'Hourglass',
268255
'Cash',
269-
'Box',
270256
'Stopwatch',
271-
'Flag',
272-
'CreditCardHourglass',
273257
'Send',
274258
'Clear',
275-
'ReceiptScan',
276259
'ThumbsUp',
277260
'CircularArrowBackwards',
278261
'ArrowSplit',
@@ -389,7 +372,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
389372
const [originalIOUTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(currentTransaction?.comment?.originalTransactionID)}`);
390373
const [originalTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transaction?.comment?.originalTransactionID)}`);
391374
const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
392-
const [cardList] = useOnyx(ONYXKEYS.CARD_LIST);
393375
const {isBetaEnabled} = usePermissions();
394376
const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
395377
const isDEWPolicy = hasDynamicExternalWorkflow(policy);
@@ -441,7 +423,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
441423
const policyType = policy?.type;
442424
const connectedIntegration = getValidConnectedIntegration(policy);
443425
const connectedIntegrationFallback = getConnectedIntegration(policy);
444-
const hasScanningReceipt = getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => isScanning(t));
445426
const hasOnlyPendingTransactions = useMemo(() => {
446427
return !!transactions && transactions.length > 0 && transactions.every((t) => isExpensifyCardTransaction(t) && isPending(t));
447428
}, [transactions]);
@@ -470,22 +451,13 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
470451
return translate('reportDetailsPage.successPDF');
471452
}, [reportPDFFilename, hasFinishedPDFDownload, translate]);
472453

473-
// Check if there is pending rter violation in all transactionViolations with given transactionIDs.
474-
// wrapped in useMemo to avoid unnecessary re-renders and for better performance (array operation inside of function)
475-
const hasAllPendingRTERViolations = useMemo(
476-
() => allHavePendingRTERViolation(transactions, violations, email ?? '', accountID, moneyRequestReport, policy),
477-
[transactions, violations, email, accountID, moneyRequestReport, policy],
478-
);
479454
// Check if any transactions have pending RTER violations (for showing the submit confirmation modal)
480455
const hasAnyPendingRTERViolation = useMemo(
481456
() => hasAnyPendingRTERViolationTransactionUtils(transactions, allTransactionViolations, email ?? '', accountID, moneyRequestReport, policy),
482457
[transactions, allTransactionViolations, email, accountID, moneyRequestReport, policy],
483458
);
484459

485-
// Check if user should see broken connection violation warning.
486-
const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationForMultipleTransactions(transactions, moneyRequestReport, policy, violations, email ?? '', accountID);
487460
const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(moneyRequestReport?.reportID);
488-
const isPayAtEndExpense = isPayAtEndExpenseTransactionUtils(transaction);
489461
const isArchivedReport = useReportIsArchived(moneyRequestReport?.reportID);
490462
const isChatReportArchived = useReportIsArchived(chatReport?.reportID);
491463

@@ -513,8 +485,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
513485
return canMoveExpense && canUserPerformWriteAction;
514486
}, [nonPendingDeleteTransactions, reportActions, isChatReportArchived, outstandingReportsByPolicyID, moneyRequestReport]);
515487

516-
const [archiveReason] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport?.reportID}`, {selector: getArchiveReason});
517-
518488
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${moneyRequestReport?.reportID}`);
519489
const getCanIOUBePaid = useCallback(
520490
(onlyShowPayElsewhere = false) =>
@@ -640,17 +610,7 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
640610

641611
const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE;
642612

643-
const hasDuplicates = hasDuplicateTransactions(email ?? '', accountID, moneyRequestReport, policy, allTransactionViolations);
644-
const shouldShowMarkAsResolved = isMarkAsResolvedAction(moneyRequestReport, transactionViolations);
645-
const shouldShowStatusBar =
646-
hasAllPendingRTERViolations ||
647-
shouldShowBrokenConnectionViolation ||
648-
hasOnlyHeldExpenses ||
649-
hasScanningReceipt ||
650-
isPayAtEndExpense ||
651-
hasOnlyPendingTransactions ||
652-
hasDuplicates ||
653-
shouldShowMarkAsResolved;
613+
const {shouldShowStatusBar, statusBarType} = useMoneyReportHeaderStatusBar(reportIDProp, moneyRequestReport?.chatReportID);
654614

655615
let optimisticNextStep = getReportNextStep(nextStep, moneyRequestReport, transactions, policy, allTransactionViolations, email ?? '', accountID);
656616

@@ -1018,96 +978,6 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
1018978
],
1019979
);
1020980

1021-
const getStatusIcon: (src: IconAsset) => React.ReactNode = (src) => (
1022-
<Icon
1023-
src={src}
1024-
height={variables.iconSizeSmall}
1025-
width={variables.iconSizeSmall}
1026-
fill={theme.icon}
1027-
/>
1028-
);
1029-
1030-
const getStatusBarProps: () => MoneyRequestHeaderStatusBarProps | undefined = () => {
1031-
if (shouldShowMarkAsResolved) {
1032-
return {
1033-
icon: getStatusIcon(expensifyIcons.Hourglass),
1034-
description: translate('iou.reject.rejectedStatus'),
1035-
};
1036-
}
1037-
1038-
if (isPayAtEndExpense) {
1039-
if (!isArchivedReport) {
1040-
return {
1041-
icon: getStatusIcon(expensifyIcons.Hourglass),
1042-
description: translate('iou.bookingPendingDescription'),
1043-
};
1044-
}
1045-
if (isArchivedReport && archiveReason === CONST.REPORT.ARCHIVE_REASON.BOOKING_END_DATE_HAS_PASSED) {
1046-
return {
1047-
icon: getStatusIcon(expensifyIcons.Box),
1048-
description: translate('iou.bookingArchivedDescription'),
1049-
};
1050-
}
1051-
}
1052-
1053-
if (hasOnlyHeldExpenses) {
1054-
return {
1055-
icon: getStatusIcon(expensifyIcons.Stopwatch),
1056-
description: translate(transactions.length > 1 ? 'iou.expensesOnHold' : 'iou.expenseOnHold'),
1057-
};
1058-
}
1059-
1060-
if (hasDuplicates) {
1061-
return {
1062-
icon: getStatusIcon(expensifyIcons.Flag),
1063-
description: translate('iou.duplicateTransaction', isProcessingReport(moneyRequestReport)),
1064-
};
1065-
}
1066-
1067-
// Show the broken connection violation message only if it's part of transactionViolations (i.e., visible to the user).
1068-
// This prevents displaying an empty message.
1069-
if (!!transaction?.transactionID && !!transactionViolations.length && shouldShowBrokenConnectionViolation) {
1070-
const brokenConnectionError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION);
1071-
const cardID = brokenConnectionError?.data?.cardID;
1072-
const card = cardID ? cardList?.[cardID] : undefined;
1073-
const isBrokenPersonalCard = isPersonalCard(card);
1074-
1075-
if (isBrokenPersonalCard && brokenConnectionError) {
1076-
return undefined;
1077-
}
1078-
return {
1079-
icon: getStatusIcon(expensifyIcons.Hourglass),
1080-
description: (
1081-
<BrokenConnectionDescription
1082-
transactionID={transaction?.transactionID}
1083-
report={moneyRequestReport}
1084-
policy={policy}
1085-
/>
1086-
),
1087-
};
1088-
}
1089-
if (hasAllPendingRTERViolations) {
1090-
return {
1091-
icon: getStatusIcon(expensifyIcons.Hourglass),
1092-
description: translate('iou.pendingMatchWithCreditCardDescription'),
1093-
};
1094-
}
1095-
if (hasOnlyPendingTransactions) {
1096-
return {
1097-
icon: getStatusIcon(expensifyIcons.CreditCardHourglass),
1098-
description: translate('iou.transactionPendingDescription'),
1099-
};
1100-
}
1101-
if (hasScanningReceipt) {
1102-
return {
1103-
icon: getStatusIcon(expensifyIcons.ReceiptScan),
1104-
description: translate('iou.receiptScanInProgressDescription'),
1105-
};
1106-
}
1107-
};
1108-
1109-
const statusBarProps = getStatusBarProps();
1110-
1111981
const dismissModalAndUpdateUseHold = () => {
1112982
setIsHoldEducationalModalVisible(false);
1113983
setNameValuePair(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, true, false, !shouldFailAllRequests);
@@ -2322,7 +2192,7 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
23222192

23232193
const showNextStepBar = shouldShowNextStep && !!optimisticNextStep && (('message' in optimisticNextStep && !!optimisticNextStep.message?.length) || 'messageKey' in optimisticNextStep);
23242194
const showNextStepSkeleton = shouldShowNextStep && !optimisticNextStep && !!isLoadingInitialReportActions && !isOffline;
2325-
const shouldShowMoreContent = showNextStepBar || showNextStepSkeleton || !!statusBarProps || isReportInSearch;
2195+
const shouldShowMoreContent = showNextStepBar || showNextStepSkeleton || !!statusBarType || isReportInSearch;
23262196

23272197
const nextStepSkeletonReasonAttributes: SkeletonSpanReasonAttributes = {
23282198
context: 'MoneyReportHeader',
@@ -2399,12 +2269,11 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa
23992269
<View style={[styles.flexShrink1, styles.flexGrow1, styles.mnw0, styles.flexWrap, styles.justifyContentCenter]}>
24002270
{showNextStepBar && <MoneyReportHeaderStatusBar nextStep={optimisticNextStep} />}
24012271
{showNextStepSkeleton && <MoneyReportHeaderStatusBarSkeleton reasonAttributes={nextStepSkeletonReasonAttributes} />}
2402-
{!!statusBarProps && (
2403-
<MoneyRequestHeaderStatusBar
2404-
icon={statusBarProps.icon}
2405-
description={statusBarProps.description}
2406-
/>
2407-
)}
2272+
<MoneyReportHeaderStatusBarSection
2273+
reportID={reportIDProp}
2274+
statusBarType={statusBarType}
2275+
iouTransactionID={transaction?.transactionID}
2276+
/>
24082277
</View>
24092278
{isReportInSearch && (
24102279
<MoneyRequestReportNavigation

0 commit comments

Comments
 (0)