Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
2 changes: 1 addition & 1 deletion src/hooks/usePersonalDetailSearchSelector/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type UseSearchSelectorConfig = {
/** Logins to exclude from suggestions only (soft exclusions - can still be manually entered) */
excludeFromSuggestionsOnly?: Record<string, boolean>;

/** Whether to include recent reports (for getMemberInviteOptions) */
/** Whether to include recent reports */
includeRecentReports?: boolean;

/** Whether to include current user */
Expand Down
51 changes: 2 additions & 49 deletions src/libs/OptionsListUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import Navigation from '@libs/Navigation/Navigation';
import {getIsOffline} from '@libs/NetworkState';
import Parser from '@libs/Parser';
import type {OptionData as PersonalDetailOptionData} from '@libs/PersonalDetailOptionsListUtils/types';
import {getLoginByAccountID, getPersonalDetailByEmail, getPersonalDetailsListByIDs, temporaryGetDisplayNameOrDefault} from '@libs/PersonalDetailsUtils';
import {getLoginByAccountID, getPersonalDetailsListByIDs, temporaryGetDisplayNameOrDefault} from '@libs/PersonalDetailsUtils';
import {addSMSDomainIfPhoneNumber, parsePhoneNumber} from '@libs/PhoneNumber';
import {
canSendInvoiceFromWorkspace,
Expand Down Expand Up @@ -187,7 +187,7 @@ import type {
Transaction,
VisibleReportActionsDerivedValue,
} from '@src/types/onyx';
import type {Attendee, Participant} from '@src/types/onyx/IOU';
import type {Participant} from '@src/types/onyx/IOU';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
Expand All @@ -206,7 +206,6 @@ import type {
GetValidReportsConfig,
IsValidReportsConfig,
MemberForList,
Option,
OptionList,
Options,
OptionsResult,
Expand Down Expand Up @@ -2825,51 +2824,6 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: OnyxEn
};
}

function getFilteredRecentAttendees(
personalDetails: OnyxEntry<PersonalDetailsList>,
attendees: Attendee[],
recentAttendees: Attendee[],
currentUserEmail: string,
currentUserAccountID: number,
translate: LocalizedTranslate,
): Option[] {
const recentAttendeeHasCurrentUser = recentAttendees.find((attendee) => attendee.email === currentUserEmail);
if (!recentAttendeeHasCurrentUser && currentUserEmail) {
const details = getPersonalDetailByEmail(currentUserEmail);
recentAttendees.push({
email: currentUserEmail,
displayName: details?.displayName ?? currentUserEmail,
avatarUrl: details?.avatarThumbnail ?? '',
});
}

// Deduplicate recentAttendees: use email for regular users, displayName for name-only attendees
const seenAttendees = new Set<string>();
const deduplicatedRecentAttendees = recentAttendees.filter((attendee) => {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const key = attendee.email || attendee.displayName || '';
if (seenAttendees.has(key)) {
return false;
}
seenAttendees.add(key);
return true;
});

const filteredRecentAttendees = deduplicatedRecentAttendees
.filter((attendee) => !attendees.find(({email, displayName}) => (attendee.email ? email === attendee.email : displayName === attendee.displayName)))
.map((attendee) => ({
...attendee,
// Use || instead of ?? to handle empty string email for name-only attendees
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
login: attendee.email || attendee.displayName,
...getPersonalDetailByEmail(attendee.email),
keyForList: `${currentUserAccountID}`,
}))
.map((attendee) => getParticipantsOption(attendee, personalDetails, translate));

return filteredRecentAttendees;
}

/**
* Format personalDetails or userToInvite to be shown in the list
*
Expand Down Expand Up @@ -3363,7 +3317,6 @@ export {
formatMemberForList,
formatSectionsFromSearchTerm,
getAlternateText,
getFilteredRecentAttendees,
getEmptyOptions,
getHeaderMessage,
getHeaderMessageForNonUserList,
Expand Down
34 changes: 34 additions & 0 deletions src/libs/PersonalDetailOptionsListUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {LoginList, OnyxInputOrEntry, PersonalDetails, PersonalDetailsList, Report, ReportAttributesDerivedValue} from '@src/types/onyx';
import type {ReportAttributes} from '@src/types/onyx/DerivedValues';
import type {Attendee} from '@src/types/onyx/IOU';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
Expand Down Expand Up @@ -365,6 +366,7 @@ function getValidOptions(
}
}
}
recentOptions = optionsOrderBy(recentOptions, personalDetailsComparator, recentMaxElements, undefined, true).options;
Comment thread
shubham1206agra marked this conversation as resolved.
} else if (includeRecentReports) {
// if maxElements is passed, filter the recent reports by searchString and return only most recent reports (@see recentReportsComparator)
const filteringFunction = (option: OptionData) => {
Expand Down Expand Up @@ -499,6 +501,37 @@ function getHeaderMessage(translate: LocaleContextProps['translate'], searchValu
return translate('common.noResultsFound');
}

/**
* Returns the logins of recent attendees to suggest, ensuring the current user is included, deduplicated,
* and excluding attendees that are already selected. The returned logins are fed to getValidOptions as
* `recentAttendees`, which rebuilds the full options (matching personal details or creating optimistic ones).
*/
function getFilteredRecentAttendees(attendees: Attendee[], recentAttendees: Attendee[], currentUserEmail: string): string[] {
const allRecentAttendees = [...recentAttendees];
if (currentUserEmail && !allRecentAttendees.some((attendee) => attendee.email === currentUserEmail)) {
allRecentAttendees.push({email: currentUserEmail, displayName: currentUserEmail, avatarUrl: ''});
}

const seenAttendees = new Set<string>();
return (
allRecentAttendees
.filter((attendee) => {
// Deduplicate: use email for regular users, displayName for name-only attendees
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const key = attendee.email || attendee.displayName || '';
if (!key || seenAttendees.has(key)) {
return false;
}
seenAttendees.add(key);
return true;
})
.filter((attendee) => !attendees.find(({email, displayName}) => (attendee.email ? email === attendee.email : displayName === attendee.displayName)))
// Use || to fall back to displayName for name-only attendees (empty email)
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
.map((attendee) => attendee.email || attendee.displayName)
);
}

export {
createOption,
getContactOption,
Expand All @@ -509,6 +542,7 @@ export {
createOptionList,
getHeaderMessage,
getUserToInviteOption,
getFilteredRecentAttendees,
};

export type {OptionData};
Loading
Loading