diff --git a/src/CONST.ts b/src/CONST.ts index c28914541113..1237d28f563f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -6071,6 +6071,11 @@ const CONST = { REPORT_ID: 'reportID', KEYWORD: 'keyword', IN: 'in', + SUBMITTED: 'submitted', + APPROVED: 'approved', + PAID: 'paid', + EXPORTED: 'exported', + POSTED: 'posted', }, EMPTY_VALUE: 'none', SEARCH_ROUTER_ITEM_TYPE: { @@ -6078,6 +6083,10 @@ const CONST = { AUTOCOMPLETE_SUGGESTION: 'autocompleteSuggestion', SEARCH: 'searchItem', }, + DATE_MODIFIERS: { + BEFORE: 'Before', + AFTER: 'After', + }, }, REFERRER: { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index a6eb3c1166df..4ffd576243a6 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -58,6 +58,11 @@ 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_SUBMITTED: 'search/filters/submitted', + SEARCH_ADVANCED_FILTERS_APPROVED: 'search/filters/approved', + SEARCH_ADVANCED_FILTERS_PAID: 'search/filters/paid', + SEARCH_ADVANCED_FILTERS_EXPORTED: 'search/filters/exported', + SEARCH_ADVANCED_FILTERS_POSTED: 'search/filters/posted', SEARCH_REPORT: { route: 'search/view/:reportID/:reportActionID?', getRoute: ({reportID, reportActionID, backTo}: {reportID: string; reportActionID?: string; backTo?: string}) => { diff --git a/src/SCREENS.ts b/src/SCREENS.ts index e4fa03bf4815..4d360eaf0ac9 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -33,6 +33,11 @@ const SCREENS = { REPORT_RHP: 'Search_Report_RHP', ADVANCED_FILTERS_RHP: 'Search_Advanced_Filters_RHP', ADVANCED_FILTERS_DATE_RHP: 'Search_Advanced_Filters_Date_RHP', + ADVANCED_FILTERS_SUBMITTED_RHP: 'Search_Advanced_Filters_Submitted_RHP', + ADVANCED_FILTERS_APPROVED_RHP: 'Search_Advanced_Filters_Approved_RHP', + ADVANCED_FILTERS_PAID_RHP: 'Search_Advanced_Filters_Paid_RHP', + ADVANCED_FILTERS_EXPORTED_RHP: 'Search_Advanced_Filters_Exported_RHP', + ADVANCED_FILTERS_POSTED_RHP: 'Search_Advanced_Filters_Posted_RHP', ADVANCED_FILTERS_CURRENCY_RHP: 'Search_Advanced_Filters_Currency_RHP', ADVANCED_FILTERS_DESCRIPTION_RHP: 'Search_Advanced_Filters_Description_RHP', ADVANCED_FILTERS_MERCHANT_RHP: 'Search_Advanced_Filters_Merchant_RHP', diff --git a/src/components/Search/SearchDateFilterBase.tsx b/src/components/Search/SearchDateFilterBase.tsx new file mode 100644 index 000000000000..483a1ccc0c9f --- /dev/null +++ b/src/components/Search/SearchDateFilterBase.tsx @@ -0,0 +1,87 @@ +import {format} from 'date-fns'; +import React from 'react'; +import {useOnyx} from 'react-native-onyx'; +import DatePicker from '@components/DatePicker'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {updateAdvancedFilters} from '@libs/actions/Search'; +import Navigation from '@libs/Navigation/Navigation'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type {SearchDateFilterKeys} from './types'; + +type SearchDateFilterBaseProps = { + /** Key used for the date filter */ + dateKey: SearchDateFilterKeys; + + /** The translation key for the page title */ + titleKey: TranslationPaths; +}; + +function SearchDateFilterBase({dateKey, titleKey}: SearchDateFilterBaseProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); + const unformattedDateAfter = searchAdvancedFiltersForm?.[`${dateKey}${CONST.SEARCH.DATE_MODIFIERS.AFTER}`]; + const unformattedDateBefore = searchAdvancedFiltersForm?.[`${dateKey}${CONST.SEARCH.DATE_MODIFIERS.BEFORE}`]; + const dateAfter = unformattedDateAfter ? format(unformattedDateAfter, 'yyyy-MM-dd') : undefined; + const dateBefore = unformattedDateBefore ? format(unformattedDateBefore, 'yyyy-MM-dd') : undefined; + + const updateDateFilter = (values: FormOnyxValues) => { + updateAdvancedFilters(values); + Navigation.goBack(ROUTES.SEARCH_ADVANCED_FILTERS); + }; + + return ( + + { + Navigation.goBack(ROUTES.SEARCH_ADVANCED_FILTERS); + }} + /> + + + + + + ); +} + +SearchDateFilterBase.displayName = 'SearchDateFilterBase'; + +export default SearchDateFilterBase; diff --git a/src/components/Search/types.ts b/src/components/Search/types.ts index af72b7f6bcc1..b9579ed914ad 100644 --- a/src/components/Search/types.ts +++ b/src/components/Search/types.ts @@ -83,6 +83,14 @@ type QueryFilter = { value: string | number; }; +type SearchDateFilterKeys = + | typeof CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE + | typeof CONST.SEARCH.SYNTAX_FILTER_KEYS.SUBMITTED + | typeof CONST.SEARCH.SYNTAX_FILTER_KEYS.APPROVED + | typeof CONST.SEARCH.SYNTAX_FILTER_KEYS.PAID + | typeof CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED + | typeof CONST.SEARCH.SYNTAX_FILTER_KEYS.POSTED; + type SearchFilterKey = | ValueOf | typeof CONST.SEARCH.SYNTAX_ROOT_KEYS.TYPE @@ -129,6 +137,7 @@ export type { SelectedTransactionInfo, SelectedTransactions, SearchColumnType, + SearchDateFilterKeys, SearchStatus, SearchQueryAST, SearchQueryJSON, diff --git a/src/languages/en.ts b/src/languages/en.ts index d79695ed8b48..2d708dafc4a6 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4577,6 +4577,11 @@ const translations = { }, current: 'Current', past: 'Past', + submitted: 'Submitted', + approved: 'Approved', + paid: 'Paid', + exported: 'Exported', + posted: 'Posted', }, noCategory: 'No category', noTag: 'No tag', diff --git a/src/languages/es.ts b/src/languages/es.ts index 5ce47db18d35..c5e3cb2abcc4 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -4626,6 +4626,11 @@ const translations = { }, current: 'Actual', past: 'Anterior', + submitted: 'Enviado', + approved: 'Aprobado', + paid: 'Pagado', + exported: 'Exportado', + posted: 'Contabilizado', }, noCategory: 'Sin categoría', noTag: 'Sin etiqueta', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 9822c60faaa8..edc32bb705b6 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -635,6 +635,11 @@ const SearchReportModalStackNavigator = createModalStackNavigator({ [SCREENS.SEARCH.ADVANCED_FILTERS_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersDatePage').default, + [SCREENS.SEARCH.ADVANCED_FILTERS_SUBMITTED_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersSubmittedPage').default, + [SCREENS.SEARCH.ADVANCED_FILTERS_APPROVED_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersApprovedPage').default, + [SCREENS.SEARCH.ADVANCED_FILTERS_PAID_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersPaidPage').default, + [SCREENS.SEARCH.ADVANCED_FILTERS_EXPORTED_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersExportedPage').default, + [SCREENS.SEARCH.ADVANCED_FILTERS_POSTED_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersPostedPage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_CURRENCY_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersCurrencyPage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_DESCRIPTION_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersDescriptionPage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_MERCHANT_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage').default, 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 16a4b2613d8f..96d3d8b74080 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -56,6 +56,11 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = SCREENS.SEARCH.ADVANCED_FILTERS_RHP, SCREENS.SEARCH.ADVANCED_FILTERS_CURRENCY_RHP, SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP, + SCREENS.SEARCH.ADVANCED_FILTERS_SUBMITTED_RHP, + SCREENS.SEARCH.ADVANCED_FILTERS_APPROVED_RHP, + SCREENS.SEARCH.ADVANCED_FILTERS_PAID_RHP, + SCREENS.SEARCH.ADVANCED_FILTERS_EXPORTED_RHP, + SCREENS.SEARCH.ADVANCED_FILTERS_POSTED_RHP, SCREENS.SEARCH.ADVANCED_FILTERS_DESCRIPTION_RHP, SCREENS.SEARCH.ADVANCED_FILTERS_MERCHANT_RHP, SCREENS.SEARCH.ADVANCED_FILTERS_REPORT_ID_RHP, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 02f7f6950a0d..5091773c92bb 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1339,6 +1339,11 @@ const config: LinkingOptions['config'] = { screens: { [SCREENS.SEARCH.ADVANCED_FILTERS_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS, [SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, + [SCREENS.SEARCH.ADVANCED_FILTERS_SUBMITTED_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_SUBMITTED, + [SCREENS.SEARCH.ADVANCED_FILTERS_APPROVED_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_APPROVED, + [SCREENS.SEARCH.ADVANCED_FILTERS_PAID_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_PAID, + [SCREENS.SEARCH.ADVANCED_FILTERS_EXPORTED_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_EXPORTED, + [SCREENS.SEARCH.ADVANCED_FILTERS_POSTED_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_POSTED, [SCREENS.SEARCH.ADVANCED_FILTERS_CURRENCY_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_CURRENCY, [SCREENS.SEARCH.ADVANCED_FILTERS_MERCHANT_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_MERCHANT, [SCREENS.SEARCH.ADVANCED_FILTERS_DESCRIPTION_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_DESCRIPTION, diff --git a/src/libs/SearchParser/searchParser.js b/src/libs/SearchParser/searchParser.js index c94222f3a6e9..e7b087a969a8 100644 --- a/src/libs/SearchParser/searchParser.js +++ b/src/libs/SearchParser/searchParser.js @@ -195,18 +195,23 @@ function peg$parse(input, options) { var peg$c12 = "cardID"; var peg$c13 = "from"; var peg$c14 = "expenseType"; - var peg$c15 = "type"; - var peg$c16 = "status"; - var peg$c17 = "sortBy"; - var peg$c18 = "sortOrder"; - var peg$c19 = "policyID"; - var peg$c20 = ","; - var peg$c21 = "!="; - var peg$c22 = ">="; - var peg$c23 = ">"; - var peg$c24 = "<="; - var peg$c25 = "<"; - var peg$c26 = "\""; + var peg$c15 = "submitted"; + var peg$c16 = "approved"; + var peg$c17 = "paid"; + var peg$c18 = "exported"; + var peg$c19 = "posted"; + 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$c26 = "!="; + var peg$c27 = ">="; + var peg$c28 = ">"; + var peg$c29 = "<="; + var peg$c30 = "<"; + var peg$c31 = "\""; var peg$r0 = /^[^ \t\r\n]/; var peg$r1 = /^[:=]/; @@ -232,28 +237,33 @@ function peg$parse(input, options) { var peg$e14 = peg$literalExpectation("cardID", false); var peg$e15 = peg$literalExpectation("from", false); var peg$e16 = peg$literalExpectation("expenseType", false); - var peg$e17 = peg$otherExpectation("default key"); - var peg$e18 = peg$literalExpectation("type", false); - var peg$e19 = peg$literalExpectation("status", false); - var peg$e20 = peg$literalExpectation("sortBy", false); - var peg$e21 = peg$literalExpectation("sortOrder", false); - var peg$e22 = peg$literalExpectation("policyID", false); - var peg$e23 = peg$literalExpectation(",", false); - var peg$e24 = peg$otherExpectation("operator"); - var peg$e25 = peg$classExpectation([":", "="], false, false); - var peg$e26 = peg$literalExpectation("!=", false); - var peg$e27 = peg$literalExpectation(">=", false); - var peg$e28 = peg$literalExpectation(">", false); - var peg$e29 = peg$literalExpectation("<=", false); - var peg$e30 = peg$literalExpectation("<", false); - var peg$e31 = peg$otherExpectation("quote"); - var peg$e32 = peg$classExpectation([" ", ",", "\"", "\t", "\n", "\r"], true, false); - var peg$e33 = peg$literalExpectation("\"", false); - var peg$e34 = peg$classExpectation(["\"", "\r", "\n"], true, false); - var peg$e35 = peg$classExpectation([" ", ",", "\t", "\n", "\r"], true, false); - var peg$e36 = peg$otherExpectation("word"); - var peg$e37 = peg$otherExpectation("whitespace"); - var peg$e38 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); + var peg$e17 = peg$literalExpectation("submitted", false); + var peg$e18 = peg$literalExpectation("approved", false); + var peg$e19 = peg$literalExpectation("paid", false); + var peg$e20 = peg$literalExpectation("exported", false); + var peg$e21 = peg$literalExpectation("posted", false); + var peg$e22 = peg$otherExpectation("default key"); + var peg$e23 = peg$literalExpectation("type", false); + var peg$e24 = peg$literalExpectation("status", false); + var peg$e25 = peg$literalExpectation("sortBy", false); + var peg$e26 = peg$literalExpectation("sortOrder", false); + var peg$e27 = peg$literalExpectation("policyID", false); + var peg$e28 = peg$literalExpectation(",", false); + var peg$e29 = peg$otherExpectation("operator"); + var peg$e30 = peg$classExpectation([":", "="], false, false); + var peg$e31 = peg$literalExpectation("!=", false); + var peg$e32 = peg$literalExpectation(">=", false); + var peg$e33 = peg$literalExpectation(">", false); + var peg$e34 = peg$literalExpectation("<=", false); + var peg$e35 = peg$literalExpectation("<", false); + var peg$e36 = peg$otherExpectation("quote"); + var peg$e37 = peg$classExpectation([" ", ",", "\"", "\t", "\n", "\r"], true, false); + var peg$e38 = peg$literalExpectation("\"", false); + var peg$e39 = peg$classExpectation(["\"", "\r", "\n"], true, false); + var peg$e40 = peg$classExpectation([" ", ",", "\t", "\n", "\r"], true, false); + var peg$e41 = peg$otherExpectation("word"); + var peg$e42 = peg$otherExpectation("whitespace"); + var peg$e43 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); var peg$f0 = function(filters) { return applyDefaults(filters); }; var peg$f1 = function(head, tail) { @@ -787,6 +797,51 @@ function peg$parse(input, options) { s1 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e16); } } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 9) === peg$c15) { + s1 = peg$c15; + peg$currPos += 9; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e17); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c16) { + s1 = peg$c16; + peg$currPos += 8; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e18); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c17) { + s1 = peg$c17; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e19); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c18) { + s1 = peg$c18; + peg$currPos += 8; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e20); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 6) === peg$c19) { + s1 = peg$c19; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e21); } + } + } + } + } + } + } } } } @@ -821,44 +876,44 @@ function peg$parse(input, options) { peg$silentFails++; s0 = peg$currPos; - if (input.substr(peg$currPos, 4) === peg$c15) { - s1 = peg$c15; + 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$e18); } + if (peg$silentFails === 0) { peg$fail(peg$e23); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 6) === peg$c16) { - s1 = peg$c16; + 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$e19); } + if (peg$silentFails === 0) { peg$fail(peg$e24); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 6) === peg$c17) { - s1 = peg$c17; + 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$e20); } + if (peg$silentFails === 0) { peg$fail(peg$e25); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 9) === peg$c18) { - s1 = peg$c18; + 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$e21); } + if (peg$silentFails === 0) { peg$fail(peg$e26); } } if (s1 === peg$FAILED) { - if (input.substr(peg$currPos, 8) === peg$c19) { - s1 = peg$c19; + 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$e22); } + if (peg$silentFails === 0) { peg$fail(peg$e27); } } } } @@ -873,7 +928,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e17); } + if (peg$silentFails === 0) { peg$fail(peg$e22); } } return s0; @@ -885,21 +940,21 @@ function peg$parse(input, options) { s0 = peg$currPos; s1 = []; if (input.charCodeAt(peg$currPos) === 44) { - s2 = peg$c20; + s2 = peg$c25; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e23); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { s1.push(s2); if (input.charCodeAt(peg$currPos) === 44) { - s2 = peg$c20; + s2 = peg$c25; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e23); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } } } else { @@ -919,21 +974,21 @@ function peg$parse(input, options) { s4 = peg$currPos; s5 = []; if (input.charCodeAt(peg$currPos) === 44) { - s6 = peg$c20; + s6 = peg$c25; peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e23); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } if (s6 !== peg$FAILED) { while (s6 !== peg$FAILED) { s5.push(s6); if (input.charCodeAt(peg$currPos) === 44) { - s6 = peg$c20; + s6 = peg$c25; peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e23); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } } } else { @@ -963,21 +1018,21 @@ function peg$parse(input, options) { if (s2 !== peg$FAILED) { s3 = []; if (input.charCodeAt(peg$currPos) === 44) { - s4 = peg$c20; + s4 = peg$c25; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e23); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } if (s4 !== peg$FAILED) { while (s4 !== peg$FAILED) { s3.push(s4); if (input.charCodeAt(peg$currPos) === 44) { - s4 = peg$c20; + s4 = peg$c25; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e23); } + if (peg$silentFails === 0) { peg$fail(peg$e28); } } } } else { @@ -1006,7 +1061,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e25); } + if (peg$silentFails === 0) { peg$fail(peg$e30); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; @@ -1015,12 +1070,12 @@ function peg$parse(input, options) { s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c21) { - s1 = peg$c21; + if (input.substr(peg$currPos, 2) === peg$c26) { + s1 = peg$c26; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e26); } + if (peg$silentFails === 0) { peg$fail(peg$e31); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; @@ -1029,12 +1084,12 @@ function peg$parse(input, options) { s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c22) { - s1 = peg$c22; + if (input.substr(peg$currPos, 2) === peg$c27) { + s1 = peg$c27; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e27); } + if (peg$silentFails === 0) { peg$fail(peg$e32); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; @@ -1044,11 +1099,11 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 62) { - s1 = peg$c23; + s1 = peg$c28; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e28); } + if (peg$silentFails === 0) { peg$fail(peg$e33); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; @@ -1057,12 +1112,12 @@ function peg$parse(input, options) { s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c24) { - s1 = peg$c24; + if (input.substr(peg$currPos, 2) === peg$c29) { + s1 = peg$c29; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e29); } + if (peg$silentFails === 0) { peg$fail(peg$e34); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; @@ -1072,11 +1127,11 @@ function peg$parse(input, options) { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 60) { - s1 = peg$c25; + s1 = peg$c30; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e30); } + if (peg$silentFails === 0) { peg$fail(peg$e35); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; @@ -1091,7 +1146,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e24); } + if (peg$silentFails === 0) { peg$fail(peg$e29); } } return s0; @@ -1108,7 +1163,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e32); } + if (peg$silentFails === 0) { peg$fail(peg$e37); } } while (s2 !== peg$FAILED) { s1.push(s2); @@ -1117,15 +1172,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e32); } + if (peg$silentFails === 0) { peg$fail(peg$e37); } } } if (input.charCodeAt(peg$currPos) === 34) { - s2 = peg$c26; + s2 = peg$c31; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e33); } + if (peg$silentFails === 0) { peg$fail(peg$e38); } } if (s2 !== peg$FAILED) { s3 = []; @@ -1134,7 +1189,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e34); } + if (peg$silentFails === 0) { peg$fail(peg$e39); } } while (s4 !== peg$FAILED) { s3.push(s4); @@ -1143,15 +1198,15 @@ function peg$parse(input, options) { peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e34); } + if (peg$silentFails === 0) { peg$fail(peg$e39); } } } if (input.charCodeAt(peg$currPos) === 34) { - s4 = peg$c26; + s4 = peg$c31; peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e33); } + if (peg$silentFails === 0) { peg$fail(peg$e38); } } if (s4 !== peg$FAILED) { s5 = []; @@ -1160,7 +1215,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + if (peg$silentFails === 0) { peg$fail(peg$e40); } } while (s6 !== peg$FAILED) { s5.push(s6); @@ -1169,7 +1224,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e35); } + if (peg$silentFails === 0) { peg$fail(peg$e40); } } } peg$savedPos = s0; @@ -1185,7 +1240,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$e36); } } return s0; @@ -1202,7 +1257,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$e40); } } if (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) { @@ -1212,7 +1267,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$e40); } } } } else { @@ -1226,7 +1281,7 @@ function peg$parse(input, options) { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e36); } + if (peg$silentFails === 0) { peg$fail(peg$e41); } } return s0; @@ -1254,7 +1309,7 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e38); } + if (peg$silentFails === 0) { peg$fail(peg$e43); } } while (s1 !== peg$FAILED) { s0.push(s1); @@ -1263,12 +1318,12 @@ function peg$parse(input, options) { peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e38); } + if (peg$silentFails === 0) { peg$fail(peg$e43); } } } peg$silentFails--; s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e37); } + if (peg$silentFails === 0) { peg$fail(peg$e42); } return s0; } diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index d3aaf5ddf97b..bf2d1fe36154 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -113,6 +113,11 @@ key "key" / "cardID" / "from" / "expenseType" + / "submitted" + / "approved" + / "paid" + / "exported" + / "posted" ) defaultKey "default key" diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 0f1354414ee0..dac2f35d27ea 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -1,11 +1,11 @@ import cloneDeep from 'lodash/cloneDeep'; import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import type {ASTNode, QueryFilter, QueryFilters, SearchQueryJSON, SearchQueryString, SearchStatus} from '@components/Search/types'; +import type {ASTNode, QueryFilter, QueryFilters, SearchDateFilterKeys, SearchQueryJSON, SearchQueryString, SearchStatus} from '@components/Search/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; -import FILTER_KEYS from '@src/types/form/SearchAdvancedFiltersForm'; +import FILTER_KEYS, {DATE_FILTER_KEYS} from '@src/types/form/SearchAdvancedFiltersForm'; import type * as OnyxTypes from '@src/types/onyx'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import * as CardUtils from './CardUtils'; @@ -50,19 +50,19 @@ function sanitizeSearchValue(str: string) { * @private * Returns date filter value for QueryString. */ -function buildDateFilterQuery(filterValues: Partial) { - const dateBefore = filterValues[FILTER_KEYS.DATE_BEFORE]; - const dateAfter = filterValues[FILTER_KEYS.DATE_AFTER]; +function buildDateFilterQuery(filterValues: Partial, filterKey: SearchDateFilterKeys) { + const dateBefore = filterValues[`${filterKey}${CONST.SEARCH.DATE_MODIFIERS.BEFORE}`]; + const dateAfter = filterValues[`${filterKey}${CONST.SEARCH.DATE_MODIFIERS.AFTER}`]; let dateFilter = ''; if (dateBefore) { - dateFilter += `${CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE}<${dateBefore}`; + dateFilter += `${filterKey}<${dateBefore}`; } if (dateBefore && dateAfter) { dateFilter += ' '; } if (dateAfter) { - dateFilter += `${CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE}>${dateAfter}`; + dateFilter += `${filterKey}>${dateAfter}`; } return dateFilter; @@ -354,8 +354,10 @@ function buildQueryStringFromFilterFormValues(filterValues: Partial { + const dateFilter = buildDateFilterQuery(filterValues, dateKey); + filtersString.push(dateFilter); + }); const amountFilter = buildAmountFilterQuery(filterValues); filtersString.push(amountFilter); @@ -441,11 +443,12 @@ function buildFilterFormValuesFromQuery( }) .join(' '); } - if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE) { - filtersForm[FILTER_KEYS.DATE_BEFORE] = - filterList.find((filter) => filter.operator === 'lt' && ValidationUtils.isValidDate(filter.value.toString()))?.value.toString() ?? filtersForm[FILTER_KEYS.DATE_BEFORE]; - filtersForm[FILTER_KEYS.DATE_AFTER] = - filterList.find((filter) => filter.operator === 'gt' && ValidationUtils.isValidDate(filter.value.toString()))?.value.toString() ?? filtersForm[FILTER_KEYS.DATE_AFTER]; + if (DATE_FILTER_KEYS.includes(filterKey as SearchDateFilterKeys)) { + const beforeKey = `${filterKey}${CONST.SEARCH.DATE_MODIFIERS.BEFORE}` as `${SearchDateFilterKeys}${typeof CONST.SEARCH.DATE_MODIFIERS.BEFORE}`; + const afterKey = `${filterKey}${CONST.SEARCH.DATE_MODIFIERS.AFTER}` as `${SearchDateFilterKeys}${typeof CONST.SEARCH.DATE_MODIFIERS.AFTER}`; + filtersForm[beforeKey] = + filterList.find((filter) => filter.operator === 'lt' && ValidationUtils.isValidDate(filter.value.toString()))?.value.toString() ?? filtersForm[beforeKey]; + filtersForm[afterKey] = filterList.find((filter) => filter.operator === 'gt' && ValidationUtils.isValidDate(filter.value.toString()))?.value.toString() ?? filtersForm[afterKey]; } if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT) { // backend amount is an integer and is 2 digits longer than frontend amount diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 60c01e6f75f0..b958789f43a8 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -9,7 +9,8 @@ import type {LocaleContextProps} from '@components/LocaleContextProvider'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import {usePersonalDetails} from '@components/OnyxProvider'; import ScrollView from '@components/ScrollView'; -import type {SearchFilterKey} from '@components/Search/types'; +import type {SearchDateFilterKeys, SearchFilterKey} from '@components/Search/types'; +import SpacerView from '@components/SpacerView'; import useLocalize from '@hooks/useLocalize'; import useSingleExecution from '@hooks/useSingleExecution'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -27,6 +28,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; +import {DATE_FILTER_KEYS} from '@src/types/form/SearchAdvancedFiltersForm'; import type {CardList, PersonalDetailsList, Policy, PolicyTagLists, Report} from '@src/types/onyx'; import type {PolicyFeatureName} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -37,6 +39,31 @@ const baseFilterConfig = { description: 'common.date' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, }, + submitted: { + getTitle: getFilterDisplayTitle, + description: 'search.filters.submitted' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_SUBMITTED, + }, + approved: { + getTitle: getFilterDisplayTitle, + description: 'search.filters.approved' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_APPROVED, + }, + paid: { + getTitle: getFilterDisplayTitle, + description: 'search.filters.paid' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_PAID, + }, + exported: { + getTitle: getFilterDisplayTitle, + description: 'search.filters.exported' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_EXPORTED, + }, + posted: { + getTitle: getFilterDisplayTitle, + description: 'search.filters.posted' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_POSTED, + }, currency: { getTitle: getFilterDisplayTitle, description: 'common.currency' as const, @@ -109,11 +136,79 @@ const baseFilterConfig = { }, }; -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', 'in'], +/** + * typeFiltersKeys is stored as an object keyed by the different search types. + * Each value is then an array of arrays where each inner array is a separate section in the UI. + */ +const typeFiltersKeys: Record>>> = { + [CONST.SEARCH.DATA_TYPES.EXPENSE]: [ + [CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD, CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM, CONST.SEARCH.SYNTAX_FILTER_KEYS.TO], + [ + CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPENSE_TYPE, + CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT, + CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, + CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT, + CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY, + CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY, + CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG, + CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION, + CONST.SEARCH.SYNTAX_FILTER_KEYS.TAX_RATE, + ], + [CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID, CONST.SEARCH.SYNTAX_FILTER_KEYS.POSTED], + [ + CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_ID, + CONST.SEARCH.SYNTAX_FILTER_KEYS.SUBMITTED, + CONST.SEARCH.SYNTAX_FILTER_KEYS.APPROVED, + CONST.SEARCH.SYNTAX_FILTER_KEYS.PAID, + CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED, + ], + ], + [CONST.SEARCH.DATA_TYPES.INVOICE]: [ + [CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD, CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM, CONST.SEARCH.SYNTAX_FILTER_KEYS.TO], + [ + CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT, + CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, + CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT, + CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY, + CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY, + CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG, + CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION, + CONST.SEARCH.SYNTAX_FILTER_KEYS.TAX_RATE, + ], + [CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID, CONST.SEARCH.SYNTAX_FILTER_KEYS.POSTED], + [ + CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_ID, + CONST.SEARCH.SYNTAX_FILTER_KEYS.SUBMITTED, + CONST.SEARCH.SYNTAX_FILTER_KEYS.APPROVED, + CONST.SEARCH.SYNTAX_FILTER_KEYS.PAID, + CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED, + ], + ], + [CONST.SEARCH.DATA_TYPES.TRIP]: [ + [CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD, CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM, CONST.SEARCH.SYNTAX_FILTER_KEYS.TO], + [ + CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT, + CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, + CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT, + CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY, + CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY, + CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG, + CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION, + CONST.SEARCH.SYNTAX_FILTER_KEYS.TAX_RATE, + ], + [CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID, CONST.SEARCH.SYNTAX_FILTER_KEYS.POSTED], + [ + CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_ID, + CONST.SEARCH.SYNTAX_FILTER_KEYS.SUBMITTED, + CONST.SEARCH.SYNTAX_FILTER_KEYS.APPROVED, + CONST.SEARCH.SYNTAX_FILTER_KEYS.PAID, + CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED, + ], + ], + [CONST.SEARCH.DATA_TYPES.CHAT]: [ + [CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD, CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM, CONST.SEARCH.SYNTAX_FILTER_KEYS.TO], + [CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE], + ], }; function getFilterCardDisplayTitle(filters: Partial, cards: CardList) { @@ -153,9 +248,12 @@ const sortOptionsWithEmptyValue = (a: string, b: string) => { }; function getFilterDisplayTitle(filters: Partial, filterKey: SearchFilterKey, translate: LocaleContextProps['translate']) { - if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE) { + if (DATE_FILTER_KEYS.includes(filterKey as SearchDateFilterKeys)) { // the value of date filter is a combination of dateBefore + dateAfter values - const {dateAfter, dateBefore} = filters; + const keyBefore = `${filterKey}${CONST.SEARCH.DATE_MODIFIERS.BEFORE}` as `${SearchDateFilterKeys}${typeof CONST.SEARCH.DATE_MODIFIERS.BEFORE}`; + const keyAfter = `${filterKey}${CONST.SEARCH.DATE_MODIFIERS.AFTER}` as `${SearchDateFilterKeys}${typeof CONST.SEARCH.DATE_MODIFIERS.AFTER}`; + const dateBefore = filters[keyBefore]; + const dateAfter = filters[keyAfter]; let dateValue = ''; if (dateBefore) { dateValue = translate('search.filters.date.before', {date: dateBefore}); @@ -170,7 +268,9 @@ function getFilterDisplayTitle(filters: Partial, filt return dateValue; } - if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT) { + const nonDateFilterKey = filterKey as Exclude; + + if (nonDateFilterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT) { const {lessThan, greaterThan} = filters; if (lessThan && greaterThan) { return translate('search.filters.amount.between', { @@ -188,32 +288,32 @@ function getFilterDisplayTitle(filters: Partial, filt return; } - if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY && filters[filterKey]) { - const filterArray = filters[filterKey] ?? []; + if (nonDateFilterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY && filters[nonDateFilterKey]) { + const filterArray = filters[nonDateFilterKey] ?? []; return filterArray.sort(localeCompare).join(', '); } - if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY && filters[filterKey]) { - const filterArray = filters[filterKey] ?? []; + if (nonDateFilterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY && filters[nonDateFilterKey]) { + const filterArray = filters[nonDateFilterKey] ?? []; return filterArray .sort(sortOptionsWithEmptyValue) .map((value) => (value === CONST.SEARCH.EMPTY_VALUE ? translate('search.noCategory') : value)) .join(', '); } - if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG && filters[filterKey]) { - const filterArray = filters[filterKey] ?? []; + if (nonDateFilterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG && filters[nonDateFilterKey]) { + const filterArray = filters[nonDateFilterKey] ?? []; return filterArray .sort(sortOptionsWithEmptyValue) .map((value) => (value === CONST.SEARCH.EMPTY_VALUE ? translate('search.noTag') : value)) .join(', '); } - if (filterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION) { - return filters[filterKey]; + if (nonDateFilterKey === CONST.SEARCH.SYNTAX_FILTER_KEYS.DESCRIPTION) { + return filters[nonDateFilterKey]; } - const filterValue = filters[filterKey]; + const filterValue = filters[nonDateFilterKey]; return Array.isArray(filterValue) ? filterValue.join(', ') : filterValue; } @@ -352,74 +452,95 @@ 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.DESCRIPTION || - key === CONST.SEARCH.SYNTAX_FILTER_KEYS.MERCHANT || - key === CONST.SEARCH.SYNTAX_FILTER_KEYS.REPORT_ID || - key === CONST.SEARCH.SYNTAX_FILTER_KEYS.KEYWORD - ) { - filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, key, translate); - } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY) { - if (!shouldDisplayCategoryFilter) { - return; - } - filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, key, translate); - } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG) { - if (!shouldDisplayTagFilter) { - return; - } - filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, key, translate); - } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID) { - if (!shouldDisplayCardFilter) { - return; - } - filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, cardList); - } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAX_RATE) { - if (!shouldDisplayTaxFilter) { - return; - } - filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, taxRates); - } 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); - } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.IN) { - filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, translate, reports); - } - return { - key, - title: filterTitle, - description: translate(baseFilterConfig[key].description), - onPress, - }; + .map((section) => { + return section + .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.SUBMITTED || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.APPROVED || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.PAID || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT || + key === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY || + 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 + ) { + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, key, translate); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY) { + if (!shouldDisplayCategoryFilter) { + return; + } + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, key, translate); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG) { + if (!shouldDisplayTagFilter) { + return; + } + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, key, translate); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID) { + if (!shouldDisplayCardFilter) { + return; + } + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, cardList); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.POSTED) { + if (!shouldDisplayCardFilter) { + return; + } + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, key, translate); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAX_RATE) { + if (!shouldDisplayTaxFilter) { + return; + } + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, taxRates); + } 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); + } else if (key === CONST.SEARCH.SYNTAX_FILTER_KEYS.IN) { + filterTitle = baseFilterConfig[key].getTitle(searchAdvancedFilters, translate, reports); + } + return { + key, + title: filterTitle, + description: translate(baseFilterConfig[key].description), + onPress, + }; + }) + .filter((filter): filter is NonNullable => !!filter); }) - .sort((a, b) => (a?.description ?? '')?.localeCompare(b?.description ?? '')); - + .filter((section) => !!section.length); const displaySearchButton = queryJSON && !SearchQueryUtils.isCannedSearchQuery(queryJSON); return ( <> - {filters.map((filter) => { - if (filter === undefined) { - return undefined; - } + {filters.map((section, index) => { return ( - + <> + {index !== 0 && ( + + )} + {section.map((item) => { + return ( + + ); + })} + ); })} diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersApprovedPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersApprovedPage.tsx new file mode 100644 index 000000000000..0eff7b73baf2 --- /dev/null +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersApprovedPage.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import SearchDateFilterBase from '@components/Search/SearchDateFilterBase'; +import CONST from '@src/CONST'; + +function SearchFiltersApprovedPage() { + return ( + + ); +} + +SearchFiltersApprovedPage.displayName = 'SearchFiltersApprovedPage'; + +export default SearchFiltersApprovedPage; diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersDatePage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersDatePage.tsx index 140708feb773..00400b10c701 100644 --- a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersDatePage.tsx +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersDatePage.tsx @@ -1,74 +1,13 @@ -import {format} from 'date-fns'; import React from 'react'; -import {useOnyx} from 'react-native-onyx'; -import DatePicker from '@components/DatePicker'; -import FormProvider from '@components/Form/FormProvider'; -import InputWrapper from '@components/Form/InputWrapper'; -import type {FormOnyxValues} from '@components/Form/types'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import {updateAdvancedFilters} from '@libs/actions/Search'; -import Navigation from '@libs/Navigation/Navigation'; +import SearchDateFilterBase from '@components/Search/SearchDateFilterBase'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import FILTER_KEYS from '@src/types/form/SearchAdvancedFiltersForm'; function SearchFiltersDatePage() { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - - const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); - - const dateAfter = searchAdvancedFiltersForm?.[FILTER_KEYS.DATE_AFTER] ? format(searchAdvancedFiltersForm?.[FILTER_KEYS.DATE_AFTER], 'yyyy-MM-dd') : undefined; - const dateBefore = searchAdvancedFiltersForm?.[FILTER_KEYS.DATE_BEFORE] ? format(searchAdvancedFiltersForm?.[FILTER_KEYS.DATE_BEFORE], 'yyyy-MM-dd') : undefined; - - const updateDateFilter = (values: FormOnyxValues) => { - updateAdvancedFilters(values); - Navigation.goBack(ROUTES.SEARCH_ADVANCED_FILTERS); - }; - return ( - - { - Navigation.goBack(ROUTES.SEARCH_ADVANCED_FILTERS); - }} - /> - - - - - + ); } diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExportedPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExportedPage.tsx new file mode 100644 index 000000000000..01b590db8a43 --- /dev/null +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersExportedPage.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import SearchDateFilterBase from '@components/Search/SearchDateFilterBase'; +import CONST from '@src/CONST'; + +function SearchFiltersExportedPage() { + return ( + + ); +} + +SearchFiltersExportedPage.displayName = 'SearchFiltersExportedPage'; + +export default SearchFiltersExportedPage; diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersPaidPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersPaidPage.tsx new file mode 100644 index 000000000000..0dca3f88f84f --- /dev/null +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersPaidPage.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import SearchDateFilterBase from '@components/Search/SearchDateFilterBase'; +import CONST from '@src/CONST'; + +function SearchFiltersPaidPage() { + return ( + + ); +} + +SearchFiltersPaidPage.displayName = 'SearchFiltersPaidPage'; + +export default SearchFiltersPaidPage; diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersPostedPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersPostedPage.tsx new file mode 100644 index 000000000000..784cedc338c4 --- /dev/null +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersPostedPage.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import SearchDateFilterBase from '@components/Search/SearchDateFilterBase'; +import CONST from '@src/CONST'; + +function SearchFiltersPostedPage() { + return ( + + ); +} + +SearchFiltersPostedPage.displayName = 'SearchFiltersPostedPage'; + +export default SearchFiltersPostedPage; diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersSubmittedPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersSubmittedPage.tsx new file mode 100644 index 000000000000..1374690836e6 --- /dev/null +++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersSubmittedPage.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import SearchDateFilterBase from '@components/Search/SearchDateFilterBase'; +import CONST from '@src/CONST'; + +function SearchFiltersSubmittedPage() { + return ( + + ); +} + +SearchFiltersSubmittedPage.displayName = 'SearchFiltersSubmittedPage'; + +export default SearchFiltersSubmittedPage; diff --git a/src/types/form/SearchAdvancedFiltersForm.ts b/src/types/form/SearchAdvancedFiltersForm.ts index a70e58d48a19..e38a1d7f814b 100644 --- a/src/types/form/SearchAdvancedFiltersForm.ts +++ b/src/types/form/SearchAdvancedFiltersForm.ts @@ -1,11 +1,32 @@ import type {ValueOf} from 'type-fest'; +import type {SearchDateFilterKeys} from '@components/Search/types'; +import CONST from '@src/CONST'; import type Form from './Form'; +const DATE_FILTER_KEYS: SearchDateFilterKeys[] = [ + CONST.SEARCH.SYNTAX_FILTER_KEYS.DATE, + CONST.SEARCH.SYNTAX_FILTER_KEYS.SUBMITTED, + CONST.SEARCH.SYNTAX_FILTER_KEYS.APPROVED, + CONST.SEARCH.SYNTAX_FILTER_KEYS.PAID, + CONST.SEARCH.SYNTAX_FILTER_KEYS.EXPORTED, + CONST.SEARCH.SYNTAX_FILTER_KEYS.POSTED, +]; + const FILTER_KEYS = { TYPE: 'type', STATUS: 'status', DATE_AFTER: 'dateAfter', DATE_BEFORE: 'dateBefore', + SUBMITTED_AFTER: 'submittedAfter', + SUBMITTED_BEFORE: 'submittedBefore', + APPROVED_AFTER: 'approvedAfter', + APPROVED_BEFORE: 'approvedBefore', + PAID_AFTER: 'paidAfter', + PAID_BEFORE: 'paidBefore', + EXPORTED_AFTER: 'exportedAfter', + EXPORTED_BEFORE: 'exportedBefore', + POSTED_AFTER: 'postedAfter', + POSTED_BEFORE: 'postedBefore', CURRENCY: 'currency', CATEGORY: 'category', POLICY_ID: 'policyID', @@ -33,6 +54,16 @@ type SearchAdvancedFiltersForm = Form< [FILTER_KEYS.STATUS]: string; [FILTER_KEYS.DATE_AFTER]: string; [FILTER_KEYS.DATE_BEFORE]: string; + [FILTER_KEYS.SUBMITTED_AFTER]: string; + [FILTER_KEYS.SUBMITTED_BEFORE]: string; + [FILTER_KEYS.APPROVED_AFTER]: string; + [FILTER_KEYS.APPROVED_BEFORE]: string; + [FILTER_KEYS.PAID_AFTER]: string; + [FILTER_KEYS.PAID_BEFORE]: string; + [FILTER_KEYS.EXPORTED_AFTER]: string; + [FILTER_KEYS.EXPORTED_BEFORE]: string; + [FILTER_KEYS.POSTED_AFTER]: string; + [FILTER_KEYS.POSTED_BEFORE]: string; [FILTER_KEYS.CURRENCY]: string[]; [FILTER_KEYS.CATEGORY]: string[]; [FILTER_KEYS.POLICY_ID]: string; @@ -54,3 +85,4 @@ type SearchAdvancedFiltersForm = Form< export type {SearchAdvancedFiltersForm}; export default FILTER_KEYS; +export {DATE_FILTER_KEYS};