Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tag delete option #38453

Merged
merged 2 commits into from
Mar 18, 2024
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
3 changes: 3 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1852,6 +1852,9 @@ export default {
title: "You haven't created any tags",
subtitle: 'Add a tag to track projects, locations, departments, and more.',
},
deleteTag: 'Delete tag',
deleteTagConfirmation: 'Are you sure that you want to delete this tag?',
deleteFailureMessage: 'An error occurred while deleting the tag, please try again.',
tagRequiredError: 'Tag name is required.',
existingTagError: 'A tag with this name already exists.',
genericFailureMessage: 'An error occurred while updating the tag, please try again.',
Expand Down
3 changes: 3 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1876,6 +1876,9 @@ export default {
title: 'No has creado ninguna etiqueta',
subtitle: 'Añade una etiqueta para realizar el seguimiento de proyectos, ubicaciones, departamentos y otros.',
},
deleteTag: 'Eliminar etiqueta',
deleteTagConfirmation: '¿Estás seguro de que quieres eliminar esta etiqueta?',
deleteFailureMessage: 'Se ha producido un error al intentar eliminar la etiqueta. Por favor, inténtalo más tarde.',
luacmartins marked this conversation as resolved.
Show resolved Hide resolved
tagRequiredError: 'Lo nombre de la etiqueta es obligatorio.',
existingTagError: 'Ya existe una etiqueta con este nombre.',
genericFailureMessage: 'Se produjo un error al actualizar la etiqueta, inténtelo nuevamente.',
Expand Down
10 changes: 10 additions & 0 deletions src/libs/API/parameters/DeletePolicyTagsParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type DeletePolicyTagsParams = {
policyID: string;
/**
* Stringified JSON object with type of following structure:
* Array<string>
*/
tags: string;
};

