From 5ba5beee7d0e4c59d7c95d3879f91fc9bf79a015 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Wed, 10 May 2023 16:26:35 -0400 Subject: [PATCH 01/16] add initial cancel task action with optimistic state and status changes --- src/libs/actions/Task.js | 29 ++++++++++++++++++++++++++++- src/pages/home/HeaderView.js | 5 ++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 12614343f52b..ca6948314b43 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -6,6 +6,7 @@ import * as ReportUtils from '../ReportUtils'; import * as Report from './Report'; import Navigation from '../Navigation/Navigation'; import ROUTES from '../../ROUTES'; +import CONST from '../../CONST'; /** * Clears out the task info from the store @@ -203,4 +204,30 @@ function clearOutTaskInfoAndNavigate(reportID) { Navigation.navigate(ROUTES.NEW_TASK_DETAILS); } -export {createTaskAndNavigate, setTitleValue, setDescriptionValue, setDetailsValue, setAssigneeValue, setShareDestinationValue, clearOutTaskInfo, clearOutTaskInfoAndNavigate}; +function cancelTask(taskReportID, parentReportID, originalStateNum, originalStatusNum) { + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, + value: { + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS.CLOSED, + }, + }, + ]; + + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, + value: { + stateNum: originalStateNum, + statusNum: originalStatusNum, + }, + }, + ]; + + API.write('CancelTask', {taskReportID}, {optimisticData, failureData}); +} + +export {createTaskAndNavigate, setTitleValue, setDescriptionValue, setDetailsValue, setAssigneeValue, setShareDestinationValue, clearOutTaskInfo, clearOutTaskInfoAndNavigate, cancelTask}; diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 937b05c97997..8facdc714b98 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -26,6 +26,7 @@ import colors from '../../styles/colors'; import reportPropTypes from '../reportPropTypes'; import ONYXKEYS from '../../ONYXKEYS'; import ThreeDotsMenu from '../../components/ThreeDotsMenu'; +import * as Task from '../../libs/actions/Task'; const propTypes = { /** Toggles the navigationMenu open and closed */ @@ -105,9 +106,7 @@ const HeaderView = (props) => { threeDotMenuItems.push({ icon: Expensicons.Trashcan, text: props.translate('common.cancel'), - - // Implementing in https://github.com/Expensify/App/issues/16857 - onSelected: () => {}, + onSelected: () => Task.cancelTask(props.report.reportID, props.report.parentReportID, props.report.stateNum, props.report.statusNum), }); } } From fde26f3c27174876560f158b5ab510013dbc6b99 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Wed, 10 May 2023 17:12:46 -0400 Subject: [PATCH 02/16] create optimistic cancel task report action --- src/CONST.js | 9 +++++++++ src/libs/ReportUtils.js | 23 +++++++++++++++++++++++ src/libs/actions/Task.js | 19 ++++++++++++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/CONST.js b/src/CONST.js index a501dc100f9d..4dd333018c3c 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -464,6 +464,9 @@ const CONST = { UPDATE_TIME_ENABLED: 'POLICYCHANGELOG_UPDATE_TIME_ENABLED', UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE', }, + TASK: { + CANCEL: 'TASK_CANCEL', + }, }, }, ARCHIVE_REASON: { @@ -913,6 +916,12 @@ const CONST = { AMOUNT_MAX_LENGTH: 10, }, + TASK: { + REPORT_ACTION_TYPE: { + CANCEL: 'cancel', + }, + }, + GROWL: { SUCCESS: 'success', ERROR: 'error', diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 060e3f6bf1a0..caceeb9cc291 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1847,6 +1847,28 @@ function getWhisperDisplayNames(participants) { return _.map(participants, (login) => getDisplayNameForParticipant(login, !isWhisperOnlyVisibleToCurrentUSer)).join(', '); } +function buildOptimisticCancelTaskReportAction(taskReportID) { + return { + taskReportID, + actionName: CONST.REPORT.ACTIONS.TYPE.TASK.CANCEL, + actorAccountID: currentUserAccountID, + actorEmail: currentUserEmail, + automatic: false, + avatar: lodashGet(currentUserPersonalDetails, 'avatar', getDefaultAvatar(currentUserEmail)), + isAttachment: false, + person: [ + { + style: 'strong', + text: lodashGet(currentUserPersonalDetails, 'displayName', currentUserEmail), + type: 'TEXT', + }, + ], + reportActionID: NumberUtils.rand64(), + shouldShow: true, + created: DateUtils.getDBTime(), + }; +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -1925,4 +1947,5 @@ export { canRequestMoney, getWhisperDisplayNames, getWorkspaceAvatar, + buildOptimisticCancelTaskReportAction, }; diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index ca6948314b43..16810ebb96f9 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -205,6 +205,9 @@ function clearOutTaskInfoAndNavigate(reportID) { } function cancelTask(taskReportID, parentReportID, originalStateNum, originalStatusNum) { + const optimisticCancelReportAction = ReportUtils.buildOptimisticCancelTaskReportAction(taskReportID); + const optimisticReportActionID = optimisticCancelReportAction.reportActionID; + const optimisticData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -214,6 +217,13 @@ function cancelTask(taskReportID, parentReportID, originalStateNum, originalStat statusNum: CONST.REPORT.STATUS.CLOSED, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, + value: { + [optimisticReportActionID]: optimisticCancelReportAction, + }, + }, ]; const failureData = [ @@ -225,9 +235,16 @@ function cancelTask(taskReportID, parentReportID, originalStateNum, originalStat statusNum: originalStatusNum, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, + value: { + [optimisticReportActionID]: null, + }, + }, ]; - API.write('CancelTask', {taskReportID}, {optimisticData, failureData}); + API.write('CancelTask', {taskReportID, optimisticReportActionID}, {optimisticData, failureData}); } export {createTaskAndNavigate, setTitleValue, setDescriptionValue, setDetailsValue, setAssigneeValue, setShareDestinationValue, clearOutTaskInfo, clearOutTaskInfoAndNavigate, cancelTask}; From 067e77ef20fb2fa17e9aff98b6bd2d31757b4c24 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Wed, 10 May 2023 17:20:56 -0400 Subject: [PATCH 03/16] remove unused const --- src/CONST.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 4dd333018c3c..a0b2d32102a7 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -916,12 +916,6 @@ const CONST = { AMOUNT_MAX_LENGTH: 10, }, - TASK: { - REPORT_ACTION_TYPE: { - CANCEL: 'cancel', - }, - }, - GROWL: { SUCCESS: 'success', ERROR: 'error', From dd199e5ae5c6eb65b9056d48c00f98e793af320b Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Thu, 11 May 2023 17:14:43 -0400 Subject: [PATCH 04/16] make canceled task actions visible --- src/libs/ReportActionsUtils.js | 6 +++++- src/libs/ReportUtils.js | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index de77e7f5243e..bdecb8d9cdc7 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -197,7 +197,11 @@ function shouldReportActionBeVisible(reportAction, key) { } // Filter out any unsupported reportAction types - if (!_.has(CONST.REPORT.ACTIONS.TYPE, reportAction.actionName) && !_.contains(_.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG), reportAction.actionName)) { + if ( + !_.has(CONST.REPORT.ACTIONS.TYPE, reportAction.actionName) && + !_.contains(_.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG), reportAction.actionName) && + !_.contains(_.values(CONST.REPORT.ACTIONS.TYPE.TASK), reportAction.actionName) + ) { return false; } diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index c38479cad699..3cf3ffd51fd0 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1913,6 +1913,18 @@ function buildOptimisticCancelTaskReportAction(taskReportID) { reportActionID: NumberUtils.rand64(), shouldShow: true, created: DateUtils.getDBTime(), + message: [ + { + type: CONST.REPORT.MESSAGE.TYPE.TEXT, + style: 'strong', + text: 'You', + }, + { + type: CONST.REPORT.MESSAGE.TYPE.TEXT, + style: 'normal', + text: ' closed this report', + }, + ], }; } From 3ac889167fabbf93187dc3d00745e2c6fba9a831 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Thu, 11 May 2023 17:54:02 -0400 Subject: [PATCH 05/16] add CancelTaskAction --- .../ReportActionItem/CancelTaskAction.js | 67 +++++++++++++++++++ src/languages/en.js | 3 + src/pages/home/report/ReportActionItem.js | 3 + 3 files changed, 73 insertions(+) create mode 100644 src/components/ReportActionItem/CancelTaskAction.js diff --git a/src/components/ReportActionItem/CancelTaskAction.js b/src/components/ReportActionItem/CancelTaskAction.js new file mode 100644 index 000000000000..7d8746675b29 --- /dev/null +++ b/src/components/ReportActionItem/CancelTaskAction.js @@ -0,0 +1,67 @@ +import React from 'react'; +import {View, Pressable} from 'react-native'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import Navigation from '../../libs/Navigation/Navigation'; +import withLocalize, {withLocalizePropTypes} from '../withLocalize'; +import ROUTES from '../../ROUTES'; +import compose from '../../libs/compose'; +import ONYXKEYS from '../../ONYXKEYS'; +import Text from '../Text'; +import styles from '../../styles/styles'; +import Icon from '../Icon'; +import * as Expensicons from '../Icon/Expensicons'; + +const propTypes = { + /** The ID of the associated taskReport */ + taskReportID: PropTypes.string.isRequired, + + /* Onyx Props */ + + taskReport: PropTypes.shape({ + /** Title of the task */ + reportName: PropTypes.string, + + /** Email address of the manager in this iou report */ + managerEmail: PropTypes.string, + + /** Email address of the creator of this iou report */ + ownerEmail: PropTypes.string, + }), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + taskReport: {}, +}; +const CancelTaskAction = (props) => { + const taskReportID = props.taskReportID; + const taskReportName = props.taskReport.reportName || ''; + + return ( + Navigation.navigate(ROUTES.getReportRoute(taskReportID))} + style={[styles.flexRow, styles.justifyContentBetween]} + > + + {props.translate('task.canceled')} + {` ${taskReportName}`} + + + + ); +}; + +CancelTaskAction.propTypes = propTypes; +CancelTaskAction.defaultProps = defaultProps; +CancelTaskAction.displayName = 'CancelTaskAction'; + +export default compose( + withLocalize, + withOnyx({ + taskReport: { + key: ({taskReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, + }, + }), +)(CancelTaskAction); diff --git a/src/languages/en.js b/src/languages/en.js index b9f49302a43a..ad10961c60ea 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1183,6 +1183,9 @@ export default { pleaseEnterTaskAssignee: 'Please select an assignee', pleaseEnterTaskDestination: 'Please select a share destination', }, + task: { + canceled: 'Canceled task', + }, statementPage: { generatingPDF: "We're generating your PDF right now. Please come back later!", }, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index b2c86d55204e..f019b4a3096d 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -37,6 +37,7 @@ import reportPropTypes from '../../reportPropTypes'; import {ShowContextMenuContext} from '../../../components/ShowContextMenuContext'; import focusTextInputAfterAnimation from '../../../libs/focusTextInputAfterAnimation'; import ChronosOOOListActions from '../../../components/ReportActionItem/ChronosOOOListActions'; +import CancelTaskAction from '../../../components/ReportActionItem/CancelTaskAction'; import ReportActionItemReactions from '../../../components/Reactions/ReportActionItemReactions'; import * as Report from '../../../libs/actions/Report'; import withLocalize from '../../../components/withLocalize'; @@ -200,6 +201,8 @@ class ReportActionItem extends Component { isHovered={hovered} /> ); + } else if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASK.CANCEL) { + children = ; } else { const message = _.last(lodashGet(this.props.action, 'message', [{}])); const isAttachment = _.has(this.props.action, 'isAttachment') ? this.props.action.isAttachment : ReportUtils.isReportMessageAttachment(message); From adad9a473653141d91f6f0462afd06bf3e7b9233 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 15 May 2023 12:00:30 -0400 Subject: [PATCH 06/16] match other task actions --- src/CONST.js | 4 +--- src/libs/ReportUtils.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index fe28090885d0..ad941d2ae4e2 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -408,6 +408,7 @@ const CONST = { CLOSED: 'CLOSED', CREATED: 'CREATED', TASKEDITED: 'TASKEDITED', + TASKCANCELED: 'TASKCANCELED', IOU: 'IOU', RENAMED: 'RENAMED', CHRONOSOOOLIST: 'CHRONOSOOOLIST', @@ -466,9 +467,6 @@ const CONST = { UPDATE_TIME_ENABLED: 'POLICYCHANGELOG_UPDATE_TIME_ENABLED', UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE', }, - TASK: { - CANCEL: 'TASK_CANCEL', - }, }, }, ARCHIVE_REASON: { diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 34e877d74bcd..1c958399dee8 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2038,7 +2038,7 @@ function getWhisperDisplayNames(participants) { function buildOptimisticCancelTaskReportAction(taskReportID) { return { taskReportID, - actionName: CONST.REPORT.ACTIONS.TYPE.TASK.CANCEL, + actionName: CONST.REPORT.ACTIONS.TYPE.TASKCANCELED, actorAccountID: currentUserAccountID, actorEmail: currentUserEmail, automatic: false, From 1017659cf8a72c2379ca9de9c2a0d7f5c7dbff75 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 15 May 2023 12:54:09 -0400 Subject: [PATCH 07/16] use TaskAction for cancel actions --- .../ReportActionItem/CancelTaskAction.js | 67 ------------------- src/components/ReportActionItem/TaskAction.js | 13 +++- src/libs/actions/Task.js | 1 - src/pages/home/report/ReportActionItem.js | 11 ++- 4 files changed, 20 insertions(+), 72 deletions(-) delete mode 100644 src/components/ReportActionItem/CancelTaskAction.js diff --git a/src/components/ReportActionItem/CancelTaskAction.js b/src/components/ReportActionItem/CancelTaskAction.js deleted file mode 100644 index 7d8746675b29..000000000000 --- a/src/components/ReportActionItem/CancelTaskAction.js +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import {View, Pressable} from 'react-native'; -import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; -import Navigation from '../../libs/Navigation/Navigation'; -import withLocalize, {withLocalizePropTypes} from '../withLocalize'; -import ROUTES from '../../ROUTES'; -import compose from '../../libs/compose'; -import ONYXKEYS from '../../ONYXKEYS'; -import Text from '../Text'; -import styles from '../../styles/styles'; -import Icon from '../Icon'; -import * as Expensicons from '../Icon/Expensicons'; - -const propTypes = { - /** The ID of the associated taskReport */ - taskReportID: PropTypes.string.isRequired, - - /* Onyx Props */ - - taskReport: PropTypes.shape({ - /** Title of the task */ - reportName: PropTypes.string, - - /** Email address of the manager in this iou report */ - managerEmail: PropTypes.string, - - /** Email address of the creator of this iou report */ - ownerEmail: PropTypes.string, - }), - - ...withLocalizePropTypes, -}; - -const defaultProps = { - taskReport: {}, -}; -const CancelTaskAction = (props) => { - const taskReportID = props.taskReportID; - const taskReportName = props.taskReport.reportName || ''; - - return ( - Navigation.navigate(ROUTES.getReportRoute(taskReportID))} - style={[styles.flexRow, styles.justifyContentBetween]} - > - - {props.translate('task.canceled')} - {` ${taskReportName}`} - - - - ); -}; - -CancelTaskAction.propTypes = propTypes; -CancelTaskAction.defaultProps = defaultProps; -CancelTaskAction.displayName = 'CancelTaskAction'; - -export default compose( - withLocalize, - withOnyx({ - taskReport: { - key: ({taskReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, - }, - }), -)(CancelTaskAction); diff --git a/src/components/ReportActionItem/TaskAction.js b/src/components/ReportActionItem/TaskAction.js index 74c0454d092d..73b526f10b5c 100644 --- a/src/components/ReportActionItem/TaskAction.js +++ b/src/components/ReportActionItem/TaskAction.js @@ -50,7 +50,18 @@ const TaskAction = (props) => { const taskReportID = props.taskReportID; const taskReportName = props.taskReport.reportName || ''; - const messageLinkText = props.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED ? props.translate('task.messages.completed') : props.translate('newTaskPage.task'); + let messageLinkText = ''; + switch (props.actionName) { + case CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED: + messageLinkText = props.translate('task.messages.completed'); + break; + case CONST.REPORT.ACTIONS.TYPE.TASKCANCELED: + messageLinkText = props.translate('task.canceled'); + break; + default: + messageLinkText = props.translate('newTaskPage.task'); + } + return ( Navigation.navigate(ROUTES.getReportRoute(taskReportID))} diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index c5a584b8c326..5c99b3dadaaf 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -9,7 +9,6 @@ import Navigation from '../Navigation/Navigation'; import ROUTES from '../../ROUTES'; import CONST from '../../CONST'; import DateUtils from '../DateUtils'; -import CONST from '../../CONST'; /** * Clears out the task info from the store diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index c7e7df4ae144..b07d2558075d 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -38,7 +38,6 @@ import reportPropTypes from '../../reportPropTypes'; import {ShowContextMenuContext} from '../../../components/ShowContextMenuContext'; import focusTextInputAfterAnimation from '../../../libs/focusTextInputAfterAnimation'; import ChronosOOOListActions from '../../../components/ReportActionItem/ChronosOOOListActions'; -import CancelTaskAction from '../../../components/ReportActionItem/CancelTaskAction'; import ReportActionItemReactions from '../../../components/Reactions/ReportActionItemReactions'; import * as Report from '../../../libs/actions/Report'; import withLocalize from '../../../components/withLocalize'; @@ -213,6 +212,14 @@ class ReportActionItem extends Component { isHovered={hovered} /> ); + } else if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELED) { + children = ( + + ); } else if (ReportActionUtils.isCreatedTaskReportAction(this.props.action)) { children = ( ); - } else if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASK.CANCEL) { - children = ; } else { const message = _.last(lodashGet(this.props.action, 'message', [{}])); const isAttachment = _.has(this.props.action, 'isAttachment') ? this.props.action.isAttachment : ReportUtils.isReportMessageAttachment(message); From eb825c7612936b451c0cec96cbe7456a8391bfa9 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 15 May 2023 13:06:00 -0400 Subject: [PATCH 08/16] update the report optimistically for the LHN --- src/libs/actions/Task.js | 14 +++++++++++++- src/pages/home/HeaderView.js | 16 ++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 5c99b3dadaaf..d03f18bfcc68 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -429,9 +429,11 @@ function getShareDestination(reportID, reports, personalDetails) { }; } -function cancelTask(taskReportID, parentReportID, originalStateNum, originalStatusNum) { +function cancelTask(taskReportID, parentReportID, originalStateNum, originalStatusNum, currentUserEmail, cancelMessage) { const optimisticCancelReportAction = ReportUtils.buildOptimisticCancelTaskReportAction(taskReportID); const optimisticReportActionID = optimisticCancelReportAction.reportActionID; + const now = DateUtils.getDBTime(); + const lastCommentText = ReportUtils.formatReportLastMessageText(cancelMessage); const optimisticData = [ { @@ -442,6 +444,16 @@ function cancelTask(taskReportID, parentReportID, originalStateNum, originalStat statusNum: CONST.REPORT.STATUS.CLOSED, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`, + value: { + lastVisibleActionCreated: now, + lastMessageText: Str.htmlDecode(lastCommentText), + lastActorEmail: currentUserEmail, + lastReadTime: now, + }, + }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 6e0319c97fd4..42b5096ecdba 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -116,7 +116,15 @@ const HeaderView = (props) => { threeDotMenuItems.push({ icon: Expensicons.Trashcan, text: props.translate('common.cancel'), - onSelected: () => Task.cancelTask(props.report.reportID, props.report.parentReportID, props.report.stateNum, props.report.statusNum), + onSelected: () => + Task.cancelTask( + props.report.reportID, + props.report.parentReportID, + props.report.stateNum, + props.report.statusNum, + props.account.primaryLogin, + props.translate('task.canceled'), + ), }); } } @@ -233,7 +241,11 @@ export default compose( withOnyx({ account: { key: ONYXKEYS.ACCOUNT, - selector: (account) => account && {guideCalendarLink: account.guideCalendarLink}, + selector: (account) => + account && { + guideCalendarLink: account.guideCalendarLink, + primaryLogin: account.primaryLogin, + }, }, parentReportActions: { key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, From e68d5ea61e2336d9cbba192db4ea5c09da8dba8f Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 15 May 2023 17:38:55 -0400 Subject: [PATCH 09/16] use shared optimistic task action helper --- src/libs/ReportUtils.js | 35 ----------------------------------- src/libs/actions/Task.js | 14 ++++++-------- src/pages/home/HeaderView.js | 13 ++----------- 3 files changed, 8 insertions(+), 54 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 6b8f9e6310f1..984ee80dd8e8 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -2090,40 +2090,6 @@ function getWhisperDisplayNames(participants) { return _.map(participants, (login) => getDisplayNameForParticipant(login, !isWhisperOnlyVisibleToCurrentUSer)).join(', '); } -function buildOptimisticCancelTaskReportAction(taskReportID) { - return { - taskReportID, - actionName: CONST.REPORT.ACTIONS.TYPE.TASKCANCELED, - actorAccountID: currentUserAccountID, - actorEmail: currentUserEmail, - automatic: false, - avatar: lodashGet(currentUserPersonalDetails, 'avatar', getDefaultAvatar(currentUserEmail)), - isAttachment: false, - person: [ - { - style: 'strong', - text: lodashGet(currentUserPersonalDetails, 'displayName', currentUserEmail), - type: 'TEXT', - }, - ], - reportActionID: NumberUtils.rand64(), - shouldShow: true, - created: DateUtils.getDBTime(), - message: [ - { - type: CONST.REPORT.MESSAGE.TYPE.TEXT, - style: 'strong', - text: 'You', - }, - { - type: CONST.REPORT.MESSAGE.TYPE.TEXT, - style: 'normal', - text: ' closed this report', - }, - ], - }; -} - /** * Show subscript on IOU or expense report * @param {Object} report @@ -2231,7 +2197,6 @@ export { canRequestMoney, getWhisperDisplayNames, getWorkspaceAvatar, - buildOptimisticCancelTaskReportAction, isThread, isThreadParent, isThreadFirstChat, diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 01d5308b3625..b343e0321102 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -440,11 +440,10 @@ function getShareDestination(reportID, reports, personalDetails) { }; } -function cancelTask(taskReportID, parentReportID, originalStateNum, originalStatusNum, currentUserEmail, cancelMessage) { - const optimisticCancelReportAction = ReportUtils.buildOptimisticCancelTaskReportAction(taskReportID); +function cancelTask(taskReportID, parentReportID, taskTitle, originalStateNum, originalStatusNum) { + const message = `Canceled task: ${taskTitle}`; + const optimisticCancelReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASKCANCELED, message); const optimisticReportActionID = optimisticCancelReportAction.reportActionID; - const now = DateUtils.getDBTime(); - const lastCommentText = ReportUtils.formatReportLastMessageText(cancelMessage); const optimisticData = [ { @@ -459,10 +458,9 @@ function cancelTask(taskReportID, parentReportID, originalStateNum, originalStat onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`, value: { - lastVisibleActionCreated: now, - lastMessageText: Str.htmlDecode(lastCommentText), - lastActorEmail: currentUserEmail, - lastReadTime: now, + lastVisibleActionCreated: optimisticCancelReportAction.created, + lastMessageText: message, + lastActorEmail: optimisticCancelReportAction.actorEmail, }, }, { diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 26c540c5db3f..b59cf2517079 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -28,7 +28,6 @@ import ONYXKEYS from '../../ONYXKEYS'; import ThreeDotsMenu from '../../components/ThreeDotsMenu'; import * as Task from '../../libs/actions/Task'; import reportActionPropTypes from './report/reportActionPropTypes'; -import * as TaskUtils from '../../libs/actions/Task'; const propTypes = { /** Toggles the navigationMenu open and closed */ @@ -95,7 +94,7 @@ const HeaderView = (props) => { threeDotMenuItems.push({ icon: Expensicons.Checkmark, text: props.translate('newTaskPage.markAsDone'), - onSelected: () => TaskUtils.completeTask(props.report.reportID, props.report.parentReportID, title), + onSelected: () => Task.completeTask(props.report.reportID, props.report.parentReportID, title), }); } @@ -115,15 +114,7 @@ const HeaderView = (props) => { threeDotMenuItems.push({ icon: Expensicons.Trashcan, text: props.translate('common.cancel'), - onSelected: () => - Task.cancelTask( - props.report.reportID, - props.report.parentReportID, - props.report.stateNum, - props.report.statusNum, - props.account.primaryLogin, - props.translate('task.canceled'), - ), + onSelected: () => Task.cancelTask(props.report.reportID, props.report.parentReportID, props.report.reportName, props.report.stateNum, props.report.statusNum), }); } } From 0d86edd15d771035018415f3fa503c2b915c7fee Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 15 May 2023 17:44:11 -0400 Subject: [PATCH 10/16] combine TaskAction logic --- src/pages/home/report/ReportActionItem.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index b07d2558075d..5ff233b4f87b 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -204,7 +204,7 @@ class ReportActionItem extends Component { checkIfContextMenuActive={this.checkIfContextMenuActive} /> ); - } else if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED) { + } else if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED || this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELED) { children = ( ); - } else if (this.props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELED) { - children = ( - - ); } else if (ReportActionUtils.isCreatedTaskReportAction(this.props.action)) { children = ( Date: Mon, 15 May 2023 18:38:26 -0400 Subject: [PATCH 11/16] added spanish translation --- ios/Podfile.lock | 6 +++--- src/components/ReportActionItem/TaskAction.js | 2 +- src/languages/en.js | 2 +- src/languages/es.js | 3 ++- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f0d6720080e8..d62469e46b86 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1019,7 +1019,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Airship: c70eed50e429f97f5adb285423c7291fb7a032ae AirshipFrameworkProxy: 2eefb77bb77b5120b0f48814b0d44439aa3ad415 - boost: 57d2868c099736d80fcd648bf211b4431e51a558 + boost: a7c83b31436843459a1961bfd74b96033dc77234 CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: ff54429f0110d3c722630a98096ba689c39f6d5f @@ -1062,7 +1062,7 @@ SPEC CHECKSUMS: Permission-LocationWhenInUse: 3ba99e45c852763f730eabecec2870c2382b7bd4 Plaid: 7d340abeadb46c7aa1a91f896c5b22395a31fcf2 PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef - RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 + RCT-Folly: 0080d0a6ebf2577475bda044aa59e2ca1f909cda RCTRequired: e9e7b8b45aa9bedb2fdad71740adf07a7265b9be RCTTypeSafety: 9ae0e9206625e995f0df4d5b9ddc94411929fb30 React: a71c8e1380f07e01de721ccd52bcf9c03e81867d @@ -1135,4 +1135,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 5feaab251246f42f41e69c8a28fa331b4899e814 -COCOAPODS: 1.12.0 +COCOAPODS: 1.11.3 diff --git a/src/components/ReportActionItem/TaskAction.js b/src/components/ReportActionItem/TaskAction.js index 3f1abbb599c0..fd17fb33ac0a 100644 --- a/src/components/ReportActionItem/TaskAction.js +++ b/src/components/ReportActionItem/TaskAction.js @@ -55,7 +55,7 @@ const TaskAction = (props) => { messageLinkText = props.translate('task.messages.completed'); break; case CONST.REPORT.ACTIONS.TYPE.TASKCANCELED: - messageLinkText = props.translate('task.canceled'); + messageLinkText = props.translate('task.messages.canceled'); break; default: messageLinkText = props.translate('newTaskPage.task'); diff --git a/src/languages/en.js b/src/languages/en.js index 7f7421897f84..791282b4906a 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -1194,10 +1194,10 @@ export default { pleaseEnterTaskDestination: 'Please select a share destination', }, task: { - canceled: 'Canceled task', completed: 'Completed', messages: { completed: 'Completed task', + canceled: 'Canceled task', }, }, statementPage: { diff --git a/src/languages/es.js b/src/languages/es.js index 0d04dea828a5..c6f2153a98fb 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -1201,7 +1201,8 @@ export default { task: { completed: 'Completada', messages: { - completed: 'tarea completada', + completed: 'Tarea completada', + canceled: 'Tarea cancelada', }, }, statementPage: { From e0bef3ab421b463e707a506202e0a637665ab126 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Tue, 16 May 2023 10:24:00 -0400 Subject: [PATCH 12/16] added jsdoc for cancelTask --- src/libs/actions/Task.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 225073f9d80d..6bd846cf1dcf 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -502,6 +502,14 @@ function getShareDestination(reportID, reports, personalDetails) { }; } +/** + * Cancels a task by setting the report state to SUBMITTED and status to CLOSED + * @param {string} taskReportID + * @param {string} parentReportID + * @param {string} taskTitle + * @param {number} originalStateNum + * @param {number} originalStatusNum + */ function cancelTask(taskReportID, parentReportID, taskTitle, originalStateNum, originalStatusNum) { const message = `Canceled task: ${taskTitle}`; const optimisticCancelReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASKCANCELED, message); From c8e284a2faebceb6622dc2b3df07dd1c11e13a16 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Tue, 16 May 2023 12:05:31 -0400 Subject: [PATCH 13/16] disable TaskPreview checkbox when canceled --- src/components/ReportActionItem/TaskPreview.js | 1 + src/libs/actions/Task.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index 394ca5d9e366..ae2c5accab26 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -69,6 +69,7 @@ const TaskPreview = (props) => { style={[styles.mr2]} containerStyle={[styles.taskCheckbox]} isChecked={isTaskCompleted} + disabled={TaskUtils.isTaskCanceled(props.taskReport)} onPress={() => { if (isTaskCompleted) { TaskUtils.reopenTask(props.taskReportID, parentReportID, taskTitle); diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 6bd846cf1dcf..0de8dd3bb30c 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -563,6 +563,10 @@ function cancelTask(taskReportID, parentReportID, taskTitle, originalStateNum, o API.write('CancelTask', {taskReportID, optimisticReportActionID}, {optimisticData, failureData}); } +function isTaskCanceled(taskReport) { + return taskReport.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && taskReport.statusNum === CONST.REPORT.STATUS.CLOSED; +} + export { createTaskAndNavigate, editTaskAndNavigate, @@ -579,4 +583,5 @@ export { getAssignee, getShareDestination, cancelTask, + isTaskCanceled, }; From af6d95cc49e839dd2e0526f83bd54d78a7c23c10 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Tue, 16 May 2023 13:37:37 -0400 Subject: [PATCH 14/16] disable done button when task is canceled --- src/components/TaskHeader.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/TaskHeader.js b/src/components/TaskHeader.js index 5b2c8825d0eb..474a009693ee 100644 --- a/src/components/TaskHeader.js +++ b/src/components/TaskHeader.js @@ -87,6 +87,7 @@ function TaskHeader(props) { ) : (