diff --git a/src/CONST.ts b/src/CONST.ts index cb40f7610fab..bb191ac5e028 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1477,6 +1477,11 @@ const CONST = { DISABLE: 'disable', ENABLE: 'enable', }, + TAGS_BULK_ACTION_TYPES: { + DELETE: 'delete', + DISABLE: 'disable', + ENABLE: 'enable', + }, DISTANCE_RATES_BULK_ACTION_TYPES: { DELETE: 'delete', DISABLE: 'disable', diff --git a/src/languages/en.ts b/src/languages/en.ts index f8bb0b219966..cd2d92e25b22 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1847,6 +1847,9 @@ export default { requiresTag: 'Members must tag all spend', customTagName: 'Custom tag name', enableTag: 'Enable tag', + enableTags: 'Enable tags', + disableTag: 'Disable tag', + disableTags: 'Disable tags', addTag: 'Add tag', editTag: 'Edit tag', subtitle: 'Tags add more detailed ways to classify costs.', @@ -1855,7 +1858,9 @@ export default { subtitle: 'Add a tag to track projects, locations, departments, and more.', }, deleteTag: 'Delete tag', + deleteTags: 'Delete tags', deleteTagConfirmation: 'Are you sure that you want to delete this tag?', + deleteTagsConfirmation: 'Are you sure that you want to delete these tags?', deleteFailureMessage: 'An error occurred while deleting the tag, please try again.', tagRequiredError: 'Tag name is required.', existingTagError: 'A tag with this name already exists.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 0537cb1d8a80..3a50e332fd57 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1871,6 +1871,9 @@ export default { requiresTag: 'Los miembros deben etiquetar todos los gastos', customTagName: 'Nombre de etiqueta personalizada', enableTag: 'Habilitar etiqueta', + enableTags: 'Habilitar etiquetas', + disableTag: 'Desactivar etiqueta', + disableTags: 'Desactivar etiquetas', addTag: 'Añadir etiqueta', editTag: 'Editar etiqueta', subtitle: 'Las etiquetas añaden formas más detalladas de clasificar los costos.', @@ -1879,7 +1882,9 @@ export default { subtitle: 'Añade una etiqueta para realizar el seguimiento de proyectos, ubicaciones, departamentos y otros.', }, deleteTag: 'Eliminar etiqueta', + deleteTags: 'Eliminar etiquetas', deleteTagConfirmation: '¿Estás seguro de que quieres eliminar esta etiqueta?', + deleteTagsConfirmation: '¿Estás seguro de que quieres eliminar estas etiquetas?', deleteFailureMessage: 'Se ha producido un error al intentar eliminar la etiqueta. Por favor, inténtalo más tarde.', tagRequiredError: 'Lo nombre de la etiqueta es obligatorio.', existingTagError: 'Ya existe una etiqueta con este nombre.', diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 74b439004020..3f929e46ab67 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -228,7 +228,7 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} customText={translate('workspace.common.selected', {selectedNumber: selectedCategoriesArray.length})} options={options} - style={[isSmallScreenWidth && styles.w50, isSmallScreenWidth && styles.mb3]} + style={[isSmallScreenWidth && styles.flexGrow1, isSmallScreenWidth && styles.mb3]} /> ); } diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index 392d392c55ef..126d548c2c8a 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -1,9 +1,12 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useEffect, useMemo, useRef, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import Button from '@components/Button'; +import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; +import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; +import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -31,6 +34,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; +import type DeepValueOf from '@src/types/utils/DeepValueOf'; type PolicyForList = { value: string; @@ -38,6 +42,7 @@ type PolicyForList = { keyForList: string; isSelected: boolean; rightElement: React.ReactNode; + enabled: boolean; }; type PolicyOption = ListItem & { @@ -58,6 +63,8 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { const theme = useTheme(); const {translate} = useLocalize(); const [selectedTags, setSelectedTags] = useState>({}); + const dropdownButtonRef = useRef(null); + const [deleteTagsConfirmModalVisible, setDeleteTagsConfirmModalVisible] = useState(false); function fetchTags() { Policy.openPolicyTagsPage(route.params.policyID); @@ -84,6 +91,7 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { isSelected: !!selectedTags[value.name], pendingAction: value.pendingAction, errors: value.errors ?? undefined, + enabled: value.enabled, rightElement: ( @@ -103,6 +111,11 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { [policyTagLists, selectedTags, styles.alignSelfCenter, styles.flexRow, styles.label, styles.p1, styles.pl2, styles.textSupporting, theme.icon, translate], ); + const tagListKeyedByName = tagList.reduce>((acc, tag) => { + acc[tag.value] = tag; + return acc; + }, {}); + const toggleTag = (tag: PolicyForList) => { setSelectedTags((prev) => ({ ...prev, @@ -135,29 +148,108 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { Navigation.navigate(ROUTES.WORKSPACE_TAG_SETTINGS.getRoute(route.params.policyID, tag.keyForList)); }; + const selectedTagsArray = Object.keys(selectedTags).filter((key) => selectedTags[key]); + + const handleDeleteTags = () => { + setSelectedTags({}); + Policy.deletePolicyTags(route.params.policyID, selectedTagsArray); + setDeleteTagsConfirmModalVisible(false); + }; + const isLoading = !isOffline && policyTags === undefined; - const headerButtons = ( - -