Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions src/components/Search/FilterDropdowns/GroupByPopup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React, {useCallback, useMemo, useState} from 'react';
import {View} from 'react-native';
import Button from '@components/Button';
import type {SearchGroupBy} from '@components/Search/types';
import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem';
import SelectionListWithSections from '@components/SelectionList/SelectionListWithSections';
import type {ListItem} from '@components/SelectionList/types';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import type {GroupBySection} from '@libs/SearchUIUtils';
import CONST from '@src/CONST';

type GroupByPopupItem = {
text: string;
value: SearchGroupBy;
};

type GroupByPopupProps = {
/** The label to show when in an overlay on mobile */
label?: string;

/** The grouped options to show in the list */
sections: GroupBySection[];

/** The currently selected item */
value: GroupByPopupItem | null;

/** Function to call to close the overlay when changes are applied */
closeOverlay: () => void;

/** Function to call when changes are applied */
onChange: (item: GroupByPopupItem | null) => void;
};

function GroupByPopup({label, value, sections, closeOverlay, onChange}: GroupByPopupProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {isSmallScreenWidth} = useResponsiveLayout();
const {windowHeight} = useWindowDimensions();
const [selectedItem, setSelectedItem] = useState(value);

const allSelectableItems = useMemo(() => sections.flatMap((section) => section.options), [sections]);

const listSections = useMemo(
() =>
sections.map((section) => ({
sectionIndex: section.sectionIndex,
...(section.sectionIndex > 0 ? {customHeader: <View style={styles.dividerLine} />} : {}),
data: section.options.map<ListItem<SearchGroupBy>>((item) => ({
text: item.text,
keyForList: item.value,
isSelected: item.value === selectedItem?.value,
})),
})),
[sections, selectedItem?.value, styles.dividerLine],
);

const optionsCount = Math.max(
1,
listSections.reduce((count, section) => count + section.data.length + (section.data.length > 0 && !!section.customHeader ? 1 : 0), 0),
);

const updateSelectedItem = useCallback(
(item: ListItem) => {
const newItem = allSelectableItems.find((option) => option.value === item.keyForList) ?? null;
setSelectedItem(newItem);
},
[allSelectableItems],
);

const applyChanges = useCallback(() => {
onChange(selectedItem);
closeOverlay();
}, [closeOverlay, onChange, selectedItem]);

const resetChanges = useCallback(() => {
onChange(null);
closeOverlay();
}, [closeOverlay, onChange]);

return (
<View style={[!isSmallScreenWidth && styles.pv4, styles.gap2]}>
{isSmallScreenWidth && !!label && <Text style={[styles.textLabel, styles.textSupporting, styles.ph5, styles.pv1]}>{label}</Text>}

<View style={[styles.getSelectionListPopoverHeight(optionsCount, windowHeight, false)]}>
<SelectionListWithSections
sections={listSections}
shouldSingleExecuteRowSelect
ListItem={SingleSelectListItem}
onSelectRow={updateSelectedItem}
/>
</View>

<View style={[styles.flexRow, styles.gap2, styles.ph5]}>
<Button
medium
style={[styles.flex1]}
text={translate('common.reset')}
onPress={resetChanges}
sentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_RESET_SINGLE_SELECT}
/>
<Button
success
medium
style={[styles.flex1]}
text={translate('common.apply')}
onPress={applyChanges}
sentryLabel={CONST.SENTRY_LABEL.SEARCH.FILTER_POPUP_APPLY_SINGLE_SELECT}
/>
</View>
</View>
);
}

