diff --git a/src/CONST.js b/src/CONST.js index 8cd797fefda2..5f53e24c0c76 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -407,6 +407,7 @@ const CONST = { ADDCOMMENT: 'ADDCOMMENT', CLOSED: 'CLOSED', CREATED: 'CREATED', + TASKEDITED: 'TASKEDITED', IOU: 'IOU', RENAMED: 'RENAMED', CHRONOSOOOLIST: 'CHRONOSOOOLIST', diff --git a/src/ROUTES.js b/src/ROUTES.js index d0e16820772c..1d5506f019e7 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -95,8 +95,10 @@ export default { NEW_TASK_WITH_REPORT_ID: `${NEW_TASK}/:reportID?`, TASK_TITLE: 'r/:reportID/title', TASK_DESCRIPTION: 'r/:reportID/description', + TASK_ASSIGNEE: 'r/:reportID/assignee', getTaskReportTitleRoute: (reportID) => `r/${reportID}/title`, getTaskReportDescriptionRoute: (reportID) => `r/${reportID}/description`, + getTaskReportAssigneeRoute: (reportID) => `r/${reportID}/assignee`, NEW_TASK_ASSIGNEE: `${NEW_TASK}/assignee`, NEW_TASK_SHARE_DESTINATION: `${NEW_TASK}/share-destination`, NEW_TASK_DETAILS: `${NEW_TASK}/details`, diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 80664f2d3521..50c933823e63 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -63,45 +63,54 @@ const GenericPressable = forwardRef((props, ref) => { return props.disabled || shouldBeDisabledByScreenReader; }, [isScreenReaderActive, enableInScreenReaderStates, props.disabled]); - const onLongPressHandler = useCallback((event) => { - if (isDisabled) { - return; - } - if (!onLongPress) { - return; - } - if (shouldUseHapticsOnLongPress) { - HapticFeedback.longPress(); - } - if (ref && ref.current) { - ref.current.blur(); - } - onLongPress(event); - - Accessibility.moveAccessibilityFocus(nextFocusRef); - }, [shouldUseHapticsOnLongPress, onLongPress, nextFocusRef, ref, isDisabled]); - - const onPressHandler = useCallback((event) => { - if (isDisabled) { - return; - } - if (shouldUseHapticsOnPress) { - HapticFeedback.press(); - } - if (ref && ref.current) { - ref.current.blur(); - } - onPress(event); + const onLongPressHandler = useCallback( + (event) => { + if (isDisabled) { + return; + } + if (!onLongPress) { + return; + } + if (shouldUseHapticsOnLongPress) { + HapticFeedback.longPress(); + } + if (ref && ref.current) { + ref.current.blur(); + } + onLongPress(event); + + Accessibility.moveAccessibilityFocus(nextFocusRef); + }, + [shouldUseHapticsOnLongPress, onLongPress, nextFocusRef, ref, isDisabled], + ); - Accessibility.moveAccessibilityFocus(nextFocusRef); - }, [shouldUseHapticsOnPress, onPress, nextFocusRef, ref, isDisabled]); + const onPressHandler = useCallback( + (event) => { + if (isDisabled) { + return; + } + if (shouldUseHapticsOnPress) { + HapticFeedback.press(); + } + if (ref && ref.current) { + ref.current.blur(); + } + onPress(event); + + Accessibility.moveAccessibilityFocus(nextFocusRef); + }, + [shouldUseHapticsOnPress, onPress, nextFocusRef, ref, isDisabled], + ); - const onKeyPressHandler = useCallback((event) => { - if (event.key !== 'Enter') { - return; - } - onPressHandler(event); - }, [onPressHandler]); + const onKeyPressHandler = useCallback( + (event) => { + if (event.key !== 'Enter') { + return; + } + onPressHandler(event); + }, + [onPressHandler], + ); useEffect(() => { if (!keyboardShortcut) { diff --git a/src/components/TaskSelectorLink.js b/src/components/TaskSelectorLink.js index 82a80223a567..16cd22e7bd27 100644 --- a/src/components/TaskSelectorLink.js +++ b/src/components/TaskSelectorLink.js @@ -36,6 +36,9 @@ const propTypes = { /** Whether the Touchable should be disabled */ disabled: PropTypes.bool, + /** Whether we're creating a new task or editing */ + isNewTask: PropTypes.bool, + ...withLocalizePropTypes, }; @@ -45,16 +48,16 @@ const defaultProps = { alternateText: '', isShareDestination: false, disabled: false, + isNewTask: true, }; const TaskSelectorLink = (props) => { const shortenedText = props.text.length > 35 ? `${props.text.substring(0, 35)}...` : props.text; const displayNameStyle = StyleUtils.combineStyles(styles.optionDisplayName, styles.pre); const alternateTextStyle = StyleUtils.combineStyles(styles.sidebarLinkText, styles.optionAlternateText, styles.textLabelSupporting, styles.pre); - const linkBottomMargin = props.icons.length !== 0 ? styles.mb6 : styles.mb2; return ( @@ -93,7 +96,7 @@ const TaskSelectorLink = (props) => { ) : ( {props.translate(props.label)} )} - {props.disabled ? null : ( + {props.disabled || !props.isNewTask ? null : ( { + const TaskAssigneeSelectorPage = require('../../../pages/tasks/TaskAssigneeSelectorModal').default; + return TaskAssigneeSelectorPage; + }, + name: 'Task_Assignee', + }, ]); const ReportSettingsModalStackNavigator = createModalStackNavigator([ diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 92a24c01ca0b..92b4414b99ae 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -263,6 +263,7 @@ export default { screens: { Task_Title: ROUTES.TASK_TITLE, Task_Description: ROUTES.TASK_DESCRIPTION, + Task_Assignee: ROUTES.TASK_ASSIGNEE, }, }, AddPersonalBankAccount: { diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 196507205ed4..5d9110ea6930 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -851,7 +851,9 @@ function getNewChatOptions(reports, personalDetails, betas = [], searchValue = ' */ function getShareDestinationOptions(reports, personalDetails, betas = [], searchValue = '', selectedOptions = [], excludeLogins = [], includeOwnedWorkspaceChats = true) { - return getOptions(reports, personalDetails, { + // We want to filter out any IOUs or expense reports + const filteredReports = _.filter(reports, (report) => !ReportUtils.isMoneyRequestReport(report)); + return getOptions(filteredReports, personalDetails, { betas, searchInputValue: searchValue.trim(), selectedOptions, diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 6160e4ca1e19..e4ee0c1a899f 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -209,6 +209,10 @@ function shouldReportActionBeVisible(reportAction, key) { return false; } + if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.TASKEDITED) { + return false; + } + // Filter out any unsupported reportAction types if (!_.has(CONST.REPORT.ACTIONS.TYPE, reportAction.actionName) && !_.contains(_.values(CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG), reportAction.actionName)) { return false; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index bf891ec11ecf..08ea0356eac6 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1429,6 +1429,7 @@ function buildOptimisticCreatedReportAction(ownerEmail) { actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, actorAccountID: currentUserAccountID, + actorEmail: currentUserEmail, message: [ { type: CONST.REPORT.MESSAGE.TYPE.TEXT, @@ -1455,6 +1456,46 @@ function buildOptimisticCreatedReportAction(ownerEmail) { }; } +/** + * Returns the necessary reportAction onyx data to indicate that a task report has been edited + * + * @param {String} ownerEmail + * @returns {Object} + */ + +function buildOptimisticEditedTaskReportAction(ownerEmail) { + return { + reportActionID: NumberUtils.rand64(), + actionName: CONST.REPORT.ACTIONS.TYPE.TASKEDITED, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + actorAccountID: currentUserAccountID, + actorEmail: currentUserEmail, + message: [ + { + type: CONST.REPORT.MESSAGE.TYPE.TEXT, + style: 'strong', + text: ownerEmail === currentUserEmail ? 'You' : ownerEmail, + }, + { + type: CONST.REPORT.MESSAGE.TYPE.TEXT, + style: 'normal', + text: ' edited this task', + }, + ], + person: [ + { + type: CONST.REPORT.MESSAGE.TYPE.TEXT, + style: 'strong', + text: lodashGet(allPersonalDetails, [currentUserEmail, 'displayName'], currentUserEmail), + }, + ], + automatic: false, + avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)), + created: DateUtils.getDBTime(), + shouldShow: false, + }; +} + /** * Returns the necessary reportAction onyx data to indicate that a chat has been archived * @@ -2068,6 +2109,7 @@ export { buildOptimisticChatReport, buildOptimisticClosedReportAction, buildOptimisticCreatedReportAction, + buildOptimisticEditedTaskReportAction, buildOptimisticIOUReport, buildOptimisticExpenseReport, buildOptimisticIOUReportAction, diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index d55d6b910c83..bad023813e8c 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -151,10 +151,122 @@ function createTaskAndNavigate(currentUserEmail, parentReportID, title, descript Navigation.navigate(ROUTES.getReportRoute(optimisticTaskReport.reportID)); } +/** + * @function editTask + * @param {object} report + * @param {string} ownerEmail + * @param {string} title + * @param {string} description + * @param {string} assignee + * @returns {object} action + * + */ + +function editTaskAndNavigate(report, ownerEmail, title, description, assignee) { + // Create the EditedReportAction on the task + const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(ownerEmail); + + // Sometimes title is undefined, so we need to check for that, and we provide it to multiple functions + const reportName = title || report.reportName; + + // If we make a change to the assignee, we want to add a comment to the assignee's chat + let optimisticAssigneeAddComment; + let assigneeChatReportID; + if (assignee && assignee !== report.assignee) { + assigneeChatReportID = ReportUtils.getChatByParticipants([assignee]).reportID; + optimisticAssigneeAddComment = ReportUtils.buildOptimisticTaskCommentReportAction(report.reportID, reportName, assignee, `Assigned a task to you: ${reportName}`); + } + + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, + value: {[editTaskReportAction.reportActionID]: editTaskReportAction}, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, + value: { + reportName, + description: description || report.description, + assignee: assignee || report.assignee, + }, + }, + ]; + const successData = []; + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, + value: {[editTaskReportAction.reportActionID]: {pendingAction: null}}, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, + value: {reportName: report.reportName, description: report.description, assignee: report.assignee}, + }, + ]; + + if (optimisticAssigneeAddComment) { + const currentTime = DateUtils.getDBTime(); + const lastAssigneeCommentText = ReportUtils.formatReportLastMessageText(optimisticAssigneeAddComment.reportAction.message[0].text); + + const optimisticAssigneeReport = { + lastVisibleActionCreated: currentTime, + lastMessageText: Str.htmlDecode(lastAssigneeCommentText), + lastActorEmail: ownerEmail, + lastReadTime: currentTime, + }; + + optimisticData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${assigneeChatReportID}`, + value: {[optimisticAssigneeAddComment.reportAction.reportActionID]: optimisticAssigneeAddComment.reportAction}, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${assigneeChatReportID}`, + value: optimisticAssigneeReport, + }, + ); + + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${assigneeChatReportID}`, + value: {[optimisticAssigneeAddComment.reportAction.reportActionID]: {pendingAction: null}}, + }); + } + + API.write( + 'EditTask', + { + taskReportID: report.reportID, + title: reportName, + description: description || report.description, + assignee: assignee || report.assignee, + editedTaskReportActionID: editTaskReportAction.reportActionID, + assigneeChatReportActionID: optimisticAssigneeAddComment ? optimisticAssigneeAddComment.reportAction.reportActionID : 0, + }, + {optimisticData, successData, failureData}, + ); + + Navigation.navigate(ROUTES.getReportRoute(report.reportID)); +} + +/** + * Sets the report info for the task being viewed + * + * @param {Object} report + */ +function setTaskReport(report) { + Onyx.merge(ONYXKEYS.TASK, {report}); +} + /** * Sets the title and description values for the task * @param {string} title - @param {string} description + * @param {string} description */ function setDetailsValue(title, description) { @@ -233,4 +345,55 @@ function clearOutTaskInfoAndNavigate(reportID) { Navigation.navigate(ROUTES.NEW_TASK_DETAILS); } -export {createTaskAndNavigate, setTitleValue, setDescriptionValue, setDetailsValue, setAssigneeValue, setShareDestinationValue, clearOutTaskInfo, clearOutTaskInfoAndNavigate}; +/** + * Get the assignee data + * + * @param {Object} details + * @returns {Object} + */ +function getAssignee(details) { + if (!details) { + return { + icons: [], + displayName: '', + subtitle: '', + }; + } + const source = ReportUtils.getAvatar(lodashGet(details, 'avatar', ''), lodashGet(details, 'login', '')); + return { + icons: [{source, type: 'avatar', name: details.login}], + displayName: details.displayName, + subtitle: details.login, + }; +} + +/** + * Get the share destination data + * @param {Object} reportID + * @param {Object} reports + * @param {Object} personalDetails + * @returns {Object} + * */ +function getShareDestination(reportID, reports, personalDetails) { + const report = lodashGet(reports, `report_${reportID}`, {}); + return { + icons: ReportUtils.getIcons(report, personalDetails), + displayName: ReportUtils.getReportName(report), + subtitle: ReportUtils.getChatRoomSubtitle(report), + }; +} + +export { + createTaskAndNavigate, + editTaskAndNavigate, + setTitleValue, + setDescriptionValue, + setTaskReport, + setDetailsValue, + setAssigneeValue, + setShareDestinationValue, + clearOutTaskInfo, + clearOutTaskInfoAndNavigate, + getAssignee, + getShareDestination, +}; diff --git a/src/pages/home/TaskHeaderView.js b/src/pages/home/TaskHeaderView.js index 9f00fe5e4f9c..10ea006a38a2 100644 --- a/src/pages/home/TaskHeaderView.js +++ b/src/pages/home/TaskHeaderView.js @@ -1,18 +1,78 @@ -import React from 'react'; +import React, {useEffect, useState} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; +import compose from '../../libs/compose'; +import styles from '../../styles/styles'; import reportPropTypes from '../reportPropTypes'; import MenuItemWithTopDescription from '../../components/MenuItemWithTopDescription'; +import TaskSelectorLink from '../../components/TaskSelectorLink'; import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; +import * as TaskUtils from '../../libs/actions/Task'; +import ONYXKEYS from '../../ONYXKEYS'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; const propTypes = { /** The report currently being looked at */ report: reportPropTypes.isRequired, + + /** All of the personal details for everyone */ + personalDetails: PropTypes.objectOf( + PropTypes.shape({ + /** Display name of the person */ + displayName: PropTypes.string, + + /** Avatar URL of the person */ + avatar: PropTypes.string, + + /** Login of the person */ + login: PropTypes.string, + }), + ), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + personalDetails: {}, }; function TaskHeaderView(props) { + const [assignee, setAssignee] = useState({}); + + useEffect(() => { + TaskUtils.clearOutTaskInfo(); + TaskUtils.setTaskReport(props.report); + if (!props.report.assignee) { + return; + } + const assigneeDetails = lodashGet(props.personalDetails, props.report.assignee); + const displayDetails = TaskUtils.getAssignee(assigneeDetails); + setAssignee(displayDetails); + }, [props]); return ( <> + {props.report.assignee ? ( + + Navigation.navigate(ROUTES.getTaskReportAssigneeRoute(props.report.reportID))} + label="common.to" + isNewTask={false} + /> + + ) : ( + Navigation.navigate(ROUTES.getTaskReportAssigneeRoute(props.report.reportID))} + /> + )} { const [assignee, setAssignee] = React.useState({}); const [shareDestination, setShareDestination] = React.useState({}); @@ -113,7 +81,7 @@ const NewTaskPage = (props) => { setSubmitError(true); return setErrorMessage(props.translate('newTaskPage.assigneeError')); } - const displayDetails = constructAssignee(assigneeDetails); + const displayDetails = TaskUtils.getAssignee(assigneeDetails); setAssignee(displayDetails); } @@ -128,7 +96,7 @@ const NewTaskPage = (props) => { // the share destination data if (props.task.shareDestination) { setParentReport(lodashGet(props.reports, `report_${props.task.shareDestination}`, {})); - const displayDetails = constructShareDestination(props.task.shareDestination, props.reports, props.personalDetails); + const displayDetails = TaskUtils.getShareDestination(props.task.shareDestination, props.reports, props.personalDetails); setShareDestination(displayDetails); } }, [props]); diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.js b/src/pages/tasks/TaskAssigneeSelectorModal.js index eb061c3c38d3..02d22d6b107c 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.js +++ b/src/pages/tasks/TaskAssigneeSelectorModal.js @@ -17,6 +17,7 @@ import compose from '../../libs/compose'; import personalDetailsPropType from '../personalDetailsPropType'; import reportPropTypes from '../reportPropTypes'; import Performance from '../../libs/Performance'; + import * as TaskUtils from '../../libs/actions/Task'; const propTypes = { @@ -29,10 +30,30 @@ const propTypes = { /** All reports shared with the user */ reports: PropTypes.objectOf(reportPropTypes), + /** URL Route params */ + route: PropTypes.shape({ + /** Params from the URL path */ + params: PropTypes.shape({ + /** reportID passed via route: /r/:reportID/title */ + reportID: PropTypes.string, + }), + }), + + // /** The report currently being looked at */ + // report: reportPropTypes.isRequired, + + /** Current user session */ + session: PropTypes.shape({ + email: PropTypes.string.isRequired, + }), + /** Grab the Share destination of the Task */ task: PropTypes.shape({ /** Share destination of the Task */ shareDestination: PropTypes.string, + + /** The task report if it's currently being edited */ + report: reportPropTypes, }), ...withLocalizePropTypes, @@ -42,9 +63,9 @@ const defaultProps = { betas: [], personalDetails: {}, reports: {}, - task: { - shareDestination: '', - }, + session: {}, + route: {}, + task: {}, }; const TaskAssigneeSelectorModal = (props) => { @@ -133,11 +154,22 @@ const TaskAssigneeSelectorModal = (props) => { return; } - if (option.alternateText) { + // Check to see if we're creating a new task + // If there's no route params, we're creating a new task + if (!props.route.params && option.alternateText) { // Clear out the state value, set the assignee and navigate back to the NewTaskPage setSearchValue(''); TaskUtils.setAssigneeValue(option.alternateText, props.task.shareDestination); - Navigation.goBack(); + return Navigation.goBack(); + } + + // Check to see if we're editing a task and if so, update the assignee + if (props.route.params.reportID && props.task.report.reportID === props.route.params.reportID) { + // There was an issue where sometimes a new assignee didn't have a DM thread + // This would cause the app to crash, so we need to make sure we have a DM thread + TaskUtils.setAssigneeValue(option.alternateText, props.task.shareDestination); + // Pass through the selected assignee + TaskUtils.editTaskAndNavigate(props.task.report, props.session.email, '', '', option.alternateText); } }; @@ -193,5 +225,8 @@ export default compose( task: { key: ONYXKEYS.TASK, }, + session: { + key: ONYXKEYS.SESSION, + }, }), )(TaskAssigneeSelectorModal); diff --git a/src/pages/tasks/TaskDescriptionPage.js b/src/pages/tasks/TaskDescriptionPage.js index 22a51fb114ff..d96682151f18 100644 --- a/src/pages/tasks/TaskDescriptionPage.js +++ b/src/pages/tasks/TaskDescriptionPage.js @@ -2,6 +2,7 @@ import _ from 'underscore'; import React, {useCallback, useRef} from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import ScreenWrapper from '../../components/ScreenWrapper'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; @@ -10,28 +11,30 @@ import ONYXKEYS from '../../ONYXKEYS'; import TextInput from '../../components/TextInput'; import styles from '../../styles/styles'; import Navigation from '../../libs/Navigation/Navigation'; -import reportPropTypes from '../reportPropTypes'; import compose from '../../libs/compose'; -import withReportOrNotFound from '../home/report/withReportOrNotFound'; +import reportPropTypes from '../reportPropTypes'; +import * as TaskUtils from '../../libs/actions/Task'; const propTypes = { - /** URL Route params */ - route: PropTypes.shape({ - /** Params from the URL path */ - params: PropTypes.shape({ - /** taskReportID passed via route: /r/:taskReportID/title */ - taskReportID: PropTypes.string, - }), - }).isRequired, + /** Current user session */ + session: PropTypes.shape({ + email: PropTypes.string.isRequired, + }), - /** The report currently being looked at */ - report: reportPropTypes.isRequired, + /** Task Report Info */ + task: PropTypes.shape({ + /** Title of the Task */ + report: reportPropTypes, + }), /* Onyx Props */ ...withLocalizePropTypes, }; -const defaultProps = {}; +const defaultProps = { + session: {}, + task: {}, +}; function TaskDescriptionPage(props) { /** @@ -52,9 +55,14 @@ function TaskDescriptionPage(props) { [props], ); - const submit = useCallback(() => { - // Functionality will be implemented in https://github.com/Expensify/App/issues/16856 - }, []); + const submit = useCallback( + (values) => { + // Set the description of the report in the store and then call TaskUtils.editTaskReport + // to update the description of the report on the server + TaskUtils.editTaskAndNavigate(props.task.report, props.session.email, '', values.description, ''); + }, + [props], + ); const inputRef = useRef(null); @@ -82,7 +90,7 @@ function TaskDescriptionPage(props) { inputID="description" name="description" label={props.translate('newTaskPage.description')} - defaultValue={props.report.description || ''} + defaultValue={(props.task.report && props.task.report.description) || ''} ref={(el) => (inputRef.current = el)} /> @@ -94,4 +102,14 @@ function TaskDescriptionPage(props) { TaskDescriptionPage.propTypes = propTypes; TaskDescriptionPage.defaultProps = defaultProps; -export default compose(withLocalize, withReportOrNotFound)(TaskDescriptionPage); +export default compose( + withLocalize, + withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + task: { + key: ONYXKEYS.TASK, + }, + }), +)(TaskDescriptionPage); diff --git a/src/pages/tasks/TaskTitlePage.js b/src/pages/tasks/TaskTitlePage.js index ae86f18bb775..9fb5e3b8a479 100644 --- a/src/pages/tasks/TaskTitlePage.js +++ b/src/pages/tasks/TaskTitlePage.js @@ -2,6 +2,7 @@ import _ from 'underscore'; import React, {useCallback, useRef} from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import ScreenWrapper from '../../components/ScreenWrapper'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; @@ -12,26 +13,28 @@ import styles from '../../styles/styles'; import Navigation from '../../libs/Navigation/Navigation'; import reportPropTypes from '../reportPropTypes'; import compose from '../../libs/compose'; -import withReportOrNotFound from '../home/report/withReportOrNotFound'; +import * as TaskUtils from '../../libs/actions/Task'; const propTypes = { - /** URL Route params */ - route: PropTypes.shape({ - /** Params from the URL path */ - params: PropTypes.shape({ - /** taskReportID passed via route: /r/:taskReportID/title */ - taskReportID: PropTypes.string, - }), - }).isRequired, + /** Task Report Info */ + task: PropTypes.shape({ + /** Title of the Task */ + report: reportPropTypes, + }), - /** The report currently being looked at */ - report: reportPropTypes.isRequired, + /** Current user session */ + session: PropTypes.shape({ + email: PropTypes.string.isRequired, + }), /* Onyx Props */ ...withLocalizePropTypes, }; -const defaultProps = {}; +const defaultProps = { + session: {}, + task: {}, +}; function TaskTitlePage(props) { /** @@ -52,9 +55,15 @@ function TaskTitlePage(props) { [props], ); - const submit = useCallback(() => { - // Functionality will be implemented in https://github.com/Expensify/App/issues/16856 - }, []); + const submit = useCallback( + (values) => { + // Set the description of the report in the store and then call TaskUtils.editTaskReport + // to update the description of the report on the server + + TaskUtils.editTaskAndNavigate(props.task.report, props.session.email, values.title, '', ''); + }, + [props], + ); const inputRef = useRef(null); @@ -82,7 +91,7 @@ function TaskTitlePage(props) { inputID="title" name="title" label={props.translate('newTaskPage.title')} - defaultValue={props.report.reportName || ''} + defaultValue={(props.task.report && props.task.report.reportName) || ''} ref={(el) => (inputRef.current = el)} /> @@ -94,4 +103,14 @@ function TaskTitlePage(props) { TaskTitlePage.propTypes = propTypes; TaskTitlePage.defaultProps = defaultProps; -export default compose(withLocalize, withReportOrNotFound)(TaskTitlePage); +export default compose( + withLocalize, + withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + task: { + key: ONYXKEYS.TASK, + }, + }), +)(TaskTitlePage); diff --git a/src/styles/styles.js b/src/styles/styles.js index bdb1ebd7469d..cef8513b4cf5 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -1688,10 +1688,8 @@ const styles = { taskSelectorLink: { alignSelf: 'center', - height: 42, width: '100%', padding: 6, - margin: 3, backgroundColor: themeColors.transparent, },