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

[TS migration] Migrate 'ReportActionItemTaskView.js' component to TypeScript #34276

Merged
merged 17 commits into from
Jan 31, 2024
2 changes: 1 addition & 1 deletion src/components/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ type MenuItemProps = (IconProps | AvatarProps | NoIcon) & {
shouldBlockSelection?: boolean;

/** Whether should render title as HTML or as Text */
shouldParseTitle?: false;
shouldParseTitle?: boolean;

/** Should check anonymous user in onPress function */
shouldCheckActionAllowedOnPress?: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import lodashGet from 'lodash/get';
import PropTypes from 'prop-types';
import React, {useEffect} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import type {OnyxEntry} from 'react-native-onyx';
import Checkbox from '@components/Checkbox';
import Hoverable from '@components/Hoverable';
import Icon from '@components/Icon';
Expand All @@ -15,68 +13,66 @@ import {usePersonalDetails} from '@components/OnyxProvider';
import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction';
import SpacerView from '@components/SpacerView';
import Text from '@components/Text';
import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import withWindowDimensions from '@components/withWindowDimensions';
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
import type {WindowDimensionsProps} from '@components/withWindowDimensions/types';
import useLocalize from '@hooks/useLocalize';
shahinyan11 marked this conversation as resolved.
Show resolved Hide resolved
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
import convertToLTR from '@libs/convertToLTR';
import getButtonState from '@libs/getButtonState';
import Navigation from '@libs/Navigation/Navigation';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportUtils from '@libs/ReportUtils';
import reportPropTypes from '@pages/reportPropTypes';
import * as Session from '@userActions/Session';
import * as Task from '@userActions/Task';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {PersonalDetailsList, Policy, Report} from '@src/types/onyx';

const propTypes = {
/** The report currently being looked at */
report: reportPropTypes.isRequired,
type TaskViewOnyxProps = {
/** All of the personal details for everyone */
personalDetails: OnyxEntry<PersonalDetailsList>;

/** The policy of root parent report */
policy: PropTypes.shape({
/** The role of current user */
role: PropTypes.string,
}),

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

...withLocalizePropTypes,

...withCurrentUserPersonalDetailsPropTypes,
/** The policy for the current route */
policy: Pick<Policy, 'role'> | null;
};

const defaultProps = {
policy: {},
};
type TaskViewProps = TaskViewOnyxProps &
WindowDimensionsProps &
WithCurrentUserPersonalDetailsProps & {
shahinyan11 marked this conversation as resolved.
Show resolved Hide resolved
/** The report currently being looked at */
report: Report;

function TaskView(props) {
/** Whether we should display the horizontal rule below the component */
shouldShowHorizontalRule: boolean;
};

function TaskView({report, policy, shouldShowHorizontalRule, ...props}: TaskViewProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
useEffect(() => {
Task.setTaskReport({...props.report});
}, [props.report]);
Task.setTaskReport(report);
}, [report]);

const taskTitle = convertToLTR(props.report.reportName || '');
const assigneeTooltipDetails = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs([props.report.managerID], props.personalDetails), false);
const isCompleted = ReportUtils.isCompletedTaskReport(props.report);
const isOpen = ReportUtils.isOpenTaskReport(props.report);
const canModifyTask = Task.canModifyTask(props.report, props.currentUserPersonalDetails.accountID, lodashGet(props.policy, 'role', ''));
const taskTitle = convertToLTR(report.reportName ?? '');
// @ts-expect-error TODO: Remove this once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript.
const assigneeTooltipDetails = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs([report.managerID], props.personalDetails), false);
const isCompleted = ReportUtils.isCompletedTaskReport(report);
const isOpen = ReportUtils.isOpenTaskReport(report);
const canModifyTask = Task.canModifyTask(report, props.currentUserPersonalDetails.accountID, policy?.role);
const disableState = !canModifyTask;
const isDisableInteractive = !canModifyTask || !isOpen;
const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
const {translate} = useLocalize();

return (
<View>
<OfflineWithFeedback
shouldShowErrorMessages
errors={lodashGet(props, 'report.errorFields.editTask') || lodashGet(props, 'report.errorFields.createTask')}
onClose={() => Task.clearTaskErrors(props.report.reportID)}
errors={report.errorFields?.editTask ?? report.errorFields?.createTask}
onClose={() => Task.clearTaskErrors(report.reportID)}
errorRowStyles={styles.ph5}
>
<Hoverable>
Expand All @@ -87,39 +83,38 @@ function TaskView(props) {
return;
}
if (e && e.type === 'click') {
e.currentTarget.blur();
(e.currentTarget as HTMLElement).blur();
}

Navigation.navigate(ROUTES.TASK_TITLE.getRoute(props.report.reportID));
Navigation.navigate(ROUTES.TASK_TITLE.getRoute(report.reportID));
})}
style={({pressed}) => [
styles.ph5,
styles.pv2,
StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, false, disableState, !isDisableInteractive), true),
isDisableInteractive && !disableState && styles.cursorDefault,
]}
ref={props.forwardedRef}
disabled={disableState}
accessibilityLabel={taskTitle || props.translate('task.task')}
accessibilityLabel={taskTitle || translate('task.task')}
>
{({pressed}) => (
<OfflineWithFeedback pendingAction={lodashGet(props, 'report.pendingFields.reportName')}>
<Text style={styles.taskTitleDescription}>{props.translate('task.title')}</Text>
<View style={[styles.flexRow, styles.alignItemsTop, styles.flex1]}>
<OfflineWithFeedback pendingAction={report.pendingFields?.reportName}>
<Text style={styles.taskTitleDescription}>{translate('task.title')}</Text>
<View style={[styles.flexRow, styles.flex1]}>
<Checkbox
onPress={Session.checkIfActionIsAllowed(() => {
if (isCompleted) {
Task.reopenTask(props.report);
Task.reopenTask(report);
} else {
Task.completeTask(props.report);
Task.completeTask(report);
}
})}
isChecked={isCompleted}
style={styles.taskMenuItemCheckbox}
containerSize={24}
containerBorderRadius={8}
caretSize={16}
accessibilityLabel={taskTitle || props.translate('task.task')}
accessibilityLabel={taskTitle || translate('task.task')}
disabled={!canModifyTask}
/>
<View style={[styles.flexRow, styles.flex1]}>
Expand All @@ -145,12 +140,12 @@ function TaskView(props) {
</PressableWithSecondaryInteraction>
)}
</Hoverable>
<OfflineWithFeedback pendingAction={lodashGet(props, 'report.pendingFields.description')}>
<OfflineWithFeedback pendingAction={report.pendingFields?.description}>
<MenuItemWithTopDescription
shouldParseTitle
description={props.translate('task.description')}
title={props.report.description || ''}
onPress={() => Navigation.navigate(ROUTES.TASK_DESCRIPTION.getRoute(props.report.reportID))}
description={translate('task.description')}
title={report.description ?? ''}
onPress={() => Navigation.navigate(ROUTES.TASK_DESCRIPTION.getRoute(report.reportID))}
shouldShowRightIcon={isOpen}
disabled={disableState}
wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
Expand All @@ -159,16 +154,16 @@ function TaskView(props) {
interactive={!isDisableInteractive}
/>
</OfflineWithFeedback>
{props.report.managerID ? (
<OfflineWithFeedback pendingAction={lodashGet(props, 'report.pendingFields.managerID')}>
{report.managerID ? (
<OfflineWithFeedback pendingAction={report.pendingFields?.managerID}>
<MenuItem
label={props.translate('task.assignee')}
title={ReportUtils.getDisplayNameForParticipant(props.report.managerID)}
icon={OptionsListUtils.getAvatarsForAccountIDs([props.report.managerID], personalDetails)}
label={translate('task.assignee')}
title={ReportUtils.getDisplayNameForParticipant(report.managerID)}
icon={OptionsListUtils.getAvatarsForAccountIDs([report.managerID], personalDetails)}
iconType={CONST.ICON_TYPE_AVATAR}
avatarSize={CONST.AVATAR_SIZE.SMALLER}
titleStyle={styles.assigneeTextStyle}
onPress={() => Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(props.report.reportID))}
onPress={() => Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report.reportID))}
shouldShowRightIcon={isOpen}
disabled={disableState}
wrapperStyle={[styles.pv2]}
Expand All @@ -180,8 +175,8 @@ function TaskView(props) {
</OfflineWithFeedback>
) : (
<MenuItemWithTopDescription
description={props.translate('task.assignee')}
onPress={() => Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(props.report.reportID))}
description={translate('task.assignee')}
onPress={() => Navigation.navigate(ROUTES.TASK_ASSIGNEE.getRoute(report.reportID))}
shouldShowRightIcon={isOpen}
disabled={disableState}
wrapperStyle={[styles.pv2]}
Expand All @@ -191,31 +186,26 @@ function TaskView(props) {
)}
</OfflineWithFeedback>
<SpacerView
shouldShow={props.shouldShowHorizontalRule}
style={[props.shouldShowHorizontalRule ? styles.reportHorizontalRule : {}]}
shouldShow={shouldShowHorizontalRule}
style={shouldShowHorizontalRule && styles.reportHorizontalRule}
/>
</View>
);
}

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

export default compose(
withWindowDimensions,
withLocalize,
withCurrentUserPersonalDetails,
withOnyx({
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
policy: {
key: ({report}) => {
const rootParentReport = ReportUtils.getRootParentReport(report);
return `${ONYXKEYS.COLLECTION.POLICY}${rootParentReport ? rootParentReport.policyID : '0'}`;
},
selector: (policy) => _.pick(policy, ['role']),
const TaskViewWithOnyx = withOnyx<TaskViewProps, TaskViewOnyxProps>({
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
policy: {
key: ({report}) => {
const rootParentReport = ReportUtils.getRootParentReport(report);
return `${ONYXKEYS.COLLECTION.POLICY}${rootParentReport ? rootParentReport.policyID : '0'}`;
},
}),
)(TaskView);
selector: (policy: OnyxEntry<Policy>) => (policy ? {role: policy.role} : null),
},
})(TaskView);

export default withCurrentUserPersonalDetails(TaskViewWithOnyx);
Loading