diff --git a/src/components/Switch.tsx b/src/components/Switch.tsx index fd9d9ae315ff..684d5e416471 100644 --- a/src/components/Switch.tsx +++ b/src/components/Switch.tsx @@ -14,6 +14,9 @@ type SwitchProps = { /** Accessibility label for the switch */ accessibilityLabel: string; + + /** Whether the switch is disabled */ + disabled?: boolean; }; const OFFSET_X = { @@ -21,7 +24,7 @@ const OFFSET_X = { ON: 20, }; -function Switch({isOn, onToggle, accessibilityLabel}: SwitchProps) { +function Switch({isOn, onToggle, accessibilityLabel, disabled}: SwitchProps) { const styles = useThemeStyles(); const offsetX = useRef(new Animated.Value(isOn ? OFFSET_X.ON : OFFSET_X.OFF)); @@ -35,6 +38,7 @@ function Switch({isOn, onToggle, accessibilityLabel}: SwitchProps) { return ( onToggle(!isOn)} onLongPress={() => onToggle(!isOn)} diff --git a/src/languages/en.ts b/src/languages/en.ts index 3718db667e3d..e216c586c078 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1776,18 +1776,23 @@ export default { }, categories: { deleteCategories: 'Delete categories', + deleteCategoriesPrompt: 'Are you sure you want to delete these categories?', + deleteCategory: 'Delete category', + deleteCategoryPrompt: 'Are you sure you want to delete this category?', disableCategories: 'Disable categories', + disableCategory: 'Disable category', enableCategories: 'Enable categories', + enableCategory: 'Enable category', deleteFailureMessage: 'An error occurred while deleting the category, please try again.', categoryName: 'Category name', requiresCategory: 'Members must categorize all spend', - enableCategory: 'Enable category', subtitle: 'Get a better overview of where money is being spent. Use our default categories or add your own.', emptyCategories: { title: "You haven't created any categories", subtitle: 'Add a category to organize your spend.', }, - genericFailureMessage: 'An error occurred while updating the category, please try again.', + updateFailureMessage: 'An error occurred while updating the category, please try again.', + createFailureMessage: 'An error occurred while creating the category, please try again.', addCategory: 'Add category', editCategory: 'Edit category', categoryRequiredError: 'Category name is required.', diff --git a/src/languages/es.ts b/src/languages/es.ts index c8f08b5278cd..507b11bf45ab 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1800,18 +1800,23 @@ export default { }, categories: { deleteCategories: 'Eliminar categorías', + deleteCategoriesPrompt: '¿Estás seguro de que quieres eliminar estas categorías?', + deleteCategory: 'Eliminar categoría', + deleteCategoryPrompt: '¿Estás seguro de que quieres eliminar esta categoría?', disableCategories: 'Desactivar categorías', + disableCategory: 'Desactivar categoría', enableCategories: 'Activar categorías', + enableCategory: 'Activar categoría', deleteFailureMessage: 'Se ha producido un error al intentar eliminar la categoría. Por favor, inténtalo más tarde.', categoryName: 'Nombre de la categoría', requiresCategory: 'Los miembros deben categorizar todos los gastos', - enableCategory: 'Activar categoría', subtitle: 'Obtén una visión general de dónde te gastas el dinero. Utiliza las categorías predeterminadas o añade las tuyas propias.', emptyCategories: { title: 'No has creado ninguna categoría', subtitle: 'Añade una categoría para organizar tu gasto.', }, - genericFailureMessage: 'Se ha producido un error al intentar eliminar la categoría. Por favor, inténtalo más tarde.', + updateFailureMessage: 'Se ha producido un error al intentar eliminar la categoría. Por favor, inténtalo más tarde.', + createFailureMessage: 'Se ha producido un error al intentar crear la categoría. Por favor, inténtalo más tarde.', addCategory: 'Añadir categoría', editCategory: 'Editar categoría', categoryRequiredError: 'Lo nombre de la categoría es obligatorio.', diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index c230116fb643..2cb4801e09c6 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -2707,7 +2707,7 @@ function setWorkspaceCategoryEnabled(policyID: string, categoriesToUpdate: Recor acc[key] = { ...policyCategories[key], ...categoriesToUpdate[key], - errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.genericFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.updateFailureMessage'), pendingFields: { enabled: null, }, @@ -2762,7 +2762,7 @@ function createPolicyCategory(policyID: string, categoryName: string) { key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, value: { [categoryName]: { - errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.genericFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.createFailureMessage'), pendingAction: null, }, }, @@ -2823,7 +2823,7 @@ function renamePolicyCategory(policyID: string, policyCategory: {oldName: string ...policyCategoryToUpdate, name: policyCategory.oldName, unencodedName: decodeURIComponent(policyCategory.oldName), - errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.genericFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.updateFailureMessage'), pendingAction: null, }, }, @@ -3055,7 +3055,7 @@ function setWorkspaceRequiresCategory(policyID: string, requiresCategory: boolea key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, value: { requiresCategory: !requiresCategory, - errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.genericFailureMessage'), + errors: ErrorUtils.getMicroSecondOnyxError('workspace.categories.updateFailureMessage'), pendingFields: { requiresCategory: null, }, diff --git a/src/pages/workspace/categories/CategoryForm.tsx b/src/pages/workspace/categories/CategoryForm.tsx index aaf954f64468..304f4153df8c 100644 --- a/src/pages/workspace/categories/CategoryForm.tsx +++ b/src/pages/workspace/categories/CategoryForm.tsx @@ -80,7 +80,6 @@ function CategoryForm({onSubmit, policyCategories, categoryName}: CategoryFormPr accessibilityLabel={translate('common.name')} inputID={INPUT_IDS.CATEGORY_NAME} role={CONST.ROLE.PRESENTATION} - autoFocus /> ); diff --git a/src/pages/workspace/categories/CategorySettingsPage.tsx b/src/pages/workspace/categories/CategorySettingsPage.tsx index 7300f7c5a9d4..1fa3fdcdd042 100644 --- a/src/pages/workspace/categories/CategorySettingsPage.tsx +++ b/src/pages/workspace/categories/CategorySettingsPage.tsx @@ -1,9 +1,11 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React from 'react'; +import React, {useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; +import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -11,6 +13,7 @@ import Switch from '@components/Switch'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import {setWorkspaceCategoryEnabled} from '@libs/actions/Policy'; import * as ErrorUtils from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; @@ -34,6 +37,8 @@ type CategorySettingsPageProps = CategorySettingsPageOnyxProps & StackScreenProp function CategorySettingsPage({route, policyCategories}: CategorySettingsPageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const {windowWidth} = useWindowDimensions(); + const [deleteCategoryConfirmModalVisible, setDeleteCategoryConfirmModalVisible] = useState(false); const policyCategory = policyCategories?.[route.params.categoryName]; @@ -49,6 +54,20 @@ function CategorySettingsPage({route, policyCategories}: CategorySettingsPagePro Navigation.navigate(ROUTES.WORKSPACE_CATEGORY_EDIT.getRoute(route.params.policyID, policyCategory.name)); }; + const deleteCategory = () => { + Policy.deleteWorkspaceCategories(route.params.policyID, [route.params.categoryName]); + setDeleteCategoryConfirmModalVisible(false); + Navigation.dismissModal(); + }; + + const threeDotsMenuItems = [ + { + icon: Expensicons.Trashcan, + text: translate('workspace.categories.deleteCategory'), + onSelected: () => setDeleteCategoryConfirmModalVisible(true), + }, + ]; + return ( @@ -57,7 +76,22 @@ function CategorySettingsPage({route, policyCategories}: CategorySettingsPagePro style={[styles.defaultModalContainer]} testID={CategorySettingsPage.displayName} > - + + setDeleteCategoryConfirmModalVisible(false)} + title={translate('workspace.categories.deleteCategory')} + prompt={translate('workspace.categories.deleteCategoryPrompt')} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} + danger + /> Navigation.goBack(ROUTES.WORKSPACE_CATEGORY_SETTINGS.getRoute(route.params.policyID, route.params.categoryName))} /> >({}); const dropdownButtonRef = useRef(null); + const [deleteCategoriesConfirmModalVisible, setDeleteCategoriesConfirmModalVisible] = useState(false); function fetchCategories() { Policy.openPolicyCategoriesPage(route.params.policyID); @@ -155,21 +157,24 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat const selectedCategoriesArray = Object.keys(selectedCategories).filter((key) => selectedCategories[key]); + const handleDeleteCategories = () => { + setSelectedCategories({}); + deleteWorkspaceCategories(route.params.policyID, selectedCategoriesArray); + setDeleteCategoriesConfirmModalVisible(false); + }; + const getHeaderButtons = () => { const options: Array>> = []; if (selectedCategoriesArray.length > 0) { options.push({ icon: Expensicons.Trashcan, - text: translate('workspace.categories.deleteCategories'), + text: translate(selectedCategoriesArray.length === 1 ? 'workspace.categories.deleteCategory' : 'workspace.categories.deleteCategories'), value: CONST.POLICY.CATEGORIES_BULK_ACTION_TYPES.DELETE, - onSelected: () => { - setSelectedCategories({}); - deleteWorkspaceCategories(route.params.policyID, selectedCategoriesArray); - }, + onSelected: () => setDeleteCategoriesConfirmModalVisible(true), }); - const enabledCategories = selectedCategoriesArray.filter((categoryName) => policyCategories?.[categoryName].enabled); + const enabledCategories = selectedCategoriesArray.filter((categoryName) => policyCategories?.[categoryName]?.enabled); if (enabledCategories.length > 0) { const categoriesToDisable = selectedCategoriesArray .filter((categoryName) => policyCategories?.[categoryName].enabled) @@ -183,7 +188,7 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat options.push({ icon: Expensicons.DocumentSlash, - text: translate('workspace.categories.disableCategories'), + text: translate(enabledCategories.length === 1 ? 'workspace.categories.disableCategory' : 'workspace.categories.disableCategories'), value: CONST.POLICY.CATEGORIES_BULK_ACTION_TYPES.DISABLE, onSelected: () => { setSelectedCategories({}); @@ -205,7 +210,7 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat }, {}); options.push({ icon: Expensicons.Document, - text: translate('workspace.categories.enableCategories'), + text: translate(disabledCategories.length === 1 ? 'workspace.categories.enableCategory' : 'workspace.categories.enableCategories'), value: CONST.POLICY.CATEGORIES_BULK_ACTION_TYPES.ENABLE, onSelected: () => { setSelectedCategories({}); @@ -253,6 +258,8 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat const isLoading = !isOffline && policyCategories === undefined; + const shouldShowEmptyState = !categoryList.some((category) => category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) && !isLoading; + return ( @@ -269,6 +276,16 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat > {!isSmallScreenWidth && getHeaderButtons()} + setDeleteCategoriesConfirmModalVisible(false)} + title={translate(selectedCategoriesArray.length === 1 ? 'workspace.categories.deleteCategory' : 'workspace.categories.deleteCategories')} + prompt={translate(selectedCategoriesArray.length === 1 ? 'workspace.categories.deleteCategoryPrompt' : 'workspace.categories.deleteCategoriesPrompt')} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} + danger + /> {isSmallScreenWidth && {getHeaderButtons()}} {translate('workspace.categories.subtitle')} @@ -280,14 +297,14 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat color={theme.spinner} /> )} - {categoryList.length === 0 && !isLoading && ( + {shouldShowEmptyState && ( )} - {categoryList.length > 0 && ( + {!shouldShowEmptyState && (