From 1d78c427ad6353c547630904ecff06fc87835556 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 13 Nov 2024 16:48:22 +0700 Subject: [PATCH 01/25] WIP Make ReportActionItem pure component --- .../home/report/PureReportActionItem.tsx | 1073 +++++++++++++++++ src/pages/home/report/ReportActionItem.tsx | 82 +- 2 files changed, 1114 insertions(+), 41 deletions(-) create mode 100644 src/pages/home/report/PureReportActionItem.tsx diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx new file mode 100644 index 000000000000..2c4637582d41 --- /dev/null +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -0,0 +1,1073 @@ +import lodashIsEqual from 'lodash/isEqual'; +import React, {memo, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import type {GestureResponderEvent, TextInput} from 'react-native'; +import {InteractionManager, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import type {Emoji} from '@assets/emojis/types'; +import {AttachmentContext} from '@components/AttachmentContext'; +import Button from '@components/Button'; +import DisplayNames from '@components/DisplayNames'; +import Hoverable from '@components/Hoverable'; +import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import InlineSystemMessage from '@components/InlineSystemMessage'; +import KYCWall from '@components/KYCWall'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider'; +import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; +import ReportActionItemEmojiReactions from '@components/Reactions/ReportActionItemEmojiReactions'; +import RenderHTML from '@components/RenderHTML'; +import type {ActionableItem} from '@components/ReportActionItem/ActionableItemButtons'; +import ActionableItemButtons from '@components/ReportActionItem/ActionableItemButtons'; +import ChronosOOOListActions from '@components/ReportActionItem/ChronosOOOListActions'; +import ExportIntegration from '@components/ReportActionItem/ExportIntegration'; +import IssueCardMessage from '@components/ReportActionItem/IssueCardMessage'; +import MoneyRequestAction from '@components/ReportActionItem/MoneyRequestAction'; +import ReportPreview from '@components/ReportActionItem/ReportPreview'; +import TaskAction from '@components/ReportActionItem/TaskAction'; +import TaskPreview from '@components/ReportActionItem/TaskPreview'; +import TripRoomPreview from '@components/ReportActionItem/TripRoomPreview'; +import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; +import Text from '@components/Text'; +import UnreadActionIndicator from '@components/UnreadActionIndicator'; +import useLocalize from '@hooks/useLocalize'; +import usePrevious from '@hooks/usePrevious'; +import useReportScrollManager from '@hooks/useReportScrollManager'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import ControlSelection from '@libs/ControlSelection'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import * as ErrorUtils from '@libs/ErrorUtils'; +import focusComposerWithDelay from '@libs/focusComposerWithDelay'; +import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; +import Navigation from '@libs/Navigation/Navigation'; +import Permissions from '@libs/Permissions'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import SelectionScraper from '@libs/SelectionScraper'; +import shouldRenderAddPaymentCard from '@libs/shouldRenderAppPaymentCard'; +import {doesUserHavePaymentCardAdded} from '@libs/SubscriptionUtils'; +import {ReactionListContext} from '@pages/home/ReportScreenContext'; +import * as BankAccounts from '@userActions/BankAccounts'; +import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; +import * as Member from '@userActions/Policy/Member'; +import * as Report from '@userActions/Report'; +import * as ReportActions from '@userActions/ReportActions'; +import * as Session from '@userActions/Session'; +import * as Transaction from '@userActions/Transaction'; +import * as User from '@userActions/User'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import {RestrictedReadOnlyContextMenuActions} from './ContextMenu/ContextMenuActions'; +import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; +import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; +import {hideContextMenu} from './ContextMenu/ReportActionContextMenu'; +import LinkPreviewer from './LinkPreviewer'; +import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; +import ReportActionItemContentCreated from './ReportActionItemContentCreated'; +import ReportActionItemDraft from './ReportActionItemDraft'; +import ReportActionItemGrouped from './ReportActionItemGrouped'; +import ReportActionItemMessage from './ReportActionItemMessage'; +import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; +import ReportActionItemSingle from './ReportActionItemSingle'; +import ReportActionItemThread from './ReportActionItemThread'; +import ReportAttachmentsContext from './ReportAttachmentsContext'; + +type PureReportActionItemProps = { + /** Report for this action */ + report: OnyxEntry; + + /** The transaction thread report associated with the report for this action, if any */ + transactionThreadReport?: OnyxEntry; + + /** Array of report actions for the report for this action */ + // eslint-disable-next-line react/no-unused-prop-types + reportActions: OnyxTypes.ReportAction[]; + + /** Report action belonging to the report's parent */ + parentReportAction: OnyxEntry; + + /** The transaction thread report's parentReportAction */ + /** It's used by withOnyx HOC */ + // eslint-disable-next-line react/no-unused-prop-types + parentReportActionForTransactionThread?: OnyxEntry; + + /** All the data of the action item */ + action: OnyxTypes.ReportAction; + + /** Should the comment have the appearance of being grouped with the previous comment? */ + displayAsGroup: boolean; + + /** Is this the most recent IOU Action? */ + isMostRecentIOUReportAction: boolean; + + /** Should we display the new marker on top of the comment? */ + shouldDisplayNewMarker: boolean; + + /** Determines if the avatar is displayed as a subscript (positioned lower than normal) */ + shouldShowSubscriptAvatar?: boolean; + + /** Position index of the report action in the overall report FlatList view */ + index: number; + + /** Flag to show, hide the thread divider line */ + shouldHideThreadDividerLine?: boolean; + + linkedReportActionID?: string; + + /** Callback to be called on onPress */ + onPress?: () => void; + + /** If this is the first visible report action */ + isFirstVisibleReportAction: boolean; + + /** IF the thread divider line will be used */ + shouldUseThreadDividerLine?: boolean; + + hideThreadReplies?: boolean; + + /** Whether context menu should be displayed */ + shouldDisplayContextMenu?: boolean; + + draftMessage? : OnyxEntry +}; + +function PureReportActionItem({ + action, + report, + transactionThreadReport, + linkedReportActionID, + displayAsGroup, + index, + isMostRecentIOUReportAction, + parentReportAction, + shouldDisplayNewMarker, + shouldHideThreadDividerLine = false, + shouldShowSubscriptAvatar = false, + onPress = undefined, + isFirstVisibleReportAction = false, + shouldUseThreadDividerLine = false, + hideThreadReplies = false, + shouldDisplayContextMenu = true, + parentReportActionForTransactionThread, +}: PureReportActionItemProps) { + const {translate} = useLocalize(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const blockedFromConcierge = useBlockedFromConcierge(); + const reportID = report?.reportID ?? ''; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); + const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, { + selector: (draftMessagesForReport) => { + const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID]; + return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message; + }, + }); + const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action) ?? -1}`); + const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`); + const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); + const [linkedTransactionRouteError] = useOnyx( + `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, + {selector: (transaction) => transaction?.errorFields?.route ?? null}, + ); + const theme = useTheme(); + const styles = useThemeStyles(); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. + const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); + const StyleUtils = useStyleUtils(); + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; + const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); + const [isEmojiPickerActive, setIsEmojiPickerActive] = useState(); + const [isPaymentMethodPopoverActive, setIsPaymentMethodPopoverActive] = useState(); + + const [isHidden, setIsHidden] = useState(false); + const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); + const reactionListRef = useContext(ReactionListContext); + const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); + const textInputRef = useRef(null); + const popoverAnchorRef = useRef>(null); + const downloadedPreviews = useRef([]); + const prevDraftMessage = usePrevious(draftMessage); + const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); + // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); + const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; + const reportScrollManager = useReportScrollManager(); + const isActionableWhisper = + ReportActionsUtils.isActionableMentionWhisper(action) || ReportActionsUtils.isActionableTrackExpense(action) || ReportActionsUtils.isActionableReportMentionWhisper(action); + const originalMessage = ReportActionsUtils.getOriginalMessage(action); + + const highlightedBackgroundColorIfNeeded = useMemo( + () => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.messageHighlightBG) : {}), + [StyleUtils, isReportActionLinked, theme.messageHighlightBG], + ); + + const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action); + const isOriginalMessageAnObject = originalMessage && typeof originalMessage === 'object'; + const hasResolutionInOriginalMessage = isOriginalMessageAnObject && 'resolution' in originalMessage; + const prevActionResolution = usePrevious(isActionableWhisper && hasResolutionInOriginalMessage ? originalMessage?.resolution : null); + + // IOUDetails only exists when we are sending money + const isSendingMoney = + ReportActionsUtils.isMoneyRequestAction(action) && + ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && + ReportActionsUtils.getOriginalMessage(action)?.IOUDetails; + + const updateHiddenState = useCallback( + (isHiddenValue: boolean) => { + setIsHidden(isHiddenValue); + const message = Array.isArray(action.message) ? action.message?.at(-1) : action.message; + const isAttachment = ReportUtils.isReportMessageAttachment(message); + if (!isAttachment) { + return; + } + updateHiddenAttachments(action.reportActionID, isHiddenValue); + }, + [action.reportActionID, action.message, updateHiddenAttachments], + ); + + useEffect( + () => () => { + // ReportActionContextMenu, EmojiPicker and PopoverReactionList are global components, + // we should also hide them when the current component is destroyed + if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { + ReportActionContextMenu.hideContextMenu(); + ReportActionContextMenu.hideDeleteModal(); + } + if (EmojiPickerAction.isActive(action.reportActionID)) { + EmojiPickerAction.hideEmojiPicker(true); + } + if (reactionListRef?.current?.isActiveReportAction(action.reportActionID)) { + reactionListRef?.current?.hideReactionList(); + } + }, + [action.reportActionID, reactionListRef], + ); + + useEffect(() => { + // We need to hide EmojiPicker when this is a deleted parent action + if (!isDeletedParentAction || !EmojiPickerAction.isActive(action.reportActionID)) { + return; + } + + EmojiPickerAction.hideEmojiPicker(true); + }, [isDeletedParentAction, action.reportActionID]); + + useEffect(() => { + if (prevDraftMessage !== undefined || draftMessage === undefined) { + return; + } + + focusComposerWithDelay(textInputRef.current)(true); + }, [prevDraftMessage, draftMessage]); + + useEffect(() => { + if (!Permissions.canUseLinkPreviews()) { + return; + } + + const urls = ReportActionsUtils.extractLinksFromMessageHtml(action); + if (lodashIsEqual(downloadedPreviews.current, urls) || action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { + return; + } + + downloadedPreviews.current = urls; + Report.expandURLPreview(reportID, action.reportActionID); + }, [action, reportID]); + + useEffect(() => { + if (draftMessage === undefined || !ReportActionsUtils.isDeletedAction(action)) { + return; + } + Report.deleteReportActionDraft(reportID, action); + }, [draftMessage, action, reportID]); + + // Hide the message if it is being moderated for a higher offense, or is hidden by a moderator + // Removed messages should not be shown anyway and should not need this flow + const latestDecision = ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision ?? ''; + useEffect(() => { + if (action.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT) { + return; + } + + // Hide reveal message button and show the message if latestDecision is changed to empty + if (!latestDecision) { + setModerationDecision(CONST.MODERATION.MODERATOR_DECISION_APPROVED); + setIsHidden(false); + return; + } + + setModerationDecision(latestDecision); + if ( + ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === latestDecision) && + !ReportActionsUtils.isPendingRemove(action) + ) { + setIsHidden(true); + return; + } + setIsHidden(false); + }, [latestDecision, action]); + + const toggleContextMenuFromActiveReportAction = useCallback(() => { + setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); + }, [action.reportActionID]); + + const isArchivedRoom = ReportUtils.isArchivedRoomWithID(originalReportID); + const disabledActions = useMemo(() => (!ReportUtils.canWriteInReport(report) ? RestrictedReadOnlyContextMenuActions : []), [report]); + const isChronosReport = ReportUtils.chatIncludesChronosWithID(originalReportID); + /** + * Show the ReportActionContextMenu modal popover. + * + * @param [event] - A press event. + */ + const showPopover = useCallback( + (event: GestureResponderEvent | MouseEvent) => { + // Block menu on the message being Edited or if the report action item has errors + if (draftMessage !== undefined || !isEmptyObject(action.errors) || !shouldDisplayContextMenu) { + return; + } + + setIsContextMenuActive(true); + const selection = SelectionScraper.getCurrentSelection(); + ReportActionContextMenu.showContextMenu( + CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, + event, + selection, + popoverAnchorRef.current, + reportID, + action.reportActionID, + originalReportID, + draftMessage ?? '', + () => setIsContextMenuActive(true), + toggleContextMenuFromActiveReportAction, + isArchivedRoom, + isChronosReport, + false, + false, + disabledActions, + false, + setIsEmojiPickerActive as () => void, + ); + }, + [draftMessage, action, reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport], + ); + + // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. + // This fixes an issue where InvertedFlatList fails to auto scroll down and results in an empty space at the bottom of the chat in IOS. + useEffect(() => { + if (index !== 0 || !isActionableWhisper) { + return; + } + + if (prevActionResolution !== (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { + reportScrollManager.scrollToIndex(index); + } + }, [index, originalMessage, prevActionResolution, reportScrollManager, isActionableWhisper, hasResolutionInOriginalMessage]); + + const toggleReaction = useCallback( + (emoji: Emoji, ignoreSkinToneOnCompare?: boolean) => { + Report.toggleEmojiReaction(reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare); + }, + [reportID, action, emojiReactions], + ); + + const contextValue = useMemo( + () => ({ + anchor: popoverAnchorRef.current, + report: {...report, reportID: report?.reportID ?? ''}, + reportNameValuePairs, + action, + transactionThreadReport, + checkIfContextMenuActive: toggleContextMenuFromActiveReportAction, + isDisabled: false, + }), + [report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport, reportNameValuePairs], + ); + + const attachmentContextValue = useMemo(() => ({reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [reportID]); + + const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID ?? '-1'}), [report?.reportID]); + + const actionableItemButtons: ActionableItem[] = useMemo(() => { + if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded() && shouldRenderAddPaymentCard()) { + return [ + { + text: 'subscription.cardSection.addCardButton', + key: `${action.reportActionID}-actionableAddPaymentCard-submit`, + onPress: () => { + Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); + }, + isMediumSized: true, + isPrimary: true, + }, + ]; + } + + if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || ReportActionsUtils.getOriginalMessage(action)?.choice !== ('' as JoinWorkspaceResolution))) { + return []; + } + + if (ReportActionsUtils.isActionableTrackExpense(action)) { + const transactionID = ReportActionsUtils.getOriginalMessage(action)?.transactionID; + return [ + { + text: 'actionableMentionTrackExpense.submit', + key: `${action.reportActionID}-actionableMentionTrackExpense-submit`, + onPress: () => { + ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); + }, + isMediumSized: true, + }, + { + text: 'actionableMentionTrackExpense.categorize', + key: `${action.reportActionID}-actionableMentionTrackExpense-categorize`, + onPress: () => { + ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); + }, + isMediumSized: true, + }, + { + text: 'actionableMentionTrackExpense.share', + key: `${action.reportActionID}-actionableMentionTrackExpense-share`, + onPress: () => { + ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); + }, + isMediumSized: true, + }, + { + text: 'actionableMentionTrackExpense.nothing', + key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`, + onPress: () => { + Report.dismissTrackExpenseActionableWhisper(reportID, action); + }, + isMediumSized: true, + }, + ]; + } + + if (ReportActionsUtils.isActionableJoinRequest(action)) { + return [ + { + text: 'actionableMentionJoinWorkspaceOptions.accept', + key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT}`, + onPress: () => Member.acceptJoinRequest(reportID, action), + isPrimary: true, + }, + { + text: 'actionableMentionJoinWorkspaceOptions.decline', + key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`, + onPress: () => Member.declineJoinRequest(reportID, action), + }, + ]; + } + + if (ReportActionsUtils.isActionableReportMentionWhisper(action)) { + return [ + { + text: 'common.yes', + key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE}`, + onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE), + isPrimary: true, + }, + { + text: 'common.no', + key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING}`, + onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING), + }, + ]; + } + + return [ + { + text: 'actionableMentionWhisperOptions.invite', + key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, + onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), + isPrimary: true, + }, + { + text: 'actionableMentionWhisperOptions.nothing', + key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, + onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), + }, + ]; + }, [action, isActionableWhisper, reportID]); + + /** + * Get the content of ReportActionItem + * @param hovered whether the ReportActionItem is hovered + * @param isWhisper whether the report action is a whisper + * @param hasErrors whether the report action has any errors + * @returns child component(s) + */ + const renderItemContent = (hovered = false, isWhisper = false, hasErrors = false): React.JSX.Element => { + let children; + + // Show the MoneyRequestPreview for when expense is present + if ( + ReportActionsUtils.isMoneyRequestAction(action) && + ReportActionsUtils.getOriginalMessage(action) && + // For the pay flow, we only want to show MoneyRequestAction when sending money. When paying, we display a regular system message + (ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || + ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || + ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK) + ) { + // There is no single iouReport for bill splits, so only 1:1 requests require an iouReportID + const iouReportID = ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ? ReportActionsUtils.getOriginalMessage(action)?.IOUReportID?.toString() ?? '-1' : '-1'; + children = ( + + ); + } else if (ReportActionsUtils.isTripPreview(action)) { + children = ( + + ); + } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { + children = ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport) ? ( + ${translate('parentReportAction.deletedReport')}`} /> + ) : ( + setIsPaymentMethodPopoverActive(true)} + onPaymentOptionsHide={() => setIsPaymentMethodPopoverActive(false)} + isWhisper={isWhisper} + /> + ); + } else if (ReportActionsUtils.isTaskAction(action)) { + children = ; + } else if (ReportActionsUtils.isCreatedTaskReportAction(action)) { + children = ( + + + + ); + } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { + const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); + const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; + + const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); + children = ( + + <> + {missingPaymentMethod === 'bankAccount' && ( + + )} + {/** + These are the actionable buttons that appear at the bottom of a Concierge message + for example: Invite a user mentioned but not a member of the room + https://github.com/Expensify/App/issues/32741 + */} + {actionableItemButtons.length > 0 && ( + + )} + + ) : ( + + )} + + + + ); + } + const numberOfThreadReplies = action.childVisibleActionCount ?? 0; + + const shouldDisplayThreadReplies = !hideThreadReplies && ReportUtils.shouldDisplayThreadReplies(action, reportID); + const oldestFourAccountIDs = + action.childOldestFourAccountIDs + ?.split(',') + .map((accountID) => Number(accountID)) + .filter((accountID): accountID is number => typeof accountID === 'number') ?? []; + const draftMessageRightAlign = draftMessage !== undefined ? styles.chatItemReactionsDraftRight : {}; + + return ( + <> + {children} + {Permissions.canUseLinkPreviews() && !isHidden && (action.linkMetadata?.length ?? 0) > 0 && ( + + !isEmptyObject(item))} /> + + )} + {!ReportActionsUtils.isMessageDeleted(action) && ( + + { + if (Session.isAnonymousUser()) { + hideContextMenu(false); + + InteractionManager.runAfterInteractions(() => { + Session.signOutAndRedirectToSignIn(); + }); + } else { + toggleReaction(emoji, ignoreSkinToneOnCompare); + } + }} + setIsEmojiPickerActive={setIsEmojiPickerActive} + /> + + )} + + {shouldDisplayThreadReplies && ( + + + + )} + + ); + }; + + /** + * Get ReportActionItem with a proper wrapper + * @param hovered whether the ReportActionItem is hovered + * @param isWhisper whether the ReportActionItem is a whisper + * @param hasErrors whether the report action has any errors + * @returns report action item + */ + + const renderReportActionItem = (hovered: boolean, isWhisper: boolean, hasErrors: boolean): React.JSX.Element => { + const content = renderItemContent(hovered || isContextMenuActive || isEmojiPickerActive, isWhisper, hasErrors); + + if (draftMessage !== undefined) { + return {content}; + } + + if (!displayAsGroup) { + return ( + item === moderationDecision) && + !ReportActionsUtils.isPendingRemove(action) + } + > + {content} + + ); + } + + return {content}; + }; + + if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { + const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportActionForTransactionThread) + ? ReportActionsUtils.getOriginalMessage(parentReportActionForTransactionThread)?.IOUTransactionID + : '-1'; + + return ( + + ); + } + if (ReportActionsUtils.isChronosOOOListAction(action)) { + return ( + + ); + } + + // For the `pay` IOU action on non-pay expense flow, we don't want to render anything if `isWaitingOnBankAccount` is true + // Otherwise, we will see two system messages informing the payee needs to add a bank account or wallet + if ( + ReportActionsUtils.isMoneyRequestAction(action) && + !!report?.isWaitingOnBankAccount && + ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && + !isSendingMoney + ) { + return null; + } + + // If action is actionable whisper and resolved by user, then we don't want to render anything + if (isActionableWhisper && (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { + return null; + } + + // We currently send whispers to all report participants and hide them in the UI for users that shouldn't see them. + // This is a temporary solution needed for comment-linking. + // The long term solution will leverage end-to-end encryption and only targeted users will be able to decrypt. + if (ReportActionsUtils.isWhisperActionTargetedToOthers(action)) { + return null; + } + + const hasErrors = !isEmptyObject(action.errors); + const whisperedTo = ReportActionsUtils.getWhisperedTo(action); + const isMultipleParticipant = whisperedTo.length > 1; + + const iouReportID = + ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUReportID + ? (ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ?? '').toString() + : '-1'; + const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); + const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; + const whisperedToPersonalDetails = isWhisper + ? (Object.values(personalDetails ?? {}).filter((details) => whisperedTo.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) + : []; + const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedTo); + const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; + + return ( + shouldUseNarrowLayout && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressOut={() => ControlSelection.unblock()} + onSecondaryInteraction={showPopover} + preventDefaultContextMenu={draftMessage === undefined && !hasErrors} + withoutFocusOnSecondaryInteraction + accessibilityLabel={translate('accessibilityHints.chatMessage')} + accessible + > + + {(hovered) => ( + + {shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && } + {shouldDisplayContextMenu && ( + + )} + + { + const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : undefined; + if (transactionID) { + Transaction.clearError(transactionID); + } + ReportActions.clearAllRelatedReportActionErrors(reportID, action); + }} + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + pendingAction={ + draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined) + } + shouldHideOnDelete={!ReportActionsUtils.isThreadParentMessage(action, reportID)} + errors={linkedTransactionRouteError ?? ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)} + errorRowStyles={[styles.ml10, styles.mr2]} + needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(action)} + shouldDisableStrikeThrough + > + {isWhisper && ( + + + + + + {translate('reportActionContextMenu.onlyVisible')} +   + + + + )} + {renderReportActionItem(!!hovered || !!isReportActionLinked, isWhisper, hasErrors)} + + + + )} + + + + + + ); +} + +export type { PureReportActionItemProps }; +export default memo(ReportActionItem, (prevProps, nextProps) => { + const prevParentReportAction = prevProps.parentReportAction; + const nextParentReportAction = nextProps.parentReportAction; + return ( + prevProps.displayAsGroup === nextProps.displayAsGroup && + prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction && + prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker && + lodashIsEqual(prevProps.action, nextProps.action) && + lodashIsEqual(prevProps.report?.pendingFields, nextProps.report?.pendingFields) && + lodashIsEqual(prevProps.report?.isDeletedParentAction, nextProps.report?.isDeletedParentAction) && + lodashIsEqual(prevProps.report?.errorFields, nextProps.report?.errorFields) && + prevProps.report?.statusNum === nextProps.report?.statusNum && + prevProps.report?.stateNum === nextProps.report?.stateNum && + prevProps.report?.parentReportID === nextProps.report?.parentReportID && + prevProps.report?.parentReportActionID === nextProps.report?.parentReportActionID && + // TaskReport's created actions render the TaskView, which updates depending on certain fields in the TaskReport + ReportUtils.isTaskReport(prevProps.report) === ReportUtils.isTaskReport(nextProps.report) && + prevProps.action.actionName === nextProps.action.actionName && + prevProps.report?.reportName === nextProps.report?.reportName && + prevProps.report?.description === nextProps.report?.description && + ReportUtils.isCompletedTaskReport(prevProps.report) === ReportUtils.isCompletedTaskReport(nextProps.report) && + prevProps.report?.managerID === nextProps.report?.managerID && + prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine && + prevProps.report?.total === nextProps.report?.total && + prevProps.report?.nonReimbursableTotal === nextProps.report?.nonReimbursableTotal && + prevProps.report?.policyAvatar === nextProps.report?.policyAvatar && + prevProps.linkedReportActionID === nextProps.linkedReportActionID && + lodashIsEqual(prevProps.report?.fieldList, nextProps.report?.fieldList) && + lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && + lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && + lodashIsEqual(prevParentReportAction, nextParentReportAction) + ); +}); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 559d635f73fe..86d84d48bebe 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -82,63 +82,63 @@ import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import ReportAttachmentsContext from './ReportAttachmentsContext'; +import type { PureReportActionItemProps } from './PureReportActionItem'; +// type ReportActionItemProps = { +// /** Report for this action */ +// report: OnyxEntry; -type ReportActionItemProps = { - /** Report for this action */ - report: OnyxEntry; +// /** The transaction thread report associated with the report for this action, if any */ +// transactionThreadReport?: OnyxEntry; - /** The transaction thread report associated with the report for this action, if any */ - transactionThreadReport?: OnyxEntry; +// /** Array of report actions for the report for this action */ +// // eslint-disable-next-line react/no-unused-prop-types +// reportActions: OnyxTypes.ReportAction[]; - /** Array of report actions for the report for this action */ - // eslint-disable-next-line react/no-unused-prop-types - reportActions: OnyxTypes.ReportAction[]; +// /** Report action belonging to the report's parent */ +// parentReportAction: OnyxEntry; - /** Report action belonging to the report's parent */ - parentReportAction: OnyxEntry; +// /** The transaction thread report's parentReportAction */ +// /** It's used by withOnyx HOC */ +// // eslint-disable-next-line react/no-unused-prop-types +// parentReportActionForTransactionThread?: OnyxEntry; - /** The transaction thread report's parentReportAction */ - /** It's used by withOnyx HOC */ - // eslint-disable-next-line react/no-unused-prop-types - parentReportActionForTransactionThread?: OnyxEntry; +// /** All the data of the action item */ +// action: OnyxTypes.ReportAction; - /** All the data of the action item */ - action: OnyxTypes.ReportAction; +// /** Should the comment have the appearance of being grouped with the previous comment? */ +// displayAsGroup: boolean; - /** Should the comment have the appearance of being grouped with the previous comment? */ - displayAsGroup: boolean; +// /** Is this the most recent IOU Action? */ +// isMostRecentIOUReportAction: boolean; - /** Is this the most recent IOU Action? */ - isMostRecentIOUReportAction: boolean; +// /** Should we display the new marker on top of the comment? */ +// shouldDisplayNewMarker: boolean; - /** Should we display the new marker on top of the comment? */ - shouldDisplayNewMarker: boolean; +// /** Determines if the avatar is displayed as a subscript (positioned lower than normal) */ +// shouldShowSubscriptAvatar?: boolean; - /** Determines if the avatar is displayed as a subscript (positioned lower than normal) */ - shouldShowSubscriptAvatar?: boolean; +// /** Position index of the report action in the overall report FlatList view */ +// index: number; - /** Position index of the report action in the overall report FlatList view */ - index: number; +// /** Flag to show, hide the thread divider line */ +// shouldHideThreadDividerLine?: boolean; - /** Flag to show, hide the thread divider line */ - shouldHideThreadDividerLine?: boolean; +// linkedReportActionID?: string; - linkedReportActionID?: string; +// /** Callback to be called on onPress */ +// onPress?: () => void; - /** Callback to be called on onPress */ - onPress?: () => void; +// /** If this is the first visible report action */ +// isFirstVisibleReportAction: boolean; - /** If this is the first visible report action */ - isFirstVisibleReportAction: boolean; +// /** IF the thread divider line will be used */ +// shouldUseThreadDividerLine?: boolean; - /** IF the thread divider line will be used */ - shouldUseThreadDividerLine?: boolean; +// hideThreadReplies?: boolean; - hideThreadReplies?: boolean; - - /** Whether context menu should be displayed */ - shouldDisplayContextMenu?: boolean; -}; +// /** Whether context menu should be displayed */ +// shouldDisplayContextMenu?: boolean; +// }; function ReportActionItem({ action, @@ -158,7 +158,7 @@ function ReportActionItem({ hideThreadReplies = false, shouldDisplayContextMenu = true, parentReportActionForTransactionThread, -}: ReportActionItemProps) { +}: PureReportActionItemProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const blockedFromConcierge = useBlockedFromConcierge(); From 9489eace5667cf484373d58ca5aa201634da0371 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 14 Nov 2024 16:47:05 +0700 Subject: [PATCH 02/25] use PureComponent in the existing ReportActionItem --- .../home/report/PureReportActionItem.tsx | 74 +- src/pages/home/report/ReportActionItem.tsx | 1872 +++++++++-------- 2 files changed, 1024 insertions(+), 922 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 2c4637582d41..f9cf7ba8bfad 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -81,6 +81,7 @@ import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import ReportAttachmentsContext from './ReportAttachmentsContext'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; type PureReportActionItemProps = { /** Report for this action */ @@ -138,7 +139,23 @@ type PureReportActionItemProps = { /** Whether context menu should be displayed */ shouldDisplayContextMenu?: boolean; - draftMessage? : OnyxEntry + + + draftMessage? : string + + iouReport?: OnyxTypes.Report; + + emojiReactions?: OnyxTypes.ReportActionReactions; + + userWallet?: OnyxTypes.UserWallet; + + linkedTransactionRouteError?: Errors; + + reportNameValuePairs?: OnyxTypes.ReportNameValuePairs; + + isUserValidated?: boolean; + + parentReport?: OnyxTypes.Report; }; function PureReportActionItem({ @@ -159,6 +176,15 @@ function PureReportActionItem({ hideThreadReplies = false, shouldDisplayContextMenu = true, parentReportActionForTransactionThread, + + draftMessage, + iouReport, + emojiReactions, + userWallet, + linkedTransactionRouteError, + reportNameValuePairs, + isUserValidated, + parentReport, }: PureReportActionItemProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -166,23 +192,23 @@ function PureReportActionItem({ const reportID = report?.reportID ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); - const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, { - selector: (draftMessagesForReport) => { - const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID]; - return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message; - }, - }); - const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action) ?? -1}`); - const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`); - const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); - const [linkedTransactionRouteError] = useOnyx( - `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, - {selector: (transaction) => transaction?.errorFields?.route ?? null}, - ); + // const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, { + // selector: (draftMessagesForReport) => { + // const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID]; + // return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message; + // }, + // }); + // const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action) ?? -1}`); + // const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`); + // const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); + // const [linkedTransactionRouteError] = useOnyx( + // `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, + // {selector: (transaction) => transaction?.errorFields?.route ?? null}, + // ); const theme = useTheme(); const styles = useThemeStyles(); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. - const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); + // const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); const StyleUtils = useStyleUtils(); const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); @@ -197,10 +223,10 @@ function PureReportActionItem({ const popoverAnchorRef = useRef>(null); const downloadedPreviews = useRef([]); const prevDraftMessage = usePrevious(draftMessage); - const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); + // const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); + // const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; const reportScrollManager = useReportScrollManager(); const isActionableWhisper = @@ -1038,7 +1064,8 @@ function PureReportActionItem({ } export type { PureReportActionItemProps }; -export default memo(ReportActionItem, (prevProps, nextProps) => { +// export default PureReportActionItem; +export default memo(PureReportActionItem, (prevProps, nextProps) => { const prevParentReportAction = prevProps.parentReportAction; const nextParentReportAction = nextProps.parentReportAction; return ( @@ -1068,6 +1095,15 @@ export default memo(ReportActionItem, (prevProps, nextProps) => { lodashIsEqual(prevProps.report?.fieldList, nextProps.report?.fieldList) && lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && - lodashIsEqual(prevParentReportAction, nextParentReportAction) + lodashIsEqual(prevParentReportAction, nextParentReportAction) && + + prevProps.draftMessage === nextProps.draftMessage && + prevProps.iouReport?.reportID === nextProps.iouReport?.reportID && + prevProps.emojiReactions === nextProps.emojiReactions && + prevProps.userWallet === nextProps.userWallet && + prevProps.linkedTransactionRouteError === nextProps.linkedTransactionRouteError && + prevProps.isUserValidated === nextProps.isUserValidated && + prevProps.parentReport?.reportID === nextProps.parentReport?.reportID + ); }); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 86d84d48bebe..a9ef46e53f84 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -83,6 +83,7 @@ import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import ReportAttachmentsContext from './ReportAttachmentsContext'; import type { PureReportActionItemProps } from './PureReportActionItem'; +import PureReportActionItem from './PureReportActionItem'; // type ReportActionItemProps = { // /** Report for this action */ // report: OnyxEntry; @@ -143,25 +144,24 @@ import type { PureReportActionItemProps } from './PureReportActionItem'; function ReportActionItem({ action, report, - transactionThreadReport, - linkedReportActionID, - displayAsGroup, - index, - isMostRecentIOUReportAction, - parentReportAction, - shouldDisplayNewMarker, - shouldHideThreadDividerLine = false, - shouldShowSubscriptAvatar = false, - onPress = undefined, - isFirstVisibleReportAction = false, - shouldUseThreadDividerLine = false, - hideThreadReplies = false, - shouldDisplayContextMenu = true, - parentReportActionForTransactionThread, + ...props + // transactionThreadReport, + // linkedReportActionID, + // displayAsGroup, + // index, + // isMostRecentIOUReportAction, + // parentReportAction, + // shouldDisplayNewMarker, + // shouldHideThreadDividerLine = false, + // shouldShowSubscriptAvatar = false, + // onPress = undefined, + // isFirstVisibleReportAction = false, + // shouldUseThreadDividerLine = false, + // hideThreadReplies = false, + // shouldDisplayContextMenu = true, + // parentReportActionForTransactionThread, + // reportActions, }: PureReportActionItemProps) { - const {translate} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); - const blockedFromConcierge = useBlockedFromConcierge(); const reportID = report?.reportID ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); @@ -176,896 +176,962 @@ function ReportActionItem({ const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); const [linkedTransactionRouteError] = useOnyx( `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, - {selector: (transaction) => transaction?.errorFields?.route ?? null}, + { selector: (transaction) => transaction?.errorFields?.route ?? null }, ); - const theme = useTheme(); - const styles = useThemeStyles(); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); - const StyleUtils = useStyleUtils(); - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; - const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); - const [isEmojiPickerActive, setIsEmojiPickerActive] = useState(); - const [isPaymentMethodPopoverActive, setIsPaymentMethodPopoverActive] = useState(); - - const [isHidden, setIsHidden] = useState(false); - const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); - const reactionListRef = useContext(ReactionListContext); - const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); - const textInputRef = useRef(null); - const popoverAnchorRef = useRef>(null); - const downloadedPreviews = useRef([]); - const prevDraftMessage = usePrevious(draftMessage); - const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); + + const [isUserValidated] = useOnyx(ONYXKEYS.USER, { selector: (user) => !!user?.validated }); // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); - const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; - const reportScrollManager = useReportScrollManager(); - const isActionableWhisper = - ReportActionsUtils.isActionableMentionWhisper(action) || ReportActionsUtils.isActionableTrackExpense(action) || ReportActionsUtils.isActionableReportMentionWhisper(action); - const originalMessage = ReportActionsUtils.getOriginalMessage(action); - - const highlightedBackgroundColorIfNeeded = useMemo( - () => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.messageHighlightBG) : {}), - [StyleUtils, isReportActionLinked, theme.messageHighlightBG], - ); - - const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action); - const isOriginalMessageAnObject = originalMessage && typeof originalMessage === 'object'; - const hasResolutionInOriginalMessage = isOriginalMessageAnObject && 'resolution' in originalMessage; - const prevActionResolution = usePrevious(isActionableWhisper && hasResolutionInOriginalMessage ? originalMessage?.resolution : null); - - // IOUDetails only exists when we are sending money - const isSendingMoney = - ReportActionsUtils.isMoneyRequestAction(action) && - ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && - ReportActionsUtils.getOriginalMessage(action)?.IOUDetails; - - const updateHiddenState = useCallback( - (isHiddenValue: boolean) => { - setIsHidden(isHiddenValue); - const message = Array.isArray(action.message) ? action.message?.at(-1) : action.message; - const isAttachment = ReportUtils.isReportMessageAttachment(message); - if (!isAttachment) { - return; - } - updateHiddenAttachments(action.reportActionID, isHiddenValue); - }, - [action.reportActionID, action.message, updateHiddenAttachments], - ); - - useEffect( - () => () => { - // ReportActionContextMenu, EmojiPicker and PopoverReactionList are global components, - // we should also hide them when the current component is destroyed - if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { - ReportActionContextMenu.hideContextMenu(); - ReportActionContextMenu.hideDeleteModal(); - } - if (EmojiPickerAction.isActive(action.reportActionID)) { - EmojiPickerAction.hideEmojiPicker(true); - } - if (reactionListRef?.current?.isActiveReportAction(action.reportActionID)) { - reactionListRef?.current?.hideReactionList(); - } - }, - [action.reportActionID, reactionListRef], - ); - - useEffect(() => { - // We need to hide EmojiPicker when this is a deleted parent action - if (!isDeletedParentAction || !EmojiPickerAction.isActive(action.reportActionID)) { - return; - } - - EmojiPickerAction.hideEmojiPicker(true); - }, [isDeletedParentAction, action.reportActionID]); - - useEffect(() => { - if (prevDraftMessage !== undefined || draftMessage === undefined) { - return; - } - - focusComposerWithDelay(textInputRef.current)(true); - }, [prevDraftMessage, draftMessage]); - - useEffect(() => { - if (!Permissions.canUseLinkPreviews()) { - return; - } - - const urls = ReportActionsUtils.extractLinksFromMessageHtml(action); - if (lodashIsEqual(downloadedPreviews.current, urls) || action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { - return; - } - - downloadedPreviews.current = urls; - Report.expandURLPreview(reportID, action.reportActionID); - }, [action, reportID]); - - useEffect(() => { - if (draftMessage === undefined || !ReportActionsUtils.isDeletedAction(action)) { - return; - } - Report.deleteReportActionDraft(reportID, action); - }, [draftMessage, action, reportID]); - - // Hide the message if it is being moderated for a higher offense, or is hidden by a moderator - // Removed messages should not be shown anyway and should not need this flow - const latestDecision = ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision ?? ''; - useEffect(() => { - if (action.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT) { - return; - } - - // Hide reveal message button and show the message if latestDecision is changed to empty - if (!latestDecision) { - setModerationDecision(CONST.MODERATION.MODERATOR_DECISION_APPROVED); - setIsHidden(false); - return; - } - - setModerationDecision(latestDecision); - if ( - ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === latestDecision) && - !ReportActionsUtils.isPendingRemove(action) - ) { - setIsHidden(true); - return; - } - setIsHidden(false); - }, [latestDecision, action]); - - const toggleContextMenuFromActiveReportAction = useCallback(() => { - setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); - }, [action.reportActionID]); - - const isArchivedRoom = ReportUtils.isArchivedRoomWithID(originalReportID); - const disabledActions = useMemo(() => (!ReportUtils.canWriteInReport(report) ? RestrictedReadOnlyContextMenuActions : []), [report]); - const isChronosReport = ReportUtils.chatIncludesChronosWithID(originalReportID); - /** - * Show the ReportActionContextMenu modal popover. - * - * @param [event] - A press event. - */ - const showPopover = useCallback( - (event: GestureResponderEvent | MouseEvent) => { - // Block menu on the message being Edited or if the report action item has errors - if (draftMessage !== undefined || !isEmptyObject(action.errors) || !shouldDisplayContextMenu) { - return; - } - - setIsContextMenuActive(true); - const selection = SelectionScraper.getCurrentSelection(); - ReportActionContextMenu.showContextMenu( - CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, - event, - selection, - popoverAnchorRef.current, - reportID, - action.reportActionID, - originalReportID, - draftMessage ?? '', - () => setIsContextMenuActive(true), - toggleContextMenuFromActiveReportAction, - isArchivedRoom, - isChronosReport, - false, - false, - disabledActions, - false, - setIsEmojiPickerActive as () => void, - ); - }, - [draftMessage, action, reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport], - ); - - // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. - // This fixes an issue where InvertedFlatList fails to auto scroll down and results in an empty space at the bottom of the chat in IOS. - useEffect(() => { - if (index !== 0 || !isActionableWhisper) { - return; - } - - if (prevActionResolution !== (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { - reportScrollManager.scrollToIndex(index); - } - }, [index, originalMessage, prevActionResolution, reportScrollManager, isActionableWhisper, hasResolutionInOriginalMessage]); - - const toggleReaction = useCallback( - (emoji: Emoji, ignoreSkinToneOnCompare?: boolean) => { - Report.toggleEmojiReaction(reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare); - }, - [reportID, action, emojiReactions], - ); - - const contextValue = useMemo( - () => ({ - anchor: popoverAnchorRef.current, - report: {...report, reportID: report?.reportID ?? ''}, - reportNameValuePairs, - action, - transactionThreadReport, - checkIfContextMenuActive: toggleContextMenuFromActiveReportAction, - isDisabled: false, - }), - [report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport, reportNameValuePairs], - ); - - const attachmentContextValue = useMemo(() => ({reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [reportID]); - - const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID ?? '-1'}), [report?.reportID]); - - const actionableItemButtons: ActionableItem[] = useMemo(() => { - if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded() && shouldRenderAddPaymentCard()) { - return [ - { - text: 'subscription.cardSection.addCardButton', - key: `${action.reportActionID}-actionableAddPaymentCard-submit`, - onPress: () => { - Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); - }, - isMediumSized: true, - isPrimary: true, - }, - ]; - } - - if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || ReportActionsUtils.getOriginalMessage(action)?.choice !== ('' as JoinWorkspaceResolution))) { - return []; - } - - if (ReportActionsUtils.isActionableTrackExpense(action)) { - const transactionID = ReportActionsUtils.getOriginalMessage(action)?.transactionID; - return [ - { - text: 'actionableMentionTrackExpense.submit', - key: `${action.reportActionID}-actionableMentionTrackExpense-submit`, - onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); - }, - isMediumSized: true, - }, - { - text: 'actionableMentionTrackExpense.categorize', - key: `${action.reportActionID}-actionableMentionTrackExpense-categorize`, - onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); - }, - isMediumSized: true, - }, - { - text: 'actionableMentionTrackExpense.share', - key: `${action.reportActionID}-actionableMentionTrackExpense-share`, - onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); - }, - isMediumSized: true, - }, - { - text: 'actionableMentionTrackExpense.nothing', - key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`, - onPress: () => { - Report.dismissTrackExpenseActionableWhisper(reportID, action); - }, - isMediumSized: true, - }, - ]; - } - - if (ReportActionsUtils.isActionableJoinRequest(action)) { - return [ - { - text: 'actionableMentionJoinWorkspaceOptions.accept', - key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT}`, - onPress: () => Member.acceptJoinRequest(reportID, action), - isPrimary: true, - }, - { - text: 'actionableMentionJoinWorkspaceOptions.decline', - key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`, - onPress: () => Member.declineJoinRequest(reportID, action), - }, - ]; - } - - if (ReportActionsUtils.isActionableReportMentionWhisper(action)) { - return [ - { - text: 'common.yes', - key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE}`, - onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE), - isPrimary: true, - }, - { - text: 'common.no', - key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING}`, - onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING), - }, - ]; - } - - return [ - { - text: 'actionableMentionWhisperOptions.invite', - key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, - onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), - isPrimary: true, - }, - { - text: 'actionableMentionWhisperOptions.nothing', - key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, - onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), - }, - ]; - }, [action, isActionableWhisper, reportID]); - - /** - * Get the content of ReportActionItem - * @param hovered whether the ReportActionItem is hovered - * @param isWhisper whether the report action is a whisper - * @param hasErrors whether the report action has any errors - * @returns child component(s) - */ - const renderItemContent = (hovered = false, isWhisper = false, hasErrors = false): React.JSX.Element => { - let children; - - // Show the MoneyRequestPreview for when expense is present - if ( - ReportActionsUtils.isMoneyRequestAction(action) && - ReportActionsUtils.getOriginalMessage(action) && - // For the pay flow, we only want to show MoneyRequestAction when sending money. When paying, we display a regular system message - (ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || - ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || - ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK) - ) { - // There is no single iouReport for bill splits, so only 1:1 requests require an iouReportID - const iouReportID = ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ? ReportActionsUtils.getOriginalMessage(action)?.IOUReportID?.toString() ?? '-1' : '-1'; - children = ( - - ); - } else if (ReportActionsUtils.isTripPreview(action)) { - children = ( - - ); - } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { - children = ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport) ? ( - ${translate('parentReportAction.deletedReport')}`} /> - ) : ( - setIsPaymentMethodPopoverActive(true)} - onPaymentOptionsHide={() => setIsPaymentMethodPopoverActive(false)} - isWhisper={isWhisper} - /> - ); - } else if (ReportActionsUtils.isTaskAction(action)) { - children = ; - } else if (ReportActionsUtils.isCreatedTaskReportAction(action)) { - children = ( - - - - ); - } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { - const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); - const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; - - const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); - children = ( - - <> - {missingPaymentMethod === 'bankAccount' && ( - - )} - {/** - These are the actionable buttons that appear at the bottom of a Concierge message - for example: Invite a user mentioned but not a member of the room - https://github.com/Expensify/App/issues/32741 - */} - {actionableItemButtons.length > 0 && ( - - )} - - ) : ( - - )} - - - - ); - } - const numberOfThreadReplies = action.childVisibleActionCount ?? 0; - - const shouldDisplayThreadReplies = !hideThreadReplies && ReportUtils.shouldDisplayThreadReplies(action, reportID); - const oldestFourAccountIDs = - action.childOldestFourAccountIDs - ?.split(',') - .map((accountID) => Number(accountID)) - .filter((accountID): accountID is number => typeof accountID === 'number') ?? []; - const draftMessageRightAlign = draftMessage !== undefined ? styles.chatItemReactionsDraftRight : {}; - - return ( - <> - {children} - {Permissions.canUseLinkPreviews() && !isHidden && (action.linkMetadata?.length ?? 0) > 0 && ( - - !isEmptyObject(item))} /> - - )} - {!ReportActionsUtils.isMessageDeleted(action) && ( - - { - if (Session.isAnonymousUser()) { - hideContextMenu(false); - - InteractionManager.runAfterInteractions(() => { - Session.signOutAndRedirectToSignIn(); - }); - } else { - toggleReaction(emoji, ignoreSkinToneOnCompare); - } - }} - setIsEmojiPickerActive={setIsEmojiPickerActive} - /> - - )} - - {shouldDisplayThreadReplies && ( - - - - )} - - ); - }; - - /** - * Get ReportActionItem with a proper wrapper - * @param hovered whether the ReportActionItem is hovered - * @param isWhisper whether the ReportActionItem is a whisper - * @param hasErrors whether the report action has any errors - * @returns report action item - */ - - const renderReportActionItem = (hovered: boolean, isWhisper: boolean, hasErrors: boolean): React.JSX.Element => { - const content = renderItemContent(hovered || isContextMenuActive || isEmojiPickerActive, isWhisper, hasErrors); - - if (draftMessage !== undefined) { - return {content}; - } - - if (!displayAsGroup) { - return ( - item === moderationDecision) && - !ReportActionsUtils.isPendingRemove(action) - } - > - {content} - - ); - } - - return {content}; - }; - - if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { - const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportActionForTransactionThread) - ? ReportActionsUtils.getOriginalMessage(parentReportActionForTransactionThread)?.IOUTransactionID - : '-1'; - - return ( - - ); - } - if (ReportActionsUtils.isChronosOOOListAction(action)) { - return ( - - ); - } - - // For the `pay` IOU action on non-pay expense flow, we don't want to render anything if `isWaitingOnBankAccount` is true - // Otherwise, we will see two system messages informing the payee needs to add a bank account or wallet - if ( - ReportActionsUtils.isMoneyRequestAction(action) && - !!report?.isWaitingOnBankAccount && - ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && - !isSendingMoney - ) { - return null; - } - - // If action is actionable whisper and resolved by user, then we don't want to render anything - if (isActionableWhisper && (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { - return null; - } - - // We currently send whispers to all report participants and hide them in the UI for users that shouldn't see them. - // This is a temporary solution needed for comment-linking. - // The long term solution will leverage end-to-end encryption and only targeted users will be able to decrypt. - if (ReportActionsUtils.isWhisperActionTargetedToOthers(action)) { - return null; - } - - const hasErrors = !isEmptyObject(action.errors); - const whisperedTo = ReportActionsUtils.getWhisperedTo(action); - const isMultipleParticipant = whisperedTo.length > 1; - - const iouReportID = - ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUReportID - ? (ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ?? '').toString() - : '-1'; - const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); - const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; - const whisperedToPersonalDetails = isWhisper - ? (Object.values(personalDetails ?? {}).filter((details) => whisperedTo.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) - : []; - const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedTo); - const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; - - return ( - shouldUseNarrowLayout && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} - onPressOut={() => ControlSelection.unblock()} - onSecondaryInteraction={showPopover} - preventDefaultContextMenu={draftMessage === undefined && !hasErrors} - withoutFocusOnSecondaryInteraction - accessibilityLabel={translate('accessibilityHints.chatMessage')} - accessible - > - - {(hovered) => ( - - {shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && } - {shouldDisplayContextMenu && ( - - )} - - { - const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : undefined; - if (transactionID) { - Transaction.clearError(transactionID); - } - ReportActions.clearAllRelatedReportActionErrors(reportID, action); - }} - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - pendingAction={ - draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined) - } - shouldHideOnDelete={!ReportActionsUtils.isThreadParentMessage(action, reportID)} - errors={linkedTransactionRouteError ?? ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)} - errorRowStyles={[styles.ml10, styles.mr2]} - needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(action)} - shouldDisableStrikeThrough - > - {isWhisper && ( - - - - - - {translate('reportActionContextMenu.onlyVisible')} -   - - - - )} - {renderReportActionItem(!!hovered || !!isReportActionLinked, isWhisper, hasErrors)} - - - - )} - - - - - - ); + + + return + + + + // const {translate} = useLocalize(); + // const {shouldUseNarrowLayout} = useResponsiveLayout(); + // const blockedFromConcierge = useBlockedFromConcierge(); + // const reportID = report?.reportID ?? ''; + // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + // const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); + // const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, { + // selector: (draftMessagesForReport) => { + // const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID]; + // return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message; + // }, + // }); + // const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action) ?? -1}`); + // const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`); + // const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); + // const [linkedTransactionRouteError] = useOnyx( + // `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, + // {selector: (transaction) => transaction?.errorFields?.route ?? null}, + // ); + // const theme = useTheme(); + // const styles = useThemeStyles(); + // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. + // const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); + // const StyleUtils = useStyleUtils(); + // const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; + // const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); + // const [isEmojiPickerActive, setIsEmojiPickerActive] = useState(); + // const [isPaymentMethodPopoverActive, setIsPaymentMethodPopoverActive] = useState(); + + // const [isHidden, setIsHidden] = useState(false); + // const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); + // const reactionListRef = useContext(ReactionListContext); + // const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); + // const textInputRef = useRef(null); + // const popoverAnchorRef = useRef>(null); + // const downloadedPreviews = useRef([]); + // const prevDraftMessage = usePrevious(draftMessage); + // const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); + // // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. + // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + // const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); + // const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; + // const reportScrollManager = useReportScrollManager(); + // const isActionableWhisper = + // ReportActionsUtils.isActionableMentionWhisper(action) || ReportActionsUtils.isActionableTrackExpense(action) || ReportActionsUtils.isActionableReportMentionWhisper(action); + // const originalMessage = ReportActionsUtils.getOriginalMessage(action); + + // const highlightedBackgroundColorIfNeeded = useMemo( + // () => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.messageHighlightBG) : {}), + // [StyleUtils, isReportActionLinked, theme.messageHighlightBG], + // ); + + // const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action); + // const isOriginalMessageAnObject = originalMessage && typeof originalMessage === 'object'; + // const hasResolutionInOriginalMessage = isOriginalMessageAnObject && 'resolution' in originalMessage; + // const prevActionResolution = usePrevious(isActionableWhisper && hasResolutionInOriginalMessage ? originalMessage?.resolution : null); + + // // IOUDetails only exists when we are sending money + // const isSendingMoney = + // ReportActionsUtils.isMoneyRequestAction(action) && + // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && + // ReportActionsUtils.getOriginalMessage(action)?.IOUDetails; + + // const updateHiddenState = useCallback( + // (isHiddenValue: boolean) => { + // setIsHidden(isHiddenValue); + // const message = Array.isArray(action.message) ? action.message?.at(-1) : action.message; + // const isAttachment = ReportUtils.isReportMessageAttachment(message); + // if (!isAttachment) { + // return; + // } + // updateHiddenAttachments(action.reportActionID, isHiddenValue); + // }, + // [action.reportActionID, action.message, updateHiddenAttachments], + // ); + + // useEffect( + // () => () => { + // // ReportActionContextMenu, EmojiPicker and PopoverReactionList are global components, + // // we should also hide them when the current component is destroyed + // if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { + // ReportActionContextMenu.hideContextMenu(); + // ReportActionContextMenu.hideDeleteModal(); + // } + // if (EmojiPickerAction.isActive(action.reportActionID)) { + // EmojiPickerAction.hideEmojiPicker(true); + // } + // if (reactionListRef?.current?.isActiveReportAction(action.reportActionID)) { + // reactionListRef?.current?.hideReactionList(); + // } + // }, + // [action.reportActionID, reactionListRef], + // ); + + // useEffect(() => { + // // We need to hide EmojiPicker when this is a deleted parent action + // if (!isDeletedParentAction || !EmojiPickerAction.isActive(action.reportActionID)) { + // return; + // } + + // EmojiPickerAction.hideEmojiPicker(true); + // }, [isDeletedParentAction, action.reportActionID]); + + // useEffect(() => { + // if (prevDraftMessage !== undefined || draftMessage === undefined) { + // return; + // } + + // focusComposerWithDelay(textInputRef.current)(true); + // }, [prevDraftMessage, draftMessage]); + + // useEffect(() => { + // if (!Permissions.canUseLinkPreviews()) { + // return; + // } + + // const urls = ReportActionsUtils.extractLinksFromMessageHtml(action); + // if (lodashIsEqual(downloadedPreviews.current, urls) || action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { + // return; + // } + + // downloadedPreviews.current = urls; + // Report.expandURLPreview(reportID, action.reportActionID); + // }, [action, reportID]); + + // useEffect(() => { + // if (draftMessage === undefined || !ReportActionsUtils.isDeletedAction(action)) { + // return; + // } + // Report.deleteReportActionDraft(reportID, action); + // }, [draftMessage, action, reportID]); + + // // Hide the message if it is being moderated for a higher offense, or is hidden by a moderator + // // Removed messages should not be shown anyway and should not need this flow + // const latestDecision = ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision ?? ''; + // useEffect(() => { + // if (action.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT) { + // return; + // } + + // // Hide reveal message button and show the message if latestDecision is changed to empty + // if (!latestDecision) { + // setModerationDecision(CONST.MODERATION.MODERATOR_DECISION_APPROVED); + // setIsHidden(false); + // return; + // } + + // setModerationDecision(latestDecision); + // if ( + // ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === latestDecision) && + // !ReportActionsUtils.isPendingRemove(action) + // ) { + // setIsHidden(true); + // return; + // } + // setIsHidden(false); + // }, [latestDecision, action]); + + // const toggleContextMenuFromActiveReportAction = useCallback(() => { + // setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); + // }, [action.reportActionID]); + + // const isArchivedRoom = ReportUtils.isArchivedRoomWithID(originalReportID); + // const disabledActions = useMemo(() => (!ReportUtils.canWriteInReport(report) ? RestrictedReadOnlyContextMenuActions : []), [report]); + // const isChronosReport = ReportUtils.chatIncludesChronosWithID(originalReportID); + // /** + // * Show the ReportActionContextMenu modal popover. + // * + // * @param [event] - A press event. + // */ + // const showPopover = useCallback( + // (event: GestureResponderEvent | MouseEvent) => { + // // Block menu on the message being Edited or if the report action item has errors + // if (draftMessage !== undefined || !isEmptyObject(action.errors) || !shouldDisplayContextMenu) { + // return; + // } + + // setIsContextMenuActive(true); + // const selection = SelectionScraper.getCurrentSelection(); + // ReportActionContextMenu.showContextMenu( + // CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, + // event, + // selection, + // popoverAnchorRef.current, + // reportID, + // action.reportActionID, + // originalReportID, + // draftMessage ?? '', + // () => setIsContextMenuActive(true), + // toggleContextMenuFromActiveReportAction, + // isArchivedRoom, + // isChronosReport, + // false, + // false, + // disabledActions, + // false, + // setIsEmojiPickerActive as () => void, + // ); + // }, + // [draftMessage, action, reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport], + // ); + + // // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. + // // This fixes an issue where InvertedFlatList fails to auto scroll down and results in an empty space at the bottom of the chat in IOS. + // useEffect(() => { + // if (index !== 0 || !isActionableWhisper) { + // return; + // } + + // if (prevActionResolution !== (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { + // reportScrollManager.scrollToIndex(index); + // } + // }, [index, originalMessage, prevActionResolution, reportScrollManager, isActionableWhisper, hasResolutionInOriginalMessage]); + + // const toggleReaction = useCallback( + // (emoji: Emoji, ignoreSkinToneOnCompare?: boolean) => { + // Report.toggleEmojiReaction(reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare); + // }, + // [reportID, action, emojiReactions], + // ); + + // const contextValue = useMemo( + // () => ({ + // anchor: popoverAnchorRef.current, + // report: {...report, reportID: report?.reportID ?? ''}, + // reportNameValuePairs, + // action, + // transactionThreadReport, + // checkIfContextMenuActive: toggleContextMenuFromActiveReportAction, + // isDisabled: false, + // }), + // [report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport, reportNameValuePairs], + // ); + + // const attachmentContextValue = useMemo(() => ({reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [reportID]); + + // const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID ?? '-1'}), [report?.reportID]); + + // const actionableItemButtons: ActionableItem[] = useMemo(() => { + // if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded() && shouldRenderAddPaymentCard()) { + // return [ + // { + // text: 'subscription.cardSection.addCardButton', + // key: `${action.reportActionID}-actionableAddPaymentCard-submit`, + // onPress: () => { + // Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); + // }, + // isMediumSized: true, + // isPrimary: true, + // }, + // ]; + // } + + // if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || ReportActionsUtils.getOriginalMessage(action)?.choice !== ('' as JoinWorkspaceResolution))) { + // return []; + // } + + // if (ReportActionsUtils.isActionableTrackExpense(action)) { + // const transactionID = ReportActionsUtils.getOriginalMessage(action)?.transactionID; + // return [ + // { + // text: 'actionableMentionTrackExpense.submit', + // key: `${action.reportActionID}-actionableMentionTrackExpense-submit`, + // onPress: () => { + // ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); + // }, + // isMediumSized: true, + // }, + // { + // text: 'actionableMentionTrackExpense.categorize', + // key: `${action.reportActionID}-actionableMentionTrackExpense-categorize`, + // onPress: () => { + // ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); + // }, + // isMediumSized: true, + // }, + // { + // text: 'actionableMentionTrackExpense.share', + // key: `${action.reportActionID}-actionableMentionTrackExpense-share`, + // onPress: () => { + // ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); + // }, + // isMediumSized: true, + // }, + // { + // text: 'actionableMentionTrackExpense.nothing', + // key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`, + // onPress: () => { + // Report.dismissTrackExpenseActionableWhisper(reportID, action); + // }, + // isMediumSized: true, + // }, + // ]; + // } + + // if (ReportActionsUtils.isActionableJoinRequest(action)) { + // return [ + // { + // text: 'actionableMentionJoinWorkspaceOptions.accept', + // key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT}`, + // onPress: () => Member.acceptJoinRequest(reportID, action), + // isPrimary: true, + // }, + // { + // text: 'actionableMentionJoinWorkspaceOptions.decline', + // key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`, + // onPress: () => Member.declineJoinRequest(reportID, action), + // }, + // ]; + // } + + // if (ReportActionsUtils.isActionableReportMentionWhisper(action)) { + // return [ + // { + // text: 'common.yes', + // key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE}`, + // onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE), + // isPrimary: true, + // }, + // { + // text: 'common.no', + // key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING}`, + // onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING), + // }, + // ]; + // } + + // return [ + // { + // text: 'actionableMentionWhisperOptions.invite', + // key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, + // onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), + // isPrimary: true, + // }, + // { + // text: 'actionableMentionWhisperOptions.nothing', + // key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, + // onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), + // }, + // ]; + // }, [action, isActionableWhisper, reportID]); + + // /** + // * Get the content of ReportActionItem + // * @param hovered whether the ReportActionItem is hovered + // * @param isWhisper whether the report action is a whisper + // * @param hasErrors whether the report action has any errors + // * @returns child component(s) + // */ + // const renderItemContent = (hovered = false, isWhisper = false, hasErrors = false): React.JSX.Element => { + // let children; + + // // Show the MoneyRequestPreview for when expense is present + // if ( + // ReportActionsUtils.isMoneyRequestAction(action) && + // ReportActionsUtils.getOriginalMessage(action) && + // // For the pay flow, we only want to show MoneyRequestAction when sending money. When paying, we display a regular system message + // (ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || + // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || + // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK) + // ) { + // // There is no single iouReport for bill splits, so only 1:1 requests require an iouReportID + // const iouReportID = ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ? ReportActionsUtils.getOriginalMessage(action)?.IOUReportID?.toString() ?? '-1' : '-1'; + // children = ( + // + // ); + // } else if (ReportActionsUtils.isTripPreview(action)) { + // children = ( + // + // ); + // } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { + // children = ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport) ? ( + // ${translate('parentReportAction.deletedReport')}`} /> + // ) : ( + // setIsPaymentMethodPopoverActive(true)} + // onPaymentOptionsHide={() => setIsPaymentMethodPopoverActive(false)} + // isWhisper={isWhisper} + // /> + // ); + // } else if (ReportActionsUtils.isTaskAction(action)) { + // children = ; + // } else if (ReportActionsUtils.isCreatedTaskReportAction(action)) { + // children = ( + // + // + // + // ); + // } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { + // const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; + // const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); + // const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; + + // const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); + // children = ( + // + // <> + // {missingPaymentMethod === 'bankAccount' && ( + // + // )} + // {/** + // These are the actionable buttons that appear at the bottom of a Concierge message + // for example: Invite a user mentioned but not a member of the room + // https://github.com/Expensify/App/issues/32741 + // */} + // {actionableItemButtons.length > 0 && ( + // + // )} + // + // ) : ( + // + // )} + // + // + // + // ); + // } + // const numberOfThreadReplies = action.childVisibleActionCount ?? 0; + + // const shouldDisplayThreadReplies = !hideThreadReplies && ReportUtils.shouldDisplayThreadReplies(action, reportID); + // const oldestFourAccountIDs = + // action.childOldestFourAccountIDs + // ?.split(',') + // .map((accountID) => Number(accountID)) + // .filter((accountID): accountID is number => typeof accountID === 'number') ?? []; + // const draftMessageRightAlign = draftMessage !== undefined ? styles.chatItemReactionsDraftRight : {}; + + // return ( + // <> + // {children} + // {Permissions.canUseLinkPreviews() && !isHidden && (action.linkMetadata?.length ?? 0) > 0 && ( + // + // !isEmptyObject(item))} /> + // + // )} + // {!ReportActionsUtils.isMessageDeleted(action) && ( + // + // { + // if (Session.isAnonymousUser()) { + // hideContextMenu(false); + + // InteractionManager.runAfterInteractions(() => { + // Session.signOutAndRedirectToSignIn(); + // }); + // } else { + // toggleReaction(emoji, ignoreSkinToneOnCompare); + // } + // }} + // setIsEmojiPickerActive={setIsEmojiPickerActive} + // /> + // + // )} + + // {shouldDisplayThreadReplies && ( + // + // + // + // )} + // + // ); + // }; + + // /** + // * Get ReportActionItem with a proper wrapper + // * @param hovered whether the ReportActionItem is hovered + // * @param isWhisper whether the ReportActionItem is a whisper + // * @param hasErrors whether the report action has any errors + // * @returns report action item + // */ + + // const renderReportActionItem = (hovered: boolean, isWhisper: boolean, hasErrors: boolean): React.JSX.Element => { + // const content = renderItemContent(hovered || isContextMenuActive || isEmojiPickerActive, isWhisper, hasErrors); + + // if (draftMessage !== undefined) { + // return {content}; + // } + + // if (!displayAsGroup) { + // return ( + // item === moderationDecision) && + // !ReportActionsUtils.isPendingRemove(action) + // } + // > + // {content} + // + // ); + // } + + // return {content}; + // }; + + // if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { + // const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportActionForTransactionThread) + // ? ReportActionsUtils.getOriginalMessage(parentReportActionForTransactionThread)?.IOUTransactionID + // : '-1'; + + // return ( + // + // ); + // } + // if (ReportActionsUtils.isChronosOOOListAction(action)) { + // return ( + // + // ); + // } + + // // For the `pay` IOU action on non-pay expense flow, we don't want to render anything if `isWaitingOnBankAccount` is true + // // Otherwise, we will see two system messages informing the payee needs to add a bank account or wallet + // if ( + // ReportActionsUtils.isMoneyRequestAction(action) && + // !!report?.isWaitingOnBankAccount && + // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && + // !isSendingMoney + // ) { + // return null; + // } + + // // If action is actionable whisper and resolved by user, then we don't want to render anything + // if (isActionableWhisper && (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { + // return null; + // } + + // // We currently send whispers to all report participants and hide them in the UI for users that shouldn't see them. + // // This is a temporary solution needed for comment-linking. + // // The long term solution will leverage end-to-end encryption and only targeted users will be able to decrypt. + // if (ReportActionsUtils.isWhisperActionTargetedToOthers(action)) { + // return null; + // } + + // const hasErrors = !isEmptyObject(action.errors); + // const whisperedTo = ReportActionsUtils.getWhisperedTo(action); + // const isMultipleParticipant = whisperedTo.length > 1; + + // const iouReportID = + // ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUReportID + // ? (ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ?? '').toString() + // : '-1'; + // const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); + // const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; + // const whisperedToPersonalDetails = isWhisper + // ? (Object.values(personalDetails ?? {}).filter((details) => whisperedTo.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) + // : []; + // const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedTo); + // const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; + + // return ( + // shouldUseNarrowLayout && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + // onPressOut={() => ControlSelection.unblock()} + // onSecondaryInteraction={showPopover} + // preventDefaultContextMenu={draftMessage === undefined && !hasErrors} + // withoutFocusOnSecondaryInteraction + // accessibilityLabel={translate('accessibilityHints.chatMessage')} + // accessible + // > + // + // {(hovered) => ( + // + // {shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && } + // {shouldDisplayContextMenu && ( + // + // )} + // + // { + // const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : undefined; + // if (transactionID) { + // Transaction.clearError(transactionID); + // } + // ReportActions.clearAllRelatedReportActionErrors(reportID, action); + // }} + // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + // pendingAction={ + // draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined) + // } + // shouldHideOnDelete={!ReportActionsUtils.isThreadParentMessage(action, reportID)} + // errors={linkedTransactionRouteError ?? ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)} + // errorRowStyles={[styles.ml10, styles.mr2]} + // needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(action)} + // shouldDisableStrikeThrough + // > + // {isWhisper && ( + // + // + // + // + // + // {translate('reportActionContextMenu.onlyVisible')} + //   + // + // + // + // )} + // {renderReportActionItem(!!hovered || !!isReportActionLinked, isWhisper, hasErrors)} + // + // + // + // )} + // + // + // + // + // + // ); } -export default memo(ReportActionItem, (prevProps, nextProps) => { - const prevParentReportAction = prevProps.parentReportAction; - const nextParentReportAction = nextProps.parentReportAction; - return ( - prevProps.displayAsGroup === nextProps.displayAsGroup && - prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction && - prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker && - lodashIsEqual(prevProps.action, nextProps.action) && - lodashIsEqual(prevProps.report?.pendingFields, nextProps.report?.pendingFields) && - lodashIsEqual(prevProps.report?.isDeletedParentAction, nextProps.report?.isDeletedParentAction) && - lodashIsEqual(prevProps.report?.errorFields, nextProps.report?.errorFields) && - prevProps.report?.statusNum === nextProps.report?.statusNum && - prevProps.report?.stateNum === nextProps.report?.stateNum && - prevProps.report?.parentReportID === nextProps.report?.parentReportID && - prevProps.report?.parentReportActionID === nextProps.report?.parentReportActionID && - // TaskReport's created actions render the TaskView, which updates depending on certain fields in the TaskReport - ReportUtils.isTaskReport(prevProps.report) === ReportUtils.isTaskReport(nextProps.report) && - prevProps.action.actionName === nextProps.action.actionName && - prevProps.report?.reportName === nextProps.report?.reportName && - prevProps.report?.description === nextProps.report?.description && - ReportUtils.isCompletedTaskReport(prevProps.report) === ReportUtils.isCompletedTaskReport(nextProps.report) && - prevProps.report?.managerID === nextProps.report?.managerID && - prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine && - prevProps.report?.total === nextProps.report?.total && - prevProps.report?.nonReimbursableTotal === nextProps.report?.nonReimbursableTotal && - prevProps.report?.policyAvatar === nextProps.report?.policyAvatar && - prevProps.linkedReportActionID === nextProps.linkedReportActionID && - lodashIsEqual(prevProps.report?.fieldList, nextProps.report?.fieldList) && - lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && - lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && - lodashIsEqual(prevParentReportAction, nextParentReportAction) - ); -}); +export default ReportActionItem; + +// export default memo(ReportActionItem, (prevProps, nextProps) => { +// const prevParentReportAction = prevProps.parentReportAction; +// const nextParentReportAction = nextProps.parentReportAction; +// return ( +// prevProps.displayAsGroup === nextProps.displayAsGroup && +// prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction && +// prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker && +// lodashIsEqual(prevProps.action, nextProps.action) && +// lodashIsEqual(prevProps.report?.pendingFields, nextProps.report?.pendingFields) && +// lodashIsEqual(prevProps.report?.isDeletedParentAction, nextProps.report?.isDeletedParentAction) && +// lodashIsEqual(prevProps.report?.errorFields, nextProps.report?.errorFields) && +// prevProps.report?.statusNum === nextProps.report?.statusNum && +// prevProps.report?.stateNum === nextProps.report?.stateNum && +// prevProps.report?.parentReportID === nextProps.report?.parentReportID && +// prevProps.report?.parentReportActionID === nextProps.report?.parentReportActionID && +// // TaskReport's created actions render the TaskView, which updates depending on certain fields in the TaskReport +// ReportUtils.isTaskReport(prevProps.report) === ReportUtils.isTaskReport(nextProps.report) && +// prevProps.action.actionName === nextProps.action.actionName && +// prevProps.report?.reportName === nextProps.report?.reportName && +// prevProps.report?.description === nextProps.report?.description && +// ReportUtils.isCompletedTaskReport(prevProps.report) === ReportUtils.isCompletedTaskReport(nextProps.report) && +// prevProps.report?.managerID === nextProps.report?.managerID && +// prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine && +// prevProps.report?.total === nextProps.report?.total && +// prevProps.report?.nonReimbursableTotal === nextProps.report?.nonReimbursableTotal && +// prevProps.report?.policyAvatar === nextProps.report?.policyAvatar && +// prevProps.linkedReportActionID === nextProps.linkedReportActionID && +// lodashIsEqual(prevProps.report?.fieldList, nextProps.report?.fieldList) && +// lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && +// lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && +// lodashIsEqual(prevParentReportAction, nextParentReportAction) +// ); +// }); From f4fe4b3ecd80605625832abd8291dfeec0778ec2 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 14 Nov 2024 17:05:29 +0700 Subject: [PATCH 03/25] Remove unnecessary code --- .../home/report/PureReportActionItem.tsx | 19 - src/pages/home/report/ReportActionItem.tsx | 927 ------------------ 2 files changed, 946 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index f9cf7ba8bfad..25731504374a 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -192,23 +192,8 @@ function PureReportActionItem({ const reportID = report?.reportID ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); - // const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, { - // selector: (draftMessagesForReport) => { - // const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID]; - // return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message; - // }, - // }); - // const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action) ?? -1}`); - // const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`); - // const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); - // const [linkedTransactionRouteError] = useOnyx( - // `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, - // {selector: (transaction) => transaction?.errorFields?.route ?? null}, - // ); const theme = useTheme(); const styles = useThemeStyles(); - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. - // const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); const StyleUtils = useStyleUtils(); const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); @@ -223,10 +208,6 @@ function PureReportActionItem({ const popoverAnchorRef = useRef>(null); const downloadedPreviews = useRef([]); const prevDraftMessage = usePrevious(draftMessage); - // const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); - // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - // const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; const reportScrollManager = useReportScrollManager(); const isActionableWhisper = diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index a9ef46e53f84..5aadda8e6232 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -192,22 +192,6 @@ function ReportActionItem({ {...props} action={action} report={report} - // transactionThreadReport={transactionThreadReport} - // linkedReportActionID={linkedReportActionID} - // displayAsGroup={displayAsGroup} - // index={index} - // isMostRecentIOUReportAction={isMostRecentIOUReportAction} - // parentReportAction={parentReportAction} - // shouldDisplayNewMarker={shouldDisplayNewMarker} - // shouldHideThreadDividerLine={shouldHideThreadDividerLine} - // shouldShowSubscriptAvatar={shouldShowSubscriptAvatar} - // onPress={onPress} - // isFirstVisibleReportAction={isFirstVisibleReportAction} - // shouldUseThreadDividerLine={shouldUseThreadDividerLine} - // hideThreadReplies={hideThreadReplies} - // shouldDisplayContextMenu={shouldDisplayContextMenu} - // parentReportActionForTransactionThread={parentReportActionForTransactionThread} - // reportActions={reportActions} draftMessage={draftMessage} iouReport={iouReport} @@ -221,917 +205,6 @@ function ReportActionItem({ /> - - - // const {translate} = useLocalize(); - // const {shouldUseNarrowLayout} = useResponsiveLayout(); - // const blockedFromConcierge = useBlockedFromConcierge(); - // const reportID = report?.reportID ?? ''; - // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - // const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); - // const [draftMessage] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, { - // selector: (draftMessagesForReport) => { - // const matchingDraftMessage = draftMessagesForReport?.[action.reportActionID]; - // return typeof matchingDraftMessage === 'string' ? matchingDraftMessage : matchingDraftMessage?.message; - // }, - // }); - // const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${ReportActionsUtils.getIOUReportIDFromReportActionPreview(action) ?? -1}`); - // const [emojiReactions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`); - // const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); - // const [linkedTransactionRouteError] = useOnyx( - // `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, - // {selector: (transaction) => transaction?.errorFields?.route ?? null}, - // ); - // const theme = useTheme(); - // const styles = useThemeStyles(); - // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. - // const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); - // const StyleUtils = useStyleUtils(); - // const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; - // const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); - // const [isEmojiPickerActive, setIsEmojiPickerActive] = useState(); - // const [isPaymentMethodPopoverActive, setIsPaymentMethodPopoverActive] = useState(); - - // const [isHidden, setIsHidden] = useState(false); - // const [moderationDecision, setModerationDecision] = useState(CONST.MODERATION.MODERATOR_DECISION_APPROVED); - // const reactionListRef = useContext(ReactionListContext); - // const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); - // const textInputRef = useRef(null); - // const popoverAnchorRef = useRef>(null); - // const downloadedPreviews = useRef([]); - // const prevDraftMessage = usePrevious(draftMessage); - // const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); - // // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. - // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - // const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); - // const isReportActionLinked = linkedReportActionID && action.reportActionID && linkedReportActionID === action.reportActionID; - // const reportScrollManager = useReportScrollManager(); - // const isActionableWhisper = - // ReportActionsUtils.isActionableMentionWhisper(action) || ReportActionsUtils.isActionableTrackExpense(action) || ReportActionsUtils.isActionableReportMentionWhisper(action); - // const originalMessage = ReportActionsUtils.getOriginalMessage(action); - - // const highlightedBackgroundColorIfNeeded = useMemo( - // () => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.messageHighlightBG) : {}), - // [StyleUtils, isReportActionLinked, theme.messageHighlightBG], - // ); - - // const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(action); - // const isOriginalMessageAnObject = originalMessage && typeof originalMessage === 'object'; - // const hasResolutionInOriginalMessage = isOriginalMessageAnObject && 'resolution' in originalMessage; - // const prevActionResolution = usePrevious(isActionableWhisper && hasResolutionInOriginalMessage ? originalMessage?.resolution : null); - - // // IOUDetails only exists when we are sending money - // const isSendingMoney = - // ReportActionsUtils.isMoneyRequestAction(action) && - // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && - // ReportActionsUtils.getOriginalMessage(action)?.IOUDetails; - - // const updateHiddenState = useCallback( - // (isHiddenValue: boolean) => { - // setIsHidden(isHiddenValue); - // const message = Array.isArray(action.message) ? action.message?.at(-1) : action.message; - // const isAttachment = ReportUtils.isReportMessageAttachment(message); - // if (!isAttachment) { - // return; - // } - // updateHiddenAttachments(action.reportActionID, isHiddenValue); - // }, - // [action.reportActionID, action.message, updateHiddenAttachments], - // ); - - // useEffect( - // () => () => { - // // ReportActionContextMenu, EmojiPicker and PopoverReactionList are global components, - // // we should also hide them when the current component is destroyed - // if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { - // ReportActionContextMenu.hideContextMenu(); - // ReportActionContextMenu.hideDeleteModal(); - // } - // if (EmojiPickerAction.isActive(action.reportActionID)) { - // EmojiPickerAction.hideEmojiPicker(true); - // } - // if (reactionListRef?.current?.isActiveReportAction(action.reportActionID)) { - // reactionListRef?.current?.hideReactionList(); - // } - // }, - // [action.reportActionID, reactionListRef], - // ); - - // useEffect(() => { - // // We need to hide EmojiPicker when this is a deleted parent action - // if (!isDeletedParentAction || !EmojiPickerAction.isActive(action.reportActionID)) { - // return; - // } - - // EmojiPickerAction.hideEmojiPicker(true); - // }, [isDeletedParentAction, action.reportActionID]); - - // useEffect(() => { - // if (prevDraftMessage !== undefined || draftMessage === undefined) { - // return; - // } - - // focusComposerWithDelay(textInputRef.current)(true); - // }, [prevDraftMessage, draftMessage]); - - // useEffect(() => { - // if (!Permissions.canUseLinkPreviews()) { - // return; - // } - - // const urls = ReportActionsUtils.extractLinksFromMessageHtml(action); - // if (lodashIsEqual(downloadedPreviews.current, urls) || action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { - // return; - // } - - // downloadedPreviews.current = urls; - // Report.expandURLPreview(reportID, action.reportActionID); - // }, [action, reportID]); - - // useEffect(() => { - // if (draftMessage === undefined || !ReportActionsUtils.isDeletedAction(action)) { - // return; - // } - // Report.deleteReportActionDraft(reportID, action); - // }, [draftMessage, action, reportID]); - - // // Hide the message if it is being moderated for a higher offense, or is hidden by a moderator - // // Removed messages should not be shown anyway and should not need this flow - // const latestDecision = ReportActionsUtils.getReportActionMessage(action)?.moderationDecision?.decision ?? ''; - // useEffect(() => { - // if (action.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT) { - // return; - // } - - // // Hide reveal message button and show the message if latestDecision is changed to empty - // if (!latestDecision) { - // setModerationDecision(CONST.MODERATION.MODERATOR_DECISION_APPROVED); - // setIsHidden(false); - // return; - // } - - // setModerationDecision(latestDecision); - // if ( - // ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === latestDecision) && - // !ReportActionsUtils.isPendingRemove(action) - // ) { - // setIsHidden(true); - // return; - // } - // setIsHidden(false); - // }, [latestDecision, action]); - - // const toggleContextMenuFromActiveReportAction = useCallback(() => { - // setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); - // }, [action.reportActionID]); - - // const isArchivedRoom = ReportUtils.isArchivedRoomWithID(originalReportID); - // const disabledActions = useMemo(() => (!ReportUtils.canWriteInReport(report) ? RestrictedReadOnlyContextMenuActions : []), [report]); - // const isChronosReport = ReportUtils.chatIncludesChronosWithID(originalReportID); - // /** - // * Show the ReportActionContextMenu modal popover. - // * - // * @param [event] - A press event. - // */ - // const showPopover = useCallback( - // (event: GestureResponderEvent | MouseEvent) => { - // // Block menu on the message being Edited or if the report action item has errors - // if (draftMessage !== undefined || !isEmptyObject(action.errors) || !shouldDisplayContextMenu) { - // return; - // } - - // setIsContextMenuActive(true); - // const selection = SelectionScraper.getCurrentSelection(); - // ReportActionContextMenu.showContextMenu( - // CONST.CONTEXT_MENU_TYPES.REPORT_ACTION, - // event, - // selection, - // popoverAnchorRef.current, - // reportID, - // action.reportActionID, - // originalReportID, - // draftMessage ?? '', - // () => setIsContextMenuActive(true), - // toggleContextMenuFromActiveReportAction, - // isArchivedRoom, - // isChronosReport, - // false, - // false, - // disabledActions, - // false, - // setIsEmojiPickerActive as () => void, - // ); - // }, - // [draftMessage, action, reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport], - // ); - - // // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. - // // This fixes an issue where InvertedFlatList fails to auto scroll down and results in an empty space at the bottom of the chat in IOS. - // useEffect(() => { - // if (index !== 0 || !isActionableWhisper) { - // return; - // } - - // if (prevActionResolution !== (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { - // reportScrollManager.scrollToIndex(index); - // } - // }, [index, originalMessage, prevActionResolution, reportScrollManager, isActionableWhisper, hasResolutionInOriginalMessage]); - - // const toggleReaction = useCallback( - // (emoji: Emoji, ignoreSkinToneOnCompare?: boolean) => { - // Report.toggleEmojiReaction(reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare); - // }, - // [reportID, action, emojiReactions], - // ); - - // const contextValue = useMemo( - // () => ({ - // anchor: popoverAnchorRef.current, - // report: {...report, reportID: report?.reportID ?? ''}, - // reportNameValuePairs, - // action, - // transactionThreadReport, - // checkIfContextMenuActive: toggleContextMenuFromActiveReportAction, - // isDisabled: false, - // }), - // [report, action, toggleContextMenuFromActiveReportAction, transactionThreadReport, reportNameValuePairs], - // ); - - // const attachmentContextValue = useMemo(() => ({reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [reportID]); - - // const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID ?? '-1'}), [report?.reportID]); - - // const actionableItemButtons: ActionableItem[] = useMemo(() => { - // if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded() && shouldRenderAddPaymentCard()) { - // return [ - // { - // text: 'subscription.cardSection.addCardButton', - // key: `${action.reportActionID}-actionableAddPaymentCard-submit`, - // onPress: () => { - // Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION_ADD_PAYMENT_CARD); - // }, - // isMediumSized: true, - // isPrimary: true, - // }, - // ]; - // } - - // if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || ReportActionsUtils.getOriginalMessage(action)?.choice !== ('' as JoinWorkspaceResolution))) { - // return []; - // } - - // if (ReportActionsUtils.isActionableTrackExpense(action)) { - // const transactionID = ReportActionsUtils.getOriginalMessage(action)?.transactionID; - // return [ - // { - // text: 'actionableMentionTrackExpense.submit', - // key: `${action.reportActionID}-actionableMentionTrackExpense-submit`, - // onPress: () => { - // ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); - // }, - // isMediumSized: true, - // }, - // { - // text: 'actionableMentionTrackExpense.categorize', - // key: `${action.reportActionID}-actionableMentionTrackExpense-categorize`, - // onPress: () => { - // ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); - // }, - // isMediumSized: true, - // }, - // { - // text: 'actionableMentionTrackExpense.share', - // key: `${action.reportActionID}-actionableMentionTrackExpense-share`, - // onPress: () => { - // ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); - // }, - // isMediumSized: true, - // }, - // { - // text: 'actionableMentionTrackExpense.nothing', - // key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`, - // onPress: () => { - // Report.dismissTrackExpenseActionableWhisper(reportID, action); - // }, - // isMediumSized: true, - // }, - // ]; - // } - - // if (ReportActionsUtils.isActionableJoinRequest(action)) { - // return [ - // { - // text: 'actionableMentionJoinWorkspaceOptions.accept', - // key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.ACCEPT}`, - // onPress: () => Member.acceptJoinRequest(reportID, action), - // isPrimary: true, - // }, - // { - // text: 'actionableMentionJoinWorkspaceOptions.decline', - // key: `${action.reportActionID}-actionableMentionJoinWorkspace-${CONST.REPORT.ACTIONABLE_MENTION_JOIN_WORKSPACE_RESOLUTION.DECLINE}`, - // onPress: () => Member.declineJoinRequest(reportID, action), - // }, - // ]; - // } - - // if (ReportActionsUtils.isActionableReportMentionWhisper(action)) { - // return [ - // { - // text: 'common.yes', - // key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE}`, - // onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE), - // isPrimary: true, - // }, - // { - // text: 'common.no', - // key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING}`, - // onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING), - // }, - // ]; - // } - - // return [ - // { - // text: 'actionableMentionWhisperOptions.invite', - // key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, - // onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), - // isPrimary: true, - // }, - // { - // text: 'actionableMentionWhisperOptions.nothing', - // key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, - // onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), - // }, - // ]; - // }, [action, isActionableWhisper, reportID]); - - // /** - // * Get the content of ReportActionItem - // * @param hovered whether the ReportActionItem is hovered - // * @param isWhisper whether the report action is a whisper - // * @param hasErrors whether the report action has any errors - // * @returns child component(s) - // */ - // const renderItemContent = (hovered = false, isWhisper = false, hasErrors = false): React.JSX.Element => { - // let children; - - // // Show the MoneyRequestPreview for when expense is present - // if ( - // ReportActionsUtils.isMoneyRequestAction(action) && - // ReportActionsUtils.getOriginalMessage(action) && - // // For the pay flow, we only want to show MoneyRequestAction when sending money. When paying, we display a regular system message - // (ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || - // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || - // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK) - // ) { - // // There is no single iouReport for bill splits, so only 1:1 requests require an iouReportID - // const iouReportID = ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ? ReportActionsUtils.getOriginalMessage(action)?.IOUReportID?.toString() ?? '-1' : '-1'; - // children = ( - // - // ); - // } else if (ReportActionsUtils.isTripPreview(action)) { - // children = ( - // - // ); - // } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { - // children = ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport) ? ( - // ${translate('parentReportAction.deletedReport')}`} /> - // ) : ( - // setIsPaymentMethodPopoverActive(true)} - // onPaymentOptionsHide={() => setIsPaymentMethodPopoverActive(false)} - // isWhisper={isWhisper} - // /> - // ); - // } else if (ReportActionsUtils.isTaskAction(action)) { - // children = ; - // } else if (ReportActionsUtils.isCreatedTaskReportAction(action)) { - // children = ( - // - // - // - // ); - // } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { - // const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; - // const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); - // const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; - - // const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); - // children = ( - // - // <> - // {missingPaymentMethod === 'bankAccount' && ( - // - // )} - // {/** - // These are the actionable buttons that appear at the bottom of a Concierge message - // for example: Invite a user mentioned but not a member of the room - // https://github.com/Expensify/App/issues/32741 - // */} - // {actionableItemButtons.length > 0 && ( - // - // )} - // - // ) : ( - // - // )} - // - // - // - // ); - // } - // const numberOfThreadReplies = action.childVisibleActionCount ?? 0; - - // const shouldDisplayThreadReplies = !hideThreadReplies && ReportUtils.shouldDisplayThreadReplies(action, reportID); - // const oldestFourAccountIDs = - // action.childOldestFourAccountIDs - // ?.split(',') - // .map((accountID) => Number(accountID)) - // .filter((accountID): accountID is number => typeof accountID === 'number') ?? []; - // const draftMessageRightAlign = draftMessage !== undefined ? styles.chatItemReactionsDraftRight : {}; - - // return ( - // <> - // {children} - // {Permissions.canUseLinkPreviews() && !isHidden && (action.linkMetadata?.length ?? 0) > 0 && ( - // - // !isEmptyObject(item))} /> - // - // )} - // {!ReportActionsUtils.isMessageDeleted(action) && ( - // - // { - // if (Session.isAnonymousUser()) { - // hideContextMenu(false); - - // InteractionManager.runAfterInteractions(() => { - // Session.signOutAndRedirectToSignIn(); - // }); - // } else { - // toggleReaction(emoji, ignoreSkinToneOnCompare); - // } - // }} - // setIsEmojiPickerActive={setIsEmojiPickerActive} - // /> - // - // )} - - // {shouldDisplayThreadReplies && ( - // - // - // - // )} - // - // ); - // }; - - // /** - // * Get ReportActionItem with a proper wrapper - // * @param hovered whether the ReportActionItem is hovered - // * @param isWhisper whether the ReportActionItem is a whisper - // * @param hasErrors whether the report action has any errors - // * @returns report action item - // */ - - // const renderReportActionItem = (hovered: boolean, isWhisper: boolean, hasErrors: boolean): React.JSX.Element => { - // const content = renderItemContent(hovered || isContextMenuActive || isEmojiPickerActive, isWhisper, hasErrors); - - // if (draftMessage !== undefined) { - // return {content}; - // } - - // if (!displayAsGroup) { - // return ( - // item === moderationDecision) && - // !ReportActionsUtils.isPendingRemove(action) - // } - // > - // {content} - // - // ); - // } - - // return {content}; - // }; - - // if (action.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { - // const transactionID = ReportActionsUtils.isMoneyRequestAction(parentReportActionForTransactionThread) - // ? ReportActionsUtils.getOriginalMessage(parentReportActionForTransactionThread)?.IOUTransactionID - // : '-1'; - - // return ( - // - // ); - // } - // if (ReportActionsUtils.isChronosOOOListAction(action)) { - // return ( - // - // ); - // } - - // // For the `pay` IOU action on non-pay expense flow, we don't want to render anything if `isWaitingOnBankAccount` is true - // // Otherwise, we will see two system messages informing the payee needs to add a bank account or wallet - // if ( - // ReportActionsUtils.isMoneyRequestAction(action) && - // !!report?.isWaitingOnBankAccount && - // ReportActionsUtils.getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && - // !isSendingMoney - // ) { - // return null; - // } - - // // If action is actionable whisper and resolved by user, then we don't want to render anything - // if (isActionableWhisper && (hasResolutionInOriginalMessage ? originalMessage.resolution : null)) { - // return null; - // } - - // // We currently send whispers to all report participants and hide them in the UI for users that shouldn't see them. - // // This is a temporary solution needed for comment-linking. - // // The long term solution will leverage end-to-end encryption and only targeted users will be able to decrypt. - // if (ReportActionsUtils.isWhisperActionTargetedToOthers(action)) { - // return null; - // } - - // const hasErrors = !isEmptyObject(action.errors); - // const whisperedTo = ReportActionsUtils.getWhisperedTo(action); - // const isMultipleParticipant = whisperedTo.length > 1; - - // const iouReportID = - // ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUReportID - // ? (ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ?? '').toString() - // : '-1'; - // const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); - // const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; - // const whisperedToPersonalDetails = isWhisper - // ? (Object.values(personalDetails ?? {}).filter((details) => whisperedTo.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) - // : []; - // const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedTo); - // const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; - - // return ( - // shouldUseNarrowLayout && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} - // onPressOut={() => ControlSelection.unblock()} - // onSecondaryInteraction={showPopover} - // preventDefaultContextMenu={draftMessage === undefined && !hasErrors} - // withoutFocusOnSecondaryInteraction - // accessibilityLabel={translate('accessibilityHints.chatMessage')} - // accessible - // > - // - // {(hovered) => ( - // - // {shouldDisplayNewMarker && (!shouldUseThreadDividerLine || !isFirstVisibleReportAction) && } - // {shouldDisplayContextMenu && ( - // - // )} - // - // { - // const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : undefined; - // if (transactionID) { - // Transaction.clearError(transactionID); - // } - // ReportActions.clearAllRelatedReportActionErrors(reportID, action); - // }} - // // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - // pendingAction={ - // draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined) - // } - // shouldHideOnDelete={!ReportActionsUtils.isThreadParentMessage(action, reportID)} - // errors={linkedTransactionRouteError ?? ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)} - // errorRowStyles={[styles.ml10, styles.mr2]} - // needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(action)} - // shouldDisableStrikeThrough - // > - // {isWhisper && ( - // - // - // - // - // - // {translate('reportActionContextMenu.onlyVisible')} - //   - // - // - // - // )} - // {renderReportActionItem(!!hovered || !!isReportActionLinked, isWhisper, hasErrors)} - // - // - // - // )} - // - // - // - // - // - // ); } export default ReportActionItem; - -// export default memo(ReportActionItem, (prevProps, nextProps) => { -// const prevParentReportAction = prevProps.parentReportAction; -// const nextParentReportAction = nextProps.parentReportAction; -// return ( -// prevProps.displayAsGroup === nextProps.displayAsGroup && -// prevProps.isMostRecentIOUReportAction === nextProps.isMostRecentIOUReportAction && -// prevProps.shouldDisplayNewMarker === nextProps.shouldDisplayNewMarker && -// lodashIsEqual(prevProps.action, nextProps.action) && -// lodashIsEqual(prevProps.report?.pendingFields, nextProps.report?.pendingFields) && -// lodashIsEqual(prevProps.report?.isDeletedParentAction, nextProps.report?.isDeletedParentAction) && -// lodashIsEqual(prevProps.report?.errorFields, nextProps.report?.errorFields) && -// prevProps.report?.statusNum === nextProps.report?.statusNum && -// prevProps.report?.stateNum === nextProps.report?.stateNum && -// prevProps.report?.parentReportID === nextProps.report?.parentReportID && -// prevProps.report?.parentReportActionID === nextProps.report?.parentReportActionID && -// // TaskReport's created actions render the TaskView, which updates depending on certain fields in the TaskReport -// ReportUtils.isTaskReport(prevProps.report) === ReportUtils.isTaskReport(nextProps.report) && -// prevProps.action.actionName === nextProps.action.actionName && -// prevProps.report?.reportName === nextProps.report?.reportName && -// prevProps.report?.description === nextProps.report?.description && -// ReportUtils.isCompletedTaskReport(prevProps.report) === ReportUtils.isCompletedTaskReport(nextProps.report) && -// prevProps.report?.managerID === nextProps.report?.managerID && -// prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine && -// prevProps.report?.total === nextProps.report?.total && -// prevProps.report?.nonReimbursableTotal === nextProps.report?.nonReimbursableTotal && -// prevProps.report?.policyAvatar === nextProps.report?.policyAvatar && -// prevProps.linkedReportActionID === nextProps.linkedReportActionID && -// lodashIsEqual(prevProps.report?.fieldList, nextProps.report?.fieldList) && -// lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && -// lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && -// lodashIsEqual(prevParentReportAction, nextParentReportAction) -// ); -// }); From b049c65910ff72029100ac58ca9736b2bf4fdb42 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 14 Nov 2024 21:42:12 +0700 Subject: [PATCH 04/25] Remove unnecessary code --- src/pages/home/report/ReportActionItem.tsx | 198 +++------------------ 1 file changed, 20 insertions(+), 178 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 5aadda8e6232..d29f9a6599ff 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -1,167 +1,12 @@ -import lodashIsEqual from 'lodash/isEqual'; -import React, {memo, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; -import type {GestureResponderEvent, TextInput} from 'react-native'; -import {InteractionManager, View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; +import React, {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; -import type {Emoji} from '@assets/emojis/types'; -import {AttachmentContext} from '@components/AttachmentContext'; -import Button from '@components/Button'; -import DisplayNames from '@components/DisplayNames'; -import Hoverable from '@components/Hoverable'; -import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import InlineSystemMessage from '@components/InlineSystemMessage'; -import KYCWall from '@components/KYCWall'; -import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider'; -import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; -import ReportActionItemEmojiReactions from '@components/Reactions/ReportActionItemEmojiReactions'; -import RenderHTML from '@components/RenderHTML'; -import type {ActionableItem} from '@components/ReportActionItem/ActionableItemButtons'; -import ActionableItemButtons from '@components/ReportActionItem/ActionableItemButtons'; -import ChronosOOOListActions from '@components/ReportActionItem/ChronosOOOListActions'; -import ExportIntegration from '@components/ReportActionItem/ExportIntegration'; -import IssueCardMessage from '@components/ReportActionItem/IssueCardMessage'; -import MoneyRequestAction from '@components/ReportActionItem/MoneyRequestAction'; -import ReportPreview from '@components/ReportActionItem/ReportPreview'; -import TaskAction from '@components/ReportActionItem/TaskAction'; -import TaskPreview from '@components/ReportActionItem/TaskPreview'; -import TripRoomPreview from '@components/ReportActionItem/TripRoomPreview'; -import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; -import Text from '@components/Text'; -import UnreadActionIndicator from '@components/UnreadActionIndicator'; -import useLocalize from '@hooks/useLocalize'; -import usePrevious from '@hooks/usePrevious'; -import useReportScrollManager from '@hooks/useReportScrollManager'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import ControlSelection from '@libs/ControlSelection'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import * as ErrorUtils from '@libs/ErrorUtils'; -import focusComposerWithDelay from '@libs/focusComposerWithDelay'; -import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; -import Navigation from '@libs/Navigation/Navigation'; -import Permissions from '@libs/Permissions'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import SelectionScraper from '@libs/SelectionScraper'; -import shouldRenderAddPaymentCard from '@libs/shouldRenderAppPaymentCard'; -import {doesUserHavePaymentCardAdded} from '@libs/SubscriptionUtils'; -import {ReactionListContext} from '@pages/home/ReportScreenContext'; -import * as BankAccounts from '@userActions/BankAccounts'; -import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; -import * as Member from '@userActions/Policy/Member'; -import * as Report from '@userActions/Report'; -import * as ReportActions from '@userActions/ReportActions'; -import * as Session from '@userActions/Session'; -import * as Transaction from '@userActions/Transaction'; -import * as User from '@userActions/User'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import type * as OnyxTypes from '@src/types/onyx'; -import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import {RestrictedReadOnlyContextMenuActions} from './ContextMenu/ContextMenuActions'; -import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; -import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; -import {hideContextMenu} from './ContextMenu/ReportActionContextMenu'; -import LinkPreviewer from './LinkPreviewer'; -import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; -import ReportActionItemContentCreated from './ReportActionItemContentCreated'; -import ReportActionItemDraft from './ReportActionItemDraft'; -import ReportActionItemGrouped from './ReportActionItemGrouped'; -import ReportActionItemMessage from './ReportActionItemMessage'; -import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; -import ReportActionItemSingle from './ReportActionItemSingle'; -import ReportActionItemThread from './ReportActionItemThread'; -import ReportAttachmentsContext from './ReportAttachmentsContext'; -import type { PureReportActionItemProps } from './PureReportActionItem'; +import type {PureReportActionItemProps} from './PureReportActionItem'; import PureReportActionItem from './PureReportActionItem'; -// type ReportActionItemProps = { -// /** Report for this action */ -// report: OnyxEntry; -// /** The transaction thread report associated with the report for this action, if any */ -// transactionThreadReport?: OnyxEntry; - -// /** Array of report actions for the report for this action */ -// // eslint-disable-next-line react/no-unused-prop-types -// reportActions: OnyxTypes.ReportAction[]; - -// /** Report action belonging to the report's parent */ -// parentReportAction: OnyxEntry; - -// /** The transaction thread report's parentReportAction */ -// /** It's used by withOnyx HOC */ -// // eslint-disable-next-line react/no-unused-prop-types -// parentReportActionForTransactionThread?: OnyxEntry; - -// /** All the data of the action item */ -// action: OnyxTypes.ReportAction; - -// /** Should the comment have the appearance of being grouped with the previous comment? */ -// displayAsGroup: boolean; - -// /** Is this the most recent IOU Action? */ -// isMostRecentIOUReportAction: boolean; - -// /** Should we display the new marker on top of the comment? */ -// shouldDisplayNewMarker: boolean; - -// /** Determines if the avatar is displayed as a subscript (positioned lower than normal) */ -// shouldShowSubscriptAvatar?: boolean; - -// /** Position index of the report action in the overall report FlatList view */ -// index: number; - -// /** Flag to show, hide the thread divider line */ -// shouldHideThreadDividerLine?: boolean; - -// linkedReportActionID?: string; - -// /** Callback to be called on onPress */ -// onPress?: () => void; - -// /** If this is the first visible report action */ -// isFirstVisibleReportAction: boolean; - -// /** IF the thread divider line will be used */ -// shouldUseThreadDividerLine?: boolean; - -// hideThreadReplies?: boolean; - -// /** Whether context menu should be displayed */ -// shouldDisplayContextMenu?: boolean; -// }; - -function ReportActionItem({ - action, - report, - ...props - // transactionThreadReport, - // linkedReportActionID, - // displayAsGroup, - // index, - // isMostRecentIOUReportAction, - // parentReportAction, - // shouldDisplayNewMarker, - // shouldHideThreadDividerLine = false, - // shouldShowSubscriptAvatar = false, - // onPress = undefined, - // isFirstVisibleReportAction = false, - // shouldUseThreadDividerLine = false, - // hideThreadReplies = false, - // shouldDisplayContextMenu = true, - // parentReportActionForTransactionThread, - // reportActions, -}: PureReportActionItemProps) { +function ReportActionItem({action, report, ...props}: PureReportActionItemProps) { const reportID = report?.reportID ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); @@ -176,35 +21,32 @@ function ReportActionItem({ const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); const [linkedTransactionRouteError] = useOnyx( `${ONYXKEYS.COLLECTION.TRANSACTION}${ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID ?? -1 : -1}`, - { selector: (transaction) => transaction?.errorFields?.route ?? null }, + {selector: (transaction) => transaction?.errorFields?.route ?? null}, ); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- This is needed to prevent the app from crashing when the app is using imported state. const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID || '-1'}`); - const [isUserValidated] = useOnyx(ONYXKEYS.USER, { selector: (user) => !!user?.validated }); + const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); - - - return + return ( + + ); } export default ReportActionItem; From c20a1aaf49baa21b219bc1dc10254be020fc8617 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 14:30:15 +0700 Subject: [PATCH 05/25] Resolve conflict --- .../home/report/PureReportActionItem.tsx | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 25731504374a..77628d4e7dcf 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -131,11 +131,15 @@ type PureReportActionItemProps = { /** If this is the first visible report action */ isFirstVisibleReportAction: boolean; + /** + * Is the action a thread's parent reportAction viewed from within the thread report? + * It will be false if we're viewing the same parent report action from the report it belongs to rather than the thread. + */ + isThreadReportParentAction?: boolean; + /** IF the thread divider line will be used */ shouldUseThreadDividerLine?: boolean; - hideThreadReplies?: boolean; - /** Whether context menu should be displayed */ shouldDisplayContextMenu?: boolean; @@ -172,8 +176,8 @@ function PureReportActionItem({ shouldShowSubscriptAvatar = false, onPress = undefined, isFirstVisibleReportAction = false, + isThreadReportParentAction = false, shouldUseThreadDividerLine = false, - hideThreadReplies = false, shouldDisplayContextMenu = true, parentReportActionForTransactionThread, @@ -364,9 +368,22 @@ function PureReportActionItem({ disabledActions, false, setIsEmojiPickerActive as () => void, + undefined, + isThreadReportParentAction, ); }, - [draftMessage, action, reportID, toggleContextMenuFromActiveReportAction, originalReportID, shouldDisplayContextMenu, disabledActions, isArchivedRoom, isChronosReport], + [ + draftMessage, + action, + reportID, + toggleContextMenuFromActiveReportAction, + originalReportID, + shouldDisplayContextMenu, + disabledActions, + isArchivedRoom, + isChronosReport, + isThreadReportParentAction, + ], ); // Handles manual scrolling to the bottom of the chat when the last message is an actionable whisper and it's resolved. @@ -792,7 +809,7 @@ function PureReportActionItem({ } const numberOfThreadReplies = action.childVisibleActionCount ?? 0; - const shouldDisplayThreadReplies = !hideThreadReplies && ReportUtils.shouldDisplayThreadReplies(action, reportID); + const shouldDisplayThreadReplies = ReportUtils.shouldDisplayThreadReplies(action, isThreadReportParentAction); const oldestFourAccountIDs = action.childOldestFourAccountIDs ?.split(',') @@ -978,6 +995,7 @@ function PureReportActionItem({ displayAsGroup={displayAsGroup} disabledActions={disabledActions} isVisible={hovered && draftMessage === undefined && !hasErrors} + isThreadReportParentAction={isThreadReportParentAction} draftMessage={draftMessage} isChronosReport={isChronosReport} checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} @@ -1043,9 +1061,7 @@ function PureReportActionItem({ ); } - export type { PureReportActionItemProps }; -// export default PureReportActionItem; export default memo(PureReportActionItem, (prevProps, nextProps) => { const prevParentReportAction = prevProps.parentReportAction; const nextParentReportAction = nextProps.parentReportAction; @@ -1085,6 +1101,5 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { prevProps.linkedTransactionRouteError === nextProps.linkedTransactionRouteError && prevProps.isUserValidated === nextProps.isUserValidated && prevProps.parentReport?.reportID === nextProps.parentReport?.reportID - ); -}); +}); \ No newline at end of file From 1333d4c40f327e07081cadbce7bc812fe85409b2 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 14:51:48 +0700 Subject: [PATCH 06/25] lint --- src/pages/home/report/PureReportActionItem.tsx | 14 +++++--------- src/pages/home/report/ReportActionItem.tsx | 1 + 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 77628d4e7dcf..3439f5fe6c74 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -62,9 +62,9 @@ import * as Session from '@userActions/Session'; import * as Transaction from '@userActions/Transaction'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; +import type {Errors} from '@src/types/onyx/OnyxCommon'; import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import {RestrictedReadOnlyContextMenuActions} from './ContextMenu/ContextMenuActions'; @@ -81,7 +81,6 @@ import ReportActionItemMessageEdit from './ReportActionItemMessageEdit'; import ReportActionItemSingle from './ReportActionItemSingle'; import ReportActionItemThread from './ReportActionItemThread'; import ReportAttachmentsContext from './ReportAttachmentsContext'; -import type {Errors} from '@src/types/onyx/OnyxCommon'; type PureReportActionItemProps = { /** Report for this action */ @@ -143,9 +142,7 @@ type PureReportActionItemProps = { /** Whether context menu should be displayed */ shouldDisplayContextMenu?: boolean; - - - draftMessage? : string + draftMessage?: string; iouReport?: OnyxTypes.Report; @@ -1061,7 +1058,7 @@ function PureReportActionItem({ ); } -export type { PureReportActionItemProps }; +export type {PureReportActionItemProps}; export default memo(PureReportActionItem, (prevProps, nextProps) => { const prevParentReportAction = prevProps.parentReportAction; const nextParentReportAction = nextProps.parentReportAction; @@ -1093,13 +1090,12 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { lodashIsEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) && lodashIsEqual(prevProps.reportActions, nextProps.reportActions) && lodashIsEqual(prevParentReportAction, nextParentReportAction) && - prevProps.draftMessage === nextProps.draftMessage && prevProps.iouReport?.reportID === nextProps.iouReport?.reportID && prevProps.emojiReactions === nextProps.emojiReactions && prevProps.userWallet === nextProps.userWallet && prevProps.linkedTransactionRouteError === nextProps.linkedTransactionRouteError && prevProps.isUserValidated === nextProps.isUserValidated && - prevProps.parentReport?.reportID === nextProps.parentReport?.reportID + prevProps.parentReport?.reportID === nextProps.parentReport?.reportID ); -}); \ No newline at end of file +}); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index d29f9a6599ff..a162efc0db26 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -34,6 +34,7 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) return ( Date: Fri, 22 Nov 2024 15:37:18 +0700 Subject: [PATCH 07/25] Add comment --- src/pages/home/report/PureReportActionItem.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 3439f5fe6c74..12ce82a57804 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -142,20 +142,28 @@ type PureReportActionItemProps = { /** Whether context menu should be displayed */ shouldDisplayContextMenu?: boolean; + /** ReportAction Draftmessage */ draftMessage?: string; + /** The IOU/Expense report we are paying */ iouReport?: OnyxTypes.Report; + /** All the emoji reactions for the report action. */ emojiReactions?: OnyxTypes.ReportActionReactions; + /** User's Expensify Wallet */ userWallet?: OnyxTypes.UserWallet; + /** Linked transaction route error */ linkedTransactionRouteError?: Errors; + /** Optional property for report name-value pairs */ reportNameValuePairs?: OnyxTypes.ReportNameValuePairs; + /** Optional property to indicate if the user is validated */ isUserValidated?: boolean; + /** Parent report */ parentReport?: OnyxTypes.Report; }; @@ -177,7 +185,6 @@ function PureReportActionItem({ shouldUseThreadDividerLine = false, shouldDisplayContextMenu = true, parentReportActionForTransactionThread, - draftMessage, iouReport, emojiReactions, From 672278e1eb60e153aff29c148a3c38d6ada72ffc Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 16:12:44 +0700 Subject: [PATCH 08/25] extract useBlockedFromConcierge, usePersonalDetails --- src/pages/home/report/PureReportActionItem.tsx | 11 ++++++++--- src/pages/home/report/ReportActionItem.tsx | 6 ++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 12ce82a57804..fc88cbb0f58d 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -14,7 +14,6 @@ import * as Expensicons from '@components/Icon/Expensicons'; import InlineSystemMessage from '@components/InlineSystemMessage'; import KYCWall from '@components/KYCWall'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import ReportActionItemEmojiReactions from '@components/Reactions/ReportActionItemEmojiReactions'; import RenderHTML from '@components/RenderHTML'; @@ -165,6 +164,12 @@ type PureReportActionItemProps = { /** Parent report */ parentReport?: OnyxTypes.Report; + + /** Personal details list */ + personalDetails?: OnyxTypes.PersonalDetailsList; + + /** Whether or not the user is blocked from concierge */ + blockedFromConcierge?: OnyxTypes.BlockedFromConcierge; }; function PureReportActionItem({ @@ -193,17 +198,17 @@ function PureReportActionItem({ reportNameValuePairs, isUserValidated, parentReport, + personalDetails, + blockedFromConcierge, }: PureReportActionItemProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); - const blockedFromConcierge = useBlockedFromConcierge(); const reportID = report?.reportID ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const [isContextMenuActive, setIsContextMenuActive] = useState(() => ReportActionContextMenu.isActiveReportAction(action.reportActionID)); const [isEmojiPickerActive, setIsEmojiPickerActive] = useState(); const [isPaymentMethodPopoverActive, setIsPaymentMethodPopoverActive] = useState(); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index a162efc0db26..67742f755bd1 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -1,7 +1,9 @@ import React, {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; +import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PureReportActionItemProps} from './PureReportActionItem'; import PureReportActionItem from './PureReportActionItem'; @@ -31,6 +33,8 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); + const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; + const blockedFromConcierge = useBlockedFromConcierge(); return ( ); } From 3e3a093f5d2ac44dd4df33a7e155c446054ed716 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 16:25:29 +0700 Subject: [PATCH 09/25] fix type error --- src/pages/home/report/PureReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index fc88cbb0f58d..52f30b429101 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -620,7 +620,7 @@ function PureReportActionItem({ ); } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails[linkedReport?.ownerAccountID ?? -1]); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[linkedReport?.ownerAccountID ?? -1] ?? {}); const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); From e5ae24d92d44b6bed5a9388c610a6b6dc0c0e6a5 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 11:21:07 +0700 Subject: [PATCH 10/25] Extract other onyx related vars from PureReportActionItem --- src/libs/ReportUtils.ts | 1 + src/libs/actions/ReportActions.ts | 1 + .../home/report/PureReportActionItem.tsx | 133 ++++++++++++++---- src/pages/home/report/ReportActionItem.tsx | 18 +++ 4 files changed, 124 insertions(+), 29 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 952e0c2fe4cc..4e98e84344bd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8759,4 +8759,5 @@ export type { TransactionDetails, PartialReportAction, ParsingDetails, + MissingPaymentMethod, }; diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 9338527eaccc..f0a018e344a1 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -98,6 +98,7 @@ function clearAllRelatedReportActionErrors(reportID: string, reportAction: Repor } } +export type {IgnoreDirection}; export { // eslint-disable-next-line import/prefer-default-export clearAllRelatedReportActionErrors, diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 52f30b429101..79195a2477dd 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -3,6 +3,7 @@ import React, {memo, useCallback, useContext, useEffect, useMemo, useRef, useSta import type {GestureResponderEvent, TextInput} from 'react-native'; import {InteractionManager, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import type {Emoji} from '@assets/emojis/types'; import {AttachmentContext} from '@components/AttachmentContext'; import Button from '@components/Button'; @@ -41,26 +42,25 @@ import ControlSelection from '@libs/ControlSelection'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as ErrorUtils from '@libs/ErrorUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; -import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import type {MissingPaymentMethod} from '@libs/ReportUtils'; import SelectionScraper from '@libs/SelectionScraper'; import shouldRenderAddPaymentCard from '@libs/shouldRenderAppPaymentCard'; -import {doesUserHavePaymentCardAdded} from '@libs/SubscriptionUtils'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; import * as BankAccounts from '@userActions/BankAccounts'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import * as Member from '@userActions/Policy/Member'; import * as Report from '@userActions/Report'; -import * as ReportActions from '@userActions/ReportActions'; +import type {IgnoreDirection} from '@userActions/ReportActions'; import * as Session from '@userActions/Session'; -import * as Transaction from '@userActions/Transaction'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; +import type {IOUAction} from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; @@ -170,6 +170,58 @@ type PureReportActionItemProps = { /** Whether or not the user is blocked from concierge */ blockedFromConcierge?: OnyxTypes.BlockedFromConcierge; + + /** ID of the original report from which the given reportAction is first created */ + originalReportID?: string; + + deleteReportActionDraft: (reportID: string, action: OnyxTypes.ReportAction) => void; + + isArchivedRoom?: boolean; + + isChronosReport?: boolean; + + toggleEmojiReaction?: ( + reportID: string, + reportAction: OnyxTypes.ReportAction, + reactionObject: Emoji, + existingReactions: OnyxEntry, + paramSkinTone: number | undefined, + ignoreSkinToneOnCompare: boolean | undefined, + ) => void; + + doesUserHavePaymentCardAdded: boolean | undefined; + + createDraftTransactionAndNavigateToParticipantSelector: (transactionID: string, reportID: string, actionName: IOUAction, reportActionID: string) => void; + + resolveActionableReportMentionWhisper: ( + reportId: string, + reportAction: OnyxEntry, + resolution: ValueOf, + ) => void; + + isClosedExpenseReportWithNoExpenses: (report: OnyxEntry) => boolean; + + getIndicatedMissingPaymentMethod: (userWallet: OnyxEntry, reportId: string, reportAction: OnyxTypes.ReportAction) => MissingPaymentMethod | undefined; + + isReimbursementDeQueuedAction: (reportAction: OnyxEntry) => reportAction is OnyxTypes.ReportAction; + + getReimbursementDeQueuedActionMessage: ( + reportAction: OnyxEntry>, + reportOrID: OnyxEntry | string, + isLHNPreview?: boolean, + ) => string; + + getForReportAction: (reportID: string | undefined, reportAction: OnyxEntry) => string; + + getTransactionsWithReceipts: (iouReportID: string | undefined) => OnyxTypes.Transaction[]; + + isCurrentUserTheOnlyParticipant: (participantAccountIDs?: number[]) => boolean; + + clearError: (transactionID: string) => void; + + clearAllRelatedReportActionErrors: (reportID: string, reportAction: OnyxTypes.ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) => void; + + dismissTrackExpenseActionableWhisper: (reportID: string, reportAction: OnyxEntry) => void; }; function PureReportActionItem({ @@ -200,12 +252,28 @@ function PureReportActionItem({ parentReport, personalDetails, blockedFromConcierge, + originalReportID = '-1', + deleteReportActionDraft = () => {}, + isArchivedRoom, + isChronosReport, + doesUserHavePaymentCardAdded, + toggleEmojiReaction = () => {}, + createDraftTransactionAndNavigateToParticipantSelector = () => {}, + resolveActionableReportMentionWhisper = () => {}, + isClosedExpenseReportWithNoExpenses, + isCurrentUserTheOnlyParticipant, + getIndicatedMissingPaymentMethod, + isReimbursementDeQueuedAction, + getReimbursementDeQueuedActionMessage, + getForReportAction, + getTransactionsWithReceipts, + clearError = () => {}, + clearAllRelatedReportActionErrors = () => {}, + dismissTrackExpenseActionableWhisper = () => {}, }: PureReportActionItemProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const reportID = report?.reportID ?? ''; - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const originalReportID = useMemo(() => ReportUtils.getOriginalReportID(reportID, action) || '-1', [reportID, action]); const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -309,8 +377,8 @@ function PureReportActionItem({ if (draftMessage === undefined || !ReportActionsUtils.isDeletedAction(action)) { return; } - Report.deleteReportActionDraft(reportID, action); - }, [draftMessage, action, reportID]); + deleteReportActionDraft(reportID, action); + }, [draftMessage, action, reportID, deleteReportActionDraft]); // Hide the message if it is being moderated for a higher offense, or is hidden by a moderator // Removed messages should not be shown anyway and should not need this flow @@ -342,9 +410,8 @@ function PureReportActionItem({ setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(action.reportActionID)); }, [action.reportActionID]); - const isArchivedRoom = ReportUtils.isArchivedRoomWithID(originalReportID); const disabledActions = useMemo(() => (!ReportUtils.canWriteInReport(report) ? RestrictedReadOnlyContextMenuActions : []), [report]); - const isChronosReport = ReportUtils.chatIncludesChronosWithID(originalReportID); + /** * Show the ReportActionContextMenu modal popover. * @@ -409,9 +476,9 @@ function PureReportActionItem({ const toggleReaction = useCallback( (emoji: Emoji, ignoreSkinToneOnCompare?: boolean) => { - Report.toggleEmojiReaction(reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare); + toggleEmojiReaction(reportID, action, emoji, emojiReactions, undefined, ignoreSkinToneOnCompare); }, - [reportID, action, emojiReactions], + [reportID, action, emojiReactions, toggleEmojiReaction], ); const contextValue = useMemo( @@ -432,7 +499,7 @@ function PureReportActionItem({ const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID ?? '-1'}), [report?.reportID]); const actionableItemButtons: ActionableItem[] = useMemo(() => { - if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded() && shouldRenderAddPaymentCard()) { + if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded && shouldRenderAddPaymentCard()) { return [ { text: 'subscription.cardSection.addCardButton', @@ -457,7 +524,7 @@ function PureReportActionItem({ text: 'actionableMentionTrackExpense.submit', key: `${action.reportActionID}-actionableMentionTrackExpense-submit`, onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); + createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); }, isMediumSized: true, }, @@ -465,7 +532,7 @@ function PureReportActionItem({ text: 'actionableMentionTrackExpense.categorize', key: `${action.reportActionID}-actionableMentionTrackExpense-categorize`, onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); + createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.CATEGORIZE, action.reportActionID); }, isMediumSized: true, }, @@ -473,7 +540,7 @@ function PureReportActionItem({ text: 'actionableMentionTrackExpense.share', key: `${action.reportActionID}-actionableMentionTrackExpense-share`, onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); + createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SHARE, action.reportActionID); }, isMediumSized: true, }, @@ -481,7 +548,7 @@ function PureReportActionItem({ text: 'actionableMentionTrackExpense.nothing', key: `${action.reportActionID}-actionableMentionTrackExpense-nothing`, onPress: () => { - Report.dismissTrackExpenseActionableWhisper(reportID, action); + dismissTrackExpenseActionableWhisper(reportID, action); }, isMediumSized: true, }, @@ -509,13 +576,13 @@ function PureReportActionItem({ { text: 'common.yes', key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE}`, - onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE), + onPress: () => resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.CREATE), isPrimary: true, }, { text: 'common.no', key: `${action.reportActionID}-actionableReportMentionWhisper-${CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING}`, - onPress: () => Report.resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING), + onPress: () => resolveActionableReportMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_REPORT_MENTION_WHISPER_RESOLUTION.NOTHING), }, ]; } @@ -533,7 +600,15 @@ function PureReportActionItem({ onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), }, ]; - }, [action, isActionableWhisper, reportID]); + }, [ + action, + isActionableWhisper, + reportID, + createDraftTransactionAndNavigateToParticipantSelector, + dismissTrackExpenseActionableWhisper, + doesUserHavePaymentCardAdded, + resolveActionableReportMentionWhisper, + ]); /** * Get the content of ReportActionItem @@ -584,7 +659,7 @@ function PureReportActionItem({ /> ); } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { - children = ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport) ? ( + children = isClosedExpenseReportWithNoExpenses(iouReport) ? ( ${translate('parentReportAction.deletedReport')}`} /> ) : ( ); - } else if (ReportActionsUtils.isReimbursementDeQueuedAction(action)) { - children = ; + } else if (isReimbursementDeQueuedAction(action)) { + children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) { - children = ; + children = ; } else if ( ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED) || ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED) @@ -965,12 +1040,12 @@ function PureReportActionItem({ ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ? (ReportActionsUtils.getOriginalMessage(action)?.IOUReportID ?? '').toString() : '-1'; - const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); + const transactionsWithReceipts = getTransactionsWithReceipts(iouReportID); const isWhisper = whisperedTo.length > 0 && transactionsWithReceipts.length === 0; const whisperedToPersonalDetails = isWhisper ? (Object.values(personalDetails ?? {}).filter((details) => whisperedTo.includes(details?.accountID ?? -1)) as OnyxTypes.PersonalDetails[]) : []; - const isWhisperOnlyVisibleByUser = isWhisper && ReportUtils.isCurrentUserTheOnlyParticipant(whisperedTo); + const isWhisperOnlyVisibleByUser = isWhisper && isCurrentUserTheOnlyParticipant(whisperedTo); const displayNamesWithTooltips = isWhisper ? ReportUtils.getDisplayNamesWithTooltips(whisperedToPersonalDetails, isMultipleParticipant) : []; return ( @@ -1021,9 +1096,9 @@ function PureReportActionItem({ onClose={() => { const transactionID = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : undefined; if (transactionID) { - Transaction.clearError(transactionID); + clearError(transactionID); } - ReportActions.clearAllRelatedReportActionErrors(reportID, action); + clearAllRelatedReportActionErrors(reportID, action); }} // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing pendingAction={ diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 67742f755bd1..a3597b1712a8 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -1,8 +1,12 @@ import React, {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider'; +import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import {doesUserHavePaymentCardAdded} from '@libs/SubscriptionUtils'; +import * as Report from '@userActions/Report'; +import * as ReportActions from '@userActions/ReportActions'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PureReportActionItemProps} from './PureReportActionItem'; @@ -52,6 +56,20 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) parentReport={parentReport} personalDetails={personalDetails} blockedFromConcierge={blockedFromConcierge} + originalReportID={originalReportID} + deleteReportActionDraft={Report.deleteReportActionDraft} + isArchivedRoom={ReportUtils.isArchivedRoomWithID(originalReportID)} + isChronosReport={ReportUtils.chatIncludesChronosWithID(originalReportID)} + toggleEmojiReaction={Report.toggleEmojiReaction} + doesUserHavePaymentCardAdded={doesUserHavePaymentCardAdded()} + createDraftTransactionAndNavigateToParticipantSelector={ReportUtils.createDraftTransactionAndNavigateToParticipantSelector} + resolveActionableReportMentionWhisper={Report.resolveActionableReportMentionWhisper} + isClosedExpenseReportWithNoExpenses={ReportUtils.isClosedExpenseReportWithNoExpenses} + isReimbursementDeQueuedAction={ReportActionsUtils.isReimbursementDeQueuedAction} + getForReportAction={ModifiedExpenseMessage.getForReportAction} + getTransactionsWithReceipts={ReportUtils.getTransactionsWithReceipts} + isCurrentUserTheOnlyParticipant={ReportUtils.isCurrentUserTheOnlyParticipant} + clearAllRelatedReportActionErrors={ReportActions.clearAllRelatedReportActionErrors} /> ); } From e137acdcf65e886cab73e91af177d5f25386d1c1 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 11:35:25 +0700 Subject: [PATCH 11/25] Make all extracted onyx vars optional, fix typecheck error --- .../home/report/PureReportActionItem.tsx | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 79195a2477dd..46802dde2205 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -174,7 +174,7 @@ type PureReportActionItemProps = { /** ID of the original report from which the given reportAction is first created */ originalReportID?: string; - deleteReportActionDraft: (reportID: string, action: OnyxTypes.ReportAction) => void; + deleteReportActionDraft?: (reportID: string, action: OnyxTypes.ReportAction) => void; isArchivedRoom?: boolean; @@ -189,39 +189,39 @@ type PureReportActionItemProps = { ignoreSkinToneOnCompare: boolean | undefined, ) => void; - doesUserHavePaymentCardAdded: boolean | undefined; + doesUserHavePaymentCardAdded?: boolean | undefined; - createDraftTransactionAndNavigateToParticipantSelector: (transactionID: string, reportID: string, actionName: IOUAction, reportActionID: string) => void; + createDraftTransactionAndNavigateToParticipantSelector?: (transactionID: string, reportID: string, actionName: IOUAction, reportActionID: string) => void; - resolveActionableReportMentionWhisper: ( + resolveActionableReportMentionWhisper?: ( reportId: string, reportAction: OnyxEntry, resolution: ValueOf, ) => void; - isClosedExpenseReportWithNoExpenses: (report: OnyxEntry) => boolean; + isClosedExpenseReportWithNoExpenses?: (report: OnyxEntry) => boolean; - getIndicatedMissingPaymentMethod: (userWallet: OnyxEntry, reportId: string, reportAction: OnyxTypes.ReportAction) => MissingPaymentMethod | undefined; + getIndicatedMissingPaymentMethod?: (userWallet: OnyxEntry, reportId: string, reportAction: OnyxTypes.ReportAction) => MissingPaymentMethod | undefined; - isReimbursementDeQueuedAction: (reportAction: OnyxEntry) => reportAction is OnyxTypes.ReportAction; + isReimbursementDeQueuedAction?: (reportAction: OnyxEntry) => reportAction is OnyxTypes.ReportAction; - getReimbursementDeQueuedActionMessage: ( + getReimbursementDeQueuedActionMessage?: ( reportAction: OnyxEntry>, reportOrID: OnyxEntry | string, isLHNPreview?: boolean, ) => string; - getForReportAction: (reportID: string | undefined, reportAction: OnyxEntry) => string; + getForReportAction?: (reportID: string | undefined, reportAction: OnyxEntry) => string; - getTransactionsWithReceipts: (iouReportID: string | undefined) => OnyxTypes.Transaction[]; + getTransactionsWithReceipts?: (iouReportID: string | undefined) => OnyxTypes.Transaction[]; - isCurrentUserTheOnlyParticipant: (participantAccountIDs?: number[]) => boolean; + isCurrentUserTheOnlyParticipant?: (participantAccountIDs?: number[]) => boolean; - clearError: (transactionID: string) => void; + clearError?: (transactionID: string) => void; - clearAllRelatedReportActionErrors: (reportID: string, reportAction: OnyxTypes.ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) => void; + clearAllRelatedReportActionErrors?: (reportID: string, reportAction: OnyxTypes.ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) => void; - dismissTrackExpenseActionableWhisper: (reportID: string, reportAction: OnyxEntry) => void; + dismissTrackExpenseActionableWhisper?: (reportID: string, reportAction: OnyxEntry) => void; }; function PureReportActionItem({ @@ -260,13 +260,14 @@ function PureReportActionItem({ toggleEmojiReaction = () => {}, createDraftTransactionAndNavigateToParticipantSelector = () => {}, resolveActionableReportMentionWhisper = () => {}, - isClosedExpenseReportWithNoExpenses, - isCurrentUserTheOnlyParticipant, - getIndicatedMissingPaymentMethod, - isReimbursementDeQueuedAction, - getReimbursementDeQueuedActionMessage, - getForReportAction, - getTransactionsWithReceipts, + isClosedExpenseReportWithNoExpenses = () => false, + isCurrentUserTheOnlyParticipant = () => false, + getIndicatedMissingPaymentMethod = () => undefined, + isReimbursementDeQueuedAction = (reportAction: OnyxEntry): reportAction is OnyxTypes.ReportAction => + false, + getReimbursementDeQueuedActionMessage = () => '', + getForReportAction = () => '', + getTransactionsWithReceipts = () => [], clearError = () => {}, clearAllRelatedReportActionErrors = () => {}, dismissTrackExpenseActionableWhisper = () => {}, @@ -698,7 +699,7 @@ function PureReportActionItem({ const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[linkedReport?.ownerAccountID ?? -1] ?? {}); const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; - const missingPaymentMethod = getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); + const missingPaymentMethod = getIndicatedMissingPaymentMethod?.(userWallet, linkedReport?.reportID ?? '-1', action); children = ( Date: Tue, 26 Nov 2024 12:41:44 +0700 Subject: [PATCH 12/25] Add comment for new props --- src/pages/home/report/PureReportActionItem.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 46802dde2205..eda0d7965172 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -174,12 +174,16 @@ type PureReportActionItemProps = { /** ID of the original report from which the given reportAction is first created */ originalReportID?: string; + /** Function to deletes the draft for a comment report action. */ deleteReportActionDraft?: (reportID: string, action: OnyxTypes.ReportAction) => void; + /** Whether the room is archived */ isArchivedRoom?: boolean; + /** Whether the room is a chronos report */ isChronosReport?: boolean; + /** Function to toggle emoji reaction */ toggleEmojiReaction?: ( reportID: string, reportAction: OnyxTypes.ReportAction, @@ -189,38 +193,51 @@ type PureReportActionItemProps = { ignoreSkinToneOnCompare: boolean | undefined, ) => void; + /** Whether the user has a payment card added to its account. */ doesUserHavePaymentCardAdded?: boolean | undefined; + /** Function to create a draft transaction and navigate to participant selector */ createDraftTransactionAndNavigateToParticipantSelector?: (transactionID: string, reportID: string, actionName: IOUAction, reportActionID: string) => void; + /** Function to resolve actionable report mention whisper */ resolveActionableReportMentionWhisper?: ( reportId: string, reportAction: OnyxEntry, resolution: ValueOf, ) => void; + /** Whether the provided report is a closed expense report with no expenses */ isClosedExpenseReportWithNoExpenses?: (report: OnyxEntry) => boolean; + /** What missing payment method does this report action indicate, if any? */ getIndicatedMissingPaymentMethod?: (userWallet: OnyxEntry, reportId: string, reportAction: OnyxTypes.ReportAction) => MissingPaymentMethod | undefined; + /** Whether the provided report action is a reimbursement de-queued action */ isReimbursementDeQueuedAction?: (reportAction: OnyxEntry) => reportAction is OnyxTypes.ReportAction; + /** Returns the preview message for `REIMBURSEMENT_DEQUEUED` action */ getReimbursementDeQueuedActionMessage?: ( reportAction: OnyxEntry>, reportOrID: OnyxEntry | string, isLHNPreview?: boolean, ) => string; + /** Get the report action message when expense has been modified. */ getForReportAction?: (reportID: string | undefined, reportAction: OnyxEntry) => string; + /** Gets all transactions on an IOU report with a receipt */ getTransactionsWithReceipts?: (iouReportID: string | undefined) => OnyxTypes.Transaction[]; + /** Whether the current user is the only participant in the report */ isCurrentUserTheOnlyParticipant?: (participantAccountIDs?: number[]) => boolean; + /** Function to clear an error from a transaction */ clearError?: (transactionID: string) => void; + /** Function to clear all errors from a report action */ clearAllRelatedReportActionErrors?: (reportID: string, reportAction: OnyxTypes.ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) => void; + /** Function to dismiss the actionable whisper for tracking expenses */ dismissTrackExpenseActionableWhisper?: (reportID: string, reportAction: OnyxEntry) => void; }; From 0a764e23c4f75c206f5a13dfa5e84ad387368e15 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 12:55:48 +0700 Subject: [PATCH 13/25] complete missing props --- src/pages/home/report/PureReportActionItem.tsx | 2 +- src/pages/home/report/ReportActionItem.tsx | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index eda0d7965172..b3cb9aa809bf 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -273,8 +273,8 @@ function PureReportActionItem({ deleteReportActionDraft = () => {}, isArchivedRoom, isChronosReport, - doesUserHavePaymentCardAdded, toggleEmojiReaction = () => {}, + doesUserHavePaymentCardAdded, createDraftTransactionAndNavigateToParticipantSelector = () => {}, resolveActionableReportMentionWhisper = () => {}, isClosedExpenseReportWithNoExpenses = () => false, diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index a3597b1712a8..174a4f544549 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -7,6 +7,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import {doesUserHavePaymentCardAdded} from '@libs/SubscriptionUtils'; import * as Report from '@userActions/Report'; import * as ReportActions from '@userActions/ReportActions'; +import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PureReportActionItemProps} from './PureReportActionItem'; @@ -65,11 +66,15 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) createDraftTransactionAndNavigateToParticipantSelector={ReportUtils.createDraftTransactionAndNavigateToParticipantSelector} resolveActionableReportMentionWhisper={Report.resolveActionableReportMentionWhisper} isClosedExpenseReportWithNoExpenses={ReportUtils.isClosedExpenseReportWithNoExpenses} + isCurrentUserTheOnlyParticipant={ReportUtils.isCurrentUserTheOnlyParticipant} + getIndicatedMissingPaymentMethod={ReportUtils.getIndicatedMissingPaymentMethod} isReimbursementDeQueuedAction={ReportActionsUtils.isReimbursementDeQueuedAction} + getReimbursementDeQueuedActionMessage={ReportUtils.getReimbursementDeQueuedActionMessage} getForReportAction={ModifiedExpenseMessage.getForReportAction} getTransactionsWithReceipts={ReportUtils.getTransactionsWithReceipts} - isCurrentUserTheOnlyParticipant={ReportUtils.isCurrentUserTheOnlyParticipant} + clearError={Transaction.clearError} clearAllRelatedReportActionErrors={ReportActions.clearAllRelatedReportActionErrors} + dismissTrackExpenseActionableWhisper={Report.dismissTrackExpenseActionableWhisper} /> ); } From cc1cd38e34d04036bb392956c11ee0f863d034f3 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 13:18:09 +0700 Subject: [PATCH 14/25] extract resolveActionableMentionWhisper --- src/pages/home/report/PureReportActionItem.tsx | 13 +++++++++++-- src/pages/home/report/ReportActionItem.tsx | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index b3cb9aa809bf..eddcea1ac021 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -206,6 +206,13 @@ type PureReportActionItemProps = { resolution: ValueOf, ) => void; + /** Function to resolve actionable mention whisper */ + resolveActionableMentionWhisper?: ( + reportId: string, + reportAction: OnyxEntry, + resolution: ValueOf, + ) => void; + /** Whether the provided report is a closed expense report with no expenses */ isClosedExpenseReportWithNoExpenses?: (report: OnyxEntry) => boolean; @@ -277,6 +284,7 @@ function PureReportActionItem({ doesUserHavePaymentCardAdded, createDraftTransactionAndNavigateToParticipantSelector = () => {}, resolveActionableReportMentionWhisper = () => {}, + resolveActionableMentionWhisper = () => {}, isClosedExpenseReportWithNoExpenses = () => false, isCurrentUserTheOnlyParticipant = () => false, getIndicatedMissingPaymentMethod = () => undefined, @@ -609,13 +617,13 @@ function PureReportActionItem({ { text: 'actionableMentionWhisperOptions.invite', key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE}`, - onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), + onPress: () => resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.INVITE), isPrimary: true, }, { text: 'actionableMentionWhisperOptions.nothing', key: `${action.reportActionID}-actionableMentionWhisper-${CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING}`, - onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), + onPress: () => resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), }, ]; }, [ @@ -626,6 +634,7 @@ function PureReportActionItem({ dismissTrackExpenseActionableWhisper, doesUserHavePaymentCardAdded, resolveActionableReportMentionWhisper, + resolveActionableMentionWhisper, ]); /** diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 174a4f544549..9b886e344bf4 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -65,6 +65,7 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) doesUserHavePaymentCardAdded={doesUserHavePaymentCardAdded()} createDraftTransactionAndNavigateToParticipantSelector={ReportUtils.createDraftTransactionAndNavigateToParticipantSelector} resolveActionableReportMentionWhisper={Report.resolveActionableReportMentionWhisper} + resolveActionableMentionWhisper={Report.resolveActionableMentionWhisper} isClosedExpenseReportWithNoExpenses={ReportUtils.isClosedExpenseReportWithNoExpenses} isCurrentUserTheOnlyParticipant={ReportUtils.isCurrentUserTheOnlyParticipant} getIndicatedMissingPaymentMethod={ReportUtils.getIndicatedMissingPaymentMethod} From d36b949b50a887d9f48544e04d7ab7c544c69b65 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 13:37:11 +0700 Subject: [PATCH 15/25] adjust memo --- src/pages/home/report/PureReportActionItem.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index eddcea1ac021..86fcf84806f8 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -1206,10 +1206,17 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { lodashIsEqual(prevParentReportAction, nextParentReportAction) && prevProps.draftMessage === nextProps.draftMessage && prevProps.iouReport?.reportID === nextProps.iouReport?.reportID && - prevProps.emojiReactions === nextProps.emojiReactions && - prevProps.userWallet === nextProps.userWallet && - prevProps.linkedTransactionRouteError === nextProps.linkedTransactionRouteError && + lodashIsEqual(prevProps.emojiReactions, nextProps.emojiReactions) && + lodashIsEqual(prevProps.userWallet, nextProps.userWallet) && + lodashIsEqual(prevProps.linkedTransactionRouteError, nextProps.linkedTransactionRouteError) && + lodashIsEqual(prevProps.reportNameValuePairs, nextProps.reportNameValuePairs) && prevProps.isUserValidated === nextProps.isUserValidated && - prevProps.parentReport?.reportID === nextProps.parentReport?.reportID + prevProps.parentReport?.reportID === nextProps.parentReport?.reportID && + lodashIsEqual(prevProps.personalDetails, nextProps.personalDetails) && + lodashIsEqual(prevProps.blockedFromConcierge, nextProps.blockedFromConcierge) && + prevProps.originalReportID === nextProps.originalReportID && + prevProps.isArchivedRoom === nextProps.isArchivedRoom && + prevProps.isChronosReport === nextProps.isChronosReport && + prevProps.doesUserHavePaymentCardAdded === nextProps.doesUserHavePaymentCardAdded ); }); From 9ffeab702d77b8b1336c8c25c8aedd73a896087b Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 3 Dec 2024 14:28:40 +0700 Subject: [PATCH 16/25] Resolve conflict --- src/pages/home/report/PureReportActionItem.tsx | 15 +++++++-------- src/pages/home/report/ReportActionItem.tsx | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 86fcf84806f8..cc8e01114327 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -193,9 +193,6 @@ type PureReportActionItemProps = { ignoreSkinToneOnCompare: boolean | undefined, ) => void; - /** Whether the user has a payment card added to its account. */ - doesUserHavePaymentCardAdded?: boolean | undefined; - /** Function to create a draft transaction and navigate to participant selector */ createDraftTransactionAndNavigateToParticipantSelector?: (transactionID: string, reportID: string, actionName: IOUAction, reportActionID: string) => void; @@ -246,6 +243,9 @@ type PureReportActionItemProps = { /** Function to dismiss the actionable whisper for tracking expenses */ dismissTrackExpenseActionableWhisper?: (reportID: string, reportAction: OnyxEntry) => void; + + /** User payment card ID */ + userBillingFundID?: number; }; function PureReportActionItem({ @@ -281,7 +281,6 @@ function PureReportActionItem({ isArchivedRoom, isChronosReport, toggleEmojiReaction = () => {}, - doesUserHavePaymentCardAdded, createDraftTransactionAndNavigateToParticipantSelector = () => {}, resolveActionableReportMentionWhisper = () => {}, resolveActionableMentionWhisper = () => {}, @@ -296,6 +295,7 @@ function PureReportActionItem({ clearError = () => {}, clearAllRelatedReportActionErrors = () => {}, dismissTrackExpenseActionableWhisper = () => {}, + userBillingFundID, }: PureReportActionItemProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -525,7 +525,7 @@ function PureReportActionItem({ const mentionReportContextValue = useMemo(() => ({currentReportID: report?.reportID ?? '-1'}), [report?.reportID]); const actionableItemButtons: ActionableItem[] = useMemo(() => { - if (ReportActionsUtils.isActionableAddPaymentCard(action) && !doesUserHavePaymentCardAdded && shouldRenderAddPaymentCard()) { + if (ReportActionsUtils.isActionableAddPaymentCard(action) && userBillingFundID === undefined && shouldRenderAddPaymentCard()) { return [ { text: 'subscription.cardSection.addCardButton', @@ -630,9 +630,9 @@ function PureReportActionItem({ action, isActionableWhisper, reportID, + userBillingFundID, createDraftTransactionAndNavigateToParticipantSelector, dismissTrackExpenseActionableWhisper, - doesUserHavePaymentCardAdded, resolveActionableReportMentionWhisper, resolveActionableMentionWhisper, ]); @@ -1216,7 +1216,6 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { lodashIsEqual(prevProps.blockedFromConcierge, nextProps.blockedFromConcierge) && prevProps.originalReportID === nextProps.originalReportID && prevProps.isArchivedRoom === nextProps.isArchivedRoom && - prevProps.isChronosReport === nextProps.isChronosReport && - prevProps.doesUserHavePaymentCardAdded === nextProps.doesUserHavePaymentCardAdded + prevProps.isChronosReport === nextProps.isChronosReport ); }); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index c68dd465879f..161541132757 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -4,7 +4,6 @@ import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvi import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import {doesUserHavePaymentCardAdded} from '@libs/SubscriptionUtils'; import * as Report from '@userActions/Report'; import * as ReportActions from '@userActions/ReportActions'; import * as Transaction from '@userActions/Transaction'; @@ -39,6 +38,7 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const blockedFromConcierge = useBlockedFromConcierge(); + const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); return ( ); } From f7fe4da3b62535bddedda6122444580b5ef6a0ec Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 3 Dec 2024 18:23:11 +0700 Subject: [PATCH 17/25] adjust some functions to accept reportobject --- src/libs/ReportUtils.ts | 12 ++++++------ src/pages/home/report/PureReportActionItem.tsx | 17 +++-------------- src/pages/home/report/ReportActionItem.tsx | 5 +++-- src/types/onyx/SearchResults.ts | 5 ++++- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index abc1940fd651..f28e448511b0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1483,10 +1483,10 @@ function isArchivedRoom(report: OnyxInputOrEntry | SearchReport, reportN /** * Whether the report with the provided reportID is an archived room */ -function isArchivedRoomWithID(reportID?: string) { +function isArchivedRoomWithID(reportOrID?: string | SearchReport) { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID || -1}`]; - return isArchivedRoom(report, getReportNameValuePairs(reportID)); + const report = typeof reportOrID === 'string' ? ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID || -1}`] : reportOrID; + return isArchivedRoom(report); } /** @@ -6733,14 +6733,14 @@ function getAllPolicyReports(policyID: string): Array> { /** * Returns true if Chronos is one of the chat participants (1:1) */ -function chatIncludesChronos(report: OnyxInputOrEntry): boolean { +function chatIncludesChronos(report: OnyxInputOrEntry | SearchReport): boolean { const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); return participantAccountIDs.includes(CONST.ACCOUNT_ID.CHRONOS); } -function chatIncludesChronosWithID(reportID?: string): boolean { +function chatIncludesChronosWithID(reportOrID?: string | SearchReport): boolean { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID || -1}`]; + const report = typeof reportOrID === 'string' ? ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID || -1}`] : reportOrID; return chatIncludesChronos(report); } diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index cc8e01114327..0d200b478e7f 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -150,9 +150,6 @@ type PureReportActionItemProps = { /** All the emoji reactions for the report action. */ emojiReactions?: OnyxTypes.ReportActionReactions; - /** User's Expensify Wallet */ - userWallet?: OnyxTypes.UserWallet; - /** Linked transaction route error */ linkedTransactionRouteError?: Errors; @@ -214,10 +211,7 @@ type PureReportActionItemProps = { isClosedExpenseReportWithNoExpenses?: (report: OnyxEntry) => boolean; /** What missing payment method does this report action indicate, if any? */ - getIndicatedMissingPaymentMethod?: (userWallet: OnyxEntry, reportId: string, reportAction: OnyxTypes.ReportAction) => MissingPaymentMethod | undefined; - - /** Whether the provided report action is a reimbursement de-queued action */ - isReimbursementDeQueuedAction?: (reportAction: OnyxEntry) => reportAction is OnyxTypes.ReportAction; + missingPaymentMethod?: MissingPaymentMethod | undefined; /** Returns the preview message for `REIMBURSEMENT_DEQUEUED` action */ getReimbursementDeQueuedActionMessage?: ( @@ -269,7 +263,6 @@ function PureReportActionItem({ draftMessage, iouReport, emojiReactions, - userWallet, linkedTransactionRouteError, reportNameValuePairs, isUserValidated, @@ -286,9 +279,7 @@ function PureReportActionItem({ resolveActionableMentionWhisper = () => {}, isClosedExpenseReportWithNoExpenses = () => false, isCurrentUserTheOnlyParticipant = () => false, - getIndicatedMissingPaymentMethod = () => undefined, - isReimbursementDeQueuedAction = (reportAction: OnyxEntry): reportAction is OnyxTypes.ReportAction => - false, + missingPaymentMethod, getReimbursementDeQueuedActionMessage = () => '', getForReportAction = () => '', getTransactionsWithReceipts = () => [], @@ -725,7 +716,6 @@ function PureReportActionItem({ const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[linkedReport?.ownerAccountID ?? -1] ?? {}); const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; - const missingPaymentMethod = getIndicatedMissingPaymentMethod?.(userWallet, linkedReport?.reportID ?? '-1', action); children = ( ); - } else if (isReimbursementDeQueuedAction(action)) { + } else if (ReportActionsUtils.isReimbursementDeQueuedAction(action)) { children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) { children = ; @@ -1207,7 +1197,6 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { prevProps.draftMessage === nextProps.draftMessage && prevProps.iouReport?.reportID === nextProps.iouReport?.reportID && lodashIsEqual(prevProps.emojiReactions, nextProps.emojiReactions) && - lodashIsEqual(prevProps.userWallet, nextProps.userWallet) && lodashIsEqual(prevProps.linkedTransactionRouteError, nextProps.linkedTransactionRouteError) && lodashIsEqual(prevProps.reportNameValuePairs, nextProps.reportNameValuePairs) && prevProps.isUserValidated === nextProps.isUserValidated && diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 161541132757..017dc3c4c361 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -39,6 +39,8 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const blockedFromConcierge = useBlockedFromConcierge(); const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); + const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; + const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod?.(userWallet, linkedReport?.reportID ?? '-1', action); return ( Date: Wed, 4 Dec 2024 13:23:42 +0700 Subject: [PATCH 18/25] Adjust functions for search result --- src/libs/ModifiedExpenseMessage.ts | 5 ++-- .../home/report/PureReportActionItem.tsx | 24 ++++++++----------- src/pages/home/report/ReportActionItem.tsx | 12 ++++++---- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index f5109cbea74b..cd978b9f2b4c 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -132,12 +132,13 @@ function getForDistanceRequest(newMerchant: string, oldMerchant: string, newAmou * ModifiedExpense::getNewDotComment in Web-Expensify should match this. * If we change this function be sure to update the backend as well. */ -function getForReportAction(reportID: string | undefined, reportAction: OnyxEntry): string { +function getForReportAction(reportOrID: string | undefined, reportAction: OnyxEntry): string { if (!ReportActionsUtils.isModifiedExpenseAction(reportAction)) { return ''; } const reportActionOriginalMessage = ReportActionsUtils.getOriginalMessage(reportAction); - const policyID = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]?.policyID ?? '-1'; + const report = typeof reportOrID === 'string' ? ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] : reportOrID; + const policyID = report?.policyID ?? '-1'; const removalFragments: string[] = []; const setFragments: string[] = []; diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 0d200b478e7f..93b41a3db297 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -208,20 +208,16 @@ type PureReportActionItemProps = { ) => void; /** Whether the provided report is a closed expense report with no expenses */ - isClosedExpenseReportWithNoExpenses?: (report: OnyxEntry) => boolean; + isClosedExpenseReportWithNoExpenses?: boolean; /** What missing payment method does this report action indicate, if any? */ missingPaymentMethod?: MissingPaymentMethod | undefined; /** Returns the preview message for `REIMBURSEMENT_DEQUEUED` action */ - getReimbursementDeQueuedActionMessage?: ( - reportAction: OnyxEntry>, - reportOrID: OnyxEntry | string, - isLHNPreview?: boolean, - ) => string; + reimbursementDeQueuedActionMessage?: string; - /** Get the report action message when expense has been modified. */ - getForReportAction?: (reportID: string | undefined, reportAction: OnyxEntry) => string; + /** The report action message when expense has been modified. */ + forReportAction?: string; /** Gets all transactions on an IOU report with a receipt */ getTransactionsWithReceipts?: (iouReportID: string | undefined) => OnyxTypes.Transaction[]; @@ -277,11 +273,11 @@ function PureReportActionItem({ createDraftTransactionAndNavigateToParticipantSelector = () => {}, resolveActionableReportMentionWhisper = () => {}, resolveActionableMentionWhisper = () => {}, - isClosedExpenseReportWithNoExpenses = () => false, + isClosedExpenseReportWithNoExpenses, isCurrentUserTheOnlyParticipant = () => false, missingPaymentMethod, - getReimbursementDeQueuedActionMessage = () => '', - getForReportAction = () => '', + reimbursementDeQueuedActionMessage = '', + forReportAction = '', getTransactionsWithReceipts = () => [], clearError = () => {}, clearAllRelatedReportActionErrors = () => {}, @@ -677,7 +673,7 @@ function PureReportActionItem({ /> ); } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW) { - children = isClosedExpenseReportWithNoExpenses(iouReport) ? ( + children = isClosedExpenseReportWithNoExpenses ? ( ${translate('parentReportAction.deletedReport')}`} /> ) : ( ); } else if (ReportActionsUtils.isReimbursementDeQueuedAction(action)) { - children = ; + children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) { - children = ; + children = ; } else if ( ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED) || ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 017dc3c4c361..2ed9c78c6dea 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -1,5 +1,6 @@ import React, {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import {useBlockedFromConcierge, usePersonalDetails} from '@components/OnyxProvider'; import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; @@ -9,6 +10,7 @@ import * as ReportActions from '@userActions/ReportActions'; import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReportAction} from '@src/types/onyx'; import type {PureReportActionItemProps} from './PureReportActionItem'; import PureReportActionItem from './PureReportActionItem'; @@ -51,7 +53,6 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) draftMessage={draftMessage} iouReport={iouReport} emojiReactions={emojiReactions} - userWallet={userWallet} linkedTransactionRouteError={linkedTransactionRouteError} reportNameValuePairs={reportNameValuePairs} isUserValidated={isUserValidated} @@ -66,11 +67,14 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) createDraftTransactionAndNavigateToParticipantSelector={ReportUtils.createDraftTransactionAndNavigateToParticipantSelector} resolveActionableReportMentionWhisper={Report.resolveActionableReportMentionWhisper} resolveActionableMentionWhisper={Report.resolveActionableMentionWhisper} - isClosedExpenseReportWithNoExpenses={ReportUtils.isClosedExpenseReportWithNoExpenses} + isClosedExpenseReportWithNoExpenses={ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport)} isCurrentUserTheOnlyParticipant={ReportUtils.isCurrentUserTheOnlyParticipant} missingPaymentMethod={missingPaymentMethod} - getReimbursementDeQueuedActionMessage={ReportUtils.getReimbursementDeQueuedActionMessage} - getForReportAction={ModifiedExpenseMessage.getForReportAction} + reimbursementDeQueuedActionMessage={ReportUtils.getReimbursementDeQueuedActionMessage( + action as OnyxEntry>, + report, + )} + forReportAction={ModifiedExpenseMessage.getForReportAction(reportID, action)} getTransactionsWithReceipts={ReportUtils.getTransactionsWithReceipts} clearError={Transaction.clearError} clearAllRelatedReportActionErrors={ReportActions.clearAllRelatedReportActionErrors} From 978fed8713b7f90a60c787e91f67c15dbb919d91 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 5 Dec 2024 12:59:05 +0700 Subject: [PATCH 19/25] Adjust function types --- src/libs/ModifiedExpenseMessage.ts | 3 ++- src/libs/ReportUtils.ts | 8 ++++---- src/pages/home/report/PureReportActionItem.tsx | 5 ++++- src/pages/home/report/ReportActionItem.tsx | 1 + 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/libs/ModifiedExpenseMessage.ts b/src/libs/ModifiedExpenseMessage.ts index cd978b9f2b4c..6b3bdd4d83ee 100644 --- a/src/libs/ModifiedExpenseMessage.ts +++ b/src/libs/ModifiedExpenseMessage.ts @@ -3,6 +3,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PolicyTagLists, ReportAction} from '@src/types/onyx'; +import type {SearchReport} from '@src/types/onyx/SearchResults'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import * as Localize from './Localize'; @@ -132,7 +133,7 @@ function getForDistanceRequest(newMerchant: string, oldMerchant: string, newAmou * ModifiedExpense::getNewDotComment in Web-Expensify should match this. * If we change this function be sure to update the backend as well. */ -function getForReportAction(reportOrID: string | undefined, reportAction: OnyxEntry): string { +function getForReportAction(reportOrID: string | SearchReport | undefined, reportAction: OnyxEntry): string { if (!ReportActionsUtils.isModifiedExpenseAction(reportAction)) { return ''; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7bdd4dec4334..381f2660ed5d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2571,7 +2571,7 @@ function getDeletedParentActionMessageForChatReport(reportAction: OnyxEntry>, - reportOrID: OnyxEntry | string, + reportOrID: OnyxEntry | string | SearchReport, shouldUseShortDisplayName = true, ): string { const report = typeof reportOrID === 'string' ? ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] : reportOrID; @@ -2592,7 +2592,7 @@ function getReimbursementQueuedActionMessage( */ function getReimbursementDeQueuedActionMessage( reportAction: OnyxEntry>, - reportOrID: OnyxEntry | string, + reportOrID: OnyxEntry | string | SearchReport, isLHNPreview = false, ): string { const report = typeof reportOrID === 'string' ? ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] : reportOrID; @@ -4785,7 +4785,7 @@ function getIOUApprovedMessage(reportAction: ReportAction, reportOrID: OnyxInputOrEntry | string) { +function getReportAutomaticallyForwardedMessage(reportAction: ReportAction, reportOrID: OnyxInputOrEntry | string | SearchReport) { const expenseReport = typeof reportOrID === 'string' ? getReport(reportOrID) : reportOrID; const originalMessage = ReportActionsUtils.getOriginalMessage(reportAction) as OriginalMessageIOU; let formattedAmount; @@ -4804,7 +4804,7 @@ function getReportAutomaticallyForwardedMessage(reportAction: ReportAction, reportOrID: OnyxInputOrEntry | string) { +function getIOUForwardedMessage(reportAction: ReportAction, reportOrID: OnyxInputOrEntry | string | SearchReport) { const expenseReport = typeof reportOrID === 'string' ? getReport(reportOrID) : reportOrID; const originalMessage = ReportActionsUtils.getOriginalMessage(reportAction) as OriginalMessageIOU; let formattedAmount; diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 93b41a3db297..f3471c5bcc97 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -236,6 +236,8 @@ type PureReportActionItemProps = { /** User payment card ID */ userBillingFundID?: number; + + reportAutomaticallyForwardedMessage: string; }; function PureReportActionItem({ @@ -283,6 +285,7 @@ function PureReportActionItem({ clearAllRelatedReportActionErrors = () => {}, dismissTrackExpenseActionableWhisper = () => {}, userBillingFundID, + reportAutomaticallyForwardedMessage, }: PureReportActionItemProps) { const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); @@ -787,7 +790,7 @@ function PureReportActionItem({ if (wasAutoForwarded) { children = ( - ${ReportUtils.getReportAutomaticallyForwardedMessage(action, reportID)}`} /> + ${reportAutomaticallyForwardedMessage}`} /> ); } else { diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 2ed9c78c6dea..1b31d47a3cee 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -80,6 +80,7 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) clearAllRelatedReportActionErrors={ReportActions.clearAllRelatedReportActionErrors} dismissTrackExpenseActionableWhisper={Report.dismissTrackExpenseActionableWhisper} userBillingFundID={userBillingFundID} + reportAutomaticallyForwardedMessage={ReportUtils.getReportAutomaticallyForwardedMessage(action as ReportAction, reportID)} /> ); } From f8bc8beb550bdbeb2e941cb06141fae8b7fa5aba Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 5 Dec 2024 13:08:52 +0700 Subject: [PATCH 20/25] Adjust memo, add comment --- src/pages/home/report/PureReportActionItem.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index f3471c5bcc97..640be78f2f25 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -237,6 +237,7 @@ type PureReportActionItemProps = { /** User payment card ID */ userBillingFundID?: number; + /** A message related to a report action that has been automatically forwarded */ reportAutomaticallyForwardedMessage: string; }; @@ -1204,6 +1205,12 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { lodashIsEqual(prevProps.blockedFromConcierge, nextProps.blockedFromConcierge) && prevProps.originalReportID === nextProps.originalReportID && prevProps.isArchivedRoom === nextProps.isArchivedRoom && - prevProps.isChronosReport === nextProps.isChronosReport + prevProps.isChronosReport === nextProps.isChronosReport && + prevProps.isClosedExpenseReportWithNoExpenses === nextProps.isClosedExpenseReportWithNoExpenses && + lodashIsEqual(prevProps.missingPaymentMethod, nextProps.missingPaymentMethod) && + prevProps.reimbursementDeQueuedActionMessage === nextProps.reimbursementDeQueuedActionMessage && + prevProps.forReportAction === nextProps.forReportAction && + prevProps.userBillingFundID === nextProps.userBillingFundID && + prevProps.reportAutomaticallyForwardedMessage === nextProps.reportAutomaticallyForwardedMessage ); }); From 265d361d7ee05f89139e5c7ad1335595f25848e9 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 5 Dec 2024 13:26:31 +0700 Subject: [PATCH 21/25] make reportAutomaticallyForwardedMessage optional --- src/pages/home/report/PureReportActionItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 640be78f2f25..9bd89d6bbb7c 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -238,7 +238,7 @@ type PureReportActionItemProps = { userBillingFundID?: number; /** A message related to a report action that has been automatically forwarded */ - reportAutomaticallyForwardedMessage: string; + reportAutomaticallyForwardedMessage?: string; }; function PureReportActionItem({ From b4973edf940c47b681f6936bbbd39e23e0366d07 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 6 Dec 2024 07:32:25 +0700 Subject: [PATCH 22/25] refactor prop name --- src/pages/home/report/PureReportActionItem.tsx | 8 ++++---- src/pages/home/report/ReportActionItem.tsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 9bd89d6bbb7c..44efc69c5119 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -217,7 +217,7 @@ type PureReportActionItemProps = { reimbursementDeQueuedActionMessage?: string; /** The report action message when expense has been modified. */ - forReportAction?: string; + modifiedExpenseMessage?: string; /** Gets all transactions on an IOU report with a receipt */ getTransactionsWithReceipts?: (iouReportID: string | undefined) => OnyxTypes.Transaction[]; @@ -280,7 +280,7 @@ function PureReportActionItem({ isCurrentUserTheOnlyParticipant = () => false, missingPaymentMethod, reimbursementDeQueuedActionMessage = '', - forReportAction = '', + modifiedExpenseMessage = '', getTransactionsWithReceipts = () => [], clearError = () => {}, clearAllRelatedReportActionErrors = () => {}, @@ -758,7 +758,7 @@ function PureReportActionItem({ } else if (ReportActionsUtils.isReimbursementDeQueuedAction(action)) { children = ; } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) { - children = ; + children = ; } else if ( ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED) || ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED) @@ -1209,7 +1209,7 @@ export default memo(PureReportActionItem, (prevProps, nextProps) => { prevProps.isClosedExpenseReportWithNoExpenses === nextProps.isClosedExpenseReportWithNoExpenses && lodashIsEqual(prevProps.missingPaymentMethod, nextProps.missingPaymentMethod) && prevProps.reimbursementDeQueuedActionMessage === nextProps.reimbursementDeQueuedActionMessage && - prevProps.forReportAction === nextProps.forReportAction && + prevProps.modifiedExpenseMessage === nextProps.modifiedExpenseMessage && prevProps.userBillingFundID === nextProps.userBillingFundID && prevProps.reportAutomaticallyForwardedMessage === nextProps.reportAutomaticallyForwardedMessage ); diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 1b31d47a3cee..67c9445c0104 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -74,7 +74,7 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) action as OnyxEntry>, report, )} - forReportAction={ModifiedExpenseMessage.getForReportAction(reportID, action)} + modifiedExpenseMessage={ModifiedExpenseMessage.getForReportAction(reportID, action)} getTransactionsWithReceipts={ReportUtils.getTransactionsWithReceipts} clearError={Transaction.clearError} clearAllRelatedReportActionErrors={ReportActions.clearAllRelatedReportActionErrors} From 2f65ece9c71700194d9a1529e650fc39e8ff377c Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 11 Dec 2024 08:54:28 +0700 Subject: [PATCH 23/25] Resolve conflict --- src/pages/home/report/PureReportActionItem.tsx | 4 ++-- src/pages/home/report/ReportActionItem.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 44efc69c5119..6167ea9244c6 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -713,7 +713,7 @@ function PureReportActionItem({ ); } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[linkedReport?.ownerAccountID ?? -1] ?? {}); + const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[linkedReport?.ownerAccountID ?? -1]); const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; children = ( @@ -1121,7 +1121,7 @@ function PureReportActionItem({ pendingAction={ draftMessage !== undefined ? undefined : action.pendingAction ?? (action.isOptimisticAction ? CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD : undefined) } - shouldHideOnDelete={!ReportActionsUtils.isThreadParentMessage(action, reportID)} + shouldHideOnDelete={!isThreadReportParentAction} errors={linkedTransactionRouteError ?? ErrorUtils.getLatestErrorMessageField(action as ErrorUtils.OnyxDataWithErrors)} errorRowStyles={[styles.ml10, styles.mr2]} needsOffscreenAlphaCompositing={ReportActionsUtils.isMoneyRequestAction(action)} diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index bda1e463f0ec..6eacf397baa4 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -8,7 +8,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as Report from '@userActions/Report'; import * as ReportActions from '@userActions/ReportActions'; import * as Transaction from '@userActions/Transaction'; -import CONST from '@src/CONST'; +import type CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReportAction} from '@src/types/onyx'; import type {PureReportActionItemProps} from './PureReportActionItem'; @@ -38,7 +38,7 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); - const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; + const personalDetails = usePersonalDetails(); const blockedFromConcierge = useBlockedFromConcierge(); const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; @@ -85,4 +85,4 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) ); } -export default ReportActionItem; \ No newline at end of file +export default ReportActionItem; From 0a58209682064db409ca497c25ddb0614610e856 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 12 Dec 2024 08:03:28 +0700 Subject: [PATCH 24/25] Resolve conflict --- src/types/onyx/SearchResults.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts index 5d3a10aeaed2..6267f326f0fc 100644 --- a/src/types/onyx/SearchResults.ts +++ b/src/types/onyx/SearchResults.ts @@ -6,8 +6,8 @@ import type TransactionListItem from '@components/SelectionList/Search/Transacti import type {ReportActionListItemType, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types'; import type CONST from '@src/CONST'; import type ONYXKEYS from '@src/ONYXKEYS'; -import type {InvoiceReceiver, Participants} from './Report'; import type {ACHAccount, ApprovalRule, ExpenseRule} from './Policy'; +import type {InvoiceReceiver, Participants} from './Report'; import type ReportActionName from './ReportActionName'; import type ReportNameValuePairs from './ReportNameValuePairs'; From a5323be0fccf292cd34d3c1f1bf66b0ce6809c99 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 13 Dec 2024 08:30:58 +0700 Subject: [PATCH 25/25] Resolve conflict add comment to PureReportActionItem --- src/pages/home/report/PureReportActionItem.tsx | 8 +++++++- src/pages/home/report/ReportActionItem.tsx | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 6167ea9244c6..34dd2a9d1350 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -42,6 +42,7 @@ import ControlSelection from '@libs/ControlSelection'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as ErrorUtils from '@libs/ErrorUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; +import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; @@ -241,6 +242,11 @@ type PureReportActionItemProps = { reportAutomaticallyForwardedMessage?: string; }; +/** + * This is a pure version of ReportActionItem, used in ReportActionList and Search result chat list items. + * Since the search result has a separate Onyx key under the 'snapshot_' prefix, we should not connect this component with Onyx. + * Instead, pass all Onyx read/write operations as props. + */ function PureReportActionItem({ action, report, @@ -713,7 +719,7 @@ function PureReportActionItem({ ); } else if (ReportActionsUtils.isReimbursementQueuedAction(action)) { const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[linkedReport?.ownerAccountID ?? -1]); + const submitterDisplayName = LocalePhoneNumber.formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(personalDetails?.[linkedReport?.ownerAccountID ?? -1])); const paymentType = ReportActionsUtils.getOriginalMessage(action)?.paymentType ?? ''; children = ( diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 6eacf397baa4..1887bf9d348a 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -42,7 +42,7 @@ function ReportActionItem({action, report, ...props}: PureReportActionItemProps) const blockedFromConcierge = useBlockedFromConcierge(); const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); const linkedReport = ReportUtils.isChatThread(report) ? parentReport : report; - const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod?.(userWallet, linkedReport?.reportID ?? '-1', action); + const missingPaymentMethod = ReportUtils.getIndicatedMissingPaymentMethod(userWallet, linkedReport?.reportID ?? '-1', action); return (