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
Comment thread
mateuuszzzzz marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
import lodashGet from 'lodash/get';
import React, {useMemo} from 'react';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import type {OnyxEntry} from 'react-native-onyx';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {defaultProps, propTypes} from './categoryPickerPropTypes';
import type * as OnyxTypes from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import SelectionList from './SelectionList';
import RadioListItem from './SelectionList/RadioListItem';
import type {ListItem} from './SelectionList/types';

function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedCategories, onSubmit}) {
type CategoryPickerOnyxProps = {
policyCategories: OnyxEntry<OnyxTypes.PolicyCategories>;
policyRecentlyUsedCategories: OnyxEntry<OnyxTypes.RecentlyUsedCategories>;
};

type CategoryPickerProps = CategoryPickerOnyxProps & {
/** It's used by withOnyx HOC */
// eslint-disable-next-line react/no-unused-prop-types
policyID: string;
selectedCategory: string;
onSubmit: (item: ListItem) => void;
};

function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedCategories, onSubmit}: CategoryPickerProps) {
const {translate} = useLocalize();
const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');

Expand All @@ -30,8 +44,8 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC
];
}, [selectedCategory]);

const [sections, headerMessage, policyCategoriesCount, shouldShowTextInput] = useMemo(() => {
const validPolicyRecentlyUsedCategories = _.filter(policyRecentlyUsedCategories, (p) => !_.isEmpty(p));
const [sections, headerMessage, shouldShowTextInput] = useMemo(() => {
const validPolicyRecentlyUsedCategories = policyRecentlyUsedCategories?.filter((p) => !isEmptyObject(p));
const {categoryOptions} = OptionsListUtils.getFilteredOptions(
{},
{},
Expand All @@ -42,44 +56,40 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC
false,
false,
true,
policyCategories,
policyCategories ?? {},
validPolicyRecentlyUsedCategories,
false,
);

const header = OptionsListUtils.getHeaderMessageForNonUserList(lodashGet(categoryOptions, '[0].data', []).length > 0, debouncedSearchValue);
const policiesCount = OptionsListUtils.getEnabledCategoriesCount(_.values(policyCategories));
const isCategoriesCountBelowThreshold = policyCategoriesCount < CONST.CATEGORY_LIST_THRESHOLD;
const categoryData = categoryOptions?.[0]?.data ?? [];
const header = OptionsListUtils.getHeaderMessageForNonUserList(categoryData.length > 0, debouncedSearchValue);
const policiesCount = OptionsListUtils.getEnabledCategoriesCount(policyCategories ?? {});
const isCategoriesCountBelowThreshold = policiesCount < CONST.CATEGORY_LIST_THRESHOLD;
const showInput = !isCategoriesCountBelowThreshold;

return [categoryOptions, header, policiesCount, showInput];
}, [policyCategories, policyRecentlyUsedCategories, debouncedSearchValue, selectedOptions]);
return [categoryOptions, header, showInput];
}, [policyRecentlyUsedCategories, debouncedSearchValue, selectedOptions, policyCategories]);

const selectedOptionKey = useMemo(
() => lodashGet(_.filter(lodashGet(sections, '[0].data', []), (category) => category.searchText === selectedCategory)[0], 'keyForList'),
[sections, selectedCategory],
);
const selectedOptionKey = useMemo(() => (sections?.[0]?.data ?? []).filter((category) => category.searchText === selectedCategory)[0]?.keyForList, [sections, selectedCategory]);

return (
<SelectionList
sections={sections}
headerMessage={headerMessage}
textInputValue={searchValue}
textInputLabel={shouldShowTextInput && translate('common.search')}
textInputLabel={shouldShowTextInput ? translate('common.search') : undefined}
onChangeText={setSearchValue}
onSelectRow={onSubmit}
ListItem={RadioListItem}
initiallyFocusedOptionKey={selectedOptionKey}
initiallyFocusedOptionKey={selectedOptionKey ?? undefined}
isRowMultilineSupported
/>
);
}

CategoryPicker.displayName = 'CategoryPicker';
CategoryPicker.propTypes = propTypes;
CategoryPicker.defaultProps = defaultProps;

export default withOnyx({
export default withOnyx<CategoryPickerProps, CategoryPickerOnyxProps>({
policyCategories: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`,
},
Expand Down
30 changes: 0 additions & 30 deletions src/components/CategoryPicker/categoryPickerPropTypes.js

This file was deleted.

6 changes: 3 additions & 3 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type ListItem = {
text: string;

/** Alternate text to display */
alternateText?: string;
alternateText?: string | null;

/** Key used internally by React */
keyForList: string;
Expand All @@ -65,7 +65,7 @@ type ListItem = {
accountID?: number | null;

/** User login */
login?: string;
login?: string | null;

/** Element to show on the right side of the item */
rightElement?: ReactNode;
Expand All @@ -88,7 +88,7 @@ type ListItem = {
index?: number;

/** Whether this option should show subscript */
shouldShowSubscript?: boolean;
shouldShowSubscript?: boolean | null;

/** Whether to wrap long text up to 2 lines */
isMultilineSupported?: boolean;
Expand Down
31 changes: 25 additions & 6 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ type Tag = {

type Option = Partial<ReportUtils.OptionData>;

/**
* A narrowed version of `Option` is used when we have a guarantee that given values exist.
*/
type OptionTree = {
text: string;
keyForList: string;
searchText: string;
tooltipText: string;
isDisabled: boolean;
isSelected: boolean;
} & Option;

type PayeePersonalDetails = {
text: string;
alternateText: string;
Expand All @@ -72,13 +84,20 @@ type PayeePersonalDetails = {
keyForList: string;
};

type CategorySection = {
type CategorySectionBase = {
title: string | undefined;
shouldShow: boolean;
indexOffset: number;
};

type CategorySection = CategorySectionBase & {
data: Option[];
};

type CategoryTreeSection = CategorySectionBase & {
data: OptionTree[];
};

type Category = {
name: string;
enabled: boolean;
Expand Down Expand Up @@ -143,7 +162,7 @@ type GetOptions = {
personalDetails: ReportUtils.OptionData[];
userToInvite: ReportUtils.OptionData | null;
currentUserOption: ReportUtils.OptionData | null | undefined;
categoryOptions: CategorySection[];
categoryOptions: CategoryTreeSection[];
tagOptions: CategorySection[];
taxRatesOptions: CategorySection[];
};
Expand Down Expand Up @@ -891,8 +910,8 @@ function sortTags(tags: Record<string, Tag> | Tag[]) {
* @param options[].name - a name of an option
* @param [isOneLine] - a flag to determine if text should be one line
*/
function getCategoryOptionTree(options: Record<string, Category> | Category[], isOneLine = false): Option[] {
const optionCollection = new Map<string, Option>();
function getCategoryOptionTree(options: Record<string, Category> | Category[], isOneLine = false): OptionTree[] {
const optionCollection = new Map<string, OptionTree>();
Object.values(options).forEach((option) => {
if (isOneLine) {
if (optionCollection.has(option.name)) {
Expand Down Expand Up @@ -943,11 +962,11 @@ function getCategoryListSections(
selectedOptions: Category[],
searchInputValue: string,
maxRecentReportsToShow: number,
): CategorySection[] {
): CategoryTreeSection[] {
const sortedCategories = sortCategories(categories);
const enabledCategories = Object.values(sortedCategories).filter((category) => category.enabled);

const categorySections: CategorySection[] = [];
const categorySections: CategoryTreeSection[] = [];
const numberOfCategories = enabledCategories.length;

let indexOffset = 0;
Expand Down