Skip to content

Commit

Permalink
Merge pull request #17992 from infinitered/dan-createTaskFrontend
Browse files Browse the repository at this point in the history
  • Loading branch information
thienlnam authored May 8, 2023
2 parents 7c48341 + 8f9abca commit cc78a08
Show file tree
Hide file tree
Showing 20 changed files with 1,425 additions and 40 deletions.
3 changes: 3 additions & 0 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export default {
// Contains all the private personal details of the user
PRIVATE_PERSONAL_DETAILS: 'private_personalDetails',

// Contains all the info for Tasks
TASK: 'task',

// Contains a list of all currencies available to the user - user can
// select a currency based on the list
CURRENCY_LIST: 'currencyList',
Expand Down
10 changes: 6 additions & 4 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ export default {
TASK_DESCRIPTION: 'r/:reportID/description',
getTaskReportTitleRoute: reportID => `r/${reportID}/title`,
getTaskReportDescriptionRoute: reportID => `r/${reportID}/description`,
NEW_TASK_ASSIGNEE: `${NEW_TASK}/assignee`,
NEW_TASK_SHARE_DESTINATION: `${NEW_TASK}/share-destination`,
NEW_TASK_DETAILS: `${NEW_TASK}/details`,
NEW_TASK_TITLE: `${NEW_TASK}/title`,
NEW_TASK_DESCRIPTION: `${NEW_TASK}/description`,
getTaskDetailsRoute: taskID => `task/details/${taskID}`,
SEARCH: 'search',
SET_PASSWORD_WITH_VALIDATE_CODE: 'setpassword/:accountID/:validateCode',
Expand All @@ -105,10 +110,7 @@ export default {
REPORT_PARTICIPANTS: 'r/:reportID/participants',
getReportParticipantsRoute: reportID => `r/${reportID}/participants`,
REPORT_PARTICIPANT: 'r/:reportID/participants/details',
getReportParticipantRoute: (
reportID,
login,
) => `r/${reportID}/participants/details?login=${encodeURIComponent(login)}`,
getReportParticipantRoute: (reportID, login) => `r/${reportID}/participants/details?login=${encodeURIComponent(login)}`,
REPORT_WITH_ID_DETAILS: 'r/:reportID/details',
getReportDetailsRoute: reportID => `r/${reportID}/details`,
REPORT_SETTINGS: 'r/:reportID/settings',
Expand Down
120 changes: 120 additions & 0 deletions src/components/TaskSelectorLink.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import React from 'react';
import {View, TouchableOpacity} from 'react-native';
import PropTypes from 'prop-types';
import styles from '../styles/styles';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import themeColors from '../styles/themes/default';
import variables from '../styles/variables';
import Text from './Text';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import * as StyleUtils from '../styles/StyleUtils';
import DisplayNames from './DisplayNames';
import MultipleAvatars from './MultipleAvatars';
import CONST from '../CONST';
import avatarPropTypes from './avatarPropTypes';

const propTypes = {
/** Array of avatar URLs or icons */
icons: PropTypes.arrayOf(avatarPropTypes),

/** The title to display */
text: PropTypes.string,

/** The description to display */
alternateText: PropTypes.string,

/** The function to call when the link is pressed */
onPress: PropTypes.func.isRequired,

/** Label for the Link */
label: PropTypes.string.isRequired,

/** Whether it is a share location */
isShareDestination: PropTypes.bool,

/** Whether the Touchable should be disabled */
disabled: PropTypes.bool,

...withLocalizePropTypes,
};

const defaultProps = {
icons: [],
text: '',
alternateText: '',
isShareDestination: false,
disabled: false,
};

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 (
<TouchableOpacity
style={[styles.flexRow, styles.taskSelectorLink, linkBottomMargin]}
onPress={props.onPress}
disabled={props.disabled}
>
<View style={[styles.flexRow, styles.containerWithSpaceBetween, styles.alignItemsCenter]}>
{props.icons.length !== 0 || props.text !== '' ? (
<View style={[styles.flexColumn, styles.justify, styles.alignItemsStart]}>
<Text style={[styles.label, styles.textWhite, styles.mb2]}>
{props.translate(props.label)}
</Text>
<View style={[styles.flexRow, styles.justifyContentCenter]}>
<View style={[styles.flexRow, styles.alignItemsCenter]}>
<MultipleAvatars
icons={props.icons}
size={CONST.AVATAR_SIZE.DEFAULT}
secondAvatarStyle={[StyleUtils.getBackgroundAndBorderStyle(themeColors.appBG)]}
/>
<View style={[styles.flexColumn]}>
<DisplayNames
accessibilityLabel={props.translate('accessibilityHints.chatUserDisplayNames')}
fullTitle={shortenedText}
tooltipEnabled={false}
numberOfLines={1}
textStyles={displayNameStyle}
shouldUseFullTitle={props.isShareDestination}
/>
{props.alternateText ? (
<Text style={alternateTextStyle} numberOfLines={1}>
{props.alternateText}
</Text>
) : null}
</View>
</View>
</View>
</View>
) : (
<Text style={[styles.textWhite, styles.textNormal]}>
{props.translate(props.label)}
</Text>
)}
{props.disabled ? null : (
<Icon
src={Expensicons.ArrowRight}
fill={themeColors.textLight}
width={variables.iconSizeSmall}
height={variables.iconSizeSmall}
inline
/>
)}
</View>
</TouchableOpacity>
);
};

TaskSelectorLink.defaultProps = defaultProps;
TaskSelectorLink.propTypes = propTypes;
TaskSelectorLink.displayName = 'TaskSelectorLink';

export default withLocalize(TaskSelectorLink);
9 changes: 8 additions & 1 deletion src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1153,12 +1153,19 @@ export default {
newTaskPage: {
task: 'Task',
assignTask: 'Assign task',
assignee: 'Assignee',
assigneeError: 'There was an error assigning this task, please try another assignee',
confirmTask: 'Confirm task',
confirmError: 'Please enter a title and select a share destination',
title: 'Title',
description: 'Description',
shareIn: 'Share in',
descriptionOptional: 'Description (optional)',
shareSomewhere: 'Share somewhere',
pleaseEnterTaskName: 'Please enter a title',
markAsComplete: 'Mark as complete',
markAsIncomplete: 'Mark as incomplete',
pleaseEnterTaskAssignee: 'Please select an assignee',
pleaseEnterTaskDestination: 'Please select a share destination',
},
statementPage: {
generatingPDF: 'We\'re generating your PDF right now. Please come back later!',
Expand Down
9 changes: 8 additions & 1 deletion src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -1154,12 +1154,19 @@ export default {
newTaskPage: {
task: 'Tarea',
assignTask: 'Asignar tarea',
assignee: 'Cesionario',
assigneeError: 'Hubo un error al asignar esta tarea, intente con otro cesionario',
confirmTask: 'Confirmar tarea',
confirmError: 'Por favor introduce un título y selecciona un destino de tarea',
title: 'Título',
description: 'Descripción',
shareIn: 'Compartir en',
descriptionOptional: 'Descripción (opcional)',
shareSomewhere: 'Compartir en algún lugar',
pleaseEnterTaskName: 'Por favor introduce un título',
markAsComplete: 'Marcar como completa',
markAsIncomplete: 'Marcar como incompleta',
pleaseEnterTaskAssignee: 'Por favor, asigna una persona a esta tarea',
pleaseEnterTaskDestination: 'Por favor, selecciona un destino de tarea',
},
statementPage: {
generatingPDF: 'Estamos generando tu PDF ahora mismo. ¡Por favor, vuelve más tarde!',
Expand Down
49 changes: 43 additions & 6 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,50 @@ const NewChatModalStackNavigator = createModalStackNavigator([{
name: 'NewChat_Root',
}]);

const NewTaskModalStackNavigator = createModalStackNavigator([{
getComponent: () => {
const NewTaskPage = require('../../../pages/NewTaskPage').default;
return NewTaskPage;
const NewTaskModalStackNavigator = createModalStackNavigator([
{
getComponent: () => {
const NewTaskPage = require('../../../pages/tasks/NewTaskPage').default;
return NewTaskPage;
},
name: 'NewTask_Root',
},
name: 'NewTask_Root',
}]);
{
getComponent: () => {
const NewTaskAssigneeSelectorPage = require('../../../pages/tasks/TaskAssigneeSelectorModal').default;
return NewTaskAssigneeSelectorPage;
},
name: 'NewTask_TaskAssigneeSelector',
},
{
getComponent: () => {
const NewTaskTaskShareDestinationPage = require('../../../pages/tasks/TaskShareDestinationSelectorModal').default;
return NewTaskTaskShareDestinationPage;
},
name: 'NewTask_TaskShareDestinationSelector',
},
{
getComponent: () => {
const NewTaskDetailsPage = require('../../../pages/tasks/NewTaskDetailsPage').default;
return NewTaskDetailsPage;
},
name: 'NewTask_Details',
},
{
getComponent: () => {
const NewTaskTitlePage = require('../../../pages/tasks/NewTaskTitlePage').default;
return NewTaskTitlePage;
},
name: 'NewTask_Title',
},
{
getComponent: () => {
const NewTaskDescriptionPage = require('../../../pages/tasks/NewTaskDescriptionPage').default;
return NewTaskDescriptionPage;
},
name: 'NewTask_Description',
},
]);

const SettingsModalStackNavigator = createModalStackNavigator([
{
Expand Down
5 changes: 5 additions & 0 deletions src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ export default {
NewTask: {
screens: {
NewTask_Root: ROUTES.NEW_TASK_WITH_REPORT_ID,
NewTask_TaskAssigneeSelector: ROUTES.NEW_TASK_ASSIGNEE,
NewTask_TaskShareDestinationSelector: ROUTES.NEW_TASK_SHARE_DESTINATION,
NewTask_Details: ROUTES.NEW_TASK_DETAILS,
NewTask_Title: ROUTES.NEW_TASK_TITLE,
NewTask_Description: ROUTES.NEW_TASK_DESCRIPTION,
},
},
Search: {
Expand Down
29 changes: 29 additions & 0 deletions src/libs/OptionsListUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,34 @@ function getNewChatOptions(
});
}

/**
* Build the options for the Share Destination for a Task
* *
* @param {Object} reports
* @param {Object} personalDetails
* @param {Array<String>} [betas]
* @param {String} [searchValue]
* @param {Array} [selectedOptions]
* @param {Array} [excludeLogins]
* @param {Boolean} [includeOwnedWorkspaceChats]
* @returns {Object}
*
*/

function getShareDestinationOptions(reports, personalDetails, betas = [], searchValue = '', selectedOptions = [], excludeLogins = [], includeOwnedWorkspaceChats = true) {
return getOptions(reports, personalDetails, {
betas,
searchInputValue: searchValue.trim(),
selectedOptions,
maxRecentReportsToShow: 5,
includeRecentReports: true,
includeMultipleParticipantReports: true,
includePersonalDetails: true,
excludeLogins,
includeOwnedWorkspaceChats,
});
}

/**
* Build the options for the Workspace Member Invite view
*
Expand Down Expand Up @@ -900,6 +928,7 @@ export {
isCurrentUser,
getSearchOptions,
getNewChatOptions,
getShareDestinationOptions,
getMemberInviteOptions,
getHeaderMessage,
getPersonalDetailsForLogins,
Expand Down
35 changes: 32 additions & 3 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -730,12 +730,14 @@ function getIcons(report, personalDetails, defaultIcon = null) {
result.source = Expensicons.ActiveRoomAvatar;
return [result];
}
if (isPolicyExpenseChat(report) || isExpenseReport(report)) {
if (isPolicyExpenseChat(report)) {
const workspaceName = lodashGet(allPolicies, [
`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, 'name',
]);

const policyExpenseChatAvatarSource = getWorkspaceAvatar(report);
const policyExpenseChatAvatarSource = lodashGet(allPolicies, [
`${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`, 'avatar',
]) || getDefaultWorkspaceAvatar(workspaceName);

// Return the workspace avatar if the user is the owner of the policy expense chat
if (report.isOwnPolicyExpenseChat && !isExpenseReport(report)) {
Expand Down Expand Up @@ -1382,6 +1384,32 @@ function buildOptimisticWorkspaceChats(policyID, policyName) {
};
}

/**
* Builds an optimistic Task Report with a randomly generated reportID
*
* @param {String} ownerEmail - Email of the person generating the Task.
* @param {String} assignee - Email of the other person participating in the Task.
* @param {String} parentReportID - Report ID of the chat where the Task is.
* @param {String} title - Task title.
* @param {String} description - Task description.
*
* @returns {Object}
*/

function buildOptimisticTaskReport(ownerEmail, assignee = null, parentReportID, title, description) {
return {
reportID: generateReportID(),
reportName: title,
description,
ownerEmail,
assignee,
type: CONST.REPORT.TYPE.TASK,
parentReportID,
stateNum: CONST.REPORT.STATE_NUM.OPEN,
statusNum: CONST.REPORT.STATUS.OPEN,
};
}

/**
* @param {Object} report
* @returns {Boolean}
Expand Down Expand Up @@ -1599,7 +1627,7 @@ function getChatByParticipants(newParticipantList) {
}

/**
* Attempts to find a report in onyx with the provided list of participants in given policy
* Attempts to find a report in onyx with the provided list of participants in given policy
* @param {Array} newParticipantList
* @param {String} policyID
* @returns {object|undefined}
Expand Down Expand Up @@ -1856,6 +1884,7 @@ export {
isUnread,
isUnreadWithMention,
buildOptimisticWorkspaceChats,
buildOptimisticTaskReport,
buildOptimisticChatReport,
buildOptimisticClosedReportAction,
buildOptimisticCreatedReportAction,
Expand Down
Loading

0 comments on commit cc78a08

Please sign in to comment.