[NEW] Quick action buttons for Omnichannel (#21123)

pull/20966/head
Rafael Ferreira 5 years ago committed by GitHub
parent 87b0b0d8aa
commit 9e8c5ec90f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      app/livechat/lib/LivechatRoomType.js
  2. 4
      app/utils/client/lib/roomTypes.js
  3. 3
      client/components/Header/Header.tsx
  4. 7
      client/components/ModalSeparator/ModalSeparator.tsx
  5. 3
      client/components/ModalSeparator/index.js
  6. 20
      client/components/ModalSeparator/style.css
  7. 64
      client/components/Omnichannel/modals/CloseChatModal.tsx
  8. 87
      client/components/Omnichannel/modals/ForwardChatModal.js
  9. 44
      client/components/Omnichannel/modals/ReturnChatQueueModal.tsx
  10. 106
      client/components/Omnichannel/modals/TranscriptModal.tsx
  11. 2
      client/providers/OmniChannelProvider.tsx
  12. 7
      client/views/room/Header/Header.js
  13. 222
      client/views/room/Header/Omnichannel/QuickActions/QuickActions.tsx
  14. 4
      client/views/room/Header/Omnichannel/QuickActions/index.js
  15. 18
      client/views/room/lib/QuickActions/QuickActionsContext.tsx
  16. 35
      client/views/room/lib/QuickActions/defaultActions.ts
  17. 47
      client/views/room/lib/QuickActions/index.tsx
  18. 17
      definition/IRoom.ts
  19. 4
      packages/rocketchat-i18n/i18n/en.i18n.json

@ -126,4 +126,8 @@ export default class LivechatRoomType extends RoomTypeConfig {
instance.tabBar.openUserInfo();
return true;
}
showQuickActionButtons() {
return true;
}
}

@ -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);
}

