Skip to content

Commit

Permalink
Merge pull request #18806 from Expensify/jack-completeTask
Browse files Browse the repository at this point in the history
Complete Task Front End
  • Loading branch information
arosiclair authored May 15, 2023
2 parents 2266274 + 41351a0 commit 92f87f7
Show file tree
Hide file tree
Showing 12 changed files with 348 additions and 120 deletions.
1 change: 1 addition & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ const CONST = {
IOU: 'IOU',
RENAMED: 'RENAMED',
CHRONOSOOOLIST: 'CHRONOSOOOLIST',
TASKCOMPLETED: 'TASKCOMPLETED',
POLICYCHANGELOG: {
ADD_APPROVER_RULE: 'POLICYCHANGELOG_ADD_APPROVER_RULE',
ADD_CATEGORY: 'POLICYCHANGELOG_ADD_CATEGORY',
Expand Down
81 changes: 81 additions & 0 deletions src/components/ReportActionItem/TaskAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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';
import * as StyleUtils from '../../styles/StyleUtils';
import getButtonState from '../../libs/getButtonState';
import CONST from '../../CONST';

const propTypes = {
/** The ID of the associated taskReport */
taskReportID: PropTypes.string.isRequired,

/** Whether the task preview is hovered so we can modify its style */
isHovered: PropTypes.bool,

/** Name of the reportAction action */
actionName: 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: {},
isHovered: false,
};
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');
return (
<Pressable
onPress={() => Navigation.navigate(ROUTES.getReportRoute(taskReportID))}
style={[styles.flexRow, styles.justifyContentBetween]}
>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter]}>
<Text style={styles.chatItemMessageLink}>{messageLinkText}</Text>
<Text style={[styles.chatItemMessage]}>{` ${taskReportName}`}</Text>
</View>
<Icon
src={Expensicons.ArrowRight}
fill={StyleUtils.getIconFillColor(getButtonState(props.isHovered))}
/>
</Pressable>
);
};

TaskAction.propTypes = propTypes;
TaskAction.defaultProps = defaultProps;
TaskAction.displayName = 'TaskAction';

