Skip to content

Commit 9b85c31

Browse files
authored
Merge pull request #88168 from bernhardoj/fix/show-the-feed-filter-chip-back
[CP Staging] Add and show the feed filter back
2 parents 4f7e2f8 + eed8f4b commit 9b85c31

8 files changed

Lines changed: 201 additions & 32 deletions

File tree

src/components/Search/SearchPageHeader/FeedFilterPopup.tsx renamed to src/components/Search/FilterDropdowns/FeedSelectPopup.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
import React, {useEffect} from 'react';
2-
import type {MultiSelectItem} from '@components/Search/FilterDropdowns/MultiSelectPopup';
2+
import useFilterFeedData from '@components/Search/hooks/useFilterFeedData';
3+
import MultiSelectFilterPopup from '@components/Search/SearchPageHeader/MultiSelectFilterPopup';
34
import useNetwork from '@hooks/useNetwork';
45
import useOnyx from '@hooks/useOnyx';
56
import {openSearchCardFiltersPage} from '@libs/actions/Search';
67
import ONYXKEYS from '@src/ONYXKEYS';
7-
import MultiSelectFilterPopup from './MultiSelectFilterPopup';
8+
import type {SearchAdvancedFiltersForm} from '@src/types/form';
89

9-
type FeedFilterPopupProps = {
10+
type FeedSelectPopupProps = {
1011
isExpanded: boolean;
11-
items: Array<MultiSelectItem<string>>;
12-
value: Array<MultiSelectItem<string>>;
1312
closeOverlay: () => void;
14-
onChangeCallback: (items: Array<MultiSelectItem<string>>) => void;
13+
updateFilterForm: (values: Partial<SearchAdvancedFiltersForm>) => void;
1514
};
1615

17-
function FeedFilterPopup({closeOverlay, items, value, isExpanded, onChangeCallback}: FeedFilterPopupProps) {
16+
function FeedSelectPopup({isExpanded, updateFilterForm, closeOverlay}: FeedSelectPopupProps) {
1817
const {isOffline} = useNetwork();
1918
const [areCardsLoaded] = useOnyx(ONYXKEYS.IS_SEARCH_FILTERS_CARD_DATA_LOADED);
19+
const {feedOptions, feedValue} = useFilterFeedData();
2020

2121
useEffect(() => {
2222
if (isOffline || !isExpanded) {
@@ -29,14 +29,14 @@ function FeedFilterPopup({closeOverlay, items, value, isExpanded, onChangeCallba
2929

3030
return (
3131
<MultiSelectFilterPopup
32-
items={items}
33-
value={value}
32+
items={feedOptions}
33+
value={feedValue}
3434
loading={shouldShowLoadingState}
3535
translationKey="search.filters.feed"
3636
closeOverlay={closeOverlay}
37-
onChangeCallback={onChangeCallback}
37+
onChangeCallback={(items) => updateFilterForm({feed: items.map((item) => item.value)})}
3838
/>
3939
);
4040
}
4141

42-
export default FeedFilterPopup;
42+
export default FeedSelectPopup;

src/components/Search/SearchPageHeader/SearchFilterBar.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react';
22
import DropdownButton from '@components/Search/FilterDropdowns/DropdownButton';
33
import type {DropdownButtonProps} from '@components/Search/FilterDropdowns/DropdownButton';
44
import useFilterCardValue from '@components/Search/hooks/useFilterCardValue';
5+
import useFilterFeedValue from '@components/Search/hooks/useFilterFeedValue';
56
import useFilterReportValue from '@components/Search/hooks/useFilterReportValue';
67
import useFilterTaxRateValue from '@components/Search/hooks/useFilterTaxRateValue';
78
import useFilterUserValue from '@components/Search/hooks/useFilterUserValue';
@@ -37,6 +38,18 @@ function WorkspaceDropdown({label, value, PopoverComponent, sentryLabel}: Search
3738
);
3839
}
3940

41+
function FeedDropdown({label, PopoverComponent, sentryLabel}: SearchDropdownProps) {
42+
const feedValue = useFilterFeedValue();
43+
return (
44+
<DropdownButton
45+
label={label}
46+
value={feedValue}
47+
PopoverComponent={PopoverComponent}
48+
sentryLabel={sentryLabel}
49+
/>
50+
);
51+
}
52+
4053
function CardDropdown({label, PopoverComponent, sentryLabel}: SearchDropdownProps) {
4154
const cardValue = useFilterCardValue();
4255
return (
@@ -81,7 +94,7 @@ const FILTER_COMPONENT_MAP: Partial<Record<SearchAdvancedFiltersKey, React.Compo
8194

8295
[FILTER_KEYS.POLICY_ID]: WorkspaceDropdown,
8396

84-
[FILTER_KEYS.FEED]: CardDropdown,
97+
[FILTER_KEYS.FEED]: FeedDropdown,
8598
[FILTER_KEYS.CARD_ID]: CardDropdown,
8699

87100
[FILTER_KEYS.TAX_RATE]: TaxRateDropdown,

src/components/Search/SearchPageHeader/useSearchFiltersBar.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import CategorySelectPopup from '@components/Search/FilterDropdowns/CategorySele
77
import CurrencySelectPopup from '@components/Search/FilterDropdowns/CurrencySelectPopup';
88
import type {PopoverComponentProps} from '@components/Search/FilterDropdowns/DropdownButton';
99
import ExportedToSelectPopup from '@components/Search/FilterDropdowns/ExportedToSelectPopup';
10+
import FeedFilterPopup from '@components/Search/FilterDropdowns/FeedSelectPopup';
1011
import InSelectPopup from '@components/Search/FilterDropdowns/InSelectPopup';
1112
import ReportFieldPopup from '@components/Search/FilterDropdowns/ReportFieldPopup';
1213
import SingleSelectPopup from '@components/Search/FilterDropdowns/SingleSelectPopup';
@@ -230,7 +231,6 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes
230231
sentryLabel: getFilterSentryLabel(filterKey),
231232
};
232233
}
233-
case FILTER_KEYS.FEED:
234234
case FILTER_KEYS.CARD_ID: {
235235
return {
236236
PopoverComponent: ({closeOverlay, isExpanded}) => (
@@ -243,6 +243,18 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON): UseSearchFiltersBarRes
243243
sentryLabel: getFilterSentryLabel(filterKey),
244244
};
245245
}
246+
case FILTER_KEYS.FEED: {
247+
return {
248+
PopoverComponent: ({closeOverlay, isExpanded}) => (
249+
<FeedFilterPopup
250+
isExpanded={isExpanded}
251+
updateFilterForm={updateFilterForm}
252+
closeOverlay={closeOverlay}
253+
/>
254+
),
255+
sentryLabel: getFilterSentryLabel(filterKey),
256+
};
257+
}
246258
case FILTER_KEYS.MERCHANT:
247259
case FILTER_KEYS.DESCRIPTION:
248260
case FILTER_KEYS.REPORT_ID:
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type {OnyxEntry} from 'react-native-onyx';
2+
import useFeedKeysWithAssignedCards from '@hooks/useFeedKeysWithAssignedCards';
3+
import useLocalize from '@hooks/useLocalize';
4+
import useOnyx from '@hooks/useOnyx';
5+
import {getFeedOptions} from '@libs/SearchUIUtils';
6+
import ONYXKEYS from '@src/ONYXKEYS';
7+
import type {SearchAdvancedFiltersForm} from '@src/types/form';
8+
9+
function filterFeedSelector(searchAdvancedFiltersForm: OnyxEntry<SearchAdvancedFiltersForm>) {
10+
return searchAdvancedFiltersForm?.feed;
11+
}
12+
13+
function useFilterFeedData() {
14+
const {translate, localeCompare} = useLocalize();
15+
const [allFeeds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER);
16+
const [personalAndWorkspaceCards] = useOnyx(ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST);
17+
const [feed] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, {selector: filterFeedSelector});
18+
19+
const feedKeysWithCards = useFeedKeysWithAssignedCards();
20+
21+
const feedOptions = getFeedOptions(allFeeds, personalAndWorkspaceCards, translate, localeCompare, feedKeysWithCards);
22+
const feedValue = feed ? feedOptions.filter((option) => feed.includes(option.value)) : [];
23+
return {feedOptions, feedValue};
24+
}
25+
26+
export default useFilterFeedData;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import useFilterFeedData from './useFilterFeedData';
2+
3+
function useFilterFeedValue(): string {
4+
const {feedValue} = useFilterFeedData();
5+
return feedValue.map((item) => item.text).join(', ');
6+
}
7+
8+
export default useFilterFeedValue;

src/libs/SearchUIUtils.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5001,7 +5001,7 @@ function mapFiltersFormToLabelValueList<T extends Record<string, unknown>>(
50015001
mapper?: (filterKey: SearchAdvancedFiltersKey) => T,
50025002
): Array<SearchFilter & T> {
50035003
const filters: Array<SearchFilter & T> = [];
5004-
const addedGroups = new Set<SearchDateFilterKeys | SearchAmountFilterKeys | typeof CONST.SEARCH.REPORT_FIELD.GLOBAL_PREFIX | typeof FILTER_KEYS.FEED | typeof FILTER_KEYS.CARD_ID>();
5004+
const addedGroups = new Set<SearchDateFilterKeys | SearchAmountFilterKeys | typeof CONST.SEARCH.REPORT_FIELD.GLOBAL_PREFIX>();
50055005
const type = searchAdvancedFiltersForm.type ?? CONST.SEARCH.DATA_TYPES.EXPENSE;
50065006

50075007
for (const filterKey of Object.keys(searchAdvancedFiltersForm)) {
@@ -5044,23 +5044,6 @@ function mapFiltersFormToLabelValueList<T extends Record<string, unknown>>(
50445044
continue;
50455045
}
50465046

5047-
// Handle card feeds and individual cards - only add once
5048-
if (key === FILTER_KEYS.FEED || key === FILTER_KEYS.CARD_ID) {
5049-
if (addedGroups.has(FILTER_KEYS.FEED) || addedGroups.has(FILTER_KEYS.CARD_ID)) {
5050-
continue;
5051-
}
5052-
5053-
const feedValue = searchAdvancedFiltersForm[FILTER_KEYS.FEED] ?? [];
5054-
const cardIDvalue = searchAdvancedFiltersForm[FILTER_KEYS.CARD_ID] ?? [];
5055-
5056-
if (feedValue.length > 0 || cardIDvalue.length > 0) {
5057-
addedGroups.add(key);
5058-
filters.push({key, label: translate('common.card'), value: cardIDvalue.concat(feedValue), ...extra});
5059-
}
5060-
5061-
continue;
5062-
}
5063-
50645047
// Handle regular filters
50655048
const label = FILTER_LABEL_MAP[key];
50665049
const value = getDisplayValue(key, searchAdvancedFiltersForm, type, policyIDQuery, translate, localeCompare);

src/pages/Search/SearchSavePage.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import InputWrapper from '@components/Form/InputWrapper';
55
import HeaderWithBackButton from '@components/HeaderWithBackButton';
66
import ScreenWrapper from '@components/ScreenWrapper';
77
import useFilterCardValue from '@components/Search/hooks/useFilterCardValue';
8+
import useFilterFeedValue from '@components/Search/hooks/useFilterFeedValue';
89
import useFilterReportValue from '@components/Search/hooks/useFilterReportValue';
910
import useFilterTaxRateValue from '@components/Search/hooks/useFilterTaxRateValue';
1011
import useFilterUserValue from '@components/Search/hooks/useFilterUserValue';
@@ -44,6 +45,10 @@ function FilterWorkspaceValue({value}: FilterValueProps) {
4445
return useFilterWorkspaceValue(value);
4546
}
4647

48+
function FilterFeedValue() {
49+
return useFilterFeedValue();
50+
}
51+
4752
function FilterCardValue() {
4853
return useFilterCardValue();
4954
}
@@ -65,7 +70,11 @@ function FilterValue({filterKey, value}: FilterValueWithKeyProps) {
6570
return <FilterWorkspaceValue value={value} />;
6671
}
6772

68-
if (filterKey === FILTER_KEYS.FEED || filterKey === FILTER_KEYS.CARD_ID) {
73+
if (filterKey === FILTER_KEYS.FEED) {
74+
return <FilterFeedValue />;
75+
}
76+
77+
if (filterKey === FILTER_KEYS.CARD_ID) {
6978
return <FilterCardValue />;
7079
}
7180

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import {act, renderHook} from '@testing-library/react-native';
2+
import Onyx from 'react-native-onyx';
3+
import useFeedKeysWithAssignedCards from '@hooks/useFeedKeysWithAssignedCards';
4+
import useLocalize from '@hooks/useLocalize';
5+
import useFilterFeedData from '@src/components/Search/hooks/useFilterFeedData';
6+
import ONYXKEYS from '@src/ONYXKEYS';
7+
import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates';
8+
9+
jest.mock('@hooks/useLocalize');
10+
jest.mock('@hooks/useFeedKeysWithAssignedCards');
11+
12+
const mockUseLocalize = useLocalize as jest.Mock;
13+
const mockUseFeedKeysWithAssignedCards = useFeedKeysWithAssignedCards as jest.Mock;
14+
15+
describe('useFilterFeedData', () => {
16+
beforeAll(() => {
17+
Onyx.init({keys: ONYXKEYS});
18+
});
19+
20+
beforeEach(async () => {
21+
jest.clearAllMocks();
22+
mockUseLocalize.mockReturnValue({
23+
translate: (key: string) => key,
24+
localeCompare: (a: string, b: string) => a.localeCompare(b),
25+
});
26+
mockUseFeedKeysWithAssignedCards.mockReturnValue({});
27+
28+
await Onyx.clear();
29+
await waitForBatchedUpdates();
30+
});
31+
32+
it('should return empty values when no data is available', async () => {
33+
const {result} = renderHook(() => useFilterFeedData());
34+
35+
expect(result.current.feedOptions).toEqual([]);
36+
expect(result.current.feedValue).toEqual([]);
37+
});
38+
39+
it('should return feed options when feeds are available in Onyx', async () => {
40+
await Onyx.merge(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}123`, {
41+
settings: {
42+
companyCards: {
43+
vcf: {},
44+
},
45+
},
46+
});
47+
48+
const {result} = renderHook(() => useFilterFeedData());
49+
50+
// Expected feed id is fundID_feed
51+
expect(result.current.feedOptions).toHaveLength(1);
52+
expect(result.current.feedOptions.at(0)?.value).toBe('123_vcf');
53+
expect(result.current.feedValue).toHaveLength(0);
54+
});
55+
56+
it('should return selected feed value when filter form has values', async () => {
57+
await Onyx.merge(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}123`, {
58+
settings: {
59+
companyCards: {
60+
vcf: {},
61+
},
62+
},
63+
});
64+
await Onyx.merge(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, {feed: ['123_vcf']});
65+
66+
const {result} = renderHook(() => useFilterFeedData());
67+
68+
expect(result.current.feedOptions).toHaveLength(1);
69+
expect(result.current.feedOptions.at(0)?.value).toBe('123_vcf');
70+
expect(result.current.feedValue).toHaveLength(1);
71+
expect(result.current.feedValue.at(0)?.value).toBe('123_vcf');
72+
});
73+
74+
it('should update when Onyx data changes', async () => {
75+
await Onyx.merge(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}123`, {
76+
settings: {
77+
companyCards: {
78+
vcf: {},
79+
},
80+
},
81+
});
82+
83+
const {result} = renderHook(() => useFilterFeedData());
84+
85+
expect(result.current.feedOptions).toHaveLength(1);
86+
expect(result.current.feedOptions.at(0)?.value).toBe('123_vcf');
87+
88+
// Add another feed
89+
await act(async () => {
90+
await Onyx.merge(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}456`, {
91+
settings: {
92+
companyCards: {
93+
cdf: {},
94+
},
95+
},
96+
});
97+
});
98+
99+
expect(result.current.feedOptions).toHaveLength(2);
100+
const values = result.current.feedOptions.map((o) => o.value);
101+
expect(values.at(0)).toBe('456_cdf');
102+
expect(values.at(1)).toBe('123_vcf');
103+
});
104+
105+
it('should include Expensify Card feeds from allCards', async () => {
106+
await Onyx.merge(ONYXKEYS.DERIVED.PERSONAL_AND_WORKSPACE_CARD_LIST, {
107+
card1: {
108+
bank: 'Expensify Card',
109+
fundID: '999',
110+
},
111+
});
112+
113+
const {result} = renderHook(() => useFilterFeedData());
114+
115+
expect(result.current.feedOptions).toHaveLength(1);
116+
expect(result.current.feedOptions.at(0)?.value).toBe('999_Expensify Card');
117+
});
118+
});

0 commit comments

Comments
 (0)