From 3e5c8b094bdec621983d5d075719ba2903bc031f Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 20 Nov 2023 17:17:26 +0100 Subject: [PATCH 01/25] ref: started migrating LHNOptionsList module --- .../{LHNOptionsList.js => LHNOptionsList.tsx} | 20 ++- .../{OptionRowLHN.js => OptionRowLHN.tsx} | 117 +++++++----------- ...tionRowLHNData.js => OptionRowLHNData.tsx} | 0 3 files changed, 60 insertions(+), 77 deletions(-) rename src/components/LHNOptionsList/{LHNOptionsList.js => LHNOptionsList.tsx} (90%) rename src/components/LHNOptionsList/{OptionRowLHN.js => OptionRowLHN.tsx} (76%) rename src/components/LHNOptionsList/{OptionRowLHNData.js => OptionRowLHNData.tsx} (100%) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.tsx similarity index 90% rename from src/components/LHNOptionsList/LHNOptionsList.js rename to src/components/LHNOptionsList/LHNOptionsList.tsx index 0d300c5e2179..5def414f9010 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -2,8 +2,9 @@ import {FlashList} from '@shopify/flash-list'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; import _ from 'underscore'; import participantPropTypes from '@components/participantPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; @@ -17,6 +18,7 @@ import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx'; import OptionRowLHNData from './OptionRowLHNData'; const propTypes = { @@ -82,6 +84,20 @@ const defaultProps = { const keyExtractor = (item) => `report_${item}`; +type LHNOptionsListProps = { + style?: StyleProp; + contentContainerStyles: StyleProp; + data: string[]; + onSelectRow: (reportID: string) => void; + optionMode: ValueOf; + shouldDisableFocusOptions?: boolean; + policy: OnyxEntry; + reports: OnyxEntry>; + reportActions: OnyxEntry; + preferredLocale: OnyxEntry>; + personalDetails: OnyxEntry>; +}; + function LHNOptionsList({ style, contentContainerStyles, diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.tsx similarity index 76% rename from src/components/LHNOptionsList/OptionRowLHN.js rename to src/components/LHNOptionsList/OptionRowLHN.tsx index 8420f3db7a1e..89133fe27d72 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,8 +1,10 @@ import {useFocusEffect} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useRef, useState} from 'react'; -import {StyleSheet, View} from 'react-native'; +import React, {RefObject, useCallback, useRef, useState} from 'react'; +import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; +import {OnyxEntry} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; import _ from 'underscore'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; @@ -30,54 +32,26 @@ import * as StyleUtils from '@styles/StyleUtils'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; +import {Beta} from '@src/types/onyx'; -const propTypes = { - /** Style for hovered state */ - // eslint-disable-next-line react/forbid-prop-types - hoverStyle: PropTypes.object, - - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - - /** The ID of the report that the option is for */ - reportID: PropTypes.string.isRequired, - - /** Whether this option is currently in focus so we can modify its style */ - isFocused: PropTypes.bool, - - /** A function that is called when an option is selected. Selected option is passed as a param */ - onSelectRow: PropTypes.func, - - /** Toggle between compact and default view */ - viewMode: PropTypes.oneOf(_.values(CONST.OPTION_MODE)), - - style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), - - /** The item that should be rendered */ - // eslint-disable-next-line react/forbid-prop-types - optionItem: PropTypes.object, -}; - -const defaultProps = { - hoverStyle: undefined, - viewMode: 'default', - onSelectRow: () => {}, - style: null, - optionItem: null, - isFocused: false, - betas: [], +type OptionRowLHNProps = { + hoverStyle?: StyleProp; + betas?: Beta[]; + reportID: string; + isFocused?: boolean; + onSelectRow?: (optionItem: unknown, popoverAnchor: RefObject) => void; + viewMode?: ValueOf; + style?: StyleProp; + optionItem?: unknown; }; - -function OptionRowLHN(props) { +function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); - const popoverAnchor = useRef(null); + const popoverAnchor = useRef(null); const isFocusedRef = useRef(true); const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); - - const optionItem = props.optionItem; const [isContextMenuActive, setIsContextMenuActive] = useState(false); useFocusEffect( @@ -94,30 +68,28 @@ function OptionRowLHN(props) { } const isHidden = optionItem.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; - if (isHidden && !props.isFocused && !optionItem.isPinned) { + if (isHidden && !isFocused && !optionItem.isPinned) { return null; } - const textStyle = props.isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; + const textStyle = isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; const textUnreadStyle = optionItem.isUnread ? [textStyle, styles.sidebarLinkTextBold] : [textStyle]; - const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], props.style); + const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], style); const alternateTextStyle = StyleUtils.combineStyles( - props.viewMode === CONST.OPTION_MODE.COMPACT + viewMode === CONST.OPTION_MODE.COMPACT ? [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2] : [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting], - props.style, + style, ); const contentContainerStyles = - props.viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, optionRowStyles.compactContentContainerStyles] : [styles.flex1]; + viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, optionRowStyles.compactContentContainerStyles] : [styles.flex1]; const sidebarInnerRowStyle = StyleSheet.flatten( - props.viewMode === CONST.OPTION_MODE.COMPACT + viewMode === CONST.OPTION_MODE.COMPACT ? [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] : [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter], ); const hoveredBackgroundColor = - (props.hoverStyle || styles.sidebarLinkHover) && (props.hoverStyle || styles.sidebarLinkHover).backgroundColor - ? (props.hoverStyle || styles.sidebarLinkHover).backgroundColor - : theme.sidebar; + (!!hoverStyle || styles.sidebarLinkHover) && (hoverStyle || styles.sidebarLinkHover).backgroundColor ? (hoverStyle || styles.sidebarLinkHover).backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; @@ -139,9 +111,9 @@ function OptionRowLHN(props) { event, '', popoverAnchor, - props.reportID, + reportID, '0', - props.reportID, + reportID, '', () => {}, () => setIsContextMenuActive(false), @@ -152,15 +124,14 @@ function OptionRowLHN(props) { ); }; - const emojiCode = lodashGet(optionItem, 'status.emojiCode', ''); - const statusText = lodashGet(optionItem, 'status.text', ''); - const statusClearAfterDate = lodashGet(optionItem, 'status.clearAfter', ''); + const emojiCode = optionItem.status.emojiCode ?? ''; + const statusText = optionItem.status.text ?? ''; + const statusClearAfterDate = optionItem.status.clearAfter ?? ''; const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText} (${formattedDate})` : statusText; - const isStatusVisible = Permissions.canUseCustomStatus(props.betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem.reportID)); + const isStatusVisible = Permissions.canUseCustomStatus(betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem.reportID)); - const isGroupChat = - optionItem.type === CONST.REPORT.TYPE.CHAT && _.isEmpty(optionItem.chatType) && !optionItem.isThread && lodashGet(optionItem, 'displayNamesWithTooltips.length', 0) > 2; + const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && !optionItem.chatType && !optionItem.isThread && (optionItem.displayNamesWithTooltips.length ?? 0) > 2; const fullTitle = isGroupChat ? getGroupChatName(ReportUtils.getReport(optionItem.reportID)) : optionItem.text; return ( @@ -180,7 +151,7 @@ function OptionRowLHN(props) { } // Enable Composer to focus on clicking the same chat after opening the context menu. ReportActionComposeFocusManager.focus(); - props.onSelectRow(optionItem, popoverAnchor); + onSelectRow(optionItem, popoverAnchor); }} onMouseDown={(e) => { // Allow composer blur on right click @@ -196,7 +167,7 @@ function OptionRowLHN(props) { showPopover(e); // Ensure that we blur the composer when opening context menu, so that only one component is focused at a time if (DomUtils.getActiveElement()) { - DomUtils.getActiveElement().blur(); + DomUtils.getActiveElement()?.blur(); } }} withoutFocusOnSecondaryInteraction @@ -208,32 +179,32 @@ function OptionRowLHN(props) { styles.sidebarLink, styles.sidebarLinkInner, StyleUtils.getBackgroundColorStyle(theme.sidebar), - props.isFocused ? styles.sidebarLinkActive : null, - (hovered || isContextMenuActive) && !props.isFocused ? props.hoverStyle || styles.sidebarLinkHover : null, + isFocused ? styles.sidebarLinkActive : null, + (hovered || isContextMenuActive) && !isFocused ? hoverStyle ?? styles.sidebarLinkHover : null, ]} role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} - needsOffscreenAlphaCompositing={props.optionItem.icons.length >= 2} + needsOffscreenAlphaCompositing={optionItem.icons.length >= 2} > {!_.isEmpty(optionItem.icons) && (optionItem.shouldShowSubscript ? ( ) : ( @@ -321,10 +292,6 @@ function OptionRowLHN(props) { ); } -OptionRowLHN.propTypes = propTypes; -OptionRowLHN.defaultProps = defaultProps; OptionRowLHN.displayName = 'OptionRowLHN'; export default React.memo(OptionRowLHN); - -export {propTypes, defaultProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.tsx similarity index 100% rename from src/components/LHNOptionsList/OptionRowLHNData.js rename to src/components/LHNOptionsList/OptionRowLHNData.tsx From 2418cd131d94790de2b152edfac532ef0d344c9b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 21 Nov 2023 16:16:51 +0100 Subject: [PATCH 02/25] ref: working on migration to typescript --- .../LHNOptionsList/LHNOptionsList.tsx | 135 ++++++------------ .../LHNOptionsList/OptionRowLHN.tsx | 12 +- .../LHNOptionsList/OptionRowLHNData.tsx | 103 +++++++------ 3 files changed, 112 insertions(+), 138 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 5def414f9010..160f1330cfa8 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,102 +1,60 @@ -import {FlashList} from '@shopify/flash-list'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import {ContentStyle, FlashList} from '@shopify/flash-list'; import React, {useCallback} from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; -import _ from 'underscore'; -import participantPropTypes from '@components/participantPropTypes'; -import transactionPropTypes from '@components/transactionPropTypes'; -import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID'; +import withCurrentReportID, {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; -import reportPropTypes from '@pages/reportPropTypes'; -import stylePropTypes from '@styles/stylePropTypes'; import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx'; +import {PersonalDetails, Policy, Report, ReportActions, Transaction} from '@src/types/onyx'; import OptionRowLHNData from './OptionRowLHNData'; -const propTypes = { +const keyExtractor = (item) => `report_${item}`; + +type LHNOptionsListProps = { /** Wrapper style for the section list */ - style: stylePropTypes, + style?: StyleProp; /** Extra styles for the section list container */ - contentContainerStyles: stylePropTypes.isRequired, + contentContainerStyles?: ContentStyle; /** Sections for the section list */ - data: PropTypes.arrayOf(PropTypes.string).isRequired, + data: string[]; /** Callback to fire when a row is selected */ - onSelectRow: PropTypes.func.isRequired, + onSelectRow: (reportID: string) => void; /** Toggle between compact and default view of the option */ - optionMode: PropTypes.oneOf(_.values(CONST.OPTION_MODE)).isRequired, + optionMode: ValueOf; /** Whether to allow option focus or not */ - shouldDisableFocusOptions: PropTypes.bool, + shouldDisableFocusOptions?: boolean; /** The policy which the user has access to and which the report could be tied to */ - policy: PropTypes.shape({ - /** The ID of the policy */ - id: PropTypes.string, - /** Name of the policy */ - name: PropTypes.string, - /** Avatar of the policy */ - avatar: PropTypes.string, - }), + policy: OnyxEntry>; /** All reports shared with the user */ - reports: PropTypes.objectOf(reportPropTypes), + reports: OnyxEntry>; /** Array of report actions for this report */ - reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + reportActions: OnyxEntry>; /** Indicates which locale the user currently has selected */ - preferredLocale: PropTypes.string, + preferredLocale: OnyxEntry>; /** List of users' personal details */ - personalDetails: PropTypes.objectOf(participantPropTypes), + personalDetails: OnyxEntry>; /** The transaction from the parent report action */ - transactions: PropTypes.objectOf(transactionPropTypes), - /** List of draft comments */ - draftComments: PropTypes.objectOf(PropTypes.string), - ...withCurrentReportIDPropTypes, -}; - -const defaultProps = { - style: undefined, - shouldDisableFocusOptions: false, - reportActions: {}, - reports: {}, - policy: {}, - preferredLocale: CONST.LOCALES.DEFAULT, - personalDetails: {}, - transactions: {}, - draftComments: {}, - ...withCurrentReportIDDefaultProps, -}; + transactions: OnyxEntry>; -const keyExtractor = (item) => `report_${item}`; - -type LHNOptionsListProps = { - style?: StyleProp; - contentContainerStyles: StyleProp; - data: string[]; - onSelectRow: (reportID: string) => void; - optionMode: ValueOf; - shouldDisableFocusOptions?: boolean; - policy: OnyxEntry; - reports: OnyxEntry>; - reportActions: OnyxEntry; - preferredLocale: OnyxEntry>; - personalDetails: OnyxEntry>; -}; + /** List of draft comments */ + draftComments: OnyxEntry>; +} & CurrentReportIDContextValue; function LHNOptionsList({ style, @@ -104,36 +62,31 @@ function LHNOptionsList({ data, onSelectRow, optionMode, - shouldDisableFocusOptions, - reports, - reportActions, - policy, - preferredLocale, - personalDetails, - transactions, - draftComments, - currentReportID, -}) { + shouldDisableFocusOptions = false, + reports = {}, + reportActions = {}, + policy = {}, + preferredLocale = CONST.LOCALES.DEFAULT, + personalDetails = {}, + transactions = {}, + draftComments = {}, + currentReportID = '', +}: LHNOptionsListProps) { const styles = useThemeStyles(); /** * Function which renders a row in the list - * - * @param {Object} params - * @param {Object} params.item - * - * @return {Component} */ const renderItem = useCallback( - ({item: reportID}) => { - const itemFullReport = reports[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] || {}; - const itemReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; - const itemParentReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport.parentReportID}`] || {}; - const itemParentReportAction = itemParentReportActions[itemFullReport.parentReportActionID] || {}; - const itemPolicy = policy[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport.policyID}`] || {}; - const transactionID = lodashGet(itemParentReportAction, ['originalMessage', 'IOUTransactionID'], ''); - const itemTransaction = transactionID ? transactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] : {}; - const itemComment = draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || ''; - const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, personalDetails); + ({item: reportID}: {item: string}) => { + const itemFullReport: Report | undefined = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; + const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`]; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? '']; + const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`]; + const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID : ''; + const itemTransaction = transactionID ? transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] : {}; + const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; + const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport?.participantAccountIDs ?? [], personalDetails); return ( + ; /** The preferred language for the app */ - preferredLocale: PropTypes.string, + preferredLocale: string; /** The full data of the report */ - // eslint-disable-next-line react/forbid-prop-types - fullReport: PropTypes.object, + fullReport: Report; /** The policy which the user has access to and which the report could be tied to */ - policy: PropTypes.shape({ - /** The ID of the policy */ - id: PropTypes.string, - /** Name of the policy */ - name: PropTypes.string, - /** Avatar of the policy */ - avatar: PropTypes.string, - }), + policy: Policy; /** The action from the parent report */ - parentReportAction: PropTypes.shape(reportActionPropTypes), + parentReportAction: ReportAction; /** The transaction from the parent report action */ - transaction: transactionPropTypes, - - ...basePropTypes, -}; - -const defaultProps = { - isFocused: false, - personalDetails: {}, - fullReport: {}, - policy: {}, - parentReportAction: {}, - transaction: {}, - preferredLocale: CONST.LOCALES.DEFAULT, - ...baseDefaultProps, -}; + transaction: Transaction; + + comment: string; + + receiptTransactions: Transaction[]; +} & LHNOptionsListProps; /* * This component gets the data from onyx for the actual @@ -74,7 +97,7 @@ function OptionRowLHNData({ parentReportAction, transaction, ...propsToForward -}) { +}: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; const optionItemRef = useRef(); @@ -116,8 +139,6 @@ function OptionRowLHNData({ ); } -OptionRowLHNData.propTypes = propTypes; -OptionRowLHNData.defaultProps = defaultProps; OptionRowLHNData.displayName = 'OptionRowLHNData'; /** From 2e6f52a91aa81c4c8ab11703f8f20fc29770f0e9 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 21 Nov 2023 20:21:41 +0100 Subject: [PATCH 03/25] ref: contuniue migration --- .../LHNOptionsList/LHNOptionsList.tsx | 44 +-------- .../LHNOptionsList/OptionRowLHN.tsx | 29 ++---- .../LHNOptionsList/OptionRowLHNData.tsx | 46 ++------- src/components/LHNOptionsList/types.ts | 95 +++++++++++++++++++ src/components/SubscriptAvatar.tsx | 32 +++---- src/libs/SidebarUtils.ts | 2 + 6 files changed, 127 insertions(+), 121 deletions(-) create mode 100644 src/components/LHNOptionsList/types.ts diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 160f1330cfa8..64acf390e6a6 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -12,50 +12,10 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {PersonalDetails, Policy, Report, ReportActions, Transaction} from '@src/types/onyx'; import OptionRowLHNData from './OptionRowLHNData'; +import {LHNOptionsListProps} from './types'; const keyExtractor = (item) => `report_${item}`; -type LHNOptionsListProps = { - /** Wrapper style for the section list */ - style?: StyleProp; - - /** Extra styles for the section list container */ - contentContainerStyles?: ContentStyle; - - /** Sections for the section list */ - data: string[]; - - /** Callback to fire when a row is selected */ - onSelectRow: (reportID: string) => void; - - /** Toggle between compact and default view of the option */ - optionMode: ValueOf; - - /** Whether to allow option focus or not */ - shouldDisableFocusOptions?: boolean; - - /** The policy which the user has access to and which the report could be tied to */ - policy: OnyxEntry>; - - /** All reports shared with the user */ - reports: OnyxEntry>; - - /** Array of report actions for this report */ - reportActions: OnyxEntry>; - - /** Indicates which locale the user currently has selected */ - preferredLocale: OnyxEntry>; - - /** List of users' personal details */ - personalDetails: OnyxEntry>; - - /** The transaction from the parent report action */ - transactions: OnyxEntry>; - - /** List of draft comments */ - draftComments: OnyxEntry>; -} & CurrentReportIDContextValue; - function LHNOptionsList({ style, contentContainerStyles, @@ -69,8 +29,8 @@ function LHNOptionsList({ preferredLocale = CONST.LOCALES.DEFAULT, personalDetails = {}, transactions = {}, - draftComments = {}, currentReportID = '', + draftComments = {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); /** diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 72add9bc8ae5..24d006ae7555 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,11 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {RefObject, useCallback, useRef, useState} from 'react'; -import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; -import {OnyxEntry} from 'react-native-onyx'; -import {ValueOf} from 'type-fest'; -import _ from 'underscore'; +import React, {useCallback, useRef, useState} from 'react'; +import {StyleSheet, View} from 'react-native'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -32,18 +27,8 @@ import * as StyleUtils from '@styles/StyleUtils'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; -import {Beta} from '@src/types/onyx'; +import {OptionRowLHNProps} from './types'; -type OptionRowLHNProps = { - hoverStyle?: StyleProp; - betas?: Beta[]; - reportID: string; - isFocused?: boolean; - onSelectRow?: (optionItem: unknown, popoverAnchor: RefObject) => void; - viewMode?: ValueOf; - style?: StyleProp; - optionItem?: unknown; -}; function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -184,7 +169,7 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe ]} role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} - needsOffscreenAlphaCompositing={optionItem.icons.length >= 2} + needsOffscreenAlphaCompositing={(optionItem?.icons?.length ?? 0) >= 2} > @@ -192,13 +177,13 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe (optionItem.shouldShowSubscript ? ( ) : ( ; - - /** The preferred language for the app */ - preferredLocale: string; - - /** The full data of the report */ - fullReport: Report; - - /** The policy which the user has access to and which the report could be tied to */ - policy: Policy; - - /** The action from the parent report */ - parentReportAction: ReportAction; - - /** The transaction from the parent report action */ - transaction: Transaction; - - comment: string; - - receiptTransactions: Transaction[]; -} & LHNOptionsListProps; - /* * This component gets the data from onyx for the actual * OptionRowLHN component. @@ -100,10 +72,10 @@ function OptionRowLHNData({ }: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; - const optionItemRef = useRef(); + const optionItemRef = useRef(); const linkedTransaction = useMemo(() => { const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); - const lastReportAction = _.first(sortedReportActions); + const lastReportAction = sortedReportActions[0]; return TransactionUtils.getLinkedTransaction(lastReportAction); // eslint-disable-next-line react-hooks/exhaustive-deps }, [fullReport.reportID, receiptTransactions, reportActions]); @@ -114,7 +86,9 @@ function OptionRowLHNData({ if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } - optionItemRef.current = item; + if (item) { + 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 @@ -122,10 +96,10 @@ function OptionRowLHNData({ }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction]); useEffect(() => { - if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { + if (!optionItem || !!optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { return; } - Report.setReportWithDraft(reportID, true); + ReportLib.setReportWithDraft(reportID, true); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts new file mode 100644 index 000000000000..2f7bfc1005c0 --- /dev/null +++ b/src/components/LHNOptionsList/types.ts @@ -0,0 +1,95 @@ +import {ContentStyle} from '@shopify/flash-list'; +import {Transaction} from 'electron'; +import {RefObject} from 'react'; +import {StyleProp, ViewStyle} from 'react-native'; +import {OnyxEntry} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; +import {CurrentReportIDContextValue} from '@components/withCurrentReportID'; +import {OptionData} from '@libs/SidebarUtils'; +import CONST from '@src/CONST'; +import {Beta, PersonalDetails, Policy, Report, ReportAction, ReportActions} from '@src/types/onyx'; + +type CustomLHNOptionsListProps = { + /** Wrapper style for the section list */ + style?: StyleProp; + + /** Extra styles for the section list container */ + contentContainerStyles?: ContentStyle; + + /** Sections for the section list */ + data: string[]; + + /** Callback to fire when a row is selected */ + onSelectRow: (reportID: string) => void; + + /** Toggle between compact and default view of the option */ + optionMode: ValueOf; + + /** Whether to allow option focus or not */ + shouldDisableFocusOptions?: boolean; + + /** The policy which the user has access to and which the report could be tied to */ + policy: OnyxEntry>; + + /** All reports shared with the user */ + reports: OnyxEntry>; + + /** Array of report actions for this report */ + reportActions: OnyxEntry>; + + /** Indicates which locale the user currently has selected */ + preferredLocale: OnyxEntry>; + + /** List of users' personal details */ + personalDetails: OnyxEntry>; + + /** The transaction from the parent report action */ + transactions: OnyxEntry>; + + /** List of draft comments */ + draftComments: OnyxEntry>; +}; + +type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue; + +type OptionRowLHNDataProps = { + /** Whether row should be focused */ + isFocused: boolean; + + /** List of users' personal details */ + personalDetails: Record; + + /** The preferred language for the app */ + preferredLocale: string; + + /** The full data of the report */ + fullReport: Report; + + /** The policy which the user has access to and which the report could be tied to */ + policy: Policy; + + /** The action from the parent report */ + parentReportAction: ReportAction; + + /** The transaction from the parent report action */ + transaction: Transaction; + + comment: string; + + receiptTransactions: Transaction[]; + + reportID: string; +} & CustomLHNOptionsListProps; + +type OptionRowLHNProps = { + hoverStyle?: StyleProp; + betas?: Beta[]; + reportID: string; + isFocused?: boolean; + onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; + viewMode?: ValueOf; + style?: StyleProp; + optionItem?: OptionData; +}; + +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps}; diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index ab9f0dec8e57..8b77565e25ff 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -1,37 +1,20 @@ import React, {memo} from 'react'; import {View} from 'react-native'; import {ValueOf} from 'type-fest'; -import type {AvatarSource} from '@libs/UserUtils'; import * as StyleUtils from '@styles/StyleUtils'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; +import {Icon} from '@src/types/onyx/OnyxCommon'; import Avatar from './Avatar'; import UserDetailsTooltip from './UserDetailsTooltip'; -type SubAvatar = { - /** Avatar source to display */ - source?: AvatarSource; - - /** Denotes whether it is an avatar or a workspace avatar */ - type?: typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; - - /** Owner of the avatar. If user, displayName. If workspace, policy name */ - name?: string; - - /** Avatar id */ - id?: number | string; - - /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ - fallbackIcon?: AvatarSource; -}; - type SubscriptAvatarProps = { /** Avatar URL or icon */ - mainAvatar?: SubAvatar; + mainAvatar?: Icon; /** Subscript avatar URL or icon */ - secondaryAvatar?: SubAvatar; + secondaryAvatar?: Icon; /** Set the size of avatars */ size?: ValueOf; @@ -46,7 +29,14 @@ type SubscriptAvatarProps = { showTooltip?: boolean; }; -function SubscriptAvatar({mainAvatar = {}, secondaryAvatar = {}, size = CONST.AVATAR_SIZE.DEFAULT, backgroundColor, noMargin = false, showTooltip = true}: SubscriptAvatarProps) { +function SubscriptAvatar({ + mainAvatar = {} as Icon, + secondaryAvatar = {} as Icon, + size = CONST.AVATAR_SIZE.DEFAULT, + backgroundColor, + noMargin = false, + showTooltip = true, +}: SubscriptAvatarProps) { const theme = useTheme(); const styles = useThemeStyles(); const isSmall = size === CONST.AVATAR_SIZE.SMALL; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 58c4a124335d..8aa01543ddae 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -527,3 +527,5 @@ export default { isSidebarLoadedReady, resetIsSidebarLoadedReadyPromise, }; + +export type {OptionData}; From 6c72cd965b0620907d4a75e8fe42ae874ce069d5 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 22 Nov 2023 13:09:20 +0100 Subject: [PATCH 04/25] fix: add few type fixes --- .../LHNOptionsList/LHNOptionsList.tsx | 63 ++++++++++++------- .../LHNOptionsList/OptionRowLHNData.tsx | 50 +++------------ src/components/LHNOptionsList/types.ts | 62 +++++++++--------- src/libs/SidebarUtils.ts | 22 ++++--- 4 files changed, 96 insertions(+), 101 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 64acf390e6a6..967525b0ff6a 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,20 +1,17 @@ -import {ContentStyle, FlashList} from '@shopify/flash-list'; import React, {useCallback} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; -import {ValueOf} from 'type-fest'; -import withCurrentReportID, {CurrentReportIDContextValue} from '@components/withCurrentReportID'; +import {FlatList, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import withCurrentReportID from '@components/withCurrentReportID'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {PersonalDetails, Policy, Report, ReportActions, Transaction} from '@src/types/onyx'; import OptionRowLHNData from './OptionRowLHNData'; -import {LHNOptionsListProps} from './types'; +import {LHNOptionsListOnyxProps, LHNOptionsListProps} from './types'; -const keyExtractor = (item) => `report_${item}`; +const keyExtractor = (item: string) => `report_${item}`; function LHNOptionsList({ style, @@ -33,21 +30,41 @@ function LHNOptionsList({ draftComments = {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); + + /** + * This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization + * so that the heights can be determined before the options are rendered. Otherwise, the heights are determined when each option is rendering and it causes a lot of overhead on large + * lists. + * + * @param itemData - This is the same as the data we pass into the component + * @param index the current item's index in the set of data + */ + const getItemLayout = useCallback( + // eslint-disable-next-line @typescript-eslint/naming-convention + (_, index: number) => { + const optionHeight = optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight; + return { + length: optionHeight, + offset: index * optionHeight, + index, + }; + }, + [optionMode], + ); /** * Function which renders a row in the list */ const renderItem = useCallback( ({item: reportID}: {item: string}) => { - const itemFullReport: Report | undefined = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; - const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; - const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`]; - const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? '']; - const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`]; - const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID : ''; - const itemTransaction = transactionID ? transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] : {}; + const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; + const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; + const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; + const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '' : ''; + const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null; const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport?.participantAccountIDs ?? [], personalDetails); - return ( - ); @@ -90,8 +109,7 @@ function LHNOptionsList({ LHNOptionsList.displayName = 'LHNOptionsList'; export default compose( - withCurrentReportID, - withOnyx({ + withOnyx({ reports: { key: ONYXKEYS.COLLECTION.REPORT, }, @@ -114,6 +132,7 @@ export default compose( key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, }), + withCurrentReportID, )(LHNOptionsList); export type {LHNOptionsListProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index de2e3742a817..4abc69828791 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -4,42 +4,10 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import SidebarUtils, {OptionData} from '@libs/SidebarUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as ReportLib from '@userActions/Report'; +import CONST from '@src/CONST'; import OptionRowLHN from './OptionRowLHN'; import {OptionRowLHNDataProps} from './types'; -// const propTypes = { -// /** Whether row should be focused */ -// isFocused: PropTypes.bool, - -// /** List of users' personal details */ -// personalDetails: PropTypes.objectOf(participantPropTypes), - -// /** The preferred language for the app */ -// preferredLocale: PropTypes.string, - -// /** The full data of the report */ -// // eslint-disable-next-line react/forbid-prop-types -// fullReport: PropTypes.object, - -// /** The policy which the user has access to and which the report could be tied to */ -// policy: PropTypes.shape({ -// /** The ID of the policy */ -// id: PropTypes.string, -// /** Name of the policy */ -// name: PropTypes.string, -// /** Avatar of the policy */ -// avatar: PropTypes.string, -// }), - -// /** The action from the parent report */ -// parentReportAction: PropTypes.shape(reportActionPropTypes), - -// /** The transaction from the parent report action */ -// transaction: transactionPropTypes, - -// ...basePropTypes, -// }; - // const defaultProps = { // isFocused: false, // personalDetails: {}, @@ -58,16 +26,16 @@ import {OptionRowLHNDataProps} from './types'; * re-render if the data really changed. */ function OptionRowLHNData({ - isFocused, - fullReport, + isFocused = false, + fullReport = null, reportActions, - personalDetails, - preferredLocale, + personalDetails = {}, + preferredLocale = CONST.LOCALES.DEFAULT, comment, - policy, + policy = null, receiptTransactions, - parentReportAction, - transaction, + parentReportAction = null, + transaction = null, ...propsToForward }: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; @@ -78,7 +46,7 @@ function OptionRowLHNData({ const lastReportAction = sortedReportActions[0]; return TransactionUtils.getLinkedTransaction(lastReportAction); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport.reportID, receiptTransactions, reportActions]); + }, [fullReport?.reportID, receiptTransactions, reportActions]); const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 2f7bfc1005c0..5a736e57721d 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,5 +1,4 @@ import {ContentStyle} from '@shopify/flash-list'; -import {Transaction} from 'electron'; import {RefObject} from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import {OnyxEntry} from 'react-native-onyx'; @@ -7,27 +6,9 @@ import {ValueOf} from 'type-fest'; import {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import {OptionData} from '@libs/SidebarUtils'; import CONST from '@src/CONST'; -import {Beta, PersonalDetails, Policy, Report, ReportAction, ReportActions} from '@src/types/onyx'; - -type CustomLHNOptionsListProps = { - /** Wrapper style for the section list */ - style?: StyleProp; - - /** Extra styles for the section list container */ - contentContainerStyles?: ContentStyle; - - /** Sections for the section list */ - data: string[]; - - /** Callback to fire when a row is selected */ - onSelectRow: (reportID: string) => void; - - /** Toggle between compact and default view of the option */ - optionMode: ValueOf; - - /** Whether to allow option focus or not */ - shouldDisableFocusOptions?: boolean; +import {Beta, PersonalDetails, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ policy: OnyxEntry>; @@ -49,8 +30,27 @@ type CustomLHNOptionsListProps = { /** List of draft comments */ draftComments: OnyxEntry>; }; +type CustomLHNOptionsListProps = { + /** Wrapper style for the section list */ + style?: StyleProp; + + /** Extra styles for the section list container */ + contentContainerStyles?: ContentStyle; + + /** Sections for the section list */ + data: string[]; + + /** Callback to fire when a row is selected */ + onSelectRow: (reportID: string) => void; -type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue; + /** Toggle between compact and default view of the option */ + optionMode: ValueOf; + + /** Whether to allow option focus or not */ + shouldDisableFocusOptions?: boolean; +}; + +type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps; type OptionRowLHNDataProps = { /** Whether row should be focused */ @@ -60,26 +60,28 @@ type OptionRowLHNDataProps = { personalDetails: Record; /** The preferred language for the app */ - preferredLocale: string; + preferredLocale: OnyxEntry>; /** The full data of the report */ - fullReport: Report; + fullReport: OnyxEntry; /** The policy which the user has access to and which the report could be tied to */ - policy: Policy; + policy: OnyxEntry; /** The action from the parent report */ - parentReportAction: ReportAction; + parentReportAction: OnyxEntry; /** The transaction from the parent report action */ - transaction: Transaction; + transaction: OnyxEntry; comment: string; - receiptTransactions: Transaction[]; + receiptTransactions: OnyxEntry>; reportID: string; -} & CustomLHNOptionsListProps; + + reportActions: OnyxEntry; +}; type OptionRowLHNProps = { hoverStyle?: StyleProp; @@ -92,4 +94,4 @@ type OptionRowLHNProps = { optionItem?: OptionData; }; -export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps}; +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps}; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index d3bc3e609486..824e397376ab 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1,6 +1,6 @@ /* eslint-disable rulesdir/prefer-underscore-method */ import Str from 'expensify-common/lib/str'; -import Onyx from 'react-native-onyx'; +import Onyx, {OnyxEntry} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -219,6 +219,12 @@ function getOrderedReportIDs( return LHNReports; } +type Status = { + text: string; + emojiCode: string; + clearAfter: string; +}; + type OptionData = { text?: string | null; alternateText?: string | null; @@ -235,7 +241,7 @@ type OptionData = { managerID?: number | null; reportID?: string | null; policyID?: string | null; - status?: string | null; + status?: Status | null; type?: string | null; stateNum?: ValueOf | null; statusNum?: ValueOf | null; @@ -292,12 +298,12 @@ type Icon = { * Gets all the data necessary for rendering an OptionRowLHN component */ function getOptionData( - report: Report, - reportActions: Record, - personalDetails: Record, - preferredLocale: ValueOf, - policy: Policy, - parentReportAction: ReportAction, + report: OnyxEntry, + reportActions: OnyxEntry>, + personalDetails: OnyxEntry>, + preferredLocale: OnyxEntry>, + policy: OnyxEntry, + parentReportAction: OnyxEntry, ): OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do From 2bf40c8f31db5342889c0b2845ec63f87c7a95ec Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 22 Nov 2023 19:37:24 +0100 Subject: [PATCH 05/25] fix: types --- src/components/DisplayNames/types.ts | 2 +- .../LHNOptionsList/OptionRowLHN.tsx | 43 +++++++++++-------- .../LHNOptionsList/OptionRowLHNData.tsx | 11 ----- src/components/LHNOptionsList/types.ts | 2 +- src/libs/SidebarUtils.ts | 12 ++---- 5 files changed, 29 insertions(+), 41 deletions(-) diff --git a/src/components/DisplayNames/types.ts b/src/components/DisplayNames/types.ts index 94e4fc7c39c6..307c28bd2df3 100644 --- a/src/components/DisplayNames/types.ts +++ b/src/components/DisplayNames/types.ts @@ -12,7 +12,7 @@ type DisplayNameWithTooltip = { login?: string; /** The avatar for the tooltip fallback */ - avatar: AvatarSource; + avatar?: AvatarSource; }; type DisplayNamesProps = { diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 24d006ae7555..e2db0cba498a 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,6 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef, useState} from 'react'; -import {StyleSheet, View} from 'react-native'; +import {StyleProp, StyleSheet, TextStyle, View} from 'react-native'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -59,22 +59,24 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe const textStyle = isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; const textUnreadStyle = optionItem?.isUnread ? [textStyle, styles.sidebarLinkTextBold] : [textStyle]; - const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], style); + const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], style ?? {}); const alternateTextStyle = StyleUtils.combineStyles( viewMode === CONST.OPTION_MODE.COMPACT ? [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2] : [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting], - style, + style ?? {}, ); const contentContainerStyles = viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, optionRowStyles.compactContentContainerStyles] : [styles.flex1]; - const sidebarInnerRowStyle = StyleSheet.flatten( - viewMode === CONST.OPTION_MODE.COMPACT - ? [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] - : [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter], - ); + const sidebarInnerRowStyle = StyleSheet.flatten([ + styles.chatLinkRowPressable, + styles.flexGrow1, + styles.optionItemAvatarNameWrapper, + viewMode === CONST.OPTION_MODE.COMPACT ? styles.optionRowCompact : styles.optionRow, + styles.justifyContentCenter, + ]); const hoveredBackgroundColor = - (!!hoverStyle || styles.sidebarLinkHover) && (hoverStyle || styles.sidebarLinkHover).backgroundColor ? (hoverStyle || styles.sidebarLinkHover).backgroundColor : theme.sidebar; + !!hoverStyle && 'backgroundColor' in hoverStyle && 'backgroundColor' in styles.sidebarLinkHover ? (hoverStyle ?? styles.sidebarLinkHover).backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; @@ -84,7 +86,7 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe /** * Show the ReportActionContextMenu modal popover. * - * @param {Object} [event] - A press event. + * @param [event] - A press event. */ const showPopover = (event) => { if (!isFocusedRef.current && isSmallScreenWidth) { @@ -114,11 +116,10 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe const statusClearAfterDate = optionItem.status?.clearAfter ?? ''; const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText} (${formattedDate})` : statusText; - const isStatusVisible = Permissions.canUseCustomStatus(betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem.reportID)); - - const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && !optionItem.chatType && !optionItem.isThread && (optionItem.displayNamesWithTooltips.length ?? 0) > 2; - const fullTitle = isGroupChat ? getGroupChatName(ReportUtils.getReport(optionItem.reportID)) : optionItem.text; + const isStatusVisible = Permissions.canUseCustomStatus(betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem?.reportID ?? '')); + const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && !optionItem.chatType && !optionItem.isThread && (optionItem?.displayNamesWithTooltips?.length ?? 0) > 2; + const fullTitle = isGroupChat ? getGroupChatName(ReportUtils.getReport(optionItem?.reportID ?? '')) : optionItem.text; return ( {isStatusVisible && ( @@ -226,9 +231,9 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe ) : null} - {optionItem.descriptiveText ? ( + {optionItem?.descriptiveText ? ( - {optionItem.descriptiveText} + {optionItem?.descriptiveText} ) : null} {hasBrickError && ( diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 4abc69828791..b48454fb7ec9 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -8,17 +8,6 @@ import CONST from '@src/CONST'; import OptionRowLHN from './OptionRowLHN'; import {OptionRowLHNDataProps} from './types'; -// const defaultProps = { -// isFocused: false, -// personalDetails: {}, -// fullReport: {}, -// policy: {}, -// parentReportAction: {}, -// transaction: {}, -// preferredLocale: CONST.LOCALES.DEFAULT, -// ...baseDefaultProps, -// }; - /* * This component gets the data from onyx for the actual * OptionRowLHN component. diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 5a736e57721d..fa0e48a413c4 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -90,7 +90,7 @@ type OptionRowLHNProps = { isFocused?: boolean; onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; viewMode?: ValueOf; - style?: StyleProp; + style?: ViewStyle | ViewStyle[]; optionItem?: OptionData; }; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 824e397376ab..5432662dcc94 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -231,7 +231,7 @@ type OptionData = { pendingAction?: OnyxCommon.PendingAction | null; allReportErrors?: OnyxCommon.Errors | null; brickRoadIndicator?: typeof CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR | '' | null; - icons?: Icon[] | null; + icons?: OnyxCommon.Icon[] | null; tooltipText?: string | null; ownerAccountID?: number | null; subtitle?: string | null; @@ -272,11 +272,12 @@ type OptionData = { notificationPreference?: string | number | null; displayNamesWithTooltips?: DisplayNamesWithTooltip[] | null; chatType?: ValueOf | null; + descriptiveText?: string; }; type DisplayNamesWithTooltip = { displayName?: string; - avatar?: string; + avatar?: UserUtils.AvatarSource; login?: string; accountID?: number; pronouns?: string; @@ -287,13 +288,6 @@ type ActorDetails = { accountID?: number; }; -type Icon = { - source?: string; - id?: number; - type?: string; - name?: string; -}; - /** * Gets all the data necessary for rendering an OptionRowLHN component */ From e205c9d9d416107e2fe8d8e1a3ca500c35d61b40 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 24 Nov 2023 12:48:21 +0100 Subject: [PATCH 06/25] fix: few type issues --- .../LHNOptionsList/LHNOptionsList.tsx | 33 ++++--------------- src/components/LHNOptionsList/types.ts | 3 +- src/styles/StyleUtils.ts | 4 +-- 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 967525b0ff6a..d71fe1db535d 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,5 +1,6 @@ +import {FlashList} from '@shopify/flash-list'; import React, {useCallback} from 'react'; -import {FlatList, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; import compose from '@libs/compose'; @@ -31,26 +32,6 @@ function LHNOptionsList({ }: LHNOptionsListProps) { const styles = useThemeStyles(); - /** - * This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization - * so that the heights can be determined before the options are rendered. Otherwise, the heights are determined when each option is rendering and it causes a lot of overhead on large - * lists. - * - * @param itemData - This is the same as the data we pass into the component - * @param index the current item's index in the set of data - */ - const getItemLayout = useCallback( - // eslint-disable-next-line @typescript-eslint/naming-convention - (_, index: number) => { - const optionHeight = optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight; - return { - length: optionHeight, - offset: index * optionHeight, - index, - }; - }, - [optionMode], - ); /** * Function which renders a row in the list */ @@ -88,19 +69,17 @@ function LHNOptionsList({ return ( - ); diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index fa0e48a413c4..bd6fb9a1a0a0 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -85,13 +85,12 @@ type OptionRowLHNDataProps = { type OptionRowLHNProps = { hoverStyle?: StyleProp; - betas?: Beta[]; reportID: string; isFocused?: boolean; onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; viewMode?: ValueOf; style?: ViewStyle | ViewStyle[]; - optionItem?: OptionData; + optionItem?: OptionData | null; }; export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps}; diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 4b998f940244..695570f2669f 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -425,7 +425,7 @@ function getAutoGrowHeightInputStyle(textInputHeight: number, maxHeight: number) /** * Returns a style with backgroundColor and borderColor set to the same color */ -function getBackgroundAndBorderStyle(backgroundColor: string): ViewStyle { +function getBackgroundAndBorderStyle(backgroundColor: ColorValue): ViewStyle { return { backgroundColor, borderColor: backgroundColor, @@ -435,7 +435,7 @@ function getBackgroundAndBorderStyle(backgroundColor: string): ViewStyle { /** * Returns a style with the specified backgroundColor */ -function getBackgroundColorStyle(backgroundColor: string): ViewStyle { +function getBackgroundColorStyle(backgroundColor: ColorValue): ViewStyle { return { backgroundColor, }; From c9378b473c260e6a6d3f7c9c8871c540b419cd8b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 12 Dec 2023 10:19:28 +0100 Subject: [PATCH 07/25] fix: conflicts --- src/components/LHNOptionsList/LHNOptionsList.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index d71fe1db535d..ea608ae7ff78 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -5,6 +5,7 @@ import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -45,7 +46,9 @@ function LHNOptionsList({ const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '' : ''; const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null; const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; - const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport?.participantAccountIDs ?? [], personalDetails); + const participants = [...ReportUtils.getParticipantsIDs(itemFullReport), itemFullReport?.ownerAccountID]; + + const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails); return ( Date: Thu, 14 Dec 2023 18:48:04 +0100 Subject: [PATCH 08/25] fix: types --- .../LHNOptionsList/LHNOptionsList.tsx | 5 +- .../LHNOptionsList/OptionRowLHN.tsx | 65 ++++++++++--------- .../LHNOptionsList/OptionRowLHNData.tsx | 5 +- src/components/LHNOptionsList/types.ts | 30 ++++----- src/components/OfflineWithFeedback.tsx | 2 +- .../types.ts | 2 +- src/libs/GroupChatUtils.ts | 4 +- src/libs/ReportUtils.ts | 5 +- src/libs/SidebarUtils.ts | 12 ++-- src/styles/utils/index.ts | 2 +- 10 files changed, 67 insertions(+), 65 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index ea608ae7ff78..76c7106ff9d2 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -3,10 +3,10 @@ import React, {useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; +import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -32,7 +32,6 @@ function LHNOptionsList({ draftComments = {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); - /** * Function which renders a row in the list */ @@ -62,7 +61,7 @@ function LHNOptionsList({ viewMode={optionMode} isFocused={!shouldDisableFocusOptions && reportID === currentReportID} onSelectRow={onSelectRow} - preferredLocale={preferredLocale} + preferredLocale={preferredLocale ?? CONST.LOCALES.DEFAULT} comment={itemComment} /> ); diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index e957d83c1607..38dcaff7b22c 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,6 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef, useState} from 'react'; -import {StyleSheet, View} from 'react-native'; +import {GestureResponderEvent, StyleProp, StyleSheet, TextInput, View, ViewStyle} from 'react-native'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -18,18 +18,20 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import DateUtils from '@libs/DateUtils'; import DomUtils from '@libs/DomUtils'; +import {getGroupChatName} from '@libs/GroupChatUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportUtils from '@libs/ReportUtils'; import * as ContextMenuActions from '@pages/home/report/ContextMenu/ContextMenuActions'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; +import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; import {OptionRowLHNProps} from './types'; function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () => {}, optionItem = null, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); - const popoverAnchor = useRef(null); + const popoverAnchor = useRef(null); const StyleUtils = useStyleUtils(); const isFocusedRef = useRef(true); const {isSmallScreenWidth} = useWindowDimensions(); @@ -65,16 +67,14 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () style ?? {}, ); const contentContainerStyles = - props.viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, StyleUtils.getCompactContentContainerStyles()] : [styles.flex1]; + viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, StyleUtils.getCompactContentContainerStyles()] : [styles.flex1]; const sidebarInnerRowStyle = StyleSheet.flatten( - props.viewMode === CONST.OPTION_MODE.COMPACT - ? [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] - : [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter], + viewMode === CONST.OPTION_MODE.COMPACT + ? ([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] as StyleProp) + : ([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter] as StyleProp), ); const hoveredBackgroundColor = - (props.hoverStyle || styles.sidebarLinkHover) && (props.hoverStyle || styles.sidebarLinkHover).backgroundColor - ? (props.hoverStyle || styles.sidebarLinkHover).backgroundColor - : theme.sidebar; + (!!hoverStyle || !!styles.sidebarLinkHover) && 'backgroundColor' in (hoverStyle ?? styles.sidebarLinkHover) ? (hoverStyle ?? styles.sidebarLinkHover).backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; @@ -86,7 +86,7 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () * * @param [event] - A press event. */ - const showPopover = (event) => { + const showPopover = (event: MouseEvent | GestureResponderEvent) => { if (!isFocusedRef.current && isSmallScreenWidth) { return; } @@ -109,19 +109,22 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () ); }; - const emojiCode = optionItem.status?.emojiCode ?? ''; - const statusText = optionItem.status?.text ?? ''; - const statusClearAfterDate = optionItem.status?.clearAfter ?? ''; - const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); + const emojiCode = typeof optionItem.status === 'object' ? optionItem.status?.emojiCode : ''; + const statusText = typeof optionItem.status === 'object' ? optionItem.status?.text : ''; + const statusClearAfterDate = typeof optionItem.status === 'object' ? optionItem.status?.clearAfter : ''; + const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate ?? ''); const statusContent = formattedDate ? `${statusText} (${formattedDate})` : statusText; - const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem?.reportID ?? '')); + const report = ReportUtils.getReport(optionItem.reportID ?? ''); + const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(isNotEmptyObject(report) ? report : null); - const subscriptAvatarBorderColor = props.isFocused ? focusedBackgroundColor : theme.sidebar; + const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && optionItem.chatType && !optionItem.isThread && (optionItem.displayNamesWithTooltips?.length ?? 0) > 2; + const fullTitle = isGroupChat ? getGroupChatName(isNotEmptyObject(report) ? report : null) : optionItem.text; + const subscriptAvatarBorderColor = isFocused ? focusedBackgroundColor : theme.sidebar; return ( @@ -129,29 +132,27 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () {(hovered) => ( { - if (e) { - e.preventDefault(); - } + onPress={(event) => { + event?.preventDefault(); // Enable Composer to focus on clicking the same chat after opening the context menu. ReportActionComposeFocusManager.focus(); onSelectRow(optionItem, popoverAnchor); }} - onMouseDown={(e) => { + onMouseDown={(event) => { // Allow composer blur on right click - if (!e) { + if (!event) { return; } // Prevent composer blur on left click - e.preventDefault(); + event.preventDefault(); }} testID={optionItem.reportID} - onSecondaryInteraction={(e) => { - showPopover(e); + onSecondaryInteraction={(event) => { + showPopover(event); // Ensure that we blur the composer when opening context menu, so that only one component is focused at a time if (DomUtils.getActiveElement()) { - DomUtils.getActiveElement()?.blur(); + (DomUtils.getActiveElement() as HTMLElement | TextInput)?.blur(); } }} withoutFocusOnSecondaryInteraction @@ -164,7 +165,7 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () styles.sidebarLinkInnerLHN, StyleUtils.getBackgroundColorStyle(theme.sidebar), isFocused ? styles.sidebarLinkActive : null, - (hovered || isContextMenuActive) && !isFocused ? hoverStyle ?? styles.sidebarLinkHover : null, + (hovered || isContextMenuActive) && !isFocused ? hoverStyle ?? styles.sidebarLinkHover : styles.sidebarLinkHover, ]} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} @@ -175,10 +176,10 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () {(optionItem.icons?.length ?? 0) > 0 && (optionItem.shouldShowSubscript ? ( ) : ( { // Note: ideally we'd have this as a dependent selector in onyx! - const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction); + const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale ?? CONST.LOCALES.DEFAULT, policy, parentReportAction); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index bd6fb9a1a0a0..3d2920a2bf2a 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,22 +1,22 @@ -import {ContentStyle} from '@shopify/flash-list'; -import {RefObject} from 'react'; -import {StyleProp, ViewStyle} from 'react-native'; -import {OnyxEntry} from 'react-native-onyx'; -import {ValueOf} from 'type-fest'; -import {CurrentReportIDContextValue} from '@components/withCurrentReportID'; -import {OptionData} from '@libs/SidebarUtils'; +import type {ContentStyle} from '@shopify/flash-list'; +import type {RefObject} from 'react'; +import {type StyleProp, View, type ViewStyle} from 'react-native'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import CONST from '@src/CONST'; -import {Beta, PersonalDetails, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +import type {OptionData} from '@src/libs/ReportUtils'; +import type {PersonalDetails, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ - policy: OnyxEntry>; + policy: OnyxCollection; /** All reports shared with the user */ - reports: OnyxEntry>; + reports: OnyxCollection; /** Array of report actions for this report */ - reportActions: OnyxEntry>; + reportActions: OnyxCollection; /** Indicates which locale the user currently has selected */ preferredLocale: OnyxEntry>; @@ -25,10 +25,10 @@ type LHNOptionsListOnyxProps = { personalDetails: OnyxEntry>; /** The transaction from the parent report action */ - transactions: OnyxEntry>; + transactions: OnyxCollection; /** List of draft comments */ - draftComments: OnyxEntry>; + draftComments: OnyxCollection; }; type CustomLHNOptionsListProps = { /** Wrapper style for the section list */ @@ -76,7 +76,7 @@ type OptionRowLHNDataProps = { comment: string; - receiptTransactions: OnyxEntry>; + receiptTransactions: OnyxCollection; reportID: string; @@ -87,7 +87,7 @@ type OptionRowLHNProps = { hoverStyle?: StyleProp; reportID: string; isFocused?: boolean; - onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; + onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; viewMode?: ValueOf; style?: ViewStyle | ViewStyle[]; optionItem?: OptionData | null; diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 5fcf1fe7442b..f9787d86abf7 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -18,7 +18,7 @@ import MessagesRow from './MessagesRow'; type OfflineWithFeedbackProps = ChildrenProps & { /** The type of action that's pending */ - pendingAction: OnyxCommon.PendingAction; + pendingAction: OnyxCommon.PendingAction | undefined; /** Determine whether to hide the component's children if deletion is pending */ shouldHideOnDelete?: boolean; diff --git a/src/components/PressableWithSecondaryInteraction/types.ts b/src/components/PressableWithSecondaryInteraction/types.ts index cf286afcb63a..33eb27afbe57 100644 --- a/src/components/PressableWithSecondaryInteraction/types.ts +++ b/src/components/PressableWithSecondaryInteraction/types.ts @@ -42,7 +42,7 @@ type PressableWithSecondaryInteractionProps = PressableWithFeedbackProps & activeOpacity?: number; /** Used to apply styles to the Pressable */ - style?: StyleProp; + style?: StyleProp; /** Whether the long press with hover behavior is enabled */ enableLongPressWithHover?: boolean; diff --git a/src/libs/GroupChatUtils.ts b/src/libs/GroupChatUtils.ts index 862c50700c0c..a44c6d12627b 100644 --- a/src/libs/GroupChatUtils.ts +++ b/src/libs/GroupChatUtils.ts @@ -13,8 +13,8 @@ Onyx.connect({ /** * Returns the report name if the report is a group chat */ -function getGroupChatName(report: Report): string | undefined { - const participants = report.participantAccountIDs ?? []; +function getGroupChatName(report: OnyxEntry): string | undefined { + const participants = report?.participantAccountIDs ?? []; const isMultipleParticipantReport = participants.length > 1; const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, allPersonalDetails ?? {}); // @ts-expect-error Error will gone when OptionsListUtils will be migrated to Typescript diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8729bb1bf957..f805e06c8557 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -16,7 +16,7 @@ import CONST from '@src/CONST'; import {ParentNavigationSummaryParams, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {Beta, Login, PersonalDetails, PersonalDetailsList, Policy, PolicyTags, Report, ReportAction, Session, Transaction} from '@src/types/onyx'; +import {Beta, CustomStatusDraft, Login, PersonalDetails, PersonalDetailsList, Policy, PolicyTags, Report, ReportAction, Session, Transaction} from '@src/types/onyx'; import {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; import {IOUMessage, OriginalMessageActionName, OriginalMessageCreated} from '@src/types/onyx/OriginalMessage'; import {NotificationPreference} from '@src/types/onyx/Report'; @@ -320,7 +320,7 @@ type OptionData = { subtitle?: string | null; login?: string | null; accountID?: number | null; - status?: string | null; + status?: CustomStatusDraft | string | null; phoneNumber?: string | null; isUnread?: boolean | null; isUnreadWithMention?: boolean | null; @@ -338,6 +338,7 @@ type OptionData = { isTaskReport?: boolean | null; parentReportAction?: ReportAction; displayNamesWithTooltips?: DisplayNameWithTooltips | null; + descriptiveText?: string; } & Report; type OnyxDataTaskAssigneeChat = { diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 1813d4f0a795..380ff7586815 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1,6 +1,6 @@ /* eslint-disable rulesdir/prefer-underscore-method */ import Str from 'expensify-common/lib/str'; -import Onyx, {OnyxCollection} from 'react-native-onyx'; +import Onyx, {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -230,12 +230,12 @@ type ActorDetails = { * Gets all the data necessary for rendering an OptionRowLHN component */ function getOptionData( - report: Report, - reportActions: Record, + report: OnyxEntry, + reportActions: OnyxEntry, personalDetails: Record, preferredLocale: ValueOf, - policy: Policy, - parentReportAction: ReportAction, + policy: OnyxEntry, + parentReportAction: OnyxEntry, ): ReportUtils.OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do @@ -278,7 +278,7 @@ function getOptionData( result.isThread = ReportUtils.isChatThread(report); result.isChatRoom = ReportUtils.isChatRoom(report); result.isTaskReport = ReportUtils.isTaskReport(report); - result.parentReportAction = parentReportAction; + result.parentReportAction = parentReportAction ?? undefined; result.isArchivedRoom = ReportUtils.isArchivedRoom(report); result.isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report); result.isExpenseRequest = ReportUtils.isExpenseRequest(report); diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index f0056f1fbd72..a0e495299252 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1,5 +1,5 @@ import {CSSProperties} from 'react'; -import {Animated, DimensionValue, PressableStateCallbackType, StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {Animated, ColorValue, DimensionValue, PressableStateCallbackType, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {EdgeInsets} from 'react-native-safe-area-context'; import {ValueOf} from 'type-fest'; import * as Browser from '@libs/Browser'; From e436a6c98eadea70607176787eee4c9c6e259590 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 14 Dec 2023 19:29:41 +0100 Subject: [PATCH 09/25] fix: removed unused prop --- src/components/LHNOptionsList/OptionRowLHN.tsx | 8 +++----- src/components/LHNOptionsList/types.ts | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 38dcaff7b22c..6f20c625938c 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -28,7 +28,7 @@ import CONST from '@src/CONST'; import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; import {OptionRowLHNProps} from './types'; -function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () => {}, optionItem = null, viewMode = 'default', style}: OptionRowLHNProps) { +function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem = null, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); const popoverAnchor = useRef(null); @@ -73,14 +73,12 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () ? ([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] as StyleProp) : ([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter] as StyleProp), ); - const hoveredBackgroundColor = - (!!hoverStyle || !!styles.sidebarLinkHover) && 'backgroundColor' in (hoverStyle ?? styles.sidebarLinkHover) ? (hoverStyle ?? styles.sidebarLinkHover).backgroundColor : theme.sidebar; + const hoveredBackgroundColor = !!styles.sidebarLinkHover && 'backgroundColor' in styles.sidebarLinkHover ? styles.sidebarLinkHover.backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; const defaultSubscriptSize = optionItem.isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT; const shouldShowGreenDotIndicator = !hasBrickError && ReportUtils.requiresAttentionFromCurrentUser(optionItem, optionItem.parentReportAction); - /** * Show the ReportActionContextMenu modal popover. * @@ -165,7 +163,7 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () styles.sidebarLinkInnerLHN, StyleUtils.getBackgroundColorStyle(theme.sidebar), isFocused ? styles.sidebarLinkActive : null, - (hovered || isContextMenuActive) && !isFocused ? hoverStyle ?? styles.sidebarLinkHover : styles.sidebarLinkHover, + (hovered || isContextMenuActive) && !isFocused ? styles.sidebarLinkHover : null, ]} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 3d2920a2bf2a..ae8513c652cf 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -84,7 +84,6 @@ type OptionRowLHNDataProps = { }; type OptionRowLHNProps = { - hoverStyle?: StyleProp; reportID: string; isFocused?: boolean; onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; From 01f2aa72364fb486be468427f28ad1152b67bffa Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 14 Dec 2023 19:47:11 +0100 Subject: [PATCH 10/25] fix: linter --- .../LHNOptionsList/LHNOptionsList.tsx | 6 +++--- src/components/LHNOptionsList/types.ts | 4 +++- .../types.ts | 2 +- src/components/SubscriptAvatar.tsx | 20 +------------------ 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 76c7106ff9d2..2eb42080ef6a 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,5 +1,5 @@ import {FlashList} from '@shopify/flash-list'; -import React, {useCallback} from 'react'; +import React, {ReactElement, useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; @@ -11,7 +11,7 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import OptionRowLHNData from './OptionRowLHNData'; -import {LHNOptionsListOnyxProps, LHNOptionsListProps} from './types'; +import {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types'; const keyExtractor = (item: string) => `report_${item}`; @@ -36,7 +36,7 @@ function LHNOptionsList({ * Function which renders a row in the list */ const renderItem = useCallback( - ({item: reportID}: {item: string}) => { + ({item: reportID}: RenderItemProps): ReactElement => { const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index ae8513c652cf..c6aa3ac2930e 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -92,4 +92,6 @@ type OptionRowLHNProps = { optionItem?: OptionData | null; }; -export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps}; +type RenderItemProps = {item: string}; + +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps, RenderItemProps}; diff --git a/src/components/PressableWithSecondaryInteraction/types.ts b/src/components/PressableWithSecondaryInteraction/types.ts index 33eb27afbe57..2e9867ba4545 100644 --- a/src/components/PressableWithSecondaryInteraction/types.ts +++ b/src/components/PressableWithSecondaryInteraction/types.ts @@ -1,4 +1,4 @@ -import {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {PressableWithFeedbackProps} from '@components/Pressable/PressableWithFeedback'; import ChildrenProps from '@src/types/utils/ChildrenProps'; diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index 71fe4423f7aa..1ab2b4238213 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -4,29 +4,11 @@ import {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {AvatarSource} from '@libs/UserUtils'; import CONST from '@src/CONST'; -import {AvatarType} from '@src/types/onyx/OnyxCommon'; +import {Icon} from '@src/types/onyx/OnyxCommon'; import Avatar from './Avatar'; import UserDetailsTooltip from './UserDetailsTooltip'; -type SubAvatar = { - /** Avatar source to display */ - source?: AvatarSource; - - /** Denotes whether it is an avatar or a workspace avatar */ - type?: AvatarType; - - /** Owner of the avatar. If user, displayName. If workspace, policy name */ - name?: string; - - /** Avatar id */ - id?: number | string; - - /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ - fallbackIcon?: AvatarSource; -}; - type SubscriptAvatarProps = { /** Avatar URL or icon */ mainAvatar?: Icon; From ab774c9a781a25dbc74fe031081f8540dc7a45cc Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Dec 2023 09:41:52 +0100 Subject: [PATCH 11/25] chore: added todos --- src/components/LHNOptionsList/LHNOptionsList.tsx | 1 + src/components/LHNOptionsList/OptionRowLHN.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 2eb42080ef6a..c61c8d711533 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -55,6 +55,7 @@ function LHNOptionsList({ reportActions={itemReportActions} parentReportAction={itemParentReportAction} policy={itemPolicy} + // @ts-expect-error TODO: Remove this once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. personalDetails={participantsPersonalDetails} transaction={itemTransaction} receiptTransactions={transactions} diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 6f20c625938c..c97d5ddfa8c7 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -93,6 +93,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti ContextMenuActions.CONTEXT_MENU_TYPES.REPORT, event, '', + // @ts-expect-error TODO: Remove this once ReportActionContextMenu (https://github.com/Expensify/App/pull/32670) is migrated to TypeScript. popoverAnchor, reportID, '0', From 91e7c92ea6ea8e34f89914de04afb492242459a2 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Dec 2023 13:49:50 +0100 Subject: [PATCH 12/25] fix: adjust type --- src/components/LHNOptionsList/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index c6aa3ac2930e..c934f7b54db0 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -6,7 +6,7 @@ import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; -import type {PersonalDetails, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ @@ -22,7 +22,7 @@ type LHNOptionsListOnyxProps = { preferredLocale: OnyxEntry>; /** List of users' personal details */ - personalDetails: OnyxEntry>; + personalDetails: OnyxEntry; /** The transaction from the parent report action */ transactions: OnyxCollection; From 18352a72c7b9759e9d13268f6265cc445ddbb251 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Dec 2023 15:35:42 +0100 Subject: [PATCH 13/25] Refactor imports and types in LHNOptionsList, OfflineWithFeedback, PressableWithSecondaryInteraction, and SidebarUtils --- src/components/LHNOptionsList/types.ts | 4 ++-- src/components/OfflineWithFeedback.tsx | 2 +- src/components/PressableWithSecondaryInteraction/types.ts | 2 +- src/libs/SidebarUtils.ts | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index c934f7b54db0..7cafbbe2ead0 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -6,7 +6,7 @@ import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; -import type {PersonalDetails, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ @@ -57,7 +57,7 @@ type OptionRowLHNDataProps = { isFocused: boolean; /** List of users' personal details */ - personalDetails: Record; + personalDetails: PersonalDetailsList; /** The preferred language for the app */ preferredLocale: OnyxEntry>; diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index f9787d86abf7..4522595826af 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -18,7 +18,7 @@ import MessagesRow from './MessagesRow'; type OfflineWithFeedbackProps = ChildrenProps & { /** The type of action that's pending */ - pendingAction: OnyxCommon.PendingAction | undefined; + pendingAction?: OnyxCommon.PendingAction; /** Determine whether to hide the component's children if deletion is pending */ shouldHideOnDelete?: boolean; diff --git a/src/components/PressableWithSecondaryInteraction/types.ts b/src/components/PressableWithSecondaryInteraction/types.ts index 2f5abfe5bb6e..bf999e9692b5 100644 --- a/src/components/PressableWithSecondaryInteraction/types.ts +++ b/src/components/PressableWithSecondaryInteraction/types.ts @@ -1,4 +1,4 @@ -import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; +import type {GestureResponderEvent} from 'react-native'; import {PressableWithFeedbackProps} from '@components/Pressable/PressableWithFeedback'; import type {ParsableStyle} from '@styles/utils/types'; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 0f2e8ae2230c..16a8fb09d21e 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -4,9 +4,8 @@ import Onyx, {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {PersonalDetails} from '@src/types/onyx'; +import {PersonalDetails, PersonalDetailsList} from '@src/types/onyx'; import Beta from '@src/types/onyx/Beta'; -import * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import Policy from '@src/types/onyx/Policy'; import Report from '@src/types/onyx/Report'; import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction'; @@ -232,7 +231,7 @@ type ActorDetails = { function getOptionData( report: OnyxEntry, reportActions: OnyxEntry, - personalDetails: Record, + personalDetails: PersonalDetailsList, preferredLocale: ValueOf, policy: OnyxEntry, parentReportAction: OnyxEntry, @@ -285,7 +284,8 @@ function getOptionData( result.isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report); result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : undefined; - result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors; + // @ts-expect-error TODO: Remove this once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. + result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions); result.brickRoadIndicator = Object.keys(result.allReportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; result.ownerAccountID = report.ownerAccountID; result.managerID = report.managerID; From 097a03ce768b1229112f9feac3027b854f3cc5df Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 21 Dec 2023 10:37:58 +0100 Subject: [PATCH 14/25] Refactor OptionRowLHN component and update types Fix type error in SidebarUtils --- src/components/LHNOptionsList/OptionRowLHN.tsx | 11 +++++------ src/components/LHNOptionsList/types.ts | 14 +++++++++----- src/libs/SidebarUtils.ts | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index b4a824071e89..869721b3997a 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -59,13 +59,12 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const textStyle = isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; const textUnreadStyle = optionItem?.isUnread ? [textStyle, styles.sidebarLinkTextBold] : [textStyle]; - const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], style ?? {}); - const alternateTextStyle = StyleUtils.combineStyles( + const displayNameStyle = [styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, textUnreadStyle, style]; + const alternateTextStyle = viewMode === CONST.OPTION_MODE.COMPACT - ? [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2] - : [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting], - style ?? {}, - ); + ? [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2, style] + : [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, style]; + const contentContainerStyles = viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, StyleUtils.getCompactContentContainerStyles()] : [styles.flex1]; const sidebarInnerRowStyle = StyleSheet.flatten( diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 7cafbbe2ead0..95cca3316ee1 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,12 +1,12 @@ import type {ContentStyle} from '@shopify/flash-list'; import type {RefObject} from 'react'; -import {type StyleProp, View, type ViewStyle} from 'react-native'; +import {type StyleProp, TextStyle, View, type ViewStyle} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; -import type {PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ @@ -19,7 +19,7 @@ type LHNOptionsListOnyxProps = { reportActions: OnyxCollection; /** Indicates which locale the user currently has selected */ - preferredLocale: OnyxEntry>; + preferredLocale: OnyxEntry; /** List of users' personal details */ personalDetails: OnyxEntry; @@ -60,7 +60,7 @@ type OptionRowLHNDataProps = { personalDetails: PersonalDetailsList; /** The preferred language for the app */ - preferredLocale: OnyxEntry>; + preferredLocale: OnyxEntry; /** The full data of the report */ fullReport: OnyxEntry; @@ -74,12 +74,16 @@ type OptionRowLHNDataProps = { /** The transaction from the parent report action */ transaction: OnyxEntry; + /** Comment added to report */ comment: string; + /** The receipt transaction from the parent report action */ receiptTransactions: OnyxCollection; + /** The reportID of the report */ reportID: string; + /** Array of report actions for this report */ reportActions: OnyxEntry; }; @@ -88,7 +92,7 @@ type OptionRowLHNProps = { isFocused?: boolean; onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; viewMode?: ValueOf; - style?: ViewStyle | ViewStyle[]; + style?: StyleProp; optionItem?: OptionData | null; }; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 16a8fb09d21e..16d568e37ee6 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -231,7 +231,7 @@ type ActorDetails = { function getOptionData( report: OnyxEntry, reportActions: OnyxEntry, - personalDetails: PersonalDetailsList, + personalDetails: OnyxEntry, preferredLocale: ValueOf, policy: OnyxEntry, parentReportAction: OnyxEntry, From 8b5fe4b34c3c7908fb4338e10eda8e74c02ead1e Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 21 Dec 2023 13:52:18 +0100 Subject: [PATCH 15/25] Update OptionData types and fix SubscriptAvatar props --- .../LHNOptionsList/OptionRowLHN.tsx | 14 ++++----- src/components/LHNOptionsList/types.ts | 8 +++-- src/components/SubscriptAvatar.tsx | 29 +++++++------------ src/libs/ReportUtils.ts | 4 +-- src/libs/SidebarUtils.ts | 4 +-- 5 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 869721b3997a..abd18ffd63e6 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,6 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef, useState} from 'react'; -import {GestureResponderEvent, StyleProp, StyleSheet, TextInput, View, ViewStyle} from 'react-native'; +import {GestureResponderEvent, StyleSheet, TextInput, View, ViewStyle} from 'react-native'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -28,7 +28,7 @@ import CONST from '@src/CONST'; import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; import {OptionRowLHNProps} from './types'; -function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem = null, viewMode = 'default', style}: OptionRowLHNProps) { +function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); const popoverAnchor = useRef(null); @@ -67,10 +67,10 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const contentContainerStyles = viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, StyleUtils.getCompactContentContainerStyles()] : [styles.flex1]; - const sidebarInnerRowStyle = StyleSheet.flatten( + const sidebarInnerRowStyle = StyleSheet.flatten( viewMode === CONST.OPTION_MODE.COMPACT - ? ([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] as StyleProp) - : ([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter] as StyleProp), + ? [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] + : [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter], ); const hoveredBackgroundColor = !!styles.sidebarLinkHover && 'backgroundColor' in styles.sidebarLinkHover ? styles.sidebarLinkHover.backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; @@ -121,7 +121,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti return ( @@ -170,7 +170,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti > - {(optionItem.icons?.length ?? 0) > 0 && + {optionItem.icons?.length && (optionItem.shouldShowSubscript ? ( ; + type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ policy: OnyxCollection; @@ -44,7 +46,7 @@ type CustomLHNOptionsListProps = { onSelectRow: (reportID: string) => void; /** Toggle between compact and default view of the option */ - optionMode: ValueOf; + optionMode: OptionMode; /** Whether to allow option focus or not */ shouldDisableFocusOptions?: boolean; @@ -91,9 +93,9 @@ type OptionRowLHNProps = { reportID: string; isFocused?: boolean; onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; - viewMode?: ValueOf; + viewMode?: OptionMode; style?: StyleProp; - optionItem?: OptionData | null; + optionItem?: OptionData; }; type RenderItemProps = {item: string}; diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index 1ab2b4238213..2bc4df4fa70b 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -29,14 +29,7 @@ type SubscriptAvatarProps = { showTooltip?: boolean; }; -function SubscriptAvatar({ - mainAvatar = {} as Icon, - secondaryAvatar = {} as Icon, - size = CONST.AVATAR_SIZE.DEFAULT, - backgroundColor, - noMargin = false, - showTooltip = true, -}: SubscriptAvatarProps) { +function SubscriptAvatar({mainAvatar, secondaryAvatar, size = CONST.AVATAR_SIZE.DEFAULT, backgroundColor, noMargin = false, showTooltip = true}: SubscriptAvatarProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -48,23 +41,23 @@ function SubscriptAvatar({ diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 29197e51aaa9..6a504ad40348 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -313,7 +313,7 @@ type DisplayNameWithTooltips = Array; displayNamesWithTooltips?: DisplayNameWithTooltips | null; descriptiveText?: string; } & Report; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 16d568e37ee6..7e93f6fc9e5d 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -245,7 +245,7 @@ function getOptionData( const result: ReportUtils.OptionData = { alternateText: null, - allReportErrors: null, + allReportErrors: undefined, brickRoadIndicator: null, tooltipText: null, subtitle: null, @@ -277,7 +277,7 @@ function getOptionData( result.isThread = ReportUtils.isChatThread(report); result.isChatRoom = ReportUtils.isChatRoom(report); result.isTaskReport = ReportUtils.isTaskReport(report); - result.parentReportAction = parentReportAction ?? undefined; + result.parentReportAction = parentReportAction; result.isArchivedRoom = ReportUtils.isArchivedRoom(report); result.isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report); result.isExpenseRequest = ReportUtils.isExpenseRequest(report); From 882d8d98841a178204449967e0c71ee31e0a8d06 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 21 Dec 2023 14:13:53 +0100 Subject: [PATCH 16/25] fix: adjust code based on the comments --- src/components/LHNOptionsList/LHNOptionsList.tsx | 2 +- src/components/LHNOptionsList/OptionRowLHNData.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index c61c8d711533..4789536c90f1 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -46,8 +46,8 @@ function LHNOptionsList({ const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null; const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; const participants = [...ReportUtils.getParticipantsIDs(itemFullReport), itemFullReport?.ownerAccountID]; - const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails); + return ( (); + const optionItemRef = useRef(undefined); const linkedTransaction = useMemo(() => { const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); const lastReportAction = sortedReportActions[0]; @@ -44,9 +44,9 @@ function OptionRowLHNData({ if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } - if (item) { - optionItemRef.current = item; - } + + 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 From 7d1c7f7af52bc2e3900219df657aa6700d72bec6 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 2 Jan 2024 14:47:25 +0100 Subject: [PATCH 17/25] fix: resolve comment --- src/components/LHNOptionsList/OptionRowLHNData.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 3eca9c5f9913..a0bd4f430c35 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -3,7 +3,7 @@ import React, {useEffect, useMemo, useRef} from 'react'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import SidebarUtils from '@libs/SidebarUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import * as ReportLib from '@userActions/Report'; +import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; import OptionRowLHN from './OptionRowLHN'; @@ -57,7 +57,7 @@ function OptionRowLHNData({ if (!optionItem || !!optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { return; } - ReportLib.setReportWithDraft(reportID, true); + Report.setReportWithDraft(reportID, true); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); From 82ce1ce70e9327f6d4c82161c8653d3b2612a79b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 8 Jan 2024 10:22:41 +0100 Subject: [PATCH 18/25] fix: resolve comments --- .../LHNOptionsList/LHNOptionsList.tsx | 5 ++-- .../LHNOptionsList/OptionRowLHN.tsx | 20 +++++++------- .../LHNOptionsList/OptionRowLHNData.tsx | 6 ++--- src/components/LHNOptionsList/types.ts | 26 ++++++++++++++----- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 4789536c90f1..77940b98bc57 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,5 +1,6 @@ import {FlashList} from '@shopify/flash-list'; -import React, {ReactElement, useCallback} from 'react'; +import type {ReactElement} from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; @@ -11,7 +12,7 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import OptionRowLHNData from './OptionRowLHNData'; -import {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types'; +import type {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types'; const keyExtractor = (item: string) => `report_${item}`; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 739ec1410f80..0696c2d8fb13 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,6 +1,7 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef, useState} from 'react'; -import {GestureResponderEvent, StyleSheet, TextInput, View, ViewStyle} from 'react-native'; +import type {GestureResponderEvent, ViewStyle} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -25,7 +26,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; -import {OptionRowLHNProps} from './types'; +import type {OptionRowLHNProps} from './types'; function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); @@ -90,8 +91,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti CONST.CONTEXT_MENU_TYPES.REPORT, event, '', - // @ts-expect-error TODO: Remove this once ReportActionContextMenu (https://github.com/Expensify/App/pull/32670) is migrated to TypeScript. - popoverAnchor, + popoverAnchor.current, reportID, '0', reportID, @@ -101,14 +101,14 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti false, false, optionItem.isPinned, - optionItem.isUnread, + optionItem.isUnread ?? undefined, ); }; - const emojiCode = typeof optionItem.status === 'object' ? optionItem.status?.emojiCode : ''; - const statusText = typeof optionItem.status === 'object' ? optionItem.status?.text : ''; - const statusClearAfterDate = typeof optionItem.status === 'object' ? optionItem.status?.clearAfter : ''; - const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate ?? ''); + const emojiCode = optionItem.status?.emojiCode ?? ''; + const statusText = optionItem.status?.text ?? ''; + const statusClearAfterDate = optionItem.status?.clearAfter ?? ''; + const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText ? `${statusText} ` : ''}(${formattedDate})` : statusText; const report = ReportUtils.getReport(optionItem.reportID ?? ''); const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(isNotEmptyObject(report) ? report : null); @@ -148,7 +148,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti showPopover(event); // Ensure that we blur the composer when opening context menu, so that only one component is focused at a time if (DomUtils.getActiveElement()) { - (DomUtils.getActiveElement() as HTMLElement | TextInput)?.blur(); + (DomUtils.getActiveElement() as HTMLElement)?.blur(); } }} withoutFocusOnSecondaryInteraction diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index a0bd4f430c35..741b66d59607 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -7,7 +7,7 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; import OptionRowLHN from './OptionRowLHN'; -import {OptionRowLHNDataProps} from './types'; +import type {OptionRowLHNDataProps} from './types'; /* * This component gets the data from onyx for the actual @@ -17,7 +17,7 @@ import {OptionRowLHNDataProps} from './types'; */ function OptionRowLHNData({ isFocused = false, - fullReport = null, + fullReport, reportActions, personalDetails = {}, preferredLocale = CONST.LOCALES.DEFAULT, @@ -30,7 +30,7 @@ function OptionRowLHNData({ }: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; - const optionItemRef = useRef(undefined); + const optionItemRef = useRef(); const linkedTransaction = useMemo(() => { const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); const lastReportAction = sortedReportActions[0]; diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index f4215fcbf388..6f28edeb79f7 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,10 +1,10 @@ import type {ContentStyle} from '@shopify/flash-list'; import type {RefObject} from 'react'; -import {type StyleProp, TextStyle, View, type ViewStyle} from 'react-native'; +import type {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; -import CONST from '@src/CONST'; +import type CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; @@ -32,6 +32,7 @@ type LHNOptionsListOnyxProps = { /** List of draft comments */ draftComments: OnyxCollection; }; + type CustomLHNOptionsListProps = { /** Wrapper style for the section list */ style?: StyleProp; @@ -56,22 +57,22 @@ type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextVal type OptionRowLHNDataProps = { /** Whether row should be focused */ - isFocused: boolean; + isFocused?: boolean; /** List of users' personal details */ - personalDetails: PersonalDetailsList; + personalDetails?: PersonalDetailsList; /** The preferred language for the app */ - preferredLocale: OnyxEntry; + preferredLocale?: OnyxEntry; /** The full data of the report */ fullReport: OnyxEntry; /** The policy which the user has access to and which the report could be tied to */ - policy: OnyxEntry; + policy?: OnyxEntry; /** The action from the parent report */ - parentReportAction: OnyxEntry; + parentReportAction?: OnyxEntry; /** The transaction from the parent report action */ transaction: OnyxEntry; @@ -90,11 +91,22 @@ type OptionRowLHNDataProps = { }; type OptionRowLHNProps = { + /** The ID of the report that the option is for */ reportID: string; + + /** Whether this option is currently in focus so we can modify its style */ isFocused?: boolean; + + /** A function that is called when an option is selected. Selected option is passed as a param */ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; + + /** Toggle between compact and default view */ viewMode?: OptionMode; + + /** Additional style props */ style?: StyleProp; + + /** The item that should be rendered */ optionItem?: OptionData; }; From c4de9b2085eb29316c92da1949d6e370a2380b8e Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 9 Jan 2024 08:08:36 +0100 Subject: [PATCH 19/25] Fix import and variable names in OptionRowLHN component --- src/components/LHNOptionsList/OptionRowLHN.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 0696c2d8fb13..46161e401228 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -25,7 +25,7 @@ import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManag import * as ReportUtils from '@libs/ReportUtils'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; -import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {OptionRowLHNProps} from './types'; function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) { @@ -101,7 +101,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti false, false, optionItem.isPinned, - optionItem.isUnread ?? undefined, + !!optionItem.isUnread, ); }; @@ -111,10 +111,10 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText ? `${statusText} ` : ''}(${formattedDate})` : statusText; const report = ReportUtils.getReport(optionItem.reportID ?? ''); - const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(isNotEmptyObject(report) ? report : null); + const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(!isEmptyObject(report) ? report : null); const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && optionItem.chatType && !optionItem.isThread && (optionItem.displayNamesWithTooltips?.length ?? 0) > 2; - const fullTitle = isGroupChat ? getGroupChatName(isNotEmptyObject(report) ? report : null) : optionItem.text; + const fullTitle = isGroupChat ? getGroupChatName(!isEmptyObject(report) ? report : null) : optionItem.text; const subscriptAvatarBorderColor = isFocused ? focusedBackgroundColor : theme.sidebar; return ( @@ -173,13 +173,13 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti (optionItem.shouldShowSubscript ? ( ) : ( {}, opti {optionItem?.descriptiveText ? ( - {optionItem?.descriptiveText} + {optionItem.descriptiveText} ) : null} {hasBrickError && ( From 7ebbccbbae2d35076208dcf1f5815e152828a37d Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 9 Jan 2024 13:01:48 +0100 Subject: [PATCH 20/25] fix: resolve comments --- src/components/LHNOptionsList/LHNOptionsList.tsx | 10 ++++------ src/components/LHNOptionsList/OptionRowLHN.tsx | 10 +++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 77940b98bc57..6fe9a5404718 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -5,7 +5,6 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; import variables from '@styles/variables'; @@ -63,7 +62,7 @@ function LHNOptionsList({ viewMode={optionMode} isFocused={!shouldDisableFocusOptions && reportID === currentReportID} onSelectRow={onSelectRow} - preferredLocale={preferredLocale ?? CONST.LOCALES.DEFAULT} + preferredLocale={preferredLocale} comment={itemComment} /> ); @@ -91,7 +90,7 @@ function LHNOptionsList({ LHNOptionsList.displayName = 'LHNOptionsList'; -export default compose( +export default withCurrentReportID( withOnyx({ reports: { key: ONYXKEYS.COLLECTION.REPORT, @@ -114,8 +113,7 @@ export default compose( draftComments: { key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, - }), - withCurrentReportID, -)(LHNOptionsList); + })(LHNOptionsList), +); export type {LHNOptionsListProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 46161e401228..1932cf6c6b7f 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -148,7 +148,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti showPopover(event); // Ensure that we blur the composer when opening context menu, so that only one component is focused at a time if (DomUtils.getActiveElement()) { - (DomUtils.getActiveElement() as HTMLElement)?.blur(); + (DomUtils.getActiveElement() as HTMLElement | null)?.blur(); } }} withoutFocusOnSecondaryInteraction @@ -169,17 +169,17 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti > - {optionItem.icons?.length && + {(optionItem.icons?.length ?? 0) > 0 && (optionItem.shouldShowSubscript ? ( ) : ( Date: Wed, 10 Jan 2024 08:10:43 +0100 Subject: [PATCH 21/25] fix: resolve comment --- src/components/LHNOptionsList/LHNOptionsList.tsx | 4 ++-- src/components/LHNOptionsList/types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 6fe9a5404718..ae98fd776f34 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,7 +1,7 @@ import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; import React, {useCallback} from 'react'; -import {View} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -75,7 +75,7 @@ function LHNOptionsList({ ; /** Extra styles for the section list container */ - contentContainerStyles?: ContentStyle; + contentContainerStyles?: StyleProp; /** Sections for the section list */ data: string[]; From 0cfe92cd1c48e53526f96f64a5dacfdd801feecf Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 10 Jan 2024 09:02:23 +0100 Subject: [PATCH 22/25] fix: lint --- src/libs/GroupChatUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/GroupChatUtils.ts b/src/libs/GroupChatUtils.ts index 4048d4c72f15..5d925ae1c684 100644 --- a/src/libs/GroupChatUtils.ts +++ b/src/libs/GroupChatUtils.ts @@ -1,3 +1,4 @@ +import type {OnyxEntry} from 'react-native-onyx'; import type {Report} from '@src/types/onyx'; import * as ReportUtils from './ReportUtils'; From 286adbd09e52f30de84275a37a22c07a9f5685d7 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 15 Jan 2024 13:36:48 +0100 Subject: [PATCH 23/25] fix: resolve comment --- src/components/LHNOptionsList/OptionRowLHNData.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 741b66d59607..e324f37cc688 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -22,7 +22,7 @@ function OptionRowLHNData({ personalDetails = {}, preferredLocale = CONST.LOCALES.DEFAULT, comment, - policy = null, + policy, receiptTransactions, parentReportAction = null, transaction = null, @@ -40,7 +40,7 @@ function OptionRowLHNData({ const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! - const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale ?? CONST.LOCALES.DEFAULT, policy, parentReportAction); + const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale ?? CONST.LOCALES.DEFAULT, policy ?? null, parentReportAction); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } From 950c62f4c77f12150893524f2924e254541d314d Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 15 Jan 2024 13:39:11 +0100 Subject: [PATCH 24/25] fix: removed memo --- src/components/LHNOptionsList/LHNOptionsList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 12ad3a59592d..ae98fd776f34 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,6 +1,6 @@ import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; -import React, {memo, useCallback} from 'react'; +import React, {useCallback} from 'react'; import {StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; @@ -113,7 +113,7 @@ export default withCurrentReportID( draftComments: { key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, - })(memo(LHNOptionsList)), + })(LHNOptionsList), ); export type {LHNOptionsListProps}; From c82a4a738a222db6b47f98556d0e75ad3402a123 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 22 Jan 2024 11:05:06 +0100 Subject: [PATCH 25/25] fix: typecheck --- src/ONYXKEYS.ts | 2 +- .../LHNOptionsList/LHNOptionsList.tsx | 24 ++++++++++++++++- .../LHNOptionsList/OptionRowLHNData.tsx | 6 ++--- src/components/LHNOptionsList/types.ts | 11 +++++++- src/libs/ReportUtils.ts | 6 ++--- src/libs/SidebarUtils.ts | 27 ++++++++++++------- 6 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 40a43d8195de..a2cb944edc76 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -461,7 +461,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.TRANSACTION_DRAFT]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags; [ONYXKEYS.COLLECTION.SELECTED_TAB]: string; - [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolations; + [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolation[]; [ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT]: string; [ONYXKEYS.COLLECTION.NEXT_STEP]: OnyxTypes.ReportNextStep; diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index b012deb5a4b4..ecf320807b48 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -4,6 +4,7 @@ import React, {useCallback} from 'react'; import {StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; +import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -30,8 +31,10 @@ function LHNOptionsList({ transactions = {}, currentReportID = '', draftComments = {}, + transactionViolations = {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); + const {canUseViolations} = usePermissions(); /** * Function which renders a row in the list */ @@ -64,10 +67,26 @@ function LHNOptionsList({ onSelectRow={onSelectRow} preferredLocale={preferredLocale} comment={itemComment} + transactionViolations={transactionViolations} + canUseViolations={canUseViolations} /> ); }, - [currentReportID, draftComments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], + [ + currentReportID, + draftComments, + onSelectRow, + optionMode, + personalDetails, + policy, + preferredLocale, + reportActions, + reports, + shouldDisableFocusOptions, + transactions, + transactionViolations, + canUseViolations, + ], ); return ( @@ -113,6 +132,9 @@ export default withCurrentReportID( draftComments: { key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + }, })(LHNOptionsList), ); diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 50cecf355395..dca74e880169 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -41,7 +41,7 @@ function OptionRowLHNData({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [fullReport?.reportID, receiptTransactions, reportActions]); - const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(fullReport, transactionViolations, parentReportAction); + const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(fullReport, transactionViolations, parentReportAction ?? null); const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! @@ -49,10 +49,10 @@ function OptionRowLHNData({ report: fullReport, reportActions, personalDetails, - preferredLocale, + preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, policy, parentReportAction, - hasViolations, + hasViolations: !!hasViolations, }); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 11980c9a38b9..24cebb8e3da2 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -6,7 +6,7 @@ import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import type CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; -import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx'; type OptionMode = ValueOf; @@ -31,6 +31,9 @@ type LHNOptionsListOnyxProps = { /** List of draft comments */ draftComments: OnyxCollection; + + /** The list of transaction violations */ + transactionViolations: OnyxCollection; }; type CustomLHNOptionsListProps = { @@ -88,6 +91,12 @@ type OptionRowLHNDataProps = { /** Array of report actions for this report */ reportActions: OnyxEntry; + + /** List of transaction violation */ + transactionViolations: OnyxCollection; + + /** Whether the user can use violations */ + canUseViolations: boolean | undefined; }; type OptionRowLHNProps = { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index de41d6f1b03f..a384f9dad4bd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3546,8 +3546,8 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b /** * Checks to see if a report's parentAction is a money request that contains a violation */ -function doesTransactionThreadHaveViolations(report: Report, transactionViolations: OnyxCollection, parentReportAction: ReportAction): boolean { - if (parentReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { +function doesTransactionThreadHaveViolations(report: OnyxEntry, transactionViolations: OnyxCollection, parentReportAction: OnyxEntry): boolean { + if (parentReportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { return false; } const {IOUTransactionID, IOUReportID} = parentReportAction.originalMessage ?? {}; @@ -3557,7 +3557,7 @@ function doesTransactionThreadHaveViolations(report: Report, transactionViolatio if (!isCurrentUserSubmitter(IOUReportID)) { return false; } - if (report.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED) { + if (report?.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report?.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED) { return false; } return TransactionUtils.hasViolation(IOUTransactionID, transactionViolations); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 1f46f8310345..4a2c4a2da22a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -11,6 +11,7 @@ import type Policy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; +import type DeepValueOf from '@src/types/utils/DeepValueOf'; import * as CollectionUtils from './CollectionUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; @@ -247,15 +248,23 @@ type ActorDetails = { /** * Gets all the data necessary for rendering an OptionRowLHN component */ -function getOptionData( - report: OnyxEntry, - reportActions: OnyxEntry, - personalDetails: OnyxEntry, - preferredLocale: ValueOf, - policy: OnyxEntry, - parentReportAction: OnyxEntry, - hasViolations: boolean, -): ReportUtils.OptionData | undefined { +function getOptionData({ + report, + reportActions, + personalDetails, + preferredLocale, + policy, + parentReportAction, + hasViolations, +}: { + report: OnyxEntry; + reportActions: OnyxEntry; + personalDetails: OnyxEntry; + preferredLocale: DeepValueOf; + policy: OnyxEntry | undefined; + parentReportAction: OnyxEntry | undefined; + hasViolations: boolean; +}): ReportUtils.OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do // a null check here and return early.