Skip to content

Commit 509b4dc

Browse files
[Improve] Do not require pre-configured tags in Omnichannel chats (RocketChat#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 e2555c0. * save tags as sorted list. * Rename Component name. Co-authored-by: Renato Becker <renato.augusto.becker@gmail.com>
1 parent 18c01bd commit 509b4dc

9 files changed

Lines changed: 154 additions & 24 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Box, Skeleton } from '@rocket.chat/fuselage';
2+
import React from 'react';
3+
4+
export const FormSkeleton = (props) => (
5+
<Box w='full' pb='x24' {...props}>
6+
<Skeleton mbe='x8' />
7+
<Skeleton mbe='x4' />
8+
</Box>
9+
);
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { Field, TextInput, Chip, Button } from '@rocket.chat/fuselage';
2+
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
3+
import React, { useState } from 'react';
4+
import { useSubscription } from 'use-subscription';
5+
6+
import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext';
7+
import { useTranslation } from '../../contexts/TranslationContext';
8+
import { AsyncStatePhase } from '../../hooks/useAsyncState';
9+
import { useEndpointData } from '../../hooks/useEndpointData';
10+
import { formsSubscription } from '../../views/omnichannel/additionalForms';
11+
import { FormSkeleton } from './Skeleton';
12+
13+
const Tags = ({ tags = [], handler = () => {}, error = '' }) => {
14+
const { value: tagsResult = [], phase: stateTags } = useEndpointData('livechat/tags.list');
15+
const t = useTranslation();
16+
const forms = useSubscription(formsSubscription);
17+
18+
const { useCurrentChatTags = () => {} } = forms;
19+
const Tags = useCurrentChatTags();
20+
21+
const dispatchToastMessage = useToastMessageDispatch();
22+
23+
const [tagValue, handleTagValue] = useState('');
24+
25+
const removeTag = (tag) => {
26+
const tagsFiltered = tags.filter((tagArray) => tagArray !== tag);
27+
handler(tagsFiltered);
28+
};
29+
30+
const handleTagTextSubmit = useMutableCallback(() => {
31+
if (!tagValue || tagValue.trim() === '') {
32+
dispatchToastMessage({ type: 'error', message: t('Enter_a_tag') });
33+
handleTagValue('');
34+
return;
35+
}
36+
if (tags.includes(tagValue)) {
37+
dispatchToastMessage({ type: 'error', message: t('Tag_already_exists') });
38+
return;
39+
}
40+
handler([...tags, tagValue]);
41+
handleTagValue('');
42+
});
43+
44+
if ([stateTags].includes(AsyncStatePhase.LOADING)) {
45+
return <FormSkeleton />;
46+
}
47+
48+
const { tags: tagsList } = tagsResult;
49+
50+
return (
51+
<>
52+
<Field.Label mb='x4'>{t('Tags')}</Field.Label>
53+
{Tags && tagsList && tagsList.length > 0 ? (
54+
<Field.Row>
55+
<Tags value={tags} handler={handler} />
56+
</Field.Row>
57+
) : (
58+
<>
59+
<Field.Row>
60+
<TextInput
61+
error={error}
62+
value={tagValue}
63+
onChange={(event) => handleTagValue(event.target.value)}
64+
flexGrow={1}
65+
placeholder={t('Enter_a_tag')}
66+
/>
67+
<Button disabled={!tagValue} mis='x8' title={t('add')} onClick={handleTagTextSubmit}>
68+
{t('Add')}
69+
</Button>
70+
</Field.Row>
71+
<Field.Row justifyContent='flex-start'>
72+
{tags.map((tag, i) => (
73+
<Chip key={i} onClick={() => removeTag(tag)} mie='x8'>
74+
{tag}
75+
</Chip>
76+
))}
77+
</Field.Row>
78+
</>
79+
)}
80+
</>
81+
);
82+
};
83+
84+
export default Tags;

client/components/Omnichannel/modals/CloseChatModal.js

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
11
import { Field, Button, TextInput, Icon, ButtonGroup, Modal, Box } from '@rocket.chat/fuselage';
22
import { useAutoFocus } from '@rocket.chat/fuselage-hooks';
3-
import React, { useCallback, useState, useMemo } from 'react';
4-
import { useSubscription } from 'use-subscription';
3+
import React, { useCallback, useState, useMemo, useEffect } from 'react';
54

