diff --git a/src/components/CategoryPicker/index.tsx b/src/components/CategoryPicker/index.tsx index 737f722ce394..16f811681165 100644 --- a/src/components/CategoryPicker/index.tsx +++ b/src/components/CategoryPicker/index.tsx @@ -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'; @@ -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. @@ -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}`); @@ -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; @@ -87,7 +110,7 @@ function CategoryPicker({selectedCategory, policyID, onSubmit, addBottomSafeArea return ( = CONST.STANDARD_LIST_ITEM_LIMIT} diff --git a/src/components/CustomUnitDefaultCategorySelector/index.tsx b/src/components/CustomUnitDefaultCategorySelector/index.tsx index 45b1e3c8ec5a..3ce21b18d9ee 100644 --- a/src/components/CustomUnitDefaultCategorySelector/index.tsx +++ b/src/components/CustomUnitDefaultCategorySelector/index.tsx @@ -1,5 +1,6 @@ import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import {getDecodedCategoryName} from '@libs/CategoryUtils'; @@ -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 = () => { @@ -46,7 +49,7 @@ function CustomUnitDefaultCategorySelector({defaultValue = '', wrapperStyle, lab return ( > = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -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, diff --git a/src/pages/workspace/categories/DynamicDefaultCategorySelectorPage.tsx b/src/pages/workspace/categories/DynamicDefaultCategorySelectorPage.tsx index 53efdf1c9112..a16e23fd330b 100644 --- a/src/pages/workspace/categories/DynamicDefaultCategorySelectorPage.tsx +++ b/src/pages/workspace/categories/DynamicDefaultCategorySelectorPage.tsx @@ -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); }; @@ -67,6 +70,7 @@ function DynamicDefaultCategorySelectorPage({route}: DynamicDefaultCategorySelec policyID={policyID} selectedCategory={currentCategory} onSubmit={onCategorySelected} + shouldShowNoneOption addBottomSafeAreaPadding />