Skip to content

Commit 0ec8b26

Browse files
authored
Merge pull request #50426 from c3024/move-deduplicating-logic-of-personal-details-to-filteredOptions
Move the logic of excluding contacts of DMs already included in reports to `filterOptions`
2 parents 1e81aa8 + 0519359 commit 0ec8b26

11 files changed

+306
-399
lines changed

src/components/CategoryPicker.tsx

+6-12
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,14 @@ function CategoryPicker({selectedCategory, policyID, onSubmit}: CategoryPickerPr
4444
const [sections, headerMessage, shouldShowTextInput] = useMemo(() => {
4545
const categories = policyCategories ?? policyCategoriesDraft ?? {};
4646
const validPolicyRecentlyUsedCategories = policyRecentlyUsedCategories?.filter?.((p) => !isEmptyObject(p));
47-
const {categoryOptions} = OptionsListUtils.getFilteredOptions(
48-
[],
49-
[],
50-
[],
51-
debouncedSearchValue,
47+
const {categoryOptions} = OptionsListUtils.getFilteredOptions({
48+
searchValue: debouncedSearchValue,
5249
selectedOptions,
53-
[],
54-
false,
55-
false,
56-
true,
50+
includeP2P: false,
51+
includeCategories: true,
5752
categories,
58-
validPolicyRecentlyUsedCategories,
59-
false,
60-
);
53+
recentlyUsedCategories: validPolicyRecentlyUsedCategories,
54+
});
6155

6256
const categoryData = categoryOptions?.at(0)?.data ?? [];
6357
const header = OptionsListUtils.getHeaderMessageForNonUserList(categoryData.length > 0, debouncedSearchValue);

src/components/Search/SearchFiltersParticipantsSelector.tsx

+6-21
Original file line numberDiff line numberDiff line change
@@ -57,28 +57,13 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate}:
5757
return defaultListOptions;
5858
}
5959

60-
return OptionsListUtils.getFilteredOptions(
61-
options.reports,
62-
options.personalDetails,
63-
undefined,
64-
'',
60+
return OptionsListUtils.getFilteredOptions({
61+
reports: options.reports,
62+
personalDetails: options.personalDetails,
6563
selectedOptions,
66-
CONST.EXPENSIFY_EMAILS,
67-
false,
68-
true,
69-
false,
70-
{},
71-
[],
72-
false,
73-
{},
74-
[],
75-
true,
76-
false,
77-
false,
78-
0,
79-
undefined,
80-
false,
81-
);
64+
excludeLogins: CONST.EXPENSIFY_EMAILS,
65+
maxRecentReportsToShow: 0,
66+
});
8267
}, [areOptionsInitialized, options.personalDetails, options.reports, selectedOptions]);
8368

