Skip to content

Commit ce21fc8

Browse files
authored
Merge pull request Expensify#81320 from software-mansion-labs/@zfurtak/migrate-TaskAssigneeSelectorModal
Make `TaskAssigneeSelectorModal` use new `SelectionListWithSections`
2 parents f939855 + aaa82a5 commit ce21fc8

3 files changed

Lines changed: 130 additions & 130 deletions

File tree

src/components/SelectionList/SelectionListWithSections/BaseSelectionListWithSections.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ function BaseSelectionListWithSections<TItem extends ListItem>({
3939
ListItem,
4040
textInputOptions,
4141
initiallyFocusedItemKey,
42+
initialScrollIndex,
4243
onSelectRow,
4344
onDismissError,
4445
onScrollBeginDrag,
@@ -308,7 +309,7 @@ function BaseSelectionListWithSections<TItem extends ListItem>({
308309
ref={listRef}
309310
extraData={flattenedData.length}
310311
getItemType={getItemType}
311-
initialScrollIndex={initialFocusedIndex}
312+
initialScrollIndex={initialScrollIndex ?? initialFocusedIndex}
312313
keyExtractor={(item) => ('flatListKey' in item ? item.flatListKey : item.keyForList)}
313314
onEndReached={onEndReached}
314315
onEndReachedThreshold={onEndReachedThreshold}

src/components/SelectionList/SelectionListWithSections/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ type SelectionListWithSectionsProps<TItem extends ListItem> = BaseSelectionListP
2828
/** Array of sections to display in the list */
2929
sections: Array<Section<TItem>>;
3030

31+
/** Index to scroll to initially (when different from the initially focused item) */
32+
initialScrollIndex?: number;
33+
3134
/** Custom content to display in the header */
3235
customHeaderContent?: ReactNode;
3336

src/pages/tasks/TaskAssigneeSelectorModal.tsx

Lines changed: 125 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
/* eslint-disable es/no-optional-chaining */
22
import {useRoute} from '@react-navigation/native';
3-
import React, {useCallback, useEffect, useMemo} from 'react';
3+
import React, {useEffect} from 'react';
44
import {InteractionManager, View} from 'react-native';
55
import type {OnyxEntry} from 'react-native-onyx';
66
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
77
import HeaderWithBackButton from '@components/HeaderWithBackButton';
88
import {usePersonalDetails} from '@components/OnyxListItemProvider';
99
import ScreenWrapper from '@components/ScreenWrapper';
10-
// eslint-disable-next-line no-restricted-imports
11-
import SelectionList from '@components/SelectionListWithSections';
12-
import type {ListItem} from '@components/SelectionListWithSections/types';
13-
import UserListItem from '@components/SelectionListWithSections/UserListItem';
10+
import UserListItem from '@components/SelectionList/ListItem/UserListItem';
11+
import SelectionList from '@components/SelectionList/SelectionListWithSections';
12+
import type {ListItem} from '@components/SelectionList/types';
1413
import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
1514
import withNavigationTransitionEnd from '@components/withNavigationTransitionEnd';
1615
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
@@ -59,31 +58,28 @@ function TaskAssigneeSelectorModal() {
5958
},
6059
});
6160

62-
const optionsWithoutCurrentUser = useMemo(() => {
63-
if (!currentUserPersonalDetails?.accountID) {
64-
return availableOptions;
65-
}
66-
67-
return {
68-
...availableOptions,
69-
personalDetails: availableOptions.personalDetails.filter((detail) => detail.accountID !== currentUserPersonalDetails.accountID),
70-
recentReports: availableOptions.recentReports.filter((report) => report.accountID !== currentUserPersonalDetails.accountID),
71-
};
72-
}, [availableOptions, currentUserPersonalDetails?.accountID]);
73-
74-
const headerMessage = useMemo(() => {
75-
return getHeaderMessage(
76-
(optionsWithoutCurrentUser.recentReports?.length || 0) + (optionsWithoutCurrentUser.personalDetails?.length || 0) !== 0 || !!optionsWithoutCurrentUser.currentUserOption,
77-
!!optionsWithoutCurrentUser.userToInvite,
78-
debouncedSearchTerm,
79-
countryCode,
80-
false,
81-
);
82-
}, [optionsWithoutCurrentUser, debouncedSearchTerm, countryCode]);
61+
const optionsWithoutCurrentUser = !currentUserPersonalDetails?.accountID
62+
? availableOptions
63+
: {
64+
...availableOptions,
65+
personalDetails: availableOptions.personalDetails.filter((detail) => detail.accountID !== currentUserPersonalDetails.accountID),
66+
recentReports: availableOptions.recentReports.filter((report) => report.accountID !== currentUserPersonalDetails.accountID),
67+
};
68+
69+
const recentReportsLength = optionsWithoutCurrentUser.recentReports?.length || 0;
70+
const personalDetailsLength = optionsWithoutCurrentUser.personalDetails?.length || 0;
71+
72+
const headerMessage = getHeaderMessage(
73+
recentReportsLength + personalDetailsLength !== 0 || !!optionsWithoutCurrentUser.currentUserOption,
74+
!!optionsWithoutCurrentUser.userToInvite,
75+
debouncedSearchTerm,
76+
countryCode,
77+
false,
78+
);
8379

8480
const allPersonalDetails = usePersonalDetails();
8581

86-
const report: OnyxEntry<Report> = useMemo(() => {
82+
const report: OnyxEntry<Report> = (() => {
8783
if (!route.params?.reportID) {
8884
return;
8985
}
@@ -94,121 +90,114 @@ function TaskAssigneeSelectorModal() {
9490
});
9591
}
9692
return reports?.[`${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID}`];
97-
}, [reports, route.params?.reportID]);
93+
})();
9894

