Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7124,6 +7124,7 @@ const CONST = {
UNAPPROVED_CASH: 'unapprovedCash',
UNAPPROVED_CARD: 'unapprovedCard',
RECONCILIATION: 'reconciliation',
TOP_SPENDERS: 'topSpenders',
},
GROUP_PREFIX: 'group_',
ANIMATION: {
Expand Down
10 changes: 7 additions & 3 deletions src/components/Search/SearchPageHeader/SearchFiltersBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,14 +308,18 @@ function SearchFiltersBar({
updatedFilterFormValues.columns = [];
}

const queryString = buildQueryStringFromFilterFormValues(updatedFilterFormValues);
// Preserve the current sortBy and sortOrder from queryJSON when updating filters
const queryString = buildQueryStringFromFilterFormValues(updatedFilterFormValues, {
sortBy: queryJSON.sortBy,
sortOrder: queryJSON.sortOrder,
});

close(() => {
// We want to explicitly clear stale rawQuery since its only used for manually typed-in queries.
// We want to explicitly clear stale rawQuery since it's only used for manually typed-in queries.
Navigation.setParams({q: queryString, rawQuery: undefined});
});
},
[searchAdvancedFiltersForm],
[searchAdvancedFiltersForm, queryJSON.sortBy, queryJSON.sortOrder],
);

