Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 13 additions & 31 deletions src/components/LHNOptionsList/LHNOptionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ 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 {getLastVisibleActionIncludingTransactionThread, getOriginalMessage, isActionableTrackExpense, isInviteOrRemovedAction} from '@libs/ReportActionsUtils';
import {canUserPerformWriteAction as canUserPerformWriteActionUtil} from '@libs/ReportUtils';
import variables from '@styles/variables';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -55,8 +54,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
const [policy] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION);
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
const [draftComments] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [onboarding] = useOnyx(ONYXKEYS.NVP_ONBOARDING);
const [isFullscreenVisible] = useOnyx(ONYXKEYS.FULLSCREEN_VISIBILITY);
Expand All @@ -65,7 +63,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio

const theme = useTheme();
const styles = useThemeStyles();
const {translate, preferredLocale, localeCompare} = useLocalize();
const {translate, localeCompare} = useLocalize();
const isReportsSplitNavigatorLast = useRootNavigationState((state) => state?.routes?.at(-1)?.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR);
const shouldShowEmptyLHN = data.length === 0;
const estimatedItemSize = optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight;
Expand Down Expand Up @@ -165,7 +163,6 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
const itemReportAttributes = reportAttributes?.[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 itemOneTransactionThreadReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${itemReportAttributes?.oneTransactionThreadReportID}`];
const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${item?.parentReportID}`];
const itemParentReportAction = item?.parentReportActionID ? itemParentReportActions?.[item?.parentReportActionID] : undefined;
Expand All @@ -180,10 +177,10 @@ 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);