export default DeletePolicyTagsParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export type {default as EnablePolicyTaxesParams} from './EnablePolicyTaxesParams
export type {default as OpenPolicyMoreFeaturesPageParams} from './OpenPolicyMoreFeaturesPageParams';
export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams';
export type {default as CreatePolicyTagsParams} from './CreatePolicyTagsParams';
export type {default as DeletePolicyTagsParams} from './DeletePolicyTagsParams';
export type {default as SetPolicyCustomTaxNameParams} from './SetPolicyCustomTaxNameParams';
export type {default as SetPolicyForeignCurrencyDefaultParams} from './SetPolicyForeignCurrencyDefaultParams';
export type {default as SetPolicyCurrencyDefaultParams} from './SetPolicyCurrencyDefaultParams';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ const WRITE_COMMANDS = {
DELETE_WORKSPACE_CATEGORIES: 'DeleteWorkspaceCategories',
SET_POLICY_REQUIRES_TAG: 'SetPolicyRequiresTag',
RENAME_POLICY_TAG_LIST: 'RenamePolicyTaglist',
DELETE_POLICY_TAGS: 'Policy_IndependentTaglist_Tags_Remove',
CREATE_TASK: 'CreateTask',
CANCEL_TASK: 'CancelTask',
EDIT_TASK_ASSIGNEE: 'EditTaskAssignee',
Expand Down Expand Up @@ -298,6 +299,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.RENAME_POLICY_TAG_LIST]: Parameters.RenamePolicyTaglist;
[WRITE_COMMANDS.CREATE_POLICY_TAG]: Parameters.CreatePolicyTagsParams;
[WRITE_COMMANDS.SET_POLICY_TAGS_ENABLED]: Parameters.SetPolicyTagsEnabled;
[WRITE_COMMANDS.DELETE_POLICY_TAGS]: Parameters.DeletePolicyTagsParams;
[WRITE_COMMANDS.CREATE_TASK]: Parameters.CreateTaskParams;
[WRITE_COMMANDS.CANCEL_TASK]: Parameters.CancelTaskParams;
[WRITE_COMMANDS.EDIT_TASK_ASSIGNEE]: Parameters.EditTaskAssigneeParams;
Expand Down
74 changes: 69 additions & 5 deletions src/libs/actions/Policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import type {
PolicyCategories,
PolicyCategory,
PolicyMember,
PolicyTag,
PolicyTagList,
PolicyTags,
RecentlyUsedCategories,
Expand All @@ -79,7 +80,7 @@ import type {
ReportAction,
Transaction,
} from '@src/types/onyx';
import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon';
import type {Errors, OnyxValueWithOfflineFeedback, PendingAction} from '@src/types/onyx/OnyxCommon';
import type {OriginalMessageJoinPolicyChangeLog} from '@src/types/onyx/OriginalMessage';
import type {Attributes, CustomUnit, Rate, Unit} from '@src/types/onyx/Policy';
import type {OnyxData} from '@src/types/onyx/Request';
Expand Down Expand Up @@ -2840,15 +2841,15 @@ function renamePolicyCategory(policyID: string, policyCategory: {oldName: string
}

function createPolicyTag(policyID: string, tagName: string) {
const tagListName = Object.keys(allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {})[0];
const policyTag = PolicyUtils.getTagLists(allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {})?.[0] ?? {};

const onyxData: OnyxData = {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`,
value: {
[tagListName]: {
[policyTag.name]: {
tags: {
[tagName]: {
name: tagName,
Expand All @@ -2866,7 +2867,7 @@ function createPolicyTag(policyID: string, tagName: string) {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`,
value: {
[tagListName]: {
[policyTag.name]: {
tags: {
[tagName]: {
errors: null,
Expand All @@ -2882,7 +2883,7 @@ function createPolicyTag(policyID: string, tagName: string) {
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`,
value: {
[tagListName]: {
[policyTag.name]: {
tags: {
[tagName]: {
errors: ErrorUtils.getMicroSecondOnyxError('workspace.tags.genericFailureMessage'),
Expand Down Expand Up @@ -2988,6 +2989,68 @@ function setWorkspaceTagEnabled(policyID: string, tagsToUpdate: Record<string, {
API.write(WRITE_COMMANDS.SET_POLICY_TAGS_ENABLED, parameters, onyxData);
}

function deletePolicyTags(policyID: string, tagsToDelete: string[]) {
const policyTag = PolicyUtils.getTagLists(allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {})?.[0] ?? {};

const onyxData: OnyxData = {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`,
value: {
[policyTag.name]: {
tags: {
...tagsToDelete.reduce<Record<string, Partial<OnyxValueWithOfflineFeedback<PolicyTag>>>>((acc, tagName) => {
acc[tagName] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE};
return acc;
}, {}),
},
},
},
},
],
successData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`,
value: {
[policyTag.name]: {
tags: {
...tagsToDelete.reduce<Record<string, null | Partial<OnyxValueWithOfflineFeedback<PolicyTag>>>>((acc, tagName) => {
acc[tagName] = null;
return acc;
}, {}),
},
},
},
},
],
failureData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`,
value: {
[policyTag.name]: {
tags: {
...tagsToDelete.reduce<Record<string, Partial<OnyxValueWithOfflineFeedback<PolicyTag>>>>((acc, tagName) => {
acc[tagName] = {pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxError('workspace.tags.deleteFailureMessage')};
return acc;
}, {}),
},
},
},
},
],
};

const parameters = {
policyID,
tags: JSON.stringify(tagsToDelete),
};

API.write(WRITE_COMMANDS.DELETE_POLICY_TAGS, parameters, onyxData);
}

function clearPolicyTagErrors(policyID: string, tagName: string) {
const tagListName = Object.keys(allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {})[0];
const tag = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`]?.[tagListName].tags?.[tagName];
Expand Down Expand Up @@ -4063,6 +4126,7 @@ export {
clearPolicyTagErrors,
clearWorkspaceReimbursementErrors,
deleteWorkspaceCategories,
deletePolicyTags,
setWorkspaceTagEnabled,
setWorkspaceCurrencyDefault,
setForeignCurrencyDefault,
Expand Down
39 changes: 38 additions & 1 deletion src/pages/workspace/tags/TagSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ import React, {useMemo} 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 {Trashcan} from '@components/Icon/Expensicons';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import ScreenWrapper from '@components/ScreenWrapper';
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 {setWorkspaceTagEnabled} from '@libs/actions/Policy';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
import type {SettingsNavigatorParamList} from '@navigation/types';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
Expand All @@ -35,12 +39,22 @@ function TagSettingsPage({route, policyTags}: TagSettingsPageProps) {
const {translate} = useLocalize();
const policyTag = useMemo(() => PolicyUtils.getTagList(policyTags, 0), [policyTags]);

const {windowWidth} = useWindowDimensions();

const [isDeleteTagModalOpen, setIsDeleteTagModalOpen] = React.useState(false);

const currentPolicyTag = policyTag.tags[decodeURIComponent(route.params.tagName)];

if (!currentPolicyTag) {
return <NotFoundPage />;
}

const deleteTagAndHideModal = () => {
Policy.deletePolicyTags(route.params.policyID, [currentPolicyTag.name]);
setIsDeleteTagModalOpen(false);
Navigation.goBack();
};

const updateWorkspaceTagEnabled = (value: boolean) => {
setWorkspaceTagEnabled(route.params.policyID, {[currentPolicyTag.name]: {name: currentPolicyTag.name, enabled: value}});
};
Expand All @@ -53,7 +67,30 @@ function TagSettingsPage({route, policyTags}: TagSettingsPageProps) {
style={[styles.defaultModalContainer]}
testID={TagSettingsPage.displayName}
>
<HeaderWithBackButton title={route.params.tagName} />
<HeaderWithBackButton
title={route.params.tagName}
shouldShowThreeDotsButton
shouldSetModalVisibility={false}
threeDotsAnchorPosition={styles.threeDotsPopoverOffset(windowWidth)}
threeDotsMenuItems={[
{
icon: Trashcan,
text: translate('workspace.tags.deleteTag'),
onSelected: () => setIsDeleteTagModalOpen(true),
},
]}
/>
<ConfirmModal
title={translate('workspace.tags.deleteTag')}
isVisible={isDeleteTagModalOpen}
onConfirm={deleteTagAndHideModal}
onCancel={() => setIsDeleteTagModalOpen(false)}
shouldSetModalVisibility={false}
prompt={translate('workspace.tags.deleteTagConfirmation')}
confirmText={translate('common.delete')}
cancelText={translate('common.cancel')}
danger
/>
<View style={styles.flexGrow1}>
<OfflineWithFeedback
errors={ErrorUtils.getLatestErrorMessageField(currentPolicyTag)}
Expand Down
Loading