From 0809a255fb4f2527a7da048ea2b288edeb3a24a4 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Mon, 22 Jul 2024 12:48:55 +0200 Subject: [PATCH 01/14] Add displaying chosen date advanced filters value --- src/languages/en.ts | 5 ++- src/languages/es.ts | 5 ++- src/pages/Search/AdvancedSearchFilters.tsx | 46 +++++++++++++++++++--- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index b49ad50421a4..2caf1961772a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3552,6 +3552,7 @@ export default { search: { selectMultiple: 'Select multiple', resultsAreLimited: 'Search results are limited.', + viewResults: 'View results', searchResults: { emptyResults: { title: 'Nothing to show', @@ -3569,8 +3570,8 @@ export default { filtersHeader: 'Filters', filters: { date: { - before: 'Before', - after: 'After', + before: (date: string) => `Before ${date || ''}`, + after: (date: string) => `After ${date || ''}`, }, }, }, diff --git a/src/languages/es.ts b/src/languages/es.ts index ff06d4f4f97e..c12d957e9034 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3609,6 +3609,7 @@ export default { search: { selectMultiple: 'Seleccionar varios', resultsAreLimited: 'Los resultados de búsqueda están limitados.', + viewResults: 'Ver resultados', searchResults: { emptyResults: { title: 'No hay nada que ver aquí', @@ -3626,8 +3627,8 @@ export default { filtersHeader: 'Filtros', filters: { date: { - before: 'Antes de', - after: 'Después de', + before: (date: string) => `Antes de ${date || ''}`, + after: (date: string) => `Después de ${date || ''}`, }, }, }, diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 171e2c45dbd7..2f6faffeed2c 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -1,37 +1,62 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import useLocalize from '@hooks/useLocalize'; import useSingleExecution from '@hooks/useSingleExecution'; +import useThemeStyles from '@hooks/useThemeStyles'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; import Navigation from '@libs/Navigation/Navigation'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {SearchAdvancedFiltersForm} from '@src/types/form'; + +type AvailableFilters = 'type' | 'date'; + +function getFilterDisplayTitle(filters: Partial, fieldName: AvailableFilters, translate: LocaleContextProps['translate']) { + if (fieldName === 'date') { + const {dateAfter, dateBefore} = filters; + let dateValue = ''; + if (dateBefore) { + dateValue = translate('search.filters.date.before', dateBefore); + } + if (dateBefore && dateAfter) { + dateValue += ', '; + } + if (dateAfter) { + dateValue += translate('search.filters.date.after', dateAfter); + } + + return dateValue; + } -function getFilterDisplayTitle(filters: Record, fieldName: string) { - // This is temporary because the full parsing of search query is not yet done - // TODO once we have values from query, this value should be `filters[fieldName].value` return fieldName; } function AdvancedSearchFilters() { const {translate} = useLocalize(); + const styles = useThemeStyles(); const {singleExecution} = useSingleExecution(); const waitForNavigate = useWaitForNavigation(); + const [searchAdvancedFilters = {}] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); + const advancedFilters = useMemo( () => [ { - title: getFilterDisplayTitle({}, 'title'), + title: getFilterDisplayTitle(searchAdvancedFilters, 'type', translate), description: 'common.type' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_TYPE, }, { - title: getFilterDisplayTitle({}, 'date'), + title: getFilterDisplayTitle(searchAdvancedFilters, 'date', translate), description: 'common.date' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, }, ], - [], + [searchAdvancedFilters], ); return ( @@ -49,6 +74,15 @@ function AdvancedSearchFilters() { /> ); })} + { + // here set the selected filters as new query and redirecting to SearchResults page + // once these are ready: https://github.com/Expensify/App/issues/45028 and https://github.com/Expensify/App/issues/45027 + Navigation.goBack(); + }} + /> ); } From ef5190cdff2f1df2e5fd289fb267bc93958159e2 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Mon, 22 Jul 2024 16:51:03 +0200 Subject: [PATCH 02/14] Update search filters type page --- src/CONST.ts | 3 ++ src/pages/Search/AdvancedSearchFilters.tsx | 18 +++++++--- src/pages/Search/SearchFiltersTypePage.tsx | 42 ++++++++++++++++++++-- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index a1193fd8bf32..228a8729b017 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5223,6 +5223,9 @@ const CONST = { DRAFTS: 'drafts', FINISHED: 'finished', }, + TYPE: { + EXPENSES: 'expenses', + }, TABLE_COLUMNS: { RECEIPT: 'receipt', DATE: 'date', diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 2f6faffeed2c..67731ff2a16c 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -1,6 +1,7 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; @@ -12,8 +13,10 @@ import Navigation from '@libs/Navigation/Navigation'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; +import INPUT_IDS from '@src/types/form/SearchAdvancedFiltersForm'; -type AvailableFilters = 'type' | 'date'; +// the values of dateBefore+dateAfter map to just a single 'date' field on advanced filters +type AvailableFilters = ValueOf | 'date'; function getFilterDisplayTitle(filters: Partial, fieldName: AvailableFilters, translate: LocaleContextProps['translate']) { if (fieldName === 'date') { @@ -32,7 +35,12 @@ function getFilterDisplayTitle(filters: Partial, fiel return dateValue; } - return fieldName; + if (fieldName === INPUT_IDS.TYPE) { + // return filters[fieldName] ? filters[fieldName].toUpperCase() : ''; + return filters[fieldName]; + } + + return filters[fieldName]; } function AdvancedSearchFilters() { @@ -56,7 +64,7 @@ function AdvancedSearchFilters() { route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, }, ], - [searchAdvancedFilters], + [searchAdvancedFilters, translate], ); return ( @@ -78,8 +86,8 @@ function AdvancedSearchFilters() { buttonText={translate('search.viewResults')} containerStyles={[styles.mh4, styles.mt4]} onSubmit={() => { - // here set the selected filters as new query and redirecting to SearchResults page - // once these are ready: https://github.com/Expensify/App/issues/45028 and https://github.com/Expensify/App/issues/45027 + // here set the selected filters as new query and redirect to SearchResults page + // waiting for: https://github.com/Expensify/App/issues/45028 and https://github.com/Expensify/App/issues/45027 Navigation.goBack(); }} /> diff --git a/src/pages/Search/SearchFiltersTypePage.tsx b/src/pages/Search/SearchFiltersTypePage.tsx index e18b865f20ef..72317a03c07f 100644 --- a/src/pages/Search/SearchFiltersTypePage.tsx +++ b/src/pages/Search/SearchFiltersTypePage.tsx @@ -1,16 +1,39 @@ import React from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +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 Picker from '@components/Picker'; import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import Text from '@src/components/Text'; +import Navigation from '@navigation/Navigation'; +import * as SearchActions from '@userActions/Search'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/SearchAdvancedFiltersForm'; function SearchFiltersTypePage() { const styles = useThemeStyles(); const {translate} = useLocalize(); + const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); + + const filterTypeItems = [ + { + label: translate('common.expenses'), + value: CONST.SEARCH.TYPE.EXPENSES, + }, + ]; + + const updateType = (value: FormOnyxValues) => { + SearchActions.mergeFilters(value); + Navigation.goBack(); + }; + return ( - {/* temporary placeholder, will be implemented in https://github.com/Expensify/App/issues/45026 */} - Advanced filters Type form + + + From 6e03029dea4397f37781fbaf1c0f1615dfd01d9e Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Tue, 23 Jul 2024 14:41:11 +0200 Subject: [PATCH 03/14] Finalize SearchFiltersTypePage with correct input UI --- src/libs/actions/Search.ts | 2 +- src/pages/Search/AdvancedSearchFilters.tsx | 1 - src/pages/Search/SearchFiltersTypePage.tsx | 59 +++++++++++----------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 245192ff6e0a..7cf0a739fcd2 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -113,7 +113,7 @@ function exportSearchItemsToCSV(query: string, reportIDList: Array) { +function updateAdvancedFilters(values: Partial>) { Onyx.merge(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, values); } diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 67731ff2a16c..79cb4383fc63 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -36,7 +36,6 @@ function getFilterDisplayTitle(filters: Partial, fiel } if (fieldName === INPUT_IDS.TYPE) { - // return filters[fieldName] ? filters[fieldName].toUpperCase() : ''; return filters[fieldName]; } diff --git a/src/pages/Search/SearchFiltersTypePage.tsx b/src/pages/Search/SearchFiltersTypePage.tsx index 72317a03c07f..5b16de7457cf 100644 --- a/src/pages/Search/SearchFiltersTypePage.tsx +++ b/src/pages/Search/SearchFiltersTypePage.tsx @@ -1,20 +1,18 @@ -import React from 'react'; +import React, {useMemo} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -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 Picker from '@components/Picker'; import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@navigation/Navigation'; import * as SearchActions from '@userActions/Search'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import INPUT_IDS from '@src/types/form/SearchAdvancedFiltersForm'; function SearchFiltersTypePage() { const styles = useThemeStyles(); @@ -22,15 +20,22 @@ function SearchFiltersTypePage() { const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); - const filterTypeItems = [ - { - label: translate('common.expenses'), - value: CONST.SEARCH.TYPE.EXPENSES, - }, - ]; + const activeItem = searchAdvancedFiltersForm?.type ?? CONST.SEARCH.TYPE.EXPENSES; - const updateType = (value: FormOnyxValues) => { - SearchActions.mergeFilters(value); + const filterTypeItems = useMemo( + () => [ + { + text: translate('common.expenses'), + value: CONST.SEARCH.TYPE.EXPENSES, + keyForList: CONST.SEARCH.TYPE.EXPENSES, + isSelected: activeItem === CONST.SEARCH.TYPE.EXPENSES, + }, + ], + [translate, activeItem], + ); + + const updateType = (values: Partial>) => { + SearchActions.mergeFilters(values); Navigation.goBack(); }; @@ -42,22 +47,18 @@ function SearchFiltersTypePage() { > - - - - + + { + updateType({ + type: item.value, + }); + }} + initiallyFocusedOptionKey={activeItem} + shouldStopPropagation + ListItem={RadioListItem} + /> From d4c91c4dc01ac8c1a2dfb3864ed10cc855ce0f68 Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Tue, 23 Jul 2024 15:23:27 +0200 Subject: [PATCH 04/14] Add status advanced filter for Search --- src/ROUTES.ts | 2 + src/SCREENS.ts | 1 + src/languages/en.ts | 1 + src/languages/es.ts | 1 + .../ModalStackNavigators/index.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 1 + src/pages/Search/AdvancedSearchFilters.tsx | 5 ++ src/pages/Search/SearchFiltersStatusPage.tsx | 88 +++++++++++++++++++ src/pages/Search/SearchFiltersTypePage.tsx | 2 +- src/types/form/SearchAdvancedFiltersForm.ts | 2 + 10 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/pages/Search/SearchFiltersStatusPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 460f63860f44..8c3782ba9c8b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -55,6 +55,8 @@ const ROUTES = { SEARCH_ADVANCED_FILTERS_TYPE: 'search/filters/type', + SEARCH_ADVANCED_FILTERS_STATUS: 'search/filters/status', + SEARCH_REPORT: { route: 'search/:query/view/:reportID', getRoute: (query: string, reportID: string) => `search/${query}/view/${reportID}` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 15361ee049a5..dfa575c97a88 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -33,6 +33,7 @@ const SCREENS = { ADVANCED_FILTERS_RHP: 'Search_Advanced_Filters_RHP', ADVANCED_FILTERS_DATE_RHP: 'Search_Advanced_Filters_Date_RHP', ADVANCED_FILTERS_TYPE_RHP: 'Search_Advanced_Filters_Type_RHP', + ADVANCED_FILTERS_STATUS_RHP: 'Search_Advanced_Filters_Status_RHP', TRANSACTION_HOLD_REASON_RHP: 'Search_Transaction_Hold_Reason_RHP', BOTTOM_TAB: 'Search_Bottom_Tab', }, diff --git a/src/languages/en.ts b/src/languages/en.ts index 2caf1961772a..31d0b713b2d8 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3573,6 +3573,7 @@ export default { before: (date: string) => `Before ${date || ''}`, after: (date: string) => `After ${date || ''}`, }, + status: 'Status', }, }, genericErrorPage: { diff --git a/src/languages/es.ts b/src/languages/es.ts index c12d957e9034..b34f56c9f98e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3630,6 +3630,7 @@ export default { before: (date: string) => `Antes de ${date || ''}`, after: (date: string) => `Después de ${date || ''}`, }, + status: 'Estado', }, }, genericErrorPage: { diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index b19dd76d9e9c..40989ceb6e8d 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -507,6 +507,7 @@ const SearchAdvancedFiltersModalStackNavigator = createModalStackNavigator require('../../../../pages/Search/SearchAdvancedFiltersPage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP]: () => require('../../../../pages/Search/SearchFiltersDatePage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_TYPE_RHP]: () => require('../../../../pages/Search/SearchFiltersTypePage').default, + [SCREENS.SEARCH.ADVANCED_FILTERS_STATUS_RHP]: () => require('../../../../pages/Search/SearchFiltersStatusPage').default, }); const RestrictedActionModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 5fbfa0ca07db..ac986a054196 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -999,6 +999,7 @@ const config: LinkingOptions['config'] = { [SCREENS.SEARCH.ADVANCED_FILTERS_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS, [SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, [SCREENS.SEARCH.ADVANCED_FILTERS_TYPE_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_TYPE, + [SCREENS.SEARCH.ADVANCED_FILTERS_STATUS_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_STATUS, }, }, [SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: { diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 79cb4383fc63..e7323401dc46 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -62,6 +62,11 @@ function AdvancedSearchFilters() { description: 'common.date' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, }, + { + title: getFilterDisplayTitle(searchAdvancedFilters, 'status', translate), + description: 'search.filters.status' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_STATUS, + }, ], [searchAdvancedFilters, translate], ); diff --git a/src/pages/Search/SearchFiltersStatusPage.tsx b/src/pages/Search/SearchFiltersStatusPage.tsx new file mode 100644 index 000000000000..319703af7151 --- /dev/null +++ b/src/pages/Search/SearchFiltersStatusPage.tsx @@ -0,0 +1,88 @@ +import React, {useMemo} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import type {FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@navigation/Navigation'; +import * as SearchActions from '@userActions/Search'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +function SearchFiltersStatusPage() { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); + + const activeItem = searchAdvancedFiltersForm?.status; + + const filterStatusItems = useMemo( + () => [ + { + text: translate('common.all'), + value: CONST.SEARCH.TAB.ALL, + keyForList: CONST.SEARCH.TAB.ALL, + isSelected: activeItem === CONST.SEARCH.TAB.ALL, + }, + { + text: translate('common.shared'), + value: CONST.SEARCH.TAB.SHARED, + keyForList: CONST.SEARCH.TAB.SHARED, + isSelected: activeItem === CONST.SEARCH.TAB.SHARED, + }, + { + text: translate('common.drafts'), + value: CONST.SEARCH.TAB.DRAFTS, + keyForList: CONST.SEARCH.TAB.DRAFTS, + isSelected: activeItem === CONST.SEARCH.TAB.DRAFTS, + }, + { + text: translate('common.finished'), + value: CONST.SEARCH.TAB.FINISHED, + keyForList: CONST.SEARCH.TAB.FINISHED, + isSelected: activeItem === CONST.SEARCH.TAB.FINISHED, + }, + ], + [translate, activeItem], + ); + + const updateType = (values: Partial>) => { + SearchActions.updateAdvancedFilters(values); + Navigation.goBack(); + }; + + return ( + + + + + { + updateType({ + status: item.value, + }); + }} + initiallyFocusedOptionKey={activeItem} + shouldStopPropagation + ListItem={RadioListItem} + /> + + + + ); +} + +SearchFiltersStatusPage.displayName = 'SearchFiltersStatusPage'; + +export default SearchFiltersStatusPage; diff --git a/src/pages/Search/SearchFiltersTypePage.tsx b/src/pages/Search/SearchFiltersTypePage.tsx index 5b16de7457cf..c9f4774eb9ba 100644 --- a/src/pages/Search/SearchFiltersTypePage.tsx +++ b/src/pages/Search/SearchFiltersTypePage.tsx @@ -35,7 +35,7 @@ function SearchFiltersTypePage() { ); const updateType = (values: Partial>) => { - SearchActions.mergeFilters(values); + SearchActions.updateAdvancedFilters(values); Navigation.goBack(); }; diff --git a/src/types/form/SearchAdvancedFiltersForm.ts b/src/types/form/SearchAdvancedFiltersForm.ts index 46808954f661..3c9a64a06976 100644 --- a/src/types/form/SearchAdvancedFiltersForm.ts +++ b/src/types/form/SearchAdvancedFiltersForm.ts @@ -3,6 +3,7 @@ import type Form from './Form'; const INPUT_IDS = { TYPE: 'type', + STATUS: 'status', DATE_AFTER: 'dateAfter', DATE_BEFORE: 'dateBefore', } as const; @@ -15,6 +16,7 @@ type SearchAdvancedFiltersForm = Form< [INPUT_IDS.TYPE]: string; [INPUT_IDS.DATE_AFTER]: string; [INPUT_IDS.DATE_BEFORE]: string; + [INPUT_IDS.STATUS]: string; } >; From b8c46434f7b6db5f0648112b136b944146e3961d Mon Sep 17 00:00:00 2001 From: Mateusz Titz Date: Wed, 24 Jul 2024 12:04:49 +0200 Subject: [PATCH 05/14] Improve styling of advanced search filters --- src/languages/en.ts | 4 +-- src/languages/es.ts | 4 +-- src/pages/Search/AdvancedSearchFilters.tsx | 38 ++++++++++---------- src/pages/Search/SearchFiltersStatusPage.tsx | 2 +- src/pages/Search/SearchFiltersTypePage.tsx | 2 +- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 31d0b713b2d8..a7d1006d8913 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3570,8 +3570,8 @@ export default { filtersHeader: 'Filters', filters: { date: { - before: (date: string) => `Before ${date || ''}`, - after: (date: string) => `After ${date || ''}`, + before: (date?: string) => `Before ${date ?? ''}`, + after: (date?: string) => `After ${date ?? ''}`, }, status: 'Status', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index b34f56c9f98e..0769a31d2bbc 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3627,8 +3627,8 @@ export default { filtersHeader: 'Filtros', filters: { date: { - before: (date: string) => `Antes de ${date || ''}`, - after: (date: string) => `Después de ${date || ''}`, + before: (date?: string) => `Antes de ${date ?? ''}`, + after: (date?: string) => `Después de ${date ?? ''}`, }, status: 'Estado', }, diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index e7323401dc46..33ad6a9433bb 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -1,3 +1,4 @@ +import {Str} from 'expensify-common'; import React, {useMemo} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; @@ -13,7 +14,7 @@ import Navigation from '@libs/Navigation/Navigation'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; -import INPUT_IDS from '@src/types/form/SearchAdvancedFiltersForm'; +import type INPUT_IDS from '@src/types/form/SearchAdvancedFiltersForm'; // the values of dateBefore+dateAfter map to just a single 'date' field on advanced filters type AvailableFilters = ValueOf | 'date'; @@ -35,11 +36,8 @@ function getFilterDisplayTitle(filters: Partial, fiel return dateValue; } - if (fieldName === INPUT_IDS.TYPE) { - return filters[fieldName]; - } - - return filters[fieldName]; + const filterValue = filters[fieldName]; + return filterValue ? Str.recapitalize(filterValue) : undefined; } function AdvancedSearchFilters() { @@ -72,20 +70,22 @@ function AdvancedSearchFilters() { ); return ( - - {advancedFilters.map((item) => { - const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(item.route))); + + + {advancedFilters.map((item) => { + const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(item.route))); - return ( - - ); - })} + return ( + + ); + })} + - + { diff --git a/src/pages/Search/SearchFiltersTypePage.tsx b/src/pages/Search/SearchFiltersTypePage.tsx index c9f4774eb9ba..f065ee827aed 100644 --- a/src/pages/Search/SearchFiltersTypePage.tsx +++ b/src/pages/Search/SearchFiltersTypePage.tsx @@ -47,7 +47,7 @@ function SearchFiltersTypePage() { > - + { From 5525339842393819cf754c8fc0955c6c30c9deb4 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Thu, 25 Jul 2024 15:53:14 +0200 Subject: [PATCH 06/14] Add category advanced filter for Search --- src/ROUTES.ts | 2 + src/SCREENS.ts | 1 + .../ModalStackNavigators/index.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 1 + src/pages/Search/AdvancedSearchFilters.tsx | 13 +++ .../Search/SearchFiltersCategoryPage.tsx | 84 +++++++++++++++++++ src/types/form/SearchAdvancedFiltersForm.ts | 2 + 7 files changed, 104 insertions(+) create mode 100644 src/pages/Search/SearchFiltersCategoryPage.tsx diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 8c3782ba9c8b..7147c0c1afe1 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -57,6 +57,8 @@ const ROUTES = { SEARCH_ADVANCED_FILTERS_STATUS: 'search/filters/status', + SEARCH_ADVANCED_FILTERS_CATEGORY: 'search/filters/category', + SEARCH_REPORT: { route: 'search/:query/view/:reportID', getRoute: (query: string, reportID: string) => `search/${query}/view/${reportID}` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index dfa575c97a88..c980474d43fd 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -34,6 +34,7 @@ const SCREENS = { ADVANCED_FILTERS_DATE_RHP: 'Search_Advanced_Filters_Date_RHP', ADVANCED_FILTERS_TYPE_RHP: 'Search_Advanced_Filters_Type_RHP', ADVANCED_FILTERS_STATUS_RHP: 'Search_Advanced_Filters_Status_RHP', + ADVANCED_FILTERS_CATEGORY_RHP: 'Search_Advanced_Filters_Category_RHP', TRANSACTION_HOLD_REASON_RHP: 'Search_Transaction_Hold_Reason_RHP', BOTTOM_TAB: 'Search_Bottom_Tab', }, diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 40989ceb6e8d..3e6c2e0e10db 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -508,6 +508,7 @@ const SearchAdvancedFiltersModalStackNavigator = createModalStackNavigator require('../../../../pages/Search/SearchFiltersDatePage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_TYPE_RHP]: () => require('../../../../pages/Search/SearchFiltersTypePage').default, [SCREENS.SEARCH.ADVANCED_FILTERS_STATUS_RHP]: () => require('../../../../pages/Search/SearchFiltersStatusPage').default, + [SCREENS.SEARCH.ADVANCED_FILTERS_CATEGORY_RHP]: () => require('../../../../pages/Search/SearchFiltersCategoryPage').default, }); const RestrictedActionModalStackNavigator = createModalStackNavigator({ diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index ac986a054196..ea5ec349dd52 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1000,6 +1000,7 @@ const config: LinkingOptions['config'] = { [SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_DATE, [SCREENS.SEARCH.ADVANCED_FILTERS_TYPE_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_TYPE, [SCREENS.SEARCH.ADVANCED_FILTERS_STATUS_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_STATUS, + [SCREENS.SEARCH.ADVANCED_FILTERS_CATEGORY_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY, }, }, [SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: { diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 33ad6a9433bb..8a537f339c05 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -36,6 +36,14 @@ function getFilterDisplayTitle(filters: Partial, fiel return dateValue; } + if (fieldName === 'category') { + const categories = filters[fieldName]; + if (!categories) { + return undefined; + } + return categories.map((category) => Str.recapitalize(category)).join(', '); + } + const filterValue = filters[fieldName]; return filterValue ? Str.recapitalize(filterValue) : undefined; } @@ -65,6 +73,11 @@ function AdvancedSearchFilters() { description: 'search.filters.status' as const, route: ROUTES.SEARCH_ADVANCED_FILTERS_STATUS, }, + { + title: getFilterDisplayTitle(searchAdvancedFilters, 'category', translate), + description: 'common.category' as const, + route: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY, + }, ], [searchAdvancedFilters, translate], ); diff --git a/src/pages/Search/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchFiltersCategoryPage.tsx new file mode 100644 index 000000000000..0a63c58d0852 --- /dev/null +++ b/src/pages/Search/SearchFiltersCategoryPage.tsx @@ -0,0 +1,84 @@ +import React, {useMemo} from 'react'; +import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import type {FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import SelectionList from '@components/SelectionList'; +import RadioListItem from '@components/SelectionList/RadioListItem'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import localeCompare from '@libs/LocaleCompare'; +import * as SearchActions from '@userActions/Search'; +import ONYXKEYS from '@src/ONYXKEYS'; + +function SearchFiltersStatusPage() { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + + const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); + const [allPolicy] = useOnyx(ONYXKEYS.COLLECTION.POLICY); + const policyId = Object.values(allPolicy ?? {})[0]?.id; + + const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyId}`); + + const activeItems = searchAdvancedFiltersForm?.category; + + const categoryList = useMemo( + () => + Object.values(policyCategories ?? {}) + .sort((a, b) => localeCompare(a.name, b.name)) + .map((value) => ({ + text: value.name, + keyForList: value.name, + isSelected: activeItems?.includes(value.name) ?? false, + value: value.name, + })), + [activeItems, policyCategories], + ); + + const updateType = (values: Partial>) => { + SearchActions.updateAdvancedFilters(values); + // Navigation.goBack(); + }; + + return ( + + + + + { + const isActive = activeItems?.includes(item.value); + + let newCategories; + + if (isActive) { + newCategories = activeItems?.filter((category) => category !== item.value); + } else { + newCategories = [...(activeItems ?? []), item.value]; + } + + updateType({ + category: newCategories, + }); + }} + initiallyFocusedOptionKey={activeItems?.[0]} + shouldStopPropagation + ListItem={RadioListItem} + /> + + + + ); +} + +SearchFiltersStatusPage.displayName = 'SearchFiltersCategoryPage'; + +export default SearchFiltersStatusPage; diff --git a/src/types/form/SearchAdvancedFiltersForm.ts b/src/types/form/SearchAdvancedFiltersForm.ts index 3c9a64a06976..809476c10bd9 100644 --- a/src/types/form/SearchAdvancedFiltersForm.ts +++ b/src/types/form/SearchAdvancedFiltersForm.ts @@ -6,6 +6,7 @@ const INPUT_IDS = { STATUS: 'status', DATE_AFTER: 'dateAfter', DATE_BEFORE: 'dateBefore', + CATEGORY: 'category', } as const; type InputID = ValueOf; @@ -17,6 +18,7 @@ type SearchAdvancedFiltersForm = Form< [INPUT_IDS.DATE_AFTER]: string; [INPUT_IDS.DATE_BEFORE]: string; [INPUT_IDS.STATUS]: string; + [INPUT_IDS.CATEGORY]: string[]; } >; From 0c944e039fb159988a37a3dbf3393dce413e5e71 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Fri, 26 Jul 2024 14:30:27 +0200 Subject: [PATCH 07/14] Update policyID usage in category page search filters --- .../Search/SearchFiltersCategoryPage.tsx | 42 ++++++++++++------- src/types/form/SearchAdvancedFiltersForm.ts | 2 + 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/pages/Search/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchFiltersCategoryPage.tsx index 0a63c58d0852..761435f8e0a7 100644 --- a/src/pages/Search/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchFiltersCategoryPage.tsx @@ -18,29 +18,41 @@ function SearchFiltersStatusPage() { const {translate} = useLocalize(); const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); - const [allPolicy] = useOnyx(ONYXKEYS.COLLECTION.POLICY); - const policyId = Object.values(allPolicy ?? {})[0]?.id; + const activeItems = searchAdvancedFiltersForm?.category; + const policyId = searchAdvancedFiltersForm?.policyId ?? '-1'; - const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyId}`); + const [callCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES); + const [singlePolicyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyId}`); - const activeItems = searchAdvancedFiltersForm?.category; + let categories: string[] = []; + if (!singlePolicyCategories) { + categories = Object.values(callCategories ?? {}) + .map((value) => Object.values(value ?? {})) + .map((value2) => value2.map((value3) => value3?.name)) + .flat(); + } else { + categories = Object.values(singlePolicyCategories ?? {}).map((value) => value.name); + } + + const removeDuplicates = (array: string[]) => array.filter((value, index) => array.indexOf(value) === index); + + const categoryNames = removeDuplicates(categories); const categoryList = useMemo( () => - Object.values(policyCategories ?? {}) - .sort((a, b) => localeCompare(a.name, b.name)) - .map((value) => ({ - text: value.name, - keyForList: value.name, - isSelected: activeItems?.includes(value.name) ?? false, - value: value.name, + categoryNames + .sort((a, b) => localeCompare(a, b)) + .map((name) => ({ + text: name, + keyForList: name, + isSelected: activeItems?.includes(name) ?? false, + value: name, })), - [activeItems, policyCategories], + [categoryNames, activeItems], ); - const updateType = (values: Partial>) => { + const updateCategory = (values: Partial>) => { SearchActions.updateAdvancedFilters(values); - // Navigation.goBack(); }; return ( @@ -65,7 +77,7 @@ function SearchFiltersStatusPage() { newCategories = [...(activeItems ?? []), item.value]; } - updateType({ + updateCategory({ category: newCategories, }); }} diff --git a/src/types/form/SearchAdvancedFiltersForm.ts b/src/types/form/SearchAdvancedFiltersForm.ts index 809476c10bd9..459df8b8bbdf 100644 --- a/src/types/form/SearchAdvancedFiltersForm.ts +++ b/src/types/form/SearchAdvancedFiltersForm.ts @@ -7,6 +7,7 @@ const INPUT_IDS = { DATE_AFTER: 'dateAfter', DATE_BEFORE: 'dateBefore', CATEGORY: 'category', + POLICY_ID: 'policyId', } as const; type InputID = ValueOf; @@ -19,6 +20,7 @@ type SearchAdvancedFiltersForm = Form< [INPUT_IDS.DATE_BEFORE]: string; [INPUT_IDS.STATUS]: string; [INPUT_IDS.CATEGORY]: string[]; + [INPUT_IDS.POLICY_ID]: string; } >; From 5df9b9715578b326cff43993d44083290d3aa7d9 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Fri, 26 Jul 2024 16:24:00 +0200 Subject: [PATCH 08/14] Apply review sugestions --- .../Search/SearchFiltersCategoryPage.tsx | 57 ++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/src/pages/Search/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchFiltersCategoryPage.tsx index 761435f8e0a7..aebeb700a6bb 100644 --- a/src/pages/Search/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchFiltersCategoryPage.tsx @@ -13,7 +13,7 @@ import localeCompare from '@libs/LocaleCompare'; import * as SearchActions from '@userActions/Search'; import ONYXKEYS from '@src/ONYXKEYS'; -function SearchFiltersStatusPage() { +function SearchFiltersCategoryPage() { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -21,35 +21,30 @@ function SearchFiltersStatusPage() { const activeItems = searchAdvancedFiltersForm?.category; const policyId = searchAdvancedFiltersForm?.policyId ?? '-1'; - const [callCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES); + const [allPolicyIdCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES); const [singlePolicyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyId}`); - let categories: string[] = []; - if (!singlePolicyCategories) { - categories = Object.values(callCategories ?? {}) - .map((value) => Object.values(value ?? {})) - .map((value2) => value2.map((value3) => value3?.name)) - .flat(); - } else { - categories = Object.values(singlePolicyCategories ?? {}).map((value) => value.name); - } + const categoryList = useMemo(() => { + let categories: string[] = []; + if (!singlePolicyCategories) { + categories = Object.values(allPolicyIdCategories ?? {}) + .map((policyCategories) => Object.values(policyCategories ?? {}).map((category) => category.name)) + .flat(); + } else { + categories = Object.values(singlePolicyCategories ?? {}).map((value) => value.name); + } - const removeDuplicates = (array: string[]) => array.filter((value, index) => array.indexOf(value) === index); + const categoryNames = [...new Set(categories)]; - const categoryNames = removeDuplicates(categories); - - const categoryList = useMemo( - () => - categoryNames - .sort((a, b) => localeCompare(a, b)) - .map((name) => ({ - text: name, - keyForList: name, - isSelected: activeItems?.includes(name) ?? false, - value: name, - })), - [categoryNames, activeItems], - ); + return categoryNames + .sort((a, b) => localeCompare(a, b)) + .map((name) => ({ + text: name, + keyForList: name, + isSelected: activeItems?.includes(name) ?? false, + value: name, + })); + }, [activeItems, allPolicyIdCategories, singlePolicyCategories]); const updateCategory = (values: Partial>) => { SearchActions.updateAdvancedFilters(values); @@ -57,7 +52,7 @@ function SearchFiltersStatusPage() { return ( @@ -67,11 +62,9 @@ function SearchFiltersStatusPage() { { - const isActive = activeItems?.includes(item.value); - let newCategories; - if (isActive) { + if (item.isSelected) { newCategories = activeItems?.filter((category) => category !== item.value); } else { newCategories = [...(activeItems ?? []), item.value]; @@ -91,6 +84,6 @@ function SearchFiltersStatusPage() { ); } -SearchFiltersStatusPage.displayName = 'SearchFiltersCategoryPage'; +SearchFiltersCategoryPage.displayName = 'SearchFiltersCategoryPage'; -export default SearchFiltersStatusPage; +export default SearchFiltersCategoryPage; From e354d81ae0a16e744f34cca64322a7bf34e31329 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Mon, 29 Jul 2024 10:15:02 +0200 Subject: [PATCH 09/14] Reduce useOnyx calls --- src/pages/Search/SearchFiltersCategoryPage.tsx | 4 ++-- src/types/form/SearchAdvancedFiltersForm.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/Search/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchFiltersCategoryPage.tsx index aebeb700a6bb..bbfe69d533ae 100644 --- a/src/pages/Search/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchFiltersCategoryPage.tsx @@ -19,10 +19,10 @@ function SearchFiltersCategoryPage() { const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); const activeItems = searchAdvancedFiltersForm?.category; - const policyId = searchAdvancedFiltersForm?.policyId ?? '-1'; + const policyID = searchAdvancedFiltersForm?.policyID ?? '-1'; const [allPolicyIdCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES); - const [singlePolicyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyId}`); + const singlePolicyCategories = allPolicyIdCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]; const categoryList = useMemo(() => { let categories: string[] = []; diff --git a/src/types/form/SearchAdvancedFiltersForm.ts b/src/types/form/SearchAdvancedFiltersForm.ts index 459df8b8bbdf..6303355a7c05 100644 --- a/src/types/form/SearchAdvancedFiltersForm.ts +++ b/src/types/form/SearchAdvancedFiltersForm.ts @@ -7,7 +7,7 @@ const INPUT_IDS = { DATE_AFTER: 'dateAfter', DATE_BEFORE: 'dateBefore', CATEGORY: 'category', - POLICY_ID: 'policyId', + POLICY_ID: 'policyID', } as const; type InputID = ValueOf; From ee9da9f869ca5204d4ab6952d3924951fef3f768 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Tue, 30 Jul 2024 10:06:36 +0200 Subject: [PATCH 10/14] Improve catgeory filter UI --- .../SelectionList/SelectableListItem.tsx | 86 +++++++++++++++ .../Search/SearchFiltersCategoryPage.tsx | 101 ++++++++++++++---- 2 files changed, 166 insertions(+), 21 deletions(-) create mode 100644 src/components/SelectionList/SelectableListItem.tsx diff --git a/src/components/SelectionList/SelectableListItem.tsx b/src/components/SelectionList/SelectableListItem.tsx new file mode 100644 index 000000000000..6061d05adbc8 --- /dev/null +++ b/src/components/SelectionList/SelectableListItem.tsx @@ -0,0 +1,86 @@ +import React, {useCallback} from 'react'; +import {View} from 'react-native'; +import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; +import SelectCircle from '@components/SelectCircle'; +import TextWithTooltip from '@components/TextWithTooltip'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; +import BaseListItem from './BaseListItem'; +import type {InviteMemberListItemProps, ListItem} from './types'; + +function SelectableListItem({ + item, + isFocused, + showTooltip, + isDisabled, + canSelectMultiple, + onSelectRow, + onCheckboxPress, + onDismissError, + onFocus, + shouldSyncFocus, +}: InviteMemberListItemProps) { + const styles = useThemeStyles(); + const handleCheckboxPress = useCallback(() => { + if (onCheckboxPress) { + onCheckboxPress(item); + } else { + onSelectRow(item); + } + }, [item, onCheckboxPress, onSelectRow]); + + return ( + + <> + + + + + + {!!item.rightElement && item.rightElement} + {canSelectMultiple && !item.isDisabled && ( + + + + )} + + + ); +} + +SelectableListItem.displayName = 'SelectableListItem'; + +export default SelectableListItem; diff --git a/src/pages/Search/SearchFiltersCategoryPage.tsx b/src/pages/Search/SearchFiltersCategoryPage.tsx index bbfe69d533ae..ee1055884353 100644 --- a/src/pages/Search/SearchFiltersCategoryPage.tsx +++ b/src/pages/Search/SearchFiltersCategoryPage.tsx @@ -1,15 +1,19 @@ -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import Button from '@components/Button'; import type {FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; -import RadioListItem from '@components/SelectionList/RadioListItem'; +import SelectableListItem from '@components/SelectionList/SelectableListItem'; +import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import localeCompare from '@libs/LocaleCompare'; +import type {CategorySection} from '@libs/OptionsListUtils'; +import Navigation from '@navigation/Navigation'; import * as SearchActions from '@userActions/Search'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -17,14 +21,18 @@ function SearchFiltersCategoryPage() { const styles = useThemeStyles(); const {translate} = useLocalize(); + const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); + const [noResultsFound, setNoResultsFound] = useState(false); + const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM); const activeItems = searchAdvancedFiltersForm?.category; + const [newCategories, setNewCategories] = useState(activeItems ?? []); const policyID = searchAdvancedFiltersForm?.policyID ?? '-1'; const [allPolicyIdCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES); const singlePolicyCategories = allPolicyIdCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]; - const categoryList = useMemo(() => { + const categoryNames = useMemo(() => { let categories: string[] = []; if (!singlePolicyCategories) { categories = Object.values(allPolicyIdCategories ?? {}) @@ -34,22 +42,69 @@ function SearchFiltersCategoryPage() { categories = Object.values(singlePolicyCategories ?? {}).map((value) => value.name); } - const categoryNames = [...new Set(categories)]; + return [...new Set(categories)]; + }, [allPolicyIdCategories, singlePolicyCategories]); - return categoryNames + const sections = useMemo(() => { + const newSections: CategorySection[] = []; + const chosenCategories = newCategories + .filter((category) => category.toLowerCase().includes(debouncedSearchTerm.toLowerCase())) .sort((a, b) => localeCompare(a, b)) .map((name) => ({ text: name, keyForList: name, - isSelected: activeItems?.includes(name) ?? false, - value: name, + isSelected: newCategories?.includes(name) ?? false, })); - }, [activeItems, allPolicyIdCategories, singlePolicyCategories]); + const remainingCategories = categoryNames + .filter((category) => newCategories.includes(category) === false) + .filter((category) => category.toLowerCase().includes(debouncedSearchTerm.toLowerCase())) + .sort((a, b) => localeCompare(a, b)) + .map((name) => ({ + text: name, + keyForList: name, + isSelected: newCategories?.includes(name) ?? false, + })); + if (chosenCategories.length === 0 && remainingCategories.length === 0) { + setNoResultsFound(true); + } else { + setNoResultsFound(false); + } + newSections.push({ + title: undefined, + data: chosenCategories, + shouldShow: chosenCategories.length > 0, + }); + newSections.push({ + title: translate('common.category'), + data: remainingCategories, + shouldShow: remainingCategories.length > 0, + }); + return newSections; + }, [categoryNames, newCategories, translate, debouncedSearchTerm]); - const updateCategory = (values: Partial>) => { + const updateCategory = useCallback((values: Partial>) => { SearchActions.updateAdvancedFilters(values); - }; + }, []); + + const handleConfirmSelection = useCallback(() => { + updateCategory({ + category: newCategories.sort((a, b) => localeCompare(a, b)), + }); + Navigation.goBack(); + }, [newCategories, updateCategory]); + const footerContent = useMemo( + () => ( +