From 4d5aff237694ebb8e0ce6ec0bb667b029646d635 Mon Sep 17 00:00:00 2001 From: ashu75575 Date: Wed, 21 Jan 2026 16:42:17 +0530 Subject: [PATCH 01/10] feat(insights): add Top Categories suggested search --- src/CONST/index.ts | 2 ++ src/languages/de.ts | 6 ++++-- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ src/languages/fr.ts | 6 ++++-- src/languages/it.ts | 4 +++- src/languages/ja.ts | 2 ++ src/languages/nl.ts | 4 +++- src/languages/pl.ts | 4 +++- src/languages/pt-BR.ts | 2 ++ src/languages/zh-hans.ts | 6 ++++-- src/libs/SearchUIUtils.ts | 43 ++++++++++++++++++++++++++++++++++++++- 12 files changed, 73 insertions(+), 10 deletions(-) mode change 100755 => 100644 src/languages/en.ts diff --git a/src/CONST/index.ts b/src/CONST/index.ts index c73cbde5f39a..b22bfddf285e 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -6801,6 +6801,7 @@ const CONST = { FROM: 'from', CARD: 'card', WITHDRAWAL_ID: 'withdrawal-id', + CATEGORY: 'category' }, get TYPE_CUSTOM_COLUMNS() { return { @@ -7234,6 +7235,7 @@ const CONST = { UNAPPROVED_CARD: 'unapprovedCard', RECONCILIATION: 'reconciliation', TOP_SPENDERS: 'topSpenders', + TOP_CATEGORIES: 'topCategories' }, GROUP_PREFIX: 'group_', ANIMATION: { diff --git a/src/languages/de.ts b/src/languages/de.ts index ee3064488ef2..9caad786a8f2 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -778,7 +778,7 @@ const translations: TranslationDeepObject = { expiredCodeDescription: 'Gehe zurück zum ursprünglichen Gerät und fordere einen neuen Code an', successfulNewCodeRequest: 'Code angefordert. Bitte überprüfe dein Gerät.', tfaRequiredTitle: dedent(` - Zwei-Faktor-Authentifizierung + Zwei-Faktor-Authentifizierung erforderlich `), tfaRequiredDescription: dedent(` @@ -6787,6 +6787,7 @@ Fordere Spesendetails wie Belege und Beschreibungen an, lege Limits und Standard deleteSavedSearchConfirm: 'Möchten Sie diese Suche wirklich löschen?', searchName: 'Name suchen', savedSearchesMenuItemTitle: 'Gespeichert', + topCategories: 'Top-Kategorien', groupedExpenses: 'gruppierte Ausgaben', bulkActions: { approve: 'Genehmigen', @@ -6845,7 +6846,8 @@ Fordere Spesendetails wie Belege und Beschreibungen an, lege Limits und Standard groupBy: { [CONST.SEARCH.GROUP_BY.FROM]: 'Von', [CONST.SEARCH.GROUP_BY.CARD]: 'Karte', - [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'Auszahlungs-ID', + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'Abhebe-ID', + [CONST.SEARCH.GROUP_BY.CATEGORY]: 'Kategorie', }, feed: 'Feed', withdrawalType: { diff --git a/src/languages/en.ts b/src/languages/en.ts old mode 100755 new mode 100644 index 726ecdde78a1..7255acfbd2dd --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -6669,6 +6669,7 @@ const translations = { deleteSavedSearchConfirm: 'Are you sure you want to delete this search?', searchName: 'Search name', savedSearchesMenuItemTitle: 'Saved', + topCategories: 'Top categories', groupedExpenses: 'grouped expenses', bulkActions: { approve: 'Approve', @@ -6728,6 +6729,7 @@ const translations = { [CONST.SEARCH.GROUP_BY.FROM]: 'From', [CONST.SEARCH.GROUP_BY.CARD]: 'Card', [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'Withdrawal ID', + [CONST.SEARCH.GROUP_BY.CATEGORY]: 'Category', }, feed: 'Feed', withdrawalType: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 08a3ed70277b..d51d8258640d 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -6416,6 +6416,7 @@ ${amount} para ${merchant} - ${date}`, topSpenders: 'Mayores gastadores', saveSearch: 'Guardar búsqueda', savedSearchesMenuItemTitle: 'Guardadas', + topCategories: 'Categorías principales', searchName: 'Nombre de la búsqueda', deleteSavedSearch: 'Eliminar búsqueda guardada', deleteSavedSearchConfirm: '¿Estás seguro de que quieres eliminar esta búsqueda?', @@ -6476,6 +6477,7 @@ ${amount} para ${merchant} - ${date}`, [CONST.SEARCH.GROUP_BY.FROM]: 'De', [CONST.SEARCH.GROUP_BY.CARD]: 'Tarjeta', [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'ID de retiro', + [CONST.SEARCH.GROUP_BY.CATEGORY]: 'Categoría', }, feed: 'Feed', withdrawalType: { diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 697db250e5fc..3db791267d25 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -781,7 +781,7 @@ const translations: TranslationDeepObject = { expiredCodeDescription: 'Retournez sur l’appareil d’origine et demandez un nouveau code', successfulNewCodeRequest: 'Code demandé. Veuillez vérifier votre appareil.', tfaRequiredTitle: dedent(` - Authentification à deux facteurs + Authentification à deux facteurs requise `), tfaRequiredDescription: dedent(` @@ -4619,7 +4619,7 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS})_.`, customSegmentScriptIDTitle: 'Quel est l’ID du script ?', - customSegmentScriptIDFooter: `Vous pouvez trouver les ID de script de segment personnalisé dans NetSuite sous : + customSegmentScriptIDFooter: `Vous pouvez trouver les ID de script de segment personnalisé dans NetSuite sous : 1. *Customization > Lists, Records, & Fields > Custom Segments*. 2. Cliquez sur un segment personnalisé. @@ -6799,6 +6799,7 @@ Exigez des informations de dépense comme les reçus et les descriptions, défin deleteSavedSearchConfirm: 'Voulez-vous vraiment supprimer cette recherche ?', searchName: 'Rechercher un nom', savedSearchesMenuItemTitle: 'Enregistré', + topCategories: 'Catégories principales', groupedExpenses: 'dépenses groupées', bulkActions: { approve: 'Approuver', @@ -6858,6 +6859,7 @@ Exigez des informations de dépense comme les reçus et les descriptions, défin [CONST.SEARCH.GROUP_BY.FROM]: 'De', [CONST.SEARCH.GROUP_BY.CARD]: 'Carte', [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'ID de retrait', + [CONST.SEARCH.GROUP_BY.CATEGORY]: 'Catégorie', }, feed: 'Flux', withdrawalType: { diff --git a/src/languages/it.ts b/src/languages/it.ts index 799e8b5325f9..c3e228404ced 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -779,7 +779,7 @@ const translations: TranslationDeepObject = { expiredCodeDescription: 'Torna al dispositivo originale e richiedi un nuovo codice', successfulNewCodeRequest: 'Codice richiesto. Controlla il tuo dispositivo.', tfaRequiredTitle: dedent(` - Autenticazione a due fattori + Autenticazione a due fattori richiesta `), tfaRequiredDescription: dedent(` @@ -6778,6 +6778,7 @@ Richiedi dettagli di spesa come ricevute e descrizioni, imposta limiti e valori deleteSavedSearchConfirm: 'Sei sicuro di voler eliminare questa ricerca?', searchName: 'Cerca nome', savedSearchesMenuItemTitle: 'Salvato', + topCategories: 'Categorie principali', groupedExpenses: 'spese raggruppate', bulkActions: { approve: 'Approva', @@ -6837,6 +6838,7 @@ Richiedi dettagli di spesa come ricevute e descrizioni, imposta limiti e valori [CONST.SEARCH.GROUP_BY.FROM]: 'Da', [CONST.SEARCH.GROUP_BY.CARD]: 'Carta', [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'ID prelievo', + [CONST.SEARCH.GROUP_BY.CATEGORY]: 'Categoria', }, feed: 'Feed', withdrawalType: { diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 7016a818d3b3..5c1f3d3a9e03 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -6719,6 +6719,7 @@ ${reportName} deleteSavedSearchConfirm: 'この検索を削除してもよろしいですか?', searchName: '名前を検索', savedSearchesMenuItemTitle: '保存済み', + topCategories: 'トップカテゴリ', groupedExpenses: 'グループ化された経費', bulkActions: { approve: '承認', @@ -6778,6 +6779,7 @@ ${reportName} [CONST.SEARCH.GROUP_BY.FROM]: '差出人', [CONST.SEARCH.GROUP_BY.CARD]: 'カード', [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: '出金ID', + [CONST.SEARCH.GROUP_BY.CATEGORY]: 'カテゴリー', }, feed: 'フィード', withdrawalType: { diff --git a/src/languages/nl.ts b/src/languages/nl.ts index ba7e9f9fee04..9d9d9dbd65f4 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -4598,7 +4598,7 @@ _Voor meer gedetailleerde instructies, [bezoek onze helppagina](${CONST.NETSUITE _Voor meer gedetailleerde instructies, [bezoek onze helpsite](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS})_.`, customSegmentScriptIDTitle: 'Wat is de script-ID?', - customSegmentScriptIDFooter: `Je kunt aangepaste segmentscript-ID’s in NetSuite vinden onder: + customSegmentScriptIDFooter: `Je kunt aangepaste segmentscript-ID’s in NetSuite vinden onder: 1. *Customization > Lists, Records, & Fields > Custom Segments*. 2. Klik op een aangepast segment. @@ -6763,6 +6763,7 @@ Vraag verplichte uitgavedetails zoals bonnetjes en beschrijvingen, stel limieten deleteSavedSearchConfirm: 'Weet je zeker dat je deze zoekopdracht wilt verwijderen?', searchName: 'Naam zoeken', savedSearchesMenuItemTitle: 'Opgeslagen', + topCategories: 'Topcategorieën', groupedExpenses: 'gegroepeerde uitgaven', bulkActions: { approve: 'Goedkeuren', @@ -6822,6 +6823,7 @@ Vraag verplichte uitgavedetails zoals bonnetjes en beschrijvingen, stel limieten [CONST.SEARCH.GROUP_BY.FROM]: 'Van', [CONST.SEARCH.GROUP_BY.CARD]: 'Kaart', [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'Opname-ID', + [CONST.SEARCH.GROUP_BY.CATEGORY]: 'Categorie', }, feed: 'Feed', withdrawalType: { diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 699d2ed3ce0e..d6a8bec8d227 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -779,7 +779,7 @@ const translations: TranslationDeepObject = { expiredCodeDescription: 'Wróć do oryginalnego urządzenia i poproś o nowy kod', successfulNewCodeRequest: 'Kod został wysłany. Sprawdź swoje urządzenie.', tfaRequiredTitle: dedent(` - Dwuskładnikowe uwierzytelnianie + Dwuskładnikowe uwierzytelnianie wymagane `), tfaRequiredDescription: dedent(` @@ -6749,6 +6749,7 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i deleteSavedSearchConfirm: 'Czy na pewno chcesz usunąć to wyszukiwanie?', searchName: 'Wyszukaj nazwę', savedSearchesMenuItemTitle: 'Zapisano', + topCategories: 'Najlepsze kategorie', groupedExpenses: 'pogrupowane wydatki', bulkActions: { approve: 'Zatwierdź', @@ -6808,6 +6809,7 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i [CONST.SEARCH.GROUP_BY.FROM]: 'Od', [CONST.SEARCH.GROUP_BY.CARD]: 'Karta', [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'ID wypłaty', + [CONST.SEARCH.GROUP_BY.CATEGORY]: 'Kategoria', }, feed: 'Kanał', withdrawalType: { diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index f45bb674ddbe..c34917506cee 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -6751,6 +6751,7 @@ Exija detalhes de despesas como recibos e descrições, defina limites e padrõe deleteSavedSearchConfirm: 'Você tem certeza de que deseja excluir esta pesquisa?', searchName: 'Pesquisar nome', savedSearchesMenuItemTitle: 'Salvo', + topCategories: 'Principais categorias', groupedExpenses: 'despesas agrupadas', bulkActions: { approve: 'Aprovar', @@ -6810,6 +6811,7 @@ Exija detalhes de despesas como recibos e descrições, defina limites e padrõe [CONST.SEARCH.GROUP_BY.FROM]: 'De', [CONST.SEARCH.GROUP_BY.CARD]: 'Cartão', [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'ID de saque', + [CONST.SEARCH.GROUP_BY.CATEGORY]: 'Categoria', }, feed: 'Feed', withdrawalType: { diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 80fa02850f20..658b53e01d78 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -764,7 +764,7 @@ const translations: TranslationDeepObject = { 请输入在最初请求该代码的设备上显示的代码 `), doNotShare: dedent(` - 不要与任何人分享你的验证码。 + 不要与任何人分享你的验证码。 Expensify 永远不会向你索要它! `), or: ',或', @@ -773,7 +773,7 @@ const translations: TranslationDeepObject = { expiredCodeDescription: '返回原始设备并请求新验证码', successfulNewCodeRequest: '已请求验证码。请检查您的设备。', tfaRequiredTitle: dedent(` - 双重身份验证 + 双重身份验证 必填 `), tfaRequiredDescription: dedent(` @@ -6607,6 +6607,7 @@ ${reportName} deleteSavedSearchConfirm: '您确定要删除此搜索吗?', searchName: '搜索名称', savedSearchesMenuItemTitle: '已保存', + topCategories: '热门类别', groupedExpenses: '已分组报销费用', bulkActions: { approve: '批准', @@ -6666,6 +6667,7 @@ ${reportName} [CONST.SEARCH.GROUP_BY.FROM]: '从', [CONST.SEARCH.GROUP_BY.CARD]: '卡片', [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: '提款 ID', + [CONST.SEARCH.GROUP_BY.CATEGORY]: '类别', }, feed: '动态', withdrawalType: { diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 1cc5c295ac06..0cb6c5c4b31b 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -630,6 +630,32 @@ function getSuggestedSearches( return this.searchQueryJSON?.similarSearchHash ?? CONST.DEFAULT_NUMBER_ID; }, }, + [CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES]: { + key: CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES, + translationPath: 'search.topCategories', + type: CONST.SEARCH.DATA_TYPES.EXPENSE, + icon: Expensicons.Folder, + searchQuery: buildQueryStringFromFilterFormValues( + { + type: CONST.SEARCH.DATA_TYPES.EXPENSE, + groupBy: CONST.SEARCH.GROUP_BY.CATEGORY, + dateOn: CONST.SEARCH.DATE_PRESETS.LAST_MONTH, + }, + { + sortBy: CONST.SEARCH.TABLE_COLUMNS.GROUP_TOTAL, + sortOrder: CONST.SEARCH.SORT_ORDER.DESC, + }, + ), + get searchQueryJSON() { + return buildSearchQueryJSON(this.searchQuery); + }, + get hash() { + return this.searchQueryJSON?.hash ?? CONST.DEFAULT_NUMBER_ID; + }, + get similarSearchHash() { + return this.searchQueryJSON?.similarSearchHash ?? CONST.DEFAULT_NUMBER_ID; + }, + }, }; } @@ -652,6 +678,7 @@ function getSuggestedSearchesVisibility( let shouldShowUnapprovedCardSuggestion = false; let shouldShowReconciliationSuggestion = false; let shouldShowTopSpendersSuggestion = false; + let shouldShowTopCategoriesSuggestion = false; const hasCardFeed = Object.values(cardFeedsByPolicy ?? {}).some((feeds) => feeds.length > 0); @@ -688,6 +715,7 @@ function getSuggestedSearchesVisibility( const isEligibleForReconciliationSuggestion = isPaidPolicy && isAdmin && ((isPaymentEnabled && hasVBBA && hasReimburser) || isECardEnabled); const isAuditor = policy.role === CONST.POLICY.ROLE.AUDITOR; const isEligibleForTopSpendersSuggestion = isPaidPolicy && (isAdmin || isAuditor || isApprover); + const isEligibleForTopCategoriesSuggestion = isPaidPolicy && (isAdmin || isAuditor || isApprover); shouldShowSubmitSuggestion ||= isEligibleForSubmitSuggestion; shouldShowPaySuggestion ||= isEligibleForPaySuggestion; @@ -698,6 +726,7 @@ function getSuggestedSearchesVisibility( shouldShowUnapprovedCardSuggestion ||= isEligibleForUnapprovedCardSuggestion; shouldShowReconciliationSuggestion ||= isEligibleForReconciliationSuggestion; shouldShowTopSpendersSuggestion ||= isEligibleForTopSpendersSuggestion; + shouldShowTopCategoriesSuggestion ||= isEligibleForTopCategoriesSuggestion; // We don't need to check the rest of the policies if we already determined that all suggestions should be displayed return ( @@ -709,7 +738,8 @@ function getSuggestedSearchesVisibility( shouldShowUnapprovedCashSuggestion && shouldShowUnapprovedCardSuggestion && shouldShowReconciliationSuggestion && - shouldShowTopSpendersSuggestion + shouldShowTopSpendersSuggestion && + shouldShowTopCategoriesSuggestion ); }); @@ -726,6 +756,7 @@ function getSuggestedSearchesVisibility( [CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CARD]: shouldShowUnapprovedCardSuggestion, [CONST.SEARCH.SEARCH_KEYS.RECONCILIATION]: shouldShowReconciliationSuggestion, [CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS]: shouldShowTopSpendersSuggestion, + [CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES]: shouldShowTopCategoriesSuggestion, }; } @@ -2928,6 +2959,16 @@ function createTypeMenuSections( }); } + if (suggestedSearchesVisibility[CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES]) { + insightsSection.menuItems.push({ + ...suggestedSearches[CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES], + emptyState: { + title: 'search.searchResults.emptyResults.title', + subtitle: 'search.searchResults.emptyResults.subtitle', + }, + }); + } + if (insightsSection.menuItems.length > 0) { typeMenuSections.push(insightsSection); } From 59f41f640111258e82065417d9b1b1754d78037f Mon Sep 17 00:00:00 2001 From: ashu75575 Date: Thu, 22 Jan 2026 02:07:34 +0530 Subject: [PATCH 02/10] fix style and translation --- src/CONST/index.ts | 4 ++-- src/languages/de.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index b22bfddf285e..f8197a0f05ea 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -6801,7 +6801,7 @@ const CONST = { FROM: 'from', CARD: 'card', WITHDRAWAL_ID: 'withdrawal-id', - CATEGORY: 'category' + CATEGORY: 'category', }, get TYPE_CUSTOM_COLUMNS() { return { @@ -7235,7 +7235,7 @@ const CONST = { UNAPPROVED_CARD: 'unapprovedCard', RECONCILIATION: 'reconciliation', TOP_SPENDERS: 'topSpenders', - TOP_CATEGORIES: 'topCategories' + TOP_CATEGORIES: 'topCategories', }, GROUP_PREFIX: 'group_', ANIMATION: { diff --git a/src/languages/de.ts b/src/languages/de.ts index 9caad786a8f2..0f22a04400da 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -6846,7 +6846,7 @@ Fordere Spesendetails wie Belege und Beschreibungen an, lege Limits und Standard groupBy: { [CONST.SEARCH.GROUP_BY.FROM]: 'Von', [CONST.SEARCH.GROUP_BY.CARD]: 'Karte', - [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'Abhebe-ID', + [CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID]: 'Auszahlungs-ID', [CONST.SEARCH.GROUP_BY.CATEGORY]: 'Kategorie', }, feed: 'Feed', From 19464953ac6cf796abfb7ed9cc5c1691572a38ce Mon Sep 17 00:00:00 2001 From: ashu75575 Date: Fri, 23 Jan 2026 04:07:22 +0530 Subject: [PATCH 03/10] fix ai comments --- src/libs/SearchUIUtils.ts | 124 +++++++++++++++++++------------------- 1 file changed, 61 insertions(+), 63 deletions(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 0cb6c5c4b31b..b3fca142ff02 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -370,6 +370,45 @@ type GetSectionsParams = { allTransactionViolations?: OnyxCollection; }; +/** + * Creates a top search menu item with common structure for TOP_SPENDERS and TOP_CATEGORIES + */ +function createTopSearchMenuItem( + key: SearchKey, + translationPath: TranslationPaths, + icon: IconAsset | Extract, + groupBy: ValueOf, +): SearchTypeMenuItem { + const searchQuery = buildQueryStringFromFilterFormValues( + { + type: CONST.SEARCH.DATA_TYPES.EXPENSE, + groupBy, + dateOn: CONST.SEARCH.DATE_PRESETS.LAST_MONTH, + }, + { + sortBy: CONST.SEARCH.TABLE_COLUMNS.GROUP_TOTAL, + sortOrder: CONST.SEARCH.SORT_ORDER.DESC, + }, + ); + + return { + key, + translationPath, + type: CONST.SEARCH.DATA_TYPES.EXPENSE, + icon, + searchQuery, + get searchQueryJSON() { + return buildSearchQueryJSON(this.searchQuery); + }, + get hash() { + return this.searchQueryJSON?.hash ?? CONST.DEFAULT_NUMBER_ID; + }, + get similarSearchHash() { + return this.searchQueryJSON?.similarSearchHash ?? CONST.DEFAULT_NUMBER_ID; + }, + }; +} + /** * Returns a list of all possible searches in the LHN, along with their query & hash. * *NOTE* When rendering the LHN, you should use the "createTypeMenuSections" method, which @@ -604,58 +643,18 @@ function getSuggestedSearches( return this.searchQueryJSON?.similarSearchHash ?? CONST.DEFAULT_NUMBER_ID; }, }, - [CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS]: { - key: CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS, - translationPath: 'search.topSpenders', - type: CONST.SEARCH.DATA_TYPES.EXPENSE, - icon: Expensicons.User, - searchQuery: buildQueryStringFromFilterFormValues( - { - type: CONST.SEARCH.DATA_TYPES.EXPENSE, - groupBy: CONST.SEARCH.GROUP_BY.FROM, - dateOn: CONST.SEARCH.DATE_PRESETS.LAST_MONTH, - }, - { - sortBy: CONST.SEARCH.TABLE_COLUMNS.GROUP_TOTAL, - sortOrder: CONST.SEARCH.SORT_ORDER.DESC, - }, - ), - get searchQueryJSON() { - return buildSearchQueryJSON(this.searchQuery); - }, - get hash() { - return this.searchQueryJSON?.hash ?? CONST.DEFAULT_NUMBER_ID; - }, - get similarSearchHash() { - return this.searchQueryJSON?.similarSearchHash ?? CONST.DEFAULT_NUMBER_ID; - }, - }, - [CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES]: { - key: CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES, - translationPath: 'search.topCategories', - type: CONST.SEARCH.DATA_TYPES.EXPENSE, - icon: Expensicons.Folder, - searchQuery: buildQueryStringFromFilterFormValues( - { - type: CONST.SEARCH.DATA_TYPES.EXPENSE, - groupBy: CONST.SEARCH.GROUP_BY.CATEGORY, - dateOn: CONST.SEARCH.DATE_PRESETS.LAST_MONTH, - }, - { - sortBy: CONST.SEARCH.TABLE_COLUMNS.GROUP_TOTAL, - sortOrder: CONST.SEARCH.SORT_ORDER.DESC, - }, - ), - get searchQueryJSON() { - return buildSearchQueryJSON(this.searchQuery); - }, - get hash() { - return this.searchQueryJSON?.hash ?? CONST.DEFAULT_NUMBER_ID; - }, - get similarSearchHash() { - return this.searchQueryJSON?.similarSearchHash ?? CONST.DEFAULT_NUMBER_ID; - }, - }, + [CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS]: createTopSearchMenuItem( + CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS, + 'search.topSpenders', + Expensicons.User, + CONST.SEARCH.GROUP_BY.FROM + ), + [CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES]: createTopSearchMenuItem( + CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES, + 'search.topCategories', + Expensicons.Folder, + CONST.SEARCH.GROUP_BY.CATEGORY + ), }; } @@ -2949,19 +2948,18 @@ function createTypeMenuSections( menuItems: [], }; - if (suggestedSearchesVisibility[CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS]) { - insightsSection.menuItems.push({ - ...suggestedSearches[CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS], - emptyState: { - title: 'search.searchResults.emptyResults.title', - subtitle: 'search.searchResults.emptyResults.subtitle', - }, - }); - } + const insightsSearchKeys = [ + CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS, + CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES, + ]; + + for (const key of insightsSearchKeys) { + if (!suggestedSearchesVisibility[key]) { + continue; + } - if (suggestedSearchesVisibility[CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES]) { insightsSection.menuItems.push({ - ...suggestedSearches[CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES], + ...suggestedSearches[key], emptyState: { title: 'search.searchResults.emptyResults.title', subtitle: 'search.searchResults.emptyResults.subtitle', From 7860598d8345a4853665470b57e9b11bcd5012e5 Mon Sep 17 00:00:00 2001 From: ashu75575 Date: Fri, 23 Jan 2026 04:09:53 +0530 Subject: [PATCH 04/10] code format --- src/libs/SearchUIUtils.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index b3fca142ff02..3464940bc2c0 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -643,17 +643,12 @@ function getSuggestedSearches( return this.searchQueryJSON?.similarSearchHash ?? CONST.DEFAULT_NUMBER_ID; }, }, - [CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS]: createTopSearchMenuItem( - CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS, - 'search.topSpenders', - Expensicons.User, - CONST.SEARCH.GROUP_BY.FROM - ), + [CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS]: createTopSearchMenuItem(CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS, 'search.topSpenders', Expensicons.User, CONST.SEARCH.GROUP_BY.FROM), [CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES]: createTopSearchMenuItem( CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES, 'search.topCategories', Expensicons.Folder, - CONST.SEARCH.GROUP_BY.CATEGORY + CONST.SEARCH.GROUP_BY.CATEGORY, ), }; } @@ -2948,10 +2943,7 @@ function createTypeMenuSections( menuItems: [], }; - const insightsSearchKeys = [ - CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS, - CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES, - ]; + const insightsSearchKeys = [CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS, CONST.SEARCH.SEARCH_KEYS.TOP_CATEGORIES]; for (const key of insightsSearchKeys) { if (!suggestedSearchesVisibility[key]) { From 97754949dda8f75d1cab629267b76a9a98085d6e Mon Sep 17 00:00:00 2001 From: ashu <127482564+ashu75575@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:51:55 +0530 Subject: [PATCH 05/10] Update src/languages/de.ts --- src/languages/de.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 0f22a04400da..10243c60ac9e 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -778,7 +778,7 @@ const translations: TranslationDeepObject = { expiredCodeDescription: 'Gehe zurück zum ursprünglichen Gerät und fordere einen neuen Code an', successfulNewCodeRequest: 'Code angefordert. Bitte überprüfe dein Gerät.', tfaRequiredTitle: dedent(` - Zwei-Faktor-Authentifizierung + Zwei-Faktor-Authentifizierung erforderlich `), tfaRequiredDescription: dedent(` From a5a06dfead7b82ba085924c12c639374d1999d79 Mon Sep 17 00:00:00 2001 From: ashu <127482564+ashu75575@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:52:51 +0530 Subject: [PATCH 06/10] Update src/languages/fr.ts --- src/languages/fr.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 3db791267d25..847d4586aaa1 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -781,7 +781,7 @@ const translations: TranslationDeepObject = { expiredCodeDescription: 'Retournez sur l’appareil d’origine et demandez un nouveau code', successfulNewCodeRequest: 'Code demandé. Veuillez vérifier votre appareil.', tfaRequiredTitle: dedent(` - Authentification à deux facteurs + Authentification à deux facteurs requise `), tfaRequiredDescription: dedent(` From d7e518350ddb62aef07be1222d96dae2ff9219b8 Mon Sep 17 00:00:00 2001 From: ashu <127482564+ashu75575@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:53:22 +0530 Subject: [PATCH 07/10] Update src/languages/fr.ts --- src/languages/fr.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 847d4586aaa1..41209ba0b679 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -4619,7 +4619,7 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS})_.`, customSegmentScriptIDTitle: 'Quel est l’ID du script ?', - customSegmentScriptIDFooter: `Vous pouvez trouver les ID de script de segment personnalisé dans NetSuite sous : + customSegmentScriptIDFooter: `Vous pouvez trouver les ID de script de segment personnalisé dans NetSuite sous : 1. *Customization > Lists, Records, & Fields > Custom Segments*. 2. Cliquez sur un segment personnalisé. From 6cb9d6a801b3ca3a466e07c4f5cd4dcc3aadda1c Mon Sep 17 00:00:00 2001 From: ashu <127482564+ashu75575@users.noreply.github.com> Date: Fri, 23 Jan 2026 14:00:40 +0530 Subject: [PATCH 08/10] remove unnecessary changes --- src/languages/it.ts | 2 +- src/languages/nl.ts | 2 +- src/languages/pl.ts | 2 +- src/languages/zh-hans.ts | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/languages/it.ts b/src/languages/it.ts index c3e228404ced..fe746ca70db1 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -779,7 +779,7 @@ const translations: TranslationDeepObject = { expiredCodeDescription: 'Torna al dispositivo originale e richiedi un nuovo codice', successfulNewCodeRequest: 'Codice richiesto. Controlla il tuo dispositivo.', tfaRequiredTitle: dedent(` - Autenticazione a due fattori + Autenticazione a due fattori richiesta `), tfaRequiredDescription: dedent(` diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 9d9d9dbd65f4..f00b86a4605b 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -4598,7 +4598,7 @@ _Voor meer gedetailleerde instructies, [bezoek onze helppagina](${CONST.NETSUITE _Voor meer gedetailleerde instructies, [bezoek onze helpsite](${CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS})_.`, customSegmentScriptIDTitle: 'Wat is de script-ID?', - customSegmentScriptIDFooter: `Je kunt aangepaste segmentscript-ID’s in NetSuite vinden onder: + customSegmentScriptIDFooter: `Je kunt aangepaste segmentscript-ID’s in NetSuite vinden onder: 1. *Customization > Lists, Records, & Fields > Custom Segments*. 2. Klik op een aangepast segment. diff --git a/src/languages/pl.ts b/src/languages/pl.ts index d6a8bec8d227..11f81aaf6474 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -779,7 +779,7 @@ const translations: TranslationDeepObject = { expiredCodeDescription: 'Wróć do oryginalnego urządzenia i poproś o nowy kod', successfulNewCodeRequest: 'Kod został wysłany. Sprawdź swoje urządzenie.', tfaRequiredTitle: dedent(` - Dwuskładnikowe uwierzytelnianie + Dwuskładnikowe uwierzytelnianie wymagane `), tfaRequiredDescription: dedent(` diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 658b53e01d78..cfcff1c8aa3c 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -764,7 +764,7 @@ const translations: TranslationDeepObject = { 请输入在最初请求该代码的设备上显示的代码 `), doNotShare: dedent(` - 不要与任何人分享你的验证码。 + 不要与任何人分享你的验证码。 Expensify 永远不会向你索要它! `), or: ',或', @@ -773,7 +773,7 @@ const translations: TranslationDeepObject = { expiredCodeDescription: '返回原始设备并请求新验证码', successfulNewCodeRequest: '已请求验证码。请检查您的设备。', tfaRequiredTitle: dedent(` - 双重身份验证 + 双重身份验证 必填 `), tfaRequiredDescription: dedent(` From 6def8314ba0d6f088874ede4ef9ec706acc048c9 Mon Sep 17 00:00:00 2001 From: ashu75575 Date: Fri, 23 Jan 2026 17:28:25 +0530 Subject: [PATCH 09/10] fix "Top Categories" visibility logic in getSuggestedSearchesVisibility() --- src/libs/SearchUIUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 3464940bc2c0..0bdc96f35548 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -709,7 +709,7 @@ function getSuggestedSearchesVisibility( const isEligibleForReconciliationSuggestion = isPaidPolicy && isAdmin && ((isPaymentEnabled && hasVBBA && hasReimburser) || isECardEnabled); const isAuditor = policy.role === CONST.POLICY.ROLE.AUDITOR; const isEligibleForTopSpendersSuggestion = isPaidPolicy && (isAdmin || isAuditor || isApprover); - const isEligibleForTopCategoriesSuggestion = isPaidPolicy && (isAdmin || isAuditor || isApprover); + const isEligibleForTopCategoriesSuggestion = isPaidPolicy; shouldShowSubmitSuggestion ||= isEligibleForSubmitSuggestion; shouldShowPaySuggestion ||= isEligibleForPaySuggestion; From af79c27cfe1cf18302c6a424c58a4f22cdf49fc5 Mon Sep 17 00:00:00 2001 From: ashu75575 Date: Tue, 27 Jan 2026 13:53:25 +0530 Subject: [PATCH 10/10] merge main and add limit filter --- src/CONST/index.ts | 1 + src/libs/SearchUIUtils.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 3979369e844e..a4281c1ad1fa 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -7292,6 +7292,7 @@ const CONST = { FADE_DURATION: 200, }, TODO_BADGE_MAX_COUNT: 50, + TOP_SEARCH_LIMIT: 10, }, SEARCH_SELECTOR: { SELECTION_MODE_SINGLE: 'single', diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 9bd9880a8fe0..98550f27fee9 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -397,6 +397,7 @@ function createTopSearchMenuItem( translationPath: TranslationPaths, icon: IconAsset | Extract, groupBy: ValueOf, + limit?: number, ): SearchTypeMenuItem { const searchQuery = buildQueryStringFromFilterFormValues( { @@ -407,6 +408,7 @@ function createTopSearchMenuItem( { sortBy: CONST.SEARCH.TABLE_COLUMNS.GROUP_TOTAL, sortOrder: CONST.SEARCH.SORT_ORDER.DESC, + ...(limit && {limit}), }, ); @@ -668,6 +670,7 @@ function getSuggestedSearches( 'search.topCategories', Expensicons.Folder, CONST.SEARCH.GROUP_BY.CATEGORY, + CONST.SEARCH.TOP_SEARCH_LIMIT, ), }; }