diff --git a/src/CONST.ts b/src/CONST.ts index d0695b1e285f..39b6902234b4 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5395,11 +5395,6 @@ const CONST = { DONE: 'done', PAID: 'paid', }, - CHAT_STATUS: { - UNREAD: 'unread', - PINNED: 'pinned', - DRAFT: 'draft', - }, BULK_ACTION_TYPES: { EXPORT: 'export', HOLD: 'hold', @@ -5441,10 +5436,6 @@ const CONST = { LINKS: 'links', }, }, - CHAT_TYPES: { - LINK: 'link', - ATTACHMENT: 'attachment', - }, TABLE_COLUMNS: { RECEIPT: 'receipt', DATE: 'date', @@ -5492,8 +5483,6 @@ const CONST = { REPORT_ID: 'reportID', KEYWORD: 'keyword', IN: 'in', - HAS: 'has', - IS: 'is', }, }, diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a28c2ef4fc57..faa406136ec9 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -35,7 +35,7 @@ const ROUTES = { SEARCH_CENTRAL_PANE: { route: 'search', - getRoute: ({query}: {query: SearchQueryString}) => `search?q=${query}` as const, + getRoute: ({query}: {query: SearchQueryString}) => `search?q=${encodeURIComponent(query)}` as const, }, SEARCH_ADVANCED_FILTERS: 'search/filters', SEARCH_ADVANCED_FILTERS_DATE: 'search/filters/date', @@ -53,9 +53,6 @@ const ROUTES = { SEARCH_ADVANCED_FILTERS_FROM: 'search/filters/from', SEARCH_ADVANCED_FILTERS_TO: 'search/filters/to', SEARCH_ADVANCED_FILTERS_IN: 'search/filters/in', - SEARCH_ADVANCED_FILTERS_HAS: 'search/filters/has', - SEARCH_ADVANCED_FILTERS_IS: 'search/filters/is', - SEARCH_REPORT: { route: 'search/view/:reportID/:reportActionID?', getRoute: (reportID: string, reportActionID?: string) => { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 67ba5b84c9ec..27328c0c8951 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -47,8 +47,6 @@ const SCREENS = { ADVANCED_FILTERS_FROM_RHP: 'Search_Advanced_Filters_From_RHP', ADVANCED_FILTERS_TO_RHP: 'Search_Advanced_Filters_To_RHP', ADVANCED_FILTERS_IN_RHP: 'Search_Advanced_Filters_In_RHP', - ADVANCED_FILTERS_HAS_RHP: 'Search_Advanced_Filters_Has_RHP', - ADVANCED_FILTERS_IS_RHP: 'Search_Advanced_Filters_Is_RHP', TRANSACTION_HOLD_REASON_RHP: 'Search_Transaction_Hold_Reason_RHP', BOTTOM_TAB: 'Search_Bottom_Tab', }, diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx index f336740a8558..5aa44fe914f2 100644 --- a/src/components/Search/SearchPageHeader.tsx +++ b/src/components/Search/SearchPageHeader.tsx @@ -298,6 +298,12 @@ function SearchPageHeader({queryJSON, hash, onSelectDeleteOption, setOfflineModa return null; } + const onPress = () => { + const values = SearchUtils.getFiltersFormValues(queryJSON); + SearchActions.updateAdvancedFilters(values); + Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS); + }; + return ( Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS)} + onPress={onPress} medium /> diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index b22c8e58e122..c1b5758763a7 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -50,7 +50,7 @@ type QueryFilter = { value: string | number; }; -type AdvancedFiltersKeys = ValueOf | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.TYPE | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.STATUS; +type AdvancedFiltersKeys = ValueOf; type QueryFilters = { [K in AdvancedFiltersKeys]?: QueryFilter[]; diff --git a/src/languages/en.ts b/src/languages/en.ts index 3d37787038ea..0fb15a8c834c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3918,12 +3918,9 @@ export default { keyword: 'Keyword', hasKeywords: 'Has keywords', currency: 'Currency', - has: 'Has', link: 'Link', - is: 'Is', pinned: 'Pinned', unread: 'Unread', - draft: 'Draft', 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 ff7650e9e02c..eddac07422b3 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3969,12 +3969,9 @@ export default { keyword: 'Palabra clave', hasKeywords: 'Tiene palabras clave', currency: 'Divisa', - has: 'Tiene', link: 'Enlace', - is: 'Está', pinned: 'Fijado', unread: 'No leído', - draft: 'Borrador', 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 1b2390b17c39..76a9fad2749b 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -544,8 +544,6 @@ const SearchAdvancedFiltersModalStackNavigator = createModalStackNavigator require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersFromPage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_TO_RHP]: () => require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersToPage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_IN_RHP]: () => require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersInPage').default, - [SCREENS.SEARCH.ADVANCED_FILTERS_HAS_RHP]: () => require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersHasPage').default, - [SCREENS.SEARCH.ADVANCED_FILTERS_IS_RHP]: () => require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersIsPage').default, }); const RestrictedActionModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index ff3c60693cbc..ecdae4ed55b1 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -61,7 +61,6 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = SCREENS.SEARCH.ADVANCED_FILTERS_TO_RHP, SCREENS.SEARCH.ADVANCED_FILTERS_IN_RHP, SCREENS.SEARCH.ADVANCED_FILTERS_CARD_RHP, - SCREENS.SEARCH.ADVANCED_FILTERS_HAS_RHP, ], [SCREENS.SETTINGS.SUBSCRIPTION.ROOT]: [ SCREENS.SETTINGS.SUBSCRIPTION.ADD_PAYMENT_CARD, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 65fb05f8d008..47bc5f421093 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1082,8 +1082,6 @@ const config: LinkingOptions['config'] = { [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_IN_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_IN, - [SCREENS.SEARCH.ADVANCED_FILTERS_HAS_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_HAS, - [SCREENS.SEARCH.ADVANCED_FILTERS_IS_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_IS, }, }, [SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: { diff --git a/src/libs/SearchParser/searchParser.js b/src/libs/SearchParser/searchParser.js index 32fd834c1346..622c3f5f3c4a 100644 --- a/src/libs/SearchParser/searchParser.js +++ b/src/libs/SearchParser/searchParser.js @@ -200,14 +200,12 @@ function peg$parse(input, options) { var peg$c17 = "cardID"; var peg$c18 = "from"; var peg$c19 = "expenseType"; - var peg$c20 = "has"; - var peg$c21 = "is"; - var peg$c22 = "type"; - var peg$c23 = "status"; - var peg$c24 = "sortBy"; - var peg$c25 = "sortOrder"; - var peg$c26 = "policyID"; - var peg$c27 = "\""; + var peg$c20 = "type"; + var peg$c21 = "status"; + var peg$c22 = "sortBy"; + var peg$c23 = "sortOrder"; + var peg$c24 = "policyID"; + var peg$c25 = "\""; var peg$r0 = /^[:=]/; var peg$r1 = /^[^"\r\n]/; @@ -237,21 +235,19 @@ function peg$parse(input, options) { var peg$e20 = peg$literalExpectation("cardID", false); var peg$e21 = peg$literalExpectation("from", false); var peg$e22 = peg$literalExpectation("expenseType", false); - var peg$e23 = peg$literalExpectation("has", false); - var peg$e24 = peg$literalExpectation("is", false); - var peg$e25 = peg$otherExpectation("default key"); - var peg$e26 = peg$literalExpectation("type", false); - var peg$e27 = peg$literalExpectation("status", false); - var peg$e28 = peg$literalExpectation("sortBy", false); - var peg$e29 = peg$literalExpectation("sortOrder", false); - var peg$e30 = peg$literalExpectation("policyID", false); - var peg$e31 = peg$otherExpectation("quote"); - var peg$e32 = peg$literalExpectation("\"", false); - var peg$e33 = peg$classExpectation(["\"", "\r", "\n"], true, false); - var peg$e34 = peg$otherExpectation("word"); - var peg$e35 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";"], false, false); - var peg$e36 = peg$otherExpectation("whitespace"); - var peg$e37 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); + var peg$e23 = peg$otherExpectation("default key"); + var peg$e24 = peg$literalExpectation("type", false); + var peg$e25 = peg$literalExpectation("status", false); + var peg$e26 = peg$literalExpectation("sortBy", false); + var peg$e27 = peg$literalExpectation("sortOrder", false); + var peg$e28 = peg$literalExpectation("policyID", false); + var peg$e29 = peg$otherExpectation("quote"); + var peg$e30 = peg$literalExpectation("\"", false); + var peg$e31 = peg$classExpectation(["\"", "\r", "\n"], true, false); + var peg$e32 = peg$otherExpectation("word"); + var peg$e33 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "_", "@", ".", "/", "#", "&", "+", "-", "\\", "'", ",", ";"], false, false); + var peg$e34 = peg$otherExpectation("whitespace"); + var peg$e35 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); var peg$f0 = function(filters) { return applyDefaults(filters); }; var peg$f1 = function(head, tail) { @@ -853,30 +849,12 @@ function peg$parse(input, options) { if (peg$silentFails === 0) { peg$fail(peg$e22); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 3) === peg$c20) { - s1 = peg$c20; - peg$currPos += 3; + if (input.substr(peg$currPos, 2) === peg$c11) { + s1 = peg$c11; + peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e23); } - } - if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c11) { - s1 = peg$c11; - peg$currPos += 2; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e14); } - } - if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c21) { - s1 = peg$c21; - peg$currPos += 2; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e24); } - } - } + if (peg$silentFails === 0) { peg$fail(peg$e14); } } } } @@ -913,44 +891,44 @@ function peg$parse(input, options) { peg$silentFails++; s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c22) { - s1 = peg$c22; + if (input.substr(peg$currPos, 4) === peg$c20) { + s1 = peg$c20; peg$currPos += 4; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e26); } + if (peg$silentFails === 0) { peg$fail(peg$e24); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 6) === peg$c23) { - s1 = peg$c23; + if (input.substr(peg$currPos, 6) === peg$c21) { + s1 = peg$c21; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e27); } + if (peg$silentFails === 0) { peg$fail(peg$e25); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 6) === peg$c24) { - s1 = peg$c24; + if (input.substr(peg$currPos, 6) === peg$c22) { + s1 = peg$c22; peg$currPos += 6; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e28); } + if (peg$silentFails === 0) { peg$fail(peg$e26); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 9) === peg$c25) { - s1 = peg$c25; + if (input.substr(peg$currPos, 9) === peg$c23) { + s1 = peg$c23; peg$currPos += 9; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } + if (peg$silentFails === 0) { peg$fail(peg$e27); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 8) === peg$c26) { - s1 = peg$c26; + if (input.substr(peg$currPos, 8) === peg$c24) { + s1 = peg$c24; peg$currPos += 8; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } } } @@ -965,7 +943,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e25); } + if (peg$silentFails === 0) { peg$fail(peg$e23); } } return s0; @@ -1006,11 +984,11 @@ function peg$parse(input, options) { peg$silentFails++; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c27; + s1 = peg$c25; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e32); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } } if (s1 !== peg$FAILED) { s2 = []; @@ -1019,7 +997,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e33); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } while (s3 !== peg$FAILED) { s2.push(s3); @@ -1028,15 +1006,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e33); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } } if (input.charCodeAt(peg$currPos) === 34) { - s3 = peg$c27; + s3 = peg$c25; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e32); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; @@ -1052,7 +1030,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e31); } + if (peg$silentFails === 0) { peg$fail(peg$e29); } } return s0; @@ -1069,7 +1047,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + if (peg$silentFails === 0) { peg$fail(peg$e33); } } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { @@ -1079,7 +1057,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + if (peg$silentFails === 0) { peg$fail(peg$e33); } } } } else { @@ -1093,7 +1071,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e34); } + if (peg$silentFails === 0) { peg$fail(peg$e32); } } return s0; @@ -1121,7 +1099,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e37); } + if (peg$silentFails === 0) { peg$fail(peg$e35); } } while (s1 !== peg$FAILED) { s0.push(s1); @@ -1130,12 +1108,12 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e37); } + if (peg$silentFails === 0) { peg$fail(peg$e35); } } } peg$silentFails--; s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e36); } + if (peg$silentFails === 0) { peg$fail(peg$e34); } return s0; } diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index bea1e5cfd6ff..f9f681736c61 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -119,9 +119,7 @@ key "key" / "cardID" / "from" / "expenseType" - / "has" / "in" - / "is" ) defaultKey "default key" diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts index 1b1fcaee8682..f488d90cfb89 100644 --- a/src/libs/SearchUtils.ts +++ b/src/libs/SearchUtils.ts @@ -22,7 +22,7 @@ import * as searchParser from './SearchParser/searchParser'; import * as TransactionUtils from './TransactionUtils'; import * as UserUtils from './UserUtils'; -type KeysOfFilterKeysObject = keyof typeof CONST.SEARCH.SYNTAX_FILTER_KEYS; +type FilterKeys = keyof typeof CONST.SEARCH.SYNTAX_FILTER_KEYS; const columnNamesToSortingProperty = { [CONST.SEARCH.TABLE_COLUMNS.TO]: 'formattedTo' as const, @@ -469,11 +469,11 @@ function buildAmountFilterQuery(filterValues: Partial } function sanitizeString(str: string) { - const safeStr = str; - if (safeStr.includes(' ') || safeStr.includes(',')) { - return `"${safeStr}"`; + const regexp = /[<>,:= ]/g; + if (regexp.test(str)) { + return `"${str}"`; } - return safeStr; + return str; } function getExpenseTypeTranslationKey(expenseType: ValueOf): TranslationPaths { @@ -488,46 +488,27 @@ 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'; - } -} - -function getChatStatusTranslationKey(chatStatus: ValueOf): TranslationPaths { - // eslint-disable-next-line default-case - switch (chatStatus) { - case CONST.SEARCH.CHAT_STATUS.PINNED: - return 'search.filters.pinned'; - case CONST.SEARCH.CHAT_STATUS.UNREAD: - return 'search.filters.unread'; - case CONST.SEARCH.CHAT_STATUS.DRAFT: - return 'search.filters.draft'; - } -} - /** * Given object with chosen search filters builds correct query string from them */ function buildQueryStringFromFilters(filterValues: Partial) { const filtersString = Object.entries(filterValues).map(([filterKey, filterValue]) => { if ((filterKey === FILTER_KEYS.MERCHANT || filterKey === FILTER_KEYS.DESCRIPTION || filterKey === FILTER_KEYS.REPORT_ID) && filterValue) { - const keyInCorrectForm = (Object.keys(CONST.SEARCH.SYNTAX_FILTER_KEYS) as KeysOfFilterKeysObject[]).find((key) => CONST.SEARCH.SYNTAX_FILTER_KEYS[key] === filterKey); + const keyInCorrectForm = (Object.keys(CONST.SEARCH.SYNTAX_FILTER_KEYS) as FilterKeys[]).find((key) => CONST.SEARCH.SYNTAX_FILTER_KEYS[key] === filterKey); if (keyInCorrectForm) { return `${CONST.SEARCH.SYNTAX_FILTER_KEYS[keyInCorrectForm]}:${sanitizeString(filterValue as string)}`; } } if (filterKey === FILTER_KEYS.KEYWORD && filterValue) { - const keyInCorrectForm = (Object.keys(CONST.SEARCH.SYNTAX_FILTER_KEYS) as KeysOfFilterKeysObject[]).find((key) => CONST.SEARCH.SYNTAX_FILTER_KEYS[key] === filterKey); - if (keyInCorrectForm) { - return `${filterValue as string}`; - } + const value = (filterValue as string).split(' ').map(sanitizeString).join(' '); + return `${value}`; + } + if (filterKey === FILTER_KEYS.TYPE && filterValue) { + return `${CONST.SEARCH.SYNTAX_ROOT_KEYS.TYPE}:${sanitizeString(filterValue as string)}`; + } + if (filterKey === FILTER_KEYS.STATUS && filterValue) { + return `${CONST.SEARCH.SYNTAX_ROOT_KEYS.STATUS}:${sanitizeString(filterValue as string)}`; } - if ( (filterKey === FILTER_KEYS.CATEGORY || filterKey === FILTER_KEYS.CARD_ID || @@ -537,14 +518,12 @@ function buildQueryStringFromFilters(filterValues: Partial 0 ) { - const filterValueArray = filterValues[filterKey] ?? []; - const keyInCorrectForm = (Object.keys(CONST.SEARCH.SYNTAX_FILTER_KEYS) as KeysOfFilterKeysObject[]).find((key) => CONST.SEARCH.SYNTAX_FILTER_KEYS[key] === filterKey); + const filterValueArray = [...new Set(filterValues[filterKey] ?? [])]; + const keyInCorrectForm = (Object.keys(CONST.SEARCH.SYNTAX_FILTER_KEYS) as FilterKeys[]).find((key) => CONST.SEARCH.SYNTAX_FILTER_KEYS[key] === filterKey); if (keyInCorrectForm) { return `${CONST.SEARCH.SYNTAX_FILTER_KEYS[keyInCorrectForm]}:${filterValueArray.map(sanitizeString).join(',')}`; } @@ -612,6 +591,57 @@ function getFilters(queryJSON: SearchQueryJSON) { return filters; } +/** + * returns the values of the filters in a format that can be used in the SearchAdvancedFiltersForm as initial form values + */ +function getFiltersFormValues(queryJSON: SearchQueryJSON) { + const filters = getFilters(queryJSON); + const filterKeys = Object.keys(filters); + const filtersForm = {} as Partial; + for (const filterKey of filterKeys) { + if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_ID || filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT || filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION) { + filtersForm[filterKey] = filters[filterKey]?.[0]?.value.toString(); + } + if ( + filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY || + filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID || + filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAX_RATE || + filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPENSE_TYPE || + filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG || + filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY || + filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM || + filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.TO || + filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.IN + ) { + filtersForm[filterKey] = filters[filterKey]?.map((filter) => filter.value.toString()); + } + if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD) { + filtersForm[filterKey] = filters[filterKey] + ?.map((filter) => filter.value.toString()) + .map((filter) => { + if (filter.includes(' ')) { + return `"${filter}"`; + } + return filter; + }) + .join(' '); + } + if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE) { + filtersForm[FILTER_KEYS.DATE_BEFORE] = filters[filterKey]?.find((filter) => filter.operator === 'lt')?.value.toString(); + filtersForm[FILTER_KEYS.DATE_AFTER] = filters[filterKey]?.find((filter) => filter.operator === 'gt')?.value.toString(); + } + if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT) { + filtersForm[FILTER_KEYS.LESS_THAN] = filters[filterKey]?.find((filter) => filter.operator === 'lt')?.value.toString(); + filtersForm[FILTER_KEYS.GREATER_THAN] = filters[filterKey]?.find((filter) => filter.operator === 'gt')?.value.toString(); + } + } + + filtersForm[FILTER_KEYS.TYPE] = queryJSON.type; + filtersForm[FILTER_KEYS.STATUS] = queryJSON.status; + + return filtersForm; +} + /** * Given a SearchQueryJSON this function will try to find the value of policyID filter saved in query * and return just the first policyID value from the filter. @@ -680,6 +710,7 @@ export { buildSearchQueryString, getCurrentSearchParams, getFilters, + getFiltersFormValues, getPolicyIDFromSearchQuery, getListItem, getSearchHeaderTitle, @@ -695,6 +726,4 @@ export { buildCannedSearchQuery, isCannedSearchQuery, getExpenseTypeTranslationKey, - getChatFiltersTranslationKey, - getChatStatusTranslationKey, }; diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 020fc1bd2c30..2ca6f7ccf47f 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -1,5 +1,6 @@ import Onyx from 'react-native-onyx'; import type {OnyxUpdate} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import type {FormOnyxValues} from '@components/Form/types'; import type {SearchQueryJSON} from '@components/Search/types'; import * as API from '@libs/API'; @@ -10,6 +11,7 @@ import fileDownload from '@libs/fileDownload'; import enhanceParameters from '@libs/Network/enhanceParameters'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import FILTER_KEYS from '@src/types/form/SearchAdvancedFiltersForm'; import type {SearchTransaction} from '@src/types/onyx/SearchResults'; import * as Report from './Report'; @@ -128,10 +130,21 @@ function updateAdvancedFilters(values: Partial, null>> = {}; + Object.values(FILTER_KEYS) + .filter((key) => key !== FILTER_KEYS.TYPE && key !== FILTER_KEYS.STATUS) + .forEach((key) => { + values[key] = null; + }); + + Onyx.merge(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, values); +} + export { search, createTransactionThread, @@ -140,5 +153,6 @@ export { unholdMoneyRequestOnSearch, exportSearchItemsToCSV, updateAdvancedFilters, + clearAllFilters, clearAdvancedFilters, }; diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 7775825f33de..aa0f6ce85802 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -1,4 +1,3 @@ -import {Str} from 'expensify-common'; import React from 'react'; import {View} from 'react-native'; import type {ValueOf} from 'react-native-gesture-handler/lib/typescript/typeUtils'; @@ -98,28 +97,18 @@ const baseFilterConfig = { 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, - }, in: { getTitle: getFilterInDisplayTitle, description: 'common.in' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_IN, }, - is: { - getTitle: getFilterIsDisplayTitle, - description: 'search.filters.is' as const, - route: ROUTES.SEARCH_ADVANCED_FILTERS_IS, - }, }; 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', 'in', 'is'], + [CONST.SEARCH.DATA_TYPES.CHAT]: ['date', 'keyword', 'from', 'in'], }; function getFilterCardDisplayTitle(filters: Partial, cards: CardList) { @@ -175,6 +164,8 @@ function getFilterDisplayTitle(filters: Partial, fiel if (greaterThan) { return translate('search.filters.amount.greaterThan', convertToDisplayStringWithoutCurrency(Number(greaterThan))); } + // Will never happen + return; } if ( @@ -189,11 +180,8 @@ function getFilterDisplayTitle(filters: Partial, fiel return filters[fieldName]; } - // Todo Once all Advanced filters are implemented this line can be cleaned up. See: https://github.com/Expensify/App/issues/45026 - // @ts-expect-error this property access is temporarily an error, because not every SYNTAX_FILTER_KEYS is handled by form. - // When all filters are updated here: src/types/form/SearchAdvancedFiltersForm.ts this line comment + type cast can be removed. - const filterValue = filters[fieldName] as string; - return filterValue ? Str.recapitalize(filterValue) : undefined; + const filterValue = filters[fieldName]; + return Array.isArray(filterValue) ? filterValue.join(', ') : filterValue; } function getFilterTaxRateDisplayTitle(filters: Partial, taxRates: Record) { @@ -226,27 +214,6 @@ function getFilterExpenseDisplayTitle(filters: Partial, translate: LocaleContextProps['translate'], reports?: OnyxCollection) { return filters.in ? filters.in.map((id) => ReportUtils.getReportName(reports?.[`${ONYXKEYS.COLLECTION.REPORT}${id}`])).join(', ') : 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 getFilterIsDisplayTitle(filters: Partial, translate: LocaleContextProps['translate']) { - const filterValue = filters[CONST.SEARCH.SYNTAX_FILTER_KEYS.IS]; - return filterValue - ? Object.values(CONST.SEARCH.CHAT_STATUS) - .filter((chatStatus) => filterValue.includes(chatStatus)) - .map((chatStatus) => translate(SearchUtils.getChatStatusTranslationKey(chatStatus))) - .join(', ') - : undefined; -} - function AdvancedSearchFilters() { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -262,7 +229,7 @@ function AdvancedSearchFilters() { const onFormSubmit = () => { const query = SearchUtils.buildQueryStringFromFilters(searchAdvancedFilters); - SearchActions.clearAdvancedFilters(); + SearchActions.clearAllFilters(); Navigation.dismissModal(); Navigation.navigate( ROUTES.SEARCH_CENTRAL_PANE.getRoute({ @@ -293,7 +260,7 @@ function AdvancedSearchFilters() { 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 || key === CONST.SEARCH.SYNTAX_FILTER_KEYS.IS) { + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPENSE_TYPE) { 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); diff --git a/src/pages/Search/SearchAdvancedFiltersPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage.tsx index 9c205de2433b..687e04c7341b 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage.tsx @@ -6,6 +6,7 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as SearchActions from '@userActions/Search'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; import AdvancedSearchFilters from './AdvancedSearchFilters'; @@ -17,7 +18,9 @@ function SearchAdvancedFiltersPage() { const emptySearchFilters: SearchAdvancedFiltersForm = {} as SearchAdvancedFiltersForm; const [searchAdvancedFilters = emptySearchFilters] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); - const shouldShowResetFilters = Object.values(searchAdvancedFilters).some((value) => (Array.isArray(value) ? value.length !== 0 : !!value)); + const shouldShowResetFilters = Object.entries(searchAdvancedFilters) + .filter(([key]) => CONST.SEARCH.SYNTAX_ROOT_KEYS.TYPE !== key && CONST.SEARCH.SYNTAX_ROOT_KEYS.STATUS !== key) + .some(([, value]) => (Array.isArray(value) ? value.length !== 0 : !!value)); return ( [ - { - 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/pages/Search/SearchAdvancedFiltersPage/SearchFiltersIsPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersIsPage.tsx deleted file mode 100644 index d2431776eb44..000000000000 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersIsPage.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, {useCallback, useMemo} from 'react'; -import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; -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 {getChatStatusTranslationKey} from '@libs/SearchUtils'; -import * as SearchActions from '@userActions/Search'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; - -function SearchFiltersIsPage() { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - - const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); - const selectedChatStatuses = searchAdvancedFiltersForm?.is?.map((chatStatus) => { - const chatStatusName = translate(getChatStatusTranslationKey(chatStatus as ValueOf)); - return {name: chatStatusName, value: chatStatus}; - }); - const allChatStatuses = Object.values(CONST.SEARCH.CHAT_STATUS); - - const chatStatusItems = useMemo(() => { - return allChatStatuses.map((chatStatus) => { - const chatStatusName = translate(getChatStatusTranslationKey(chatStatus)); - return {name: chatStatusName, value: chatStatus}; - }); - }, [allChatStatuses, translate]); - - const updateChatIsFilter = useCallback((values: string[]) => SearchActions.updateAdvancedFilters({is: values}), []); - - return ( - - { - Navigation.goBack(ROUTES.SEARCH_ADVANCED_FILTERS); - }} - /> - - - - - ); -} - -SearchFiltersIsPage.displayName = 'SearchFiltersIsPage'; - -export default SearchFiltersIsPage; diff --git a/src/pages/Search/SearchTypeMenu.tsx b/src/pages/Search/SearchTypeMenu.tsx index a83fd364fa4a..84373b0d4964 100644 --- a/src/pages/Search/SearchTypeMenu.tsx +++ b/src/pages/Search/SearchTypeMenu.tsx @@ -6,6 +6,7 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSingleExecution from '@hooks/useSingleExecution'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as SearchActions from '@libs/actions/Search'; import Navigation from '@libs/Navigation/Navigation'; import * as SearchUtils from '@libs/SearchUtils'; import variables from '@styles/variables'; @@ -72,6 +73,7 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { ); @@ -80,7 +82,10 @@ function SearchTypeMenu({queryJSON}: SearchTypeMenuProps) { return ( {typeMenuItems.map((item, index) => { - const onPress = singleExecution(() => Navigation.navigate(item.route)); + const onPress = singleExecution(() => { + SearchActions.clearAllFilters(); + Navigation.navigate(item.route); + }); return ( setIsPopoverVisible(true); const closeMenu = () => setIsPopoverVisible(false); + const onPress = () => { + const values = SearchUtils.getFiltersFormValues(queryJSON); + SearchActions.updateAdvancedFilters(values); + Navigation.navigate(ROUTES.SEARCH_ADVANCED_FILTERS); + }; const popoverMenuItems = typeMenuItems.map((item, index) => { const isSelected = title ? false : index === activeItemIndex; return { text: item.title, - onSelected: singleExecution(() => Navigation.navigate(item.route)), + onSelected: singleExecution(() => { + SearchActions.clearAllFilters(); + Navigation.navigate(item.route); + }), icon: item.icon, iconFill: isSelected ? theme.iconSuccessFill : theme.icon, iconRight: Expensicons.Checkmark, @@ -96,7 +108,7 @@ function SearchTypeMenuNarrow({typeMenuItems, activeItemIndex, title}: SearchTyp