9995
const parentReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`];
10096

10197
const hasOutstandingChildTask = useHasOutstandingChildTask(report);
10298

103-
const sections = useMemo(() => {
104-
const sectionsList = [];
105-
106-
if (optionsWithoutCurrentUser.currentUserOption) {
107-
sectionsList.push({
108-
title: translate('newTaskPage.assignMe'),
109-
data: [optionsWithoutCurrentUser.currentUserOption],
110-
shouldShow: true,
111-
});
112-
}
99+
const sectionsList = [];
113100

101+
if (optionsWithoutCurrentUser.currentUserOption) {
114102
sectionsList.push({
115-
title: translate('common.recents'),
116-
data: optionsWithoutCurrentUser.recentReports,
117-
shouldShow: optionsWithoutCurrentUser.recentReports?.length > 0,
103+
title: translate('newTaskPage.assignMe'),
104+
data: [optionsWithoutCurrentUser.currentUserOption],
105+
sectionIndex: 0,
118106
});
107+
}
119108

109+
sectionsList.push({
110+
title: translate('common.recents'),
111+
data: optionsWithoutCurrentUser.recentReports,
112+
sectionIndex: 1,
113+
});
114+
115+
sectionsList.push({
116+
title: translate('common.contacts'),
117+
data: optionsWithoutCurrentUser.personalDetails,
118+
sectionIndex: 2,
119+
});
120+
121+
if (optionsWithoutCurrentUser.userToInvite) {
120122
sectionsList.push({
121-
title: translate('common.contacts'),
122-
data: optionsWithoutCurrentUser.personalDetails,
123-
shouldShow: optionsWithoutCurrentUser.personalDetails?.length > 0,
123+
title: '',
124+
data: [optionsWithoutCurrentUser.userToInvite],
125+
sectionIndex: 3,
124126
});
125-
126-
if (optionsWithoutCurrentUser.userToInvite) {
127-
sectionsList.push({
128-
title: '',
129-
data: [optionsWithoutCurrentUser.userToInvite],
130-
shouldShow: true,
131-
});
127+
}
128+
129+
const sections = sectionsList.map((section) => ({
130+
...section,
131+
data: section.data.map((option) => ({
132+
...option,
133+
text: option.text ?? '',
134+
alternateText: option.alternateText ?? undefined,
135+
keyForList: option.keyForList ?? '',
136+
isDisabled: option.isDisabled ?? undefined,
137+
login: option.login ?? undefined,
138+
shouldShowSubscript: option.shouldShowSubscript ?? undefined,
139+
isSelected: task?.assigneeAccountID === option.accountID || task?.report?.managerID === option.accountID,
140+
})),
141+
}));
142+
143+
const initiallyFocusedOptionKey = sections.flatMap((section) => section.data).find((mode) => mode.isSelected === true)?.keyForList;
144+
145+
const selectReport = (option: ListItem) => {
146+
HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS);
147+
if (!option) {
148+
return;
132149
}
133150

134-
return sectionsList.map((section) => ({
135-
...section,
136-
data: section.data.map((option) => ({
137-
...option,
138-
text: option.text ?? '',
139-
alternateText: option.alternateText ?? undefined,
140-
keyForList: option.keyForList ?? '',
141-
isDisabled: option.isDisabled ?? undefined,
142-
login: option.login ?? undefined,
143-
shouldShowSubscript: option.shouldShowSubscript ?? undefined,
144-
isSelected: task?.assigneeAccountID === option.accountID || task?.report?.managerID === option.accountID,
145-
})),
146-
}));
147-
}, [optionsWithoutCurrentUser, task?.assigneeAccountID, translate, task?.report?.managerID]);
148-
149-
const initiallyFocusedOptionKey = useMemo(() => {
150-
return sections.flatMap((section) => section.data).find((mode) => mode.isSelected === true)?.keyForList;
151-
}, [sections]);
152-
153-
const selectReport = useCallback(
154-
(option: ListItem) => {
155-
HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS);
156-
if (!option) {
157-
return;
158-
}
151+
const assigneePersonalDetails = {
152+
...allPersonalDetails?.[option?.accountID ?? CONST.DEFAULT_NUMBER_ID],
153+
accountID: option.accountID ?? CONST.DEFAULT_NUMBER_ID,
154+
login: option.login ?? '',
155+
};
159156

160-
const assigneePersonalDetails = {
161-
...allPersonalDetails?.[option?.accountID ?? CONST.DEFAULT_NUMBER_ID],
162-
accountID: option.accountID ?? CONST.DEFAULT_NUMBER_ID,
163-
login: option.login ?? '',
164-
};
165-
166-
// Check to see if we're editing a task and if so, update the assignee
167-
if (report) {
168-
if (option.accountID !== report.managerID) {
169-
const {report: assigneeChatReport, isOptimisticReport} = setAssigneeValue(
170-
currentUserPersonalDetails.accountID,
171-
assigneePersonalDetails,
172-
report.reportID,
173-
undefined, // passing null as report because for editing task the report will be task details report page not the actual report where task was created
174-
isCurrentUser({...option, accountID: option?.accountID ?? CONST.DEFAULT_NUMBER_ID, login: option?.login ?? ''}, loginList, currentUserEmail),
175-
);
176-
// Pass through the selected assignee
177-
editTaskAssignee(
178-
report,
179-
parentReport,
180-
currentUserPersonalDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID,
181-
option?.login ?? '',
182-
currentUserPersonalDetails.accountID,
183-
hasOutstandingChildTask,
184-
option?.accountID,
185-
assigneeChatReport,
186-
isOptimisticReport,
187-
);
188-
}
189-
// eslint-disable-next-line @typescript-eslint/no-deprecated
190-
InteractionManager.runAfterInteractions(() => {
191-
Navigation.dismissModalWithReport({reportID: report?.reportID});
192-
});
193-
// If there's no report, we're creating a new task
194-
} else if (option.accountID) {
195-
setAssigneeValue(
157+
// Check to see if we're editing a task and if so, update the assignee
158+
if (report) {
159+
if (option.accountID !== report.managerID) {
160+
const {report: assigneeChatReport, isOptimisticReport} = setAssigneeValue(
196161
currentUserPersonalDetails.accountID,
197162
assigneePersonalDetails,
198-
task?.shareDestination ?? '',
199-
undefined, // passing null as report is null in this condition
200-
isCurrentUser({...option, accountID: option?.accountID ?? CONST.DEFAULT_NUMBER_ID, login: option?.login ?? undefined}, loginList, currentUserEmail),
163+
report.reportID,
164+
undefined, // passing null as report because for editing task the report will be task details report page not the actual report where task was created
165+
isCurrentUser({...option, accountID: option?.accountID ?? CONST.DEFAULT_NUMBER_ID, login: option?.login ?? ''}, loginList, currentUserEmail),
166+
);
167+
// Pass through the selected assignee
168+
editTaskAssignee(
169+
report,
170+
parentReport,
171+
currentUserPersonalDetails?.accountID ?? CONST.DEFAULT_NUMBER_ID,
172+
option?.login ?? '',
173+
currentUserPersonalDetails.accountID,
174+
hasOutstandingChildTask,
175+
option?.accountID,
176+
assigneeChatReport,
177+
isOptimisticReport,
201178
);
202-
// eslint-disable-next-line @typescript-eslint/no-deprecated
203-
InteractionManager.runAfterInteractions(() => {
204-
Navigation.goBack(ROUTES.NEW_TASK.getRoute(backTo));
205-
});
206179
}
207-
},
208-
[allPersonalDetails, report, currentUserPersonalDetails.accountID, loginList, currentUserEmail, parentReport, hasOutstandingChildTask, task?.shareDestination, backTo],
209-
);
180+
// eslint-disable-next-line @typescript-eslint/no-deprecated
181+
InteractionManager.runAfterInteractions(() => {
182+
Navigation.dismissModalWithReport({reportID: report?.reportID});
183+
});
184+
// If there's no report, we're creating a new task
185+
} else if (option.accountID) {
186+
setAssigneeValue(
187+
currentUserPersonalDetails.accountID,
188+
assigneePersonalDetails,
189+
task?.shareDestination ?? '',
190+
undefined, // passing null as report is null in this condition
191+
isCurrentUser({...option, accountID: option?.accountID ?? CONST.DEFAULT_NUMBER_ID, login: option?.login ?? undefined}, loginList, currentUserEmail),
192+
);
193+
// eslint-disable-next-line @typescript-eslint/no-deprecated
194+
InteractionManager.runAfterInteractions(() => {
195+
Navigation.goBack(ROUTES.NEW_TASK.getRoute(backTo));
196+
});
197+
}
198+
};
210199

211-
const handleBackButtonPress = useCallback(() => Navigation.goBack(!route.params?.reportID ? ROUTES.NEW_TASK.getRoute(backTo) : backTo), [route.params, backTo]);
200+
const handleBackButtonPress = () => Navigation.goBack(!route.params?.reportID ? ROUTES.NEW_TASK.getRoute(backTo) : backTo);
212201

213202
const isOpen = isOpenTaskReport(report);
214203
const isParentReportArchived = useReportIsArchived(report?.parentReportID);
@@ -219,6 +208,13 @@ function TaskAssigneeSelectorModal() {
219208
searchInServer(debouncedSearchTerm);
220209
}, [debouncedSearchTerm]);
221210

211+
const textInputOptions = {
212+
value: searchTerm,
213+
onChangeText: setSearchTerm,
214+
headerMessage,
215+
label: translate('selectionList.nameEmailOrPhoneNumber'),
216+
};
217+
222218
return (
223219
<ScreenWrapper
224220
includeSafeAreaPaddingBottom={false}
@@ -235,14 +231,14 @@ function TaskAssigneeSelectorModal() {
235231
ListItem={UserListItem}
236232
onSelectRow={selectReport}
237233
shouldSingleExecuteRowSelect
238-
onChangeText={setSearchTerm}
239-
textInputValue={searchTerm}
240-
headerMessage={headerMessage}
241-
initiallyFocusedOptionKey={initiallyFocusedOptionKey}
242-
shouldUpdateFocusedIndex
243-
textInputLabel={translate('selectionList.nameEmailOrPhoneNumber')}
234+
textInputOptions={textInputOptions}
235+
initialScrollIndex={0}
236+
initiallyFocusedItemKey={initiallyFocusedOptionKey}
244237
showLoadingPlaceholder={!areOptionsInitialized}
245238
isLoadingNewOptions={!!isSearchingForReports}
239+
disableMaintainingScrollPosition
240+
shouldUpdateFocusedIndex
241+
shouldShowTextInput
246242
/>
247243
</View>
248244
</FullPageNotFoundView>

0 commit comments

Comments
 (0)