From 5f8e5d2d6bb303e7edf813fa4f8aee4b48f3bea8 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 2 Apr 2024 14:46:57 -0700 Subject: [PATCH 1/8] show delete option for requests on one transaction view --- src/components/MoneyReportHeader.tsx | 72 +++++++++++++++++++++++++++- src/pages/home/ReportScreen.tsx | 5 +- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 488dfc574ab2..32dc3d2cd64f 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -8,6 +8,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as HeaderUtils from '@libs/HeaderUtils'; import Navigation from '@libs/Navigation/Navigation'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; @@ -15,6 +16,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import Button from './Button'; import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; @@ -32,6 +34,9 @@ type MoneyReportHeaderOnyxProps = { /** Session info for the currently logged in user. */ session: OnyxEntry; + + /** The transaction thread report associated with the current report, if any */ + transactionThreadReport: OnyxEntry; }; type MoneyReportHeaderProps = MoneyReportHeaderOnyxProps & { @@ -40,14 +45,34 @@ type MoneyReportHeaderProps = MoneyReportHeaderOnyxProps & { /** The policy tied to the money request report */ policy: OnyxEntry; + + /** Array of report actions for the report*/ + reportActions: OnyxTypes.ReportAction[]; + + /** The reportID of the transaction thread report associated with this current report, if any */ + // eslint-disable-next-line react/no-unused-prop-types + transactionThreadReportID?: string | null; }; -function MoneyReportHeader({session, policy, chatReport, nextStep, report: moneyRequestReport}: MoneyReportHeaderProps) { +function MoneyReportHeader({session, policy, chatReport, nextStep, report: moneyRequestReport, transactionThreadReport, reportActions}: MoneyReportHeaderProps) { const styles = useThemeStyles(); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); const {translate} = useLocalize(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(moneyRequestReport); const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); + const parentReportAction = useMemo(() => { + if (!reportActions || !transactionThreadReport?.parentReportActionID) { + return null; + } + return reportActions.find((action) => action.reportActionID === transactionThreadReport.parentReportActionID ?? '0'); + }, [reportActions, transactionThreadReport?.parentReportActionID]); + const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); + + // Only the requestor can take delete the request, admins can only edit it. + const isActionOwner = typeof parentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && parentReportAction.actorAccountID === session?.accountID; + const canDeleteRequest = + isActionOwner && (ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) || ReportUtils.isTrackExpenseReport(transactionThreadReport)) && !isDeletedParentAction; const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [paymentType, setPaymentType] = useState(); const [requestType, setRequestType] = useState<'pay' | 'approve'>(); @@ -106,6 +131,19 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money } }; + const deleteTransaction = useCallback(() => { + if (parentReportAction) { + const iouTransactionID = parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; + if (ReportActionsUtils.isTrackExpenseAction(parentReportAction)) { + IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, parentReportAction, true); + return; + } + IOU.deleteMoneyRequest(iouTransactionID, parentReportAction, true); + } + + setIsDeleteModalVisible(false); + }, [moneyRequestReport?.reportID, parentReportAction, setIsDeleteModalVisible]); + // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( () => chatReport?.isOwnPolicyExpenseChat && !policy?.harvesting?.enabled, @@ -121,6 +159,23 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money }); } + // If the report supports adding transactions to it, then it also supports deleting transactions from it. + if (canDeleteRequest && !isEmptyObject(transactionThreadReport)) { + threeDotsMenuItems.push({ + icon: Expensicons.Trashcan, + text: translate('reportActionContextMenu.deleteAction', {action: parentReportAction}), + onSelected: () => setIsDeleteModalVisible(true), + }); + } + + useEffect(() => { + if (canDeleteRequest) { + return; + } + + setIsDeleteModalVisible(false); + }, [canDeleteRequest]); + return ( + setIsDeleteModalVisible(false)} + prompt={translate('iou.deleteConfirmation')} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} + danger + /> ); } @@ -244,6 +309,9 @@ export default withOnyx({ nextStep: { key: ({report}) => `${ONYXKEYS.COLLECTION.NEXT_STEP}${report.reportID}`, }, + transactionThreadReport: { + key: ({transactionThreadReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, + }, session: { key: ONYXKEYS.SESSION, }, diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 152b02366227..563aeb3c4e76 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -331,11 +331,14 @@ function ReportScreen({ ); } + const transactionThreadReportID = useMemo(() => ReportActionsUtils.getOneTransactionThreadReportID(reportActions ?? []), [reportActions]); if (ReportUtils.isMoneyRequestReport(report)) { headerView = ( ); } @@ -654,7 +657,7 @@ function ReportScreen({ isLoadingNewerReportActions={reportMetadata?.isLoadingNewerReportActions} isLoadingOlderReportActions={reportMetadata?.isLoadingOlderReportActions} isReadyForCommentLinking={!shouldShowSkeleton} - transactionThreadReportID={ReportActionsUtils.getOneTransactionThreadReportID(reportActions ?? [])} + transactionThreadReportID={transactionThreadReportID} /> )} From 74ba28ecb218695c7e550d86a8065d0e09b4fdd2 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 2 Apr 2024 14:51:14 -0700 Subject: [PATCH 2/8] minor style --- src/components/MoneyReportHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 32dc3d2cd64f..71a975c4f602 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -46,7 +46,7 @@ type MoneyReportHeaderProps = MoneyReportHeaderOnyxProps & { /** The policy tied to the money request report */ policy: OnyxEntry; - /** Array of report actions for the report*/ + /** Array of report actions for the report */ reportActions: OnyxTypes.ReportAction[]; /** The reportID of the transaction thread report associated with this current report, if any */ From bcc8535d96adcbb172d3b890a2662d5b7328101a Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 2 Apr 2024 15:07:40 -0700 Subject: [PATCH 3/8] typescript fix --- src/components/MoneyReportHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 71a975c4f602..b0f48c2c6b74 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -65,7 +65,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money if (!reportActions || !transactionThreadReport?.parentReportActionID) { return null; } - return reportActions.find((action) => action.reportActionID === transactionThreadReport.parentReportActionID ?? '0'); + return reportActions.find((action) => action.reportActionID === transactionThreadReport.parentReportActionID ?? '0') as OnyxTypes.ReportAction; }, [reportActions, transactionThreadReport?.parentReportActionID]); const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); From 1c2d68ea4483585ee109a6f3dfe6282d4a0d3b7c Mon Sep 17 00:00:00 2001 From: Nikki Wines Date: Tue, 2 Apr 2024 15:19:17 -0700 Subject: [PATCH 4/8] add clarifying variable names and remove extra word Co-authored-by: Vit Horacek <36083550+mountiny@users.noreply.github.com> --- src/components/MoneyReportHeader.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index b0f48c2c6b74..e268c3ced5aa 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -56,12 +56,12 @@ type MoneyReportHeaderProps = MoneyReportHeaderOnyxProps & { function MoneyReportHeader({session, policy, chatReport, nextStep, report: moneyRequestReport, transactionThreadReport, reportActions}: MoneyReportHeaderProps) { const styles = useThemeStyles(); - const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + const [isDeleteRequestModalVisible, setIsDeleteRequestModalVisible] = useState(false); const {translate} = useLocalize(); const {windowWidth, isSmallScreenWidth} = useWindowDimensions(); const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(moneyRequestReport); const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); - const parentReportAction = useMemo(() => { + const requestParentReportAction = useMemo(() => { if (!reportActions || !transactionThreadReport?.parentReportActionID) { return null; } @@ -69,7 +69,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money }, [reportActions, transactionThreadReport?.parentReportActionID]); const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); - // Only the requestor can take delete the request, admins can only edit it. + // Only the requestor can delete the request, admins can only edit it. const isActionOwner = typeof parentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && parentReportAction.actorAccountID === session?.accountID; const canDeleteRequest = isActionOwner && (ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) || ReportUtils.isTrackExpenseReport(transactionThreadReport)) && !isDeletedParentAction; From 0b1ead91ff6e7344d58c16e0f219636706374423 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 2 Apr 2024 15:21:34 -0700 Subject: [PATCH 5/8] safely acces IOUTransactionID --- src/components/MoneyRequestHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 5d3231ca0a41..f451f5f15581 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -222,7 +222,7 @@ const MoneyRequestHeaderWithTransaction = withOnyx { const parentReportAction = (report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : {}) as ReportAction & OriginalMessageIOU; - return `${ONYXKEYS.COLLECTION.TRANSACTION}${parentReportAction.originalMessage.IOUTransactionID ?? 0}`; + return `${ONYXKEYS.COLLECTION.TRANSACTION}${parentReportAction?.originalMessage?.IOUTransactionID ?? 0}`; }, }, shownHoldUseExplanation: { From 3350d7799fdc84f5bd5af15e299ebd807b69d291 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 2 Apr 2024 15:25:25 -0700 Subject: [PATCH 6/8] fully refactor names --- src/components/MoneyReportHeader.tsx | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index e268c3ced5aa..58a5daf55fc1 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -67,10 +67,10 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money } return reportActions.find((action) => action.reportActionID === transactionThreadReport.parentReportActionID ?? '0') as OnyxTypes.ReportAction; }, [reportActions, transactionThreadReport?.parentReportActionID]); - const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction); + const isDeletedParentAction = ReportActionsUtils.isDeletedAction(requestParentReportAction); // Only the requestor can delete the request, admins can only edit it. - const isActionOwner = typeof parentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && parentReportAction.actorAccountID === session?.accountID; + const isActionOwner = typeof requestParentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && requestParentReportAction.actorAccountID === session?.accountID; const canDeleteRequest = isActionOwner && (ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) || ReportUtils.isTrackExpenseReport(transactionThreadReport)) && !isDeletedParentAction; const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); @@ -132,17 +132,17 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money }; const deleteTransaction = useCallback(() => { - if (parentReportAction) { - const iouTransactionID = parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; - if (ReportActionsUtils.isTrackExpenseAction(parentReportAction)) { - IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, parentReportAction, true); + if (requestParentReportAction) { + const iouTransactionID = requestParentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? requestParentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; + if (ReportActionsUtils.isTrackExpenseAction(requestParentReportAction)) { + IOU.deleteTrackExpense(moneyRequestReport?.reportID ?? '', iouTransactionID, requestParentReportAction, true); return; } - IOU.deleteMoneyRequest(iouTransactionID, parentReportAction, true); + IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, true); } - setIsDeleteModalVisible(false); - }, [moneyRequestReport?.reportID, parentReportAction, setIsDeleteModalVisible]); + setIsDeleteRequestModalVisible(false); + }, [moneyRequestReport?.reportID, requestParentReportAction, setIsDeleteRequestModalVisible]); // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( @@ -163,8 +163,8 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money if (canDeleteRequest && !isEmptyObject(transactionThreadReport)) { threeDotsMenuItems.push({ icon: Expensicons.Trashcan, - text: translate('reportActionContextMenu.deleteAction', {action: parentReportAction}), - onSelected: () => setIsDeleteModalVisible(true), + text: translate('reportActionContextMenu.deleteAction', {action: requestParentReportAction}), + onSelected: () => setIsDeleteRequestModalVisible(true), }); } @@ -173,7 +173,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money return; } - setIsDeleteModalVisible(false); + setIsDeleteRequestModalVisible(false); }, [canDeleteRequest]); return ( @@ -288,9 +288,9 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money /> setIsDeleteModalVisible(false)} + onCancel={() => setIsDeleteRequestModalVisible(false)} prompt={translate('iou.deleteConfirmation')} confirmText={translate('common.delete')} cancelText={translate('common.cancel')} From 3b04a3fd324f1aad4f59d4de0b38c8237ad567da Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 2 Apr 2024 15:40:44 -0700 Subject: [PATCH 7/8] typescript fix pt 2 --- src/components/MoneyReportHeader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 58a5daf55fc1..80cd8d91a6c6 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -65,9 +65,9 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money if (!reportActions || !transactionThreadReport?.parentReportActionID) { return null; } - return reportActions.find((action) => action.reportActionID === transactionThreadReport.parentReportActionID ?? '0') as OnyxTypes.ReportAction; + return reportActions.find((action) => action.reportActionID === transactionThreadReport.parentReportActionID); }, [reportActions, transactionThreadReport?.parentReportActionID]); - const isDeletedParentAction = ReportActionsUtils.isDeletedAction(requestParentReportAction); + const isDeletedParentAction = ReportActionsUtils.isDeletedAction(requestParentReportAction as OnyxTypes.ReportAction); // Only the requestor can delete the request, admins can only edit it. const isActionOwner = typeof requestParentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && requestParentReportAction.actorAccountID === session?.accountID; From ab09e28ce6437fc31c48cb8963f8e5dc5828bf10 Mon Sep 17 00:00:00 2001 From: NikkiWines Date: Tue, 2 Apr 2024 15:41:52 -0700 Subject: [PATCH 8/8] indentation --- src/components/MoneyReportHeader.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 80cd8d91a6c6..d36c8c43d322 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -70,7 +70,8 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money const isDeletedParentAction = ReportActionsUtils.isDeletedAction(requestParentReportAction as OnyxTypes.ReportAction); // Only the requestor can delete the request, admins can only edit it. - const isActionOwner = typeof requestParentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && requestParentReportAction.actorAccountID === session?.accountID; + const isActionOwner = + typeof requestParentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && requestParentReportAction.actorAccountID === session?.accountID; const canDeleteRequest = isActionOwner && (ReportUtils.canAddOrDeleteTransactions(moneyRequestReport) || ReportUtils.isTrackExpenseReport(transactionThreadReport)) && !isDeletedParentAction; const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false);