Skip to content

Commit

Permalink
Merge pull request #46566 from software-mansion-labs/search-v2/curren…
Browse files Browse the repository at this point in the history
…cy-filter

[Search v2] [App] Create Currency filter
  • Loading branch information
luacmartins authored Aug 6, 2024
2 parents 6c3dcc2 + 17de584 commit f7e265a
Show file tree
Hide file tree
Showing 20 changed files with 140 additions and 24 deletions.
2 changes: 2 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const ROUTES = {

SEARCH_ADVANCED_FILTERS_STATUS: 'search/filters/status',

SEARCH_ADVANCED_FILTERS_CURRENCY: 'search/filters/currency',

SEARCH_ADVANCED_FILTERS_MERCHANT: 'search/filters/merchant',

SEARCH_ADVANCED_FILTERS_DESCRIPTION: 'search/filters/description',
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const SCREENS = {
ADVANCED_FILTERS_DATE_RHP: 'Search_Advanced_Filters_Date_RHP',
ADVANCED_FILTERS_TYPE_RHP: 'Search_Advanced_Filters_Type_RHP',
ADVANCED_FILTERS_STATUS_RHP: 'Search_Advanced_Filters_Status_RHP',
ADVANCED_FILTERS_CURRENCY_RHP: 'Search_Advanced_Filters_Currency_RHP',
ADVANCED_FILTERS_DESCRIPTION_RHP: 'Search_Advanced_Filters_Description_RHP',
ADVANCED_FILTERS_MERCHANT_RHP: 'Search_Advanced_Filters_Merchant_RHP',
ADVANCED_FILTERS_REPORT_ID_RHP: 'Search_Advanced_Filters_ReportID_RHP',
Expand Down
20 changes: 16 additions & 4 deletions src/components/CurrencySelectionList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@ import React, {useMemo, useState} from 'react';
import {withOnyx} from 'react-native-onyx';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import SelectableListItem from '@components/SelectionList/SelectableListItem';
import useLocalize from '@hooks/useLocalize';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import type {CurrencyListItem, CurrencySelectionListOnyxProps, CurrencySelectionListProps} from './types';

function CurrencySelectionList({searchInputLabel, initiallySelectedCurrencyCode, onSelect, currencyList}: CurrencySelectionListProps) {
function CurrencySelectionList({searchInputLabel, initiallySelectedCurrencyCode, onSelect, currencyList, selectedCurrencies = [], canSelectMultiple = false}: CurrencySelectionListProps) {
const [searchValue, setSearchValue] = useState('');
const {translate} = useLocalize();

const {sections, headerMessage} = useMemo(() => {
const currencyOptions: CurrencyListItem[] = Object.entries(currencyList ?? {}).map(([currencyCode, currencyInfo]) => {
const isSelectedCurrency = currencyCode === initiallySelectedCurrencyCode;
const isSelectedCurrency = currencyCode === initiallySelectedCurrencyCode || selectedCurrencies.includes(currencyCode);
return {
currencyName: currencyInfo?.name ?? '',
text: `${currencyCode} - ${CurrencyUtils.getCurrencySymbol(currencyCode)}`,
Expand All @@ -28,6 +29,16 @@ function CurrencySelectionList({searchInputLabel, initiallySelectedCurrencyCode,
const filteredCurrencies = currencyOptions.filter((currencyOption) => searchRegex.test(currencyOption.text ?? '') || searchRegex.test(currencyOption.currencyName));
const isEmpty = searchValue.trim() && !filteredCurrencies.length;

if (canSelectMultiple) {
filteredCurrencies.sort((currencyA, currencyB) => {
if (currencyA.isSelected === currencyB.isSelected) {
return 0;
}

return currencyA.isSelected ? -1 : 1;
});
}

return {
sections: isEmpty
? []
Expand All @@ -38,12 +49,12 @@ function CurrencySelectionList({searchInputLabel, initiallySelectedCurrencyCode,
],
headerMessage: isEmpty ? translate('common.noResultsFound') : '',
};
}, [currencyList, searchValue, translate, initiallySelectedCurrencyCode]);
}, [currencyList, searchValue, canSelectMultiple, translate, initiallySelectedCurrencyCode, selectedCurrencies]);

return (
<SelectionList
sections={sections}
ListItem={RadioListItem}
ListItem={canSelectMultiple ? SelectableListItem : RadioListItem}
textInputLabel={searchInputLabel}
textInputValue={searchValue}
onChangeText={setSearchValue}
Expand All @@ -52,6 +63,7 @@ function CurrencySelectionList({searchInputLabel, initiallySelectedCurrencyCode,
headerMessage={headerMessage}
initiallyFocusedOptionKey={initiallySelectedCurrencyCode}
showScrollIndicator
canSelectMultiple={canSelectMultiple}
/>
);
}
Expand Down
6 changes: 6 additions & 0 deletions src/components/CurrencySelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ type CurrencySelectionListProps = CurrencySelectionListOnyxProps & {

/** Callback to fire when a currency is selected */
onSelect: (item: CurrencyListItem) => void;

/** The array of selected currencies. This prop should be used when multiple currencies can be selected */
selectedCurrencies?: string[];

/** Whether this is a multi-select list */
canSelectMultiple?: boolean;
};

export type {CurrencyListItem, CurrencySelectionListProps, CurrencySelectionListOnyxProps};
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3635,6 +3635,7 @@ export default {
after: (date?: string) => `After ${date ?? ''}`,
},
status: 'Status',
currency: 'Currency',
},
},
genericErrorPage: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3689,6 +3689,7 @@ export default {
after: (date?: string) => `Después de ${date ?? ''}`,
},
status: 'Estado',
currency: 'Divisa',
},
},
genericErrorPage: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ const SearchAdvancedFiltersModalStackNavigator = createModalStackNavigator<Searc
[SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersDatePage').default,
[SCREENS.SEARCH.ADVANCED_FILTERS_TYPE_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersTypePage').default,
[SCREENS.SEARCH.ADVANCED_FILTERS_STATUS_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersStatusPage').default,
[SCREENS.SEARCH.ADVANCED_FILTERS_CURRENCY_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersCurrencyPage').default,
[SCREENS.SEARCH.ADVANCED_FILTERS_DESCRIPTION_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersDescriptionPage').default,
[SCREENS.SEARCH.ADVANCED_FILTERS_MERCHANT_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersMerchantPage').default,
[SCREENS.SEARCH.ADVANCED_FILTERS_REPORT_ID_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersReportIDPage').default,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial<Record<CentralPaneName, string[]>> =
SCREENS.SEARCH.REPORT_RHP,
SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP,
SCREENS.SEARCH.ADVANCED_FILTERS_RHP,
SCREENS.SEARCH.ADVANCED_FILTERS_CURRENCY_RHP,
SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP,
SCREENS.SEARCH.ADVANCED_FILTERS_TYPE_RHP,
SCREENS.SEARCH.ADVANCED_FILTERS_STATUS_RHP,
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,7 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_DATE,
[SCREENS.SEARCH.ADVANCED_FILTERS_TYPE_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_TYPE,
[SCREENS.SEARCH.ADVANCED_FILTERS_STATUS_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_STATUS,
[SCREENS.SEARCH.ADVANCED_FILTERS_CURRENCY_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_CURRENCY,
[SCREENS.SEARCH.ADVANCED_FILTERS_MERCHANT_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_MERCHANT,
[SCREENS.SEARCH.ADVANCED_FILTERS_DESCRIPTION_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_DESCRIPTION,
[SCREENS.SEARCH.ADVANCED_FILTERS_REPORT_ID_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_REPORT_ID,
Expand Down
5 changes: 4 additions & 1 deletion src/libs/SearchUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,9 @@ function buildQueryStringFromFilters(filterValues: Partial<SearchAdvancedFilters
return `${CONST.SEARCH.SYNTAX_ROOT_KEYS.STATUS}:${filterValue as string}`;
}

if (filterKey === INPUT_IDS.CURRENCY && Array.isArray(filterValue) && filterValue.length > 0) {
return `${CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY}:${filterValue.join(',')}`;
}
if (filterKey === INPUT_IDS.MERCHANT && filterValue) {
return `${CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT}:${filterValue as string}`;
}
Expand Down Expand Up @@ -502,7 +505,7 @@ function buildFilterString(filterName: string, queryFilters: QueryFilter[]) {
queryFilters.forEach((queryFilter, index) => {
// If the previous queryFilter has the same operator (this rule applies only to eq and neq operators) then append the current value
if ((queryFilter.operator === 'eq' && queryFilters[index - 1]?.operator === 'eq') || (queryFilter.operator === 'neq' && queryFilters[index - 1]?.operator === 'neq')) {
filterValueString += `,${filterName}:${queryFilter.value}`;
filterValueString += `,${sanitizeString(queryFilter.value.toString())}`;
} else {
filterValueString += ` ${filterName}${operatorToSignMap[queryFilter.operator]}${queryFilter.value}`;
}
Expand Down
22 changes: 16 additions & 6 deletions src/pages/Search/AdvancedSearchFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {useOnyx} from 'react-native-onyx';
import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton';
import type {LocaleContextProps} from '@components/LocaleContextProvider';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import ScrollView from '@components/ScrollView';
import type {AdvancedFiltersKeys} from '@components/Search/types';
import useLocalize from '@hooks/useLocalize';
import useSingleExecution from '@hooks/useSingleExecution';
Expand Down Expand Up @@ -37,9 +38,13 @@ function getFilterDisplayTitle(filters: Partial<SearchAdvancedFiltersForm>, fiel
return dateValue;
}

if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY && filters[fieldName]) {
const categories = filters[fieldName] ?? [];
return categories.join(', ');
if ((fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY || fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY) && filters[fieldName]) {
const filterArray = filters[fieldName] ?? [];
return filterArray.join(', ');
}

if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION) {
return filters[fieldName];
}
if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID && filters[fieldName]) {
const cards = filters[fieldName] ?? [];
Expand Down Expand Up @@ -89,6 +94,11 @@ function AdvancedSearchFilters() {
description: 'common.date' as const,
route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE,
},
{
title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY, translate),
description: 'common.currency' as const,
route: ROUTES.SEARCH_ADVANCED_FILTERS_CURRENCY,
},
{
title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT, translate),
description: 'common.merchant' as const,
Expand Down Expand Up @@ -131,7 +141,7 @@ function AdvancedSearchFilters() {
};

return (
<View style={[styles.flex1, styles.justifyContentBetween]}>
<ScrollView contentContainerStyle={[styles.flexGrow1, styles.justifyContentBetween]}>
<View>
{advancedFilters.map((item) => {
const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(item.route)));
Expand All @@ -151,11 +161,11 @@ function AdvancedSearchFilters() {
</View>
<FormAlertWithSubmitButton
buttonText={translate('search.viewResults')}
containerStyles={[styles.m4]}
containerStyles={[styles.m4, styles.mb5]}
onSubmit={onFormSubmit}
enabledWhenOffline
/>
</View>
</ScrollView>
);
}

Expand Down
1 change: 1 addition & 0 deletions src/pages/Search/SearchAdvancedFiltersPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function SearchAdvancedFiltersPage() {
testID={SearchAdvancedFiltersPage.displayName}
shouldShowOfflineIndicatorInWideScreen
offlineIndicatorStyle={styles.mtAuto}
includeSafeAreaPaddingBottom={false}
>
<FullPageNotFoundView shouldShow={false}>
<HeaderWithBackButton title={translate('search.filtersHeader')} />
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Search/SearchFiltersCategoryPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ function SearchFiltersCategoryPage() {
testID={SearchFiltersCategoryPage.displayName}
shouldShowOfflineIndicatorInWideScreen
offlineIndicatorStyle={styles.mtAuto}
includeSafeAreaPaddingBottom={false}
>
<FullPageNotFoundView shouldShow={false}>
<HeaderWithBackButton
Expand All @@ -134,7 +135,7 @@ function SearchFiltersCategoryPage() {
Navigation.goBack(ROUTES.SEARCH_ADVANCED_FILTERS);
}}
/>
<View style={[styles.flex1]}>
<View style={[styles.flex1, styles.pb5]}>
<SelectionList
sections={sections}
textInputValue={searchTerm}
Expand Down
71 changes: 71 additions & 0 deletions src/pages/Search/SearchFiltersCurrencyPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, {useState} from 'react';
import {useOnyx} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import CurrencySelectionList from '@components/CurrencySelectionList';
import type {CurrencyListItem} from '@components/CurrencySelectionList/types';
import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as SearchActions from '@libs/actions/Search';
import Navigation from '@libs/Navigation/Navigation';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';

function SearchFiltersCurrencyPage() {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM);
const [selectedCurrencies, setSelectedCurrencies] = useState<string[]>(searchAdvancedFiltersForm?.currency ?? []);

const handleOnSelectOption = (option: CurrencyListItem) => {
if (selectedCurrencies.includes(option.currencyCode)) {
setSelectedCurrencies(selectedCurrencies.filter((currency) => currency !== option.currencyCode));
return;
}

setSelectedCurrencies([option.currencyCode, ...selectedCurrencies]);
};

const handleOnSubmit = () => {
SearchActions.updateAdvancedFilters({...searchAdvancedFiltersForm, currency: selectedCurrencies});
Navigation.goBack(ROUTES.SEARCH_ADVANCED_FILTERS);
};

return (
<ScreenWrapper
testID={SearchFiltersCurrencyPage.displayName}
shouldShowOfflineIndicatorInWideScreen
offlineIndicatorStyle={styles.mtAuto}
includeSafeAreaPaddingBottom={false}
>
{({didScreenTransitionEnd}) => (
<FullPageNotFoundView shouldShow={false}>
<HeaderWithBackButton title={translate('search.filters.currency')} />
<CurrencySelectionList
canSelectMultiple
selectedCurrencies={selectedCurrencies}
searchInputLabel={translate('common.search')}
onSelect={(option: CurrencyListItem) => {
if (!didScreenTransitionEnd) {
return;
}
handleOnSelectOption(option);
}}
/>
<FormAlertWithSubmitButton
buttonText={translate('common.save')}
containerStyles={[styles.m4, styles.mb5]}
onSubmit={handleOnSubmit}
enabledWhenOffline
/>
</FullPageNotFoundView>
)}
</ScreenWrapper>
);
}

SearchFiltersCurrencyPage.displayName = 'SearchFiltersCurrencyPage';

export default SearchFiltersCurrencyPage;
1 change: 1 addition & 0 deletions src/pages/Search/SearchFiltersDatePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function SearchFiltersDatePage() {
testID={SearchFiltersDatePage.displayName}
shouldShowOfflineIndicatorInWideScreen
offlineIndicatorStyle={styles.mtAuto}
includeSafeAreaPaddingBottom={false}
>
<FullPageNotFoundView shouldShow={false}>
<HeaderWithBackButton
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Search/SearchFiltersDescriptionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function SearchFiltersDescriptionPage() {
testID={SearchFiltersDescriptionPage.displayName}
shouldShowOfflineIndicatorInWideScreen
offlineIndicatorStyle={styles.mtAuto}
includeSafeAreaPaddingBottom={false}
>
<FullPageNotFoundView shouldShow={false}>
<HeaderWithBackButton
Expand All @@ -51,7 +52,7 @@ function SearchFiltersDescriptionPage() {
submitButtonText={translate('common.save')}
enabledWhenOffline
>
<View style={styles.mb4}>
<View style={styles.mb5}>
<InputWrapper
InputComponent={TextInput}
inputID={INPUT_IDS.DESCRIPTION}
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Search/SearchFiltersMerchantPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function SearchFiltersMerchantPage() {
testID={SearchFiltersMerchantPage.displayName}
shouldShowOfflineIndicatorInWideScreen
offlineIndicatorStyle={styles.mtAuto}
includeSafeAreaPaddingBottom={false}
>
<FullPageNotFoundView shouldShow={false}>
<HeaderWithBackButton
Expand All @@ -51,7 +52,7 @@ function SearchFiltersMerchantPage() {
submitButtonText={translate('common.save')}
enabledWhenOffline
>
<View style={styles.mb4}>
<View style={styles.mb5}>
<InputWrapper
InputComponent={TextInput}
inputID={INPUT_IDS.MERCHANT}
Expand Down
3 changes: 2 additions & 1 deletion src/pages/Search/SearchFiltersReportIDPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ function SearchFiltersReportIDPage() {
testID={SearchFiltersReportIDPage.displayName}
shouldShowOfflineIndicatorInWideScreen
offlineIndicatorStyle={styles.mtAuto}
includeSafeAreaPaddingBottom={false}
>
<FullPageNotFoundView shouldShow={false}>
<HeaderWithBackButton
Expand All @@ -51,7 +52,7 @@ function SearchFiltersReportIDPage() {
submitButtonText={translate('common.save')}
enabledWhenOffline
>
<View style={styles.mb4}>
<View style={styles.mb5}>
<InputWrapper
InputComponent={TextInput}
inputID={INPUT_IDS.REPORT_ID}
Expand Down
Loading

0 comments on commit f7e265a

Please sign in to comment.