diff --git a/app/livechat/lib/LivechatRoomType.js b/app/livechat/lib/LivechatRoomType.js index ff8e44ba983..c2392a9e711 100644 --- a/app/livechat/lib/LivechatRoomType.js +++ b/app/livechat/lib/LivechatRoomType.js @@ -126,4 +126,8 @@ export default class LivechatRoomType extends RoomTypeConfig { instance.tabBar.openUserInfo(); return true; } + + showQuickActionButtons() { + return true; + } } diff --git a/app/utils/client/lib/roomTypes.js b/app/utils/client/lib/roomTypes.js index 709d8ee6395..1fc9f81bf76 100644 --- a/app/utils/client/lib/roomTypes.js +++ b/app/utils/client/lib/roomTypes.js @@ -51,6 +51,10 @@ export const roomTypes = new class RocketChatRoomTypes extends RoomTypesCommon { return room && room.t; } + showQuickActionButtons(roomType) { + return this.roomTypes[roomType] && typeof this.roomTypes[roomType].showQuickActionButtons === 'function' && this.roomTypes[roomType].showQuickActionButtons(); + } + getUserStatusText(roomType, rid) { return this.roomTypes[roomType] && typeof this.roomTypes[roomType].getUserStatusText === 'function' && this.roomTypes[roomType].getUserStatusText(rid); } diff --git a/client/components/Header/Header.tsx b/client/components/Header/Header.tsx index a93ecb00bc4..1aecc40dfb4 100644 --- a/client/components/Header/Header.tsx +++ b/client/components/Header/Header.tsx @@ -11,7 +11,7 @@ const HeaderIcon: FC<{ icon: JSX.Element | { name: string; color?: string } | nu const ToolBox: FC = (props: any) => ; -const ToolBoxAction: FC = ({ id, icon, title, action, className, tabId, index, ...props }: any) => ; diff --git a/client/components/ModalSeparator/ModalSeparator.tsx b/client/components/ModalSeparator/ModalSeparator.tsx new file mode 100644 index 00000000000..f1c3c212204 --- /dev/null +++ b/client/components/ModalSeparator/ModalSeparator.tsx @@ -0,0 +1,7 @@ +import React, { FC } from 'react'; + +import './style.css'; + +const ModalSeparator: FC<{ text: string }> = ({ text, ...props }) =>
{text}
; + +export default ModalSeparator; diff --git a/client/components/ModalSeparator/index.js b/client/components/ModalSeparator/index.js new file mode 100644 index 00000000000..cc364696486 --- /dev/null +++ b/client/components/ModalSeparator/index.js @@ -0,0 +1,3 @@ +import ModalSeparator from './ModalSeparator'; + +export default ModalSeparator; diff --git a/client/components/ModalSeparator/style.css b/client/components/ModalSeparator/style.css new file mode 100644 index 00000000000..f95775461fc --- /dev/null +++ b/client/components/ModalSeparator/style.css @@ -0,0 +1,20 @@ +.modal-separator { + display: flex; + + text-align: center; + text-transform: uppercase; + + font-size: 0.625rem; + font-weight: normal; +} + +.modal-separator::before, +.modal-separator::after { + flex: 1; + + margin: auto 0.8em; + + content: ''; + + border-bottom: solid 2px #f2f3f5; +} diff --git a/client/components/Omnichannel/modals/CloseChatModal.tsx b/client/components/Omnichannel/modals/CloseChatModal.tsx new file mode 100644 index 00000000000..d6960b554b3 --- /dev/null +++ b/client/components/Omnichannel/modals/CloseChatModal.tsx @@ -0,0 +1,64 @@ +import React, { FC, useCallback, useState, useMemo } from 'react'; +import { Field, Button, TextInput, Icon, ButtonGroup, Modal, Box } from '@rocket.chat/fuselage'; +import { useAutoFocus } from '@rocket.chat/fuselage-hooks'; + +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useForm } from '../../../hooks/useForm'; +import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; + + +type CloseChatModalProps = { + onCancel: () => void; + onConfirm: (comment: string) => void; +}; + +const CloseChatModal: FC = ({ onCancel, onConfirm, ...props }) => { + const t = useTranslation(); + + const inputRef = useAutoFocus(true); + + const { values, handlers } = useForm({ comment: '' }); + + const { comment } = values as { comment: string }; + const { handleComment } = handlers; + const [commentError, setCommentError] = useState(''); + + + const handleConfirm = useCallback(() => { + onConfirm(comment); + }, [comment, onConfirm]); + + useComponentDidUpdate(() => { + setCommentError(!comment ? t('The_field_is_required', t('Comment')) : ''); + }, [t, comment]); + + const canConfirm = useMemo(() => !!comment, [comment]); + + return + + + {t('Closing_chat')} + + + + {t('Close_room_description')} + + {t('Comment')}* + + + + + {commentError} + + + + + + + + + + ; +}; + +export default CloseChatModal; diff --git a/client/components/Omnichannel/modals/ForwardChatModal.js b/client/components/Omnichannel/modals/ForwardChatModal.js new file mode 100644 index 00000000000..9a762b2ceec --- /dev/null +++ b/client/components/Omnichannel/modals/ForwardChatModal.js @@ -0,0 +1,87 @@ +import React, { useEffect, useState } from 'react'; +import { Field, Button, TextAreaInput, Icon, ButtonGroup, Modal, Box } from '@rocket.chat/fuselage'; +import { useMutableCallback, useAutoFocus } from '@rocket.chat/fuselage-hooks'; + +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useForm } from '../../../hooks/useForm'; +import ModalSeparator from '../../ModalSeparator'; +import DepartmentAutoComplete from '../../../views/omnichannel/DepartmentAutoComplete'; +import { UserAutoComplete } from '../../AutoComplete'; +import { useEndpointData } from '../../../hooks/useEndpointData'; + +const ForwardChatModal = ({ onForward, onCancel, ...props }) => { + const t = useTranslation(); + + const inputRef = useAutoFocus(true); + + const { values, handlers } = useForm({ departmentName: '', username: '', comment: '' }); + const { departmentName, username, comment } = values; + const [userId, setUserId] = useState(''); + + const { handleDepartmentName, handleUsername, handleComment } = handlers; + const { value } = useEndpointData('GET', `users.info?username=${ username }`); + + + const handleSend = useMutableCallback(() => { + onForward(departmentName, userId, comment); + }, [onForward, departmentName, userId, comment]); + + + const onChangeDepartment = useMutableCallback((departmentId) => { + handleDepartmentName(departmentId); + handleUsername(''); + setUserId(''); + }); + + const onChangeUsername = useMutableCallback((username) => { + handleUsername(username); + handleDepartmentName(''); + }); + + useEffect(() => { + if (!username) { return; } + const fetchData = async () => { + const { user } = value; + setUserId(user._id); + }; + fetchData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [username]); + + return + + + {t('Forward_chat')} + + + + + {t('Forward_to_department')} + + + + + + + {t('Forward_to_user')} + + + + + + {t('Leave_a_comment')} ({t('Optional')}) + + + + + + + + + + + + ; +}; + +export default ForwardChatModal; diff --git a/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx b/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx new file mode 100644 index 00000000000..61efbca83b1 --- /dev/null +++ b/client/components/Omnichannel/modals/ReturnChatQueueModal.tsx @@ -0,0 +1,44 @@ +import { Box, Button, ButtonGroup, Icon, Modal } from '@rocket.chat/fuselage'; +import React, { FC } from 'react'; + +import { useTranslation } from '../../../contexts/TranslationContext'; +import { withDoNotAskAgain, RequiredModalProps } from '../../withDoNotAskAgain'; + +type ReturnChatQueueModalProps = RequiredModalProps & { + onMoveChat: () => void; + onCancel: () => void; +}; + +const ReturnChatQueueModal: FC = ({ + onCancel, + onMoveChat, + confirm = onMoveChat, + dontAskAgain, + ...props +}) => { + const t = useTranslation(); + + return + + + {t('Return_to_the_queue')} + + + + {t('Would_you_like_to_return_the_queue')} + + + + {dontAskAgain} + + + + + + + ; +}; + +export const ReturnChatQueueDoNotAskAgain = withDoNotAskAgain(ReturnChatQueueModal); + +export default ReturnChatQueueModal; diff --git a/client/components/Omnichannel/modals/TranscriptModal.tsx b/client/components/Omnichannel/modals/TranscriptModal.tsx new file mode 100644 index 00000000000..676b930503e --- /dev/null +++ b/client/components/Omnichannel/modals/TranscriptModal.tsx @@ -0,0 +1,106 @@ +import React, { FC, useCallback, useEffect, useState, useMemo } from 'react'; +import { Field, Button, TextInput, Icon, ButtonGroup, Modal } from '@rocket.chat/fuselage'; +import { useAutoFocus } from '@rocket.chat/fuselage-hooks'; + +import { useTranslation } from '../../../contexts/TranslationContext'; +import { useForm } from '../../../hooks/useForm'; +import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; +import { IRoom } from '../../../../definition/IRoom'; + + +type TranscriptModalProps = { + email: string; + room?: IRoom; + onRequest: (email: string, subject: string) => void; + onSend?: (email: string, subject: string, token: string) => void; + onCancel: () => void; + onDiscard: () => void; +}; + +const TranscriptModal: FC = ({ email: emailDefault = '', room, onRequest, onSend, onCancel, onDiscard, ...props }) => { + const t = useTranslation(); + + const inputRef = useAutoFocus(true); + + const { values, handlers } = useForm({ email: emailDefault || '', subject: t('Transcript_of_your_livechat_conversation') }); + + const { email, subject } = values as { email: string; subject: string }; + const { handleEmail, handleSubject } = handlers; + const [emailError, setEmailError] = useState(''); + const [subjectError, setSubjectError] = useState(''); + const { transcriptRequest } = room as unknown as IRoom; + const roomOpen = room && room.open; + const token = room?.v?.token; + + + const handleRequest = useCallback(() => { + onRequest(email, subject); + }, [email, onRequest, subject]); + + const handleSend = useCallback(() => { + onSend && token && onSend(email, subject, token); + }, [email, onSend, subject, token]); + + const handleDiscard = useCallback(() => onDiscard(), [onDiscard]); + + useComponentDidUpdate(() => { + setEmailError(!email ? t('The_field_is_required', t('Email')) : ''); + }, [t, email]); + + useComponentDidUpdate(() => { + setSubjectError(!subject ? t('The_field_is_required', t('Subject')) : ''); + }, [t, subject]); + + const canSave = useMemo(() => !!subject, [subject]); + + useEffect(() => { + if (transcriptRequest) { + handleEmail(transcriptRequest.email); + handleSubject(transcriptRequest.subject); + } + }); + + return + + + {t('Transcript')} + + + + {!!transcriptRequest &&

{t('Livechat_transcript_already_requested_warning')}

} + + {t('Email')}* + + + + + {emailError} + + + + {t('Subject')}* + + + + + {subjectError} + + +
+ + + + { + roomOpen && transcriptRequest + ? + : + } + { + !roomOpen && + } + + +
; +}; + +export default TranscriptModal; diff --git a/client/providers/OmniChannelProvider.tsx b/client/providers/OmniChannelProvider.tsx index e78fc0cf81f..e7265c2e405 100644 --- a/client/providers/OmniChannelProvider.tsx +++ b/client/providers/OmniChannelProvider.tsx @@ -97,7 +97,7 @@ const OmnichannelEnabledProvider: FC = ({ children }) => { ...context, agentAvailable: user?.statusLivechat === 'available', })); - }, [user?.statusLivechat]); + }, [user?.statusLivechat, routeConfig]); if (!routeConfig || !user) { return ; diff --git a/client/views/room/Header/Header.js b/client/views/room/Header/Header.js index f10b67b9fde..07eb04056fc 100644 --- a/client/views/room/Header/Header.js +++ b/client/views/room/Header/Header.js @@ -1,4 +1,5 @@ import React from 'react'; +import { Box } from '@rocket.chat/fuselage'; import Header from '../../../components/Header'; import Breadcrumbs from '../../../components/Breadcrumbs'; @@ -7,6 +8,7 @@ import Encrypted from './icons/Encrypted'; import Favorite from './icons/Favorite'; import Translate from './icons/Translate'; import ToolBox from './ToolBox'; +import QuickActions from './Omnichannel/QuickActions'; import RoomAvatar from '../../../components/avatar/RoomAvatar'; import { useLayout } from '../../../contexts/LayoutContext'; import Burger from './Burger'; @@ -64,7 +66,7 @@ const DirectRoomHeader = ({ room }) => { const RoomHeader = ({ room, topic }) => { const { isMobile } = useLayout(); const avatar = ; - + const showQuickActions = roomTypes.showQuickActionButtons(room.t); return
{ isMobile && @@ -76,6 +78,9 @@ const RoomHeader = ({ room, topic }) => { + { showQuickActions && + + } {topic && } diff --git a/client/views/room/Header/Omnichannel/QuickActions/QuickActions.tsx b/client/views/room/Header/Omnichannel/QuickActions/QuickActions.tsx new file mode 100644 index 00000000000..d3c1ecf0817 --- /dev/null +++ b/client/views/room/Header/Omnichannel/QuickActions/QuickActions.tsx @@ -0,0 +1,222 @@ +import React, { memo, useContext, useCallback, useState, useEffect, useMemo } from 'react'; +import { BoxProps, ButtonGroup } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { FlowRouter } from 'meteor/kadira:flow-router'; +import { Session } from 'meteor/session'; +import toastr from 'toastr'; + +import Header from '../../../../../components/Header'; +import { useTranslation } from '../../../../../contexts/TranslationContext'; +import { QuickActionsActionConfig, QuickActionsEnum } from '../../../lib/QuickActions'; +import { useLayout } from '../../../../../contexts/LayoutContext'; +import { useSetModal } from '../../../../../contexts/ModalContext'; +import { QuickActionsContext } from '../../../lib/QuickActions/QuickActionsContext'; +import ReturnChatQueueModal from '../../../../../components/Omnichannel/modals/ReturnChatQueueModal'; +import ForwardChatModal from '../../../../../components/Omnichannel/modals/ForwardChatModal'; +import TranscriptModal from '../../../../../components/Omnichannel/modals/TranscriptModal'; +import CloseChatModal from '../../../../../components/Omnichannel/modals/CloseChatModal'; +import { handleError } from '../../../../../../app/utils/client'; +import { IRoom } from '../../../../../../definition/IRoom'; +import { useAtLeastOnePermission, usePermission, useRole } from '../../../../../contexts/AuthorizationContext'; +import { useUserId } from '../../../../../contexts/UserContext'; +import { useOmnichannelRouteConfig } from '../../../../../contexts/OmnichannelContext'; +import { useEndpoint, useMethod } from '../../../../../contexts/ServerContext'; + + +const QuickActions = ({ room, className }: { room: IRoom; className: BoxProps['className'] }): JSX.Element => { + const setModal = useSetModal(); + const { isMobile } = useLayout(); + const t = useTranslation(); + const { actions: mapActions } = useContext(QuickActionsContext); + const actions = (Array.from(mapActions.values()) as QuickActionsActionConfig[]).sort((a, b) => (a.order || 0) - (b.order || 0)); + const visibleActions = isMobile ? [] : actions.slice(0, 6); + const [email, setEmail] = useState(''); + const visitorRoomId = room.v?._id; + const rid = room._id; + const uid = useUserId(); + + const getVisitorInfo = useEndpoint('GET', `livechat/visitors.info?visitorId=${ visitorRoomId }`); + + const getVisitorEmail = useMutableCallback(async () => { + if (!visitorRoomId) { return; } + const { visitor: { visitorEmails } } = await getVisitorInfo(); + setEmail(visitorEmails && visitorEmails.length > 0 && visitorEmails[0].address); + }); + + useEffect(() => { + getVisitorEmail(); + }, [room, getVisitorEmail]); + + const closeModal = useCallback(() => setModal(null), [setModal]); + + const methodReturn = useMethod('livechat:returnAsInquiry'); + + const handleMoveChat = useCallback(async () => { + try { + await methodReturn(rid); + closeModal(); + Session.set('openedRoom', null); + FlowRouter.go('/home'); + } catch (error) { + handleError(error); + } + }, [closeModal, methodReturn, rid]); + + const requestTranscript = useMethod('livechat:requestTranscript'); + + const handleRequestTranscript = useCallback(async (email: string, subject: string) => { + try { + await requestTranscript(rid, email, subject); + closeModal(); + Session.set('openedRoom', null); + FlowRouter.go('/home'); + toastr.success(t('Livechat_transcript_has_been_requested')); + } catch (error) { + handleError(error); + } + }, [closeModal, requestTranscript, rid, t]); + + const sendTranscript = useMethod('livechat:sendTranscript'); + + const handleSendTranscript = useCallback(async (email: string, subject: string, token: string) => { + try { + await sendTranscript(token, rid, email, subject); + closeModal(); + } catch (error) { + handleError(error); + } + }, [closeModal, rid, sendTranscript]); + + const discardTranscript = useMethod('livechat:discardTranscript'); + + const handleDiscardTranscript = useCallback(async () => { + try { + await discardTranscript(rid); + toastr.success(t('Livechat_transcript_request_has_been_canceled')); + closeModal(); + } catch (error) { + handleError(error); + } + }, [closeModal, discardTranscript, rid, t]); + + const forwardChat = useMethod('livechat:transfer'); + + const handleForwardChat = useCallback(async (departmentId?: string, userId?: string, comment?: string) => { + if (departmentId && userId) { + return; + } + const transferData: { roomId: string; comment?: string; departmentId?: string; userId?: string } = { + roomId: rid, + comment, + }; + + if (departmentId) { transferData.departmentId = departmentId; } + if (userId) { transferData.userId = userId; } + + try { + await forwardChat(transferData); + closeModal(); + toastr.success(t('Transferred')); + FlowRouter.go('/'); + } catch (error) { + handleError(error); + } + }, [closeModal, forwardChat, rid, t]); + + const closeChat = useMethod('livechat:closeRoom'); + + const handleClose = useCallback(async (comment: string) => { + try { + await closeChat(rid, comment, { clientAction: true }); + closeModal(); + toastr.success(t('Chat_closed_successfully')); + } catch (error) { + handleError(error); + } + }, [closeChat, closeModal, rid, t]); + + const openModal = useMutableCallback((id: string) => { + switch (id) { + case QuickActionsEnum.MoveQueue: + setModal(); + break; + case QuickActionsEnum.Transcript: + setModal(); + break; + case QuickActionsEnum.ChatForward: + setModal(); + break; + case QuickActionsEnum.CloseChat: + setModal(); + break; + default: + break; + } + }); + + const actionDefault = useMutableCallback((e) => { + const index = e.currentTarget.getAttribute('data-quick-actions'); + const { id } = actions[index]; + openModal(id); + }); + + const hasManagerRole = useRole('livechat-manager'); + + const roomOpen = room && room.open && ((room.servedBy && room.servedBy._id === uid) || hasManagerRole); + + const canForwardGuest = usePermission('transfer-livechat-guest'); + + const canSendTranscript = usePermission('send-omnichannel-chat-transcript'); + + const canCloseRoom = usePermission('close-others-livechat-room'); + + const omnichannelRouteConfig = useOmnichannelRouteConfig(); + + const hasPermissionButtons = (id: string): boolean => { + switch (id) { + case QuickActionsEnum.MoveQueue: + return !!roomOpen && !!omnichannelRouteConfig?.returnQueue; + case QuickActionsEnum.ChatForward: + return !!roomOpen && canForwardGuest; + case QuickActionsEnum.Transcript: + return !!email && canSendTranscript; + case QuickActionsEnum.CloseChat: + return !!roomOpen && canCloseRoom; + default: + break; + } + return false; + }; + + const hasPermissionGroup = useAtLeastOnePermission( + useMemo(() => [ + 'close-others-livechat-room', 'transfer-livechat-guest', + ], []), + ); + + return + { visibleActions.map(({ id, color, icon, title, action = actionDefault }, index) => { + const props = { + id, + icon, + color, + title: t(title), + className, + tabId: id, + index, + primary: false, + 'data-quick-actions': index, + action, + key: id, + }; + + if (!hasPermissionGroup || !hasPermissionButtons(id)) { + return; + } + + return ; + })} + ; +}; + +export default memo(QuickActions); diff --git a/client/views/room/Header/Omnichannel/QuickActions/index.js b/client/views/room/Header/Omnichannel/QuickActions/index.js new file mode 100644 index 00000000000..20bec69e27f --- /dev/null +++ b/client/views/room/Header/Omnichannel/QuickActions/index.js @@ -0,0 +1,4 @@ +import QuickActions from './QuickActions'; + +export default QuickActions; +export * from './QuickActions'; diff --git a/client/views/room/lib/QuickActions/QuickActionsContext.tsx b/client/views/room/lib/QuickActions/QuickActionsContext.tsx new file mode 100644 index 00000000000..9ba316cd032 --- /dev/null +++ b/client/views/room/lib/QuickActions/QuickActionsContext.tsx @@ -0,0 +1,18 @@ +import { createContext } from 'react'; +import { EventHandlerOf } from '@rocket.chat/emitter'; + +import { actions, listen, QuickActionsActionConfig, QuickActionsAction, Events } from '.'; +import './defaultActions'; + + +export type QuickActionsEventHandler = (handler: EventHandlerOf) => Function; + +export type ChannelContextValue = { + actions: Map; + listen: QuickActionsEventHandler; +} + +export const QuickActionsContext = createContext({ + actions, + listen, +}); diff --git a/client/views/room/lib/QuickActions/defaultActions.ts b/client/views/room/lib/QuickActions/defaultActions.ts new file mode 100644 index 00000000000..b105ce9d270 --- /dev/null +++ b/client/views/room/lib/QuickActions/defaultActions.ts @@ -0,0 +1,35 @@ +import { addAction, QuickActionsEnum } from '.'; + + +addAction(QuickActionsEnum.MoveQueue, { + groups: ['live'], + id: QuickActionsEnum.MoveQueue, + title: 'Move_queue', + icon: 'burger-arrow-left', + order: 1, +}); + +addAction(QuickActionsEnum.ChatForward, { + groups: ['live'], + id: QuickActionsEnum.ChatForward, + title: 'Forward_chat', + icon: 'balloon-arrow-top-right', + order: 2, +}); + +addAction(QuickActionsEnum.Transcript, { + groups: ['live'], + id: QuickActionsEnum.Transcript, + title: 'Transcript', + icon: 'mail-arrow-top-right', + order: 3, +}); + +addAction(QuickActionsEnum.CloseChat, { + groups: ['live'], + id: QuickActionsEnum.CloseChat, + title: 'Close', + icon: 'balloon-close-top-right', + order: 4, + color: 'danger', +}); diff --git a/client/views/room/lib/QuickActions/index.tsx b/client/views/room/lib/QuickActions/index.tsx new file mode 100644 index 00000000000..8ceed9f3ae0 --- /dev/null +++ b/client/views/room/lib/QuickActions/index.tsx @@ -0,0 +1,47 @@ +import { ReactNode, MouseEvent } from 'react'; +import { BoxProps, OptionProps } from '@rocket.chat/fuselage'; + +import { IRoom } from '../../../../../definition/IRoom'; +import { generator, Events as GeneratorEvents } from '../Toolbox/generator'; + + +type QuickActionsHook = ({ room }: { room: IRoom }) => QuickActionsActionConfig | null + +type ActionRendererProps = Omit & { + className: BoxProps['className']; + tabId: QuickActionsActionConfig['id'] | undefined; + index: number; +} + +export type ActionRenderer = (props: ActionRendererProps) => ReactNode; + +type OptionRendererProps = OptionProps; + +export type OptionRenderer = (props: OptionRendererProps) => ReactNode; + +export type QuickActionsActionConfig = { + id: string; + icon: string; + color?: string; + title: string; + full?: true; + order?: number; + renderAction?: ActionRenderer; + groups: Array<'live'>; + action?: (e: MouseEvent) => void; +} + +export type QuickActionsAction = QuickActionsHook | QuickActionsActionConfig; + +const { listen, add: addAction, remove: deleteAction, store: actions } = generator(); + +export type Events = GeneratorEvents; + +export { listen, addAction, deleteAction, actions }; + +export enum QuickActionsEnum { + MoveQueue = 'rocket-move-to-queue', + ChatForward = 'rocket-chat-forward', + Transcript = 'rocket-transcript', + CloseChat = 'rocket-close-chat' +} diff --git a/definition/IRoom.ts b/definition/IRoom.ts index a6504dedc7d..2965f5e8d96 100644 --- a/definition/IRoom.ts +++ b/definition/IRoom.ts @@ -6,6 +6,12 @@ type RoomType = 'c' | 'd' | 'p' | 'l'; export type RoomID = string; export type ChannelName = string; +interface IRequestTranscript { + email: string; + requestedAt: Date; + requestedBy: IUser; + subject: string; +} export interface IRoom extends IRocketChatRecord { _id: RoomID; @@ -38,7 +44,16 @@ export interface IRoom extends IRocketChatRecord { balance: number; }[]; }; - + v?: { + _id?: string; + token?: string; + status?: string; + }; + transcriptRequest?: IRequestTranscript; + open?: boolean; + servedBy?: { + _id: string; + }; onHold?: boolean; } diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 52166fdec7c..1ea5855c03d 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1319,6 +1319,7 @@ "delete-user_description": "Permission to delete users", "Deleted": "Deleted!", "Department": "Department", + "Department_name": "Department name", "Department_removed": "Department removed", "Departments": "Departments", "Deployment_ID": "Deployment ID", @@ -2799,6 +2800,7 @@ "Most_popular_channels_top_5": "Most popular channels (Top 5)", "Move_beginning_message": "`%s` - Move to the beginning of the message", "Move_end_message": "`%s` - Move to the end of the message", + "Move_queue": "Move to the queue", "Msgs": "Msgs", "multi": "multi", "multi_line": "multi line", @@ -3322,6 +3324,7 @@ "Retry_Count": "Retry Count", "Return_to_home": "Return to home", "Return_to_previous_page": "Return to previous page", + "Return_to_the_queue": "Return back to the Queue", "Robot_Instructions_File_Content": "Robots.txt File Contents", "Rocket_Chat_Alert": "Rocket.Chat Alert", "Role": "Role", @@ -4298,6 +4301,7 @@ "Without_priority": "Without priority", "Worldwide": "Worldwide", "Would_you_like_to_return_the_inquiry": "Would you like to return the inquiry?", + "Would_you_like_to_return_the_queue": "Would you like to move back this room to the queue? All conversation history will be kept on the room.", "Would_you_like_to_place_chat_on_hold": "Would you like to place this chat On-Hold?", "Yes": "Yes", "Yes_archive_it": "Yes, archive it!",