Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tasks Detailed View #17940

Merged
merged 26 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e3b8cc0
Add check if it is a task
thienlnam Apr 24, 2023
2f1ac41
Update header to show multi select
thienlnam Apr 24, 2023
3d1789f
add new h1 headline
thienlnam Apr 24, 2023
d807c31
add it as an option in MenuItem
thienlnam Apr 24, 2023
822b1be
add default / cleanup
thienlnam Apr 24, 2023
190b82d
Add Task report specific methods
thienlnam Apr 24, 2023
1dbb30a
Move into component
thienlnam Apr 24, 2023
af5351e
use the passed in report
thienlnam Apr 25, 2023
b40ae92
Add routing for title page
thienlnam Apr 26, 2023
a4876a0
Update ModalStackNavigators.js
thienlnam Apr 26, 2023
30a8b3d
add new push to page title
thienlnam Apr 26, 2023
8bffea3
update title to pass in task route
thienlnam Apr 26, 2023
5d83a7b
navigation for push to page for description
thienlnam Apr 26, 2023
a23136b
Add description page
thienlnam Apr 26, 2023
65f7e6b
Clean up TaskHeaderView
thienlnam Apr 26, 2023
2d6e0c6
update to use ref
thienlnam Apr 26, 2023
8d05766
Merge branch 'main' into jack-DetailedView
thienlnam Apr 26, 2023
9f45a62
style / cleanup
thienlnam Apr 26, 2023
34a9c79
update options shown in header
thienlnam Apr 26, 2023
2ebebb4
revert shouldShowCallButton changes
thienlnam Apr 26, 2023
8b583ba
clean up
thienlnam Apr 26, 2023
5897950
Merge branch 'main' into jack-DetailedView
thienlnam May 2, 2023
feb14f2
Merge branch 'main' into jack-DetailedView
thienlnam May 2, 2023
20bcf89
add ref
thienlnam May 2, 2023
dd8ef23
autofocus
thienlnam May 2, 2023
4e0b418
Update TaskDescriptionPage.js
thienlnam May 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ const CONST = {
CHAT: 'chat',
EXPENSE: 'expense',
IOU: 'iou',
TASK: 'task',
},
CHAT_TYPE: {
POLICY_ANNOUNCE: 'policyAnnounce',
Expand Down
1 change: 1 addition & 0 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export default {
NEW_ROOM_FORM: 'newRoomForm',
ROOM_SETTINGS_FORM: 'roomSettingsForm',
NEW_TASK_FORM: 'newTaskForm',
EDIT_TASK_FORM: 'editTaskForm',
MONEY_REQUEST_DESCRIPTION_FORM: 'moneyRequestDescriptionForm',
},

Expand Down
4 changes: 4 additions & 0 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ export default {
getIouDetailsRoute: (chatReportID, iouReportID) => `iou/details/${chatReportID}/${iouReportID}`,
getNewTaskRoute: reportID => `${NEW_TASK}/${reportID}`,
NEW_TASK_WITH_REPORT_ID: `${NEW_TASK}/:reportID?`,
TASK_TITLE: 'r/:reportID/title',
TASK_DESCRIPTION: 'r/:reportID/description',
getTaskReportTitleRoute: reportID => `r/${reportID}/title`,
getTaskReportDescriptionRoute: reportID => `r/${reportID}/description`,
getTaskDetailsRoute: taskID => `task/details/${taskID}`,
SEARCH: 'search',
SET_PASSWORD_WITH_VALIDATE_CODE: 'setpassword/:accountID/:validateCode',
Expand Down
2 changes: 2 additions & 0 deletions src/components/MenuItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const defaultProps = {
shouldShowSelectedState: false,
shouldShowBasicTitle: false,
shouldShowDescriptionOnTop: false,
shouldShowHeaderTitle: false,
wrapperStyle: [],
style: styles.popoverMenuItem,
titleStyle: {},
Expand Down Expand Up @@ -67,6 +68,7 @@ const MenuItem = (props) => {
(props.interactive && props.disabled ? {...styles.disabledText, ...styles.userSelectNone} : undefined),
styles.pre,
styles.ltr,
(props.shouldShowHeaderTitle ? styles.textHeadlineH1 : undefined),
(_.contains(props.style, styles.offlineFeedback.deleted) ? styles.offlineFeedback.deleted : undefined),
], props.titleStyle);
const descriptionVerticalMargin = props.shouldShowDescriptionOnTop ? styles.mb1 : styles.mt1;
Expand Down
3 changes: 3 additions & 0 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1148,11 +1148,14 @@ export default {
},
},
newTaskPage: {
task: 'Task',
assignTask: 'Assign task',
title: 'Title',
description: 'Description',
shareIn: 'Share in',
pleaseEnterTaskName: 'Please enter a title',
markAsComplete: 'Mark as complete',
markAsIncomplete: 'Mark as incomplete',
},
statementPage: {
generatingPDF: 'We\'re generating your PDF right now. Please come back later!',
Expand Down
3 changes: 3 additions & 0 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -1149,11 +1149,14 @@ export default {
},
},
newTaskPage: {
task: 'Tarea',
assignTask: 'Asignar tarea',
title: 'Título',
description: 'Descripción',
shareIn: 'Compartir en',
pleaseEnterTaskName: 'Por favor introduce un título',
markAsComplete: 'Marcar como completa',
markAsIncomplete: 'Marcar como incompleta',
},
statementPage: {
generatingPDF: 'Estamos generando tu PDF ahora mismo. ¡Por favor, vuelve más tarde!',
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/AppNavigator/AuthScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,12 @@ class AuthScreens extends React.Component {
component={ModalStackNavigators.NewTaskModalStackNavigator}
listeners={modalScreenListeners}
/>
<RootStack.Screen
name="Task_Details"
options={modalScreenOptions}
component={ModalStackNavigators.TaskModalStackNavigator}
listeners={modalScreenListeners}
/>
<RootStack.Screen
name="IOU_Bill"
options={modalScreenOptions}
Expand Down
18 changes: 18 additions & 0 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,23 @@ const ReportDetailsModalStackNavigator = createModalStackNavigator([{
name: 'Report_Details_Root',
}]);

const TaskModalStackNavigator = createModalStackNavigator([
{
getComponent: () => {
const TaskTitlePage = require('../../../pages/tasks/TaskTitlePage').default;
return TaskTitlePage;
},
name: 'Task_Title',
},
{
getComponent: () => {
const TaskDescriptionPage = require('../../../pages/tasks/TaskDescriptionPage').default;
return TaskDescriptionPage;
},
name: 'Task_Description',
},
]);

const ReportSettingsModalStackNavigator = createModalStackNavigator([{
getComponent: () => {
const ReportSettingsPage = require('../../../pages/ReportSettingsPage').default;
Expand Down Expand Up @@ -554,6 +571,7 @@ export {
IOUDetailsModalStackNavigator,
DetailsModalStackNavigator,
ReportDetailsModalStackNavigator,
TaskModalStackNavigator,
ReportSettingsModalStackNavigator,
ReportParticipantsModalStackNavigator,
SearchModalStackNavigator,
Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ export default {
IOU_Details_Add_Debit_Card: ROUTES.IOU_DETAILS_ADD_DEBIT_CARD,
},
},
Task_Details: {
screens: {
Task_Title: ROUTES.TASK_TITLE,
Task_Description: ROUTES.TASK_DESCRIPTION,
},
},
AddPersonalBankAccount: {
screens: {
AddPersonalBankAccount_Root: ROUTES.BANK_ACCOUNT_PERSONAL,
Expand Down
11 changes: 11 additions & 0 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ function isIOUReport(report) {
return lodashGet(report, 'type') === CONST.REPORT.TYPE.IOU;
}

/**
* Checks if a report is a task report.
*
* @param {Object} report
* @returns {Boolean}
*/
function isTaskReport(report) {
return lodashGet(report, 'type') === CONST.REPORT.TYPE.TASK;
}

/**
* Checks if a report is an IOU or expense report.
*
Expand Down Expand Up @@ -1854,6 +1864,7 @@ export {
getDisplayNameForParticipant,
isExpenseReport,
isIOUReport,
isTaskReport,
isMoneyRequestReport,
chatIncludesChronos,
getAvatar,
Expand Down
45 changes: 45 additions & 0 deletions src/pages/home/HeaderView.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import variables from '../../styles/variables';
import colors from '../../styles/colors';
import reportPropTypes from '../reportPropTypes';
import ONYXKEYS from '../../ONYXKEYS';
import ThreeDotsMenu from '../../components/ThreeDotsMenu';

const propTypes = {
/** Toggles the navigationMenu open and closed */
Expand Down Expand Up @@ -64,6 +65,7 @@ const HeaderView = (props) => {
const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(participantPersonalDetails, isMultipleParticipant);
const isChatRoom = ReportUtils.isChatRoom(props.report);
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(props.report);
const isTaskReport = ReportUtils.isTaskReport(props.report);
const title = ReportUtils.getReportName(props.report);

const subtitle = ReportUtils.getChatRoomSubtitle(props.report);
Expand All @@ -74,6 +76,43 @@ const HeaderView = (props) => {
// We hide the button when we are chatting with an automated Expensify account since it's not possible to contact
// these users via alternative means. It is possible to request a call with Concierge so we leave the option for them.
const shouldShowCallButton = (isConcierge && guideCalendarLink) || !isAutomatedExpensifyAccount;
const shouldShowThreeDotsButton = isTaskReport;
const threeDotMenuItems = [];

if (shouldShowThreeDotsButton) {
if (props.report.stateNum === CONST.REPORT.STATE_NUM.OPEN && props.report.statusNum === CONST.REPORT.STATUS.OPEN) {
threeDotMenuItems.push({
icon: Expensicons.Checkmark,
text: props.translate('newTaskPage.markAsComplete'),

// Implementing in https://github.com/Expensify/App/issues/16858
onSelected: () => {},
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✋ Coming from #19631

This introduces a regression where the user non-assignee, or not owner, can still see the action button and press it.


// Task is marked as completed
if (props.report.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && props.report.statusNum === CONST.REPORT.STATUS.APPROVED) {
threeDotMenuItems.push({
icon: Expensicons.Checkmark,
text: props.translate('newTaskPage.markAsIncomplete'),

// Implementing in https://github.com/Expensify/App/issues/16858
onSelected: () => {},
});
}

// Task is not closed
if (props.report.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED && props.report.statusNum !== CONST.REPORT.STATUS.CLOSED) {
threeDotMenuItems.push({
icon: Expensicons.Trashcan,
text: props.translate('common.cancel'),

// Implementing in https://github.com/Expensify/App/issues/16857
onSelected: () => {},
});
}
}

const avatarTooltip = isChatRoom ? undefined : _.pluck(displayNamesWithTooltips, 'tooltip');
const shouldShowSubscript = isPolicyExpenseChat && !props.report.isOwnPolicyExpenseChat && !ReportUtils.isArchivedRoom(props.report);
const icons = ReportUtils.getIcons(props.report, props.personalDetails);
Expand Down Expand Up @@ -162,6 +201,12 @@ const HeaderView = (props) => {
<Icon src={Expensicons.Pin} fill={props.report.isPinned ? themeColors.heading : themeColors.icon} />
</Pressable>
</Tooltip>
{shouldShowThreeDotsButton && (
<ThreeDotsMenu
anchorPosition={styles.threeDotsPopoverOffset}
menuItems={threeDotMenuItems}
/>
)}
</View>
</View>
)}
Expand Down
3 changes: 3 additions & 0 deletions src/pages/home/ReportScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import personalDetailsPropType from '../personalDetailsPropType';
import getIsReportFullyVisible from '../../libs/getIsReportFullyVisible';
import EmojiPicker from '../../components/EmojiPicker/EmojiPicker';
import * as EmojiPickerAction from '../../libs/actions/EmojiPickerAction';
import TaskHeaderView from './TaskHeaderView';

const propTypes = {
/** Navigation route context info provided by react navigation */
Expand Down Expand Up @@ -223,6 +224,7 @@ class ReportScreen extends React.Component {
const addWorkspaceRoomOrChatPendingAction = lodashGet(this.props.report, 'pendingFields.addWorkspaceRoom') || lodashGet(this.props.report, 'pendingFields.createChat');
const addWorkspaceRoomOrChatErrors = lodashGet(this.props.report, 'errorFields.addWorkspaceRoom') || lodashGet(this.props.report, 'errorFields.createChat');
const screenWrapperStyle = [styles.appContent, styles.flex1, {marginTop: this.props.viewportOffsetTop}];
const isTaskReport = ReportUtils.isTaskReport(this.props.report);

// There are no reportActions at all to display and we are still in the process of loading the next set of actions.
const isLoadingInitialReportActions = _.isEmpty(this.props.reportActions) && this.props.report.isLoadingReportActions;
Expand Down Expand Up @@ -290,6 +292,7 @@ class ReportScreen extends React.Component {
shouldShowCloseButton
/>
)}
{isTaskReport && <TaskHeaderView report={this.props.report} />}
</>
)}
<View
Expand Down
34 changes: 34 additions & 0 deletions src/pages/home/TaskHeaderView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import lodashGet from 'lodash/get';
import reportPropTypes from '../reportPropTypes';
import MenuItemWithTopDescription from '../../components/MenuItemWithTopDescription';
import Navigation from '../../libs/Navigation/Navigation';
import ROUTES from '../../ROUTES';

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

function TaskHeaderView(props) {
return (
<>
<MenuItemWithTopDescription
shouldShowHeaderTitle
title={props.report.reportName}
description="Task"
onPress={() => Navigation.navigate(ROUTES.getTaskReportTitleRoute(props.report.reportID))}
/>
<MenuItemWithTopDescription
title={lodashGet(props.report, 'description', '')}
description="Description"
onPress={() => Navigation.navigate(ROUTES.getTaskReportDescriptionRoute(props.report.reportID))}
/>
</>
);
}

TaskHeaderView.propTypes = propTypes;
TaskHeaderView.displayName = 'TaskHeaderView';

export default TaskHeaderView;
99 changes: 99 additions & 0 deletions src/pages/tasks/TaskDescriptionPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import _ from 'underscore';
import React, {useCallback, useRef} from 'react';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import ScreenWrapper from '../../components/ScreenWrapper';
import HeaderWithCloseButton from '../../components/HeaderWithCloseButton';
import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize';
import Form from '../../components/Form';
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';

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,

/** The report currently being looked at */
report: reportPropTypes.isRequired,

/* Onyx Props */
...withLocalizePropTypes,
};

const defaultProps = {

};

function TaskDescriptionPage(props) {
/**
* @param {Object} values
* @param {String} values.description
* @returns {Object} - An object containing the errors for each inputID
*/
const validate = useCallback((values) => {
const errors = {};

if (_.isEmpty(values.description)) {
errors.description = props.translate('common.error.fieldRequired');
}
Comment on lines +47 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This introduced a regression in #19022, the description field is optional and should allow empty inputs.


return errors;
}, [props]);

const submit = useCallback(() => {
// Functionality will be implemented in https://github.com/Expensify/App/issues/16856
}, []);

const inputRef = useRef(null);

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
onEntryTransitionEnd={() => inputRef.current && inputRef.current.focus()}
>
<HeaderWithCloseButton
title={props.translate('newTaskPage.task')}
shouldShowBackButton
onBackButtonPress={() => Navigation.goBack()}
onCloseButtonPress={() => Navigation.dismissModal(true)}
/>
<Form
style={[styles.flexGrow1, styles.ph5]}
formID={ONYXKEYS.FORMS.EDIT_TASK_FORM}
validate={validate}
onSubmit={submit}
submitButtonText={props.translate('common.save')}
enabledWhenOffline
>
<View style={[styles.mb4]}>
<TextInput
inputID="description"
name="description"
label={props.translate('newTaskPage.description')}
defaultValue={props.report.description || ''}
ref={el => inputRef.current = el}
/>
</View>
</Form>
</ScreenWrapper>
);
}

TaskDescriptionPage.propTypes = propTypes;
TaskDescriptionPage.defaultProps = defaultProps;

export default compose(
withLocalize,
withReportOrNotFound,
)(TaskDescriptionPage);
Loading