Skip to content

Commit

Permalink
Merge pull request Expensify#20841 from Expensify/jasper-taskRedesign
Browse files Browse the repository at this point in the history
  • Loading branch information
thienlnam authored Jul 7, 2023
2 parents f98a9e2 + 3645840 commit 58f663a
Show file tree
Hide file tree
Showing 33 changed files with 558 additions and 297 deletions.
19 changes: 16 additions & 3 deletions src/components/Checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import themeColors from '../styles/themes/default';
import stylePropTypes from '../styles/stylePropTypes';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import * as StyleUtils from '../styles/StyleUtils';
import CONST from '../CONST';
import PressableWithFeedback from './Pressable/PressableWithFeedback';

Expand Down Expand Up @@ -34,6 +35,15 @@ const propTypes = {
/** Callback that is called when mousedown is triggered. */
onMouseDown: PropTypes.func,

/** The size of the checkbox container */
containerSize: PropTypes.number,

/** The border radius of the checkbox container */
containerBorderRadius: PropTypes.number,

/** The size of the caret (checkmark) */
caretSize: PropTypes.number,

/** A ref to forward to the Pressable */
forwardedRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({current: PropTypes.instanceOf(React.Component)})]),

Expand All @@ -50,6 +60,9 @@ const defaultProps = {
forwardedRef: undefined,
children: null,
onMouseDown: undefined,
containerSize: 20,
containerBorderRadius: 4,
caretSize: 14,
};

