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
31 changes: 27 additions & 4 deletions src/components/CategoryPicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import useThemeStyles from '@hooks/useThemeStyles';
import {getCategoryListSections} from '@libs/CategoryOptionListUtils';
import type {Category} from '@libs/CategoryOptionListUtils';
import {getEnabledCategoriesCount} from '@libs/CategoryUtils';
import {getHeaderMessageForNonUserList} from '@libs/OptionsListUtils';
import {getHeaderMessageForNonUserList, getNoneOption} from '@libs/OptionsListUtils';
import type {OptionTree} from '@libs/OptionsListUtils/types';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -25,6 +26,7 @@ type CategoryPickerProps = {
policyID: string | undefined;
selectedCategory?: string;
onSubmit: (item: ListItem) => void;
shouldShowNoneOption?: boolean;

/**
* If enabled, the content will have a bottom padding equal to account for the safe bottom area inset.
Expand All @@ -46,7 +48,7 @@ const getSelectedOptions = (selectedCategory?: string): Category[] => {
];
};

function CategoryPicker({selectedCategory, policyID, onSubmit, addBottomSafeAreaPadding = false}: CategoryPickerProps) {
function CategoryPicker({selectedCategory, policyID, onSubmit, shouldShowNoneOption = false, addBottomSafeAreaPadding = false}: CategoryPickerProps) {
const styles = useThemeStyles();
const {inputCallbackRef} = useAutoFocusInput();
const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`);
Expand All @@ -71,7 +73,28 @@ function CategoryPicker({selectedCategory, policyID, onSubmit, addBottomSafeArea
translate,
});

const categoryData = sections?.at(0)?.data ?? [];
const noneOption: OptionTree[] = shouldShowNoneOption
? getNoneOption(debouncedSearchValue, !selectedCategory, translate).map(
(option): OptionTree => ({
text: option.text,
keyForList: option.keyForList,
searchText: '',
tooltipText: option.text,
isDisabled: false,
isSelected: option.isSelected,
}),
)
: [];
const noneOptionSection = {
title: '',
data: noneOption,
sectionIndex: -1,
};
const selectedCategorySectionIndex = sections.findIndex((section) => section.data.some((category) => category.searchText === selectedCategory));
const sectionsWithNoneOption =
noneOption.length > 0 ? [...sections.slice(0, selectedCategorySectionIndex + 1), noneOptionSection, ...sections.slice(selectedCategorySectionIndex + 1)] : sections;

const categoryData = sectionsWithNoneOption.flatMap((section) => section.data);
const categoriesCount = getEnabledCategoriesCount(categories);
const selectedOptionKey = categoryData.find((category) => category.searchText === selectedCategory)?.keyForList;

Expand All @@ -87,7 +110,7 @@ function CategoryPicker({selectedCategory, policyID, onSubmit, addBottomSafeArea

return (
<SelectionListWithSections
sections={sections}
sections={sectionsWithNoneOption}
onSelectRow={onSubmit}
ListItem={SingleSelectListItem}
shouldShowTextInput={categoriesCount >= CONST.STANDARD_LIST_ITEM_LIMIT}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';

import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';

import {getDecodedCategoryName} from '@libs/CategoryUtils';
Expand Down Expand Up @@ -35,8 +36,10 @@ type CustomUnitDefaultCategorySelectorProps = {

function CustomUnitDefaultCategorySelector({defaultValue = '', wrapperStyle, label, focused, customUnitID, interactive = true}: CustomUnitDefaultCategorySelectorProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();

const decodedCategoryName = getDecodedCategoryName(defaultValue);
const title = decodedCategoryName || translate('common.none');
const descStyle = decodedCategoryName.length === 0 ? styles.textNormal : null;

const onPress = () => {
Expand All @@ -46,7 +49,7 @@ function CustomUnitDefaultCategorySelector({defaultValue = '', wrapperStyle, lab
return (
<MenuItemWithTopDescription
shouldShowRightIcon={interactive}
title={decodedCategoryName}
title={title}
description={label}
descriptionTextStyle={descStyle}
onPress={onPress}
Expand Down
14 changes: 2 additions & 12 deletions src/components/Search/SearchSingleSelectionPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';

import Navigation from '@libs/Navigation/Navigation';
import {getNoneOption} from '@libs/OptionsListUtils';
import type {OptionData} from '@libs/ReportUtils';
import {sortOptionsWithEmptyValue} from '@libs/SearchQueryUtils';

import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';

Expand Down Expand Up @@ -53,17 +53,7 @@ function SearchSingleSelectionPicker({
}, [initiallySelectedItem]);

const searchLower = debouncedSearchTerm?.toLowerCase();
const noneItem =
allowNoneOption && translate('common.none').toLowerCase().includes(searchLower)
? [
{
text: translate('common.none'),
keyForList: CONST.SEARCH.NONE_OPTION_KEY,
isSelected: !selectedItem?.value,
value: '',
},
]
: [];
const noneItem = allowNoneOption ? getNoneOption(debouncedSearchTerm, !selectedItem?.value, translate) : [];

const initiallySelectedItemSection =
initiallySelectedItem?.name.toLowerCase().includes(searchLower) || initiallySelectedItem?.searchableText?.toLowerCase().includes(searchLower)
Expand Down
17 changes: 17 additions & 0 deletions src/libs/OptionsListUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2979,6 +2979,22 @@ function getHeaderMessageForNonUserList(hasSelectableOptions: boolean, searchVal
return '';
}

function getNoneOption(searchValue: string, isSelected: boolean, translate: LocalizedTranslate) {
const noneText = translate('common.none');
if (!noneText.toLowerCase().includes(searchValue.toLowerCase())) {
return [];
}

return [
{
text: noneText,
keyForList: CONST.SEARCH.NONE_OPTION_KEY,
isSelected,
value: '',
},
];
}

/**
* Helper method to check whether an option can show tooltip or not
*/
Expand Down Expand Up @@ -3397,6 +3413,7 @@ export {
getLastActorDisplayName,
getLastActorDisplayNameFromLastVisibleActions,
getLastMessageTextForReport,
getNoneOption,
getParticipantsOption,
getPersonalDetailsForAccountIDs,
getPolicyExpenseReportOption,
Expand Down
30 changes: 21 additions & 9 deletions src/libs/actions/Policy/Category.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import getIsNarrowLayout from '@libs/getIsNarrowLayout';
import Log from '@libs/Log';
import enhanceParameters from '@libs/Network/enhanceParameters';
import {hasEnabledOptions} from '@libs/OptionsListUtils';
import {goBackWhenEnableFeature} from '@libs/PolicyUtils';
import {goBackWhenEnableFeature, removePendingFieldsFromCustomUnit} from '@libs/PolicyUtils';
import {pushTransactionAutoSelectionsOnyxData, pushTransactionViolationsOnyxData} from '@libs/ReportUtils';

import {getFinishOnboardingTaskOnyxData} from '@userActions/Task';
Expand All @@ -40,7 +40,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy, PolicyCategories, PolicyCategory, Report, ReportAction} from '@src/types/onyx';
import type {ImportFinalModal} from '@src/types/onyx/ImportedSpreadsheet';
import type {ApprovalRule, ExpenseRule, MccGroup} from '@src/types/onyx/Policy';
import type {ApprovalRule, CustomUnit, ExpenseRule, MccGroup} from '@src/types/onyx/Policy';
import type {PolicyCategoryExpenseLimitType} from '@src/types/onyx/PolicyCategory';
import type {OnyxData} from '@src/types/onyx/Request';

Expand Down Expand Up @@ -1454,7 +1454,7 @@ function enablePolicyCategories(policyData: PolicyData, enabled: boolean, should
}
}