8469
const chatOptions = useMemo(() => {

src/components/TagPicker/index.tsx

+17-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React, {useMemo, useState} from 'react';
2-
import type {OnyxEntry} from 'react-native-onyx';
3-
import {withOnyx} from 'react-native-onyx';
2+
import {useOnyx} from 'react-native-onyx';
43
import SelectionList from '@components/SelectionList';
54
import RadioListItem from '@components/SelectionList/RadioListItem';
65
import useLocalize from '@hooks/useLocalize';
@@ -10,7 +9,7 @@ import * as PolicyUtils from '@libs/PolicyUtils';
109
import type * as ReportUtils from '@libs/ReportUtils';
1110
import CONST from '@src/CONST';
1211
import ONYXKEYS from '@src/ONYXKEYS';
13-
import type {PolicyTag, PolicyTagLists, PolicyTags, RecentlyUsedTags} from '@src/types/onyx';
12+
import type {PolicyTag, PolicyTags} from '@src/types/onyx';
1413
import type {PendingAction} from '@src/types/onyx/OnyxCommon';
1514

1615
type SelectedTagOption = {
@@ -21,15 +20,7 @@ type SelectedTagOption = {
2120
pendingAction?: PendingAction;
2221
};
2322

24-
type TagPickerOnyxProps = {
25-
/** Collection of tag list on a policy */
26-
policyTags: OnyxEntry<PolicyTagLists>;
27-
28-
/** List of recently used tags */
29-
policyRecentlyUsedTags: OnyxEntry<RecentlyUsedTags>;
30-
};
31-
32-
type TagPickerProps = TagPickerOnyxProps & {
23+
type TagPickerProps = {
3324
/** The policyID we are getting tags for */
3425
// It's used in withOnyx HOC.
3526
// eslint-disable-next-line react/no-unused-prop-types
@@ -51,7 +42,9 @@ type TagPickerProps = TagPickerOnyxProps & {
5142
tagListIndex: number;
5243
};
5344

54-
function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRecentlyUsedTags, shouldShowDisabledAndSelectedOption = false, onSubmit}: TagPickerProps) {
45+
function TagPicker({selectedTag, tagListName, policyID, tagListIndex, shouldShowDisabledAndSelectedOption = false, onSubmit}: TagPickerProps) {
46+
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`);
47+
const [policyRecentlyUsedTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`);
5548
const styles = useThemeStyles();
5649
const {translate} = useLocalize();
5750
const [searchValue, setSearchValue] = useState('');
@@ -87,7 +80,16 @@ function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRe
8780
}, [selectedOptions, policyTagList, shouldShowDisabledAndSelectedOption]);
8881

8982
const sections = useMemo(
90-
() => OptionsListUtils.getFilteredOptions([], [], [], searchValue, selectedOptions, [], false, false, false, {}, [], true, enabledTags, policyRecentlyUsedTagsList, false).tagOptions,
83+
() =>
84+
OptionsListUtils.getFilteredOptions({
85+
searchValue,
86+
selectedOptions,
87+
includeP2P: false,
88+
includeTags: true,
89+
tags: enabledTags,
90+
recentlyUsedTags: policyRecentlyUsedTagsList,
91+
canInviteUser: false,
92+
}).tagOptions,
9193
[searchValue, enabledTags, selectedOptions, policyRecentlyUsedTagsList],
9294
);
9395

@@ -113,13 +115,6 @@ function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRe
113115

114116
TagPicker.displayName = 'TagPicker';
115117

116-
export default withOnyx<TagPickerProps, TagPickerOnyxProps>({
117-
policyTags: {
118-
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`,
119-
},
120-
policyRecentlyUsedTags: {
121-
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`,
122-
},
123-
})(TagPicker);
118+
export default TagPicker;
124119

125120
export type {SelectedTagOption};

src/libs/OptionsListUtils.ts

+84-36
Original file line numberDiff line numberDiff line change
@@ -1977,11 +1977,6 @@ function getOptions(
19771977
}
19781978
}
19791979
}
1980-
1981-
// Add this login to the exclude list so it won't appear when we process the personal details
1982-
if (reportOption.login) {
1983-
optionsToExclude.push({login: reportOption.login});
1984-
}
19851980
}
19861981
}
19871982

