@@ -82,6 +82,7 @@ import {
8282 isWhisperAction ,
8383 shouldReportActionBeVisible ,
8484} from '@libs/ReportActionsUtils' ;
85+ import { computeReportName } from '@libs/ReportNameUtils' ;
8586import type { OptionData } from '@libs/ReportUtils' ;
8687import {
8788 canUserPerformWriteAction ,
@@ -104,7 +105,6 @@ import {
104105 getRejectedReportMessage ,
105106 getReportActionActorAccountID ,
106107 getReportLastMessage ,
107- getReportName ,
108108 getReportNotificationPreference ,
109109 getReportOrDraftReport ,
110110 getReportPreviewMessage ,
@@ -658,8 +658,8 @@ function getLastMessageTextForReport({
658658 : undefined ;
659659 // For workspace chats, use the report title
660660 if ( reportUtilsIsPolicyExpenseChat ( report ) && ! isEmptyObject ( iouReport ) ) {
661- // eslint-disable-next-line @typescript-eslint/no-deprecated
662- lastMessageTextFromReport = formatReportLastMessageText ( getReportName ( iouReport ) ) ;
661+ const reportName = computeReportName ( iouReport ) ;
662+ lastMessageTextFromReport = formatReportLastMessageText ( reportName ) ;
663663 } else {
664664 const reportPreviewMessage = getReportPreviewMessage (
665665 ! isEmptyObject ( iouReport ) ? iouReport : null ,
@@ -913,11 +913,12 @@ function createOption(
913913 showPersonalDetails && personalDetail ?. login
914914 ? personalDetail . login
915915 : getAlternateText ( result , { showChatPreviewLine, forcePolicyNamePreview} , ! ! result . private_isArchived , lastActorDetails ) ;
916+
917+ const personalDetailsForCompute : PersonalDetailsList | undefined = personalDetails ?? undefined ;
918+ const computedReportName = computeReportName ( report , undefined , undefined , undefined , allReportNameValuePairs , personalDetailsForCompute , undefined ) ;
916919 reportName = showPersonalDetails
917- ? getDisplayNameForParticipant ( { accountID : accountIDs . at ( 0 ) , personalDetailsData : personalDetails ?? undefined , formatPhoneNumber : formatPhoneNumberPhoneUtils } ) ||
918- formatPhoneNumberPhoneUtils ( personalDetail ?. login ?? '' )
919- : // eslint-disable-next-line @typescript-eslint/no-deprecated
920- getReportName ( report ) ;
920+ ? getDisplayNameForParticipant ( { accountID : accountIDs . at ( 0 ) , formatPhoneNumber : formatPhoneNumberPhoneUtils } ) || formatPhoneNumberPhoneUtils ( personalDetail ?. login ?? '' )
921+ : computedReportName ;
921922 } else {
922923 // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
923924 reportName =
@@ -934,7 +935,8 @@ function createOption(
934935 result . subtitle = subtitle ;
935936
936937 // Set login and accountID only for single participant cases (used in SearchOption context)
937- if ( ! hasMultipleParticipants && ( ! report || ( report && ! reportUtilsIsGroupChat ( report ) && ! reportUtilsIsChatRoom ( report ) ) ) ) {
938+ // Also always set for personal details options (showPersonalDetails: true) to ensure search filtering works correctly
939+ if ( showPersonalDetails || ( ! hasMultipleParticipants && ( ! report || ( report && ! reportUtilsIsGroupChat ( report ) && ! reportUtilsIsChatRoom ( report ) ) ) ) ) {
938940 result . login = personalDetail ?. login ;
939941 result . accountID = Number ( personalDetail ?. accountID ) ;
940942 }
@@ -965,8 +967,7 @@ function getReportOption(participant: Participant, reportAttributesDerived?: Rep
965967 // eslint-disable-next-line @typescript-eslint/no-deprecated
966968 option . alternateText = translateLocal ( 'reportActionsView.yourSpace' ) ;
967969 } else if ( option . isInvoiceRoom ) {
968- // eslint-disable-next-line @typescript-eslint/no-deprecated
969- option . text = getReportName ( report ) ;
970+ option . text = computeReportName ( report , undefined , undefined , undefined , allReportNameValuePairs , allPersonalDetails , undefined ) ;
970971 // eslint-disable-next-line @typescript-eslint/no-deprecated
971972 option . alternateText = translateLocal ( 'workspace.common.invoices' ) ;
972973 } else {
@@ -1015,8 +1016,7 @@ function getReportDisplayOption(report: OnyxEntry<Report>, unknownUserDetails: O
10151016 // eslint-disable-next-line @typescript-eslint/no-deprecated
10161017 option . alternateText = translateLocal ( 'reportActionsView.yourSpace' ) ;
10171018 } else if ( option . isInvoiceRoom ) {
1018- // eslint-disable-next-line @typescript-eslint/no-deprecated
1019- option . text = getReportName ( report ) ;
1019+ option . text = computeReportName ( report , undefined , undefined , undefined , allReportNameValuePairs , allPersonalDetails , undefined ) ;
10201020 // eslint-disable-next-line @typescript-eslint/no-deprecated
10211021 option . alternateText = translateLocal ( 'workspace.common.invoices' ) ;
10221022 } else if ( unknownUserDetails ) {
@@ -1236,6 +1236,119 @@ function createOptionList(personalDetails: OnyxEntry<PersonalDetailsList>, repor
12361236 } ;
12371237}
12381238
1239+ /**
1240+ * Creates an optimized option list with smart pre-filtering.
1241+ *
1242+ * Performance optimization approach:
1243+ * 1. Pre-filters reports using shouldReportBeInOptionList with correct parameters (betas, etc.)
1244+ * 2. Sorts by lastVisibleActionCreated (most recent first)
1245+ * 3. Limits to top N reports
1246+ * 4. Processes only those N reports
1247+ *
1248+ * This avoids processing thousands of reports while ensuring correct filtering.
1249+ *
1250+ * Use this for screens that need recent reports (NewChatPage, WorkspaceInvitePage, etc.)
1251+ */
1252+ function createFilteredOptionList (
1253+ personalDetails : OnyxEntry < PersonalDetailsList > ,
1254+ reports : OnyxCollection < Report > ,
1255+ reportAttributesDerived : ReportAttributesDerivedValue [ 'reports' ] | undefined ,
1256+ options : {
1257+ maxRecentReports ?: number ;
1258+ includeP2P ?: boolean ;
1259+ searchTerm ?: string ;
1260+ betas ?: OnyxEntry < Beta [ ] > ;
1261+ } = { } ,
1262+ ) {
1263+ const { maxRecentReports = 500 , includeP2P = true , searchTerm = '' } = options ;
1264+ const reportMapForAccountIDs : Record < number , Report > = { } ;
1265+
1266+ // Step 1: Pre-filter reports to avoid processing thousands
1267+ // Only filter out null/undefined - let shouldReportBeInOptionList handle business logic
1268+ const reportsArray = Object . values ( reports ?? { } ) . filter ( ( report ) : report is Report => {
1269+ return ! ! report ;
1270+ } ) ;
1271+
1272+ // Step 2: Sort by lastVisibleActionCreated (most recent first)
1273+ const sortedReports = reportsArray . sort ( ( a , b ) => {
1274+ const aTime = new Date ( a . lastVisibleActionCreated ?? 0 ) . getTime ( ) ;
1275+ const bTime = new Date ( b . lastVisibleActionCreated ?? 0 ) . getTime ( ) ;
1276+ return bTime - aTime ;
1277+ } ) ;
1278+
1279+ // Step 3: Limit to top N reports
1280+ const limitedReports = sortedReports . slice ( 0 , maxRecentReports ) ;
1281+
1282+ // Step 4: If search term is present, build report map with ONLY 1:1 DM reports
1283+ // This allows personal details to have valid 1:1 DM reportIDs for proper avatar display
1284+ // Users without 1:1 DMs will have no report mapped, causing getIcons to fall back to personal avatar
1285+ if ( searchTerm ?. trim ( ) ) {
1286+ const allReportsArray = Object . values ( reports ?? { } ) ;
1287+
1288+ // Add ONLY 1:1 DM reports (never add group/policy chats to maintain personal avatars)
1289+ for ( const report of allReportsArray ) {
1290+ if ( ! report ) {
1291+ continue ;
1292+ }
1293+
1294+ // Check if this is a 1:1 DM (not a group/policy/room chat)
1295+ const is1on1DM = reportUtilsIsOneOnOneChat ( report ) ;
1296+
1297+ if ( is1on1DM ) {
1298+ const accountIDs = getParticipantsAccountIDsForDisplay ( report ) ;
1299+ for ( const accountID of accountIDs ) {
1300+ // ALWAYS set 1:1 DMs - prioritize them over policy/group chats
1301+ // This ensures proper avatar display for personal details
1302+ reportMapForAccountIDs [ accountID ] = report ;
1303+ }
1304+ }
1305+ }
1306+ }
1307+
1308+ // Step 5: Process the limited set of reports (performance optimization)
1309+ const reportOptions : Array < SearchOption < Report > > = [ ] ;
1310+ for ( const report of limitedReports ) {
1311+ const { reportMapEntry, reportOption} = processReport ( report , personalDetails , reportAttributesDerived ) ;
1312+
1313+ if ( reportMapEntry ) {
1314+ const [ accountID , reportValue ] = reportMapEntry ;
1315+
1316+ // Preserve 1:1 DMs from Step 4 - don't overwrite them with non-1:1 reports
1317+ const existing = reportMapForAccountIDs [ accountID ] ;
1318+ const existingIs1on1 = existing && reportUtilsIsOneOnOneChat ( existing ) ;
1319+ const newIs1on1 = reportUtilsIsOneOnOneChat ( reportValue ) ;
1320+
1321+ // Only overwrite if: no existing, existing is not 1:1, or both are 1:1 (prefer newer)
1322+ const shouldOverwrite = ! existing || ! existingIs1on1 || newIs1on1 ;
1323+
1324+ if ( shouldOverwrite ) {
1325+ reportMapForAccountIDs [ accountID ] = reportValue ;
1326+ }
1327+ }
1328+
1329+ if ( reportOption ) {
1330+ reportOptions . push ( reportOption ) ;
1331+ }
1332+ }
1333+
1334+ // Step 6: Process personal details (all of them - needed for search functionality)
1335+ const personalDetailsOptions = includeP2P
1336+ ? Object . values ( personalDetails ?? { } ) . map ( ( personalDetail ) => {
1337+ const accountID = personalDetail ?. accountID ?? CONST . DEFAULT_NUMBER_ID ;
1338+
1339+ return {
1340+ item : personalDetail ,
1341+ ...createOption ( [ accountID ] , personalDetails , reportMapForAccountIDs [ accountID ] , { showPersonalDetails : true } , reportAttributesDerived ) ,
1342+ } ;
1343+ } )
1344+ : [ ] ;
1345+
1346+ return {
1347+ reports : reportOptions ,
1348+ personalDetails : personalDetailsOptions as Array < SearchOption < PersonalDetails > > ,
1349+ } ;
1350+ }
1351+
12391352function createOptionFromReport ( report : Report , personalDetails : OnyxEntry < PersonalDetailsList > , reportAttributesDerived ?: ReportAttributesDerivedValue [ 'reports' ] , config ?: PreviewConfig ) {
12401353 const accountIDs = getParticipantsAccountIDsForDisplay ( report ) ;
12411354
@@ -2812,6 +2925,7 @@ export {
28122925 combineOrderingOfReportsAndPersonalDetails ,
28132926 createOptionFromReport ,
28142927 createOptionList ,
2928+ createFilteredOptionList ,
28152929 createOption ,
28162930 filterAndOrderOptions ,
28172931 filterOptions ,
0 commit comments