Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Category settings: Default categories follow-up #49030

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
6 changes: 1 addition & 5 deletions src/components/CategoryPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,9 @@ type CategoryPickerProps = CategoryPickerOnyxProps & {
policyID: string;
selectedCategory?: string;
onSubmit: (item: ListItem) => void;

/** Whether SectionList should use custom ScrollView */
shouldUseCustomScrollView?: boolean;
};

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

Expand Down Expand Up @@ -87,7 +84,6 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC
ListItem={RadioListItem}
initiallyFocusedOptionKey={selectedOptionKey ?? undefined}
isRowMultilineSupported
shouldUseCustomScrollView={shouldUseCustomScrollView}
/>
);
}
Expand Down
8 changes: 1 addition & 7 deletions src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native';
import isEmpty from 'lodash/isEmpty';
import type {ForwardedRef} from 'react';
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import type {LayoutChangeEvent, SectionList as RNSectionList, TextInput as RNTextInput, ScrollViewProps, SectionListData, SectionListRenderItemInfo} from 'react-native';
import type {LayoutChangeEvent, SectionList as RNSectionList, TextInput as RNTextInput, SectionListData, SectionListRenderItemInfo} from 'react-native';
import {View} from 'react-native';
import Button from '@components/Button';
import Checkbox from '@components/Checkbox';
import FixedFooter from '@components/FixedFooter';
import OptionsListSkeletonView from '@components/OptionsListSkeletonView';
import {PressableWithFeedback} from '@components/Pressable';
import SafeAreaConsumer from '@components/SafeAreaConsumer';
import ScrollView from '@components/ScrollView';
import SectionList from '@components/SectionList';
import ShowMoreButton from '@components/ShowMoreButton';
import Text from '@components/Text';
Expand Down Expand Up @@ -74,7 +73,6 @@ function BaseSelectionList<TItem extends ListItem>(
shouldStopPropagation = false,
shouldShowTooltips = true,
shouldUseDynamicMaxToRenderPerBatch = false,
shouldUseCustomScrollView = false,
rightHandSideComponent,
isLoadingNewOptions = false,
onLayout,
Expand Down Expand Up @@ -426,9 +424,6 @@ function BaseSelectionList<TItem extends ListItem>(
</>
);

// eslint-disable-next-line react/jsx-props-no-spreading
const scrollComponent = shouldUseCustomScrollView ? (props: ScrollViewProps) => <ScrollView {...props} /> : undefined;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the workaround I've added previously


const renderItem = ({item, index, section}: SectionListRenderItemInfo<TItem, SectionWithIndexOffset<TItem>>) => {
const normalizedIndex = index + (section?.indexOffset ?? 0);
const isDisabled = !!section.isDisabled || item.isDisabled;
Expand Down Expand Up @@ -708,7 +703,6 @@ function BaseSelectionList<TItem extends ListItem>(
{!listHeaderContent && header()}
<SectionList
removeClippedSubviews={removeClippedSubviews}
renderScrollComponent={scrollComponent}
ref={listRef}
sections={slicedSections}
stickySectionHeadersEnabled={false}
Expand Down
3 changes: 0 additions & 3 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,6 @@ type BaseSelectionListProps<TItem extends ListItem> = Partial<ChildrenProps> & {
/** Whether to use dynamic maxToRenderPerBatch depending on the visible number of elements */
shouldUseDynamicMaxToRenderPerBatch?: boolean;

/** Whether SectionList should use custom ScrollView */
shouldUseCustomScrollView?: boolean;

/** Whether keyboard shortcuts should be disabled */
disableKeyboardShortcuts?: boolean;

Expand Down
45 changes: 12 additions & 33 deletions src/pages/workspace/categories/SpendCategorySelectorListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,35 @@
import React, {useState} from 'react';
import type {SetOptional} from 'type-fest';
import React from 'react';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import BaseListItem from '@components/SelectionList/BaseListItem';
import type {BaseListItemProps, ListItem} from '@components/SelectionList/types';
import useThemeStyles from '@hooks/useThemeStyles';
import CategorySelector from '@pages/workspace/distanceRates/CategorySelector';
import * as Policy from '@userActions/Policy/Policy';

type SpendCategorySelectorListItemProps<TItem extends ListItem> = SetOptional<BaseListItemProps<TItem>, 'onSelectRow'>;

function SpendCategorySelectorListItem<TItem extends ListItem>({item, onSelectRow = () => {}, isFocused}: SpendCategorySelectorListItemProps<TItem>) {
function SpendCategorySelectorListItem<TItem extends ListItem>({item, onSelectRow, isFocused}: BaseListItemProps<TItem>) {
const styles = useThemeStyles();
const [isCategoryPickerVisible, setIsCategoryPickerVisible] = useState(false);
const {policyID, groupID, categoryID} = item;
const {groupID, categoryID} = item;

if (!policyID || !groupID) {
if (!groupID) {
return;
}

const onSelect = (data: TItem) => {
setIsCategoryPickerVisible(true);
onSelectRow(data);
};

const setNewCategory = (selectedCategory: ListItem) => {
if (!selectedCategory.keyForList) {
return;
}
Policy.setWorkspaceDefaultSpendCategory(policyID, groupID, selectedCategory.keyForList);
};

return (
<BaseListItem
item={item}
wrapperStyle={[isFocused && styles.sidebarLinkActive]}
pressableStyle={[styles.mt2]}
onSelectRow={onSelect}
onSelectRow={onSelectRow}
isFocused={isFocused}
showTooltip
keyForList={item.keyForList}
>
<CategorySelector
<MenuItemWithTopDescription
shouldShowRightIcon
title={categoryID}
description={groupID[0].toUpperCase() + groupID.slice(1)}
descriptionTextStyle={[styles.textNormal]}
wrapperStyle={[styles.ph5]}
onPress={() => onSelectRow(item)}
focused={isFocused}
policyID={policyID}
label={groupID[0].toUpperCase() + groupID.slice(1)}
defaultValue={categoryID}
setNewCategory={setNewCategory}
isPickerVisible={isCategoryPickerVisible}
showPickerModal={() => setIsCategoryPickerVisible(true)}
hidePickerModal={() => {
setIsCategoryPickerVisible(false);
}}
/>
</BaseListItem>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, {useMemo} from 'react';
import React, {useMemo, useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
import SelectionList from '@components/SelectionList';
import type {ListItem} from '@components/SelectionList/types';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
Expand All @@ -12,6 +12,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import CategorySelectorModal from '@pages/workspace/distanceRates/CategorySelector/CategorySelectorModal';
import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow';
Expand All @@ -32,42 +33,49 @@ function WorkspaceCategoriesSettingsPage({policy, route}: WorkspaceCategoriesSet
const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`);
const [currentPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
const currentConnectionName = PolicyUtils.getCurrentConnectionName(policy);
const [isSelectorModalVisible, setIsSelectorModalVisible] = useState(false);
const [categoryID, setCategoryID] = useState<string>();
const [groupID, setGroupID] = useState<string>();

const toggleSubtitle = isConnectedToAccounting && currentConnectionName ? `${translate('workspace.categories.needCategoryForExportToIntegration')} ${currentConnectionName}.` : undefined;

const updateWorkspaceRequiresCategory = (value: boolean) => {
setWorkspaceRequiresCategory(policyID, value);
};

const policyMccGroup = currentPolicy?.mccGroup;
const listItems = useMemo(() => {
let data: ListItem[] = [];

if (policyMccGroup) {
data = Object.entries(policyMccGroup).map(
([mccKey, mccGroup]) =>
({
categoryID: mccGroup.category,
keyForList: mccKey,
groupID: mccKey,
policyID,
tabIndex: -1,
} as ListItem),
);
const {sections} = useMemo(() => {
if (!(currentPolicy && currentPolicy.mccGroup)) {
return {sections: [{data: []}]};
}

return data.map((item) => (
<SpendCategorySelectorListItem
key={item.keyForList}
item={item}
showTooltip
/>
));
}, [policyMccGroup, policyID]);
return {
sections: [
{
data: Object.entries(currentPolicy.mccGroup).map(
([mccKey, mccGroup]) =>
({
categoryID: mccGroup.category,
keyForList: mccKey,
groupID: mccKey,
tabIndex: -1,
} as ListItem),
),
},
],
};
}, [currentPolicy]);

const hasEnabledOptions = OptionsListUtils.hasEnabledOptions(policyCategories ?? {});
const isToggleDisabled = !policy?.areCategoriesEnabled || !hasEnabledOptions || isConnectedToAccounting;

const setNewCategory = (selectedCategory: ListItem) => {
if (!selectedCategory.keyForList || !groupID) {
return;
}
Policy.setWorkspaceDefaultSpendCategory(policyID, groupID, selectedCategory.keyForList);
setIsSelectorModalVisible(false);
};

return (
<AccessOrNotFoundWrapper
policyID={policyID}
Expand All @@ -94,14 +102,35 @@ function WorkspaceCategoriesSettingsPage({policy, route}: WorkspaceCategoriesSet
shouldPlaceSubtitleBelowSwitch
/>
<View style={[styles.containerWithSpaceBetween]}>
{!!currentPolicy && listItems.length > 0 && canUseWorkspaceRules && (
<>
<View style={[styles.mh5, styles.mt2, styles.mb1]}>
<Text style={[styles.headerText]}>{translate('workspace.categories.defaultSpendCategories')}</Text>
<Text style={[styles.mt1, styles.lh20]}>{translate('workspace.categories.spendCategoriesDescription')}</Text>
</View>
<ScrollView>{listItems}</ScrollView>
</>
{canUseWorkspaceRules && !!currentPolicy && sections[0].data.length > 0 && (
<SelectionList
headerContent={
<View style={[styles.mh5, styles.mt2, styles.mb1]}>
<Text style={[styles.headerText]}>{translate('workspace.categories.defaultSpendCategories')}</Text>
<Text style={[styles.mt1, styles.lh20]}>{translate('workspace.categories.spendCategoriesDescription')}</Text>
</View>
}
sections={sections}
ListItem={SpendCategorySelectorListItem}
onSelectRow={(item) => {
if (!item.groupID || !item.categoryID) {
return;
}
setIsSelectorModalVisible(true);
setCategoryID(item.categoryID);
setGroupID(item.groupID);
}}
/>
)}
{canUseWorkspaceRules && categoryID && groupID && (
<CategorySelectorModal
policyID={policyID}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the modal here to avoid SectionList nesting, which was causing a lot of problems, especially scrolling issues.

isVisible={isSelectorModalVisible}
currentCategory={categoryID}
onClose={() => setIsSelectorModalVisible(false)}
onCategorySelected={setNewCategory}
label={groupID[0].toUpperCase() + groupID.slice(1)}
/>
)}
</View>
</ScreenWrapper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,9 @@ type CategorySelectorModalProps = {

/** Label to display on field */
label: string;

/** Whether SectionList should use custom ScrollView */
shouldUseCustomScrollView: boolean;
};

function CategorySelectorModal({policyID, isVisible, currentCategory, onCategorySelected, onClose, label, shouldUseCustomScrollView}: CategorySelectorModalProps) {
function CategorySelectorModal({policyID, isVisible, currentCategory, onCategorySelected, onClose, label}: CategorySelectorModalProps) {
const styles = useThemeStyles();

return (
Expand All @@ -57,7 +54,6 @@ function CategorySelectorModal({policyID, isVisible, currentCategory, onCategory
policyID={policyID}
selectedCategory={currentCategory}
onSubmit={onCategorySelected}
shouldUseCustomScrollView={shouldUseCustomScrollView}
/>
</ScreenWrapper>
</Modal>
Expand Down
17 changes: 1 addition & 16 deletions src/pages/workspace/distanceRates/CategorySelector/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,9 @@ type CategorySelectorProps = {

/** Callback to hide category picker */
hidePickerModal: () => void;

/** Whether SectionList should use custom ScrollView */
shouldUseCustomScrollView?: boolean;
};

function CategorySelector({
defaultValue = '',
wrapperStyle,
label,
setNewCategory,
policyID,
focused,
isPickerVisible,
showPickerModal,
hidePickerModal,
shouldUseCustomScrollView = false,
}: CategorySelectorProps) {
function CategorySelector({defaultValue = '', wrapperStyle, label, setNewCategory, policyID, focused, isPickerVisible, showPickerModal, hidePickerModal}: CategorySelectorProps) {
const styles = useThemeStyles();

const updateCategoryInput = (categoryItem: ListItem) => {
Expand Down Expand Up @@ -78,7 +64,6 @@ function CategorySelector({
onClose={hidePickerModal}
onCategorySelected={updateCategoryInput}
label={label}
shouldUseCustomScrollView={shouldUseCustomScrollView}
/>
</View>
);
Expand Down
Loading