function setPolicyCustomUnitDefaultCategory(policyID: string, customUnitID: string, oldCategory: string | undefined, category: string) {
function setPolicyCustomUnitDefaultCategory(policyID: string, customUnitID: string, oldCategory: string | undefined, category: string, customUnit?: CustomUnit) {
const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.POLICY>> = [
{
onyxMethod: Onyx.METHOD.MERGE,
Expand Down Expand Up @@ -1504,13 +1504,25 @@ function setPolicyCustomUnitDefaultCategory(policyID: string, customUnitID: stri
},
];

const params = {
policyID,
customUnitID,
category,
};
const shouldUpdateCustomUnit = category === '' && customUnit !== undefined;
const command = shouldUpdateCustomUnit ? WRITE_COMMANDS.UPDATE_WORKSPACE_CUSTOM_UNIT : WRITE_COMMANDS.SET_CUSTOM_UNIT_DEFAULT_CATEGORY;
const params = shouldUpdateCustomUnit
? {
policyID,
customUnit: JSON.stringify(
removePendingFieldsFromCustomUnit({
...customUnit,
defaultCategory: category,
}),
),
}
: {
policyID,
customUnitID,
category,
};

API.write(WRITE_COMMANDS.SET_CUSTOM_UNIT_DEFAULT_CATEGORY, params, {
API.write(command, params, {
optimisticData,
successData,
failureData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,21 @@ function DynamicDefaultCategorySelectorPage({route}: DynamicDefaultCategorySelec
const styles = useThemeStyles();
const {translate} = useLocalize();
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(policyID)}`);
const currentCategory = policy?.customUnits?.[customUnitID]?.defaultCategory ?? '';
const customUnit = policy?.customUnits?.[customUnitID];
const currentCategory = customUnit?.defaultCategory ?? '';
const backPath = useDynamicBackPath(DYNAMIC_ROUTES.DEFAULT_CATEGORY_SELECTOR.path);

const onCategorySelected = (selectedCategory: ListItem) => {
if (!selectedCategory.searchText) {
const isNoneSelected = selectedCategory.keyForList === CONST.SEARCH.NONE_OPTION_KEY;
if (!isNoneSelected && !selectedCategory.searchText) {
return;
}
if (currentCategory === selectedCategory.searchText) {
const newCategory = isNoneSelected ? '' : (selectedCategory.searchText ?? '');
if (currentCategory === newCategory) {
Navigation.goBack(backPath);
return;
}
setPolicyCustomUnitDefaultCategory(policyID, customUnitID, currentCategory, selectedCategory.searchText);
setPolicyCustomUnitDefaultCategory(policyID, customUnitID, currentCategory, newCategory, customUnit);
Navigation.goBack(backPath);
};

Expand All @@ -67,6 +70,7 @@ function DynamicDefaultCategorySelectorPage({route}: DynamicDefaultCategorySelec
policyID={policyID}
selectedCategory={currentCategory}
onSubmit={onCategorySelected}
shouldShowNoneOption
addBottomSafeAreaPadding
/>
</ScreenWrapper>
Expand Down
Loading