Expand All @@ -195,12 +192,6 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
itemOneTransactionThreadReport?.reportID,
);

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)) {
Expand All @@ -225,23 +216,17 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
reportAttributesDerived={reportAttributes}
oneTransactionThreadReport={itemOneTransactionThreadReport}
reportNameValuePairs={itemReportNameValuePairs}
reportActions={itemReportActions}
parentReportAction={itemParentReportAction}
iouReportReportActions={itemIouReportReportActions}
policy={itemPolicy}
invoiceReceiverPolicy={itemInvoiceReceiverPolicy}
personalDetails={personalDetails ?? {}}
Comment thread
BartekObudzinski marked this conversation as resolved.
transaction={itemTransaction}
lastReportActionTransaction={lastReportActionTransaction}
viewMode={optionMode}
isOptionFocused={!shouldDisableFocusOptions}
lastMessageTextFromReport={lastMessageTextFromReport}
onSelectRow={onSelectRow}
preferredLocale={preferredLocale}
transactionViolations={transactionViolations}
hasDraftComment={hasDraftComment}
onLayout={onLayoutItem}
shouldShowRBRorGBRTooltip={shouldShowRBRorGBRTooltip}
activePolicyID={activePolicyID}
onboardingPurpose={introSelected?.choice}
onboarding={onboarding}
isFullscreenVisible={isFullscreenVisible}
Expand All @@ -264,16 +249,14 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
reportActions,
policy,
transactions,
draftComments,
personalDetails,
firstReportIDWithGBRorRBR,
isFullscreenVisible,
optionMode,
shouldDisableFocusOptions,
onSelectRow,
preferredLocale,
transactionViolations,
onLayoutItem,
activePolicyID,
introSelected?.choice,
onboarding,
isReportsSplitNavigatorLast,
Expand All @@ -288,33 +271,32 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
const extraData = useMemo(
() => [
reportActions,
reportAttributes,
reports,
reportAttributes,
reportNameValuePairs,
transactionViolations,
policy,
personalDetails,
data.length,
optionMode,
preferredLocale,
transactions,
draftComments,
isOffline,
isScreenFocused,
isReportsSplitNavigatorLast,
visibleReportActionsData,
],
[
reportActions,
reports,
reportAttributes,
reports,
reportNameValuePairs,
transactionViolations,
policy,
personalDetails,
data.length,
optionMode,
preferredLocale,
transactions,
draftComments,
isOffline,
isScreenFocused,
isReportsSplitNavigatorLast,
Expand Down
82 changes: 12 additions & 70 deletions src/components/LHNOptionsList/OptionRowLHNData.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import {deepEqual} from 'fast-equals';
import React, {useMemo, useRef} from 'react';
import React, {useMemo} from 'react';
import useReportPreviewSenderID from '@components/ReportActionAvatars/useReportPreviewSenderID';
import {useCurrentReportIDState} from '@hooks/useCurrentReportID';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useGetExpensifyCardFromReportAction from '@hooks/useGetExpensifyCardFromReportAction';
import useOnyx from '@hooks/useOnyx';
import usePrevious from '@hooks/usePrevious';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
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 {Icon} from '@src/types/onyx/OnyxCommon';
import OptionRowLHN from './OptionRowLHN';
Expand All @@ -29,16 +26,10 @@ function OptionRowLHNData({
reportAttributesDerived,
oneTransactionThreadReport,
reportNameValuePairs,
reportActions,
personalDetails = {},
preferredLocale = CONST.LOCALES.DEFAULT,
policy,
invoiceReceiverPolicy,
parentReportAction,
iouReportReportActions,
transaction,
lastReportActionTransaction,
transactionViolations,
lastMessageTextFromReport,
localeCompare,
translate,
Expand All @@ -51,18 +42,12 @@ function OptionRowLHNData({
const reportID = propsToForward.reportID;
const {currentReportID: currentReportIDValue} = useCurrentReportIDState();
const isReportFocused = isOptionFocused && currentReportIDValue === reportID;
const optionItemRef = useRef<OptionData | undefined>(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);
const [draftComment] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`);
const {login} = useCurrentUserPersonalDetails();
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fullReport?.policyID}`);
// Check the report errors equality to avoid re-rendering when there are no changes
const prevReportErrors = usePrevious(reportAttributes?.reportErrors);
const areReportErrorsEqual = useMemo(() => deepEqual(prevReportErrors, reportAttributes?.reportErrors), [prevReportErrors, reportAttributes?.reportErrors]);
const hasDraftComment = !!draftComment && !draftComment.match(CONST.REGEX.EMPTY_COMMENT);

const card = useGetExpensifyCardFromReportAction({reportAction: lastAction, policyID: fullReport?.policyID});

Expand All @@ -74,76 +59,34 @@ function OptionRowLHNData({
chatReport: chatReportForIOU,
});

const optionItem = useMemo(() => {
// Note: ideally we'd have this as a dependent selector in onyx!
const item = SidebarUtils.getOptionData({
report: fullReport,
reportAttributes,
oneTransactionThreadReport,
reportNameValuePairs,
personalDetails,
policy,
parentReportAction,
conciergeReportID,
lastMessageTextFromReport,
invoiceReceiverPolicy,
card,
lastAction,
translate,
localeCompare,
isReportArchived,
lastActionReport,
movedFromReport,
movedToReport,
currentUserAccountID,
reportAttributesDerived,
policyTags,
currentUserLogin: login ?? '',
});
if (deepEqual(item, optionItemRef.current)) {
return optionItemRef.current;
}

optionItemRef.current = item;

return item;
// Listen parentReportAction to update title of thread report when parentReportAction changed
// Listen to transaction to update title of transaction report when transaction changed
// Listen to lastAction to update when action is deleted or gets pendingAction
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
fullReport,
reportAttributes?.brickRoadStatus,
reportAttributes?.actionBadge,
reportAttributes?.actionTargetReportActionID,
reportAttributes?.reportName,
areReportErrorsEqual,
// React Compiler auto-memoizes each expression in OptionRowLHN independently,
// so there is no need to stabilize the optionItem reference with deepEqual.
// When getOptionData returns a fresh object with the same content, the Compiler
// ensures that only expressions whose inputs actually changed recompute.
const optionItem = SidebarUtils.getOptionData({
report: fullReport,
reportAttributes,
oneTransactionThreadReport,
reportNameValuePairs,
lastReportActionTransaction,
reportActions,
personalDetails,
preferredLocale,
policy,
parentReportAction,
conciergeReportID,
iouReportReportActions,
transaction,
invoiceReceiverPolicy,
lastMessageTextFromReport,
invoiceReceiverPolicy,
card,
lastAction,
lastActionReport,
translate,
localeCompare,
isReportArchived,
lastActionReport,
movedFromReport,
movedToReport,
currentUserAccountID,
reportAttributesDerived,
policyTags,
login,
]);
currentUserLogin: login ?? '',
});

// For single-sender IOUs, trim to the sender's avatar to match the header.
// The header uses reportPreviewSenderID as accountID for its primary avatar,
Expand All @@ -165,7 +108,6 @@ function OptionRowLHNData({
optionItem={finalOptionItem}
report={fullReport}
conciergeReportID={conciergeReportID}
hasDraftComment={hasDraftComment}
/>
);
}
Expand Down
43 changes: 4 additions & 39 deletions src/components/LHNOptionsList/types.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
import type {RefObject} from 'react';
import type {LayoutChangeEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
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 {Onboarding, OnboardingPurpose, PersonalDetailsList, Policy, Report, ReportAction, ReportNameValuePairs} from '@src/types/onyx';
import type {ReportAttributes, ReportAttributesDerivedValue} from '@src/types/onyx/DerivedValues';

type OptionMode = ValueOf<typeof CONST.OPTION_MODE>;
Expand Down Expand Up @@ -54,12 +42,6 @@ type OptionRowLHNDataProps = {
/** List of users' personal details */
personalDetails?: PersonalDetailsList;

/** The preferred language for the app */
preferredLocale?: OnyxEntry<Locale>;

/** The active policy ID */
activePolicyID?: string;

/** The onboarding purpose */
onboardingPurpose?: OnboardingPurpose;

Expand Down Expand Up @@ -90,29 +72,12 @@ type OptionRowLHNDataProps = {
/** The action from the parent report */
parentReportAction?: OnyxEntry<ReportAction>;

/** The transaction from the parent report action */
transaction: OnyxEntry<Transaction>;

/** The transaction linked to the report's last action */
lastReportActionTransaction?: OnyxEntry<Transaction>;
/** Whether a report contains a draft */
hasDraftComment: boolean;

/** The reportID of the report */
reportID: string;

/** Array of report actions for this report */
reportActions: OnyxEntry<ReportActions>;

/**
* 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<ReportActions>;

/** List of transaction violation */
transactionViolations: OnyxCollection<TransactionViolation[]>;

/** Toggle between compact and default view */
viewMode?: OptionMode;

Expand Down
Loading