diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 218c8fee2901..3a81e82d457a 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -11,7 +11,6 @@ import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import useTransactionViolations from '@hooks/useTransactionViolations'; import {deleteMoneyRequest, deleteTrackExpense, initSplitExpense} from '@libs/actions/IOU'; import {setupMergeTransactionData} from '@libs/actions/MergeTransaction'; import Navigation from '@libs/Navigation/Navigation'; @@ -34,7 +33,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; -import type {Policy, Report, ReportAction} from '@src/types/onyx'; +import type {Policy, Report, ReportAction, TransactionViolation} from '@src/types/onyx'; import type IconAsset from '@src/types/utils/IconAsset'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import BrokenConnectionDescription from './BrokenConnectionDescription'; @@ -65,9 +64,12 @@ type MoneyRequestHeaderProps = { /** Method to trigger when pressing close button of the header */ onBackButtonPress: () => void; + + /** Precomputed violations for the transaction */ + transactionViolations?: TransactionViolation[]; }; -function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPress}: MoneyRequestHeaderProps) { +function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPress, transactionViolations = []}: MoneyRequestHeaderProps) { // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to use a correct layout for the hold expense modal https://github.com/Expensify/App/pull/47990#issuecomment-2362382026 // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); @@ -81,7 +83,6 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre }`, {canBeMissing: true}, ); - const transactionViolations = useTransactionViolations(transaction?.transactionID); const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(transaction?.transactionID ? [transaction.transactionID] : []); const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false); diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index 95182e0950b5..6744ee67e82c 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -13,6 +13,7 @@ import Text from '@components/Text'; import useCopySelectionHelper from '@hooks/useCopySelectionHelper'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; +import useReportWithTransactionsAndViolations from '@hooks/useReportWithTransactionsAndViolations'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -100,6 +101,7 @@ function MoneyRequestReportTransactionList({ const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate, localeCompare} = useLocalize(); + const [, , violations] = useReportWithTransactionsAndViolations(report.reportID); // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth, isMediumScreenWidth} = useResponsiveLayout(); const [isModalVisible, setIsModalVisible] = useState(false); @@ -157,8 +159,9 @@ function MoneyRequestReportTransactionList({ .map((transaction) => ({ ...transaction, shouldBeHighlighted: newTransactions?.includes(transaction), + violations: violations?.[transaction.transactionID] ?? [], })); - }, [newTransactions, sortBy, sortOrder, transactions, localeCompare]); + }, [newTransactions, sortBy, sortOrder, transactions, localeCompare, violations]); const navigateToTransaction = useCallback( (activeTransactionID: string) => { diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx index ec5710093bb3..4048f6411844 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportView.tsx @@ -19,7 +19,7 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import Log from '@libs/Log'; import {getAllNonDeletedTransactions, shouldDisplayReportTableView, shouldWaitForTransactions as shouldWaitForTransactionsUtil} from '@libs/MoneyRequestReportUtils'; import navigationRef from '@libs/Navigation/navigationRef'; -import {getFilteredReportActionsForReportView, getOneTransactionThreadReportID, isMoneyRequestAction} from '@libs/ReportActionsUtils'; +import {getFilteredReportActionsForReportView, getOneTransactionThreadReportID, getTransactionIDFromReportAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {canEditReportAction, getReportOfflinePendingActionAndErrors, isReportTransactionThread} from '@libs/ReportUtils'; import {buildCannedSearchQuery} from '@libs/SearchQueryUtils'; import Navigation from '@navigation/Navigation'; @@ -99,7 +99,7 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe const {reportActions: unfilteredReportActions, hasNewerActions, hasOlderActions} = usePaginatedReportActions(reportID); const reportActions = getFilteredReportActionsForReportView(unfilteredReportActions); - const {transactions: reportTransactions} = useTransactionsAndViolationsForReport(reportID); + const {transactions: reportTransactions, violations: reportViolations} = useTransactionsAndViolationsForReport(reportID); const transactions = useMemo(() => getAllNonDeletedTransactions(reportTransactions, reportActions), [reportTransactions, reportActions]); const visibleTransactions = transactions?.filter((transaction) => isOffline || transaction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); @@ -133,13 +133,18 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe const isEmptyTransactionReport = visibleTransactions && visibleTransactions.length === 0 && transactionThreadReportID === undefined; const shouldDisplayMoneyRequestActionsList = !!isEmptyTransactionReport || shouldDisplayReportTableView(report, visibleTransactions ?? []); - const reportHeaderView = useMemo( - () => - isTransactionThreadView ? ( + const reportHeaderView = useMemo(() => { + if (isTransactionThreadView) { + // Extract transaction ID from parent report action to get specific violations + const transactionID = getTransactionIDFromReportAction(parentReportAction); + const transactionViolations = transactionID && reportViolations ? (reportViolations as Record)[transactionID] : undefined; + + return ( { if (!backToRoute) { goBackFromSearchMoneyRequest(); @@ -148,25 +153,27 @@ function MoneyRequestReportView({report, policy, reportMetadata, shouldDisplayRe Navigation.goBack(backToRoute); }} /> - ) : ( - { - if (!backToRoute) { - goBackFromSearchMoneyRequest(); - return; - } - Navigation.goBack(backToRoute); - }} - /> - ), - [backToRoute, isLoadingInitialReportActions, isTransactionThreadView, parentReportAction, policy, report, reportActions, transactionThreadReportID], - ); + ); + } + + return ( + { + if (!backToRoute) { + goBackFromSearchMoneyRequest(); + return; + } + Navigation.goBack(backToRoute); + }} + /> + ); + }, [backToRoute, isLoadingInitialReportActions, isTransactionThreadView, parentReportAction, policy, report, reportActions, reportViolations, transactionThreadReportID]); if (!!(isLoadingInitialReportActions && reportActions.length === 0 && !isOffline) || shouldWaitForTransactions) { return ; diff --git a/src/components/TransactionItemRow/TransactionItemRowRBRWithOnyx.tsx b/src/components/TransactionItemRow/TransactionItemRowRBRWithOnyx.tsx index 383d9b440d6d..e6765f1852b4 100644 --- a/src/components/TransactionItemRow/TransactionItemRowRBRWithOnyx.tsx +++ b/src/components/TransactionItemRow/TransactionItemRowRBRWithOnyx.tsx @@ -8,12 +8,11 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import useTransactionViolations from '@hooks/useTransactionViolations'; import {getIOUActionForTransactionID} from '@libs/ReportActionsUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report} from '@src/types/onyx'; +import type {Report, TransactionViolation} from '@src/types/onyx'; import type Transaction from '@src/types/onyx/Transaction'; type TransactionItemRowRBRProps = { @@ -28,11 +27,13 @@ type TransactionItemRowRBRProps = { /** Error message for missing required fields in the transaction */ missingFieldError?: string; + + /** Precomputed violations for the transaction */ + violations?: TransactionViolation[]; }; -function TransactionItemRowRBRWithOnyx({transaction, report, containerStyles, missingFieldError}: TransactionItemRowRBRProps) { +function TransactionItemRowRBRWithOnyx({transaction, report, containerStyles, missingFieldError, violations = []}: TransactionItemRowRBRProps) { const styles = useThemeStyles(); - const transactionViolations = useTransactionViolations(transaction?.transactionID, false); const {translate} = useLocalize(); const theme = useTheme(); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction.reportID}`, { @@ -44,7 +45,7 @@ function TransactionItemRowRBRWithOnyx({transaction, report, containerStyles, mi canBeMissing: true, }); - const RBRMessages = ViolationsUtils.getRBRMessages(transaction, transactionViolations, translate, missingFieldError, Object.values(transactionThreadActions ?? {}), policyTags); + const RBRMessages = ViolationsUtils.getRBRMessages(transaction, violations, translate, missingFieldError, Object.values(transactionThreadActions ?? {}), policyTags); return ( RBRMessages.length > 0 && ( diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx index 059fba133801..f05abd9ee565 100644 --- a/src/components/TransactionItemRow/index.tsx +++ b/src/components/TransactionItemRow/index.tsx @@ -453,6 +453,7 @@ function TransactionItemRow({ report={report} containerStyles={[styles.mt2, styles.minHeight4]} missingFieldError={missingFieldError} + violations={transactionItem.violations} /> )} @@ -501,6 +502,7 @@ function TransactionItemRow({ transaction={transactionItem} report={report} missingFieldError={missingFieldError} + violations={transactionItem.violations} /> )} diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 2ea06e8a137c..81d3ff4b2c26 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -267,6 +267,13 @@ function getOriginalMessage(reportAction: OnyxInputO return reportAction.originalMessage; } +/** + * Get the transaction ID from a money request report action + */ +function getTransactionIDFromReportAction(reportAction: OnyxInputOrEntry): string | undefined { + return isMoneyRequestAction(reportAction) ? getOriginalMessage(reportAction)?.IOUTransactionID : undefined; +} + function isExportIntegrationAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.EXPORTED_TO_INTEGRATION; } @@ -3021,6 +3028,7 @@ export { getSortedReportActionsForDisplay, getTextFromHtml, getTrackExpenseActionableWhisper, + getTransactionIDFromReportAction, getWhisperedTo, hasRequestFromCurrentAccount, isActionOfType, diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index e6df82f391a6..7cfa27c416a7 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -43,6 +43,7 @@ import { getCombinedReportActions, getFilteredReportActionsForReportView, getOneTransactionThreadReportID, + getTransactionIDFromReportAction, isCreatedAction, isDeletedParentAction, isMoneyRequestAction, @@ -295,7 +296,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // If the count is too high (equal to or exceeds the web pagination size / 50) and there are no cached messages in the report, // OpenReport will be called each time the user scrolls up the report a bit, clicks on report preview, and then goes back. const isLinkedMessagePageReady = isLinkedMessageAvailable && (reportActions.length - indexOfLinkedMessage >= CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || doesCreatedActionExists()); - const {transactions: allReportTransactions} = useTransactionsAndViolationsForReport(reportIDFromRoute); + const {transactions: allReportTransactions, violations: allReportViolations} = useTransactionsAndViolationsForReport(reportIDFromRoute); const reportTransactions = useMemo(() => getAllNonDeletedTransactions(allReportTransactions, reportActions), [allReportTransactions, reportActions]); // wrapping in useMemo because this is array operation and can cause performance issues @@ -370,12 +371,16 @@ function ReportScreen({route, navigation}: ReportScreenProps) { ); if (isTransactionThreadView) { + const transactionID = getTransactionIDFromReportAction(parentReportAction); + const transactionViolations = transactionID && allReportViolations ? (allReportViolations as Record)[transactionID] : undefined; + headerView = ( ); }