Skip to content

Commit 13d5dbd

Browse files
authored
Merge pull request Expensify#64392 from sofi-a/64353-save-button-overlaps-device-navigation-bar-in-type-status-page
Feat/add status and type filters to filters list
2 parents 077e011 + 97519f9 commit 13d5dbd

14 files changed

Lines changed: 424 additions & 100 deletions

File tree

src/CONST/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6655,6 +6655,8 @@ const CONST = {
66556655
GROUP_BY: 'groupBy',
66566656
},
66576657
SYNTAX_FILTER_KEYS: {
6658+
TYPE: 'type',
6659+
STATUS: 'status',
66586660
DATE: 'date',
66596661
AMOUNT: 'amount',
66606662
EXPENSE_TYPE: 'expenseType',

src/ROUTES.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ const ROUTES = {
5151
getRoute: ({name, jsonQuery}: {name: string; jsonQuery: SearchQueryString}) => `search/saved-search/rename?name=${name}&q=${jsonQuery}` as const,
5252
},
5353
SEARCH_ADVANCED_FILTERS: 'search/filters',
54+
SEARCH_ADVANCED_FILTERS_TYPE: 'search/filters/type',
55+
SEARCH_ADVANCED_FILTERS_STATUS: 'search/filters/status',
5456
SEARCH_ADVANCED_FILTERS_DATE: 'search/filters/date',
5557
SEARCH_ADVANCED_FILTERS_CURRENCY: 'search/filters/currency',
5658
SEARCH_ADVANCED_FILTERS_MERCHANT: 'search/filters/merchant',

src/SCREENS.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ const SCREENS = {
4343
MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS: 'Search_Money_Request_Report_Hold_Transactions',
4444
REPORT_RHP: 'Search_Report_RHP',
4545
ADVANCED_FILTERS_RHP: 'Search_Advanced_Filters_RHP',
46+
ADVANCED_FILTERS_TYPE_RHP: 'Search_Advanced_Filters_Type_RHP',
47+
ADVANCED_FILTERS_STATUS_RHP: 'Search_Advanced_Filters_Status_RHP',
4648
ADVANCED_FILTERS_DATE_RHP: 'Search_Advanced_Filters_Date_RHP',
4749
ADVANCED_FILTERS_SUBMITTED_RHP: 'Search_Advanced_Filters_Submitted_RHP',
4850
ADVANCED_FILTERS_APPROVED_RHP: 'Search_Advanced_Filters_Approved_RHP',

src/components/Search/SearchPageHeader/SearchFiltersBar.tsx

Lines changed: 9 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import type {SingleSelectItem} from '@components/Search/FilterDropdowns/SingleSe
1919
import SingleSelectPopup from '@components/Search/FilterDropdowns/SingleSelectPopup';
2020
import UserSelectPopup from '@components/Search/FilterDropdowns/UserSelectPopup';
2121
import {useSearchContext} from '@components/Search/SearchContext';
22-
import type {SearchGroupBy, SearchQueryJSON, SingularSearchStatus} from '@components/Search/types';
22+
import type {SearchQueryJSON, SingularSearchStatus} from '@components/Search/types';
2323
import SearchFiltersSkeleton from '@components/Skeletons/SearchFiltersSkeleton';
2424
import useLocalize from '@hooks/useLocalize';
2525
import useNetwork from '@hooks/useNetwork';
@@ -30,9 +30,9 @@ import {updateAdvancedFilters} from '@libs/actions/Search';
3030
import {mergeCardListWithWorkspaceFeeds} from '@libs/CardUtils';
3131
import DateUtils from '@libs/DateUtils';
3232
import Navigation from '@libs/Navigation/Navigation';
33-
import {canSendInvoice, getAllTaxRates} from '@libs/PolicyUtils';
34-
import {hasInvoiceReports} from '@libs/ReportUtils';
33+
import {getAllTaxRates} from '@libs/PolicyUtils';
3534
import {buildFilterFormValuesFromQuery, buildQueryStringFromFilterFormValues, buildSearchQueryJSON, buildSearchQueryString} from '@libs/SearchQueryUtils';
35+
import {getStatusOptions, getTypeOptions} from '@libs/SearchUIUtils';
3636
import CONST from '@src/CONST';
3737
import ONYXKEYS from '@src/ONYXKEYS';
3838
import ROUTES from '@src/ROUTES';
@@ -44,70 +44,6 @@ type SearchFiltersBarProps = {
4444
headerButtonsOptions: Array<DropdownOption<SearchHeaderOptionValue>>;
4545
};
4646

47-
const typeOptions: Array<SingleSelectItem<SearchDataTypes>> = [
48-
{translation: 'common.expense', value: CONST.SEARCH.DATA_TYPES.EXPENSE},
49-
{translation: 'common.chat', value: CONST.SEARCH.DATA_TYPES.CHAT},
50-
{translation: 'common.invoice', value: CONST.SEARCH.DATA_TYPES.INVOICE},
51-
{translation: 'common.trip', value: CONST.SEARCH.DATA_TYPES.TRIP},
52-
{translation: 'common.task', value: CONST.SEARCH.DATA_TYPES.TASK},
53-
];
54-
55-
const expenseStatusOptions: Array<MultiSelectItem<SingularSearchStatus>> = [
56-
{translation: 'common.unreported', value: CONST.SEARCH.STATUS.EXPENSE.UNREPORTED},
57-
{translation: 'common.drafts', value: CONST.SEARCH.STATUS.EXPENSE.DRAFTS},
58-
{translation: 'common.outstanding', value: CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING},
59-
{translation: 'iou.approved', value: CONST.SEARCH.STATUS.EXPENSE.APPROVED},
60-
{translation: 'iou.settledExpensify', value: CONST.SEARCH.STATUS.EXPENSE.PAID},
61-
{translation: 'iou.done', value: CONST.SEARCH.STATUS.EXPENSE.DONE},
62-
];
63-
64-
const expenseReportStatusOptions: Array<MultiSelectItem<SingularSearchStatus>> = [
65-
{translation: 'common.drafts', value: CONST.SEARCH.STATUS.EXPENSE.DRAFTS},
66-
{translation: 'common.outstanding', value: CONST.SEARCH.STATUS.EXPENSE.OUTSTANDING},
67-
{translation: 'iou.approved', value: CONST.SEARCH.STATUS.EXPENSE.APPROVED},
68-
{translation: 'iou.settledExpensify', value: CONST.SEARCH.STATUS.EXPENSE.PAID},
69-
{translation: 'iou.done', value: CONST.SEARCH.STATUS.EXPENSE.DONE},
70-
];
71-
72-
const chatStatusOptions: Array<MultiSelectItem<SingularSearchStatus>> = [
73-
{translation: 'common.unread', value: CONST.SEARCH.STATUS.CHAT.UNREAD},
74-
{translation: 'common.sent', value: CONST.SEARCH.STATUS.CHAT.SENT},
75-
{translation: 'common.attachments', value: CONST.SEARCH.STATUS.CHAT.ATTACHMENTS},
76-
{translation: 'common.links', value: CONST.SEARCH.STATUS.CHAT.LINKS},
77-
{translation: 'search.filters.pinned', value: CONST.SEARCH.STATUS.CHAT.PINNED},
78-
];
79-
80-
const invoiceStatusOptions: Array<MultiSelectItem<SingularSearchStatus>> = [
81-
{translation: 'common.outstanding', value: CONST.SEARCH.STATUS.INVOICE.OUTSTANDING},
82-
{translation: 'iou.settledExpensify', value: CONST.SEARCH.STATUS.INVOICE.PAID},
83-
];
84-
85-
const tripStatusOptions: Array<MultiSelectItem<SingularSearchStatus>> = [
86-
{translation: 'search.filters.current', value: CONST.SEARCH.STATUS.TRIP.CURRENT},
87-
{translation: 'search.filters.past', value: CONST.SEARCH.STATUS.TRIP.PAST},
88-
];
89-
90-
const taskStatusOptions: Array<MultiSelectItem<SingularSearchStatus>> = [
91-
{translation: 'common.outstanding', value: CONST.SEARCH.STATUS.TASK.OUTSTANDING},
92-
{translation: 'search.filters.completed', value: CONST.SEARCH.STATUS.TASK.COMPLETED},
93-
];
94-
95-
function getStatusOptions(type: SearchDataTypes, groupBy: SearchGroupBy | undefined) {
96-
switch (type) {
97-
case CONST.SEARCH.DATA_TYPES.CHAT:
98-
return chatStatusOptions;
99-
case CONST.SEARCH.DATA_TYPES.INVOICE:
100-
return invoiceStatusOptions;
101-
case CONST.SEARCH.DATA_TYPES.TRIP:
102-
return tripStatusOptions;
103-
case CONST.SEARCH.DATA_TYPES.TASK:
104-
return taskStatusOptions;
105-
case CONST.SEARCH.DATA_TYPES.EXPENSE:
106-
default:
107-
return groupBy === CONST.SEARCH.GROUP_BY.REPORTS ? expenseReportStatusOptions : expenseStatusOptions;
108-
}
109-
}
110-
11147
function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarProps) {
11248
const {hash, type, groupBy, status} = queryJSON;
11349
const scrollRef = useRef<RNScrollView>(null);
@@ -139,6 +75,8 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarPro
13975
const hasErrors = Object.keys(currentSearchResults?.errors ?? {}).length > 0 && !isOffline;
14076
const shouldShowSelectedDropdown = headerButtonsOptions.length > 0 && (!shouldUseNarrowLayout || (!!selectionMode && selectionMode.isEnabled));
14177

78+
const typeOptions = useMemo(() => getTypeOptions(allPolicies, session?.email), [allPolicies, session?.email]);
79+
14280
const filterFormValues = useMemo(() => {
14381
return buildFilterFormValuesFromQuery(queryJSON, policyCategories, policyTagsLists, currencyList, personalDetails, allCards, reports, taxRates);
14482
}, [allCards, currencyList, personalDetails, policyCategories, policyTagsLists, queryJSON, reports, taxRates]);
@@ -171,23 +109,17 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarPro
171109
Navigation.setParams({q: query});
172110
};
173111

174-
// Remove the invoice option if the user is not allowed to send invoices
175-
let visibleOptions = typeOptions;
176-
if (!canSendInvoice(allPolicies, session?.email) && !hasInvoiceReports()) {
177-
visibleOptions = visibleOptions.filter((typeOption) => typeOption.value !== CONST.SEARCH.DATA_TYPES.INVOICE);
178-
}
179-
180112
return (
181113
<SingleSelectPopup
182114
label={translate('common.type')}
183115
value={value}
184-
items={visibleOptions}
116+
items={typeOptions}
185117
closeOverlay={closeOverlay}
186118
onChange={onChange}
187119
/>
188120
);
189121
},
190-
[allPolicies, groupBy, queryJSON, session?.email, status, translate, type],
122+
[groupBy, queryJSON, status, translate, type, typeOptions],
191123
);
192124

