Skip to content

Commit 8ac7e7f

Browse files
authored
Merge pull request Expensify#86967 from mkzie2/mkzie2-issue/86737
2 parents f7c2a86 + f74a8bd commit 8ac7e7f

2 files changed

Lines changed: 80 additions & 40 deletions

File tree

src/components/Search/FilterDropdowns/UserSelectPopup.tsx

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
1414
import useWindowDimensions from '@hooks/useWindowDimensions';
1515
import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
1616
import {getParticipantsOption} from '@libs/OptionsListUtils';
17+
import {doesPersonalDetailMatchSearchTerm} from '@libs/OptionsListUtils/searchMatchUtils';
1718
import type {OptionData} from '@libs/ReportUtils';
1819
import CONST from '@src/CONST';
1920
import ONYXKEYS from '@src/ONYXKEYS';
@@ -92,24 +93,48 @@ function UserSelectPopup({value, label, closeOverlay, onChange, isSearchable}: U
9293
...report,
9394
keyForList: String(report.reportID),
9495
}));
95-
const combinedOptions = [...selectedOptionsForDisplay, ...personalDetailsList, ...recentReports];
9696

97-
// Sort the options so that selected items appear first, and the current user appears right after that, followed by the rest of the options in their original order
97+
const isCurrentUserSelected = selectedOptionsForDisplay.some((option) => option.accountID === currentUserAccountID);
98+
99+
// Extract the current user from available options to guarantee they appear at the top.
100+
// Falls back to creating from personal details to handle pagination edge cases.
101+
let currentUserOption: OptionData | undefined;
102+
if (!isCurrentUserSelected && currentUserAccountID) {
103+
const currentUserPersonalDetail = personalDetailsList.find((p) => p.accountID === currentUserAccountID) ?? recentReports.find((r) => r.accountID === currentUserAccountID);
104+
if (currentUserPersonalDetail) {
105+
currentUserOption = currentUserPersonalDetail;
106+
} else if (personalDetails?.[currentUserAccountID]) {
107+
const candidateOption = getParticipantsOption(personalDetails[currentUserAccountID], personalDetails) as OptionData;
108+
const trimmedSearchTerm = debouncedSearchTerm.trim().toLowerCase();
109+
if (!trimmedSearchTerm || doesPersonalDetailMatchSearchTerm(candidateOption, currentUserAccountID, trimmedSearchTerm)) {
110+
currentUserOption = candidateOption;
111+
}
112+
}
113+
}
114+
115+
// Filter current user from regular lists to avoid duplication
116+
const filteredPersonalDetails = currentUserOption ? personalDetailsList.filter((p) => p.accountID !== currentUserAccountID) : personalDetailsList;
117+
const filteredRecentReports = currentUserOption ? recentReports.filter((r) => r.accountID !== currentUserAccountID) : recentReports;
118+
119+
// Place selected options first, then the current user, then the rest
120+
const combinedOptions = [...selectedOptionsForDisplay, ...(currentUserOption ? [currentUserOption] : []), ...filteredPersonalDetails, ...filteredRecentReports];
121+
122+
// Sort so that selected items appear first; current user placement is handled explicitly above
98123
combinedOptions.sort((a, b) => {
99-
// selected items first
100124
if (a.isSelected && !b.isSelected) {
101125
return -1;
102126
}
103127
if (!a.isSelected && b.isSelected) {
104128
return 1;
105129
}
106-
107-
// Put the current user at the top of the list
108-
if (a.accountID === currentUserAccountID) {
109-
return -1;
110-
}
111-
if (b.accountID === currentUserAccountID) {
112-
return 1;
130+
// Among selected items, prioritize the current user
131+
if (a.isSelected && b.isSelected) {
132+
if (a.accountID === currentUserAccountID) {
133+
return -1;
134+
}
135+
if (b.accountID === currentUserAccountID) {
136+
return 1;
137+
}
113138
}
114139
return 0;
115140
});
@@ -119,7 +144,7 @@ function UserSelectPopup({value, label, closeOverlay, onChange, isSearchable}: U
119144
keyForList: option.keyForList ?? option.login ?? '',
120145
}));
121146
return combinedOptionsWithKeyForList;
122-
}, [availableOptions.personalDetails, availableOptions.recentReports, selectedOptionsForDisplay, currentUserAccountID]);
147+
}, [availableOptions.personalDetails, availableOptions.recentReports, selectedOptionsForDisplay, currentUserAccountID, personalDetails, debouncedSearchTerm]);
123148

