[Improve] Do not require pre-configured tags in Omnichannel chats (#21488)

* create tags community component

* create new tags manual component to community version and with no tags.

* translate missing "Tag_already_exists".

* eslint fixes

* Refactoring Tags component, placeholder and better UI

* missed translation

* refactoring CloseChatModal to accept department data to check "requestTagBeforeClosingChat".

* variable names refactor

* bring tags sorted on rooms endpoints

* Revert "bring tags sorted on rooms endpoints"

This reverts commit e2555c05e4.

* save tags as sorted list.

* Rename Component name.

Co-authored-by: Renato Becker <renato.augusto.becker@gmail.com>
pull/21682/head^2
Rafael Ferreira 5 years ago committed by GitHub
parent 18c01bdfd6
commit 509b4dc239
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      client/components/Omnichannel/Skeleton.js
  2. 84
      client/components/Omnichannel/Tags.js
  3. 37
      client/components/Omnichannel/modals/CloseChatModal.js
  4. 19
      client/components/Omnichannel/modals/CloseChatModalData.js
  5. 13
      client/views/omnichannel/directory/chats/contextualBar/RoomEdit.js
  6. 13
      client/views/room/Header/Omnichannel/QuickActions/QuickActions.tsx
  7. 1
      definition/IRoom.ts
  8. 1
      packages/rocketchat-i18n/i18n/en.i18n.json
  9. 1
      packages/rocketchat-i18n/i18n/pt.i18n.json

@ -0,0 +1,9 @@
import { Box, Skeleton } from '@rocket.chat/fuselage';
import React from 'react';
export const FormSkeleton = (props) => (
<Box w='full' pb='x24' {...props}>
<Skeleton mbe='x8' />
<Skeleton mbe='x4' />
</Box>
);

@ -0,0 +1,84 @@
import { Field, TextInput, Chip, Button } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import React, { useState } from 'react';
import { useSubscription } from 'use-subscription';
import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext';
import { useTranslation } from '../../contexts/TranslationContext';
import { AsyncStatePhase } from '../../hooks/useAsyncState';
import { useEndpointData } from '../../hooks/useEndpointData';
import { formsSubscription } from '../../views/omnichannel/additionalForms';
import { FormSkeleton } from './Skeleton';
const Tags = ({ tags = [], handler = () => {}, error = '' }) => {
const { value: tagsResult = [], phase: stateTags } = useEndpointData('livechat/tags.list');
const t = useTranslation();
const forms = useSubscription(formsSubscription);
const { useCurrentChatTags = () => {} } = forms;
const Tags = useCurrentChatTags();
const dispatchToastMessage = useToastMessageDispatch();
const [tagValue, handleTagValue] = useState('');
const removeTag = (tag) => {
const tagsFiltered = tags.filter((tagArray) => tagArray !== tag);
handler(tagsFiltered);
};
const handleTagTextSubmit = useMutableCallback(() => {
if (!tagValue || tagValue.trim() === '') {
dispatchToastMessage({ type: 'error', message: t('Enter_a_tag') });
handleTagValue('');
return;
}
if (tags.includes(tagValue)) {
dispatchToastMessage({ type: 'error', message: t('Tag_already_exists') });
return;
}
handler([...tags, tagValue]);
handleTagValue('');
});
if ([stateTags].includes(AsyncStatePhase.LOADING)) {
return <FormSkeleton />;
}
const { tags: tagsList } = tagsResult;
return (
<>
<Field.Label mb='x4'>{t('Tags')}</Field.Label>
{Tags && tagsList && tagsList.length > 0 ? (
<Field.Row>
<Tags value={tags} handler={handler} />
</Field.Row>
) : (
<>
<Field.Row>
<TextInput
error={error}
value={tagValue}
onChange={(event) => handleTagValue(event.target.value)}
flexGrow={1}
placeholder={t('Enter_a_tag')}
/>
<Button disabled={!tagValue} mis='x8' title={t('add')} onClick={handleTagTextSubmit}>
{t('Add')}
</Button>
</Field.Row>
<Field.Row justifyContent='flex-start'>
{tags.map((tag, i) => (
<Chip key={i} onClick={() => removeTag(tag)} mie='x8'>
{tag}
</Chip>
))}
</Field.Row>
</>
)}
</>
);
};
export default Tags;

@ -1,28 +1,24 @@
import { Field, Button, TextInput, Icon, ButtonGroup, Modal, Box } from '@rocket.chat/fuselage';
import { useAutoFocus } from '@rocket.chat/fuselage-hooks';
import React, { useCallback, useState, useMemo } from 'react';
import { useSubscription } from 'use-subscription';
import React, { useCallback, useState, useMemo, useEffect } from 'react';
import { useTranslation } from '../../../contexts/TranslationContext';
import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate';
import { useForm } from '../../../hooks/useForm';
import { formsSubscription } from '../../../views/omnichannel/additionalForms';
import Tags from '../Tags';
const CloseChatModal = ({ onCancel, onConfirm, ...props }) => {
const CloseChatModal = ({ department = {}, onCancel, onConfirm }) => {
const t = useTranslation();
const inputRef = useAutoFocus(true);
const forms = useSubscription(formsSubscription);
const { useCurrentChatTags = () => {} } = forms;
const Tags = useCurrentChatTags();
const { values, handlers } = useForm({ comment: '', tags: [] });
const { comment, tags } = values;
const { handleComment, handleTags } = handlers;
const [commentError, setCommentError] = useState('');
const [tagError, setTagError] = useState('');
const [tagRequired, setTagRequired] = useState(false);
const handleConfirm = useCallback(() => {
onConfirm(comment, tags);
@ -32,10 +28,23 @@ const CloseChatModal = ({ onCancel, onConfirm, ...props }) => {
setCommentError(!comment ? t('The_field_is_required', t('Comment')) : '');
}, [t, comment]);
const canConfirm = useMemo(() => !!comment, [comment]);
const canConfirm = useMemo(() => (!tagRequired ? !!comment : !!comment && tags.length > 0), [
comment,
tagRequired,
tags,
]);
useEffect(() => {
department?.requestTagBeforeClosingChat && setTagRequired(true);
setTagError(
tagRequired && (!tags || tags.length === 0)
? t('error-tags-must-be-assigned-before-closing-chat')
: '',
);
}, [department, tagRequired, t, tags]);
return (
<Modal {...props}>
<Modal>
<Modal.Header>
<Icon name='baloon-close-top-right' size={20} />
<Modal.Title>{t('Closing_chat')}</Modal.Title>
@ -59,10 +68,8 @@ const CloseChatModal = ({ onCancel, onConfirm, ...props }) => {
</Field>
{Tags && (
<Field>
<Field.Label mb='x4'>{t('Tags')}</Field.Label>
<Field.Row>
<Tags value={tags} handler={handleTags} />
</Field.Row>
<Tags tags={tags} handler={handleTags} error={tagError} />
<Field.Error>{tagError}</Field.Error>
</Field>
)}
</Modal.Content>

@ -0,0 +1,19 @@
import React from 'react';
import { AsyncStatePhase } from '../../../hooks/useAsyncState';
import { useEndpointData } from '../../../hooks/useEndpointData';
import { FormSkeleton } from '../Skeleton';
import CloseChatModal from './CloseChatModal';
const CloseChatModalData = ({ departmentId, onCancel, onConfirm }) => {
const { value: data, phase: state } = useEndpointData(
`livechat/department/${departmentId}?includeAgents=false`,
);
if ([state].includes(AsyncStatePhase.LOADING)) {
return <FormSkeleton />;
}
const { department } = data || {};
return <CloseChatModal onCancel={onCancel} onConfirm={onConfirm} department={department} />;
};
export default CloseChatModalData;

@ -5,6 +5,7 @@ import { useSubscription } from 'use-subscription';
import { hasAtLeastOnePermission } from '../../../../../../app/authorization/client';
import CustomFieldsForm from '../../../../../components/CustomFieldsForm';
import Tags from '../../../../../components/Omnichannel/Tags';
import VerticalBar from '../../../../../components/VerticalBar';
import { useMethod } from '../../../../../contexts/ServerContext';
import { useToastMessageDispatch } from '../../../../../contexts/ToastMessagesContext';
@ -21,7 +22,7 @@ const initialValuesUser = {
const initialValuesRoom = {
topic: '',
tags: '',
tags: [],
livechatData: {},
priorityId: '',
};
@ -75,9 +76,8 @@ function RoomEdit({ room, visitor, reload, close }) {
const forms = useSubscription(formsSubscription);
const { useCurrentChatTags = () => {}, usePrioritiesSelect = () => {} } = forms;
const { usePrioritiesSelect = () => {} } = forms;
const Tags = useCurrentChatTags();
const PrioritiesSelect = usePrioritiesSelect();
const {
@ -137,7 +137,7 @@ function RoomEdit({ room, visitor, reload, close }) {
const roomData = {
_id: room._id,
topic,
tags: Object.values(tags),
tags: tags.sort(),
livechatData,
...(priorityId && { priorityId }),
};
@ -187,10 +187,7 @@ function RoomEdit({ room, visitor, reload, close }) {
</Field>
{Tags && (
<Field>
<Field.Label mb='x4'>{t('Tags')}</Field.Label>
<Field.Row>
<Tags value={Object.values(tags)} handler={handleTags} />
</Field.Row>
<Tags tags={tags} handler={handleTags} />
</Field>
)}
{PrioritiesSelect && priorities && priorities.length > 0 && (

@ -18,6 +18,7 @@ import { IRoom } from '../../../../../../definition/IRoom';
import PlaceChatOnHoldModal from '../../../../../../ee/app/livechat-enterprise/client/components/modals/PlaceChatOnHoldModal';
import Header from '../../../../../components/Header';
import CloseChatModal from '../../../../../components/Omnichannel/modals/CloseChatModal';
import CloseChatModalData from '../../../../../components/Omnichannel/modals/CloseChatModalData';
import ForwardChatModal from '../../../../../components/Omnichannel/modals/ForwardChatModal';
import ReturnChatQueueModal from '../../../../../components/Omnichannel/modals/ReturnChatQueueModal';
import TranscriptModal from '../../../../../components/Omnichannel/modals/TranscriptModal';
@ -213,7 +214,17 @@ const QuickActions: FC<QuickActionsProps> = ({ room, className }) => {
);
break;
case QuickActionsEnum.CloseChat:
setModal(<CloseChatModal onConfirm={handleClose} onCancel={closeModal} />);
setModal(
room?.departmentId ? (
<CloseChatModalData
departmentId={room?.departmentId}
onConfirm={handleClose}
onCancel={closeModal}
/>
) : (
<CloseChatModal onConfirm={handleClose} onCancel={closeModal} />
),
);
break;
case QuickActionsEnum.OnHoldChat:
setModal(<PlaceChatOnHoldModal onOnHoldChat={handleOnHoldChat} onCancel={closeModal} />);

@ -60,6 +60,7 @@ export interface IRoom extends IRocketChatRecord {
_id: string;
};
onHold?: boolean;
departmentId?: string;
}
export interface IDirectMessageRoom extends Omit<IRoom, 'default' | 'featured' | 'u' | 'name'> {

@ -3781,6 +3781,7 @@
"System_messages": "System Messages",
"Tag": "Tag",
"Tag_removed": "Tag Removed",
"Tag_already_exists": "Tag already exists",
"Take_it": "Take it!",
"Taken_at": "Taken at",
"Target user not allowed to receive messages": "Target user not allowed to receive messages",

@ -2805,6 +2805,7 @@
"System_messages": "Mensagens do sistema",
"Tag": "Tag",
"Take_it": "Pegue!",
"Tag_already_exists": "Tag já existe",
"Target user not allowed to receive messages": "Utilizador de destino não autorizado a receber mensagens",
"TargetRoom": "Sala de destino",
"TargetRoom_Description": "A sala onde as mensagens serão enviadas, que são o resultado desse evento a ser disparado. Somente uma sala-alvo é permitido e deve existir.",

Loading…
Cancel
Save