export default compose(
withLocalize,
withOnyx({
taskReport: {
key: ({taskReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`,
},
}),
)(TaskAction);
9 changes: 7 additions & 2 deletions src/components/ReportActionItem/TaskPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import getButtonState from '../../libs/getButtonState';
import Navigation from '../../libs/Navigation/Navigation';
import ROUTES from '../../ROUTES';
import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes';
import * as TaskUtils from '../../libs/actions/Task';

const propTypes = {
/** The ID of the associated taskReport */
Expand Down Expand Up @@ -54,8 +55,9 @@ const TaskPreview = (props) => {
// Other linked reportActions will only contain the taskReportID and we will grab the details from there
const isTaskCompleted =
(props.taskReport.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && props.taskReport.statusNum === CONST.REPORT.STATUS.APPROVED) ||
(props.action.childStateNum === CONST.REPORT.STATE_NUM.CLOSED && props.action.childStatusNum === CONST.REPORT.STATUS.APPROVED);
(props.action.childStateNum === CONST.REPORT.STATE_NUM.SUBMITTED && props.action.childStatusNum === CONST.REPORT.STATUS.APPROVED);
const taskTitle = props.action.taskTitle || props.taskReport.reportName;
const parentReportID = props.action.parentReportID || props.taskReport.parentReportID;

return (
<Pressable
Expand All @@ -68,7 +70,10 @@ const TaskPreview = (props) => {
containerStyle={[styles.taskCheckbox]}
isChecked={isTaskCompleted}
onPress={() => {
// Being implemented in https://github.com/Expensify/App/issues/16858
if (isTaskCompleted) {
return;
}
TaskUtils.completeTask(props.taskReportID, parentReportID, taskTitle);
}}
/>
<Text>{taskTitle}</Text>
Expand Down
120 changes: 120 additions & 0 deletions src/components/TaskHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React, {useEffect} from 'react';
import {View, TouchableOpacity} from 'react-native';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import reportPropTypes from '../pages/reportPropTypes';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import * as ReportUtils from '../libs/ReportUtils';
import * as Expensicons from './Icon/Expensicons';
import Text from './Text';
import participantPropTypes from './participantPropTypes';
import Avatar from './Avatar';
import styles from '../styles/styles';
import themeColors from '../styles/themes/default';
import CONST from '../CONST';
import withWindowDimensions from './withWindowDimensions';
import compose from '../libs/compose';
import Navigation from '../libs/Navigation/Navigation';
import ROUTES from '../ROUTES';
import Icon from './Icon';
import MenuItemWithTopDescription from './MenuItemWithTopDescription';
import Button from './Button';
import * as TaskUtils from '../libs/actions/Task';

const propTypes = {
/** The report currently being looked at */
report: reportPropTypes.isRequired,

/** Personal details so we can get the ones for the report participants */
personalDetails: PropTypes.objectOf(participantPropTypes).isRequired,

...withLocalizePropTypes,
};

function TaskHeader(props) {
const title = ReportUtils.getReportName(props.report);
const assigneeName = ReportUtils.getDisplayNameForParticipant(props.report.managerEmail);
const assigneeAvatar = ReportUtils.getAvatar(lodashGet(props.personalDetails, [props.report.managerEmail, 'avatar']), props.report.managerEmail);
const isOpen = props.report.stateNum === CONST.REPORT.STATE_NUM.OPEN && props.report.statusNum === CONST.REPORT.STATUS.OPEN;
const isCompleted = props.report.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && props.report.statusNum === CONST.REPORT.STATUS.APPROVED;
const parentReportID = props.report.parentReportID;

useEffect(() => {
TaskUtils.setTaskReport(props.report);
}, [props.report]);

return (
<View style={styles.borderBottom}>
<View style={[{backgroundColor: themeColors.highlightBG}, styles.pl0]}>
<View style={[styles.ph5, styles.pb5]}>
<Text style={[styles.textLabelSupporting, styles.lh16]}>{props.translate('common.to')}</Text>
<TouchableOpacity
onPress={() => Navigation.navigate(ROUTES.getTaskReportAssigneeRoute(props.report.reportID))}
disabled={!isOpen}
>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.justifyContentBetween, styles.pv3]}>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.justifyContentBetween]}>
{props.report.managerEmail && (
<>
<Avatar
source={assigneeAvatar}
type={CONST.ICON_TYPE_AVATAR}
name={assigneeName}
size={CONST.AVATAR_SIZE.HEADER}
/>
<View style={[styles.flexColumn, styles.ml3]}>
<Text
style={[styles.headerText, styles.pre]}
numberOfLines={1}
>
{assigneeName}
</Text>
</View>
</>
)}
</View>
<View style={[styles.flexRow]}>
{isCompleted ? (
<>
<Text>{props.translate('task.completed')}</Text>
<View style={styles.moneyRequestHeaderCheckmark}>
<Icon
src={Expensicons.Checkmark}
fill={themeColors.iconSuccessFill}
/>
</View>
</>
) : (
<Button
success
medium
text={props.translate('newTaskPage.markAsDone')}
onPress={() => TaskUtils.completeTask(props.report.reportID, parentReportID, title)}
/>
)}
</View>
</View>
</TouchableOpacity>
</View>
</View>
<MenuItemWithTopDescription
shouldShowHeaderTitle
title={props.report.reportName}
description="Task"
onPress={() => Navigation.navigate(ROUTES.getTaskReportTitleRoute(props.report.reportID))}
disabled={!isOpen}
/>
<MenuItemWithTopDescription
title={lodashGet(props.report, 'description', '')}
description="Description"
onPress={() => Navigation.navigate(ROUTES.getTaskReportDescriptionRoute(props.report.reportID))}
disabled={!isOpen}
/>
</View>
);
}

TaskHeader.propTypes = propTypes;
TaskHeader.displayName = 'TaskHeader';

export default compose(withWindowDimensions, withLocalize)(TaskHeader);
8 changes: 7 additions & 1 deletion src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1188,11 +1188,17 @@ export default {
descriptionOptional: 'Description (optional)',
shareSomewhere: 'Share somewhere',
pleaseEnterTaskName: 'Please enter a title',
markAsComplete: 'Mark as complete',
markAsDone: 'Mark as done',
markAsIncomplete: 'Mark as incomplete',
pleaseEnterTaskAssignee: 'Please select an assignee',
pleaseEnterTaskDestination: 'Please select a share destination',
},
task: {
completed: 'Completed',
messages: {
completed: 'Completed task',
},
},
statementPage: {
generatingPDF: "We're generating your PDF right now. Please come back later!",
},
Expand Down
8 changes: 7 additions & 1 deletion src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -1193,11 +1193,17 @@ export default {
descriptionOptional: 'Descripción (opcional)',
shareSomewhere: 'Compartir en algún lugar',
pleaseEnterTaskName: 'Por favor introduce un título',
markAsComplete: 'Marcar como completa',
markAsDone: 'Marcar como hecho',
markAsIncomplete: 'Marcar como incompleta',
pleaseEnterTaskAssignee: 'Por favor, asigna una persona a esta tarea',
pleaseEnterTaskDestination: 'Por favor, selecciona un destino de tarea',
},
task: {
completed: 'Completada',
messages: {
completed: 'tarea completada',
},
},
statementPage: {
generatingPDF: 'Estamos generando tu PDF ahora mismo. ¡Por favor, vuelve más tarde!',
},
Expand Down
44 changes: 42 additions & 2 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -1165,9 +1165,10 @@ function buildOptimisticAddCommentReportAction(text, file) {
* @param {String} taskTitle - Title of the task
* @param {String} taskAssignee - Email of the person assigned to the task
* @param {String} text - Text of the comment
* @param {String} parentReportID - Report ID of the parent report
* @returns {Object}
*/
function buildOptimisticTaskCommentReportAction(taskReportID, taskTitle, taskAssignee, text) {
function buildOptimisticTaskCommentReportAction(taskReportID, taskTitle, taskAssignee, text, parentReportID) {
const reportAction = buildOptimisticAddCommentReportAction(text);
reportAction.reportAction.message[0].taskReportID = taskReportID;

Expand All @@ -1178,6 +1179,7 @@ function buildOptimisticTaskCommentReportAction(taskReportID, taskTitle, taskAss
taskReportID: reportAction.reportAction.message[0].taskReportID,
};
reportAction.reportAction.childReportID = taskReportID;
reportAction.reportAction.parentReportID = parentReportID;
reportAction.reportAction.childType = CONST.REPORT.TYPE.TASK;
reportAction.reportAction.taskTitle = taskTitle;
reportAction.reportAction.taskAssignee = taskAssignee;
Expand Down Expand Up @@ -1378,6 +1380,43 @@ function buildOptimisticIOUReportAction(type, amount, currency, comment, partici
};
}

function buildOptimisticTaskReportAction(taskReportID, actionName, message = '') {
const originalMessage = {
taskReportID,
type: actionName,
text: message,
};

return {
actionName,
actorAccountID: currentUserAccountID,
actorEmail: currentUserEmail,
automatic: false,
avatar: lodashGet(currentUserPersonalDetails, 'avatar', getDefaultAvatar(currentUserEmail)),
isAttachment: false,
originalMessage,
message: [
{
text: message,
taskReportID,
type: CONST.REPORT.MESSAGE.TYPE.TEXT,
},
],
person: [
{
style: 'strong',
text: lodashGet(currentUserPersonalDetails, 'displayName', currentUserEmail),
type: 'TEXT',
},
],
reportActionID: NumberUtils.rand64(),
shouldShow: true,
created: DateUtils.getDBTime(),
isFirstItem: false,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
};
}

/**
* Builds an optimistic chat report with a randomly generated reportID and as much information as we currently have
*
Expand Down Expand Up @@ -1629,7 +1668,7 @@ function buildOptimisticTaskReport(ownerEmail, assignee = null, parentReportID,
reportName: title,
description,
ownerEmail,
assignee,
managerEmail: assignee,
type: CONST.REPORT.TYPE.TASK,
parentReportID,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
Expand Down Expand Up @@ -2129,6 +2168,7 @@ export {
buildOptimisticIOUReport,
buildOptimisticExpenseReport,
buildOptimisticIOUReportAction,
buildOptimisticTaskReportAction,
buildOptimisticAddCommentReportAction,
buildOptimisticTaskCommentReportAction,
shouldReportBeInOptionList,
Expand Down
Loading

0 comments on commit 92f87f7

Please sign in to comment.