Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support empty value for categories and tags v2 #51033

Merged
merged 3 commits into from
Oct 21, 2024
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.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5680,6 +5680,7 @@ const CONST = {
KEYWORD: 'keyword',
IN: 'in',
},
EMPTY_VALUE: 'none',
},

REFERRER: {
Expand Down
16 changes: 14 additions & 2 deletions src/components/Search/SearchMultipleSelectionPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import useLocalize from '@hooks/useLocalize';
import localeCompare from '@libs/LocaleCompare';
import Navigation from '@libs/Navigation/Navigation';
import type {OptionData} from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';

type SearchMultipleSelectionPickerItem = {
Expand All @@ -28,14 +29,25 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit
const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState('');
const [selectedItems, setSelectedItems] = useState<SearchMultipleSelectionPickerItem[]>(initiallySelectedItems ?? []);

const sortOptionsWithEmptyValue = (a: SearchMultipleSelectionPickerItem, b: SearchMultipleSelectionPickerItem) => {
// Always show `No category` and `No tag` as the first option
if (a.value === CONST.SEARCH.EMPTY_VALUE) {
return -1;
}
if (b.value === CONST.SEARCH.EMPTY_VALUE) {
return 1;
}
return localeCompare(a.name, b.name);
};

useEffect(() => {
setSelectedItems(initiallySelectedItems ?? []);
}, [initiallySelectedItems]);

const {sections, noResultsFound} = useMemo(() => {
const selectedItemsSection = selectedItems
.filter((item) => item?.name.toLowerCase().includes(debouncedSearchTerm?.toLowerCase()))
.sort((a, b) => localeCompare(a.name, b.name))
.sort((a, b) => sortOptionsWithEmptyValue(a, b))
.map((item) => ({
text: item.name,
keyForList: item.name,
Expand All @@ -44,7 +56,7 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit
}));
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))
.sort((a, b) => sortOptionsWithEmptyValue(a, b))
.map((item) => ({
text: item.name,
keyForList: item.name,
Expand Down
2 changes: 2 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4294,6 +4294,8 @@ const translations = {
current: 'Current',
past: 'Past',
},
noCategory: 'No category',
noTag: 'No tag',
expenseType: 'Expense type',
recentSearches: 'Recent searches',
recentChats: 'Recent chats',
Expand Down
2 changes: 2 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4340,6 +4340,8 @@ const translations = {
current: 'Actual',
past: 'Anterior',
},
noCategory: 'Sin categoría',
noTag: 'Sin etiqueta',
expenseType: 'Tipo de gasto',
recentSearches: 'Búsquedas recientes',
recentChats: 'Chats recientes',
Expand Down
4 changes: 3 additions & 1 deletion src/libs/SearchUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -704,14 +704,16 @@ function buildFilterFormValuesFromQuery(
.filter((item) => !!item)
.map((tagList) => getTagNamesFromTagsLists(tagList ?? {}))
.flat();
tags.push(CONST.SEARCH.EMPTY_VALUE);
filtersForm[filterKey] = filters[filterKey]?.map((tag) => tag.value.toString()).filter((name) => tags.includes(name));
}
if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY) {
const categories = policyID
? Object.values(policyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`] ?? {}).map((category) => category.name)
: Object.values(policyCategories ?? {})
.map((xd) => Object.values(xd ?? {}).map((category) => category.name))
.map((item) => Object.values(item ?? {}).map((category) => category.name))
.flat();
categories.push(CONST.SEARCH.EMPTY_VALUE);
filtersForm[filterKey] = filters[filterKey]?.map((category) => category.value.toString()).filter((name) => categories.includes(name));
}
if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD) {
Expand Down
32 changes: 28 additions & 4 deletions src/pages/Search/AdvancedSearchFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ function getFilterParticipantDisplayTitle(accountIDs: string[], personalDetails:
.join(', ');
}

const sortOptionsWithEmptyValue = (a: string, b: string) => {
// Always show `No category` and `No tag` as the first option
if (a === CONST.SEARCH.EMPTY_VALUE) {
return -1;
}
if (b === CONST.SEARCH.EMPTY_VALUE) {
return 1;
}
return localeCompare(a, b);
};

function getFilterDisplayTitle(filters: Partial<SearchAdvancedFiltersForm>, fieldName: AdvancedFiltersKeys, translate: LocaleContextProps['translate']) {
if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE) {
// the value of date filter is a combination of dateBefore + dateAfter values
Expand Down Expand Up @@ -175,14 +186,27 @@ function getFilterDisplayTitle(filters: Partial<SearchAdvancedFiltersForm>, fiel
return;
}

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

if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY && filters[fieldName]) {
const filterArray = filters[fieldName] ?? [];
return filterArray
.sort(sortOptionsWithEmptyValue)
.map((value) => (value === CONST.SEARCH.EMPTY_VALUE ? translate('search.noCategory') : value))
.join(', ');
}

if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG && filters[fieldName]) {
const filterArray = filters[fieldName] ?? [];
return filterArray
.sort(sortOptionsWithEmptyValue)
.map((value) => (value === CONST.SEARCH.EMPTY_VALUE ? translate('search.noTag') : value))
.join(', ');
}

if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION) {
return filters[fieldName];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import useSafePaddingBottomStyle from '@hooks/useSafePaddingBottomStyle';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import * as SearchActions from '@userActions/Search';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';

Expand All @@ -17,19 +18,27 @@ function SearchFiltersCategoryPage() {
const {translate} = useLocalize();

const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM);
const selectedCategoriesItems = searchAdvancedFiltersForm?.category?.map((category) => ({name: category, value: category}));
const selectedCategoriesItems = searchAdvancedFiltersForm?.category?.map((category) => {
if (category === CONST.SEARCH.EMPTY_VALUE) {
return {name: translate('search.noCategory'), value: category};
}
return {name: category, value: category};
});
const policyID = searchAdvancedFiltersForm?.policyID ?? '-1';
const [allPolicyIDCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES);
const singlePolicyCategories = allPolicyIDCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`];

const categoryItems = useMemo(() => {
const items = [{name: translate('search.noCategory'), value: CONST.SEARCH.EMPTY_VALUE as string}];
if (!singlePolicyCategories) {
const uniqueCategoryNames = new Set<string>();
Object.values(allPolicyIDCategories ?? {}).map((policyCategories) => Object.values(policyCategories ?? {}).forEach((category) => uniqueCategoryNames.add(category.name)));
return Array.from(uniqueCategoryNames).map((categoryName) => ({name: categoryName, value: categoryName}));
items.push(...Array.from(uniqueCategoryNames).map((categoryName) => ({name: categoryName, value: categoryName})));
} else {
items.push(...Object.values(singlePolicyCategories ?? {}).map((category) => ({name: category.name, value: category.name})));
}
return Object.values(singlePolicyCategories ?? {}).map((category) => ({name: category.name, value: category.name}));
}, [allPolicyIDCategories, singlePolicyCategories]);
return items;
}, [allPolicyIDCategories, singlePolicyCategories, translate]);

const onSaveSelection = useCallback((values: string[]) => SearchActions.updateAdvancedFilters({category: values}), []);
const safePaddingBottomStyle = useSafePaddingBottomStyle();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import {getTagNamesFromTagsLists} from '@libs/PolicyUtils';
import * as SearchActions from '@userActions/Search';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {PolicyTagLists} from '@src/types/onyx';
Expand All @@ -18,12 +19,18 @@ function SearchFiltersTagPage() {
const {translate} = useLocalize();

const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM);
const selectedTagsItems = searchAdvancedFiltersForm?.tag?.map((tag) => ({name: tag, value: tag}));
const selectedTagsItems = searchAdvancedFiltersForm?.tag?.map((tag) => {
if (tag === CONST.SEARCH.EMPTY_VALUE) {
return {name: translate('search.noTag'), value: tag};
}
return {name: tag, value: tag};
});
const policyID = searchAdvancedFiltersForm?.policyID ?? '-1';
const [allPoliciesTagsLists] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS);
const singlePolicyTagsList: PolicyTagLists | undefined = allPoliciesTagsLists?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`];

const tagItems = useMemo(() => {
const items = [{name: translate('search.noTag'), value: CONST.SEARCH.EMPTY_VALUE as string}];
if (!singlePolicyTagsList) {
const uniqueTagNames = new Set<string>();
const tagListsUnpacked = Object.values(allPoliciesTagsLists ?? {}).filter((item) => !!item) as PolicyTagLists[];
Expand All @@ -33,10 +40,12 @@ function SearchFiltersTagPage() {
})
.flat()
.forEach((tag) => uniqueTagNames.add(tag));
return Array.from(uniqueTagNames).map((tagName) => ({name: tagName, value: tagName}));
items.push(...Array.from(uniqueTagNames).map((tagName) => ({name: tagName, value: tagName})));
} else {
items.push(...getTagNamesFromTagsLists(singlePolicyTagsList).map((name) => ({name, value: name})));
}
return getTagNamesFromTagsLists(singlePolicyTagsList).map((name) => ({name, value: name}));
}, [allPoliciesTagsLists, singlePolicyTagsList]);
return items;
}, [allPoliciesTagsLists, singlePolicyTagsList, translate]);

const updateTagFilter = useCallback((values: string[]) => SearchActions.updateAdvancedFilters({tag: values}), []);

Expand Down
Loading