From e29a5b38edd745fbf8355abd4529c6aa3442a2df Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Wed, 16 Apr 2025 14:37:33 +0200 Subject: [PATCH 1/4] Display splits as separate expenses in group chat --- .../TransactionPreview/index.tsx | 7 +++- .../home/report/PureReportActionItem.tsx | 41 ++++++++++++++----- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/components/ReportActionItem/TransactionPreview/index.tsx b/src/components/ReportActionItem/TransactionPreview/index.tsx index 16e44f9fb63b..bd64111278ac 100644 --- a/src/components/ReportActionItem/TransactionPreview/index.tsx +++ b/src/components/ReportActionItem/TransactionPreview/index.tsx @@ -21,7 +21,12 @@ import TransactionPreviewContent from './TransactionPreviewContent'; import type {TransactionPreviewProps} from './types'; const getOriginalTransactionIfBillIsSplit = (transaction: OnyxEntry) => { - const {originalTransactionID, source} = transaction?.comment ?? {}; + const {originalTransactionID, source, splits} = transaction?.comment ?? {}; + + // If splits property is defined in the transaction, it is actually an original transaction + if (splits && splits.length > 0) { + return {isSplit: true, originalTransaction: transaction}; + } if (!originalTransactionID || source !== CONST.IOU.TYPE.SPLIT) { return {isSplit: false, originalTransaction: transaction}; diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index f7939dc6cfb4..8955584c8508 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -111,6 +111,7 @@ import { isWhisperActionTargetedToOthers, useNewTableReportViewActionRenderConditionals, } from '@libs/ReportActionsUtils'; +import type {MissingPaymentMethod} from '@libs/ReportUtils'; import { canWriteInReport, chatIncludesConcierge, @@ -138,7 +139,6 @@ import { isTaskReport, shouldDisplayThreadReplies as shouldDisplayThreadRepliesUtils, } from '@libs/ReportUtils'; -import type {MissingPaymentMethod} from '@libs/ReportUtils'; import SelectionScraper from '@libs/SelectionScraper'; import shouldRenderAddPaymentCard from '@libs/shouldRenderAppPaymentCard'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; @@ -150,8 +150,8 @@ import {addComment, expandURLPreview} from '@userActions/Report'; import type {IgnoreDirection} from '@userActions/ReportActions'; import {isAnonymousUser, signOutAndRedirectToSignIn} from '@userActions/Session'; import {isBlockedFromConcierge} from '@userActions/User'; -import CONST from '@src/CONST'; import type {IOUAction} from '@src/CONST'; +import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {Errors} from '@src/types/onyx/OnyxCommon'; @@ -341,6 +341,12 @@ type PureReportActionItemProps = { const emptyHTML = ; const isEmptyHTML = ({props: {html}}: T): boolean => typeof html === 'string' && html.length === 0; +const simpleMoneyRequestActions: ReadonlyArray> = [ + CONST.IOU.REPORT_ACTION_TYPE.CREATE, + CONST.IOU.REPORT_ACTION_TYPE.SPLIT, + CONST.IOU.REPORT_ACTION_TYPE.TRACK, +]; + /** * 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. @@ -767,22 +773,23 @@ function PureReportActionItem({ */ const renderItemContent = (hovered = false, isWhisper = false, hasErrors = false): React.JSX.Element => { let children; + const moneyRequestOriginalMessage = isMoneyRequestAction(action) ? getOriginalMessage(action) : undefined; + const moneyRequestActionType = moneyRequestOriginalMessage?.type; // Show the MoneyRequestPreview for when expense is present if ( - isMoneyRequestAction(action) && - getOriginalMessage(action) && // For the pay flow, we only want to show MoneyRequestAction when sending money. When paying, we display a regular system message - (getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.CREATE || - getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT || - getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK) + !!moneyRequestActionType && + simpleMoneyRequestActions.includes(moneyRequestActionType) ) { + const isSplitInGroupChat = moneyRequestActionType === CONST.IOU.REPORT_ACTION_TYPE.SPLIT && report?.chatType === CONST.REPORT.CHAT_TYPE.GROUP; + const chatReportID = moneyRequestOriginalMessage?.IOUReportID ? report?.chatReportID : reportID; // There is no single iouReport for bill splits, so only 1:1 requests require an iouReportID - const iouReportID = getOriginalMessage(action)?.IOUReportID?.toString(); + const iouReportID = moneyRequestOriginalMessage?.IOUReportID?.toString(); children = ( {}} + onPreviewPressed={() => { + if (isSplitInGroupChat) { + Navigation.navigate(ROUTES.SPLIT_BILL_DETAILS.getRoute(chatReportID, action.reportActionID, Navigation.getReportRHPActiveRoute())); + return; + } + + if (!action.childReportID) { + return; + } + + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(action.childReportID)); + }} isTrackExpense={isTrackExpenseActionReportActionsUtils(action)} /> From e9684704057f113b50365fd76b2bc7b8d996e256 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Thu, 17 Apr 2025 09:59:07 +0200 Subject: [PATCH 2/4] Make useOnyx optionals to keep the logic and fix eslint error --- .../ReportActionItem/TransactionPreview/index.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/ReportActionItem/TransactionPreview/index.tsx b/src/components/ReportActionItem/TransactionPreview/index.tsx index bd64111278ac..772db41ca20e 100644 --- a/src/components/ReportActionItem/TransactionPreview/index.tsx +++ b/src/components/ReportActionItem/TransactionPreview/index.tsx @@ -40,17 +40,17 @@ const getOriginalTransactionIfBillIsSplit = (transaction: OnyxEntry function TransactionPreview(props: TransactionPreviewProps) { const {action, chatReportID, reportID, contextMenuAnchor, checkIfContextMenuActive = () => {}, shouldDisplayContextMenu, iouReportID, transactionID: transactionIDFromProps} = props; - const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`); + const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, {canBeMissing: true}); const route = useRoute>(); - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params?.threadReportID}`); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params?.threadReportID}`, {canBeMissing: true}); const isMoneyRequestAction = isMoneyRequestActionReportActionsUtils(action); const transactionID = transactionIDFromProps ?? (isMoneyRequestAction ? getOriginalMessage(action)?.IOUTransactionID : null); - const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true}); const violations = useTransactionViolations(transaction?.transactionID); - const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); - const [session] = useOnyx(ONYXKEYS.SESSION); - const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`); - const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS, {canBeMissing: true}); + const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true}); + const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, {canBeMissing: true}); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: true}); // Get transaction violations for given transaction id from onyx, find duplicated transactions violations and get duplicates const allDuplicates = useMemo(() => violations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], [violations]); From aef62e00169d706e6442b910bc4aef26885a6de3 Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Fri, 18 Apr 2025 11:43:48 +0200 Subject: [PATCH 3/4] Make transaction required in TransactionPreview --- src/components/ReportActionItem/TransactionPreview/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/TransactionPreview/index.tsx b/src/components/ReportActionItem/TransactionPreview/index.tsx index 772db41ca20e..f4cbb6a31bfc 100644 --- a/src/components/ReportActionItem/TransactionPreview/index.tsx +++ b/src/components/ReportActionItem/TransactionPreview/index.tsx @@ -45,7 +45,7 @@ function TransactionPreview(props: TransactionPreviewProps) { const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params?.threadReportID}`, {canBeMissing: true}); const isMoneyRequestAction = isMoneyRequestActionReportActionsUtils(action); const transactionID = transactionIDFromProps ?? (isMoneyRequestAction ? getOriginalMessage(action)?.IOUTransactionID : null); - const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true}); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: false}); const violations = useTransactionViolations(transaction?.transactionID); const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS, {canBeMissing: true}); const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true}); From 2585ffdcc56edf9741600c95450d159ef33a890f Mon Sep 17 00:00:00 2001 From: Jakub Korytko Date: Tue, 22 Apr 2025 08:31:23 +0200 Subject: [PATCH 4/4] Reuse IOU requests type --- src/libs/ReportActionsUtils.ts | 26 +++++++++---------- .../home/report/PureReportActionItem.tsx | 13 ++-------- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index fc71c151475e..4be573ba5274 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -515,6 +515,11 @@ function getCombinedReportActions( return getSortedReportActions(filteredReportActions, true); } +const iouRequestTypes: Array> = [CONST.IOU.REPORT_ACTION_TYPE.CREATE, CONST.IOU.REPORT_ACTION_TYPE.SPLIT, CONST.IOU.REPORT_ACTION_TYPE.TRACK]; + +// Get all IOU report actions for the report. +const iouRequestTypesSet = new Set>([...iouRequestTypes, CONST.IOU.REPORT_ACTION_TYPE.PAY]); + /** * Finds most recent IOU request action ID. */ @@ -522,11 +527,6 @@ function getMostRecentIOURequestActionID(reportActions: ReportAction[] | null): if (!Array.isArray(reportActions)) { return null; } - const iouRequestTypes: Array> = [ - CONST.IOU.REPORT_ACTION_TYPE.CREATE, - CONST.IOU.REPORT_ACTION_TYPE.SPLIT, - CONST.IOU.REPORT_ACTION_TYPE.TRACK, - ]; const iouRequestActions = reportActions?.filter((action) => { if (!isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.IOU)) { @@ -1160,6 +1160,11 @@ function isSplitBillAction(reportAction: OnyxInputOrEntry): report return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; } +function isIOURequestReportAction(reportAction: OnyxInputOrEntry): boolean { + const type = isMoneyRequestAction(reportAction) && getOriginalMessage(reportAction)?.type; + return !!type && iouRequestTypes.includes(type); +} + function isTrackExpenseAction(reportAction: OnyxEntry): reportAction is ReportAction { return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK; } @@ -1193,14 +1198,6 @@ function isTagModificationAction(actionName: string): boolean { ); } -// Get all IOU report actions for the report. -const iouRequestTypes = new Set>([ - CONST.IOU.REPORT_ACTION_TYPE.CREATE, - CONST.IOU.REPORT_ACTION_TYPE.SPLIT, - CONST.IOU.REPORT_ACTION_TYPE.PAY, - CONST.IOU.REPORT_ACTION_TYPE.TRACK, -]); - /** * Gets the reportID for the transaction thread associated with a report by iterating over the reportActions and identifying the IOU report actions. * Returns a reportID if there is exactly one transaction thread for the report, and null otherwise. @@ -1232,7 +1229,7 @@ function getOneTransactionThreadReportID( const actionType = originalMessage?.type; if ( actionType && - iouRequestTypes.has(actionType) && + iouRequestTypesSet.has(actionType) && action.childReportID && // Include deleted IOU reportActions if: // - they have an assocaited IOU transaction ID or @@ -2418,6 +2415,7 @@ export { isResolvedConciergeCategoryOptions, isAddCommentAction, isApprovedOrSubmittedReportAction, + isIOURequestReportAction, isChronosOOOListAction, isClosedAction, isConsecutiveActionMadeByPreviousActor, diff --git a/src/pages/home/report/PureReportActionItem.tsx b/src/pages/home/report/PureReportActionItem.tsx index 726a26ee1762..098ce26cd019 100644 --- a/src/pages/home/report/PureReportActionItem.tsx +++ b/src/pages/home/report/PureReportActionItem.tsx @@ -95,6 +95,7 @@ import { isCreatedTaskReportAction, isDeletedAction, isDeletedParentAction as isDeletedParentActionUtils, + isIOURequestReportAction, isMessageDeleted, isMoneyRequestAction, isPendingRemove, @@ -344,12 +345,6 @@ type PureReportActionItemProps = { const emptyHTML = ; const isEmptyHTML = ({props: {html}}: T): boolean => typeof html === 'string' && html.length === 0; -const simpleMoneyRequestActions: ReadonlyArray> = [ - CONST.IOU.REPORT_ACTION_TYPE.CREATE, - CONST.IOU.REPORT_ACTION_TYPE.SPLIT, - CONST.IOU.REPORT_ACTION_TYPE.TRACK, -]; - /** * 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. @@ -781,11 +776,7 @@ function PureReportActionItem({ const moneyRequestActionType = moneyRequestOriginalMessage?.type; // Show the MoneyRequestPreview for when expense is present - if ( - // For the pay flow, we only want to show MoneyRequestAction when sending money. When paying, we display a regular system message - !!moneyRequestActionType && - simpleMoneyRequestActions.includes(moneyRequestActionType) - ) { + if (isIOURequestReportAction(action)) { const isSplitInGroupChat = moneyRequestActionType === CONST.IOU.REPORT_ACTION_TYPE.SPLIT && report?.chatType === CONST.REPORT.CHAT_TYPE.GROUP; const chatReportID = moneyRequestOriginalMessage?.IOUReportID ? report?.chatReportID : reportID; // There is no single iouReport for bill splits, so only 1:1 requests require an iouReportID