@@ -2114,34 +2109,75 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: OnyxEn
21142109
/**
21152110
* Build the options for the New Group view
21162111
*/
2117-
function getFilteredOptions(
2118-
reports: Array<SearchOption<Report>> = [],
2119-
personalDetails: Array<SearchOption<PersonalDetails>> = [],
2120-
betas: OnyxEntry<Beta[]> = [],
2121-
searchValue = '',
2122-
selectedOptions: Array<Partial<ReportUtils.OptionData>> = [],
2123-
excludeLogins: string[] = [],
2124-
includeOwnedWorkspaceChats = false,
2125-
includeP2P = true,
2126-
includeCategories = false,
2127-
categories: PolicyCategories = {},
2128-
recentlyUsedCategories: string[] = [],
2129-
includeTags = false,
2130-
tags: PolicyTags | Array<PolicyTag | SelectedTagOption> = {},
2131-
recentlyUsedTags: string[] = [],
2132-
canInviteUser = true,
2133-
includeSelectedOptions = false,
2134-
includeTaxRates = false,
2135-
maxRecentReportsToShow: number = CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
2136-
taxRates: TaxRatesWithDefault = {} as TaxRatesWithDefault,
2137-
includeSelfDM = false,
2138-
includePolicyReportFieldOptions = false,
2139-
policyReportFieldOptions: string[] = [],
2140-
recentlyUsedPolicyReportFieldOptions: string[] = [],
2141-
includeInvoiceRooms = false,
2142-
action: IOUAction | undefined = undefined,
2143-
sortByReportTypeInSearch = false,
2144-
) {
2112+
type FilteredOptionsParams = {
2113+
reports?: Array<SearchOption<Report>>;
2114+
personalDetails?: Array<SearchOption<PersonalDetails>>;
2115+
betas?: OnyxEntry<Beta[]>;
2116+
searchValue?: string;
2117+
selectedOptions?: Array<Partial<ReportUtils.OptionData>>;
2118+
excludeLogins?: string[];
2119+
includeOwnedWorkspaceChats?: boolean;
2120+
includeP2P?: boolean;
2121+
includeCategories?: boolean;
2122+
categories?: PolicyCategories;
2123+
recentlyUsedCategories?: string[];
2124+
includeTags?: boolean;
2125+
tags?: PolicyTags | Array<PolicyTag | SelectedTagOption>;
2126+
recentlyUsedTags?: string[];
2127+
canInviteUser?: boolean;
2128+
includeSelectedOptions?: boolean;
2129+
includeTaxRates?: boolean;
2130+
taxRates?: TaxRatesWithDefault;
2131+
maxRecentReportsToShow?: number;
2132+
includeSelfDM?: boolean;
2133+
includePolicyReportFieldOptions?: boolean;
2134+
policyReportFieldOptions?: string[];
2135+
recentlyUsedPolicyReportFieldOptions?: string[];
2136+
includeInvoiceRooms?: boolean;
2137+
action?: IOUAction;
2138+
sortByReportTypeInSearch?: boolean;
2139+
};
2140+
2141+
// It is not recommended to pass a search value to getFilteredOptions when passing reports and personalDetails.
2142+
// If a search value is passed, the search value should be passed to filterOptions.
2143+
// When it is necessary to pass a search value when passing reports and personalDetails, follow these steps:
2144+
// 1. Use getFilteredOptions with reports and personalDetails only, without the search value.
2145+
// 2. Pass the returned options from getFilteredOptions to filterOptions along with the search value.
2146+
// The above constraints are enforced with TypeScript.
2147+
2148+
type FilteredOptionsParamsWithDefaultSearchValue = Omit<FilteredOptionsParams, 'searchValue'> & {searchValue?: ''};
2149+
2150+
type FilteredOptionsParamsWithoutOptions = Omit<FilteredOptionsParams, 'reports' | 'personalDetails'> & {reports?: []; personalDetails?: []};
2151+
2152+
function getFilteredOptions(params: FilteredOptionsParamsWithDefaultSearchValue | FilteredOptionsParamsWithoutOptions) {
2153+
const {
2154+
reports = [],
2155+
personalDetails = [],
2156+
betas = [],
2157+
searchValue = '',
2158+
selectedOptions = [],
2159+
excludeLogins = [],
2160+
includeOwnedWorkspaceChats = false,
2161+
includeP2P = true,
2162+
includeCategories = false,
2163+
categories = {},
2164+
recentlyUsedCategories = [],
2165+
includeTags = false,
2166+
tags = {},
2167+
recentlyUsedTags = [],
2168+
canInviteUser = true,
2169+
includeSelectedOptions = false,
2170+
includeTaxRates = false,
2171+
maxRecentReportsToShow = CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
2172+
taxRates = {} as TaxRatesWithDefault,
2173+
includeSelfDM = false,
2174+
includePolicyReportFieldOptions = false,
2175+
policyReportFieldOptions = [],
2176+
recentlyUsedPolicyReportFieldOptions = [],
2177+
includeInvoiceRooms = false,
2178+
action,
2179+
sortByReportTypeInSearch = false,
2180+
} = params;
21452181
return getOptions(
21462182
{reports, personalDetails},
21472183
{
@@ -2421,8 +2457,19 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
24212457
preferPolicyExpenseChat = false,
24222458
preferRecentExpenseReports = false,
24232459
} = config ?? {};
2460+
// Remove the personal details for the DMs that are already in the recent reports so that we don't show duplicates
2461+
function filteredPersonalDetailsOfRecentReports(recentReports: ReportUtils.OptionData[], personalDetails: ReportUtils.OptionData[]) {
2462+
const excludedLogins = new Set(recentReports.map((report) => report.login));
2463+
return personalDetails.filter((personalDetail) => !excludedLogins.has(personalDetail.login));
2464+
}
24242465
if (searchInputValue.trim() === '' && maxRecentReportsToShow > 0) {
2425-
return {...options, recentReports: options.recentReports.slice(0, maxRecentReportsToShow)};
2466+
const recentReports = options.recentReports.slice(0, maxRecentReportsToShow);
2467+
const personalDetails = filteredPersonalDetailsOfRecentReports(recentReports, options.personalDetails);
2468+
return {
2469+
...options,
2470+
recentReports,
2471+
personalDetails,
2472+
};
24262473
}
24272474

24282475
const parsedPhoneNumber = PhoneNumber.parsePhoneNumber(LoginUtils.appendCountryCode(Str.removeSMSDomain(searchInputValue)));
@@ -2464,7 +2511,6 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
24642511
const currentUserOptionSearchText = items.currentUserOption ? uniqFast(getCurrentUserSearchTerms(items.currentUserOption)).join(' ') : '';
24652512

24662513
const currentUserOption = isSearchStringMatch(term, currentUserOptionSearchText) ? items.currentUserOption : null;
2467-
24682514
return {
24692515
recentReports: recentReports ?? [],
24702516
personalDetails: personalDetails ?? [],
@@ -2479,6 +2525,7 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
24792525
let {recentReports, personalDetails} = matchResults;
24802526

24812527
if (sortByReportTypeInSearch) {
2528+
personalDetails = filteredPersonalDetailsOfRecentReports(recentReports, personalDetails);
24822529
recentReports = recentReports.concat(personalDetails);
24832530
personalDetails = [];
24842531
recentReports = orderOptions(recentReports, searchValue);
@@ -2489,9 +2536,10 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
24892536
if (maxRecentReportsToShow > 0 && recentReports.length > maxRecentReportsToShow) {
24902537
recentReports.splice(maxRecentReportsToShow);
24912538
}
2539+
const filteredPersonalDetails = filteredPersonalDetailsOfRecentReports(recentReports, personalDetails);
24922540

24932541
return {
2494-
personalDetails,
2542+
personalDetails: filteredPersonalDetails,
24952543
recentReports: orderOptions(recentReports, searchValue, {preferChatroomsOverThreads, preferPolicyExpenseChat, preferRecentExpenseReports}),
24962544
userToInvite,
24972545
currentUserOption: matchResults.currentUserOption,

src/pages/EditReportFieldDropdown.tsx

+15-40
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import React, {useCallback, useMemo} from 'react';
2-
import {withOnyx} from 'react-native-onyx';
3-
import type {OnyxEntry} from 'react-native-onyx';
2+
import {useOnyx} from 'react-native-onyx';
43
import Icon from '@components/Icon';
54
import * as Expensicons from '@components/Icon/Expensicons';
65
import SelectionList from '@components/SelectionList';
@@ -11,9 +10,7 @@ import useLocalize from '@hooks/useLocalize';
1110
import useTheme from '@hooks/useTheme';
1211
import localeCompare from '@libs/LocaleCompare';
1312
import * as OptionsListUtils from '@libs/OptionsListUtils';
14-
import CONST from '@src/CONST';
1513
import ONYXKEYS from '@src/ONYXKEYS';
16-
import type {RecentlyUsedReportFields} from '@src/types/onyx';
1714

1815
type EditReportFieldDropdownPageComponentProps = {
1916
/** Value of the policy report field */
@@ -33,13 +30,10 @@ type EditReportFieldDropdownPageComponentProps = {
3330
onSubmit: (form: Record<string, string>) => void;
3431
};
3532

36-
type EditReportFieldDropdownPageOnyxProps = {
37-
recentlyUsedReportFields: OnyxEntry<RecentlyUsedReportFields>;
38-
};
39-
40-
type EditReportFieldDropdownPageProps = EditReportFieldDropdownPageComponentProps & EditReportFieldDropdownPageOnyxProps;
33+
type EditReportFieldDropdownPageProps = EditReportFieldDropdownPageComponentProps;
4134

42-
function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptions, recentlyUsedReportFields}: EditReportFieldDropdownPageProps) {
35+
function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptions}: EditReportFieldDropdownPageProps) {
36+
const [recentlyUsedReportFields] = useOnyx(ONYXKEYS.RECENTLY_USED_REPORT_FIELDS);
4337
const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');
4438
const theme = useTheme();
4539
const {translate} = useLocalize();
@@ -64,37 +58,22 @@ function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptio
6458
const [sections, headerMessage] = useMemo(() => {
6559
const validFieldOptions = fieldOptions?.filter((option) => !!option)?.sort(localeCompare);
6660

67-
const {policyReportFieldOptions} = OptionsListUtils.getFilteredOptions(
68-
[],
69-
[],
70-
[],
71-
debouncedSearchValue,
72-
[
61+
const {policyReportFieldOptions} = OptionsListUtils.getFilteredOptions({
62+
searchValue: debouncedSearchValue,
63+
selectedOptions: [
7364
{
7465
keyForList: fieldValue,
7566
searchText: fieldValue,
7667
text: fieldValue,
7768
},
7869
],
79-
[],
80-
false,
81-
false,
82-
false,
83-
{},
84-
[],
85-
false,
86-
{},
87-
[],
88-
false,
89-
false,
90-
undefined,
91-
CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
92-
undefined,
93-
undefined,
94-
true,
95-
validFieldOptions,
96-
recentlyUsedOptions,
97-
);
70+
71+
includeP2P: false,
72+
canInviteUser: false,
73+
includePolicyReportFieldOptions: true,
74+
policyReportFieldOptions: validFieldOptions,
75+
recentlyUsedPolicyReportFieldOptions: recentlyUsedOptions,
76+
});
9877

9978
const policyReportFieldData = policyReportFieldOptions?.[0]?.data ?? [];
10079
const header = OptionsListUtils.getHeaderMessageForNonUserList(policyReportFieldData.length > 0, debouncedSearchValue);
@@ -121,8 +100,4 @@ function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptio
121100

122101
EditReportFieldDropdownPage.displayName = 'EditReportFieldDropdownPage';
123102

124-
export default withOnyx<EditReportFieldDropdownPageProps, EditReportFieldDropdownPageOnyxProps>({
125-
recentlyUsedReportFields: {
126-
key: () => ONYXKEYS.RECENTLY_USED_REPORT_FIELDS,
127-
},
128-
})(EditReportFieldDropdownPage);
103+
export default EditReportFieldDropdownPage;

0 commit comments

Comments
 (0)