@ -11,7 +11,7 @@ const HeaderIcon: FC<{ icon: JSX.Element | { name: string; color?: string } | nu
const ToolBox: FC = (props: any) => <ButtonGroup mi='x4' medium {...props}/>;
const ToolBoxAction: FC = ({ id, icon, title, action, className, tabId, index, ...props }: any) => <ActionButton
const ToolBoxAction: FC = ({ id, icon, color, title, action, className, tabId, index, ...props }: any) => <ActionButton
className={className}
primary={tabId === id}
onClick={action}
@ -23,6 +23,7 @@ const ToolBoxAction: FC = ({ id, icon, title, action, className, tabId, index, .
ghost
tiny
overflow='visible'
color={!!color && color}
{...props}
/>;

@ -0,0 +1,7 @@
import React, { FC } from 'react';
import './style.css';
const ModalSeparator: FC<{ text: string }> = ({ text, ...props }) => <h6 className='modal-separator' {...props}>{text}</h6>;
export default ModalSeparator;

@ -0,0 +1,3 @@
import ModalSeparator from './ModalSeparator';
export default ModalSeparator;

@ -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;
}

@ -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<CloseChatModalProps> = ({ 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 <Modal {...props}>
<Modal.Header>
<Icon name='baloon-close-top-right' size={20}/>
<Modal.Title>{t('Closing_chat')}</Modal.Title>
<Modal.Close onClick={onCancel}/>
</Modal.Header>
<Modal.Content fontScale='p1'>
<Box color='neutral-600'>{t('Close_room_description')}</Box>
<Field marginBlock='x15'>
<Field.Label>{t('Comment')}*</Field.Label>
<Field.Row>
<TextInput ref={inputRef} error={commentError} flexGrow={1} value={comment} onChange={handleComment} placeholder={t('Please_add_a_comment')} />
</Field.Row>
<Field.Error>
{commentError}
</Field.Error>
</Field>
</Modal.Content>
<Modal.Footer>
<ButtonGroup align='end'>
<Button onClick={onCancel}>{t('Cancel')}</Button>
<Button disabled={!canConfirm} primary onClick={handleConfirm}>{t('Confirm')}</Button>
</ButtonGroup>
</Modal.Footer>
</Modal>;
};
export default CloseChatModal;

@ -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 <Modal {...props}>
<Modal.Header>
<Icon name='baloon-arrow-top-right' size={20}/>
<Modal.Title>{t('Forward_chat')}</Modal.Title>
<Modal.Close onClick={onCancel}/>
</Modal.Header>
<Modal.Content fontScale='p1'>
<Field mbe={'x30'}>
<Field.Label>{t('Forward_to_department')}</Field.Label>
<Field.Row>
<DepartmentAutoComplete value={departmentName} onChange={onChangeDepartment} flexShrink={1} placeholder={t('Department_name')} />
</Field.Row>
</Field>
<ModalSeparator text={t('or')} />
<Field mbs={'x30'}>
<Field.Label>{t('Forward_to_user')}</Field.Label>
<Field.Row>
<UserAutoComplete flexGrow={1} value={username} onChange={onChangeUsername} placeholder={t('Username')} />
</Field.Row>
</Field>
<Field marginBlock='x15'>
<Field.Label>{t('Leave_a_comment')} <Box is='span' color='neutral-600'>({t('Optional')})</Box></Field.Label>
<Field.Row>
<TextAreaInput ref={inputRef} rows={8} flexGrow={1} value={comment} onChange={handleComment} />
</Field.Row>
</Field>
</Modal.Content>
<Modal.Footer>
<ButtonGroup align='end'>
<Button onClick={onCancel}>{t('Cancel')}</Button>
<Button primary onClick={handleSend}>{t('Forward')}</Button>
</ButtonGroup>
</Modal.Footer>
</Modal>;
};
export default ForwardChatModal;

@ -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<ReturnChatQueueModalProps> = ({
onCancel,
onMoveChat,
confirm = onMoveChat,
dontAskAgain,
...props
}) => {
const t = useTranslation();
return <Modal {...props}>
<Modal.Header>
<Icon name='burger-arrow-left' size={20}/>
<Modal.Title>{t('Return_to_the_queue')}</Modal.Title>
<Modal.Close onClick={onCancel}/>
</Modal.Header>
<Modal.Content fontScale='p1'>
{t('Would_you_like_to_return_the_queue')}
</Modal.Content>
<Modal.Footer>
<Box>
{dontAskAgain}
<ButtonGroup align='end'>
<Button onClick={onCancel}>{t('Cancel')}</Button>
<Button primary onClick={confirm}>{t('Move_queue')}</Button>
</ButtonGroup>
</Box>
</Modal.Footer>
</Modal>;
};
export const ReturnChatQueueDoNotAskAgain = withDoNotAskAgain<ReturnChatQueueModalProps>(ReturnChatQueueModal);
export default ReturnChatQueueModal;

@ -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<TranscriptModalProps> = ({ 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 <Modal {...props}>
<Modal.Header>
<Icon name='mail-arrow-top-right' size={20}/>
<Modal.Title>{t('Transcript')}</Modal.Title>
<Modal.Close onClick={onCancel}/>
</Modal.Header>
<Modal.Content fontScale='p1'>
{!!transcriptRequest && <p>{t('Livechat_transcript_already_requested_warning')}</p>}
<Field marginBlock='x15'>
<Field.Label>{t('Email')}*</Field.Label>
<Field.Row>
<TextInput disabled={!!emailDefault || !!transcriptRequest} error={emailError} flexGrow={1} value={email} onChange={handleEmail} />
</Field.Row>
<Field.Error>
{emailError}
</Field.Error>
</Field>
<Field marginBlock='x15'>
<Field.Label>{t('Subject')}*</Field.Label>
<Field.Row>
<TextInput ref={inputRef} disabled={!!transcriptRequest} error={subjectError} flexGrow={1} value={subject} onChange={handleSubject} />
</Field.Row>
<Field.Error>
{subjectError}
</Field.Error>
</Field>
</Modal.Content>
<Modal.Footer>
<ButtonGroup align='end'>
<Button onClick={onCancel}>{t('Cancel')}</Button>
{
roomOpen && transcriptRequest
? <Button primary danger onClick={handleDiscard}>{t('Discard')}</Button>
: <Button disabled={!canSave} primary onClick={handleRequest}>{t('Request')}</Button>
}
{
!roomOpen && <Button disabled={!canSave} primary onClick={handleSend}>{t('Send')}</Button>
}
</ButtonGroup>
</Modal.Footer>
</Modal>;
};
export default TranscriptModal;

@ -97,7 +97,7 @@ const OmnichannelEnabledProvider: FC = ({ children }) => {
...context,
agentAvailable: user?.statusLivechat === 'available',
}));
}, [user?.statusLivechat]);
}, [user?.statusLivechat, routeConfig]);
if (!routeConfig || !user) {
return <OmnichannelDisabledProvider children={children}/>;

@ -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 = <RoomAvatar room={room}/>;
const showQuickActions = roomTypes.showQuickActionButtons(room.t);
return <Header>
{ isMobile && <Header.ToolBox>
<Burger/>
@ -76,6 +78,9 @@ const RoomHeader = ({ room, topic }) => {
<Favorite room={room} />
<Encrypted room={room} />
<Translate room={room} />
{ showQuickActions && <Box mis='x20' display='flex'>
<QuickActions room={room}/>
</Box> }
</Header.Content.Row>
<Header.Content.Row>
<Header.Subtitle>{topic && <MarkdownText variant='inlineWithoutBreaks' content={topic}/>}</Header.Subtitle>

@ -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(<ReturnChatQueueModal onMoveChat={handleMoveChat} onCancel={closeModal} />);
break;
case QuickActionsEnum.Transcript:
setModal(<TranscriptModal room={room} email={email} onRequest={handleRequestTranscript} onSend={handleSendTranscript} onDiscard={handleDiscardTranscript} onCancel={closeModal} />);
break;
case QuickActionsEnum.ChatForward:
setModal(<ForwardChatModal onForward={handleForwardChat} onCancel={closeModal} />);
break;
case QuickActionsEnum.CloseChat:
setModal(<CloseChatModal onConfirm={handleClose} onCancel={closeModal} />);
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 <ButtonGroup mi='x4' medium>
{ 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 <Header.ToolBoxAction {...props} />;
})}
</ButtonGroup>;
};
export default memo(QuickActions);

@ -0,0 +1,4 @@
import QuickActions from './QuickActions';
export default QuickActions;
export * from './QuickActions';

@ -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<Events, 'change'>) => Function;
export type ChannelContextValue = {
actions: Map<QuickActionsActionConfig['id'], QuickActionsAction>;
listen: QuickActionsEventHandler;
}
export const QuickActionsContext = createContext<ChannelContextValue>({
actions,
listen,
});

@ -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',
});

@ -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<QuickActionsActionConfig, 'renderAction' | 'groups'> & {
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<HTMLElement>) => void;
}
export type QuickActionsAction = QuickActionsHook | QuickActionsActionConfig;
const { listen, add: addAction, remove: deleteAction, store: actions } = generator<QuickActionsAction>();
export type Events = GeneratorEvents<QuickActionsAction>;
export { listen, addAction, deleteAction, actions };
export enum QuickActionsEnum {
MoveQueue = 'rocket-move-to-queue',
ChatForward = 'rocket-chat-forward',
Transcript = 'rocket-transcript',
CloseChat = 'rocket-close-chat'
}

@ -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;
}

@ -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!",

Loading…
Cancel
Save