65
import { useTranslation } from '../../../contexts/TranslationContext';
76
import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate';
87
import { useForm } from '../../../hooks/useForm';
9-
import { formsSubscription } from '../../../views/omnichannel/additionalForms';
8+
import Tags from '../Tags';
109

11-
const CloseChatModal = ({ onCancel, onConfirm, ...props }) => {
10+
const CloseChatModal = ({ department = {}, onCancel, onConfirm }) => {
1211
const t = useTranslation();
1312

1413
const inputRef = useAutoFocus(true);
15-
const forms = useSubscription(formsSubscription);
16-
17-
const { useCurrentChatTags = () => {} } = forms;
18-
19-
const Tags = useCurrentChatTags();
2014

2115
const { values, handlers } = useForm({ comment: '', tags: [] });
2216

2317
const { comment, tags } = values;
2418
const { handleComment, handleTags } = handlers;
2519
const [commentError, setCommentError] = useState('');
20+
const [tagError, setTagError] = useState('');
21+
const [tagRequired, setTagRequired] = useState(false);
2622

2723
const handleConfirm = useCallback(() => {
2824
onConfirm(comment, tags);
@@ -32,10 +28,23 @@ const CloseChatModal = ({ onCancel, onConfirm, ...props }) => {
3228
setCommentError(!comment ? t('The_field_is_required', t('Comment')) : '');
3329
}, [t, comment]);
3430

35-
const canConfirm = useMemo(() => !!comment, [comment]);
31+
const canConfirm = useMemo(() => (!tagRequired ? !!comment : !!comment && tags.length > 0), [
32+
comment,
33+
tagRequired,
34+
tags,
35+
]);
36+
37+
useEffect(() => {
38+
department?.requestTagBeforeClosingChat && setTagRequired(true);
39+
setTagError(
40+
tagRequired && (!tags || tags.length === 0)
41+
? t('error-tags-must-be-assigned-before-closing-chat')
42+
: '',
43+
);
44+
}, [department, tagRequired, t, tags]);
3645

3746
return (
38-
<Modal {...props}>
47+
<Modal>
3948
<Modal.Header>
4049
<Icon name='baloon-close-top-right' size={20} />
4150
<Modal.Title>{t('Closing_chat')}</Modal.Title>
@@ -59,10 +68,8 @@ const CloseChatModal = ({ onCancel, onConfirm, ...props }) => {
5968
</Field>
6069
{Tags && (
6170
<Field>
62-
<Field.Label mb='x4'>{t('Tags')}</Field.Label>
63-
<Field.Row>
64-
<Tags value={tags} handler={handleTags} />
65-
</Field.Row>
71+
<Tags tags={tags} handler={handleTags} error={tagError} />
72+
<Field.Error>{tagError}</Field.Error>
6673
</Field>
6774
)}
6875
</Modal.Content>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
3+
import { AsyncStatePhase } from '../../../hooks/useAsyncState';
4+
import { useEndpointData } from '../../../hooks/useEndpointData';
5+
import { FormSkeleton } from '../Skeleton';
6+
import CloseChatModal from './CloseChatModal';
7+
8+
const CloseChatModalData = ({ departmentId, onCancel, onConfirm }) => {
9+
const { value: data, phase: state } = useEndpointData(
10+
`livechat/department/${departmentId}?includeAgents=false`,
11+
);
12+
if ([state].includes(AsyncStatePhase.LOADING)) {
13+
return <FormSkeleton />;
14+
}
15+
const { department } = data || {};
16+
return <CloseChatModal onCancel={onCancel} onConfirm={onConfirm} department={department} />;
17+
};
18+
19+
export default CloseChatModalData;

client/views/omnichannel/directory/chats/contextualBar/RoomEdit.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useSubscription } from 'use-subscription';
55

66
import { hasAtLeastOnePermission } from '../../../../../../app/authorization/client';
77
import CustomFieldsForm from '../../../../../components/CustomFieldsForm';
8+
import Tags from '../../../../../components/Omnichannel/Tags';
89
import VerticalBar from '../../../../../components/VerticalBar';
910
import { useMethod } from '../../../../../contexts/ServerContext';
1011
import { useToastMessageDispatch } from '../../../../../contexts/ToastMessagesContext';
@@ -21,7 +22,7 @@ const initialValuesUser = {
2122

2223
const initialValuesRoom = {
2324
topic: '',
24-
tags: '',
25+
tags: [],
2526
livechatData: {},
2627
priorityId: '',
2728
};
@@ -75,9 +76,8 @@ function RoomEdit({ room, visitor, reload, close }) {
7576

7677
const forms = useSubscription(formsSubscription);
7778

78-
const { useCurrentChatTags = () => {}, usePrioritiesSelect = () => {} } = forms;
79+
const { usePrioritiesSelect = () => {} } = forms;
7980

80-
const Tags = useCurrentChatTags();
8181
const PrioritiesSelect = usePrioritiesSelect();
8282

8383
const {
@@ -137,7 +137,7 @@ function RoomEdit({ room, visitor, reload, close }) {
137137
const roomData = {
138138
_id: room._id,
139139
topic,
140-
tags: Object.values(tags),
140+
tags: tags.sort(),
141141
livechatData,
142142
...(priorityId && { priorityId }),
143143
};
@@ -187,10 +187,7 @@ function RoomEdit({ room, visitor, reload, close }) {
187187
</Field>
188188
{Tags && (
189189
<Field>
190-
<Field.Label mb='x4'>{t('Tags')}</Field.Label>
191-
<Field.Row>
192-
<Tags value={Object.values(tags)} handler={handleTags} />
193-
</Field.Row>
190+
<Tags tags={tags} handler={handleTags} />
194191
</Field>
195192
)}
196193
{PrioritiesSelect && priorities && priorities.length > 0 && (

client/views/room/Header/Omnichannel/QuickActions/QuickActions.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { IRoom } from '../../../../../../definition/IRoom';
1818
import PlaceChatOnHoldModal from '../../../../../../ee/app/livechat-enterprise/client/components/modals/PlaceChatOnHoldModal';
1919
import Header from '../../../../../components/Header';
2020
import CloseChatModal from '../../../../../components/Omnichannel/modals/CloseChatModal';
21+
import CloseChatModalData from '../../../../../components/Omnichannel/modals/CloseChatModalData';
2122
import ForwardChatModal from '../../../../../components/Omnichannel/modals/ForwardChatModal';
2223
import ReturnChatQueueModal from '../../../../../components/Omnichannel/modals/ReturnChatQueueModal';
2324
import TranscriptModal from '../../../../../components/Omnichannel/modals/TranscriptModal';
@@ -213,7 +214,17 @@ const QuickActions: FC<QuickActionsProps> = ({ room, className }) => {
213214
);
214215
break;
215216
case QuickActionsEnum.CloseChat:
216-
setModal(<CloseChatModal onConfirm={handleClose} onCancel={closeModal} />);
217+
setModal(
218+
room?.departmentId ? (
219+
<CloseChatModalData
220+
departmentId={room?.departmentId}
221+
onConfirm={handleClose}
222+
onCancel={closeModal}
223+
/>
224+
) : (
225+
<CloseChatModal onConfirm={handleClose} onCancel={closeModal} />
226+
),
227+
);
217228
break;
218229
case QuickActionsEnum.OnHoldChat:
219230
setModal(<PlaceChatOnHoldModal onOnHoldChat={handleOnHoldChat} onCancel={closeModal} />);

definition/IRoom.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export interface IRoom extends IRocketChatRecord {
6060
_id: string;
6161
};
6262
onHold?: boolean;
63+
departmentId?: string;
6364
}
6465

6566
export interface IDirectMessageRoom extends Omit<IRoom, 'default' | 'featured' | 'u' | 'name'> {

packages/rocketchat-i18n/i18n/en.i18n.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3781,6 +3781,7 @@
37813781
"System_messages": "System Messages",
37823782
"Tag": "Tag",
37833783
"Tag_removed": "Tag Removed",
3784+
"Tag_already_exists": "Tag already exists",
37843785
"Take_it": "Take it!",
37853786
"Taken_at": "Taken at",
37863787
"Target user not allowed to receive messages": "Target user not allowed to receive messages",

packages/rocketchat-i18n/i18n/pt.i18n.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2805,6 +2805,7 @@
28052805
"System_messages": "Mensagens do sistema",
28062806
"Tag": "Tag",
28072807
"Take_it": "Pegue!",
2808+
"Tag_already_exists": "Tag já existe",
28082809
"Target user not allowed to receive messages": "Utilizador de destino não autorizado a receber mensagens",
28092810
"TargetRoom": "Sala de destino",
28102811
"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.",

0 commit comments

Comments
 (0)