From cc54548f32c97ce6c254618ceb3b60e43cc91ffd Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 15 Jan 2025 18:17:56 +0100 Subject: [PATCH 01/12] create report-transactions map to improve performance --- src/libs/TransactionUtils/index.ts | 33 ++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index ba70305a3996..99f370608e75 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -68,6 +68,7 @@ type BuildOptimisticTransactionParams = { }; let allTransactions: OnyxCollection = {}; +let transactionsByReport: Record> = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, @@ -76,6 +77,18 @@ Onyx.connect({ return; } allTransactions = Object.fromEntries(Object.entries(value).filter(([, transaction]) => !!transaction)); + + transactionsByReport = {}; + Object.entries(allTransactions).forEach(([transactionID, transaction]) => { + if (!transaction?.reportID) { + return; + } + + if (!transactionsByReport[transaction.reportID]) { + transactionsByReport[transaction.reportID] = new Set(); + } + transactionsByReport[transaction.reportID].add(transactionID); + }); }, }); @@ -818,10 +831,22 @@ function hasRoute(transaction: OnyxEntry, isDistanceRequestType?: b } function getAllReportTransactions(reportID?: string, transactions?: OnyxCollection): Transaction[] { - const reportTransactions: Transaction[] = Object.values(transactions ?? allTransactions ?? {}).filter( - (transaction): transaction is Transaction => !!transaction && transaction.reportID === reportID, - ); - return reportTransactions; + if (!reportID) { + return []; + } + + if (transactions) { + return Object.values(transactions).filter((transaction): transaction is Transaction => !!transaction && transaction.reportID === reportID); + } + + const transactionIDs = transactionsByReport[reportID]; + if (!transactionIDs) { + return []; + } + + return Array.from(transactionIDs) + .map((transactionID) => allTransactions?.[transactionID]) + .filter((transaction): transaction is Transaction => !!transaction); } function waypointHasValidAddress(waypoint: RecentWaypoint | Waypoint): boolean { From cf9b7e4e194660a50cb7ffc3cf676471d895f175 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Fri, 17 Jan 2025 17:48:41 +0100 Subject: [PATCH 02/12] chore: refactor to rely on existing map of report transactions from report utils --- src/components/MoneyReportHeader.tsx | 27 +++++++------ .../ReportActionItem/ReportPreview.tsx | 29 +++++++------- src/libs/IOUUtils.ts | 5 ++- src/libs/ReportUtils.ts | 17 +++++++-- src/libs/TransactionUtils/index.ts | 38 ++----------------- src/libs/actions/IOU.ts | 6 +-- src/libs/actions/Policy/Policy.ts | 3 +- .../TransactionBackupsToCollection.ts | 2 +- src/pages/ReportDetailsPage.tsx | 12 +++--- 9 files changed, 63 insertions(+), 76 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 6c7a43758374..488a1a48c7cd 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -77,15 +77,17 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea } return reportActions.find((action): action is OnyxTypes.ReportAction => action.reportActionID === transactionThreadReport.parentReportActionID); }, [reportActions, transactionThreadReport?.parentReportActionID]); - const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); + const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { + selector: (_transactions) => ReportUtils.reportTransactionsSelector(_transactions, moneyRequestReport?.reportID), + initialValue: [], + }); + const [transaction] = useOnyx( + `${ONYXKEYS.COLLECTION.TRANSACTION}${ + ReportActionsUtils.isMoneyRequestAction(requestParentReportAction) && ReportActionsUtils.getOriginalMessage(requestParentReportAction)?.IOUTransactionID + }`, + ); const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true}); const isLoadingHoldUseExplained = isLoadingOnyxValue(dismissedHoldUseExplanationResult); - const transaction = - transactions?.[ - `${ONYXKEYS.COLLECTION.TRANSACTION}${ - ReportActionsUtils.isMoneyRequestAction(requestParentReportAction) && ReportActionsUtils.getOriginalMessage(requestParentReportAction)?.IOUTransactionID - }` - ] ?? undefined; const styles = useThemeStyles(); const theme = useTheme(); @@ -105,7 +107,6 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [paymentType, setPaymentType] = useState(); const [requestType, setRequestType] = useState(); - const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID, transactions), [moneyRequestReport?.reportID, transactions]); const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy); const policyType = policy?.type; const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); @@ -113,8 +114,12 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const navigateBackToAfterDelete = useRef(); const hasHeldExpenses = ReportUtils.hasHeldExpenses(moneyRequestReport?.reportID); const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => TransactionUtils.isReceiptBeingScanned(t)); - const hasOnlyPendingTransactions = allTransactions.length > 0 && allTransactions.every((t) => TransactionUtils.isExpensifyCardTransaction(t) && TransactionUtils.isPending(t)); - const transactionIDs = allTransactions.map((t) => t.transactionID); + const hasOnlyPendingTransactions = + transactions?.some((t) => { + const isPending = TransactionUtils.isExpensifyCardTransaction(t) && TransactionUtils.isPending(t); + return !isPending; + }) ?? false; + const transactionIDs = transactions?.map((t) => t.transactionID); const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation([transaction?.transactionID]); const shouldShowBrokenConnectionViolation = TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, moneyRequestReport, policy); const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport?.reportID); @@ -494,7 +499,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea paymentType={paymentType} chatReport={chatReport} moneyRequestReport={moneyRequestReport} - transactionCount={transactionIDs.length} + transactionCount={transactionIDs?.length ?? 0} /> )} reportTransactionsSelector(_transactions, iouReportID), + initialValue: [], + }); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); const [invoiceReceiverPolicy] = useOnyx( @@ -155,7 +158,6 @@ function ReportPreview({ const styles = useThemeStyles(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const allTransactions = useMemo(() => getAllReportTransactions(iouReportID, transactions), [iouReportID, transactions]); const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyTransactionsWithPendingRoutes, hasNonReimbursableTransactions} = useMemo( () => ({ @@ -177,8 +179,8 @@ function ReportPreview({ const getCanIOUBePaid = useCallback( (onlyShowPayElsewhere = false, shouldCheckApprovedState = true) => - canIOUBePaidIOUActions(iouReport, chatReport, policy, allTransactions, onlyShowPayElsewhere, undefined, undefined, shouldCheckApprovedState), - [iouReport, chatReport, policy, allTransactions], + canIOUBePaidIOUActions(iouReport, chatReport, policy, transactions, onlyShowPayElsewhere, undefined, undefined, shouldCheckApprovedState), + [iouReport, chatReport, policy, transactions], ); const canIOUBePaid = useMemo(() => getCanIOUBePaid(), [getCanIOUBePaid]); @@ -216,7 +218,7 @@ function ReportPreview({ const isOpenExpenseReport = isPolicyExpenseChat && isOpenExpenseReportUtils(iouReport); const canAllowSettlement = hasUpdatedTotal(iouReport, policy); - const numberOfRequests = allTransactions.length; + const numberOfRequests = transactions?.length ?? 0; const transactionsWithReceipts = getTransactionsWithReceipts(iouReportID); const numberOfScanningReceipts = transactionsWithReceipts.filter((transaction) => isReceiptBeingScanned(transaction)).length; const numberOfPendingRequests = transactionsWithReceipts.filter((transaction) => isPending(transaction) && isCardTransaction(transaction)).length; @@ -231,12 +233,13 @@ function ReportPreview({ hasWarningTypeViolations(iouReportID, transactionViolations, true) || (isReportOwner(iouReport) && hasReportViolations(iouReportID)) || hasActionsWithErrors(iouReportID); - const lastThreeTransactions = allTransactions.slice(-3); + const lastThreeTransactions = transactions?.slice(-3) ?? []; + const lastTransaction = transactions?.at(0); const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...getThumbnailAndImageURIs(transaction), transaction})); - const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(allTransactions.at(0), getTransactionViolations(allTransactions.at(0)?.transactionID, transactionViolations)); - const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(allTransactions.at(0)?.transactionID, iouReport, policy); - let formattedMerchant = numberOfRequests === 1 ? getMerchant(allTransactions.at(0)) : null; - const formattedDescription = numberOfRequests === 1 ? getDescription(allTransactions.at(0)) : null; + const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(lastTransaction, getTransactionViolations(lastTransaction?.transactionID, transactionViolations)); + const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(lastTransaction?.transactionID, iouReport, policy); + let formattedMerchant = numberOfRequests === 1 ? getMerchant(lastTransaction) : null; + const formattedDescription = numberOfRequests === 1 ? getDescription(lastTransaction) : null; if (isPartialMerchant(formattedMerchant ?? '')) { formattedMerchant = null; @@ -427,7 +430,7 @@ function ReportPreview({ const shouldShowScanningSubtitle = (numberOfScanningReceipts === 1 && numberOfRequests === 1) || (numberOfScanningReceipts >= 1 && Number(nonHeldAmount) === 0); const shouldShowPendingSubtitle = numberOfPendingRequests === 1 && numberOfRequests === 1; - const isPayAtEndExpense = isPayAtEndExpenseReport(iouReportID, allTransactions); + const isPayAtEndExpense = isPayAtEndExpenseReport(iouReportID, transactions); const [archiveReason] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, {selector: getArchiveReason}); const getPendingMessageProps: () => PendingMessageProps = () => { @@ -549,7 +552,7 @@ function ReportPreview({ {lastThreeReceipts.length > 0 && ( )} diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index 61f636703b95..596e0193ee30 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -3,12 +3,13 @@ import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {OnyxInputOrEntry, PersonalDetails, Report, Transaction} from '@src/types/onyx'; +import type {OnyxInputOrEntry, PersonalDetails, Report} from '@src/types/onyx'; import type {Attendee} from '@src/types/onyx/IOU'; import type {IOURequestType} from './actions/IOU'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import Navigation from './Navigation/Navigation'; +import {getReportTransactions} from './ReportUtils'; import * as TransactionUtils from './TransactionUtils'; let lastLocationPermissionPrompt: string; @@ -118,7 +119,7 @@ function updateIOUOwnerAndTotal>( * that are either created or cancelled offline, and thus haven't been converted to the report's currency yet */ function isIOUReportPendingCurrencyConversion(iouReport: Report): boolean { - const reportTransactions: Transaction[] = TransactionUtils.getAllReportTransactions(iouReport.reportID); + const reportTransactions = getReportTransactions(iouReport.reportID); const pendingRequestsInDifferentCurrency = reportTransactions.filter((transaction) => transaction.pendingAction && TransactionUtils.getCurrency(transaction) !== iouReport.currency); return pendingRequestsInDifferentCurrency.length > 0; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7b26e6f6f2a9..4098a540eacf 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -157,7 +157,6 @@ import { wasActionTakenByCurrentUser, } from './ReportActionsUtils'; import { - getAllReportTransactions, getAttendees, getBillable, getCardID, @@ -896,6 +895,14 @@ function getReportOrDraftReport(reportID: string | undefined): OnyxEntry return allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`]; } +function reportTransactionsSelector(transactions: OnyxCollection, reportID: string | undefined): Transaction[] { + if (!transactions || !reportID) { + return []; + } + + return Object.values(transactions).filter((transaction): transaction is Transaction => !!transaction && transaction.reportID === reportID); +} + function getReportTransactions(reportID: string | undefined): Transaction[] { if (!reportID) { return []; @@ -1891,7 +1898,7 @@ function isPayAtEndExpenseReport(reportID: string | undefined, transactions: Tra return false; } - return isPayAtEndExpense(transactions?.[0] ?? getAllReportTransactions(reportID).at(0)); + return isPayAtEndExpense(transactions?.[0] ?? getReportTransactions(reportID).at(0)); } /** @@ -2988,7 +2995,7 @@ function getReasonAndReportActionThatRequiresAttention( const iouReportActionToApproveOrPay = getIOUReportActionToApproveOrPay(optionOrReport, optionOrReport.reportID); const iouReportID = getIOUReportIDFromReportActionPreview(iouReportActionToApproveOrPay); - const transactions = getAllReportTransactions(iouReportID); + const transactions = getReportTransactions(iouReportID); const hasOnlyPendingTransactions = transactions.length > 0 && transactions.every((t) => isExpensifyCardTransaction(t) && isPending(t)); // Has a child report that is awaiting action (e.g. approve, pay, add bank account) from current user @@ -3715,7 +3722,7 @@ function getReportPreviewMessage( return reportActionMessage; } - const allReportTransactions = getAllReportTransactions(report.reportID); + const allReportTransactions = getReportTransactions(report.reportID); const transactionsWithReceipts = allReportTransactions.filter(hasReceiptTransactionUtils); const numberOfScanningReceipts = transactionsWithReceipts.filter(isReceiptBeingScanned).length; @@ -8943,6 +8950,8 @@ export { getReportFieldKey, getReportIDFromLink, getReportName, + getReportTransactions, + reportTransactionsSelector, getReportNotificationPreference, getReportOfflinePendingActionAndErrors, getReportParticipantsTitle, diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 99f370608e75..3a04305b2270 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -26,7 +26,7 @@ import { isPolicyAdmin, } from '@libs/PolicyUtils'; import {getOriginalMessage, getReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; -import {isOpenExpenseReport, isProcessingReport, isReportApproved, isSettled, isThread} from '@libs/ReportUtils'; +import {getReportTransactions, isOpenExpenseReport, isProcessingReport, isReportApproved, isSettled, isThread} from '@libs/ReportUtils'; import type {IOURequestType} from '@userActions/IOU'; import CONST from '@src/CONST'; import type {IOUType} from '@src/CONST'; @@ -68,7 +68,7 @@ type BuildOptimisticTransactionParams = { }; let allTransactions: OnyxCollection = {}; -let transactionsByReport: Record> = {}; + Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, @@ -77,18 +77,6 @@ Onyx.connect({ return; } allTransactions = Object.fromEntries(Object.entries(value).filter(([, transaction]) => !!transaction)); - - transactionsByReport = {}; - Object.entries(allTransactions).forEach(([transactionID, transaction]) => { - if (!transaction?.reportID) { - return; - } - - if (!transactionsByReport[transaction.reportID]) { - transactionsByReport[transaction.reportID] = new Set(); - } - transactionsByReport[transaction.reportID].add(transactionID); - }); }, }); @@ -830,25 +818,6 @@ function hasRoute(transaction: OnyxEntry, isDistanceRequestType?: b return !!transaction?.routes?.route0?.geometry?.coordinates || (!!isDistanceRequestType && !!transaction?.comment?.customUnit?.quantity); } -function getAllReportTransactions(reportID?: string, transactions?: OnyxCollection): Transaction[] { - if (!reportID) { - return []; - } - - if (transactions) { - return Object.values(transactions).filter((transaction): transaction is Transaction => !!transaction && transaction.reportID === reportID); - } - - const transactionIDs = transactionsByReport[reportID]; - if (!transactionIDs) { - return []; - } - - return Array.from(transactionIDs) - .map((transactionID) => allTransactions?.[transactionID]) - .filter((transaction): transaction is Transaction => !!transaction); -} - function waypointHasValidAddress(waypoint: RecentWaypoint | Waypoint): boolean { return !!waypoint?.address?.trim(); } @@ -1353,7 +1322,7 @@ function getCategoryTaxCodeAndAmount(category: string, transaction: OnyxEntry> { - return getAllReportTransactions(iouReportID).sort((transA, transB) => { + return getReportTransactions(iouReportID).sort((transA, transB) => { if (transA.created < transB.created) { return -1; } @@ -1401,7 +1370,6 @@ export { getTagArrayFromName, getTagForDisplay, getTransactionViolations, - getAllReportTransactions, hasReceipt, hasEReceipt, hasRoute, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0ac396709b07..df2942b96835 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -105,6 +105,7 @@ import { getPersonalDetailsForAccountID, getReportNameValuePairs, getReportOrDraftReport, + getReportTransactions, getTransactionDetails, hasHeldExpenses as hasHeldExpensesReportUtils, hasNonReimbursableTransactions as hasNonReimbursableTransactionsReportUtils, @@ -132,7 +133,6 @@ import playSound, {SOUNDS} from '@libs/Sound'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; import { buildOptimisticTransaction, - getAllReportTransactions, getAmount, getCategoryTaxCodeAndAmount, getCurrency, @@ -7234,7 +7234,7 @@ function getPayMoneyRequestParams( // Optimistically unhold all transactions if we pay all requests if (full) { - const reportTransactions = getAllReportTransactions(iouReport?.reportID); + const reportTransactions = getReportTransactions(iouReport?.reportID); for (const transaction of reportTransactions) { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -7358,7 +7358,7 @@ function canApproveIOU( const reportNameValuePairs = chatReportRNVP ?? getReportNameValuePairs(iouReport?.reportID); const isArchivedExpenseReport = isArchivedReport(iouReport, reportNameValuePairs); let isTransactionBeingScanned = false; - const reportTransactions = getAllReportTransactions(iouReport?.reportID); + const reportTransactions = getReportTransactions(iouReport?.reportID); for (const transaction of reportTransactions) { const hasReceipt = hasReceiptTransactionUtils(transaction); const isReceiptBeingScanned = isReceiptBeingScannedTransactionUtils(transaction); diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index f28a82bea9bb..564b19c98eb3 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -74,7 +74,6 @@ import * as PhoneNumber from '@libs/PhoneNumber'; import * as PolicyUtils from '@libs/PolicyUtils'; import {navigateWhenEnableFeature} from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import {getAllReportTransactions} from '@libs/TransactionUtils'; import type {PolicySelector} from '@pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as PersistedRequests from '@userActions/PersistedRequests'; @@ -2550,7 +2549,7 @@ function createWorkspaceFromIOUPayment(iouReport: OnyxEntry): WorkspaceF }); // The expense report transactions need to have the amount reversed to negative values - const reportTransactions = getAllReportTransactions(iouReportID); + const reportTransactions = ReportUtils.getReportTransactions(iouReportID); // For performance reasons, we are going to compose a merge collection data for transactions const transactionsOptimisticData: Record = {}; diff --git a/src/libs/migrations/TransactionBackupsToCollection.ts b/src/libs/migrations/TransactionBackupsToCollection.ts index addbcb02cc02..43f49392ba18 100644 --- a/src/libs/migrations/TransactionBackupsToCollection.ts +++ b/src/libs/migrations/TransactionBackupsToCollection.ts @@ -7,7 +7,7 @@ import type {Transaction} from '@src/types/onyx'; /** * This migration moves all the transaction backups stored in the transaction collection, ONYXKEYS.COLLECTION.TRANSACTION, to a reserved collection that only * stores draft transactions, ONYXKEYS.COLLECTION.TRANSACTION_DRAFT. The purpose of the migration is that there is a possibility that transaction backups are - * not filtered by most functions, e.g, getAllReportTransactions (src/libs/TransactionUtils/index.ts). One problem that arose from storing transaction backups with + * not filtered by most functions, e.g, getReportTransactions (src/libs/ReportUtils/index.ts). One problem that arose from storing transaction backups with * the other transactions is that for every distance expense which have their waypoints updated offline, we expect the ReportPreview component to display the * default image of a pending map. However, due to the presence of the transaction backup, the previous map image will be displayed alongside the pending map. * The problem was further discussed in this PR. https://github.com/Expensify/App/pull/30232#issuecomment-178110172 diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index b48a90e88d95..6b935e3b3deb 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -40,7 +40,6 @@ import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; -import {getAllReportTransactions} from '@libs/TransactionUtils'; import * as IOU from '@userActions/IOU'; import * as Report from '@userActions/Report'; import * as Session from '@userActions/Session'; @@ -108,7 +107,10 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const [isDebugModeEnabled] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.isDebugModeEnabled}); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const [session] = useOnyx(ONYXKEYS.SESSION); - const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); + const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { + selector: (_transactions) => ReportUtils.reportTransactionsSelector(_transactions, report.reportID), + initialValue: [], + }); const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); @@ -163,11 +165,11 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const connectedIntegration = PolicyUtils.getConnectedIntegration(policy); const transactionIDList = useMemo(() => { - if (!isMoneyRequestReport) { + if (!isMoneyRequestReport || !transactions) { return []; } - return getAllReportTransactions(report.reportID, transactions).map((transaction) => transaction.transactionID); - }, [isMoneyRequestReport, report.reportID, transactions]); + return transactions.map((transaction) => transaction.transactionID); + }, [isMoneyRequestReport, transactions]); // Get the active chat members by filtering out the pending members with delete action const activeChatMembers = participants.flatMap((accountID) => { From a8615933aaa87be9c5444ca40d434f80ef160396 Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Fri, 17 Jan 2025 17:51:49 +0100 Subject: [PATCH 03/12] chore: wrap hasOnlyPendingTransactions in a useMemo --- src/components/MoneyReportHeader.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 488a1a48c7cd..063bf5861383 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -114,11 +114,14 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const navigateBackToAfterDelete = useRef(); const hasHeldExpenses = ReportUtils.hasHeldExpenses(moneyRequestReport?.reportID); const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => TransactionUtils.isReceiptBeingScanned(t)); - const hasOnlyPendingTransactions = - transactions?.some((t) => { - const isPending = TransactionUtils.isExpensifyCardTransaction(t) && TransactionUtils.isPending(t); - return !isPending; - }) ?? false; + const hasOnlyPendingTransactions = useMemo(() => { + return ( + transactions?.some((t) => { + const isPending = TransactionUtils.isExpensifyCardTransaction(t) && TransactionUtils.isPending(t); + return !isPending; + }) ?? false + ); + }, [transactions]); const transactionIDs = transactions?.map((t) => t.transactionID); const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation([transaction?.transactionID]); const shouldShowBrokenConnectionViolation = TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, moneyRequestReport, policy); From 3097d4725f27597c5856da0914e044cd9cbdc80f Mon Sep 17 00:00:00 2001 From: Adam Horodyski Date: Fri, 17 Jan 2025 17:58:57 +0100 Subject: [PATCH 04/12] fix: money report header errors --- src/components/MoneyReportHeader.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 063bf5861383..bb6a5d42f555 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -115,16 +115,16 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const hasHeldExpenses = ReportUtils.hasHeldExpenses(moneyRequestReport?.reportID); const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => TransactionUtils.isReceiptBeingScanned(t)); const hasOnlyPendingTransactions = useMemo(() => { - return ( - transactions?.some((t) => { - const isPending = TransactionUtils.isExpensifyCardTransaction(t) && TransactionUtils.isPending(t); - return !isPending; - }) ?? false - ); + return !transactions?.some((t) => { + const isPending = TransactionUtils.isExpensifyCardTransaction(t) && TransactionUtils.isPending(t); + return !isPending; + }); }, [transactions]); const transactionIDs = transactions?.map((t) => t.transactionID); - const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation([transaction?.transactionID]); - const shouldShowBrokenConnectionViolation = TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, moneyRequestReport, policy); + const hasAllPendingRTERViolations = transaction?.transactionID ? TransactionUtils.allHavePendingRTERViolation([transaction.transactionID]) : false; + const shouldShowBrokenConnectionViolation = transaction?.transactionID + ? TransactionUtils.shouldShowBrokenConnectionViolation([transaction.transactionID], moneyRequestReport, policy) + : false; const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport?.reportID); const isPayAtEndExpense = TransactionUtils.isPayAtEndExpense(transaction); const isArchivedReport = ReportUtils.isArchivedReport(moneyRequestReport); From 54077fe6cc54e6aed6e4c9c633e01653d5a33c92 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 20 Jan 2025 12:33:44 +0100 Subject: [PATCH 05/12] fix lint-changed --- src/libs/IOUUtils.ts | 10 +- src/pages/ReportDetailsPage.tsx | 297 ++++++++++++++++++++------------ 2 files changed, 189 insertions(+), 118 deletions(-) diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts index 596e0193ee30..a2d83259c52b 100644 --- a/src/libs/IOUUtils.ts +++ b/src/libs/IOUUtils.ts @@ -6,11 +6,11 @@ import ROUTES from '@src/ROUTES'; import type {OnyxInputOrEntry, PersonalDetails, Report} from '@src/types/onyx'; import type {Attendee} from '@src/types/onyx/IOU'; import type {IOURequestType} from './actions/IOU'; -import * as CurrencyUtils from './CurrencyUtils'; +import {getCurrencyUnit} from './CurrencyUtils'; import DateUtils from './DateUtils'; import Navigation from './Navigation/Navigation'; import {getReportTransactions} from './ReportUtils'; -import * as TransactionUtils from './TransactionUtils'; +import {getCurrency, getTagArrayFromName} from './TransactionUtils'; let lastLocationPermissionPrompt: string; Onyx.connect({ @@ -48,7 +48,7 @@ function navigateToStartMoneyRequestStep(requestType: IOURequestType, iouType: I function calculateAmount(numberOfParticipants: number, total: number, currency: string, isDefaultUser = false): number { // Since the backend can maximum store 2 decimal places, any currency with more than 2 decimals // has to be capped to 2 decimal places - const currencyUnit = Math.min(100, CurrencyUtils.getCurrencyUnit(currency)); + const currencyUnit = Math.min(100, getCurrencyUnit(currency)); const totalInCurrencySubunit = (total / 100) * currencyUnit; const totalParticipants = numberOfParticipants + 1; const amountPerPerson = Math.round(totalInCurrencySubunit / totalParticipants); @@ -120,7 +120,7 @@ function updateIOUOwnerAndTotal>( */ function isIOUReportPendingCurrencyConversion(iouReport: Report): boolean { const reportTransactions = getReportTransactions(iouReport.reportID); - const pendingRequestsInDifferentCurrency = reportTransactions.filter((transaction) => transaction.pendingAction && TransactionUtils.getCurrency(transaction) !== iouReport.currency); + const pendingRequestsInDifferentCurrency = reportTransactions.filter((transaction) => transaction.pendingAction && getCurrency(transaction) !== iouReport.currency); return pendingRequestsInDifferentCurrency.length > 0; } @@ -151,7 +151,7 @@ function isValidMoneyRequestType(iouType: string): boolean { * @returns */ function insertTagIntoTransactionTagsString(transactionTags: string, tag: string, tagIndex: number): string { - const tagArray = TransactionUtils.getTagArrayFromName(transactionTags); + const tagArray = getTagArrayFromName(transactionTags); tagArray[tagIndex] = tag; while (tagArray.length > 0 && !tagArray.at(-1)) { diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 6b935e3b3deb..ac6bd0fe904d 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -6,6 +6,7 @@ import {useOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import ConfirmModal from '@components/ConfirmModal'; import DecisionModal from '@components/DecisionModal'; import DelegateNoAccessModal from '@components/DelegateNoAccessModal'; import DisplayNames from '@components/DisplayNames'; @@ -31,20 +32,100 @@ import useNetwork from '@hooks/useNetwork'; import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ReportActions from '@libs/actions/Report'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportDetailsNavigatorParamList} from '@libs/Navigation/types'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; +import {getConnectedIntegration, isPolicyAdmin as isPolicyAdminUtil, isPolicyEmployee as isPolicyEmployeeUtil, isSubmitAndClose, shouldShowPolicy} from '@libs/PolicyUtils'; +import { + getOneTransactionThreadReportID, + getOriginalMessage, + getReportAction, + getTrackExpenseActionableWhisper, + isDeletedAction, + isMoneyRequestAction, + isTrackExpenseAction, +} from '@libs/ReportActionsUtils'; +import { + canDeleteTransaction, + canEditReportDescription as canEditReportDescriptionUtil, + canHoldUnholdReportAction as canHoldUnholdReportActionUtil, + canJoinChat, + canLeaveChat, + canWriteInReport, + createDraftTransactionAndNavigateToParticipantSelector, + getAvailableReportFields, + getChatRoomSubtitle, + getDisplayNamesWithTooltips, + getIcons, + getOriginalReportID, + getParentNavigationSubtitle, + getParticipantsAccountIDsForDisplay, + getParticipantsList, + getReportDescription, + getReportFieldKey, + getReportName, + isAdminOwnerApproverOrReportOwner, + isArchivedNonExpenseReport, + isCanceledTaskReport as isCanceledTaskReportUtil, + isChatRoom as isChatRoomUtil, + isChatThread as isChatThreadUtil, + isClosedReport, + isCompletedTaskReport, + isConciergeChatReport, + isDefaultRoom as isDefaultRoomUtil, + isExpenseReport as isExpenseReportUtil, + isExported, + isGroupChat as isGroupChatUtil, + isHiddenForCurrentUser, + isInvoiceReport as isInvoiceReportUtil, + isInvoiceRoom as isInvoiceRoomUtil, + isMoneyRequestReport as isMoneyRequestReportUtil, + isMoneyRequest as isMoneyRequestUtil, + isPayer as isPayerUtil, + isPolicyExpenseChat as isPolicyExpenseChatUtil, + isPublicRoom as isPublicRoomUtil, + isReportApproved as isReportApprovedUtil, + isReportFieldDisabled, + isReportFieldOfTypeTitle, + isReportManager as isReportManagerUtil, + isRootGroupChat as isRootGroupChatUtil, + isSelfDM as isSelfDMUtil, + isSettled as isSettledUtil, + isSystemChat as isSystemChatUtil, + isTaskReport as isTaskReportUtil, + isThread as isThreadUtil, + isTrackExpenseReport as isTrackExpenseReportUtil, + isUserCreatedPolicyRoom as isUserCreatedPolicyRoomUtil, + navigateBackOnDeleteTransaction, + navigateToPrivateNotes, + reportTransactionsSelector, + shouldDisableRename as shouldDisableRenameUtil, + shouldUseFullTitleToDisplay, +} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; -import * as IOU from '@userActions/IOU'; -import * as Report from '@userActions/Report'; -import * as Session from '@userActions/Session'; -import * as Task from '@userActions/Task'; -import ConfirmModal from '@src/components/ConfirmModal'; +import { + cancelPayment as cancelPaymentAction, + deleteMoneyRequest, + deleteTrackExpense, + getNavigationUrlAfterTrackExpenseDelete, + getNavigationUrlOnMoneyRequestDelete, + unapproveExpenseReport, +} from '@userActions/IOU'; +import { + clearAvatarErrors, + clearPolicyRoomNameErrors, + clearReportFieldKeyErrors, + exportReportToCSV, + getReportPrivateNote, + hasErrorInPrivateNotes, + leaveGroupChat, + leaveRoom, + setDeleteTransactionNavigateBackUrl, + updateGroupChatAvatar, +} from '@userActions/Report'; +import {checkIfActionIsAllowed} from '@userActions/Session'; +import {canActionTask as canActionTaskAction, canModifyTask as canModifyTaskAction, deleteTask, reopenTask} from '@userActions/Task'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -98,17 +179,14 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); - const transactionThreadReportID = useMemo( - () => ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions ?? [], isOffline), - [report.reportID, reportActions, isOffline], - ); + const transactionThreadReportID = useMemo(() => getOneTransactionThreadReportID(report.reportID, reportActions ?? [], isOffline), [report.reportID, reportActions, isOffline]); const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`); const [isDebugModeEnabled] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.isDebugModeEnabled}); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const [session] = useOnyx(ONYXKEYS.SESSION); const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { - selector: (_transactions) => ReportUtils.reportTransactionsSelector(_transactions, report.reportID), + selector: (_transactions) => reportTransactionsSelector(_transactions, report.reportID), initialValue: [], }); @@ -119,34 +197,34 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const [offlineModalVisible, setOfflineModalVisible] = useState(false); const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false); const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`], [policies, report?.policyID]); - const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy), [policy]); - const isPolicyEmployee = useMemo(() => PolicyUtils.isPolicyEmployee(report?.policyID, policies), [report?.policyID, policies]); - const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(report), [report]); - const shouldUseFullTitle = useMemo(() => ReportUtils.shouldUseFullTitleToDisplay(report), [report]); - const isChatRoom = useMemo(() => ReportUtils.isChatRoom(report), [report]); - const isUserCreatedPolicyRoom = useMemo(() => ReportUtils.isUserCreatedPolicyRoom(report), [report]); - const isDefaultRoom = useMemo(() => ReportUtils.isDefaultRoom(report), [report]); - const isChatThread = useMemo(() => ReportUtils.isChatThread(report), [report]); - const isArchivedRoom = useMemo(() => ReportUtils.isArchivedNonExpenseReport(report, reportNameValuePairs), [report, reportNameValuePairs]); - const isMoneyRequestReport = useMemo(() => ReportUtils.isMoneyRequestReport(report), [report]); - const isMoneyRequest = useMemo(() => ReportUtils.isMoneyRequest(report), [report]); - const isInvoiceReport = useMemo(() => ReportUtils.isInvoiceReport(report), [report]); - const isInvoiceRoom = useMemo(() => ReportUtils.isInvoiceRoom(report), [report]); - const isTaskReport = useMemo(() => ReportUtils.isTaskReport(report), [report]); - const isSelfDM = useMemo(() => ReportUtils.isSelfDM(report), [report]); - const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(report); - const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID, report?.parentReportActionID); - const isCanceledTaskReport = ReportUtils.isCanceledTaskReport(report, parentReportAction); - const canEditReportDescription = useMemo(() => ReportUtils.canEditReportDescription(report, policy), [report, policy]); + const isPolicyAdmin = useMemo(() => isPolicyAdminUtil(policy), [policy]); + const isPolicyEmployee = useMemo(() => isPolicyEmployeeUtil(report?.policyID, policies), [report?.policyID, policies]); + const isPolicyExpenseChat = useMemo(() => isPolicyExpenseChatUtil(report), [report]); + const shouldUseFullTitle = useMemo(() => shouldUseFullTitleToDisplay(report), [report]); + const isChatRoom = useMemo(() => isChatRoomUtil(report), [report]); + const isUserCreatedPolicyRoom = useMemo(() => isUserCreatedPolicyRoomUtil(report), [report]); + const isDefaultRoom = useMemo(() => isDefaultRoomUtil(report), [report]); + const isChatThread = useMemo(() => isChatThreadUtil(report), [report]); + const isArchivedRoom = useMemo(() => isArchivedNonExpenseReport(report, reportNameValuePairs), [report, reportNameValuePairs]); + const isMoneyRequestReport = useMemo(() => isMoneyRequestReportUtil(report), [report]); + const isMoneyRequest = useMemo(() => isMoneyRequestUtil(report), [report]); + const isInvoiceReport = useMemo(() => isInvoiceReportUtil(report), [report]); + const isInvoiceRoom = useMemo(() => isInvoiceRoomUtil(report), [report]); + const isTaskReport = useMemo(() => isTaskReportUtil(report), [report]); + const isSelfDM = useMemo(() => isSelfDMUtil(report), [report]); + const isTrackExpenseReport = useMemo(() => isTrackExpenseReportUtil(report), [report]); + const parentReportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); + const isCanceledTaskReport = isCanceledTaskReportUtil(report, parentReportAction); + const canEditReportDescription = useMemo(() => canEditReportDescriptionUtil(report, policy), [report, policy]); const shouldShowReportDescription = isChatRoom && (canEditReportDescription || report.description !== ''); const isExpenseReport = isMoneyRequestReport || isInvoiceReport || isMoneyRequest; const isSingleTransactionView = isMoneyRequest || isTrackExpenseReport; - const isSelfDMTrackExpenseReport = isTrackExpenseReport && ReportUtils.isSelfDM(parentReport); - const shouldDisableRename = useMemo(() => ReportUtils.shouldDisableRename(report), [report]); - const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(report); + const isSelfDMTrackExpenseReport = isTrackExpenseReport && isSelfDMUtil(parentReport); + const shouldDisableRename = useMemo(() => shouldDisableRenameUtil(report), [report]); + const parentNavigationSubtitleData = getParentNavigationSubtitle(report); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- policy is a dependency because `getChatRoomSubtitle` calls `getPolicyName` which in turn retrieves the value from the `policy` value stored in Onyx const chatRoomSubtitle = useMemo(() => { - const subtitle = ReportUtils.getChatRoomSubtitle(report); + const subtitle = getChatRoomSubtitle(report); if (subtitle) { return subtitle; @@ -154,15 +232,15 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return ''; }, [report]); - const isSystemChat = useMemo(() => ReportUtils.isSystemChat(report), [report]); - const isGroupChat = useMemo(() => ReportUtils.isGroupChat(report), [report]); - const isRootGroupChat = useMemo(() => ReportUtils.isRootGroupChat(report), [report]); - const isThread = useMemo(() => ReportUtils.isThread(report), [report]); + const isSystemChat = useMemo(() => isSystemChatUtil(report), [report]); + const isGroupChat = useMemo(() => isGroupChatUtil(report), [report]); + const isRootGroupChat = useMemo(() => isRootGroupChatUtil(report), [report]); + const isThread = useMemo(() => isThreadUtil(report), [report]); const shouldOpenRoomMembersPage = isUserCreatedPolicyRoom || isChatThread || (isPolicyExpenseChat && isPolicyAdmin); const participants = useMemo(() => { - return ReportUtils.getParticipantsList(report, personalDetails, shouldOpenRoomMembersPage); + return getParticipantsList(report, personalDetails, shouldOpenRoomMembersPage); }, [report, personalDetails, shouldOpenRoomMembersPage]); - const connectedIntegration = PolicyUtils.getConnectedIntegration(policy); + const connectedIntegration = getConnectedIntegration(policy); const transactionIDList = useMemo(() => { if (!isMoneyRequestReport || !transactions) { @@ -208,7 +286,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const isActionOwner = typeof requestParentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && requestParentReportAction.actorAccountID === session?.accountID; - const isDeletedParentAction = ReportActionsUtils.isDeletedAction(requestParentReportAction); + const isDeletedParentAction = isDeletedAction(requestParentReportAction); const moneyRequestReport: OnyxEntry = useMemo(() => { if (caseID === CASES.MONEY_REQUEST) { @@ -219,21 +297,14 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const moneyRequestAction = transactionThreadReportID ? requestParentReportAction : parentReportAction; - const canModifyTask = Task.canModifyTask(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); - const canActionTask = Task.canActionTask(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); + const canModifyTask = canModifyTaskAction(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); + const canActionTask = canActionTaskAction(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); const shouldShowTaskDeleteButton = - isTaskReport && - !isCanceledTaskReport && - ReportUtils.canWriteInReport(report) && - report.stateNum !== CONST.REPORT.STATE_NUM.APPROVED && - !ReportUtils.isClosedReport(report) && - canModifyTask && - canActionTask; - const canDeleteRequest = isActionOwner && (ReportUtils.canDeleteTransaction(moneyRequestReport) || isSelfDMTrackExpenseReport) && !isDeletedParentAction; + isTaskReport && !isCanceledTaskReport && canWriteInReport(report) && report.stateNum !== CONST.REPORT.STATE_NUM.APPROVED && !isClosedReport(report) && canModifyTask && canActionTask; + const canDeleteRequest = isActionOwner && (canDeleteTransaction(moneyRequestReport) || isSelfDMTrackExpenseReport) && !isDeletedParentAction; const shouldShowDeleteButton = shouldShowTaskDeleteButton || canDeleteRequest; - const canUnapproveRequest = - ReportUtils.isExpenseReport(report) && (ReportUtils.isReportManager(report) || isPolicyAdmin) && ReportUtils.isReportApproved(report) && !PolicyUtils.isSubmitAndClose(policy); + const canUnapproveRequest = isExpenseReportUtil(report) && (isReportManagerUtil(report) || isPolicyAdmin) && isReportApprovedUtil(report) && !isSubmitAndClose(policy); useEffect(() => { if (canDeleteRequest) { @@ -249,23 +320,23 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } - Report.getReportPrivateNote(report?.reportID); + getReportPrivateNote(report?.reportID); }, [report?.reportID, isOffline, isPrivateNotesFetchTriggered, isSelfDM]); const leaveChat = useCallback(() => { Navigation.dismissModal(); Navigation.isNavigationReady().then(() => { if (isRootGroupChat) { - Report.leaveGroupChat(report.reportID); + leaveGroupChat(report.reportID); return; } const isWorkspaceMemberLeavingWorkspaceRoom = (report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED || isPolicyExpenseChat) && isPolicyEmployee; - Report.leaveRoom(report.reportID, isWorkspaceMemberLeavingWorkspaceRoom); + leaveRoom(report.reportID, isWorkspaceMemberLeavingWorkspaceRoom); }); }, [isPolicyEmployee, isPolicyExpenseChat, isRootGroupChat, report.reportID, report.visibility]); const [moneyRequestReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport?.reportID}`); - const isMoneyRequestExported = ReportUtils.isExported(moneyRequestReportActions); + const isMoneyRequestExported = isExported(moneyRequestReportActions); const {isDelegateAccessRestricted} = useDelegateUserDetails(); const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); @@ -277,16 +348,16 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } Navigation.dismissModal(); - IOU.unapproveExpenseReport(moneyRequestReport); + unapproveExpenseReport(moneyRequestReport); }, [isMoneyRequestExported, moneyRequestReport, isDelegateAccessRestricted]); - const shouldShowLeaveButton = ReportUtils.canLeaveChat(report, policy); - const shouldShowGoToWorkspace = PolicyUtils.shouldShowPolicy(policy, false, session?.email) && !policy?.isJoinRequestPending; + const shouldShowLeaveButton = canLeaveChat(report, policy); + const shouldShowGoToWorkspace = shouldShowPolicy(policy, false, session?.email) && !policy?.isJoinRequestPending; - const reportName = ReportUtils.getReportName(report); + const reportName = getReportName(report); const additionalRoomDetails = - (isPolicyExpenseChat && !!report?.isOwnPolicyExpenseChat) || ReportUtils.isExpenseReport(report) || isPolicyExpenseChat || isInvoiceRoom + (isPolicyExpenseChat && !!report?.isOwnPolicyExpenseChat) || isExpenseReportUtil(report) || isPolicyExpenseChat || isInvoiceRoom ? chatRoomSubtitle : `${translate('threads.in')} ${chatRoomSubtitle}`; @@ -299,24 +370,24 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta roomDescription = translate('newRoomPage.roomName'); } - const shouldShowNotificationPref = !isMoneyRequestReport && !ReportUtils.isHiddenForCurrentUser(report); + const shouldShowNotificationPref = !isMoneyRequestReport && !isHiddenForCurrentUser(report); const shouldShowWriteCapability = !isMoneyRequestReport; const shouldShowMenuItem = shouldShowNotificationPref || shouldShowWriteCapability || (!!report?.visibility && report.chatType !== CONST.REPORT.CHAT_TYPE.INVOICE); - const isPayer = ReportUtils.isPayer(session, moneyRequestReport); - const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); + const isPayer = isPayerUtil(session, moneyRequestReport); + const isSettled = isSettledUtil(moneyRequestReport?.reportID); - const shouldShowCancelPaymentButton = caseID === CASES.MONEY_REPORT && isPayer && isSettled && ReportUtils.isExpenseReport(moneyRequestReport); + const shouldShowCancelPaymentButton = caseID === CASES.MONEY_REPORT && isPayer && isSettled && isExpenseReportUtil(moneyRequestReport); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReport?.chatReportID}`); - const iouTransactionID = ReportActionsUtils.isMoneyRequestAction(requestParentReportAction) ? ReportActionsUtils.getOriginalMessage(requestParentReportAction)?.IOUTransactionID : ''; + const iouTransactionID = isMoneyRequestAction(requestParentReportAction) ? getOriginalMessage(requestParentReportAction)?.IOUTransactionID : ''; const cancelPayment = useCallback(() => { if (!chatReport) { return; } - IOU.cancelPayment(moneyRequestReport, chatReport); + cancelPaymentAction(moneyRequestReport, chatReport); setIsConfirmModalVisible(false); }, [moneyRequestReport, chatReport]); @@ -339,8 +410,8 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta (isGroupChat || (isDefaultRoom && isChatThread && isPolicyEmployee) || (!isUserCreatedPolicyRoom && participants.length) || - (isUserCreatedPolicyRoom && (isPolicyEmployee || (isChatThread && !ReportUtils.isPublicRoom(report))))) && - !ReportUtils.isConciergeChatReport(report) && + (isUserCreatedPolicyRoom && (isPolicyEmployee || (isChatThread && !isPublicRoomUtil(report))))) && + !isConciergeChatReport(report) && !isSystemChat ) { items.push({ @@ -385,8 +456,8 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta } if (isTrackExpenseReport && !isDeletedParentAction) { - const actionReportID = ReportUtils.getOriginalReportID(report.reportID, parentReportAction); - const whisperAction = ReportActionsUtils.getTrackExpenseActionableWhisper(iouTransactionID, moneyRequestReport?.reportID); + const actionReportID = getOriginalReportID(report.reportID, parentReportAction); + const whisperAction = getTrackExpenseActionableWhisper(iouTransactionID, moneyRequestReport?.reportID); const actionableWhisperReportActionID = whisperAction?.reportActionID; items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.SETTINGS, @@ -395,7 +466,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isAnonymousAction: false, shouldShowRightIcon: true, action: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.SUBMIT, actionableWhisperReportActionID); + createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.SUBMIT, actionableWhisperReportActionID); }, }); items.push({ @@ -405,7 +476,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isAnonymousAction: false, shouldShowRightIcon: true, action: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.CATEGORIZE, actionableWhisperReportActionID); + createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.CATEGORIZE, actionableWhisperReportActionID); }, }); items.push({ @@ -415,7 +486,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isAnonymousAction: false, shouldShowRightIcon: true, action: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.SHARE, actionableWhisperReportActionID); + createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.SHARE, actionableWhisperReportActionID); }, }); } @@ -428,22 +499,22 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta icon: Expensicons.Pencil, isAnonymousAction: false, shouldShowRightIcon: true, - action: () => ReportUtils.navigateToPrivateNotes(report, session, backTo), - brickRoadIndicator: Report.hasErrorInPrivateNotes(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + action: () => navigateToPrivateNotes(report, session, backTo), + brickRoadIndicator: hasErrorInPrivateNotes(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, }); } // Show actions related to Task Reports if (isTaskReport && !isCanceledTaskReport) { - if (ReportUtils.isCompletedTaskReport(report) && canModifyTask && canActionTask) { + if (isCompletedTaskReport(report) && canModifyTask && canActionTask) { items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.MARK_AS_INCOMPLETE, icon: Expensicons.Checkmark, translationKey: 'task.markAsIncomplete', isAnonymousAction: false, - action: Session.checkIfActionIsAllowed(() => { + action: checkIfActionIsAllowed(() => { Navigation.dismissModal(); - Task.reopenTask(report); + reopenTask(report); }), }); } @@ -471,7 +542,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } - ReportActions.exportReportToCSV({reportID: report.reportID, transactionIDList}, () => { + exportReportToCSV({reportID: report.reportID, transactionIDList}, () => { setDownloadErrorModalVisible(true); }); }, @@ -527,7 +598,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta icon: Expensicons.Exit, isAnonymousAction: true, action: () => { - if (ReportUtils.getParticipantsAccountIDsForDisplay(report, false, true).length === 1 && isRootGroupChat) { + if (getParticipantsAccountIDsForDisplay(report, false, true).length === 1 && isRootGroupChat) { setIsLastMemberLeavingGroupModalVisible(true); return; } @@ -597,10 +668,10 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const displayNamesWithTooltips = useMemo(() => { const hasMultipleParticipants = participants.length > 1; - return ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails), hasMultipleParticipants); + return getDisplayNamesWithTooltips(getPersonalDetailsForAccountIDs(participants, personalDetails), hasMultipleParticipants); }, [participants, personalDetails]); - const icons = useMemo(() => ReportUtils.getIcons(report, personalDetails, null, '', -1, policy), [report, personalDetails, policy]); + const icons = useMemo(() => getIcons(report, personalDetails, null, '', -1, policy), [report, personalDetails, policy]); const chatRoomSubtitleText = chatRoomSubtitle ? ( Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(report.reportID))} onImageRemoved={() => { // Calling this without a file will remove the avatar - Report.updateGroupChatAvatar(report.reportID); + updateGroupChatAvatar(report.reportID); }} - onImageSelected={(file) => Report.updateGroupChatAvatar(report.reportID, file)} + onImageSelected={(file) => updateGroupChatAvatar(report.reportID, file)} editIcon={Expensicons.Camera} editIconStyle={styles.smallEditIconAccount} pendingAction={report.pendingFields?.avatar ?? undefined} errors={report.errorFields?.avatar ?? null} errorRowStyles={styles.mt6} - onErrorClose={() => Report.clearAvatarErrors(report.reportID)} + onErrorClose={() => clearAvatarErrors(report.reportID)} shouldUseStyleUtilityForAnchorPosition style={[styles.w100, styles.mb3]} /> @@ -666,12 +737,12 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta ); }, [report, icons, isMoneyRequestReport, isInvoiceReport, isGroupChat, isThread, styles]); - const canHoldUnholdReportAction = ReportUtils.canHoldUnholdReportAction(moneyRequestAction); + const canHoldUnholdReportAction = canHoldUnholdReportActionUtil(moneyRequestAction); const shouldShowHoldAction = caseID !== CASES.DEFAULT && (canHoldUnholdReportAction.canHoldRequest || canHoldUnholdReportAction.canUnholdRequest) && - !ReportUtils.isArchivedNonExpenseReport(transactionThreadReportID ? report : parentReport, parentReportNameValuePairs); - const canJoin = ReportUtils.canJoinChat(report, parentReportAction, policy); + !isArchivedNonExpenseReport(transactionThreadReportID ? report : parentReport, parentReportNameValuePairs); + const canJoin = canJoinChat(report, parentReportAction, policy); const promotedActions = useMemo(() => { const result: PromotedAction[] = []; @@ -767,7 +838,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta pendingAction={report?.pendingFields?.reportName} errors={report?.errorFields?.reportName} errorRowStyles={[styles.ph5]} - onClose={() => Report.clearPolicyRoomNameErrors(report?.reportID)} + onClose={() => clearPolicyRoomNameErrors(report?.reportID)} > ((): OnyxTypes.PolicyReportField | undefined => { - const fields = ReportUtils.getAvailableReportFields(report, Object.values(policy?.fieldList ?? {})); - return fields.find((reportField) => ReportUtils.isReportFieldOfTypeTitle(reportField)); + const fields = getAvailableReportFields(report, Object.values(policy?.fieldList ?? {})); + return fields.find((reportField) => isReportFieldOfTypeTitle(reportField)); }, [report, policy?.fieldList]); - const fieldKey = ReportUtils.getReportFieldKey(titleField?.fieldID); - const isFieldDisabled = ReportUtils.isReportFieldDisabled(report, titleField, policy); + const fieldKey = getReportFieldKey(titleField?.fieldID); + const isFieldDisabled = isReportFieldDisabled(report, titleField, policy); - const shouldShowTitleField = caseID !== CASES.MONEY_REQUEST && !isFieldDisabled && ReportUtils.isAdminOwnerApproverOrReportOwner(report, policy); + const shouldShowTitleField = caseID !== CASES.MONEY_REQUEST && !isFieldDisabled && isAdminOwnerApproverOrReportOwner(report, policy); const nameSectionFurtherDetailsContent = ( { if (report.errorFields?.reportName) { - Report.clearPolicyRoomNameErrors(report.reportID); + clearPolicyRoomNameErrors(report.reportID); } - Report.clearReportFieldKeyErrors(report.reportID, fieldKey); + clearReportFieldKeyErrors(report.reportID, fieldKey); }} > @@ -843,7 +914,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const deleteTransaction = useCallback(() => { if (caseID === CASES.DEFAULT) { - Task.deleteTask(report); + deleteTask(report); return; } @@ -851,12 +922,12 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } - const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + const isTrackExpense = isTrackExpenseAction(requestParentReportAction); if (isTrackExpense) { - IOU.deleteTrackExpense(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); + deleteTrackExpense(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); } else { - IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); + deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); } }, [caseID, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, report, requestParentReportAction]); @@ -886,19 +957,19 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta // Only proceed with navigation logic if transaction was actually deleted if (!isEmptyObject(requestParentReportAction)) { - const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + const isTrackExpense = isTrackExpenseAction(requestParentReportAction); if (isTrackExpense) { - urlToNavigateBack = IOU.getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); + urlToNavigateBack = getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); } else { - urlToNavigateBack = IOU.getNavigationUrlOnMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); + urlToNavigateBack = getNavigationUrlOnMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); } } if (!urlToNavigateBack) { Navigation.dismissModal(); } else { - Report.setDeleteTransactionNavigateBackUrl(urlToNavigateBack); - ReportUtils.navigateBackOnDeleteTransaction(urlToNavigateBack as Route, true); + setDeleteTransactionNavigateBackUrl(urlToNavigateBack); + navigateBackOnDeleteTransaction(urlToNavigateBack as Route, true); } }, [iouTransactionID, requestParentReportAction, isSingleTransactionView, isTransactionDeleted, moneyRequestReport?.reportID]); @@ -927,7 +998,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta { setIsUnapproveModalVisible(false); Navigation.dismissModal(); - IOU.unapproveExpenseReport(moneyRequestReport); + unapproveExpenseReport(moneyRequestReport); }} cancelText={translate('common.cancel')} onCancel={() => setIsUnapproveModalVisible(false)} From 2c82b011500782b3eaff9532166d304b3dd6e5ec Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 20 Jan 2025 12:55:42 +0100 Subject: [PATCH 06/12] fix typecheck --- tests/actions/EnforceActionExportRestrictions.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/actions/EnforceActionExportRestrictions.ts b/tests/actions/EnforceActionExportRestrictions.ts index 142f1df6f367..53be4a961540 100644 --- a/tests/actions/EnforceActionExportRestrictions.ts +++ b/tests/actions/EnforceActionExportRestrictions.ts @@ -20,7 +20,6 @@ describe('ReportUtils', () => { }); it('does not export getReportTransactions', () => { - // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal expect(ReportUtils.getReportTransactions).toBeUndefined(); }); From 784e0e9c8c6dd585ab150a6308a65a3bc681aece Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 20 Jan 2025 12:59:04 +0100 Subject: [PATCH 07/12] fix tests --- tests/actions/EnforceActionExportRestrictions.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/actions/EnforceActionExportRestrictions.ts b/tests/actions/EnforceActionExportRestrictions.ts index 53be4a961540..bead431d030f 100644 --- a/tests/actions/EnforceActionExportRestrictions.ts +++ b/tests/actions/EnforceActionExportRestrictions.ts @@ -19,10 +19,6 @@ describe('ReportUtils', () => { expect(ReportUtils.getReport).toBeUndefined(); }); - it('does not export getReportTransactions', () => { - expect(ReportUtils.getReportTransactions).toBeUndefined(); - }); - it('does not export isOneTransactionReport', () => { // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal expect(ReportUtils.isOneTransactionReport).toBeUndefined(); From 9effd36d2772bb786f88c70625f5cba5d3f10569 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Mon, 20 Jan 2025 13:06:07 +0100 Subject: [PATCH 08/12] fix lint changed --- tests/actions/EnforceActionExportRestrictions.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/actions/EnforceActionExportRestrictions.ts b/tests/actions/EnforceActionExportRestrictions.ts index bead431d030f..b8a64dd883d8 100644 --- a/tests/actions/EnforceActionExportRestrictions.ts +++ b/tests/actions/EnforceActionExportRestrictions.ts @@ -1,3 +1,6 @@ +// this file is for testing which methods should not be exported so it is not possible to use named imports - that's why we need to disable the no-restricted-syntax rule + +/* eslint-disable no-restricted-syntax */ import * as IOU from '@libs/actions/IOU'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; From ae77d7e92bd962a323b449899672569d79d37ee3 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Tue, 21 Jan 2025 08:53:41 +0100 Subject: [PATCH 09/12] use every instead of some --- src/components/MoneyReportHeader.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 2e2fa2be2d91..423398141ee2 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -147,10 +147,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const hasHeldExpenses = hasHeldExpensesReportUtils(moneyRequestReport?.reportID); const hasScanningReceipt = getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => isReceiptBeingScanned(t)); const hasOnlyPendingTransactions = useMemo(() => { - return !transactions?.some((t) => { - const isTransactionPending = isExpensifyCardTransaction(t) && isPending(t); - return !isTransactionPending; - }); + return transactions?.every((t) => isExpensifyCardTransaction(t) && isPending(t)); }, [transactions]); const transactionIDs = transactions?.map((t) => t.transactionID) ?? []; const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs); From 8fc0d315b3e61e58ac25420b05e5ccf891762fd7 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Tue, 21 Jan 2025 09:38:20 +0100 Subject: [PATCH 10/12] fix linter --- src/components/MoneyReportHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 423398141ee2..a92169053a1c 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -205,7 +205,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(moneyRequestReport, shouldShowPayButton); const isAnyTransactionOnHold = hasHeldExpensesReportUtils(moneyRequestReport?.reportID); const displayedAmount = isAnyTransactionOnHold && canAllowSettlement && hasValidNonHeldAmount ? nonHeldAmount : formattedAmount; - const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout); + const isMoreContentShown = shouldShowNextStep ?? shouldShowStatusBar ?? (shouldShowAnyButton && shouldUseNarrowLayout); const {isDelegateAccessRestricted} = useDelegateUserDetails(); const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); From 1c30c60fa4a9b6b1fd55e451e0e4f1a8d34a36cf Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 22 Jan 2025 16:10:57 +0100 Subject: [PATCH 11/12] code review updates --- src/components/MoneyReportHeader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index a92169053a1c..a89a40f0c8a1 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -147,7 +147,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const hasHeldExpenses = hasHeldExpensesReportUtils(moneyRequestReport?.reportID); const hasScanningReceipt = getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => isReceiptBeingScanned(t)); const hasOnlyPendingTransactions = useMemo(() => { - return transactions?.every((t) => isExpensifyCardTransaction(t) && isPending(t)); + return transactions?.every((t) => isExpensifyCardTransaction(t) && isPending(t)) ?? false; }, [transactions]); const transactionIDs = transactions?.map((t) => t.transactionID) ?? []; const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs); @@ -205,7 +205,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(moneyRequestReport, shouldShowPayButton); const isAnyTransactionOnHold = hasHeldExpensesReportUtils(moneyRequestReport?.reportID); const displayedAmount = isAnyTransactionOnHold && canAllowSettlement && hasValidNonHeldAmount ? nonHeldAmount : formattedAmount; - const isMoreContentShown = shouldShowNextStep ?? shouldShowStatusBar ?? (shouldShowAnyButton && shouldUseNarrowLayout); + const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout); const {isDelegateAccessRestricted} = useDelegateUserDetails(); const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); From 596e731e07d7cd7959d0784cc33a243643b0f2d3 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 22 Jan 2025 16:42:22 +0100 Subject: [PATCH 12/12] check transactions length --- src/components/MoneyReportHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index a89a40f0c8a1..73ed11b5ea22 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -147,7 +147,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const hasHeldExpenses = hasHeldExpensesReportUtils(moneyRequestReport?.reportID); const hasScanningReceipt = getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => isReceiptBeingScanned(t)); const hasOnlyPendingTransactions = useMemo(() => { - return transactions?.every((t) => isExpensifyCardTransaction(t) && isPending(t)) ?? false; + return !!transactions && transactions.length > 0 && transactions.every((t) => isExpensifyCardTransaction(t) && isPending(t)); }, [transactions]); const transactionIDs = transactions?.map((t) => t.transactionID) ?? []; const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs);