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 'Task' page to TypeScript #35484

Merged
merged 11 commits into from
Mar 7, 2024
12 changes: 6 additions & 6 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ type Hierarchy = Record<string, Category & {[key: string]: Hierarchy & Category}

type GetOptionsConfig = {
reportActions?: ReportActions;
betas?: Beta[];
betas?: OnyxEntry<Beta[]>;
selectedOptions?: Option[];
maxRecentReportsToShow?: number;
excludeLogins?: string[];
Expand Down Expand Up @@ -156,7 +156,6 @@ type SectionForSearchTerm = {
section: CategorySection;
newIndexOffset: number;
};

type GetOptions = {
recentReports: ReportUtils.OptionData[];
personalDetails: ReportUtils.OptionData[];
Expand Down Expand Up @@ -1439,7 +1438,8 @@ function getOptions(
const {parentReportID, parentReportActionID} = report ?? {};
const canGetParentReport = parentReportID && parentReportActionID && allReportActions;
const parentReportAction = canGetParentReport ? allReportActions[parentReportID]?.[parentReportActionID] ?? null : null;
const doesReportHaveViolations = betas.includes(CONST.BETAS.VIOLATIONS) && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction);
const doesReportHaveViolations =
(betas?.includes(CONST.BETAS.VIOLATIONS) && ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction)) ?? false;

return ReportUtils.shouldReportBeInOptionList({
report,
Expand Down Expand Up @@ -1803,7 +1803,7 @@ function getIOUConfirmationOptionsFromParticipants(participants: Participant[],
function getFilteredOptions(
reports: OnyxCollection<Report>,
personalDetails: OnyxEntry<PersonalDetailsList>,
betas: Beta[] = [],
betas: OnyxEntry<Beta[]> = [],
searchValue = '',
selectedOptions: Array<Partial<ReportUtils.OptionData>> = [],
excludeLogins: string[] = [],
Expand Down Expand Up @@ -1850,9 +1850,9 @@ function getFilteredOptions(
*/

function getShareDestinationOptions(
reports: Record<string, Report>,
reports: Record<string, Report | null>,
personalDetails: OnyxEntry<PersonalDetailsList>,
betas: Beta[] = [],
betas: OnyxEntry<Beta[]> = [],
searchValue = '',
selectedOptions: Array<Partial<ReportUtils.OptionData>> = [],
excludeLogins: string[] = [],
Expand Down
2 changes: 1 addition & 1 deletion src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3953,7 +3953,7 @@ function shouldReportBeInOptionList({
report: OnyxEntry<Report>;
currentReportId: string;
isInGSDMode: boolean;
betas: Beta[];
betas: OnyxEntry<Beta[]>;
policies: OnyxCollection<Policy>;
excludeEmptyChats: boolean;
doesReportHaveViolations: boolean;
Expand Down
10 changes: 8 additions & 2 deletions src/libs/actions/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,13 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task
API.write(WRITE_COMMANDS.EDIT_TASK, parameters, {optimisticData, successData, failureData});
}

function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assigneeEmail: string, assigneeAccountID = 0, assigneeChatReport: OnyxEntry<OnyxTypes.Report> = null) {
function editTaskAssignee(
report: OnyxTypes.Report,
ownerAccountID: number,
assigneeEmail: string,
assigneeAccountID: number | null = 0,
assigneeChatReport: OnyxEntry<OnyxTypes.Report> = null,
) {
// Create the EditedReportAction on the task
const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail);
const reportName = report.reportName?.trim();
Expand All @@ -460,7 +466,7 @@ function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assi
const assigneeChatReportID = assigneeChatReport ? assigneeChatReport.reportID : '0';
const optimisticReport: OptimisticReport = {
reportName,
managerID: assigneeAccountID || report.managerID,
managerID: assigneeAccountID ?? report.managerID,
pendingFields: {
...(assigneeAccountID && {managerID: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,58 +1,49 @@
/* eslint-disable es/no-optional-chaining */
import type {RouteProp} from '@react-navigation/native';
import {useRoute} from '@react-navigation/native';
import lodashGet from 'lodash/get';
import lodashPick from 'lodash/pick';
import PropTypes from 'prop-types';
import React, {useCallback, useMemo, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import {useBetas, usePersonalDetails, useSession} from '@components/OnyxProvider';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import type {ListItem} from '@components/SelectionList/types';
import UserListItem from '@components/SelectionList/UserListItem';
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
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 Task from '@userActions/Task';
import type {TaskDetailsNavigatorParamList} from '@navigation/types';
import * as TaskActions from '@userActions/Task';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {Report, Task} from '@src/types/onyx';

const propTypes = {
type TaskAssigneeSelectorModalOnyxProps = {
/** All reports shared with the user */
reports: PropTypes.objectOf(reportPropTypes),
reports: OnyxCollection<Report>;

/** Grab the Share destination of the Task */
task: PropTypes.shape({
/** Share destination of the Task */
shareDestination: PropTypes.string,

/** The task report if it's currently being edited */
report: reportPropTypes,
}),

/** The policy of root parent report */
rootParentReportPolicy: PropTypes.shape({
/** The role of current user */
role: PropTypes.string,
}),
task: OnyxEntry<Task>;
};

const defaultProps = {
reports: {},
task: {},
rootParentReportPolicy: {},
type UseOptions = {
reports: OnyxCollection<Report>;
};

function useOptions({reports}) {
type TaskAssigneeSelectorModalProps = TaskAssigneeSelectorModalOnyxProps & WithCurrentUserPersonalDetailsProps;

function useOptions({reports}: UseOptions) {
const allPersonalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
const betas = useBetas();
const [isLoading, setIsLoading] = useState(true);
Expand All @@ -78,7 +69,7 @@ function useOptions({reports}) {
);

const headerMessage = OptionsListUtils.getHeaderMessage(
(recentReports.length || 0 + personalDetails.length || 0) !== 0 || currentUserOption,
(recentReports?.length || 0) + (personalDetails?.length || 0) !== 0 || Boolean(currentUserOption),
Boolean(userToInvite),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Boolean(userToInvite),
!!userToInvite,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think Boolean is better since it is straight forward conversion

Copy link
Contributor

Choose a reason for hiding this comment

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

The convention is to use !! across files, I agree with @kubabutkiewicz

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't have such a convention in the contributional guidelines.

I think using Boolean is better as you always understand what is going on and do not need to focus on the quantity of !

debouncedSearchValue,
);
Expand All @@ -99,28 +90,28 @@ function useOptions({reports}) {
return {...options, isLoading, searchValue, debouncedSearchValue, setSearchValue};
}

function TaskAssigneeSelectorModal({reports, task, rootParentReportPolicy}) {
function TaskAssigneeSelectorModal({reports, task}: TaskAssigneeSelectorModalProps) {
const styles = useThemeStyles();
const route = useRoute();
const route = useRoute<RouteProp<TaskDetailsNavigatorParamList, typeof SCREENS.TASK.ASSIGNEE>>();
const {translate} = useLocalize();
const session = useSession();
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const {userToInvite, recentReports, personalDetails, currentUserOption, isLoading, searchValue, setSearchValue, headerMessage} = useOptions({reports, task});
const {userToInvite, recentReports, personalDetails, currentUserOption, isLoading, searchValue, setSearchValue, headerMessage} = useOptions({reports});

const onChangeText = (newSearchTerm = '') => {
setSearchValue(newSearchTerm);
};

const report = useMemo(() => {
if (!route.params || !route.params.reportID) {
const report: OnyxEntry<Report> = useMemo(() => {
if (!route.params?.reportID) {
return null;
}
if (report && !ReportUtils.isTaskReport(report)) {
Navigation.isNavigationReady().then(() => {
Navigation.dismissModal(report.reportID);
});
}
return reports[`${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`];
return reports?.[`${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID}`] ?? null;
}, [reports, route]);

const sections = useMemo(() => {
Expand Down Expand Up @@ -155,66 +146,87 @@ function TaskAssigneeSelectorModal({reports, task, rootParentReportPolicy}) {

if (userToInvite) {
sectionsList.push({
title: '',
data: [userToInvite],
shouldShow: true,
indexOffset,
});
}

return sectionsList;
}, [currentUserOption, personalDetails, recentReports, userToInvite, translate]);
return sectionsList.map((section) => ({
...section,
data: section.data.map((option) => ({
...option,
text: option.text ?? '',
alternateText: option.alternateText ?? undefined,
keyForList: option.keyForList ?? '',
isDisabled: option.isDisabled ?? undefined,
login: option.login ?? undefined,
shouldShowSubscript: option.shouldShowSubscript ?? undefined,
})),
}));
}, [currentUserOption, personalDetails, recentReports, translate, userToInvite]);

const selectReport = useCallback(
(option) => {
(option: ListItem) => {
if (!option) {
return;
}

// Check to see if we're editing a task and if so, update the assignee
if (report) {
if (option.accountID !== report.managerID) {
const assigneeChatReport = Task.setAssigneeValue(option.login, option.accountID, report.reportID, OptionsListUtils.isCurrentUser(option));
const assigneeChatReport = TaskActions.setAssigneeValue(
option?.login ?? '',
option?.accountID ?? -1,
report.reportID,
OptionsListUtils.isCurrentUser({...option, accountID: option?.accountID ?? -1, login: option?.login ?? ''}),
);

// Pass through the selected assignee
Task.editTaskAssignee(report, session.accountID, option.login, option.accountID, assigneeChatReport);
TaskActions.editTaskAssignee(report, session?.accountID ?? 0, option?.login ?? '', option?.accountID, assigneeChatReport);
}
Navigation.dismissModal(report.reportID);
// If there's no report, we're creating a new task
} else if (option.accountID) {
Task.setAssigneeValue(option.login, option.accountID, task.shareDestination, OptionsListUtils.isCurrentUser(option));
TaskActions.setAssigneeValue(
option?.login ?? '',
option.accountID,
task?.shareDestination ?? '',
OptionsListUtils.isCurrentUser({...option, accountID: option?.accountID ?? -1, login: option?.login ?? undefined}),
);
Navigation.goBack(ROUTES.NEW_TASK);
}
},
[session.accountID, task.shareDestination, report],
[session?.accountID, task?.shareDestination, report],
);

const handleBackButtonPress = useCallback(() => (lodashGet(route.params, 'reportID') ? Navigation.dismissModal() : Navigation.goBack(ROUTES.NEW_TASK)), [route.params]);
const handleBackButtonPress = useCallback(() => (route.params?.reportID ? Navigation.dismissModal() : Navigation.goBack(ROUTES.NEW_TASK)), [route.params]);

const isOpen = ReportUtils.isOpenTaskReport(report);
const canModifyTask = Task.canModifyTask(report, currentUserPersonalDetails.accountID, lodashGet(rootParentReportPolicy, 'role', ''));
const canModifyTask = TaskActions.canModifyTask(report, currentUserPersonalDetails.accountID);
const isTaskNonEditable = ReportUtils.isTaskReport(report) && (!canModifyTask || !isOpen);

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
testID={TaskAssigneeSelectorModal.displayName}
>
{({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => (
{({didScreenTransitionEnd}) => (
<FullPageNotFoundView shouldShow={isTaskNonEditable}>
<HeaderWithBackButton
title={translate('task.assignee')}
onBackButtonPress={handleBackButtonPress}
/>
<View style={[styles.flex1, styles.w100, styles.pRelative]}>
<SelectionList
sections={didScreenTransitionEnd && !isLoading ? sections : CONST.EMPTY_ARRAY}
sections={didScreenTransitionEnd && !isLoading ? sections : []}
ListItem={UserListItem}
onSelectRow={selectReport}
onChangeText={onChangeText}
textInputValue={searchValue}
headerMessage={headerMessage}
textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')}
safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle}
showLoadingPlaceholder={isLoading || !didScreenTransitionEnd}
/>
</View>
Expand All @@ -225,26 +237,14 @@ function TaskAssigneeSelectorModal({reports, task, rootParentReportPolicy}) {
}

TaskAssigneeSelectorModal.displayName = 'TaskAssigneeSelectorModal';
TaskAssigneeSelectorModal.propTypes = propTypes;
TaskAssigneeSelectorModal.defaultProps = defaultProps;

export default compose(
withOnyx({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
task: {
key: ONYXKEYS.TASK,
},
}),
withOnyx({
rootParentReportPolicy: {
key: ({reports, route}) => {
const report = reports[`${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID || '0'}`];
const rootParentReport = ReportUtils.getRootParentReport(report);
return `${ONYXKEYS.COLLECTION.POLICY}${rootParentReport ? rootParentReport.policyID : '0'}`;
},
selector: (policy) => lodashPick(policy, ['role']),
},
}),
)(TaskAssigneeSelectorModal);
const TaskAssigneeSelectorModalWithOnyx = withOnyx<TaskAssigneeSelectorModalProps, TaskAssigneeSelectorModalOnyxProps>({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
task: {
key: ONYXKEYS.TASK,
},
})(TaskAssigneeSelectorModal);

export default withCurrentUserPersonalDetails(TaskAssigneeSelectorModalWithOnyx);
Loading
Loading