Skip to content
Open
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
91 changes: 3 additions & 88 deletions src/components/LHNOptionsList/LHNOptionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -24,8 +23,6 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import getPlatform from '@libs/getPlatform';
import Log from '@libs/Log';
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';
import NAVIGATORS from '@src/NAVIGATORS';
Expand All @@ -49,16 +46,11 @@ 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 [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();
Expand Down Expand Up @@ -162,10 +154,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
const reportID = item.reportID;
const itemReportAttributes = reportAttributes?.[reportID];
const itemParentReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${item.parentReportID}`];
const itemReportNameValuePairs = reportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${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;

let invoiceReceiverPolicyID = '-1';
if (item?.invoiceReceiver && 'policyID' in item.invoiceReceiver) {
Expand All @@ -177,54 +166,21 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
const itemInvoiceReceiverPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${invoiceReceiverPolicyID}`];

const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${item?.policyID}`];
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,
itemOneTransactionThreadReport?.reportID,
);

// 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<Report> | undefined;
if (isInviteOrRemovedAction(lastAction)) {
const lastActionOriginalMessage = lastAction?.actionName ? getOriginalMessage(lastAction) : null;
lastActionReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${lastActionOriginalMessage?.reportID}`];
}

return (
<OptionRowLHNData
reportID={reportID}
fullReport={item}
reportAttributes={itemReportAttributes}
reportAttributesDerived={reportAttributes}
oneTransactionThreadReport={itemOneTransactionThreadReport}
reportNameValuePairs={itemReportNameValuePairs}
parentReportAction={itemParentReportAction}
policy={itemPolicy}
invoiceReceiverPolicy={itemInvoiceReceiverPolicy}
personalDetails={personalDetails ?? {}}
viewMode={optionMode}
isOptionFocused={!shouldDisableFocusOptions}
lastMessageTextFromReport={lastMessageTextFromReport}
onSelectRow={onSelectRow}
hasDraftComment={hasDraftComment}
onLayout={onLayoutItem}
shouldShowRBRorGBRTooltip={shouldShowRBRorGBRTooltip}
onboardingPurpose={introSelected?.choice}
Expand All @@ -235,21 +191,14 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
localeCompare={localeCompare}
translate={translate}
testID={index}
isReportArchived={isReportArchived}
lastAction={lastAction}
lastActionReport={lastActionReport}
currentUserAccountID={currentUserAccountID}
/>
);
},
[
reportAttributes,
reports,
reportNameValuePairs,
reportActions,
policy,
transactions,
draftComments,
personalDetails,
firstReportIDWithGBRorRBR,
isFullscreenVisible,
Expand All @@ -263,45 +212,13 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
isScreenFocused,
localeCompare,
translate,
visibleReportActionsData,
currentUserAccountID,
],
);

const extraData = useMemo(
() => [
reportActions,
reportAttributes,
reports,
reportAttributes,
reportNameValuePairs,
policy,
personalDetails,
data.length,
optionMode,
transactions,
draftComments,
isOffline,
isScreenFocused,
isReportsSplitNavigatorLast,
visibleReportActionsData,
],
[
reportActions,
reportAttributes,
reports,
reportNameValuePairs,
policy,
personalDetails,
data.length,
optionMode,
transactions,
draftComments,
isOffline,
isScreenFocused,
isReportsSplitNavigatorLast,
visibleReportActionsData,
],
() => [reports, reportAttributes, policy, personalDetails, data.length, optionMode, isOffline, isScreenFocused, isReportsSplitNavigatorLast],
[reports, reportAttributes, policy, personalDetails, data.length, optionMode, isOffline, isScreenFocused, isReportsSplitNavigatorLast],
);

const previousOptionMode = usePrevious(optionMode);
Expand Down Expand Up @@ -356,14 +273,12 @@ 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, reports, policy, personalDetails]);