124149
const headerMessage = useMemo(() => {
125150
const noResultsFound = isEmpty(listData);

src/components/Search/SearchFiltersParticipantsSelector.tsx

Lines changed: 44 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionS
1111
import useSearchSelector from '@hooks/useSearchSelector';
1212
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
1313
import {formatSectionsFromSearchTerm, getFilteredRecentAttendees, getParticipantsOption} from '@libs/OptionsListUtils';
14+
import {doesPersonalDetailMatchSearchTerm} from '@libs/OptionsListUtils/searchMatchUtils';
1415
import type {OptionData} from '@libs/ReportUtils';
1516
import {getDisplayNameForParticipant} from '@libs/ReportUtils';
1617
import Navigation from '@navigation/Navigation';
@@ -76,18 +77,19 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate,
7677
[personalDetails, recentAttendees, currentUserEmail, currentUserAccountID, shouldAllowNameOnlyOptions],
7778
);
7879

79-
const {searchTerm, setSearchTerm, availableOptions, selectedOptions, setSelectedOptions, toggleSelection, areOptionsInitialized, onListEndReached} = useSearchSelector({
80-
selectionMode: CONST.SEARCH_SELECTOR.SELECTION_MODE_MULTI,
81-
searchContext: CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_GENERAL,
82-
maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
83-
includeUserToInvite: true,
84-
excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT,
85-
includeRecentReports: true,
86-
shouldInitialize: didScreenTransitionEnd,
87-
includeCurrentUser: true,
88-
recentAttendees: recentAttendeeLists,
89-
shouldAllowNameOnlyOptions,
90-
});
80+
const {searchTerm, debouncedSearchTerm, setSearchTerm, availableOptions, selectedOptions, setSelectedOptions, toggleSelection, areOptionsInitialized, onListEndReached} =
81+
useSearchSelector({
82+
selectionMode: CONST.SEARCH_SELECTOR.SELECTION_MODE_MULTI,
83+
searchContext: CONST.SEARCH_SELECTOR.SEARCH_CONTEXT_GENERAL,
84+
maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
85+
includeUserToInvite: true,
86+
excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT,
87+
includeRecentReports: true,
88+
shouldInitialize: didScreenTransitionEnd,
89+
includeCurrentUser: true,
90+
recentAttendees: recentAttendeeLists,
91+
shouldAllowNameOnlyOptions,
92+
});
9193

9294
const {sections, headerMessage} = useMemo(() => {
9395
const newSections = [];
@@ -104,7 +106,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate,
104106
}
105107

106108
const formattedResults = formatSectionsFromSearchTerm(
107-
searchTerm.trim().toLowerCase(),
109+
debouncedSearchTerm.trim().toLowerCase(),
108110
selectedOptions,
109111
chatOptions.recentReports,
110112
chatOptions.personalDetails,
@@ -116,7 +118,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate,
116118
undefined,
117119
reportAttributesDerived,
118120
);
119-
const selectedCurrentUser = formattedResults.section.data.find((option) => option.accountID === chatOptions.currentUserOption?.accountID);
121+
const selectedCurrentUser = formattedResults.section.data.find((option) => option.accountID === currentUserAccountID);
120122

121123
// If the current user is already selected, remove them from the recent reports and personal details
122124
if (selectedCurrentUser) {
@@ -125,20 +127,33 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate,
125127
}
126128

127129
// If the current user is not selected, add them to the top of the list
128-
if (!selectedCurrentUser && chatOptions.currentUserOption) {
129-
const formattedName = getDisplayNameForParticipant({
130-
accountID: chatOptions.currentUserOption.accountID,
131-
shouldAddCurrentUserPostfix: true,
132-
personalDetailsData: personalDetails,
133-
formatPhoneNumber,
134-
});
135-
chatOptions.currentUserOption.text = formattedName;
136-
137-
newSections.push({
138-
title: '',
139-
data: [chatOptions.currentUserOption],
140-
sectionIndex: 0,
141-
});
130+
// Falls back to creating from personal details to handle pagination edge cases
131+
if (!selectedCurrentUser) {
132+
let currentUserOptionToShow = chatOptions.currentUserOption;
133+
const currentUserDetails = currentUserAccountID ? personalDetails?.[currentUserAccountID] : undefined;
134+
if (!currentUserOptionToShow && currentUserAccountID && currentUserDetails) {
135+
const candidateOption = getParticipantsOption(currentUserDetails, personalDetails) as OptionData;
136+
const trimmedSearchTerm = debouncedSearchTerm.trim().toLowerCase();
137+
if (!trimmedSearchTerm || doesPersonalDetailMatchSearchTerm(candidateOption, currentUserAccountID, trimmedSearchTerm)) {
138+
currentUserOptionToShow = candidateOption;
139+
}
140+
}
141+
142+
if (currentUserOptionToShow) {
143+
const formattedName = getDisplayNameForParticipant({
144+
accountID: currentUserOptionToShow.accountID,
145+
shouldAddCurrentUserPostfix: true,
146+
personalDetailsData: personalDetails,
147+
formatPhoneNumber,
148+
});
149+
currentUserOptionToShow.text = formattedName;
150+
151+
newSections.push({
152+
title: '',
153+
data: [currentUserOptionToShow],
154+
sectionIndex: 0,
155+
});
156+
}
142157
}
143158

144159
newSections.push({
@@ -175,7 +190,7 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate,
175190
}, [
176191
areOptionsInitialized,
177192
availableOptions,
178-
searchTerm,
193+
debouncedSearchTerm,
179194
selectedOptions,
180195
privateIsArchivedMap,
181196
currentUserAccountID,

0 commit comments

Comments
 (0)