diff --git a/src/CONST.ts b/src/CONST.ts index 603c024ba33e..1f27234cf32b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5229,6 +5229,7 @@ const CONST = { EXPENSE: 'expense', INVOICE: 'invoice', TRIP: 'trip', + CHAT: 'chat', }, ACTION_TYPES: { VIEW: 'view', @@ -5272,6 +5273,10 @@ const CONST = { PAID: 'paid', }, }, + CHAT_TYPES: { + LINK: 'link', + ATTACHMENT: 'attachment', + }, TABLE_COLUMNS: { RECEIPT: 'receipt', DATE: 'date', @@ -5318,6 +5323,7 @@ const CONST = { CARD_ID: 'cardID', REPORT_ID: 'reportID', KEYWORD: 'keyword', + HAS: 'has', }, }, diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c532970824b0..05a76441bf01 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -52,6 +52,7 @@ const ROUTES = { SEARCH_ADVANCED_FILTERS_TAG: 'search/filters/tag', SEARCH_ADVANCED_FILTERS_FROM: 'search/filters/from', SEARCH_ADVANCED_FILTERS_TO: 'search/filters/to', + SEARCH_ADVANCED_FILTERS_HAS: 'search/filters/has', SEARCH_REPORT: { route: 'search/view/:reportID', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 9634cc1b02ac..cf4a3a6e8ed6 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -46,6 +46,7 @@ const SCREENS = { ADVANCED_FILTERS_TAG_RHP: 'Search_Advanced_Filters_Tag_RHP', ADVANCED_FILTERS_FROM_RHP: 'Search_Advanced_Filters_From_RHP', ADVANCED_FILTERS_TO_RHP: 'Search_Advanced_Filters_To_RHP', + ADVANCED_FILTERS_HAS_RHP: 'Search_Advanced_Filters_Has_RHP', TRANSACTION_HOLD_REASON_RHP: 'Search_Transaction_Hold_Reason_RHP', BOTTOM_TAB: 'Search_Bottom_Tab', }, diff --git a/src/components/Search/SearchMultipleSelectionPicker.tsx b/src/components/Search/SearchMultipleSelectionPicker.tsx index 57003110fd1a..558b89715b61 100644 --- a/src/components/Search/SearchMultipleSelectionPicker.tsx +++ b/src/components/Search/SearchMultipleSelectionPicker.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import Button from '@components/Button'; import SelectionList from '@components/SelectionList'; import SelectableListItem from '@components/SelectionList/SelectableListItem'; @@ -28,6 +28,10 @@ function SearchMultipleSelectionPicker({items, initiallySelectedItems, pickerTit const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const [selectedItems, setSelectedItems] = useState(initiallySelectedItems ?? []); + useEffect(() => { + setSelectedItems(initiallySelectedItems ?? []); + }, [initiallySelectedItems]); + const {sections, noResultsFound} = useMemo(() => { const selectedItemsSection = selectedItems .filter((item) => item?.name.toLowerCase().includes(debouncedSearchTerm?.toLowerCase())) diff --git a/src/languages/en.ts b/src/languages/en.ts index 48953757deca..a0381dfab376 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3799,6 +3799,8 @@ export default { keyword: 'Keyword', hasKeywords: 'Has keywords', currency: 'Currency', + has: 'Has', + link: 'Link', amount: { lessThan: (amount?: string) => `Less than ${amount ?? ''}`, greaterThan: (amount?: string) => `Greater than ${amount ?? ''}`, diff --git a/src/languages/es.ts b/src/languages/es.ts index 2ab6deacda47..d5259562dfef 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3849,6 +3849,8 @@ export default { keyword: 'Palabra clave', hasKeywords: 'Tiene palabras clave', currency: 'Divisa', + has: 'Tiene', + link: 'Enlace', amount: { lessThan: (amount?: string) => `Menos de ${amount ?? ''}`, greaterThan: (amount?: string) => `Más que ${amount ?? ''}`, diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 187e71df4d98..5b5655b44ec1 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -533,6 +533,7 @@ const SearchAdvancedFiltersModalStackNavigator = createModalStackNavigator require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_FROM_RHP]: () => require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersFromPage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_TO_RHP]: () => require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersToPage').default, + [SCREENS.SEARCH.ADVANCED_FILTERS_HAS_RHP]: () => require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage').default, }); const RestrictedActionModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 6ee3b14b64ed..0252e8088160 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1053,6 +1053,7 @@ const config: LinkingOptions['config'] = { [SCREENS.SEARCH.ADVANCED_FILTERS_TAG_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_TAG, [SCREENS.SEARCH.ADVANCED_FILTERS_FROM_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_FROM, [SCREENS.SEARCH.ADVANCED_FILTERS_TO_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_TO, + [SCREENS.SEARCH.ADVANCED_FILTERS_HAS_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, }, }, [SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: { diff --git a/src/libs/SearchParser/searchParser.js b/src/libs/SearchParser/searchParser.js index 403e8442ed5b..a93e3fae8551 100644 --- a/src/libs/SearchParser/searchParser.js +++ b/src/libs/SearchParser/searchParser.js @@ -200,7 +200,8 @@ function peg$parse(input, options) { var peg$c22 = "sortBy"; var peg$c23 = "sortOrder"; var peg$c24 = "policyID"; - var peg$c25 = "\""; + var peg$c25 = "has"; + var peg$c26 = "\""; var peg$r0 = /^[:=]/; var peg$r1 = /^[^"\r\n]/; @@ -233,11 +234,12 @@ function peg$parse(input, options) { var peg$e23 = peg$literalExpectation("sortBy", false); var peg$e24 = peg$literalExpectation("sortOrder", false); var peg$e25 = peg$literalExpectation("policyID", false); - var peg$e26 = peg$literalExpectation("\"", false); - var peg$e27 = peg$classExpectation(["\"", "\r", "\n"], true, false); - var peg$e28 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";"], false, false); - var peg$e29 = peg$otherExpectation("whitespace"); - var peg$e30 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); + var peg$e26 = peg$literalExpectation("has", false); + var peg$e27 = peg$literalExpectation("\"", false); + var peg$e28 = peg$classExpectation(["\"", "\r", "\n"], true, false); + var peg$e29 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";"], false, false); + var peg$e30 = peg$otherExpectation("whitespace"); + var peg$e31 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); var peg$f0 = function(filters) { const withDefaults = applyDefaults(filters); @@ -311,10 +313,11 @@ function peg$parse(input, options) { var peg$f26 = function() { return "sortBy"; }; var peg$f27 = function() { return "sortOrder"; }; var peg$f28 = function() { return "policyID"; }; - var peg$f29 = function(parts) { return parts.join(''); }; - var peg$f30 = function(chars) { return chars.join(''); }; + var peg$f29 = function() { return "has"; }; + var peg$f30 = function(parts) { return parts.join(''); }; var peg$f31 = function(chars) { return chars.join(''); }; - var peg$f32 = function() { return "and"; }; + var peg$f32 = function(chars) { return chars.join(''); }; + var peg$f33 = function() { return "and"; }; var peg$currPos = options.peg$currPos | 0; var peg$savedPos = peg$currPos; var peg$posDetailsCache = [{ line: 1, column: 1 }]; @@ -937,6 +940,21 @@ function peg$parse(input, options) { s1 = peg$f28(); } s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 3) === peg$c25) { + s1 = peg$c25; + peg$currPos += 3; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e26); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f29(); + } + s0 = s1; + } } } } @@ -982,7 +1000,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f29(s1); + s1 = peg$f30(s1); } s0 = s1; @@ -994,11 +1012,11 @@ function peg$parse(input, options) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c25; + s1 = peg$c26; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e26); } + if (peg$silentFails === 0) { peg$fail(peg$e27); } } if (s1 !== peg$FAILED) { s2 = []; @@ -1007,7 +1025,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e27); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } while (s3 !== peg$FAILED) { s2.push(s3); @@ -1016,19 +1034,19 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e27); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } } if (input.charCodeAt(peg$currPos) === 34) { - s3 = peg$c25; + s3 = peg$c26; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e26); } + if (peg$silentFails === 0) { peg$fail(peg$e27); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f30(s2); + s0 = peg$f31(s2); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1051,7 +1069,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e28); } + if (peg$silentFails === 0) { peg$fail(peg$e29); } } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { @@ -1061,7 +1079,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e28); } + if (peg$silentFails === 0) { peg$fail(peg$e29); } } } } else { @@ -1069,7 +1087,7 @@ function peg$parse(input, options) { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$f31(s1); + s1 = peg$f32(s1); } s0 = s1; @@ -1082,7 +1100,7 @@ function peg$parse(input, options) { s0 = peg$currPos; s1 = peg$parse_(); peg$savedPos = s0; - s1 = peg$f32(); + s1 = peg$f33(); s0 = s1; return s0; @@ -1098,7 +1116,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } while (s1 !== peg$FAILED) { s0.push(s1); @@ -1107,12 +1125,12 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } } peg$silentFails--; s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } return s0; } diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index 2f3efb39a6b1..105a8a62bc39 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -134,6 +134,7 @@ key / "sortBy" { return "sortBy"; } / "sortOrder" { return "sortOrder"; } / "policyID" { return "policyID"; } + / "has" { return "has"; } identifier = parts:(quotedString / alphanumeric)+ { return parts.join(''); } diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index 4a8864fef769..9069a3064e05 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -427,6 +427,16 @@ function getExpenseTypeTranslationKey(expenseType: ValueOf): TranslationPaths { + // eslint-disable-next-line default-case + switch (has) { + case CONST.SEARCH.CHAT_TYPES.LINK: + return 'search.filters.link'; + case CONST.SEARCH.CHAT_TYPES.ATTACHMENT: + return 'common.attachment'; + } +} + /** * Given object with chosen search filters builds correct query string from them */ @@ -453,7 +463,8 @@ function buildQueryStringFromFilters(filterValues: Partial 0 ) { @@ -588,4 +599,5 @@ export { shouldShowYear, buildCannedSearchQuery, getExpenseTypeTranslationKey, + getChatFiltersTranslationKey, }; diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index baebe821d101..611593c46894 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -1,6 +1,7 @@ import {Str} from 'expensify-common'; -import React, {useMemo} from 'react'; +import React from 'react'; import {View} from 'react-native'; +import type {ValueOf} from 'react-native-gesture-handler/lib/typescript/typeUtils'; import {useOnyx} from 'react-native-onyx'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; @@ -24,6 +25,91 @@ import ROUTES from '@src/ROUTES'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; import type {CardList, PersonalDetailsList} from '@src/types/onyx'; +const baseFilterConfig = { + date: { + getTitle: getFilterDisplayTitle, + description: 'common.date' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, + }, + currency: { + getTitle: getFilterDisplayTitle, + description: 'common.currency' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_CURRENCY, + }, + merchant: { + getTitle: getFilterDisplayTitle, + description: 'common.merchant' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_MERCHANT, + }, + description: { + getTitle: getFilterDisplayTitle, + description: 'common.description' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_DESCRIPTION, + }, + reportID: { + getTitle: getFilterDisplayTitle, + description: 'common.reportID' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_REPORT_ID, + }, + amount: { + getTitle: getFilterDisplayTitle, + description: 'common.total' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_AMOUNT, + }, + category: { + getTitle: getFilterDisplayTitle, + description: 'common.category' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY, + }, + keyword: { + getTitle: getFilterDisplayTitle, + description: 'search.filters.hasKeywords' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_KEYWORD, + }, + cardID: { + getTitle: getFilterCardDisplayTitle, + description: 'common.card' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_CARD, + }, + taxRate: { + getTitle: getFilterTaxRateDisplayTitle, + description: 'workspace.taxes.taxRate' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_TAX_RATE, + }, + expenseType: { + getTitle: getExpenseTypeDisplayTitle, + description: 'search.expenseType' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_EXPENSE_TYPE, + }, + tag: { + getTitle: getFilterDisplayTitle, + description: 'common.tag' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_TAG, + }, + from: { + getTitle: getFilterParticipantDisplayTitle, + description: 'common.from' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_FROM, + }, + to: { + getTitle: getFilterParticipantDisplayTitle, + description: 'common.to' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_TO, + }, + has: { + getTitle: getFilterHasDisplayTitle, + description: 'search.filters.has' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, + }, +}; + +const typeFiltersKeys: Record>> = { + [CONST.SEARCH.DATA_TYPES.EXPENSE]: ['date', 'currency', 'merchant', 'description', 'reportID', 'amount', 'category', 'keyword', 'taxRate', 'expenseType', 'tag', 'from', 'to', 'cardID'], + [CONST.SEARCH.DATA_TYPES.INVOICE]: ['date', 'currency', 'merchant', 'description', 'reportID', 'amount', 'category', 'keyword', 'taxRate', 'tag', 'from', 'to', 'cardID'], + [CONST.SEARCH.DATA_TYPES.TRIP]: ['date', 'currency', 'merchant', 'description', 'reportID', 'amount', 'category', 'keyword', 'taxRate', 'tag', 'from', 'to', 'cardID'], + [CONST.SEARCH.DATA_TYPES.CHAT]: ['date', 'keyword', 'from', 'has'], +}; + function getFilterCardDisplayTitle(filters: Partial, cards: CardList) { const filterValue = filters[CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID]; return filterValue @@ -125,6 +211,16 @@ function getExpenseTypeDisplayTitle(filters: Partial, : undefined; } +function getFilterHasDisplayTitle(filters: Partial, translate: LocaleContextProps['translate']) { + const filterValue = filters[CONST.SEARCH.SYNTAX_FILTER_KEYS.HAS]; + return filterValue + ? Object.values(CONST.SEARCH.CHAT_TYPES) + .filter((hasFilter) => filterValue.includes(hasFilter)) + .map((hasFilter) => translate(SearchUtils.getChatFiltersTranslationKey(hasFilter))) + .join(', ') + : undefined; +} + function AdvancedSearchFilters() { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -136,82 +232,7 @@ function AdvancedSearchFilters() { const taxRates = getAllTaxRates(); const personalDetails = usePersonalDetails(); - const advancedFilters = useMemo( - () => [ - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, translate), - description: 'common.date' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY, translate), - description: 'common.currency' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_CURRENCY, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT, translate), - description: 'common.merchant' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_MERCHANT, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION, translate), - description: 'common.description' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_DESCRIPTION, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_ID, translate), - description: 'common.reportID' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_REPORT_ID, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT, translate), - description: 'common.total' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_AMOUNT, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY, translate), - description: 'common.category' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD, translate), - description: 'search.filters.hasKeywords' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_KEYWORD, - }, - { - title: getFilterCardDisplayTitle(searchAdvancedFilters, cardList), - description: 'common.card' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_CARD, - shouldHide: Object.keys(cardList).length === 0, - }, - { - title: getFilterTaxRateDisplayTitle(searchAdvancedFilters, taxRates), - description: 'workspace.taxes.taxRate' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_TAX_RATE, - }, - { - title: getExpenseTypeDisplayTitle(searchAdvancedFilters, translate), - description: 'search.expenseType' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_EXPENSE_TYPE, - }, - { - title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG, translate), - description: 'common.tag' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_TAG, - }, - { - title: getFilterParticipantDisplayTitle(searchAdvancedFilters.from ?? [], personalDetails), - description: 'common.from' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_FROM, - }, - { - title: getFilterParticipantDisplayTitle(searchAdvancedFilters.to ?? [], personalDetails), - description: 'common.to' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_TO, - }, - ], - [searchAdvancedFilters, translate, cardList, taxRates, personalDetails], - ); + const currentType = searchAdvancedFilters?.type ?? CONST.SEARCH.DATA_TYPES.EXPENSE; const onFormSubmit = () => { const query = SearchUtils.buildQueryStringFromFilters(searchAdvancedFilters); @@ -225,22 +246,56 @@ function AdvancedSearchFilters() { ); }; + const filters = typeFiltersKeys[currentType].map((key) => { + const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(baseFilterConfig[key].route))); + let filterTitle; + if ( + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_ID || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG + ) { + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, key, translate); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID) { + if (Object.keys(cardList).length === 0) { + return undefined; + } + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, cardList); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAX_RATE) { + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, taxRates); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPENSE_TYPE || key === CONST.SEARCH.SYNTAX_FILTER_KEYS.HAS) { + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, translate); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM || key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TO) { + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters[key] ?? [], personalDetails); + } + return { + key, + title: filterTitle, + description: translate(baseFilterConfig[key].description), + onPress, + }; + }); + return ( <> - {advancedFilters.map((item) => { - const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(item.route))); - if (item.shouldHide) { + {filters.map((filter) => { + if (filter === undefined) { return undefined; } return ( ); })} diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage.tsx index 96984029142b..65d474b41905 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage.tsx @@ -19,10 +19,14 @@ function SearchFiltersExpenseTypePage() { const {translate} = useLocalize(); const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); - const selectedExpenseTypes = searchAdvancedFiltersForm?.expenseType?.map((expenseType) => { - const expenseTypeName = translate(getExpenseTypeTranslationKey(expenseType as ValueOf)); - return {name: expenseTypeName, value: expenseType}; - }); + const initiallySelectedItems = useMemo( + () => + searchAdvancedFiltersForm?.expenseType?.map((expenseType) => { + const expenseTypeName = translate(getExpenseTypeTranslationKey(expenseType as ValueOf)); + return {name: expenseTypeName, value: expenseType}; + }), + [searchAdvancedFiltersForm, translate], + ); const allExpenseTypes = Object.values(CONST.SEARCH.TRANSACTION_TYPE); const expenseTypesItems = useMemo(() => { @@ -52,7 +56,7 @@ function SearchFiltersExpenseTypePage() { diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx new file mode 100644 index 000000000000..e964554373b0 --- /dev/null +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage.tsx @@ -0,0 +1,75 @@ +import React, {useCallback, useMemo} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SearchMultipleSelectionPicker from '@components/Search/SearchMultipleSelectionPicker'; +import useLocalize from '@hooks/useLocalize'; +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'; + +type FilterItem = { + name: string; + value: typeof CONST.SEARCH.CHAT_TYPES.ATTACHMENT | typeof CONST.SEARCH.CHAT_TYPES.LINK; +}; + +function SearchFiltersHasPage() { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); + + const filterItems: FilterItem[] = useMemo( + () => [ + { + name: translate('common.attachment'), + value: CONST.SEARCH.CHAT_TYPES.ATTACHMENT, + }, + { + name: translate('search.filters.link'), + value: CONST.SEARCH.CHAT_TYPES.LINK, + }, + ], + [translate], + ); + + const selectedOptions = useMemo(() => { + return searchAdvancedFiltersForm?.has?.map((value) => filterItems.find((filterItem) => filterItem.value === value)).filter((item): item is FilterItem => item !== undefined) ?? []; + }, [searchAdvancedFiltersForm, filterItems]); + + const updateHasFilter = useCallback((values: string[]) => SearchActions.updateAdvancedFilters({has: values}), []); + + return ( + + { + Navigation.goBack(ROUTES.SEARCH_ADVANCED_FILTERS); + }} + /> + + + + + ); +} + +SearchFiltersHasPage.displayName = 'SearchFiltersHasPage'; + +export default SearchFiltersHasPage; diff --git a/src/types/form/SearchAdvancedFiltersForm.ts b/src/types/form/SearchAdvancedFiltersForm.ts index 6541072cae81..0b9ea53596ed 100644 --- a/src/types/form/SearchAdvancedFiltersForm.ts +++ b/src/types/form/SearchAdvancedFiltersForm.ts @@ -2,6 +2,7 @@ import type {ValueOf} from 'type-fest'; import type Form from './Form'; const FILTER_KEYS = { + TYPE: 'type', DATE_AFTER: 'dateAfter', DATE_BEFORE: 'dateBefore', CURRENCY: 'currency', @@ -19,6 +20,7 @@ const FILTER_KEYS = { KEYWORD: 'keyword', FROM: 'from', TO: 'to', + HAS: 'has', } as const; type InputID = ValueOf; @@ -26,6 +28,7 @@ type InputID = ValueOf; type SearchAdvancedFiltersForm = Form< InputID, { + [FILTER_KEYS.TYPE]: string; [FILTER_KEYS.DATE_AFTER]: string; [FILTER_KEYS.DATE_BEFORE]: string; [FILTER_KEYS.CURRENCY]: string[]; @@ -43,6 +46,7 @@ type SearchAdvancedFiltersForm = Form< [FILTER_KEYS.TAG]: string[]; [FILTER_KEYS.FROM]: string[]; [FILTER_KEYS.TO]: string[]; + [FILTER_KEYS.HAS]: string[]; } >;