function Checkbox(props) {
Expand Down Expand Up @@ -89,7 +102,7 @@ function Checkbox(props) {
) : (
<View
style={[
styles.checkboxContainer,
StyleUtils.getCheckboxContainerStyle(props.containerSize, props.containerBorderRadius),
props.containerStyle,
props.isChecked && styles.checkedContainer,
props.hasError && styles.borderColorDanger,
Expand All @@ -101,8 +114,8 @@ function Checkbox(props) {
<Icon
src={Expensicons.Checkmark}
fill={themeColors.textLight}
height={14}
width={14}
height={props.caretSize}
width={props.caretSize}
/>
)}
</View>
Expand Down
2 changes: 1 addition & 1 deletion src/components/LHNOptionsList/OptionRowLHN.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function OptionRowLHN(props) {
!hasBrickError &&
(optionItem.isUnreadWithMention ||
(optionItem.hasOutstandingIOU && !optionItem.isIOUReportOwner) ||
(optionItem.isTaskReport && optionItem.isTaskAssignee && !optionItem.isTaskCompleted && !optionItem.isArchivedRoom));
(optionItem.isTaskReport && optionItem.isTaskAssignee && !optionItem.isCompletedTaskReport && !optionItem.isArchivedRoom));

/**
* Show the ReportActionContextMenu modal popover.
Expand Down
17 changes: 11 additions & 6 deletions src/components/MenuItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,15 @@ const defaultProps = {
floatRightAvatars: [],
shouldStackHorizontally: false,
avatarSize: undefined,
floatRightAvatarSize: undefined,
shouldBlockSelection: false,
hoverAndPressStyle: [],
furtherDetails: '',
furtherDetailsIcon: undefined,
isSmallAvatarSubscriptMenu: false,
title: '',
numberOfLinesTitle: 1,
shouldGreyOutWhenDisabled: true,
};

function MenuItem(props) {
Expand All @@ -77,7 +81,7 @@ function MenuItem(props) {
[
styles.flexShrink1,
styles.popoverMenuText,
props.icon && !_.isArray(props.icon) ? styles.ml3 : undefined,
props.icon && !_.isArray(props.icon) && (props.avatarSize === CONST.AVATAR_SIZE.SMALL ? styles.ml2 : styles.ml3),
props.shouldShowBasicTitle ? undefined : styles.textStrong,
props.shouldShowHeaderTitle ? styles.textHeadlineH1 : undefined,
props.numberOfLinesTitle > 1 ? styles.preWrap : styles.pre,
Expand Down Expand Up @@ -120,7 +124,7 @@ function MenuItem(props) {
StyleUtils.getButtonBackgroundColorStyle(getButtonState(props.focused || hovered, pressed, props.success, props.disabled, props.interactive), true),
(hovered || pressed) && props.hoverAndPressStyle,
...(_.isArray(props.wrapperStyle) ? props.wrapperStyle : [props.wrapperStyle]),
props.disabled && styles.buttonOpacityDisabled,
props.shouldGreyOutWhenDisabled && props.disabled && styles.buttonOpacityDisabled,
]}
disabled={props.disabled}
ref={props.forwardedRef}
Expand Down Expand Up @@ -150,7 +154,7 @@ function MenuItem(props) {
/>
)}
{Boolean(props.icon) && !_.isArray(props.icon) && (
<View style={[styles.popoverMenuIcon, ...props.iconStyles]}>
<View style={[styles.popoverMenuIcon, ...props.iconStyles, StyleUtils.getAvatarWidthStyle(props.avatarSize || CONST.AVATAR_SIZE.DEFAULT)]}>
{props.iconType === CONST.ICON_TYPE_ICON && (
<Icon
src={props.icon}
Expand All @@ -174,14 +178,15 @@ function MenuItem(props) {
)}
{props.iconType === CONST.ICON_TYPE_AVATAR && (
<Avatar
imageStyles={[styles.avatarNormal, styles.alignSelfCenter]}
imageStyles={[styles.alignSelfCenter]}
source={props.icon}
fallbackIcon={props.fallbackIcon}
size={props.avatarSize || CONST.AVATAR_SIZE.DEFAULT}
/>
)}
</View>
)}
<View style={[styles.justifyContentCenter, styles.menuItemTextContainer, styles.flex1]}>
<View style={[styles.justifyContentCenter, styles.flex1, StyleUtils.getMenuItemTextContainerStyle(props.isSmallAvatarSubscriptMenu)]}>
{Boolean(props.description) && props.shouldShowDescriptionOnTop && (
<Text
style={descriptionTextStyle}
Expand Down Expand Up @@ -254,7 +259,7 @@ function MenuItem(props) {
isHovered={hovered}
isPressed={pressed}
icons={props.floatRightAvatars}
size={props.avatarSize || fallbackAvatarSize}
size={props.floatRightAvatarSize || fallbackAvatarSize}
fallbackIcon={defaultWorkspaceAvatars.WorkspaceBuilding}
shouldStackHorizontally={props.shouldStackHorizontally}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/components/MoneyRequestHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function MoneyRequestHeader(props) {
report.ownerEmail = lodashGet(props, ['parentReport', 'ownerEmail'], '');
}
return (
<View style={[{backgroundColor: themeColors.highlightBG}, styles.pl0]}>
<View style={[styles.highlightBG, styles.pl0]}>
<HeaderWithBackButton
shouldShowAvatarWithDisplay
shouldShowPinButton={props.isSingleTransactionView}
Expand Down
8 changes: 5 additions & 3 deletions src/components/ReportActionItem/TaskAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,16 @@ function TaskAction(props) {
messageLinkText = props.translate('task.messages.reopened');
break;
default:
messageLinkText = props.translate('newTaskPage.task');
messageLinkText = props.translate('task.task');
}

return (
<>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter]}>
<Text style={styles.chatItemMessageLink}>{messageLinkText}</Text>
<Text style={[styles.chatItemMessage]}>{` ${taskReportName}`}</Text>
<Text>
<Text style={styles.chatItemMessageLink}>{messageLinkText}</Text>
<Text style={[styles.chatItemMessage]}>{` ${taskReportName}`}</Text>
</Text>
</View>
</>
);
Expand Down
15 changes: 8 additions & 7 deletions src/components/ReportActionItem/TaskPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ 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';
import * as Task from '../../libs/actions/Task';
import * as ReportUtils from '../../libs/ReportUtils';
import RenderHTML from '../RenderHTML';
import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback';
import personalDetailsPropType from '../../pages/personalDetailsPropType';
Expand Down Expand Up @@ -65,7 +66,7 @@ function TaskPreview(props) {
? props.taskReport.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && props.taskReport.statusNum === CONST.REPORT.STATUS.APPROVED
: props.action.childStateNum === CONST.REPORT.STATE_NUM.SUBMITTED && props.action.childStatusNum === CONST.REPORT.STATUS.APPROVED;
const taskTitle = props.taskReport.reportName || props.action.childReportName;
const taskAssigneeAccountID = TaskUtils.getTaskAssigneeAccountID(props.taskReport);
const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(props.taskReport);
const taskAssignee = lodashGet(props.personalDetailsList, [taskAssigneeAccountID, 'login'], lodashGet(props.personalDetailsList, [taskAssigneeAccountID, 'displayName'], ''));
const htmlForTaskPreview = taskAssignee ? `<comment><mention-user>@${taskAssignee}</mention-user> ${taskTitle}</comment>` : `<comment>${taskTitle}</comment>`;

Expand All @@ -75,22 +76,22 @@ function TaskPreview(props) {
onPress={() => Navigation.navigate(ROUTES.getReportRoute(props.taskReportID))}
style={[styles.flexRow, styles.justifyContentBetween]}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
accessibilityLabel={props.translate('newTaskPage.task')}
accessibilityLabel={props.translate('task.task')}
>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsStart]}>
<Checkbox
style={[styles.mr2]}
containerStyle={[styles.taskCheckbox]}
isChecked={isTaskCompleted}
disabled={TaskUtils.isTaskCanceled(props.taskReport)}
disabled={ReportUtils.isCanceledTaskReport(props.taskReport)}
onPress={() => {
if (isTaskCompleted) {
TaskUtils.reopenTask(props.taskReportID, taskTitle);
Task.reopenTask(props.taskReportID, taskTitle);
} else {
TaskUtils.completeTask(props.taskReportID, taskTitle);
Task.completeTask(props.taskReportID, taskTitle);
}
}}
accessibilityLabel={props.translate('newTaskPage.task')}
accessibilityLabel={props.translate('task.task')}
/>
<RenderHTML html={htmlForTaskPreview} />
</View>
Expand Down
148 changes: 148 additions & 0 deletions src/components/ReportActionItem/TaskView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React, {useEffect} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import reportPropTypes from '../../pages/reportPropTypes';
import withLocalize, {withLocalizePropTypes} from '../withLocalize';
import withWindowDimensions from '../withWindowDimensions';
import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '../withCurrentUserPersonalDetails';
import compose from '../../libs/compose';
import Navigation from '../../libs/Navigation/Navigation';
import ROUTES from '../../ROUTES';
import MenuItemWithTopDescription from '../MenuItemWithTopDescription';
import MenuItem from '../MenuItem';
import styles from '../../styles/styles';
import * as ReportUtils from '../../libs/ReportUtils';
import * as PersonalDetailsUtils from '../../libs/PersonalDetailsUtils';
import * as UserUtils from '../../libs/UserUtils';
import * as StyleUtils from '../../styles/StyleUtils';
import * as Task from '../../libs/actions/Task';
import CONST from '../../CONST';
import Checkbox from '../Checkbox';
import convertToLTR from '../../libs/convertToLTR';
import Text from '../Text';
import Icon from '../Icon';
import getButtonState from '../../libs/getButtonState';
import PressableWithSecondaryInteraction from '../PressableWithSecondaryInteraction';
import * as Session from '../../libs/actions/Session';
import * as Expensicons from '../Icon/Expensicons';

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

/** Whether we should display the horizontal rule below the component */
shouldShowHorizontalRule: PropTypes.bool.isRequired,

...withLocalizePropTypes,

...withCurrentUserPersonalDetailsPropTypes,
};

function TaskView(props) {
useEffect(() => {
Task.setTaskReport({...props.report, isExistingTaskReport: true});
}, [props.report]);

const taskTitle = convertToLTR(props.report.reportName || '');
const isCompleted = ReportUtils.isCompletedTaskReport(props.report);
const isOpen = ReportUtils.isOpenTaskReport(props.report);
const isCanceled = ReportUtils.isCanceledTaskReport(props.report);

return (
<View>
<PressableWithSecondaryInteraction
onPress={Session.checkIfActionIsAllowed((e) => {
if (e && e.type === 'click') {
e.currentTarget.blur();
}

Navigation.navigate(ROUTES.getTaskReportTitleRoute(props.report.reportID));
})}
style={({hovered, pressed}) => [styles.ph5, styles.pv2, StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, false, !isOpen), true)]}
ref={props.forwardedRef}
disabled={!isOpen}
accessibilityLabel={taskTitle || props.translate('task.task')}
>
{({hovered, pressed}) => (
<>
<Text style={styles.taskTitleDescription}>Title</Text>
<View style={[styles.flexRow, styles.alignItemsTop, styles.flex1]}>
<Checkbox
onPress={() => (isCompleted ? Task.reopenTask(props.report.reportID, taskTitle) : Task.completeTask(props.report.reportID, taskTitle))}
isChecked={isCompleted}
style={styles.taskMenuItemCheckbox}
containerSize={24}
containerBorderRadius={8}
caretSize={16}
accessibilityLabel={taskTitle || props.translate('task.task')}
disabled={isCanceled}
/>
<View style={[styles.flexRow, styles.flex1]}>
<Text
numberOfLines={3}
style={styles.taskTitleMenuItem}
>
{taskTitle}
</Text>
</View>
{isOpen && (
<View style={styles.taskRightIconContainer}>
<Icon
additionalStyles={[styles.alignItemsCenter]}
src={Expensicons.ArrowRight}
fill={StyleUtils.getIconFillColor(getButtonState(hovered, pressed, false, !isOpen))}
/>
</View>
)}
</View>
</>
)}
</PressableWithSecondaryInteraction>
<MenuItemWithTopDescription
description={props.translate('task.description')}
title={props.report.description || ''}
onPress={() => Navigation.navigate(ROUTES.getTaskReportDescriptionRoute(props.report.reportID))}
shouldShowRightIcon={isOpen}
disabled={!isOpen}
wrapperStyle={[styles.pv2]}
numberOfLinesTitle={3}
shouldGreyOutWhenDisabled={false}
/>
{props.report.managerID ? (
<MenuItem
label={props.translate('task.assignee')}
title={ReportUtils.getDisplayNameForParticipant(props.report.managerID)}
icon={UserUtils.getAvatar(
PersonalDetailsUtils.getPersonalDetailsByIDs([props.report.managerID], props.currentUserPersonalDetails.accountID)[0].avatar,
props.report.managerID,
)}
iconType={CONST.ICON_TYPE_AVATAR}
avatarSize={CONST.AVATAR_SIZE.SMALLER}
titleStyle={styles.assigneeTextStyle}
onPress={() => Navigation.navigate(ROUTES.getTaskReportAssigneeRoute(props.report.reportID))}
shouldShowRightIcon={isOpen}
disabled={!isOpen}
wrapperStyle={[styles.pv2]}
isSmallAvatarSubscriptMenu
shouldGreyOutWhenDisabled={false}
/>
) : (
<MenuItemWithTopDescription
description={props.translate('task.assignee')}
onPress={() => Navigation.navigate(ROUTES.getTaskReportAssigneeRoute(props.report.reportID))}
shouldShowRightIcon={isOpen}
disabled={!isOpen}
wrapperStyle={[styles.pv2]}
shouldGreyOutWhenDisabled={false}
/>
)}

{props.shouldShowHorizontalRule && <View style={styles.taskHorizontalRule} />}
</View>
);
}

TaskView.propTypes = propTypes;
TaskView.displayName = 'TaskView';

export default compose(withWindowDimensions, withLocalize, withCurrentUserPersonalDetails)(TaskView);
Loading

0 comments on commit 58f663a

Please sign in to comment.