Skip to content

Commit

Permalink
Merge pull request #46736 from software-mansion-labs/@szymczak/select…
Browse files Browse the repository at this point in the history
…ion-advanced-filters

Search filters: tax, expense type and tag
  • Loading branch information
luacmartins authored Aug 12, 2024
2 parents ab772d5 + 8f6b335 commit 28df199
Show file tree
Hide file tree
Showing 53 changed files with 758 additions and 453 deletions.
2 changes: 1 addition & 1 deletion src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy;
[ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategories;
[ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT]: OnyxTypes.PolicyCategories;
[ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTagList;
[ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTagLists;
[ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories;
[ONYXKEYS.COLLECTION.POLICY_HAS_CONNECTIONS_DATA_BEEN_FETCHED]: boolean;
[ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyEmployeeList;
Expand Down
12 changes: 3 additions & 9 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,22 @@ const ROUTES = {
getRoute: ({query, isCustomQuery = false, policyIDs}: {query: SearchQueryString; isCustomQuery?: boolean; policyIDs?: string}) =>
`search?q=${query}&isCustomQuery=${isCustomQuery}${policyIDs ? `&policyIDs=${policyIDs}` : ''}` as const,
},

SEARCH_ADVANCED_FILTERS: 'search/filters',

SEARCH_ADVANCED_FILTERS_DATE: 'search/filters/date',

SEARCH_ADVANCED_FILTERS_CURRENCY: 'search/filters/currency',

SEARCH_ADVANCED_FILTERS_MERCHANT: 'search/filters/merchant',

SEARCH_ADVANCED_FILTERS_DESCRIPTION: 'search/filters/description',

SEARCH_ADVANCED_FILTERS_REPORT_ID: 'search/filters/reportID',

SEARCH_ADVANCED_FILTERS_CATEGORY: 'search/filters/category',
SEARCH_ADVANCED_FILTERS_KEYWORD: 'search/filters/keyword',
SEARCH_ADVANCED_FILTERS_CARD: 'search/filters/card',

SEARCH_ADVANCED_FILTERS_TAX_RATE: 'search/filters/taxRate',
SEARCH_ADVANCED_FILTERS_EXPENSE_TYPE: 'search/filters/expenseType',
SEARCH_ADVANCED_FILTERS_TAG: 'search/filters/tag',
SEARCH_REPORT: {
route: 'search/view/:reportID',
getRoute: (reportID: string) => `search/view/${reportID}` as const,
},

TRANSACTION_HOLD_REASON_RHP: 'search/hold',

// This is a utility route used to go to the user's concierge chat, or the sign-in page if the user's not authenticated
Expand Down
3 changes: 3 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ const SCREENS = {
ADVANCED_FILTERS_CATEGORY_RHP: 'Search_Advanced_Filters_Category_RHP',
ADVANCED_FILTERS_KEYWORD_RHP: 'Search_Advanced_Filters_Keyword_RHP',
ADVANCED_FILTERS_CARD_RHP: 'Search_Advanced_Filters_Card_RHP',
ADVANCED_FILTERS_TAX_RATE_RHP: 'Search_Advanced_Filters_Tax_Rate_RHP',
ADVANCED_FILTERS_EXPENSE_TYPE_RHP: 'Search_Advanced_Filters_Expense_Type_RHP',
ADVANCED_FILTERS_TAG_RHP: 'Search_Advanced_Filters_Tag_RHP',
TRANSACTION_HOLD_REASON_RHP: 'Search_Transaction_Hold_Reason_RHP',
BOTTOM_TAB: 'Search_Bottom_Tab',
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/MoneyRequestConfirmationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ type MoneyRequestConfirmationListOnyxProps = {
policyCategoriesDraft: OnyxEntry<OnyxTypes.PolicyCategories>;

/** Collection of tags attached to a policy */
policyTags: OnyxEntry<OnyxTypes.PolicyTagList>;
policyTags: OnyxEntry<OnyxTypes.PolicyTagLists>;

/** The policy of the report */
policy: OnyxEntry<OnyxTypes.Policy>;
Expand Down
4 changes: 2 additions & 2 deletions src/components/MoneyRequestConfirmationListFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ type MoneyRequestConfirmationListFooterProps = {
policy: OnyxEntry<OnyxTypes.Policy>;

/** The policy tag lists */
policyTags: OnyxEntry<OnyxTypes.PolicyTagList>;
policyTags: OnyxEntry<OnyxTypes.PolicyTagLists>;

/** The policy tag lists */
policyTagLists: Array<ValueOf<OnyxTypes.PolicyTagList>>;
policyTagLists: Array<ValueOf<OnyxTypes.PolicyTagLists>>;

/** The rate of the transaction */
rate: number | undefined;
Expand Down
2 changes: 1 addition & 1 deletion src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ type MoneyRequestViewOnyxPropsWithoutTransaction = {
policyCategories: OnyxEntry<OnyxTypes.PolicyCategories>;

/** Collection of tags attached to a policy */
policyTagList: OnyxEntry<OnyxTypes.PolicyTagList>;
policyTagList: OnyxEntry<OnyxTypes.PolicyTagLists>;

/** The expense report or iou report (only will have a value if this is a transaction thread) */
parentReport: OnyxEntry<OnyxTypes.Report>;
Expand Down
122 changes: 122 additions & 0 deletions src/components/Search/SearchMultipleSelectionPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React, {useCallback, useMemo, useState} from 'react';
import Button from '@components/Button';
import SelectionList from '@components/SelectionList';
import SelectableListItem from '@components/SelectionList/SelectableListItem';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import localeCompare from '@libs/LocaleCompare';
import Navigation from '@libs/Navigation/Navigation';
import type {OptionData} from '@libs/ReportUtils';
import ROUTES from '@src/ROUTES';

type SearchMultipleSelectionPickerItem = {
name: string;
value: string | string[];
};

type SearchMultipleSelectionPickerProps = {
items: SearchMultipleSelectionPickerItem[];
initiallySelectedItems: SearchMultipleSelectionPickerItem[] | undefined;
pickerTitle?: string;
onSaveSelection: (values: string[]) => void;
shouldShowTextInput?: boolean;
};

function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTitle, onSaveSelection, shouldShowTextInput = true}: SearchMultipleSelectionPickerProps) {
const {translate} = useLocalize();

const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState('');
const [selectedItems, setSelectedItems] = useState<SearchMultipleSelectionPickerItem[]>(initiallySelectedItems ?? []);

const {sections, noResultsFound} = useMemo(() => {
const selectedItemsSection = selectedItems
.filter((item) => item?.name.toLowerCase().includes(debouncedSearchTerm?.toLowerCase()))
.sort((a, b) => localeCompare(a.name, b.name))
.map((item) => ({
text: item.name,
keyForList: item.name,
isSelected: true,
value: item.value,
}));
const remainingItemsSection = items
.filter((item) => selectedItems.some((selectedItem) => selectedItem.value === item.value) === false && item?.name.toLowerCase().includes(debouncedSearchTerm?.toLowerCase()))
.sort((a, b) => localeCompare(a.name, b.name))
.map((item) => ({
text: item.name,
keyForList: item.name,
isSelected: false,
value: item.value,
}));
const isEmpty = !selectedItemsSection.length && !remainingItemsSection.length;
return {
sections: isEmpty
? []
: [
{
title: undefined,
data: selectedItemsSection,
shouldShow: selectedItemsSection.length > 0,
},
{
title: pickerTitle,
data: remainingItemsSection,
shouldShow: remainingItemsSection.length > 0,
},
],
noResultsFound: isEmpty,
};
}, [selectedItems, items, pickerTitle, debouncedSearchTerm]);

const onSelectItem = useCallback(
(item: Partial<OptionData & SearchMultipleSelectionPickerItem>) => {
if (!item.text || !item.keyForList || !item.value) {
return;
}
if (item.isSelected) {
setSelectedItems(selectedItems?.filter((selectedItem) => selectedItem.name !== item.keyForList));
} else {
setSelectedItems([...(selectedItems ?? []), {name: item.text, value: item.value}]);
}
},
[selectedItems],
);

const handleConfirmSelection = useCallback(() => {
onSaveSelection(selectedItems.map((item) => item.value).flat());
Navigation.goBack(ROUTES.SEARCH_ADVANCED_FILTERS);
}, [onSaveSelection, selectedItems]);

const footerContent = useMemo(
() => (
<Button
success
text={translate('common.save')}
pressOnEnter
onPress={handleConfirmSelection}
large
/>
),
[translate, handleConfirmSelection],
);
return (
<SelectionList
sections={sections}
textInputValue={searchTerm}
onChangeText={setSearchTerm}
textInputLabel={shouldShowTextInput ? translate('common.search') : undefined}
onSelectRow={onSelectItem}
headerMessage={noResultsFound ? translate('common.noResultsFound') : undefined}
footerContent={footerContent}
shouldStopPropagation
showLoadingPlaceholder={!noResultsFound}
shouldShowTooltips
canSelectMultiple
ListItem={SelectableListItem}
/>
);
}

SearchMultipleSelectionPicker.displayName = 'SearchMultipleSelectionPicker';

export default SearchMultipleSelectionPicker;
export type {SearchMultipleSelectionPickerItem};
1 change: 0 additions & 1 deletion src/components/SelectionList/SelectableListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ function SelectableListItem<TItem extends ListItem>({
/>
</View>
</View>
{!!item.rightElement && item.rightElement}
{canSelectMultiple && !item.isDisabled && (
<PressableWithFeedback
onPress={handleCheckboxPress}
Expand Down
4 changes: 2 additions & 2 deletions src/components/TagPicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as PolicyUtils from '@libs/PolicyUtils';
import type * as ReportUtils from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PolicyTag, PolicyTagList, PolicyTags, RecentlyUsedTags} from '@src/types/onyx';
import type {PolicyTag, PolicyTagLists, PolicyTags, RecentlyUsedTags} from '@src/types/onyx';
import type {PendingAction} from '@src/types/onyx/OnyxCommon';

type SelectedTagOption = {
Expand All @@ -23,7 +23,7 @@ type SelectedTagOption = {

type TagPickerOnyxProps = {
/** Collection of tag list on a policy */
policyTags: OnyxEntry<PolicyTagList>;
policyTags: OnyxEntry<PolicyTagLists>;

/** List of recently used tags */
policyRecentlyUsedTags: OnyxEntry<RecentlyUsedTags>;
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3666,6 +3666,7 @@ export default {
hasKeywords: 'Has keywords',
currency: 'Currency',
},
expenseType: 'Expense type',
},
genericErrorPage: {
title: 'Uh-oh, something went wrong!',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3720,6 +3720,7 @@ export default {
hasKeywords: 'Tiene palabras clave',
currency: 'Divisa',
},
expenseType: 'Tipo de gasto',
},
genericErrorPage: {
title: '¡Oh-oh, algo salió mal!',
Expand Down
4 changes: 2 additions & 2 deletions src/libs/ModifiedExpenseMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Onyx from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PolicyTagList, ReportAction} from '@src/types/onyx';
import type {PolicyTagLists, ReportAction} from '@src/types/onyx';
import * as CurrencyUtils from './CurrencyUtils';
import DateUtils from './DateUtils';
import * as Localize from './Localize';
Expand All @@ -11,7 +11,7 @@ import * as ReportActionsUtils from './ReportActionsUtils';
import * as ReportConnection from './ReportConnection';
import * as TransactionUtils from './TransactionUtils';

let allPolicyTags: OnyxCollection<PolicyTagList> = {};
let allPolicyTags: OnyxCollection<PolicyTagLists> = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.POLICY_TAGS,
waitForCollectionCallback: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,9 @@ const SearchAdvancedFiltersModalStackNavigator = createModalStackNavigator<Searc
[SCREENS.SEARCH.ADVANCED_FILTERS_CATEGORY_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersCategoryPage').default,
[SCREENS.SEARCH.ADVANCED_FILTERS_KEYWORD_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersKeywordPage').default,
[SCREENS.SEARCH.ADVANCED_FILTERS_CARD_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersCardPage').default,
[SCREENS.SEARCH.ADVANCED_FILTERS_TAX_RATE_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersTaxRatePage').default,
[SCREENS.SEARCH.ADVANCED_FILTERS_EXPENSE_TYPE_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersExpenseTypePage').default,
[SCREENS.SEARCH.ADVANCED_FILTERS_TAG_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchFiltersTagPage').default,
});

const RestrictedActionModalStackNavigator = createModalStackNavigator<SearchReportParamList>({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial<Record<CentralPaneName, string[]>> =
SCREENS.SEARCH.ADVANCED_FILTERS_REPORT_ID_RHP,
SCREENS.SEARCH.ADVANCED_FILTERS_CATEGORY_RHP,
SCREENS.SEARCH.ADVANCED_FILTERS_KEYWORD_RHP,
SCREENS.SEARCH.ADVANCED_FILTERS_TAX_RATE_RHP,
SCREENS.SEARCH.ADVANCED_FILTERS_EXPENSE_TYPE_RHP,
SCREENS.SEARCH.ADVANCED_FILTERS_TAG_RHP,
],
[SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: [
SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD,
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.SEARCH.ADVANCED_FILTERS_CATEGORY_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY,
[SCREENS.SEARCH.ADVANCED_FILTERS_KEYWORD_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_KEYWORD,
[SCREENS.SEARCH.ADVANCED_FILTERS_CARD_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_CARD,
[SCREENS.SEARCH.ADVANCED_FILTERS_TAX_RATE_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_TAX_RATE,
[SCREENS.SEARCH.ADVANCED_FILTERS_EXPENSE_TYPE_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_EXPENSE_TYPE,
[SCREENS.SEARCH.ADVANCED_FILTERS_TAG_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_TAG,
},
},
[SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: {
Expand Down
4 changes: 2 additions & 2 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type {
PolicyCategories,
PolicyCategory,
PolicyTag,
PolicyTagList,
PolicyTagLists,
PolicyTags,
Report,
ReportAction,
Expand Down Expand Up @@ -1353,7 +1353,7 @@ function getTagListSections(
/**
* Verifies that there is at least one enabled tag
*/
function hasEnabledTags(policyTagList: Array<PolicyTagList[keyof PolicyTagList]>) {
function hasEnabledTags(policyTagList: Array<PolicyTagLists[keyof PolicyTagLists]>) {
const policyTagValueList = policyTagList.map(({tags}) => Object.values(tags)).flat();

return hasEnabledOptions(policyTagValueList);
Expand Down
Loading

0 comments on commit 28df199

Please sign in to comment.