export default GroupByPopup;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {OnyxCollection} from 'react-native-onyx';
import {usePersonalDetails} from '@components/OnyxListItemProvider';
import type {SearchDateValues} from '@components/Search/FilterComponents/DatePresetFilterBase';
import type {PopoverComponentProps} from '@components/Search/FilterDropdowns/DropdownButton';
import GroupByPopup from '@components/Search/FilterDropdowns/GroupByPopup';
import type {MultiSelectItem} from '@components/Search/FilterDropdowns/MultiSelectPopup';
import MultiSelectPopup from '@components/Search/FilterDropdowns/MultiSelectPopup';
import SingleSelectPopup from '@components/Search/FilterDropdowns/SingleSelectPopup';
Expand Down Expand Up @@ -33,6 +34,7 @@ import {
filterValidHasValues,
getFeedOptions,
getGroupByOptions,
getGroupBySections,
getGroupCurrencyOptions,
getHasOptions,
getStatusOptions,
Expand Down Expand Up @@ -165,6 +167,7 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON, isMobileSelectionModeEn
const type = typeOptions.find((option) => option.value === unsafeType) ?? null;

const groupByOptions = getGroupByOptions(translate);
const groupBySections = getGroupBySections(translate);
const groupBy = groupByOptions.find((option) => option.value === unsafeGroupBy) ?? null;

const viewOptions = getViewOptions(translate);
Expand Down Expand Up @@ -283,9 +286,9 @@ function useSearchFiltersBar(queryJSON: SearchQueryJSON, isMobileSelectionModeEn
);

const groupByComponent = ({closeOverlay}: PopoverComponentProps) => (
<SingleSelectPopup
<GroupByPopup
label={translate('search.groupBy')}
items={groupByOptions}
sections={groupBySections}
value={groupBy}
closeOverlay={closeOverlay}
onChange={(item) => {
Expand Down
31 changes: 30 additions & 1 deletion src/libs/SearchUIUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ import ViolationsUtils from './Violations/ViolationsUtils';

type ColumnSortMapping<T> = Partial<Record<SearchColumnType, keyof T | null>>;
type ColumnVisibility = Partial<Record<SearchColumnType, boolean>>;
type GroupBySection = {
sectionIndex: number;
options: Array<SingleSelectItem<SearchGroupBy>>;
};

// List Item sorting
type TransactionSorting = ColumnSortMapping<TransactionListItemType>;
Expand Down Expand Up @@ -3989,6 +3993,30 @@ function getGroupByOptions(translate: LocalizedTranslate) {
return Object.values(CONST.SEARCH.GROUP_BY).map<SingleSelectItem<SearchGroupBy>>((value) => ({text: translate(`search.filters.groupBy.${value}`), value}));
}

Comment thread
marufsharifi marked this conversation as resolved.
function getGroupBySections(translate: LocalizedTranslate): GroupBySection[] {
const getOption = (groupBy: SearchGroupBy): SingleSelectItem<SearchGroupBy> => ({
text: translate(`search.filters.groupBy.${groupBy}`),
value: groupBy,
});
return [
{
options: [getOption(CONST.SEARCH.GROUP_BY.FROM), getOption(CONST.SEARCH.GROUP_BY.CARD)],
},
{
options: [getOption(CONST.SEARCH.GROUP_BY.CATEGORY), getOption(CONST.SEARCH.GROUP_BY.MERCHANT), getOption(CONST.SEARCH.GROUP_BY.TAG)],
},
{
options: [getOption(CONST.SEARCH.GROUP_BY.MONTH), getOption(CONST.SEARCH.GROUP_BY.WEEK), getOption(CONST.SEARCH.GROUP_BY.YEAR), getOption(CONST.SEARCH.GROUP_BY.QUARTER)],
},
{
options: [getOption(CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID)],
},
].map((section, sectionIndex) => ({
...section,
sectionIndex,
}));
}

function getViewOptions(translate: LocalizedTranslate) {
return Object.values(CONST.SEARCH.VIEW).map<SingleSelectItem<SearchView>>((value) => ({text: translate(`search.view.${value}`), value}));
}
Expand Down Expand Up @@ -4754,6 +4782,7 @@ export {
getStatusOptions,
getTypeOptions,
getGroupByOptions,
getGroupBySections,
getViewOptions,
getGroupCurrencyOptions,
getFeedOptions,
Expand Down Expand Up @@ -4784,4 +4813,4 @@ export {
isEligibleForApproveSuggestion,
applySelectionToItem,
};
export type {SavedSearchMenuItem, SearchTypeMenuSection, SearchTypeMenuItem, SearchDateModifier, SearchDateModifierLower, SearchKey, ArchivedReportsIDSet};
export type {SavedSearchMenuItem, SearchTypeMenuSection, SearchTypeMenuItem, SearchDateModifier, SearchDateModifierLower, SearchKey, ArchivedReportsIDSet, GroupBySection};
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import FixedFooter from '@components/FixedFooter';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import type {SearchGroupBy} from '@components/Search/types';
import SelectionList from '@components/SelectionList';
import SingleSelectListItem from '@components/SelectionList/ListItem/SingleSelectListItem';
import SelectionListWithSections from '@components/SelectionList/SelectionListWithSections';
import type {ListItem} from '@components/SelectionList/types';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
import {updateAdvancedFilters} from '@libs/actions/Search';
import Navigation from '@libs/Navigation/Navigation';
import {getGroupByOptions} from '@libs/SearchUIUtils';
import {getGroupBySections} from '@libs/SearchUIUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';

Expand All @@ -23,14 +23,17 @@ function SearchFiltersGroupByPage() {
const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM);
const [selectedItem, setSelectedItem] = useState(searchAdvancedFiltersForm?.groupBy);

const listData: Array<ListItem<SearchGroupBy>> = useMemo(() => {
return getGroupByOptions(translate).map((groupOption) => ({
text: groupOption.text,
keyForList: groupOption.value,
isSelected: selectedItem === groupOption.value,
const sections = useMemo(() => {
return getGroupBySections(translate).map((section) => ({
sectionIndex: section.sectionIndex,
...(section.sectionIndex > 0 ? {customHeader: <View style={styles.dividerLine} />} : {}),
data: section.options.map<ListItem<SearchGroupBy>>((groupOption) => ({
text: groupOption.text,
keyForList: groupOption.value,
isSelected: selectedItem === groupOption.value,
})),
}));
}, [translate, selectedItem]);

}, [selectedItem, styles.dividerLine, translate]);
const updateSelectedItem = useCallback((type: ListItem<SearchGroupBy>) => {
setSelectedItem(type?.keyForList ?? undefined);
}, []);
Expand Down Expand Up @@ -60,9 +63,9 @@ function SearchFiltersGroupByPage() {
}}
/>
<View style={[styles.flex1]}>
<SelectionList
<SelectionListWithSections
shouldSingleExecuteRowSelect
data={listData}
sections={sections}
ListItem={SingleSelectListItem}
onSelectRow={updateSelectedItem}
/>
Expand Down
Loading