return (
<View style={[style ?? styles.flex1, shouldShowEmptyLHN ? styles.emptyLHNWrapper : undefined]}>
Expand Down
84 changes: 77 additions & 7 deletions src/components/LHNOptionsList/OptionRowLHNData.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import React, {useMemo} from 'react';
import React, {useCallback, useMemo} 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';
import useGetExpensifyCardFromReportAction from '@hooks/useGetExpensifyCardFromReportAction';
import useOnyx from '@hooks/useOnyx';
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import {getLastVisibleActionIncludingTransactionThread, getOriginalMessage, isActionableTrackExpense, isInviteOrRemovedAction} 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 ONYXKEYS from '@src/ONYXKEYS';
import type {ReportActions as ReportActionsType} from '@src/types/onyx';
import type {VisibleReportActionsDerivedValue} from '@src/types/onyx/DerivedValues';
import type {Icon} from '@src/types/onyx/OnyxCommon';
import OptionRowLHN from './OptionRowLHN';
import type {OptionRowLHNDataProps} from './types';
Expand All @@ -25,24 +30,88 @@ function OptionRowLHNData({
reportAttributes,
reportAttributesDerived,
oneTransactionThreadReport,
reportNameValuePairs,
personalDetails = {},
policy,
invoiceReceiverPolicy,
parentReportAction,
lastMessageTextFromReport,
localeCompare,
translate,
isReportArchived = false,
lastAction,
lastActionReport,
currentUserAccountID,
...propsToForward
}: OptionRowLHNDataProps) {
const reportID = propsToForward.reportID;
const {currentReportID: currentReportIDValue} = useCurrentReportIDState();
const isReportFocused = isOptionFocused && currentReportIDValue === reportID;

const oneTransactionThreadReportID = oneTransactionThreadReport?.reportID;

// Per-item report actions subscriptions (scoped by specific report ID)
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(oneTransactionThreadReportID)}`);

// Scoped VISIBLE_REPORT_ACTIONS selector
const visibleActionsSelector = useCallback(
(data: VisibleReportActionsDerivedValue | undefined) => {
if (!data) {
return undefined;
}
const result: VisibleReportActionsDerivedValue = {};
const reportEntry = data[reportID];
if (reportEntry) {
result[reportID] = reportEntry;
}
if (oneTransactionThreadReportID) {
const txThreadEntry = data[oneTransactionThreadReportID];
if (txThreadEntry) {
result[oneTransactionThreadReportID] = txThreadEntry;
}
}
return result;
},
[reportID, oneTransactionThreadReportID],
);
const [visibleReportActionsData] = useOnyx(ONYXKEYS.DERIVED.VISIBLE_REPORT_ACTIONS, {selector: visibleActionsSelector});

// Per-item NVP subscription instead of collection-level subscription in parent
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}`);

const parentReportAction = fullReport?.parentReportActionID ? parentReportActions?.[fullReport.parentReportActionID] : undefined;

const isReportArchived = !!reportNameValuePairs?.private_isArchived;
const canUserPerformWrite = canUserPerformWriteActionUtil(fullReport, isReportArchived);

const lastAction = useMemo(() => {
const actionsCollection: OnyxCollection<ReportActionsType> = {
[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: reportActions ?? undefined,
};
if (oneTransactionThreadReportID) {
actionsCollection[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneTransactionThreadReportID}`] = transactionThreadReportActions ?? undefined;
}
return getLastVisibleActionIncludingTransactionThread(reportID, canUserPerformWrite, actionsCollection, visibleReportActionsData, oneTransactionThreadReportID);
}, [reportID, canUserPerformWrite, reportActions, transactionThreadReportActions, visibleReportActionsData, oneTransactionThreadReportID]);

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 [draftComment] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`);
const hasDraftComment = !!draftComment && !draftComment.match(CONST.REGEX.EMPTY_COMMENT);

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);
Expand Down Expand Up @@ -107,6 +176,7 @@ function OptionRowLHNData({
isOptionFocused={isReportFocused}
optionItem={finalOptionItem}
report={fullReport}
hasDraftComment={hasDraftComment}
conciergeReportID={conciergeReportID}
/>
);
Expand Down
22 changes: 1 addition & 21 deletions src/components/LHNOptionsList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +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 {Onboarding, OnboardingPurpose, PersonalDetailsList, Policy, Report, ReportAction, ReportNameValuePairs} from '@src/types/onyx';
import type {Onboarding, OnboardingPurpose, PersonalDetailsList, Policy, Report} from '@src/types/onyx';
import type {ReportAttributes, ReportAttributesDerivedValue} from '@src/types/onyx/DerivedValues';

type OptionMode = ValueOf<typeof CONST.OPTION_MODE>;
Expand Down Expand Up @@ -60,30 +60,18 @@ type OptionRowLHNDataProps = {
/** The transaction thread report associated with the current report, if any */
oneTransactionThreadReport: OnyxEntry<Report>;

/** Array of report name value pairs for this report */
reportNameValuePairs: OnyxEntry<ReportNameValuePairs>;

/** The policy which the user has access to and which the report could be tied to */
policy?: OnyxEntry<Policy>;

/** Invoice receiver policy */
invoiceReceiverPolicy?: OnyxEntry<Policy>;

/** The action from the parent report */
parentReportAction?: OnyxEntry<ReportAction>;

/** Whether a report contains a draft */
hasDraftComment: boolean;

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

/** 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<View | null>) => void;

Expand Down Expand Up @@ -111,14 +99,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<Report> | undefined;

/** The current user's account ID */
currentUserAccountID: number;
};
Expand Down
Loading