const openAdvancedFilters = useCallback(() => {
Expand Down
2 changes: 2 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ const translations: TranslationDeepObject<typeof en> = {
reimbursableTotal: 'Erstattungsfähiger Gesamtbetrag',
nonReimbursableTotal: 'Nicht erstattungsfähiger Gesamtbetrag',
originalAmount: 'Ursprünglicher Betrag',
insights: 'Einblicke',
},
supportalNoAccess: {
title: 'Nicht so schnell',
Expand Down Expand Up @@ -6861,6 +6862,7 @@ Fordere Spesendetails wie Belege und Beschreibungen an, lege Limits und Standard
selectAllMatchingItems: 'Alle passenden Elemente auswählen',
allMatchingItemsSelected: 'Alle passenden Elemente ausgewählt',
},
topSpenders: 'Top-Ausgaben',
},
genericErrorPage: {
title: 'Oh je, etwas ist schiefgelaufen!',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,7 @@ const translations = {
sharedIn: 'Shared in',
unreported: 'Unreported',
explore: 'Explore',
insights: 'Insights',
todo: 'To-do',
invoice: 'Invoice',
expense: 'Expense',
Expand Down Expand Up @@ -6620,6 +6621,7 @@ const translations = {
unapprovedCash: 'Unapproved cash',
unapprovedCard: 'Unapproved card',
reconciliation: 'Reconciliation',
topSpenders: 'Top spenders',
saveSearch: 'Save search',
deleteSavedSearch: 'Delete saved search',
deleteSavedSearchConfirm: 'Are you sure you want to delete this search?',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ const translations: TranslationDeepObject<typeof en> = {
sharedIn: 'Compartido en',
unreported: 'No reportado',
explore: 'Explorar',
insights: 'Información',
todo: 'Tereas',
invoice: 'Factura',
expense: 'Gasto',
Expand Down Expand Up @@ -6327,6 +6328,7 @@ ${amount} para ${merchant} - ${date}`,
unapprovedCash: 'Efectivo no aprobado',
unapprovedCard: 'Tarjeta no aprobada',
reconciliation: 'Conciliación',
topSpenders: 'Mayores gastadores',
saveSearch: 'Guardar búsqueda',
savedSearchesMenuItemTitle: 'Guardadas',
searchName: 'Nombre de la búsqueda',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@ const translations: TranslationDeepObject<typeof en> = {
reimbursableTotal: 'Total remboursable',
nonReimbursableTotal: 'Total non remboursable',
originalAmount: 'Montant d’origine',
insights: 'Analyses',
},
supportalNoAccess: {
title: 'Pas si vite',
Expand Down Expand Up @@ -6872,6 +6873,7 @@ Exigez des informations de dépense comme les reçus et les descriptions, défin
selectAllMatchingItems: 'Sélectionner tous les éléments correspondants',
allMatchingItemsSelected: 'Tous les éléments correspondants sont sélectionnés',
},
topSpenders: 'Plus gros dépensiers',
},
genericErrorPage: {
title: 'Oh oh, quelque chose s’est mal passé !',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ const translations: TranslationDeepObject<typeof en> = {
reimbursableTotal: 'Totale rimborsabile',
nonReimbursableTotal: 'Totale non rimborsabile',
originalAmount: 'Importo originale',
insights: 'Analisi',
},
supportalNoAccess: {
title: 'Non così in fretta',
Expand Down Expand Up @@ -6846,6 +6847,7 @@ Richiedi dettagli di spesa come ricevute e descrizioni, imposta limiti e valori
selectAllMatchingItems: 'Seleziona tutti gli elementi corrispondenti',
allMatchingItemsSelected: 'Tutti gli elementi corrispondenti selezionati',
},
topSpenders: 'Maggiori spenditori',
},
genericErrorPage: {
title: 'Uh-oh, qualcosa è andato storto!',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ const translations: TranslationDeepObject<typeof en> = {
reimbursableTotal: '経費精算対象の合計',
nonReimbursableTotal: '非払い戻し合計',
originalAmount: '元の金額',
insights: 'インサイト',
},
supportalNoAccess: {
title: 'ちょっと待ってください',
Expand Down Expand Up @@ -6790,6 +6791,7 @@ ${reportName}
selectAllMatchingItems: '一致する項目をすべて選択',
allMatchingItemsSelected: '一致する項目をすべて選択済み',
},
topSpenders: 'トップ支出者',
},
genericErrorPage: {
title: 'おっと、問題が発生しました!',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ const translations: TranslationDeepObject<typeof en> = {
reimbursableTotal: 'Totaal te vergoeden',
nonReimbursableTotal: 'Niet-vergoedbaar totaal',
originalAmount: 'Oorspronkelijk bedrag',
insights: 'Inzichten',
},
supportalNoAccess: {
title: 'Niet zo snel',
Expand Down Expand Up @@ -6833,6 +6834,7 @@ Vraag verplichte uitgavedetails zoals bonnetjes en beschrijvingen, stel limieten
selectAllMatchingItems: 'Selecteer alle overeenkomende items',
allMatchingItemsSelected: 'Alle overeenkomende items geselecteerd',
},
topSpenders: 'Grootste uitgaven',
},
genericErrorPage: {
title: 'O jee, er is iets misgegaan!',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ const translations: TranslationDeepObject<typeof en> = {
reimbursableTotal: 'Łączna kwota podlegająca zwrotowi',
nonReimbursableTotal: 'Suma niepodlegająca zwrotowi',
originalAmount: 'Kwota pierwotna',
insights: 'Analizy',
},
supportalNoAccess: {
title: 'Nie tak szybko',
Expand Down Expand Up @@ -6821,6 +6822,7 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i
selectAllMatchingItems: 'Zaznacz wszystkie pasujące elementy',
allMatchingItemsSelected: 'Wybrano wszystkie pasujące elementy',
},
topSpenders: 'Najwięksi wydający',
},
genericErrorPage: {
title: 'Ups, coś poszło nie tak!',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,7 @@ const translations: TranslationDeepObject<typeof en> = {
reimbursableTotal: 'Total reembolsável',
nonReimbursableTotal: 'Total não reembolsável',
originalAmount: 'Valor original',
insights: 'Insights',
},
supportalNoAccess: {
title: 'Não tão rápido',
Expand Down Expand Up @@ -6825,6 +6826,7 @@ Exija detalhes de despesas como recibos e descrições, defina limites e padrõe
selectAllMatchingItems: 'Selecionar todos os itens correspondentes',
allMatchingItemsSelected: 'Todos os itens correspondentes selecionados',
},
topSpenders: 'Maiores gastadores',
},
genericErrorPage: {
title: 'Opa, algo deu errado!',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ const translations: TranslationDeepObject<typeof en> = {
reimbursableTotal: '可报销总额',
nonReimbursableTotal: '不可报销总额',
originalAmount: '原始金额',
insights: '洞察',
},
supportalNoAccess: {
title: '先别急',
Expand Down Expand Up @@ -6683,6 +6684,7 @@ ${reportName}
selectAllMatchingItems: '选择所有匹配的项目',
allMatchingItemsSelected: '已选择所有匹配的项目',
},
topSpenders: '最高支出者',
},
genericErrorPage: {
title: '哎呀,出错了!',
Expand Down
11 changes: 8 additions & 3 deletions src/libs/SearchQueryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,13 +542,18 @@ function getSanitizedRawFilters(queryJSON: SearchQueryJSON): RawQueryFilter[] |
return sanitizedFilters;
}

type BuildQueryStringOptions = {
sortBy?: string;
sortOrder?: string;
};

/**
* Formats a given object with search filter values into the string version of the query.
* Main usage is to consume data format that comes from AdvancedFilters Onyx Form Data, and generate query string.
*
* Reverse operation of buildFilterFormValuesFromQuery()
*/
function buildQueryStringFromFilterFormValues(filterValues: Partial<SearchAdvancedFiltersForm>) {
function buildQueryStringFromFilterFormValues(filterValues: Partial<SearchAdvancedFiltersForm>, options?: BuildQueryStringOptions) {
const supportedFilterValues = {...filterValues};

// When switching types/setting the type, ensure we aren't polluting our query with filters that are
Expand All @@ -566,8 +571,8 @@ function buildQueryStringFromFilterFormValues(filterValues: Partial<SearchAdvanc
const {type, status, groupBy, columns, ...otherFilters} = supportedFilterValues;
const filtersString: string[] = [];

filtersString.push(`${CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_BY}:${CONST.SEARCH.TABLE_COLUMNS.DATE}`);
filtersString.push(`${CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_ORDER}:${CONST.SEARCH.SORT_ORDER.DESC}`);
filtersString.push(`${CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_BY}:${options?.sortBy ?? CONST.SEARCH.TABLE_COLUMNS.DATE}`);
filtersString.push(`${CONST.SEARCH.SYNTAX_ROOT_KEYS.SORT_ORDER}:${options?.sortOrder ?? CONST.SEARCH.SORT_ORDER.DESC}`);

if (type) {
const sanitizedType = sanitizeSearchValue(type);
Expand Down
56 changes: 55 additions & 1 deletion src/libs/SearchUIUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,32 @@ 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;
},
},
};
}

Expand All @@ -618,6 +644,7 @@ function getSuggestedSearchesVisibility(
let shouldShowUnapprovedCashSuggestion = false;
let shouldShowUnapprovedCardSuggestion = false;
let shouldShowReconciliationSuggestion = false;
let shouldShowTopSpendersSuggestion = false;

const hasCardFeed = Object.values(cardFeedsByPolicy ?? {}).some((feeds) => feeds.length > 0);

Expand Down Expand Up @@ -652,6 +679,8 @@ function getSuggestedSearchesVisibility(
const isEligibleForUnapprovedCashSuggestion = isPaidPolicy && isAdmin && isApprovalEnabled && isPaymentEnabled;
const isEligibleForUnapprovedCardSuggestion = isPaidPolicy && isAdmin && isApprovalEnabled && (hasCardFeed || !!defaultExpensifyCard);
const isEligibleForReconciliationSuggestion = isPaidPolicy && isAdmin && ((isPaymentEnabled && hasVBBA && hasReimburser) || isECardEnabled);
const isAuditor = policy.role === CONST.POLICY.ROLE.AUDITOR;
const isEligibleForTopSpendersSuggestion = isPaidPolicy && (isAdmin || isAuditor || isApprover);

shouldShowSubmitSuggestion ||= isEligibleForSubmitSuggestion;
shouldShowPaySuggestion ||= isEligibleForPaySuggestion;
Expand All @@ -661,6 +690,7 @@ function getSuggestedSearchesVisibility(
shouldShowUnapprovedCashSuggestion ||= isEligibleForUnapprovedCashSuggestion;
shouldShowUnapprovedCardSuggestion ||= isEligibleForUnapprovedCardSuggestion;
shouldShowReconciliationSuggestion ||= isEligibleForReconciliationSuggestion;
shouldShowTopSpendersSuggestion ||= isEligibleForTopSpendersSuggestion;

// We don't need to check the rest of the policies if we already determined that all suggestions should be displayed
return (
Expand All @@ -671,7 +701,8 @@ function getSuggestedSearchesVisibility(
shouldShowStatementsSuggestion &&
shouldShowUnapprovedCashSuggestion &&
shouldShowUnapprovedCardSuggestion &&
shouldShowReconciliationSuggestion
shouldShowReconciliationSuggestion &&
shouldShowTopSpendersSuggestion
);
});

Expand All @@ -687,6 +718,7 @@ function getSuggestedSearchesVisibility(
[CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CASH]: shouldShowUnapprovedCashSuggestion,
[CONST.SEARCH.SEARCH_KEYS.UNAPPROVED_CARD]: shouldShowUnapprovedCardSuggestion,
[CONST.SEARCH.SEARCH_KEYS.RECONCILIATION]: shouldShowReconciliationSuggestion,
[CONST.SEARCH.SEARCH_KEYS.TOP_SPENDERS]: shouldShowTopSpendersSuggestion,
};
}

Expand Down Expand Up @@ -2849,6 +2881,28 @@ function createTypeMenuSections(
}
}

// Insights section
{
const insightsSection: SearchTypeMenuSection = {
translationPath: 'common.insights',
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',
},
});
}

if (insightsSection.menuItems.length > 0) {
typeMenuSections.push(insightsSection);
}
}

// Explore section
{
const exploreSection: SearchTypeMenuSection = {
Expand Down
Loading