193125
const statusComponent = useCallback(
@@ -280,7 +212,6 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarPro
280212
* filter bar
281213
*/
282214
const filters = useMemo(() => {
283-
const typeValue = typeOptions.find((option) => option.value === type) ?? null;
284215
const statusValue = getStatusOptions(type, groupBy).filter((option) => status.includes(option.value));
285216
const dateValue = [
286217
filterFormValues.dateAfter ? `${translate('common.after')} ${DateUtils.formatToReadableString(filterFormValues.dateAfter)}` : null,
@@ -293,7 +224,7 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarPro
293224
{
294225
label: translate('common.type'),
295226
PopoverComponent: typeComponent,
296-
value: typeValue?.translation ? translate(typeValue.translation) : null,
227+
value: translate(`common.${type}`),
297228
},
298229
{
299230
label: translate('common.status'),
@@ -316,10 +247,10 @@ function SearchFiltersBar({queryJSON, headerButtonsOptions}: SearchFiltersBarPro
316247
}, [
317248
type,
318249
groupBy,
319-
filterFormValues.from,
320250
filterFormValues.dateAfter,
321251
filterFormValues.dateBefore,
322252
filterFormValues.dateOn,
253+
filterFormValues.from,
323254
translate,
324255
typeComponent,
325256
statusComponent,

src/components/SelectionList/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,15 @@ type ExtendedTargetedEvent = TargetedEvent & {
108108
};
109109
};
110110

111-
type ListItem = {
111+
type ListItem<K extends string | number = string> = {
112112
/** Text to display */
113113
text?: string;
114114

115115
/** Alternate text to display */
116116
alternateText?: string | null;
117117

118118
/** Key used internally by React */
119-
keyForList?: string | null;
119+
keyForList?: K | null;
120120

121121
/** Whether this option is selected */
122122
isSelected?: boolean;

src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,8 @@ const SearchReportModalStackNavigator = createModalStackNavigator<SearchReportPa
719719

720720
const SearchAdvancedFiltersModalStackNavigator = createModalStackNavigator<SearchAdvancedFiltersParamList>({
721721
[SCREENS.SEARCH.ADVANCED_FILTERS_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchAdvancedFiltersPage').default,
722+
[SCREENS.SEARCH.ADVANCED_FILTERS_TYPE_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersTypePage').default,
723+
[SCREENS.SEARCH.ADVANCED_FILTERS_STATUS_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersStatusPage').default,
722724
[SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersDatePage').default,
723725
[SCREENS.SEARCH.ADVANCED_FILTERS_SUBMITTED_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersSubmittedPage').default,
724726
[SCREENS.SEARCH.ADVANCED_FILTERS_APPROVED_RHP]: () => require<ReactComponentModule>('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersApprovedPage').default,

src/libs/Navigation/linkingConfig/RELATIONS/SEARCH_TO_RHP.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import SCREENS from '@src/SCREENS';
44
// This file is used to define RHP screens that are in relation to the search screen.
55
const SEARCH_TO_RHP: Partial<Record<keyof SearchFullscreenNavigatorParamList, string[]>> = {
66
[SCREENS.SEARCH.ROOT]: [
7+
SCREENS.SEARCH.ADVANCED_FILTERS_TYPE_RHP,
8+
SCREENS.SEARCH.ADVANCED_FILTERS_STATUS_RHP,
79
SCREENS.SEARCH.REPORT_RHP,
810
SCREENS.SEARCH.TRANSACTION_HOLD_REASON_RHP,
911
SCREENS.SEARCH.TRANSACTIONS_CHANGE_REPORT_SEARCH_RHP,

src/libs/Navigation/linkingConfig/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,6 +1512,8 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
15121512
[SCREENS.RIGHT_MODAL.SEARCH_ADVANCED_FILTERS]: {
15131513
screens: {
15141514
[SCREENS.SEARCH.ADVANCED_FILTERS_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS,
1515+
[SCREENS.SEARCH.ADVANCED_FILTERS_TYPE_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_TYPE,
1516+
[SCREENS.SEARCH.ADVANCED_FILTERS_STATUS_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_STATUS,
15151517
[SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_DATE,
15161518
[SCREENS.SEARCH.ADVANCED_FILTERS_SUBMITTED_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_SUBMITTED,
15171519
[SCREENS.SEARCH.ADVANCED_FILTERS_APPROVED_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_APPROVED,

src/libs/SearchQueryUtils.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -572,15 +572,21 @@ function buildFilterFormValuesFromQuery(
572572
}
573573
}
574574

575-
const [typeKey = '', typeValue] = Object.entries(CONST.SEARCH.DATA_TYPES).find(([, value]) => value === queryJSON.type) ?? [];
575+
const [typeKey, typeValue] = Object.entries(CONST.SEARCH.DATA_TYPES).find(([, value]) => value === queryJSON.type) ?? [];
576576
filtersForm[FILTER_KEYS.TYPE] = typeValue ? queryJSON.type : CONST.SEARCH.DATA_TYPES.EXPENSE;
577-
const [statusKey] =
578-
Object.entries(CONST.SEARCH.STATUS).find(([, value]) =>
579-
Array.isArray(queryJSON.status) ? queryJSON.status.some((status) => Object.values(value).includes(status)) : Object.values(value).includes(queryJSON.status),
580-
) ?? [];
581577

582-
if (typeKey === statusKey) {
583-
filtersForm[FILTER_KEYS.STATUS] = Array.isArray(queryJSON.status) ? queryJSON.status.join(',') : queryJSON.status;
578+
if (typeKey) {
579+
if (Array.isArray(queryJSON.status)) {
580+
const validStatuses = queryJSON.status.filter((status) => Object.values(CONST.SEARCH.STATUS[typeKey as keyof typeof CONST.SEARCH.DATA_TYPES]).includes(status));
581+
582+
if (validStatuses.length) {
583+
filtersForm[FILTER_KEYS.STATUS] = queryJSON.status.join(',');
584+
} else {
585+
filtersForm[FILTER_KEYS.STATUS] = CONST.SEARCH.STATUS.EXPENSE.ALL;
586+
}
587+
} else {
588+
filtersForm[FILTER_KEYS.STATUS] = queryJSON.status;
589+
}
584590
} else {
585591
filtersForm[FILTER_KEYS.STATUS] = CONST.SEARCH.STATUS.EXPENSE.ALL;
586592
}

0 commit comments

Comments
 (0)