chore: Refactor Omnichannel Edit Tags UI (#30732)

pull/30865/head
Douglas Fabris 3 years ago committed by GitHub
parent a19f9c36c3
commit 8db7b92983
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      apps/meteor/client/components/AutoCompleteDepartmentMultiple.tsx
  2. 3
      apps/meteor/ee/client/omnichannel/priorities/PriorityList.tsx
  3. 42
      apps/meteor/ee/client/omnichannel/tags/RemoveTagButton.tsx
  4. 99
      apps/meteor/ee/client/omnichannel/tags/TagEdit.js
  5. 141
      apps/meteor/ee/client/omnichannel/tags/TagEdit.tsx
  6. 38
      apps/meteor/ee/client/omnichannel/tags/TagEditWithData.js
  7. 36
      apps/meteor/ee/client/omnichannel/tags/TagEditWithData.tsx
  8. 42
      apps/meteor/ee/client/omnichannel/tags/TagEditWithDepartmentData.tsx
  9. 24
      apps/meteor/ee/client/omnichannel/tags/TagsPage.tsx
  10. 36
      apps/meteor/ee/client/omnichannel/tags/TagsRoute.js
  11. 17
      apps/meteor/ee/client/omnichannel/tags/TagsRoute.tsx
  12. 32
      apps/meteor/ee/client/omnichannel/tags/TagsTable.tsx
  13. 34
      apps/meteor/ee/client/omnichannel/tags/useRemoveTag.tsx
  14. 2
      packages/rest-typings/src/v1/omnichannel.ts

@ -2,6 +2,7 @@ import { PaginatedMultiSelectFiltered } from '@rocket.chat/fuselage';
import type { PaginatedMultiSelectOption } from '@rocket.chat/fuselage';
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ComponentProps } from 'react';
import React, { memo, useMemo, useState } from 'react';
import { useRecordList } from '../hooks/lists/useRecordList';
@ -14,7 +15,7 @@ type AutoCompleteDepartmentMultipleProps = {
onlyMyDepartments?: boolean;
showArchived?: boolean;
enabled?: boolean;
};
} & Omit<ComponentProps<typeof PaginatedMultiSelectFiltered>, 'options'>;
const AutoCompleteDepartmentMultiple = ({
value = [],

@ -4,6 +4,7 @@ import React from 'react';
import {
Contextualbar,
ContextualbarTitle,
ContextualbarHeader,
ContextualbarClose,
ContextualbarScrollableContent,
@ -24,7 +25,7 @@ const PriorityList = ({ priorityId, onClose, onSave }: PriorityListProps): React
return (
<Contextualbar>
<ContextualbarHeader>
{t('Edit_Priority')}
<ContextualbarTitle>{t('Edit_Priority')}</ContextualbarTitle>
<ContextualbarClose onClick={onClose} />
</ContextualbarHeader>
<ContextualbarScrollableContent height='100%'>

@ -1,42 +0,0 @@
import type { ILivechatTag } from '@rocket.chat/core-typings';
import { IconButton } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useToastMessageDispatch, useRoute, useMethod, useTranslation } from '@rocket.chat/ui-contexts';
import React from 'react';
import GenericModal from '../../../../client/components/GenericModal';
import { GenericTableCell } from '../../../../client/components/GenericTable';
const RemoveTagButton = ({ _id, reload }: { _id: ILivechatTag['_id']; reload: () => void }) => {
const t = useTranslation();
const setModal = useSetModal();
const dispatchToastMessage = useToastMessageDispatch();
const tagsRoute = useRoute('omnichannel-tags');
const removeTag = useMethod('livechat:removeTag');
const handleDelete = useMutableCallback((e) => {
e.stopPropagation();
const onDeleteAgent = async () => {
try {
await removeTag(_id);
dispatchToastMessage({ type: 'success', message: t('Tag_removed') });
tagsRoute.push({});
reload();
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
setModal();
}
};
setModal(<GenericModal variant='danger' onConfirm={onDeleteAgent} onCancel={() => setModal()} confirmText={t('Delete')} />);
});
return (
<GenericTableCell fontScale='p2' color='hint' withTruncatedText>
<IconButton icon='trash' small title={t('Remove')} onClick={handleDelete} />
</GenericTableCell>
);
};
export default RemoveTagButton;

@ -1,99 +0,0 @@
import { Field, TextInput, Button, ButtonGroup, FieldGroup } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useRoute, useMethod, useTranslation } from '@rocket.chat/ui-contexts';
import React, { useMemo } from 'react';
import AutoCompleteDepartmentMultiple from '../../../../client/components/AutoCompleteDepartmentMultiple';
import Page from '../../../../client/components/Page';
import { useForm } from '../../../../client/hooks/useForm';
function TagEdit({ title, data, tagId, reload, currentDepartments, ...props }) {
const t = useTranslation();
const tagsRoute = useRoute('omnichannel-tags');
const tag = data || {};
const { values, handlers, hasUnsavedChanges } = useForm({
name: tag.name,
description: tag.description,
departments:
currentDepartments && currentDepartments.departments
? currentDepartments.departments.map((dep) => ({ label: dep.name, value: dep._id }))
: [],
});
const { handleName, handleDescription, handleDepartments } = handlers;
const { name, description, departments } = values;
const nameError = useMemo(() => (!name || name.length === 0 ? t('The_field_is_required', 'name') : undefined), [name, t]);
const saveTag = useMethod('livechat:saveTag');
const dispatchToastMessage = useToastMessageDispatch();
const handleReturn = useMutableCallback(() => {
tagsRoute.push({});
});
const canSave = useMemo(() => !nameError, [nameError]);
const handleSave = useMutableCallback(async () => {
const tagData = { name, description };
if (!canSave) {
return dispatchToastMessage({ type: 'error', message: t('The_field_is_required') });
}
const finalDepartments = departments ? departments.map((dep) => dep.value) : [''];
try {
await saveTag(tagId, tagData, finalDepartments);
dispatchToastMessage({ type: 'success', message: t('Saved') });
reload();
tagsRoute.push({});
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
}
});
return (
<Page flexDirection='row'>
<Page>
<Page.Header title={title}>
<ButtonGroup>
<Button icon='back' onClick={handleReturn}>
{t('Back')}
</Button>
<Button primary mie='none' flexGrow={1} disabled={!hasUnsavedChanges || !canSave} onClick={handleSave}>
{t('Save')}
</Button>
</ButtonGroup>
</Page.Header>
<Page.ScrollableContentWithShadow>
<FieldGroup w='full' alignSelf='center' maxWidth='x600' is='form' autoComplete='off' {...props}>
<Field>
<Field.Label>{t('Name')}*</Field.Label>
<Field.Row>
<TextInput placeholder={t('Name')} flexGrow={1} value={name} onChange={handleName} error={hasUnsavedChanges && nameError} />
</Field.Row>
</Field>
<Field>
<Field.Label>{t('Description')}</Field.Label>
<Field.Row>
<TextInput placeholder={t('Description')} flexGrow={1} value={description} onChange={handleDescription} />
</Field.Row>
</Field>
<Field>
<Field.Label>{t('Departments')}</Field.Label>
<Field.Row>
<AutoCompleteDepartmentMultiple value={departments} onChange={handleDepartments} showArchived />
</Field.Row>
</Field>
</FieldGroup>
</Page.ScrollableContentWithShadow>
</Page>
</Page>
);
}
export default TagEdit;

@ -0,0 +1,141 @@
import type { ILivechatDepartment, ILivechatTag, Serialized } from '@rocket.chat/core-typings';
import { Field, FieldLabel, FieldRow, FieldError, TextInput, Button, ButtonGroup, FieldGroup, Box } from '@rocket.chat/fuselage';
import { useMutableCallback, useUniqueId } from '@rocket.chat/fuselage-hooks';
import { useToastMessageDispatch, useRouter, useMethod, useTranslation } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import AutoCompleteDepartmentMultiple from '../../../../client/components/AutoCompleteDepartmentMultiple';
import {
ContextualbarScrollableContent,
ContextualbarFooter,
ContextualbarTitle,
Contextualbar,
ContextualbarHeader,
ContextualbarClose,
} from '../../../../client/components/Contextualbar';
import { useRemoveTag } from './useRemoveTag';
type TagEditPayload = {
name: string;
description: string;
departments: { label: string; value: string }[];
};
type TagEditProps = {
tagData?: ILivechatTag;
currentDepartments?: Serialized<ILivechatDepartment>[];
};
const TagEdit = ({ tagData, currentDepartments }: TagEditProps) => {
const t = useTranslation();
const router = useRouter();
const queryClient = useQueryClient();
const handleDeleteTag = useRemoveTag();
const dispatchToastMessage = useToastMessageDispatch();
const saveTag = useMethod('livechat:saveTag');
const { _id, name, description } = tagData || {};
const {
control,
formState: { isDirty, errors },
handleSubmit,
} = useForm<TagEditPayload>({
mode: 'onBlur',
values: {
name: name || '',
description: description || '',
departments: currentDepartments?.map((dep) => ({ label: dep.name, value: dep._id })) || [],
},
});
const handleSave = useMutableCallback(async ({ name, description, departments }: TagEditPayload) => {
const departmentsId = departments?.map((dep) => dep.value) || [''];
try {
await saveTag(_id as unknown as string, { name, description }, departmentsId);
dispatchToastMessage({ type: 'success', message: t('Saved') });
queryClient.invalidateQueries(['livechat-tags']);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
router.navigate('/omnichannel/tags');
}
});
const formId = useUniqueId();
const nameField = useUniqueId();
const descriptionField = useUniqueId();
const departmentsField = useUniqueId();
return (
<Contextualbar>
<ContextualbarHeader>
<ContextualbarTitle>{_id ? t('Edit_Tag') : t('New_Tag')}</ContextualbarTitle>
<ContextualbarClose onClick={() => router.navigate('/omnichannel/tags')}></ContextualbarClose>
</ContextualbarHeader>
<ContextualbarScrollableContent>
<Box id={formId} is='form' autoComplete='off' onSubmit={handleSubmit(handleSave)}>
<FieldGroup>
<Field>
<FieldLabel htmlFor={nameField} required>
{t('Name')}
</FieldLabel>
<FieldRow>
<Controller
name='name'
control={control}
rules={{ required: t('The_field_is_required', 'name') }}
render={({ field }) => <TextInput {...field} error={errors?.name?.message} aria-describedby={`${nameField}-error`} />}
/>
</FieldRow>
{errors?.name && (
<FieldError aria-live='assertive' id={`${nameField}-error`}>
{errors?.name?.message}
</FieldError>
)}
</Field>
<Field>
<FieldLabel htmlFor={descriptionField}>{t('Description')}</FieldLabel>
<FieldRow>
<Controller name='description' control={control} render={({ field }) => <TextInput id={descriptionField} {...field} />} />
</FieldRow>
</Field>
<Field>
<FieldLabel htmlFor={departmentsField}>{t('Departments')}</FieldLabel>
<FieldRow>
<Controller
name='departments'
control={control}
render={({ field: { onChange, value, onBlur } }) => (
<AutoCompleteDepartmentMultiple id={departmentsField} onChange={onChange} value={value} onBlur={onBlur} showArchived />
)}
/>
</FieldRow>
</Field>
</FieldGroup>
</Box>
</ContextualbarScrollableContent>
<ContextualbarFooter>
<ButtonGroup stretch>
<Button onClick={() => router.navigate('/omnichannel/tags')}>{t('Cancel')}</Button>
<Button form={formId} disabled={!isDirty} type='submit' primary>
{t('Save')}
</Button>
</ButtonGroup>
{_id && (
<ButtonGroup stretch mbs={8}>
<Button icon='trash' danger onClick={() => handleDeleteTag(_id)}>
{t('Delete')}
</Button>
</ButtonGroup>
)}
</ContextualbarFooter>
</Contextualbar>
);
};
export default TagEdit;

@ -1,38 +0,0 @@
import { Callout } from '@rocket.chat/fuselage';
import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import { FormSkeleton } from '../../../../client/components/Skeleton';
import TagEdit from './TagEdit';
import TagEditWithDepartmentData from './TagEditWithDepartmentData';
function TagEditWithData({ tagId, reload, title }) {
const getTag = useEndpoint('GET', '/v1/livechat/tags/:tagId', { tagId });
const { data, isLoading, isError } = useQuery(['/v1/livechat/tags/:tagId', tagId], () => getTag(), { enabled: Boolean(tagId) });
const t = useTranslation();
if (isLoading && tagId) {
return <FormSkeleton />;
}
if (isError) {
return (
<Callout m={16} type='danger'>
{t('Not_Available')}
</Callout>
);
}
return (
<>
{data && data.departments && data.departments.length > 0 ? (
<TagEditWithDepartmentData tagId={tagId} data={data} reload={reload} title={title} />
) : (
<TagEdit tagId={tagId} data={data} reload={reload} title={title} />
)}
</>
);
}
export default TagEditWithData;

@ -0,0 +1,36 @@
import type { ILivechatTag } from '@rocket.chat/core-typings';
import { Callout } from '@rocket.chat/fuselage';
import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import { ContextualbarSkeleton } from '../../../../client/components/Contextualbar';
import TagEdit from './TagEdit';
import TagEditWithDepartmentData from './TagEditWithDepartmentData';
const TagEditWithData = ({ tagId }: { tagId: ILivechatTag['_id'] }) => {
const t = useTranslation();
const getTagById = useEndpoint('GET', '/v1/livechat/tags/:tagId', { tagId });
const { data, isLoading, isError } = useQuery(['livechat-getTagById', tagId], async () => getTagById(), { refetchOnWindowFocus: false });
if (isLoading) {
return <ContextualbarSkeleton />;
}
if (isError) {
return (
<Callout m={16} type='danger'>
{t('Not_Available')}
</Callout>
);
}
if (data?.departments && data.departments.length > 0) {
return <TagEditWithDepartmentData tagData={data} />;
}
return <TagEdit tagData={data} />;
};
export default TagEditWithData;

@ -1,37 +1,27 @@
import type { ILivechatTag } from '@rocket.chat/core-typings';
import { Callout } from '@rocket.chat/fuselage';
import { useTranslation } from '@rocket.chat/ui-contexts';
import type { ReactElement, ReactNode } from 'react';
import React, { useMemo } from 'react';
import { useTranslation, useEndpoint } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import { FormSkeleton } from '../../../../client/components/Skeleton';
import { AsyncStatePhase } from '../../../../client/hooks/useAsyncState';
import { useEndpointData } from '../../../../client/hooks/useEndpointData';
import { ContextualbarSkeleton } from '../../../../client/components/Contextualbar';
import TagEdit from './TagEdit';
type TagEditWithDepartmentDataPropsType = {
data: ILivechatTag;
title: ReactNode;
tagId: ILivechatTag['_id'];
reload: () => void;
};
function TagEditWithDepartmentData({ data, title, ...props }: TagEditWithDepartmentDataPropsType): ReactElement {
const TagEditWithDepartmentData = ({ tagData }: { tagData: ILivechatTag }) => {
const t = useTranslation();
const {
value: currentDepartments,
phase: currentDepartmentsState,
error: currentDepartmentsError,
} = useEndpointData('/v1/livechat/department.listByIds', {
params: useMemo(() => ({ ids: data?.departments ? data.departments : [] }), [data]),
});
const getDepartmentsById = useEndpoint('GET', '/v1/livechat/department.listByIds');
const { data, isLoading, isError } = useQuery(
['livechat-getDepartmentsById', tagData.departments],
async () => getDepartmentsById({ ids: tagData.departments }),
{ refetchOnWindowFocus: false },
);
if ([currentDepartmentsState].includes(AsyncStatePhase.LOADING)) {
return <FormSkeleton />;
if (isLoading) {
return <ContextualbarSkeleton />;
}
if (currentDepartmentsError) {
if (isError) {
return (
<Callout m={16} type='danger'>
{t('Not_Available')}
@ -39,7 +29,7 @@ function TagEditWithDepartmentData({ data, title, ...props }: TagEditWithDepartm
);
}
return <TagEdit title={title} currentDepartments={currentDepartments} data={data} {...props} />;
}
return <TagEdit tagData={tagData} currentDepartments={data?.departments} />;
};
export default TagEditWithDepartmentData;

@ -1,34 +1,32 @@
import { Button, ButtonGroup } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useRoute, useTranslation } from '@rocket.chat/ui-contexts';
import type { MutableRefObject } from 'react';
import { useRouter, useTranslation, useRouteParameter } from '@rocket.chat/ui-contexts';
import React from 'react';
import Page from '../../../../client/components/Page';
import TagEdit from './TagEdit';
import TagEditWithData from './TagEditWithData';
import TagsTable from './TagsTable';
const TagsPage = ({ reload }: { reload: MutableRefObject<() => void> }) => {
const TagsPage = () => {
const t = useTranslation();
const tagsRoute = useRoute('omnichannel-tags');
const handleClick = useMutableCallback(() =>
tagsRoute.push({
context: 'new',
}),
);
const router = useRouter();
const context = useRouteParameter('context');
const id = useRouteParameter('id');
return (
<Page flexDirection='row'>
<Page>
<Page.Header title={t('Tags')}>
<ButtonGroup>
<Button onClick={handleClick}>{t('Create_tag')}</Button>
<Button onClick={() => router.navigate('/omnichannel/tags/new')}>{t('Create_tag')}</Button>
</ButtonGroup>
</Page.Header>
<Page.Content>
<TagsTable reload={reload} />
<TagsTable />
</Page.Content>
</Page>
{context === 'edit' && id && <TagEditWithData tagId={id} />}
{context === 'new' && <TagEdit />}
</Page>
);
};

@ -1,36 +0,0 @@
import { useRouteParameter, usePermission, useTranslation } from '@rocket.chat/ui-contexts';
import React, { useRef, useCallback } from 'react';
import NotAuthorizedPage from '../../../../client/views/notAuthorized/NotAuthorizedPage';
import TagEdit from './TagEdit';
import TagEditWithData from './TagEditWithData';
import TagsPage from './TagsPage';
const TagsRoute = () => {
const t = useTranslation();
const reload = useRef(() => null);
const canViewTags = usePermission('manage-livechat-tags');
const handleReload = useCallback(() => {
reload.current();
}, []);
const context = useRouteParameter('context');
const id = useRouteParameter('id');
if (context === 'edit') {
return <TagEditWithData reload={handleReload} tagId={id} title={t('Edit_Tag')} />;
}
if (context === 'new') {
return <TagEdit reload={handleReload} title={t('New_Tag')} />;
}
if (!canViewTags) {
return <NotAuthorizedPage />;
}
return <TagsPage reload={reload} />;
};
export default TagsRoute;

@ -0,0 +1,17 @@
import { usePermission } from '@rocket.chat/ui-contexts';
import React from 'react';
import NotAuthorizedPage from '../../../../client/views/notAuthorized/NotAuthorizedPage';
import TagsPage from './TagsPage';
const TagsRoute = () => {
const canViewTags = usePermission('manage-livechat-tags');
if (!canViewTags) {
return <NotAuthorizedPage />;
}
return <TagsPage />;
};
export default TagsRoute;

@ -1,9 +1,8 @@
import { Pagination } from '@rocket.chat/fuselage';
import { IconButton, Pagination } from '@rocket.chat/fuselage';
import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation, useEndpoint, useRouter } from '@rocket.chat/ui-contexts';
import { useQuery, hashQueryKey } from '@tanstack/react-query';
import type { MutableRefObject } from 'react';
import React, { useMemo, useState, useEffect } from 'react';
import React, { useMemo, useState } from 'react';
import FilterByText from '../../../../client/components/FilterByText';
import GenericNoResults from '../../../../client/components/GenericNoResults';
@ -18,9 +17,9 @@ import {
} from '../../../../client/components/GenericTable';
import { usePagination } from '../../../../client/components/GenericTable/hooks/usePagination';
import { useSort } from '../../../../client/components/GenericTable/hooks/useSort';
import RemoveTagButton from './RemoveTagButton';
import { useRemoveTag } from './useRemoveTag';
const TagsTable = ({ reload }: { reload: MutableRefObject<() => void> }) => {
const TagsTable = () => {
const t = useTranslation();
const [filter, setFilter] = useState('');
const debouncedFilter = useDebouncedValue(filter, 500);
@ -31,6 +30,7 @@ const TagsTable = ({ reload }: { reload: MutableRefObject<() => void> }) => {
const onRowClick = useMutableCallback((id) => router.navigate(`/omnichannel/tags/edit/${id}`));
const handleAddNew = useMutableCallback(() => router.navigate('/omnichannel/tags/new'));
const handleDeleteTag = useRemoveTag();
const query = useMemo(
() => ({
@ -45,15 +45,11 @@ const TagsTable = ({ reload }: { reload: MutableRefObject<() => void> }) => {
);
const getTags = useEndpoint('GET', '/v1/livechat/tags');
const { data, refetch, isSuccess, isLoading } = useQuery(['livechat-tags', query], async () => getTags(query));
const { data, isSuccess, isLoading } = useQuery(['livechat-tags', query], async () => getTags(query), { refetchOnWindowFocus: false });
const [defaultQuery] = useState(hashQueryKey([query]));
const queryHasChanged = defaultQuery !== hashQueryKey([query]);
useEffect(() => {
reload.current = refetch;
}, [reload, refetch]);
const headers = (
<>
<GenericTableHeaderCell key='name' direction={sortDirection} active={sortBy === 'name'} onClick={setSort} sort='name'>
@ -68,9 +64,7 @@ const TagsTable = ({ reload }: { reload: MutableRefObject<() => void> }) => {
>
{t('Description')}
</GenericTableHeaderCell>
<GenericTableHeaderCell key='remove' w='x60'>
{t('Remove')}
</GenericTableHeaderCell>
<GenericTableHeaderCell key='remove' w='x60' />
</>
);
@ -106,7 +100,17 @@ const TagsTable = ({ reload }: { reload: MutableRefObject<() => void> }) => {
<GenericTableRow key={_id} tabIndex={0} role='link' onClick={() => onRowClick(_id)} action qa-user-id={_id}>
<GenericTableCell withTruncatedText>{name}</GenericTableCell>
<GenericTableCell withTruncatedText>{description}</GenericTableCell>
<RemoveTagButton _id={_id} reload={refetch} />
<GenericTableCell>
<IconButton
icon='trash'
small
title={t('Remove')}
onClick={(e) => {
e.stopPropagation();
handleDeleteTag(_id);
}}
/>
</GenericTableCell>
</GenericTableRow>
))}
</GenericTableBody>

@ -0,0 +1,34 @@
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useSetModal, useToastMessageDispatch, useRouter, useMethod, useTranslation } from '@rocket.chat/ui-contexts';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import GenericModal from '../../../../client/components/GenericModal';
export const useRemoveTag = () => {
const t = useTranslation();
const setModal = useSetModal();
const dispatchToastMessage = useToastMessageDispatch();
const removeTag = useMethod('livechat:removeTag');
const queryClient = useQueryClient();
const router = useRouter();
const handleDeleteTag = useMutableCallback((tagId) => {
const handleDelete = async () => {
try {
await removeTag(tagId);
dispatchToastMessage({ type: 'success', message: t('Tag_removed') });
router.navigate('/omnichannel/tags');
queryClient.invalidateQueries(['livechat-tags']);
} catch (error) {
dispatchToastMessage({ type: 'error', message: error });
} finally {
setModal();
}
};
setModal(<GenericModal variant='danger' onConfirm={handleDelete} onCancel={() => setModal()} confirmText={t('Delete')} />);
});
return handleDeleteTag;
};

@ -3277,7 +3277,7 @@ export type OmnichannelEndpoints = {
}>;
};
'/v1/livechat/tags/:tagId': {
GET: () => ILivechatTag | null;
GET: () => ILivechatTag;
};
'/v1/livechat/department': {
GET: (params?: LivechatDepartmentProps) => PaginatedResult<{

Loading…
Cancel
Save