diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 371bd94e6225..b9e7c4d5d274 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -405,6 +405,8 @@ const ONYXKEYS = { EXIT_SURVEY_REASON_FORM_DRAFT: 'exitSurveyReasonFormDraft', EXIT_SURVEY_RESPONSE_FORM: 'exitSurveyResponseForm', EXIT_SURVEY_RESPONSE_FORM_DRAFT: 'exitSurveyResponseFormDraft', + POLICY_TAG_NAME_FORM: 'policyTagNameForm', + POLICY_TAG_NAME_FORM_DRAFT: 'policyTagNameFormDraft', }, } as const; @@ -450,6 +452,7 @@ type OnyxFormValuesMapping = { [ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM]: FormTypes.ReimbursementAccountForm; [ONYXKEYS.FORMS.PERSONAL_BANK_ACCOUNT]: FormTypes.PersonalBankAccountForm; [ONYXKEYS.FORMS.WORKSPACE_DESCRIPTION_FORM]: FormTypes.WorkspaceDescriptionForm; + [ONYXKEYS.FORMS.POLICY_TAG_NAME_FORM]: FormTypes.PolicyTagNameForm; }; type OnyxFormDraftValuesMapping = { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 1c33e2ab5bab..d9f0c6658a2b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -569,6 +569,14 @@ const ROUTES = { route: 'workspace/:policyID/tags', getRoute: (policyID: string) => `workspace/${policyID}/tags` as const, }, + WORKSPACE_TAGS_SETTINGS: { + route: 'workspace/:policyID/tags/settings', + getRoute: (policyID: string) => `workspace/${policyID}/tags/settings` as const, + }, + WORKSPACE_EDIT_TAGS: { + route: 'workspace/:policyID/tags/edit', + getRoute: (policyID: string) => `workspace/${policyID}/tags/edit` as const, + }, WORKSPACE_MEMBER_DETAILS: { route: 'workspace/:policyID/members/:accountID', getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/members/${accountID}`, backTo), diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 8b653a8e22a1..a0e06b98da2b 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -216,6 +216,8 @@ const SCREENS = { INVITE_MESSAGE: 'Workspace_Invite_Message', CATEGORIES: 'Workspace_Categories', TAGS: 'Workspace_Tags', + TAGS_SETTINGS: 'Tags_Settings', + TAGS_EDIT: 'Tags_Edit', CURRENCY: 'Workspace_Profile_Currency', WORKFLOWS: 'Workspace_Workflows', WORKFLOWS_APPROVER: 'Workspace_Workflows_Approver', diff --git a/src/languages/en.ts b/src/languages/en.ts index 96673be70698..03f43184791f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1824,12 +1824,14 @@ export default { }, tags: { requiresTag: 'Members must tag all spend', + customTagName: 'Custom tag name', enableTag: 'Enable tag', subtitle: 'Tags add more detailed ways to classify costs.', emptyTags: { title: "You haven't created any tags", subtitle: 'Add a tag to track projects, locations, departments, and more.', }, + genericFailureMessage: 'An error occurred while updating the tag, please try again.', }, emptyWorkspace: { title: 'Create a workspace', diff --git a/src/languages/es.ts b/src/languages/es.ts index d7dc9a1b404e..4a5ad5a09ac2 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1848,12 +1848,14 @@ export default { }, tags: { requiresTag: 'Los miembros deben etiquetar todos los gastos', + customTagName: 'Nombre de etiqueta personalizada', enableTag: 'Habilitar etiqueta', subtitle: 'Las etiquetas añaden formas más detalladas de clasificar los costos.', emptyTags: { title: 'No has creado ninguna etiqueta', subtitle: 'Añade una etiqueta para realizar el seguimiento de proyectos, ubicaciones, departamentos y otros.', }, + genericFailureMessage: 'Se produjo un error al actualizar la etiqueta, inténtelo nuevamente.', }, emptyWorkspace: { title: 'Crea un espacio de trabajo', diff --git a/src/libs/API/parameters/OpenPolicyTagsPageParams.ts b/src/libs/API/parameters/OpenPolicyTagsPageParams.ts new file mode 100644 index 000000000000..e85e9d630a60 --- /dev/null +++ b/src/libs/API/parameters/OpenPolicyTagsPageParams.ts @@ -0,0 +1,5 @@ +type OpenPolicyTagsPageParams = { + policyID: string; +}; + +export default OpenPolicyTagsPageParams; diff --git a/src/libs/API/parameters/RenamePolicyTaglist.ts b/src/libs/API/parameters/RenamePolicyTaglist.ts new file mode 100644 index 000000000000..8c92c99aa7fb --- /dev/null +++ b/src/libs/API/parameters/RenamePolicyTaglist.ts @@ -0,0 +1,7 @@ +type RenamePolicyTaglist = { + policyID: string; + oldName: string; + newName: string; +}; + +export default RenamePolicyTaglist; diff --git a/src/libs/API/parameters/SetPolicyRequiresTag.ts b/src/libs/API/parameters/SetPolicyRequiresTag.ts new file mode 100644 index 000000000000..b8c2e71e7e3b --- /dev/null +++ b/src/libs/API/parameters/SetPolicyRequiresTag.ts @@ -0,0 +1,6 @@ +type SetPolicyRequiresTag = { + policyID: string; + requiresTag: boolean; +}; + +export default SetPolicyRequiresTag; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index df11c3bb9a7d..7e0e9b6e4a96 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -115,6 +115,7 @@ export type {default as OpenWorkspaceReimburseViewParams} from './OpenWorkspaceR export type {default as OpenWorkspaceInvitePageParams} from './OpenWorkspaceInvitePageParams'; export type {default as OpenWorkspaceMembersPageParams} from './OpenWorkspaceMembersPageParams'; export type {default as OpenPolicyCategoriesPageParams} from './OpenPolicyCategoriesPageParams'; +export type {default as OpenPolicyTagsPageParams} from './OpenPolicyTagsPageParams'; export type {default as OpenDraftWorkspaceRequestParams} from './OpenDraftWorkspaceRequestParams'; export type {default as UpdateWorkspaceCustomUnitAndRateParams} from './UpdateWorkspaceCustomUnitAndRateParams'; export type {default as CreateWorkspaceFromIOUPaymentParams} from './CreateWorkspaceFromIOUPaymentParams'; @@ -155,6 +156,8 @@ export type {default as SetWorkspaceAutoReportingParams} from './SetWorkspaceAut export type {default as SetWorkspaceAutoReportingFrequencyParams} from './SetWorkspaceAutoReportingFrequencyParams'; export type {default as SetWorkspaceAutoReportingMonthlyOffsetParams} from './SetWorkspaceAutoReportingMonthlyOffsetParams'; export type {default as SetWorkspaceApprovalModeParams} from './SetWorkspaceApprovalModeParams'; +export type {default as SetPolicyRequiresTag} from './SetPolicyRequiresTag'; +export type {default as RenamePolicyTaglist} from './RenamePolicyTaglist'; export type {default as SwitchToOldDotParams} from './SwitchToOldDotParams'; export type {default as EnablePolicyCategoriesParams} from './EnablePolicyCategoriesParams'; export type {default as EnablePolicyConnectionsParams} from './EnablePolicyConnectionsParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index e75e743d5788..bf215e09e37d 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -117,6 +117,8 @@ const WRITE_COMMANDS = { SET_WORKSPACE_CATEGORIES_ENABLED: 'SetWorkspaceCategoriesEnabled', CREATE_WORKSPACE_CATEGORIES: 'CreateWorkspaceCategories', SET_WORKSPACE_REQUIRES_CATEGORY: 'SetWorkspaceRequiresCategory', + SET_POLICY_REQUIRES_TAG: 'SetPolicyRequiresTag', + RENAME_POLICY_TAG_LIST: 'RenamePolicyTaglist', CREATE_TASK: 'CreateTask', CANCEL_TASK: 'CancelTask', EDIT_TASK_ASSIGNEE: 'EditTaskAssignee', @@ -277,6 +279,8 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_WORKSPACE_CATEGORIES_ENABLED]: Parameters.SetWorkspaceCategoriesEnabledParams; [WRITE_COMMANDS.CREATE_WORKSPACE_CATEGORIES]: Parameters.CreateWorkspaceCategoriesParams; [WRITE_COMMANDS.SET_WORKSPACE_REQUIRES_CATEGORY]: Parameters.SetWorkspaceRequiresCategoryParams; + [WRITE_COMMANDS.SET_POLICY_REQUIRES_TAG]: Parameters.SetPolicyRequiresTag; + [WRITE_COMMANDS.RENAME_POLICY_TAG_LIST]: Parameters.RenamePolicyTaglist; [WRITE_COMMANDS.CREATE_TASK]: Parameters.CreateTaskParams; [WRITE_COMMANDS.CANCEL_TASK]: Parameters.CancelTaskParams; [WRITE_COMMANDS.EDIT_TASK_ASSIGNEE]: Parameters.EditTaskAssigneeParams; @@ -363,6 +367,7 @@ const READ_COMMANDS = { OPEN_WORKSPACE: 'OpenWorkspace', OPEN_WORKSPACE_MEMBERS_PAGE: 'OpenWorkspaceMembersPage', OPEN_POLICY_CATEGORIES_PAGE: 'OpenPolicyCategoriesPage', + OPEN_POLICY_TAGS_PAGE: 'OpenPolicyTagsPage', OPEN_WORKSPACE_INVITE_PAGE: 'OpenWorkspaceInvitePage', OPEN_DRAFT_WORKSPACE_REQUEST: 'OpenDraftWorkspaceRequest', OPEN_POLICY_DISTANCE_RATES_PAGE: 'OpenPolicyDistanceRatesPage', @@ -399,6 +404,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_WORKSPACE]: Parameters.OpenWorkspaceParams; [READ_COMMANDS.OPEN_WORKSPACE_MEMBERS_PAGE]: Parameters.OpenWorkspaceMembersPageParams; [READ_COMMANDS.OPEN_POLICY_CATEGORIES_PAGE]: Parameters.OpenPolicyCategoriesPageParams; + [READ_COMMANDS.OPEN_POLICY_TAGS_PAGE]: Parameters.OpenPolicyTagsPageParams; [READ_COMMANDS.OPEN_WORKSPACE_INVITE_PAGE]: Parameters.OpenWorkspaceInvitePageParams; [READ_COMMANDS.OPEN_DRAFT_WORKSPACE_REQUEST]: Parameters.OpenDraftWorkspaceRequestParams; [READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE]: Parameters.OpenPolicyDistanceRatesPageParams; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 978e338796ea..d56e38564149 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -254,6 +254,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/members/WorkspaceMemberDetailsPage').default as React.ComponentType, [SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION]: () => require('../../../pages/workspace/members/WorkspaceMemberDetailsRoleSelectionPage').default as React.ComponentType, [SCREENS.WORKSPACE.CATEGORY_CREATE]: () => require('../../../pages/workspace/categories/CreateCategoryPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAGS_SETTINGS]: () => require('../../../pages/workspace/tags/WorkspaceTagsSettingsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAGS_EDIT]: () => require('../../../pages/workspace/tags/WorkspaceEditTagsPage').default as React.ComponentType, [SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../pages/ReimbursementAccount/ReimbursementAccountPage').default as React.ComponentType, [SCREENS.GET_ASSISTANCE]: () => require('../../../pages/GetAssistancePage').default as React.ComponentType, [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: () => require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default as React.ComponentType, diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index 5bc7d52230a8..743bf2e0cff1 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -6,6 +6,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = [SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT, SCREENS.WORKSPACE.RATE_AND_UNIT_RATE, SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT], [SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE, SCREENS.WORKSPACE.MEMBER_DETAILS, SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION], [SCREENS.WORKSPACE.WORKFLOWS]: [SCREENS.WORKSPACE.WORKFLOWS_APPROVER, SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY, SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET], + [SCREENS.WORKSPACE.TAGS]: [SCREENS.WORKSPACE.TAGS_SETTINGS, SCREENS.WORKSPACE.TAGS_EDIT], [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], }; diff --git a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts index be9c0b55e761..b2939cf38d9f 100755 --- a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts @@ -14,6 +14,7 @@ const TAB_TO_CENTRAL_PANE_MAPPING: Record = { SCREENS.WORKSPACE.TRAVEL, SCREENS.WORKSPACE.MEMBERS, SCREENS.WORKSPACE.CATEGORIES, + SCREENS.WORKSPACE.TAGS, SCREENS.WORKSPACE.MORE_FEATURES, SCREENS.WORKSPACE.DISTANCE_RATES, ], diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 643860e4d81e..97d7650a9043 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -297,6 +297,12 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.CATEGORY_CREATE]: { path: ROUTES.WORKSPACE_CATEGORY_CREATE.route, }, + [SCREENS.WORKSPACE.TAGS_SETTINGS]: { + path: ROUTES.WORKSPACE_TAGS_SETTINGS.route, + }, + [SCREENS.WORKSPACE.TAGS_EDIT]: { + path: ROUTES.WORKSPACE_EDIT_TAGS.route, + }, [SCREENS.REIMBURSEMENT_ACCOUNT]: { path: ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.route, exact: true, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index b6b19c4560e0..33e79b637cc4 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -214,6 +214,13 @@ type SettingsNavigatorParamList = { [SCREENS.WORKSPACE.CATEGORIES_SETTINGS]: { policyID: string; }; + [SCREENS.WORKSPACE.TAGS_SETTINGS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.TAGS_EDIT]: { + policyID: string; + tagName: string; + }; [SCREENS.WORKSPACE.MEMBER_DETAILS]: { policyID: string; accountID: string; diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 8bfa2a4a11fd..b55efc2afff1 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -25,6 +25,7 @@ import type { OpenDraftWorkspaceRequestParams, OpenPolicyCategoriesPageParams, OpenPolicyDistanceRatesPageParams, + OpenPolicyTagsPageParams, OpenWorkspaceInvitePageParams, OpenWorkspaceMembersPageParams, OpenWorkspaceParams, @@ -1946,6 +1947,19 @@ function openPolicyCategoriesPage(policyID: string) { API.read(READ_COMMANDS.OPEN_POLICY_CATEGORIES_PAGE, params); } +function openPolicyTagsPage(policyID: string) { + if (!policyID) { + Log.warn('openPolicyTasgPage invalid params', {policyID}); + return; + } + + const params: OpenPolicyTagsPageParams = { + policyID, + }; + + API.read(READ_COMMANDS.OPEN_POLICY_TAGS_PAGE, params); +} + function openWorkspaceInvitePage(policyID: string, clientMemberEmails: string[]) { if (!policyID || !clientMemberEmails) { Log.warn('openWorkspaceInvitePage invalid params', {policyID, clientMemberEmails}); @@ -2639,6 +2653,109 @@ function setWorkspaceRequiresCategory(policyID: string, requiresCategory: boolea API.write('SetWorkspaceRequiresCategory', parameters, onyxData); } +function setPolicyRequiresTag(policyID: string, requiresTag: boolean) { + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + requiresTag, + errors: { + requiresTag: null, + }, + pendingFields: { + requiresTag: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + errors: { + requiresTag: null, + }, + pendingFields: { + requiresTag: null, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + requiresTag: !requiresTag, + errors: ErrorUtils.getMicroSecondOnyxError('workspace.tags.genericFailureMessage'), + pendingFields: { + requiresTag: null, + }, + }, + }, + ], + }; + + const parameters = { + policyID, + requiresTag, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_REQUIRES_TAG, parameters, onyxData); +} + +function renamePolicyTaglist(policyID: string, policyTagListName: {oldName: string; newName: string}, policyTags: OnyxEntry) { + const newName = policyTagListName.newName; + const oldName = policyTagListName.oldName; + const oldPolicyTags = policyTags?.[oldName] ?? {}; + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + value: { + [newName]: {...oldPolicyTags, name: newName, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, + [oldName]: null, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + value: { + [newName]: {pendingAction: null}, + [oldName]: null, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + value: { + errors: { + [oldName]: oldName, + [newName]: ErrorUtils.getMicroSecondOnyxError('workspace.tags.genericFailureMessage'), + }, + [newName]: null, + [oldName]: oldPolicyTags, + }, + }, + ], + }; + const parameters = { + policyID, + oldName, + newName, + }; + + API.write(WRITE_COMMANDS.RENAME_POLICY_TAG_LIST, parameters, onyxData); +} + function clearCategoryErrors(policyID: string, categoryName: string) { const category = allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]?.[categoryName]; @@ -3150,6 +3267,7 @@ export { createWorkspace, openWorkspaceMembersPage, openPolicyCategoriesPage, + openPolicyTagsPage, openWorkspaceInvitePage, openWorkspace, removeWorkspace, @@ -3174,6 +3292,8 @@ export { declineJoinRequest, createPolicyCategory, clearCategoryErrors, + setPolicyRequiresTag, + renamePolicyTaglist, enablePolicyCategories, enablePolicyConnections, enablePolicyDistanceRates, diff --git a/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx b/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx new file mode 100644 index 000000000000..98ae6f726d73 --- /dev/null +++ b/src/pages/workspace/tags/WorkspaceEditTagsPage.tsx @@ -0,0 +1,91 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useCallback, useMemo} from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import TextInput from '@components/TextInput'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Policy from '@libs/actions/Policy'; +import Navigation from '@libs/Navigation/Navigation'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import type {SettingsNavigatorParamList} from '@navigation/types'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import INPUT_IDS from '@src/types/form/PolicyTagNameForm'; +import type * as OnyxTypes from '@src/types/onyx'; + +type WorkspaceEditTagsPageOnyxProps = { + /** Collection of tags attached to a policy */ + policyTags: OnyxEntry; +}; + +type WorkspaceEditTagsPageProps = WorkspaceEditTagsPageOnyxProps & StackScreenProps; + +const validateTagName = (values: FormOnyxValues) => { + const errors: FormInputErrors = {}; + if (!values[INPUT_IDS.POLICY_TAGS_NAME] && values[INPUT_IDS.POLICY_TAGS_NAME].trim() === '') { + errors[INPUT_IDS.POLICY_TAGS_NAME] = 'common.error.fieldRequired'; + } + return errors; +}; + +function WorkspaceEditTagsPage({route, policyTags}: WorkspaceEditTagsPageProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const taglistName = useMemo(() => PolicyUtils.getTagLists(policyTags)[0].name, [policyTags]); + const {inputCallbackRef} = useAutoFocusInput(); + + const updateTaglistName = useCallback( + (values: FormOnyxValues) => { + Policy.renamePolicyTaglist(route.params.policyID, {oldName: taglistName, newName: values[INPUT_IDS.POLICY_TAGS_NAME]}, policyTags); + Navigation.goBack(); + }, + [policyTags, route.params.policyID, taglistName], + ); + + return ( + + + + + + + + + ); +} + +WorkspaceEditTagsPage.displayName = 'WorkspaceEditTagsPage'; + +export default withOnyx({ + policyTags: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${route.params.policyID}`, + }, +})(WorkspaceEditTagsPage); diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index fb7611ae681c..d0905ab6e805 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -1,8 +1,9 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useMemo, useState} from 'react'; -import {View} from 'react-native'; +import React, {useEffect, useMemo, 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 HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -13,14 +14,19 @@ import TableListItem from '@components/SelectionList/TableListItem'; import Text from '@components/Text'; import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import type {CentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; +import * as Policy from '@userActions/Policy'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; @@ -45,6 +51,18 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { const theme = useTheme(); const {translate} = useLocalize(); const [selectedTags, setSelectedTags] = useState>({}); + + function fetchTags() { + Policy.openPolicyTagsPage(route.params.policyID); + } + + const {isOffline} = useNetwork({onReconnect: fetchTags}); + + useEffect(() => { + fetchTags(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); const tagList = useMemo( () => @@ -57,7 +75,7 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { isSelected: !!selectedTags[value.name], rightElement: ( - + {value.enabled ? translate('workspace.common.enabled') : translate('workspace.common.disabled')} @@ -71,7 +89,7 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { })), ) .flat(), - [policyTagLists, selectedTags, styles.alignSelfCenter, styles.disabledText, styles.flexRow, styles.p1, styles.pl2, theme.icon, translate], + [policyTagLists, selectedTags, styles.alignSelfCenter, styles.flexRow, styles.label, styles.p1, styles.pl2, styles.textSupporting, theme.icon, translate], ); const toggleTag = (tag: PolicyForList) => { @@ -93,6 +111,25 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { ); + const navigateToTagsSettings = () => { + Navigation.navigate(ROUTES.WORKSPACE_TAGS_SETTINGS.getRoute(route.params.policyID)); + }; + + const isLoading = !isOffline && policyTags === undefined; + + const settingsButton = ( + +