diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 6fc2f4012f820..0c5d3b3d68dea 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -5,7 +5,6 @@ import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; import React, {memo, useCallback, useContext, useEffect, useMemo, useRef} from 'react'; import {StyleSheet, View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; import type {BlockingViewProps} from '@components/BlockingViews/BlockingView'; import BlockingView from '@components/BlockingViews/BlockingView'; import Icon from '@components/Icon'; @@ -24,9 +23,6 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import getPlatform from '@libs/getPlatform'; import Log from '@libs/Log'; -import {getIOUReportIDOfLastAction} from '@libs/OptionsListUtils'; -import {getLastVisibleActionIncludingTransactionThread, getOriginalMessage, isActionableTrackExpense, isInviteOrRemovedAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; -import {canUserPerformWriteAction as canUserPerformWriteActionUtil} from '@libs/ReportUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; @@ -51,17 +47,14 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); const reportAttributes = useReportAttributes(); const [reportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS); - const [reportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS); const [policy] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); - const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [onboarding] = useOnyx(ONYXKEYS.NVP_ONBOARDING); const [isFullscreenVisible] = useOnyx(ONYXKEYS.FULLSCREEN_VISIBILITY); - const [visibleReportActionsData] = useOnyx(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS); const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); const theme = useTheme(); @@ -165,9 +158,6 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const reportID = item.reportID; const itemParentReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${item.parentReportID}`]; const itemReportNameValuePairs = reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`]; - const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; - const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${item?.parentReportID}`]; - const itemParentReportAction = item?.parentReportActionID ? itemParentReportActions?.[item?.parentReportActionID] : undefined; let invoiceReceiverPolicyID = '-1'; if (item?.invoiceReceiver && 'policyID' in item.invoiceReceiver) { @@ -179,63 +169,22 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const itemInvoiceReceiverPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`]; const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${item?.policyID}`]; - const transactionID = isMoneyRequestAction(itemParentReportAction) - ? (getOriginalMessage(itemParentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID) - : CONST.DEFAULT_NUMBER_ID; - const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; const hasDraftComment = !!draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] && !draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`]?.match(CONST.REGEX.EMPTY_COMMENT); - const isReportArchived = !!itemReportNameValuePairs?.private_isArchived; - const canUserPerformWrite = canUserPerformWriteActionUtil(item, isReportArchived); - - const lastAction = getLastVisibleActionIncludingTransactionThread( - reportID, - canUserPerformWrite, - reportActions, - visibleReportActionsData, - reportAttributes?.[reportID]?.oneTransactionThreadReportID, - ); - - const iouReportIDOfLastAction = getIOUReportIDOfLastAction(item, itemReportNameValuePairs?.private_isArchived, visibleReportActionsData, lastAction); - const itemIouReportReportActions = iouReportIDOfLastAction ? reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportIDOfLastAction}`] : undefined; - - const lastReportActionTransactionID = isMoneyRequestAction(lastAction) ? (getOriginalMessage(lastAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID) : CONST.DEFAULT_NUMBER_ID; - const lastReportActionTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${lastReportActionTransactionID}`]; - - // Only override lastMessageTextFromReport when a track expense whisper's transaction has been deleted, to prevent showing stale text. - let lastMessageTextFromReport: string | undefined; - if (isActionableTrackExpense(lastAction)) { - const whisperTransactionID = getOriginalMessage(lastAction)?.transactionID; - if (whisperTransactionID && !transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${whisperTransactionID}`]) { - lastMessageTextFromReport = ''; - } - } const shouldShowRBRorGBRTooltip = firstReportIDWithGBRorRBR === reportID; - let lastActionReport: OnyxEntry | undefined; - if (isInviteOrRemovedAction(lastAction)) { - const lastActionOriginalMessage = lastAction?.actionName ? getOriginalMessage(lastAction) : null; - lastActionReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${lastActionOriginalMessage?.reportID}`]; - } - return ( ); }, [ - reportAttributes, reports, reportNameValuePairs, - reportActions, policy, - transactions, draftComments, personalDetails, firstReportIDWithGBRorRBR, @@ -282,14 +225,12 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio isScreenFocused, localeCompare, translate, - visibleReportActionsData, currentUserAccountID, ], ); const extraData = useMemo( () => [ - reportActions, reports, reportNameValuePairs, transactionViolations, @@ -299,14 +240,11 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio draftComments, optionMode, preferredLocale, - transactions, isOffline, isScreenFocused, isReportsSplitNavigatorLast, - visibleReportActionsData, ], [ - reportActions, reports, reportNameValuePairs, transactionViolations, @@ -316,11 +254,9 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio draftComments, optionMode, preferredLocale, - transactions, isOffline, isScreenFocused, isReportsSplitNavigatorLast, - visibleReportActionsData, ], ); @@ -376,14 +312,13 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio if (shouldShowEmptyLHN) { Log.info('Woohoo! All caught up. Was rendered', false, { reportsCount: Object.keys(reports ?? {}).length, - reportActionsCount: Object.keys(reportActions ?? {}).length, policyCount: Object.keys(policy ?? {}).length, personalDetailsCount: Object.keys(personalDetails ?? {}).length, route, reportsIDsFromUseReportsCount: data.length, }); } - }, [data.length, shouldShowEmptyLHN, route, reports, reportActions, policy, personalDetails]); + }, [data.length, shouldShowEmptyLHN, route, reports, policy, personalDetails]); return ( diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 1b7243cb6b1d6..1696a7620111f 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -1,5 +1,6 @@ import {deepEqual} from 'fast-equals'; import React, {useCallback, useMemo, useRef} from 'react'; +import type {OnyxCollection} from 'react-native-onyx'; import useReportPreviewSenderID from '@components/ReportActionAvatars/useReportPreviewSenderID'; import {useCurrentReportIDState} from '@hooks/useCurrentReportID'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -7,11 +8,15 @@ import useGetExpensifyCardFromReportAction from '@hooks/useGetExpensifyCardFromR import useOnyx from '@hooks/useOnyx'; import usePrevious from '@hooks/usePrevious'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; +import {getIOUReportIDOfLastAction} from '@libs/OptionsListUtils'; +import {getLastVisibleActionIncludingTransactionThread, getOriginalMessage, isActionableTrackExpense, isInviteOrRemovedAction, isMoneyRequestAction} from '@libs/ReportActionsUtils'; +import {canUserPerformWriteAction as canUserPerformWriteActionUtil} from '@libs/ReportUtils'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import {getMovedReportID} from '@src/libs/ModifiedExpenseMessage'; import type {OptionData} from '@src/libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReportActions as ReportActionsType, VisibleReportActionsDerivedValue} from '@src/types/onyx'; import type {ReportAttributesDerivedValue} from '@src/types/onyx/DerivedValues'; import type {Icon} from '@src/types/onyx/OnyxCommon'; import OptionRowLHN from './OptionRowLHN'; @@ -27,22 +32,13 @@ function OptionRowLHNData({ isOptionFocused = false, fullReport, reportNameValuePairs, - reportActions, personalDetails = {}, preferredLocale = CONST.LOCALES.DEFAULT, policy, invoiceReceiverPolicy, - parentReportAction, - iouReportReportActions, - transaction, - lastReportActionTransaction, transactionViolations, - lastMessageTextFromReport, localeCompare, translate, - isReportArchived = false, - lastAction, - lastActionReport, currentUserAccountID, ...propsToForward }: OptionRowLHNDataProps) { @@ -51,12 +47,91 @@ function OptionRowLHNData({ const isReportFocused = isOptionFocused && currentReportIDValue === reportID; const optionItemRef = useRef(undefined); + // Per-item scoped subscriptions const reportAttributesSelector = useCallback((data: ReportAttributesDerivedValue | undefined) => data?.reports?.[reportID], [reportID]); const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: reportAttributesSelector}); // Look up the one-transaction thread report using the ID from our own attributes. const [oneTransactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(reportAttributes?.oneTransactionThreadReportID)}`); + const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`); + const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(fullReport?.parentReportID)}`); + const [transactionThreadReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(oneTransactionThreadReport?.reportID)}`); + + // Scoped VISIBLE_REPORT_ACTIONS selector — only picks entries for this report and its transaction thread + const visibleActionsSelector = useCallback( + (data: VisibleReportActionsDerivedValue | undefined) => { + if (!data) { + return undefined; + } + const result: VisibleReportActionsDerivedValue = {}; + const reportEntry = data[reportID]; + if (reportEntry) { + result[reportID] = reportEntry; + } + const txThreadID = oneTransactionThreadReport?.reportID; + if (txThreadID) { + const txThreadEntry = data[txThreadID]; + if (txThreadEntry) { + result[txThreadID] = txThreadEntry; + } + } + return result; + }, + [reportID, oneTransactionThreadReport?.reportID], + ); + const [visibleReportActionsData] = useOnyx(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS, {selector: visibleActionsSelector}); + + const [reportNameValuePairsEntry] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`); + + const parentReportAction = fullReport?.parentReportActionID ? parentReportActions?.[fullReport.parentReportActionID] : undefined; + + const transactionID = isMoneyRequestAction(parentReportAction) ? (getOriginalMessage(parentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID) : CONST.DEFAULT_NUMBER_ID; + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID !== CONST.DEFAULT_NUMBER_ID ? String(transactionID) : undefined)}`); + + const isReportArchived = !!(reportNameValuePairsEntry ?? reportNameValuePairs)?.private_isArchived; + const canUserPerformWrite = canUserPerformWriteActionUtil(fullReport, isReportArchived); + + const lastAction = useMemo(() => { + const actionsCollection: OnyxCollection = { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: reportActions ?? undefined, + }; + if (oneTransactionThreadReport?.reportID) { + actionsCollection[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneTransactionThreadReport.reportID}`] = transactionThreadReportActions ?? undefined; + } + return getLastVisibleActionIncludingTransactionThread(reportID, canUserPerformWrite, actionsCollection, visibleReportActionsData, oneTransactionThreadReport?.reportID); + }, [reportID, canUserPerformWrite, reportActions, transactionThreadReportActions, visibleReportActionsData, oneTransactionThreadReport?.reportID]); + + const iouReportIDOfLastAction = useMemo( + () => getIOUReportIDOfLastAction(fullReport, (reportNameValuePairsEntry ?? reportNameValuePairs)?.private_isArchived, visibleReportActionsData, lastAction), + [fullReport, reportNameValuePairsEntry, reportNameValuePairs, visibleReportActionsData, lastAction], + ); + const [iouReportReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(iouReportIDOfLastAction)}`); + + const lastReportActionTransactionID = isMoneyRequestAction(lastAction) ? (getOriginalMessage(lastAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID) : CONST.DEFAULT_NUMBER_ID; + const [lastReportActionTransaction] = useOnyx( + `${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(lastReportActionTransactionID !== CONST.DEFAULT_NUMBER_ID ? String(lastReportActionTransactionID) : undefined)}`, + ); + + const whisperTransactionID = isActionableTrackExpense(lastAction) ? getOriginalMessage(lastAction)?.transactionID : undefined; + const [whisperTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(whisperTransactionID)}`); + + const lastMessageTextFromReport = useMemo(() => { + if (whisperTransactionID && !whisperTransaction) { + return ''; + } + return undefined; + }, [whisperTransactionID, whisperTransaction]); + + const lastActionReportID = useMemo(() => { + if (isInviteOrRemovedAction(lastAction)) { + const lastActionOriginalMessage = lastAction?.actionName ? getOriginalMessage(lastAction) : null; + return lastActionOriginalMessage?.reportID; + } + return undefined; + }, [lastAction]); + const [lastActionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(lastActionReportID ? String(lastActionReportID) : undefined)}`); + const [movedFromReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getMovedReportID(lastAction, CONST.REPORT.MOVE_TYPE.FROM)}`); const [movedToReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getMovedReportID(lastAction, CONST.REPORT.MOVE_TYPE.TO)}`); const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index a6fd1e62cef85..fb56d0939ea08 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -5,19 +5,7 @@ import type {ValueOf} from 'type-fest'; import type {LocaleContextProps, LocalizedTranslate} from '@components/LocaleContextProvider'; import type CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; -import type { - Locale, - Onboarding, - OnboardingPurpose, - PersonalDetailsList, - Policy, - Report, - ReportAction, - ReportActions, - ReportNameValuePairs, - Transaction, - TransactionViolation, -} from '@src/types/onyx'; +import type {Locale, Onboarding, OnboardingPurpose, PersonalDetailsList, Policy, Report, ReportNameValuePairs, TransactionViolation} from '@src/types/onyx'; type OptionMode = ValueOf; @@ -83,41 +71,18 @@ type OptionRowLHNDataProps = { /** Invoice receiver policy */ invoiceReceiverPolicy?: OnyxEntry; - /** The action from the parent report */ - parentReportAction?: OnyxEntry; - - /** The transaction from the parent report action */ - transaction: OnyxEntry; - - /** The transaction linked to the report's last action */ - lastReportActionTransaction?: OnyxEntry; - /** Whether a report contains a draft */ hasDraftComment: boolean; /** The reportID of the report */ reportID: string; - /** Array of report actions for this report */ - reportActions: OnyxEntry; - - /** - * Array of report actions for the IOU report related to the last action of this report. - * If the last action is a report action preview, the last message of the report depends on - * the report actions of the IOU report linked to the report action preview. - * Changes in the IOU report report actions will affect the last message of this report. - */ - iouReportReportActions: OnyxEntry; - /** List of transaction violation */ transactionViolations: OnyxCollection; /** Toggle between compact and default view */ viewMode?: OptionMode; - /** The last message text from the report */ - lastMessageTextFromReport?: string; - /** A function that is called when an option is selected. Selected option is passed as a param */ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; @@ -139,14 +104,6 @@ type OptionRowLHNDataProps = { /** TestID of the row, indicating order */ testID: number; - /** Whether the report is archived */ - isReportArchived: boolean; - - /** The last action should be displayed */ - lastAction: ReportAction | undefined; - - lastActionReport: OnyxEntry | undefined; - /** The current user's account ID */ currentUserAccountID: number; };