diff --git a/app/apps/server/bridges/listeners.js b/app/apps/server/bridges/listeners.js index 617e86801a799f43dc6a7de05f39e576bba78a60..d0cf92eec0bdc02a82039e403a59c62bb503dc1b 100644 --- a/app/apps/server/bridges/listeners.js +++ b/app/apps/server/bridges/listeners.js @@ -29,9 +29,6 @@ export class AppListenerBridge { case AppInterface.IPreRoomUserJoined: case AppInterface.IPostRoomUserJoined: return 'roomEvent'; - case AppInterface.IPostExternalComponentOpened: - case AppInterface.IPostExternalComponentClosed: - return 'externalComponentEvent'; /** * @deprecated please prefer the AppInterface.IPostLivechatRoomClosed event */ @@ -43,13 +40,20 @@ export class AppListenerBridge { case AppInterface.IPostLivechatRoomTransferred: return 'livechatEvent'; case AppInterface.IUIKitInteractionHandler: - return 'uiKitInteractionEvent'; + case AppInterface.IUIKitLivechatInteractionHandler: + case AppInterface.IPostExternalComponentOpened: + case AppInterface.IPostExternalComponentClosed: + return 'defaultEvent'; } })(); return this[method](event, ...payload); } + async defaultEvent(inte, payload) { + return this.orch.getManager().getListenerManager().executeListener(inte, payload); + } + async messageEvent(inte, message) { const msg = this.orch.getConverters().get('messages').convertMessage(message); const result = await this.orch.getManager().getListenerManager().executeListener(inte, msg); @@ -86,14 +90,6 @@ export class AppListenerBridge { return this.orch.getConverters().get('rooms').convertAppRoom(result); } - async externalComponentEvent(inte, externalComponent) { - return this.orch.getManager().getListenerManager().executeListener(inte, externalComponent); - } - - async uiKitInteractionEvent(inte, action) { - return this.orch.getManager().getListenerManager().executeListener(inte, action); - } - async livechatEvent(inte, data) { switch (inte) { case AppInterface.IPostLivechatAgentAssigned: diff --git a/app/apps/server/communication/uikit.js b/app/apps/server/communication/uikit.js index ded3cb2b45e8a8cb98829db33fb2bd54d96dd9df..0eb8bb87d42daeab3221078d9411b0f1464526a1 100644 --- a/app/apps/server/communication/uikit.js +++ b/app/apps/server/communication/uikit.js @@ -3,9 +3,11 @@ import rateLimit from 'express-rate-limit'; import { Meteor } from 'meteor/meteor'; import { WebApp } from 'meteor/webapp'; import { UIKitIncomingInteractionType } from '@rocket.chat/apps-engine/definition/uikit'; +import { AppInterface } from '@rocket.chat/apps-engine/definition/metadata'; import { Users } from '../../../models/server'; import { settings } from '../../../settings/server'; +import { Apps } from '../orchestrator'; const apiServer = express(); @@ -37,19 +39,21 @@ router.use((req, res, next) => { const { 'x-user-id': userId, 'x-auth-token': authToken, + 'x-visitor-token': visitorToken, } = req.headers; - if (!userId || !authToken) { - return unauthorized(res); + if (userId && authToken) { + req.user = Users.findOneByIdAndLoginToken(userId, authToken); + req.userId = req.user._id; } - const user = Users.findOneByIdAndLoginToken(userId, authToken); - if (!user) { - return unauthorized(res); + if (visitorToken) { + req.visitor = Apps.getConverters().get('visitors').convertByToken(visitorToken); } - req.user = user; - req.userId = user._id; + if (!req.user && !req.visitor) { + return unauthorized(res); + } next(); }); @@ -81,6 +85,7 @@ export class AppUIKitInteractionApi { container, } = req.body; + const { visitor } = req; const room = this.orch.getConverters().get('rooms').convertById(rid); const user = this.orch.getConverters().get('users').convertToApp(req.user); const message = mid && this.orch.getConverters().get('messages').convertById(mid); @@ -94,11 +99,14 @@ export class AppUIKitInteractionApi { triggerId, payload, user, + visitor, room, }; try { - const result = Promise.await(this.orch.getBridges().getListenerBridge().uiKitInteractionEvent('IUIKitInteractionHandler', action)); + const eventInterface = !visitor ? AppInterface.IUIKitInteractionHandler : AppInterface.IUIKitLivechatInteractionHandler; + + const result = Promise.await(this.orch.triggerEvent(eventInterface, action)); res.send(result); } catch (e) { @@ -131,7 +139,7 @@ export class AppUIKitInteractionApi { }; try { - Promise.await(this.orch.getBridges().getListenerBridge().uiKitInteractionEvent('IUIKitInteractionHandler', action)); + Promise.await(this.orch.triggerEvent('IUIKitInteractionHandler', action)); res.send(200); } catch (e) { @@ -161,7 +169,7 @@ export class AppUIKitInteractionApi { }; try { - const result = Promise.await(this.orch.getBridges().getListenerBridge().uiKitInteractionEvent('IUIKitInteractionHandler', action)); + const result = Promise.await(this.orch.triggerEvent('IUIKitInteractionHandler', action)); res.send(result); } catch (e) { diff --git a/app/ui-flextab/client/tabs/userActions.js b/app/ui-flextab/client/tabs/userActions.js index deda9cbc7715bdf326d6c5612f702f37fd84a08c..5af08975d72e8893be00ab40ca219c56ec2eb1b5 100644 --- a/app/ui-flextab/client/tabs/userActions.js +++ b/app/ui-flextab/client/tabs/userActions.js @@ -4,6 +4,7 @@ import { Session } from 'meteor/session'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import toastr from 'toastr'; import _ from 'underscore'; +import s from 'underscore.string'; import { WebRTC } from '../../../webrtc/client'; import { ChatRoom, ChatSubscription, RoomRoles, Subscriptions } from '../../../models/client'; @@ -223,7 +224,7 @@ export const getActions = ({ user, directActions, hideAdminControls }) => { } Meteor.call('removeRoomOwner', Session.get('openedRoom'), _id, success(() => { const room = ChatRoom.findOne(Session.get('openedRoom')); - toastr.success(TAPi18n.__('User__username__removed_from__room_name__owners', { username, room_name: roomTypes.getRoomName(room.t, room) })); + toastr.success(TAPi18n.__('User__username__removed_from__room_name__owners', { username, room_name: s.escapeHTML(roomTypes.getRoomName(room.t, room)) })); })); }) }; } @@ -238,7 +239,7 @@ export const getActions = ({ user, directActions, hideAdminControls }) => { } Meteor.call('addRoomOwner', Session.get('openedRoom'), _id, success(() => { const room = ChatRoom.findOne(Session.get('openedRoom')); - toastr.success(TAPi18n.__('User__username__is_now_a_owner_of__room_name_', { username, room_name: roomTypes.getRoomName(room.t, room) })); + toastr.success(TAPi18n.__('User__username__is_now_a_owner_of__room_name_', { username, room_name: s.escapeHTML(roomTypes.getRoomName(room.t, room)) })); })); }), }; @@ -261,7 +262,7 @@ export const getActions = ({ user, directActions, hideAdminControls }) => { } Meteor.call('removeRoomLeader', Session.get('openedRoom'), _id, success(() => { const room = ChatRoom.findOne(Session.get('openedRoom')); - toastr.success(TAPi18n.__('User__username__removed_from__room_name__leaders', { username, room_name: roomTypes.getRoomName(room.t, room) })); + toastr.success(TAPi18n.__('User__username__removed_from__room_name__leaders', { username, room_name: s.escapeHTML(roomTypes.getRoomName(room.t, room)) })); })); }), }; @@ -277,7 +278,7 @@ export const getActions = ({ user, directActions, hideAdminControls }) => { } Meteor.call('addRoomLeader', Session.get('openedRoom'), _id, success(() => { const room = ChatRoom.findOne(Session.get('openedRoom')); - toastr.success(TAPi18n.__('User__username__is_now_a_leader_of__room_name_', { username, room_name: roomTypes.getRoomName(room.t, room) })); + toastr.success(TAPi18n.__('User__username__is_now_a_leader_of__room_name_', { username, room_name: s.escapeHTML(roomTypes.getRoomName(room.t, room)) })); })); }), }; @@ -300,7 +301,7 @@ export const getActions = ({ user, directActions, hideAdminControls }) => { } Meteor.call('removeRoomModerator', Session.get('openedRoom'), _id, success(() => { const room = ChatRoom.findOne(Session.get('openedRoom')); - toastr.success(TAPi18n.__('User__username__removed_from__room_name__moderators', { username, room_name: roomTypes.getRoomName(room.t, room) })); + toastr.success(TAPi18n.__('User__username__removed_from__room_name__moderators', { username, room_name: s.escapeHTML(roomTypes.getRoomName(room.t, room)) })); })); }), }; @@ -316,7 +317,7 @@ export const getActions = ({ user, directActions, hideAdminControls }) => { } Meteor.call('addRoomModerator', Session.get('openedRoom'), _id, success(() => { const room = ChatRoom.findOne(Session.get('openedRoom')); - toastr.success(TAPi18n.__('User__username__is_now_a_moderator_of__room_name_', { username, room_name: roomTypes.getRoomName(room.t, room) })); + toastr.success(TAPi18n.__('User__username__is_now_a_moderator_of__room_name_', { username, room_name: s.escapeHTML(roomTypes.getRoomName(room.t, room)) })); })); }), }; @@ -374,7 +375,7 @@ export const getActions = ({ user, directActions, hideAdminControls }) => { } modal.open({ title: t('Are_you_sure'), - text: t('The_user_wont_be_able_to_type_in_s', roomTypes.getRoomName(room.t, room)), + text: t('The_user_wont_be_able_to_type_in_s', s.escapeHTML(roomTypes.getRoomName(room.t, room))), type: 'warning', showCancelButton: true, confirmButtonColor: '#DD6B55', @@ -386,7 +387,7 @@ export const getActions = ({ user, directActions, hideAdminControls }) => { Meteor.call('muteUserInRoom', { rid, username }, success(() => { modal.open({ title: t('Muted'), - text: t('User_has_been_muted_in_s', roomTypes.getRoomName(room.t, room)), + text: t('User_has_been_muted_in_s', s.escapeHTML(roomTypes.getRoomName(room.t, room))), type: 'success', timer: 2000, showConfirmButton: false, @@ -408,7 +409,7 @@ export const getActions = ({ user, directActions, hideAdminControls }) => { } modal.open({ title: t('Are_you_sure'), - text: t('The_user_will_be_removed_from_s', roomTypes.getRoomName(room.t, room)), + text: t('The_user_will_be_removed_from_s', s.escapeHTML(roomTypes.getRoomName(room.t, room))), type: 'warning', showCancelButton: true, confirmButtonColor: '#DD6B55', @@ -419,7 +420,7 @@ export const getActions = ({ user, directActions, hideAdminControls }) => { }, () => Meteor.call('removeUserFromRoom', { rid, username: user.username }, success(() => { modal.open({ title: t('Removed'), - text: t('User_has_been_removed_from_s', roomTypes.getRoomName(room.t, room)), + text: t('User_has_been_removed_from_s', s.escapeHTML(roomTypes.getRoomName(room.t, room))), type: 'success', timer: 2000, showConfirmButton: false, diff --git a/client/channel/ExportMessages/index.js b/client/channel/ExportMessages/index.js index 7dbc537b3fa32e93d64b7677b2ffa1308e04f25e..93b0e06dfefd91631c91eb6b5d20df817b792187 100644 --- a/client/channel/ExportMessages/index.js +++ b/client/channel/ExportMessages/index.js @@ -5,6 +5,7 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import toastr from 'toastr'; import VerticalBar from '../../components/basic/VerticalBar'; +import { UserAutoComplete } from '../../components/basic/AutoComplete'; import { useTranslation } from '../../contexts/TranslationContext'; import { useForm } from '../../hooks/useForm'; import { useUserRoom } from '../hooks/useUserRoom'; @@ -63,9 +64,14 @@ const FileExport = ({ onCancel, rid }) => { return ( <FieldGroup> <Field> - <Field.Label>{t('Date')}</Field.Label> + <Field.Label>{t('Date_From')}</Field.Label> <Field.Row> <TextInput type='date' value={dateFrom} onChange={handleDateFrom} /> + </Field.Row> + </Field> + <Field> + <Field.Label>{t('Date_to')}</Field.Label> + <Field.Row> <TextInput type='date' value={dateTo} onChange={handleDateTo} /> </Field.Row> </Field> @@ -196,7 +202,7 @@ const MailExportForm = ({ onCancel, rid }) => { <Field> <Field.Label>{t('To_users')}</Field.Label> <Field.Row> - <TextInput placeholder={t('Username_Placeholder')} value={toUsers} onChange={handleToUsers} addon={<Icon name='at' size='x20'/>} /> + <UserAutoComplete value={toUsers} onChange={handleToUsers}/> </Field.Row> </Field> <Field> diff --git a/client/channel/hooks/useUserInfoActions.js b/client/channel/hooks/useUserInfoActions.js index 720f041f02a8b99e11aa5a737a2a9f17f29d7237..155511438e81ccbc733c18dc057ec421992d1a89 100644 --- a/client/channel/hooks/useUserInfoActions.js +++ b/client/channel/hooks/useUserInfoActions.js @@ -1,6 +1,7 @@ import React, { useCallback, useMemo } from 'react'; import { Button, ButtonGroup, Icon, Modal, Box } from '@rocket.chat/fuselage'; import { useAutoFocus, useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import s from 'underscore.string'; import { useTranslation } from '../../contexts/TranslationContext'; import { useReactiveValue } from '../../hooks/useReactiveValue'; @@ -145,7 +146,7 @@ export const useUserInfoActions = (user = {}, rid) => { }, }; - const roomName = room && room.t && roomTypes.getRoomName(room.t, room); + const roomName = room && room.t && s.escapeHTML(roomTypes.getRoomName(room.t, room)); const userCanSetOwner = usePermission('set-owner', rid); const userCanSetLeader = usePermission('set-leader', rid); diff --git a/client/components/basic/AutoCompleteAgent.js b/client/components/basic/AutoCompleteAgent.js new file mode 100644 index 0000000000000000000000000000000000000000..5e42fdd0df5fb908767fa98dc8539eb3a98aaaf0 --- /dev/null +++ b/client/components/basic/AutoCompleteAgent.js @@ -0,0 +1,22 @@ +import React, { useMemo, useState } from 'react'; +import { AutoComplete, Option } from '@rocket.chat/fuselage'; + +import { useEndpointDataExperimental } from '../../hooks/useEndpointDataExperimental'; +import { useTranslation } from '../../contexts/TranslationContext'; + +export const AutoCompleteAgent = React.memo((props) => { + const t = useTranslation(); + const [filter, setFilter] = useState(''); + const { data } = useEndpointDataExperimental('livechat/users/agent', useMemo(() => ({ text: filter }), [filter])); + + const options = useMemo(() => (data && [{ value: 'all', label: t('All') }, ...data.users.map((user) => ({ value: user._id, label: user.name }))]) || [{ value: 'all', label: t('All') }], [data, t]); + + return <AutoComplete + {...props} + filter={filter} + setFilter={setFilter} + renderSelected={({ label }) => <>{label}</>} + renderItem={({ value, ...props }) => <Option key={value} {...props} />} + options={ options } + />; +}); diff --git a/client/components/basic/AutoCompleteDepartment.js b/client/components/basic/AutoCompleteDepartment.js new file mode 100644 index 0000000000000000000000000000000000000000..0fa8784cf91158cc4c6b75ffad2f9bc63b0d3d02 --- /dev/null +++ b/client/components/basic/AutoCompleteDepartment.js @@ -0,0 +1,22 @@ +import React, { useMemo, useState } from 'react'; +import { AutoComplete, Option } from '@rocket.chat/fuselage'; + +import { useEndpointDataExperimental } from '../../hooks/useEndpointDataExperimental'; +import { useTranslation } from '../../contexts/TranslationContext'; + +export const AutoCompleteDepartment = React.memo((props) => { + const t = useTranslation(); + const [filter, setFilter] = useState(''); + const { data } = useEndpointDataExperimental('livechat/department', useMemo(() => ({ text: filter }), [filter])); + + const options = useMemo(() => (data && [{ value: 'all', label: t('All') }, ...data.departments.map((department) => ({ value: department._id, label: department.name }))]) || [{ value: 'all', label: t('All') }], [data, t]); + + return <AutoComplete + {...props} + filter={filter} + setFilter={setFilter} + renderSelected={({ label }) => <>{label}</>} + renderItem={({ value, ...props }) => <Option key={value} {...props} />} + options={ options } + />; +}); diff --git a/client/omnichannel/agents/AgentEdit.js b/client/omnichannel/agents/AgentEdit.js index f7fa60ae8bf7dda9f14d672d52066f744358729b..4654e77a5c21f87b8dadba8ece5b8acc48111e0c 100644 --- a/client/omnichannel/agents/AgentEdit.js +++ b/client/omnichannel/agents/AgentEdit.js @@ -26,7 +26,7 @@ export default function AgentEditWithData({ uid, reload }) { return <FormSkeleton/>; } - if (error || userDepartmentsError || availableDepartmentsError) { + if (error || userDepartmentsError || availableDepartmentsError || !data || !data.user) { return <Box mbs='x16'>{t('User_not_found')}</Box>; } diff --git a/client/omnichannel/agents/AgentInfo.js b/client/omnichannel/agents/AgentInfo.js index ad5159ce56a922cc4057ceacd4ae5c69d5d65d05..153ddaf09717879fcfd78b209197e5dba003f673 100644 --- a/client/omnichannel/agents/AgentInfo.js +++ b/client/omnichannel/agents/AgentInfo.js @@ -30,11 +30,11 @@ export const AgentInfo = React.memo(function AgentInfo({ return <FormSkeleton/>; } - if (error) { + if (error || !data || !data.user) { return <Box mbs='x16'>{t('User_not_found')}</Box>; } - const { user } = data || { user: {} }; + const { user } = data; const { username, statusLivechat, diff --git a/client/omnichannel/agents/AgentsRoute.js b/client/omnichannel/agents/AgentsRoute.js index 2e511de4a413fb2acc4e410f1ca9f08a900205f3..acac7a3cd657c30dbaf77f8f84e96949b5c29d3f 100644 --- a/client/omnichannel/agents/AgentsRoute.js +++ b/client/omnichannel/agents/AgentsRoute.js @@ -1,7 +1,7 @@ import { useDebouncedValue, useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useMemo, useCallback, useState } from 'react'; -import { Box, Table, Icon } from '@rocket.chat/fuselage'; +import { Box, Table, Icon, Button } from '@rocket.chat/fuselage'; import { Th } from '../../components/GenericTable'; import { useTranslation } from '../../contexts/TranslationContext'; @@ -15,19 +15,45 @@ import AgentInfo from './AgentInfo'; import UserAvatar from '../../components/basic/avatar/UserAvatar'; import { useRouteParameter, useRoute } from '../../contexts/RouterContext'; import VerticalBar from '../../components/basic/VerticalBar'; +import DeleteWarningModal from '../DeleteWarningModal'; +import { useSetModal } from '../../contexts/ModalContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; + export function RemoveAgentButton({ _id, reload }) { const deleteAction = useEndpointAction('DELETE', `livechat/users/agent/${ _id }`); + const setModal = useSetModal(); + const dispatchToastMessage = useToastMessageDispatch(); + const t = useTranslation(); + - const handleRemoveClick = useMutableCallback(async (e) => { - e.preventDefault(); + const handleRemoveClick = useMutableCallback(async () => { const result = await deleteAction(); if (result.success === true) { reload(); } }); - return <Table.Cell fontScale='p1' color='hint' onClick={handleRemoveClick} withTruncatedText><Icon name='trash' size='x20'/></Table.Cell>; + const handleDelete = useMutableCallback((e) => { + e.stopPropagation(); + const onDeleteAgent = async () => { + try { + await handleRemoveClick(); + dispatchToastMessage({ type: 'success', message: t('Agent_removed') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + setModal(); + }; + + setModal(<DeleteWarningModal onDelete={onDeleteAgent} onCancel={() => setModal()}/>); + }); + + return <Table.Cell fontScale='p1' color='hint' withTruncatedText> + <Button small ghost title={t('Remove')} onClick={handleDelete}> + <Icon name='trash' size='x16'/> + </Button> + </Table.Cell>; } export function AgentInfoActions({ reload }) { @@ -35,6 +61,8 @@ export function AgentInfoActions({ reload }) { const _id = useRouteParameter('id'); const agentsRoute = useRoute('omnichannel-agents'); const deleteAction = useEndpointAction('DELETE', `livechat/users/agent/${ _id }`); + const setModal = useSetModal(); + const dispatchToastMessage = useToastMessageDispatch(); const handleRemoveClick = useMutableCallback(async () => { const result = await deleteAction(); @@ -44,13 +72,28 @@ export function AgentInfoActions({ reload }) { } }); + const handleDelete = useMutableCallback((e) => { + e.stopPropagation(); + const onDeleteAgent = async () => { + try { + await handleRemoveClick(); + dispatchToastMessage({ type: 'success', message: t('Agent_removed') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + setModal(); + }; + + setModal(<DeleteWarningModal onDelete={onDeleteAgent} onCancel={() => setModal()}/>); + }); + const handleEditClick = useMutableCallback(() => agentsRoute.push({ context: 'edit', id: _id, })); return [ - <AgentInfo.Action key={t('Remove')} title={t('Remove')} label={t('Remove')} onClick={handleRemoveClick} icon={'trash'} />, + <AgentInfo.Action key={t('Remove')} title={t('Remove')} label={t('Remove')} onClick={handleDelete} icon={'trash'} />, <AgentInfo.Action key={t('Edit')} title={t('Edit')} label={t('Edit')} onClick={handleEditClick} icon={'edit'} />, ]; } diff --git a/client/omnichannel/businessHours/EditBusinessHoursPage.js b/client/omnichannel/businessHours/EditBusinessHoursPage.js index 305be5b77d7e124f0c6d70b09e985c5197544d6e..51db2695949e9fa9710f9bbe305a09faa306b888 100644 --- a/client/omnichannel/businessHours/EditBusinessHoursPage.js +++ b/client/omnichannel/businessHours/EditBusinessHoursPage.js @@ -57,6 +57,7 @@ const EditBusinessHoursPage = ({ id, type }) => { await save(payload); dispatchToastMessage({ type: 'success', message: t('Business_hours_updated') }); + router.push({}); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } diff --git a/client/omnichannel/currentChats/CurrentChatsPage.js b/client/omnichannel/currentChats/CurrentChatsPage.js index 3d2806880d5a0e71daa2d78d9bc921a1dbc0010a..01cae3222db18b408a489e087c57ff9ece400db4 100644 --- a/client/omnichannel/currentChats/CurrentChatsPage.js +++ b/client/omnichannel/currentChats/CurrentChatsPage.js @@ -1,6 +1,6 @@ import React, { useEffect, useMemo } from 'react'; import { TextInput, Box, Icon, MultiSelect, Select, InputBox, Menu } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useMutableCallback, useLocalStorage } from '@rocket.chat/fuselage-hooks'; import moment from 'moment'; import { useSubscription } from 'use-subscription'; @@ -10,12 +10,13 @@ import { useTranslation } from '../../contexts/TranslationContext'; import { useEndpointDataExperimental } from '../../hooks/useEndpointDataExperimental'; import { usePermission } from '../../contexts/AuthorizationContext'; import { GenericTable } from '../../components/GenericTable'; -import { useForm } from '../../hooks/useForm'; import { useMethod } from '../../contexts/ServerContext'; +import DeleteWarningModal from '../DeleteWarningModal'; +import { useSetModal } from '../../contexts/ModalContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; +import { AutoCompleteDepartment } from '../../components/basic/AutoCompleteDepartment'; +import { AutoCompleteAgent } from '../../components/basic/AutoCompleteAgent'; - -// moment(new Date(from)).utc().format('YYYY-MM-DDTHH:mm:ss') -// guest: '', servedBy: '', status: '', department: '', from: '', to: '' const Label = (props) => <Box fontScale='p2' color='default' {...props} />; const RemoveAllClosed = ({ handleClearFilters, handleRemoveClosed, ...props }) => { @@ -43,38 +44,42 @@ const RemoveAllClosed = ({ handleClearFilters, handleRemoveClosed, ...props }) = const FilterByText = ({ setFilter, reload, ...props }) => { + const setModal = useSetModal(); + const dispatchToastMessage = useToastMessageDispatch(); const t = useTranslation(); - const { data: departments } = useEndpointDataExperimental('livechat/department') || {}; - const { data: agents } = useEndpointDataExperimental('livechat/users/agent'); - - const depOptions = useMemo(() => (departments && departments.departments ? departments.departments.map(({ _id, name }) => [_id, name || _id]) : []), [departments]); - const agentOptions = useMemo(() => (agents && agents.users ? agents.users.map(({ _id, username }) => [_id, username || _id]) : []), [agents]); + const { data: allCustomFields } = useEndpointDataExperimental('livechat/custom-fields'); const statusOptions = [['all', t('All')], ['closed', t('Closed')], ['opened', t('Open')]]; - - useEffect(() => { - !depOptions.find((dep) => dep[0] === 'all') && depOptions.unshift(['all', t('All')]); - }, [depOptions, t]); - - const { values, handlers, reset } = useForm({ guest: '', servedBy: [], status: 'all', department: 'all', from: '', to: '', tags: [] }); - const { - handleGuest, - handleServedBy, - handleStatus, - handleDepartment, - handleFrom, - handleTo, - handleTags, - } = handlers; - const { - guest, - servedBy, - status, - department, - from, - to, - tags, - } = values; + const customFieldsOptions = useMemo(() => (allCustomFields && allCustomFields.customFields ? allCustomFields.customFields.map(({ _id, label }) => [_id, label]) : []), [allCustomFields]); + + const [guest, setGuest] = useLocalStorage('guest', ''); + const [servedBy, setServedBy] = useLocalStorage('servedBy', 'all'); + const [status, setStatus] = useLocalStorage('status', 'all'); + const [department, setDepartment] = useLocalStorage('department', 'all'); + const [from, setFrom] = useLocalStorage('from', ''); + const [to, setTo] = useLocalStorage('to', ''); + const [tags, setTags] = useLocalStorage('tags', []); + const [customFields, setCustomFields] = useLocalStorage('tags', []); + + const handleGuest = useMutableCallback((e) => setGuest(e.target.value)); + const handleServedBy = useMutableCallback((e) => setServedBy(e)); + const handleStatus = useMutableCallback((e) => setStatus(e)); + const handleDepartment = useMutableCallback((e) => setDepartment(e)); + const handleFrom = useMutableCallback((e) => setFrom(e.target.value)); + const handleTo = useMutableCallback((e) => setTo(e.target.value)); + const handleTags = useMutableCallback((e) => setTags(e)); + const handleCustomFields = useMutableCallback((e) => setCustomFields(e)); + + const reset = useMutableCallback(() => { + setGuest(''); + setServedBy('all'); + setStatus('all'); + setDepartment('all'); + setFrom(''); + setTo(''); + setTags([]); + setCustomFields([]); + }); const forms = useSubscription(formsSubscription); @@ -84,8 +89,11 @@ const FilterByText = ({ setFilter, reload, ...props }) => { const Tags = useCurrentChatTags(); - const onSubmit = useMutableCallback((e) => e.preventDefault()); + const reducer = function(acc, curr) { + acc[curr] = ''; + return acc; + }; useEffect(() => { setFilter({ @@ -96,8 +104,9 @@ const FilterByText = ({ setFilter, reload, ...props }) => { from: from && moment(new Date(from)).utc().format('YYYY-MM-DDTHH:mm:ss'), to: to && moment(new Date(to)).utc().format('YYYY-MM-DDTHH:mm:ss'), tags, + customFields: customFields.reduce(reducer, {}), }); - }, [setFilter, guest, servedBy, status, department, from, to, tags]); + }, [setFilter, guest, servedBy, status, department, from, to, tags, customFields]); const handleClearFilters = useMutableCallback(() => { reset(); @@ -106,10 +115,21 @@ const FilterByText = ({ setFilter, reload, ...props }) => { const removeClosedChats = useMethod('livechat:removeAllClosedRooms'); const handleRemoveClosed = useMutableCallback(async () => { - await removeClosedChats(); - reload(); + const onDeleteAll = async () => { + try { + await removeClosedChats(); + reload(); + dispatchToastMessage({ type: 'success', message: t('Chat_removed') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + setModal(); + }; + + setModal(<DeleteWarningModal onDelete={onDeleteAll} onCancel={() => setModal()}/>); }); + return <Box mb='x16' is='form' onSubmit={onSubmit} display='flex' flexDirection='column' {...props}> <Box display='flex' flexDirection='row' flexWrap='wrap' {...props}> <Box display='flex' mie='x8' flexGrow={1} flexDirection='column'> @@ -118,11 +138,11 @@ const FilterByText = ({ setFilter, reload, ...props }) => { </Box> <Box display='flex' mie='x8' flexGrow={1} flexDirection='column'> <Label mb='x4'>{t('Served_By')}:</Label> - <MultiSelect flexShrink={0} options={agentOptions} value={servedBy} onChange={handleServedBy} placeholder={t('Served_By')}/> + <AutoCompleteAgent value={servedBy} onChange={handleServedBy}/> </Box> <Box display='flex' mie='x8' flexGrow={1} flexDirection='column'> <Label mb='x4'>{t('Department')}:</Label> - <Select flexShrink={0} options={depOptions} value={department} onChange={handleDepartment} placeholder={t('Department')}/> + <AutoCompleteDepartment value={department} onChange={handleDepartment}/> </Box> <Box display='flex' mie='x8' flexGrow={1} flexDirection='column'> <Label mb='x4'>{t('Status')}:</Label> @@ -145,6 +165,12 @@ const FilterByText = ({ setFilter, reload, ...props }) => { <Tags value={tags} handler={handleTags} /> </Box> </Box>} + {allCustomFields && <Box display='flex' flexDirection='row' marginBlockStart='x8' {...props}> + <Box display='flex' mie='x8' flexGrow={1} flexDirection='column'> + <Label mb='x4'>{t('Custom_fields')}:</Label> + <MultiSelect options={customFieldsOptions} value={customFields} onChange={handleCustomFields} flexGrow={1} {...props}/> + </Box> + </Box>} </Box>; }; @@ -156,7 +182,6 @@ function CurrentChatsPage({ params, title, renderRow, - departments, reload, children, }) { @@ -164,7 +189,7 @@ function CurrentChatsPage({ <Page> <Page.Header title={title} /> <Page.Content> - <GenericTable FilterComponent={FilterByText} header={header} renderRow={renderRow} results={data && data.rooms} departments={departments} total={data && data.total} setParams={setParams} params={params} reload={reload}/> + <GenericTable FilterComponent={FilterByText} header={header} renderRow={renderRow} results={data && data.rooms} total={data && data.total} setParams={setParams} params={params} reload={reload}/> </Page.Content> </Page> {children} diff --git a/client/omnichannel/currentChats/CurrentChatsRoute.js b/client/omnichannel/currentChats/CurrentChatsRoute.js index 18fb5ced1f00407edc9a02098ea4502cfc66a0a7..da9edc10ea86010259cdfc1acd84dc27c0263aeb 100644 --- a/client/omnichannel/currentChats/CurrentChatsRoute.js +++ b/client/omnichannel/currentChats/CurrentChatsRoute.js @@ -2,7 +2,7 @@ import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useMemo, useCallback, useState } from 'react'; -import { Table, Icon } from '@rocket.chat/fuselage'; +import { Table, Icon, Button } from '@rocket.chat/fuselage'; import moment from 'moment'; import { FlowRouter } from 'meteor/kadira:flow-router'; @@ -13,35 +13,54 @@ import { useEndpointDataExperimental } from '../../hooks/useEndpointDataExperime import { useMethod } from '../../contexts/ServerContext'; import { usePermission } from '../../contexts/AuthorizationContext'; import NotAuthorizedPage from '../../components/NotAuthorizedPage'; -import { useRoute } from '../../contexts/RouterContext'; import CurrentChatsPage from './CurrentChatsPage'; +import DeleteWarningModal from '../DeleteWarningModal'; +import { useSetModal } from '../../contexts/ModalContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; + +export function RemoveChatButton({ _id, reload }) { + const removeChat = useMethod('livechat:removeRoom'); + const setModal = useSetModal(); + const dispatchToastMessage = useToastMessageDispatch(); + const t = useTranslation(); -export function RemoveCurrentChatButton({ _id, reload }) { - const removeCurrentChat = useMethod('livechat:removeCurrentChat'); - const currentChatsRoute = useRoute('omnichannel-currentChats'); - - - const handleRemoveClick = useMutableCallback(async (e) => { - e.preventDefault(); - e.stopPropagation(); + const handleRemoveClick = useMutableCallback(async () => { try { - await removeCurrentChat(_id); + await removeChat(_id); } catch (error) { console.log(error); } - currentChatsRoute.push({}); reload(); }); - return <Table.Cell fontScale='p1' color='hint' onClick={handleRemoveClick} withTruncatedText><Icon name='trash' size='x20'/></Table.Cell>; + const handleDelete = useMutableCallback((e) => { + e.stopPropagation(); + const onDeleteAgent = async () => { + try { + await handleRemoveClick(); + dispatchToastMessage({ type: 'success', message: t('Chat_removed') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + setModal(); + }; + + setModal(<DeleteWarningModal onDelete={onDeleteAgent} onCancel={() => setModal()}/>); + }); + + return <Table.Cell fontScale='p1' color='hint'withTruncatedText> + <Button small ghost title={t('Remove')} onClick={handleDelete}> + <Icon name='trash' size='x16'/> + </Button> + </Table.Cell>; } const sortDir = (sortDir) => (sortDir === 'asc' ? 1 : -1); -const useQuery = ({ guest, servedBy, department, status, from, to, tags, itemsPerPage, current }, [column, direction]) => useMemo(() => { +const useQuery = ({ guest, servedBy, department, status, from, to, tags, customFields, itemsPerPage, current }, [column, direction]) => useMemo(() => { const query = { - roomName: guest, - sort: JSON.stringify({ [column]: sortDir(direction), usernames: column === 'name' ? sortDir(direction) : undefined }), + ...guest && { roomName: guest }, + sort: JSON.stringify({ [column]: sortDir(direction), ts: column === 'ts' ? sortDir(direction) : undefined }), ...itemsPerPage && { count: itemsPerPage }, ...current && { offset: current }, }; @@ -52,27 +71,30 @@ const useQuery = ({ guest, servedBy, department, status, from, to, tags, itemsPe if (status !== 'all') { query.open = status === 'open'; } - if (servedBy && servedBy.length > 0) { - query.agents = servedBy; + if (servedBy && servedBy !== 'all') { + query.agents = [servedBy]; } - if (department && department.length > 0) { - if (department !== 'all') { - query.departmentId = department; - } + if (department && department !== 'all') { + query.departmentId = department; } + if (tags && tags.length > 0) { query.tags = tags; } + if (customFields && Object.keys(customFields).length > 0) { + query.customFields = JSON.stringify(customFields); + } + return query; -}, [guest, column, direction, itemsPerPage, current, from, to, status, servedBy, department, tags]); +}, [guest, column, direction, itemsPerPage, current, from, to, status, servedBy, department, tags, customFields]); function CurrentChatsRoute() { const t = useTranslation(); const canViewCurrentChats = usePermission('view-livechat-current-chats'); - const [params, setParams] = useState({ fname: '', servedBy: [], status: '', department: '', from: '', to: '', current: 0, itemsPerPage: 25 }); - const [sort, setSort] = useState(['name', 'asc']); + const [params, setParams] = useState({ fname: '', servedBy: [], status: '', department: '', from: '', to: '', customFields: {}, current: 0, itemsPerPage: 25 }); + const [sort, setSort] = useState(['ts', 'desc']); const debouncedParams = useDebouncedValue(params, 500); const debouncedSort = useDebouncedValue(sort, 500); @@ -96,15 +118,15 @@ function CurrentChatsRoute() { }); const { data, reload } = useEndpointDataExperimental('livechat/rooms', query) || {}; - const { data: departments } = useEndpointDataExperimental('livechat/department', query) || {}; const header = useMemo(() => [ <Th key={'name'} direction={sort[1]} active={sort[0] === 'name'} onClick={onHeaderClick} sort='name' w='x120'>{t('Name')}</Th>, <Th key={'departmentId'} direction={sort[1]} active={sort[0] === 'departmentId'} onClick={onHeaderClick} sort='departmentId' w='x200'>{t('Department')}</Th>, - <Th key={'servedBy'} direction={sort[1]} active={sort[0] === 'servedBy'} onClick={onHeaderClick} sort='servedBy' w='x120'>{t('Served_by')}</Th>, - <Th key={'ts'} direction={sort[1]} active={sort[0] === 'ts'} onClick={onHeaderClick} sort='ts' w='x120'>{t('Started_at')}</Th>, - <Th key={'lm'} direction={sort[1]} active={sort[0] === 'lm'} onClick={onHeaderClick} sort='visibility' w='x120'>{t('Last_message')}</Th>, + <Th key={'servedBy'} direction={sort[1]} active={sort[0] === 'servedBy'} onClick={onHeaderClick} sort='servedBy' w='x120'>{t('Served_By')}</Th>, + <Th key={'ts'} direction={sort[1]} active={sort[0] === 'ts'} onClick={onHeaderClick} sort='ts' w='x120'>{t('Started_At')}</Th>, + <Th key={'lm'} direction={sort[1]} active={sort[0] === 'lm'} onClick={onHeaderClick} sort='visibility' w='x120'>{t('Last_Message')}</Th>, <Th key={'status'} direction={sort[1]} active={sort[0] === 'status'} onClick={onHeaderClick} sort='status' w='x120'>{t('Status')}</Th>, + <Th key={'remove'} w='x40'>{t('Remove')}</Th>, ].filter(Boolean), [sort, onHeaderClick, t]); const renderRow = useCallback(({ _id, fname, servedBy, ts, lm, department, open }) => <Table.Row key={_id} tabIndex={0} role='link' onClick={() => onRowClick(_id)} action qa-user-id={_id}> @@ -114,7 +136,8 @@ function CurrentChatsRoute() { <Table.Cell withTruncatedText>{moment(ts).format('L LTS')}</Table.Cell> <Table.Cell withTruncatedText>{moment(lm).format('L LTS')}</Table.Cell> <Table.Cell withTruncatedText>{open ? t('Open') : t('Closed')}</Table.Cell> - </Table.Row>, [onRowClick, t]); + {!open && <RemoveChatButton _id={_id} reload={reload}/>} + </Table.Row>, [onRowClick, reload, t]); if (!canViewCurrentChats) { return <NotAuthorizedPage />; @@ -129,7 +152,6 @@ function CurrentChatsRoute() { reload={reload} header={header} renderRow={renderRow} - departments={departments} title={'Current Chats'}> </CurrentChatsPage>; } diff --git a/client/omnichannel/customFields/CustomFieldsTable.js b/client/omnichannel/customFields/CustomFieldsTable.js index 4eefebd5231ee9955b10c15ee831d8e8bc7f9fdb..1ad532bfef7a2d70c3991ea02daa64b9f908525a 100644 --- a/client/omnichannel/customFields/CustomFieldsTable.js +++ b/client/omnichannel/customFields/CustomFieldsTable.js @@ -1,15 +1,59 @@ -import { Icon, Table, Button, Callout } from '@rocket.chat/fuselage'; +import { Icon, Table, Callout, Button } from '@rocket.chat/fuselage'; import React, { useState, memo } from 'react'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + import GenericTable from '../../components/GenericTable'; import { useRoute } from '../../contexts/RouterContext'; import { useTranslation } from '../../contexts/TranslationContext'; import { useResizeInlineBreakpoint } from '../../hooks/useResizeInlineBreakpoint'; import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../hooks/useEndpointDataExperimental'; +import DeleteWarningModal from '../DeleteWarningModal'; +import { useSetModal } from '../../contexts/ModalContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; +import { useMethod } from '../../contexts/ServerContext'; + +export function RemoveCustomFieldButton({ _id, reload }) { + const removeChat = useMethod('livechat:removeCustomField'); + const setModal = useSetModal(); + const dispatchToastMessage = useToastMessageDispatch(); + const t = useTranslation(); + + const handleRemoveClick = useMutableCallback(async () => { + try { + await removeChat(_id); + } catch (error) { + console.log(error); + } + reload(); + }); + + const handleDelete = useMutableCallback((e) => { + e.stopPropagation(); + const onDeleteAgent = async () => { + try { + await handleRemoveClick(); + dispatchToastMessage({ type: 'success', message: t('Custom_Field_Removed') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + setModal(); + }; + + setModal(<DeleteWarningModal onDelete={onDeleteAgent} onCancel={() => setModal()}/>); + }); + + return <Table.Cell fontScale='p1' color='hint' withTruncatedText> + <Button small ghost title={t('Remove')} onClick={handleDelete}> + <Icon name='trash' size='x16'/> + </Button> + </Table.Cell>; +} + const CustomFieldsRow = memo(function CustomFieldsRow({ medium, - onDelete = () => {}, + reload, ...props }) { const { @@ -19,7 +63,6 @@ const CustomFieldsRow = memo(function CustomFieldsRow({ visibility, } = props; - const t = useTranslation(); const cfRoute = useRoute('omnichannel-customfields'); @@ -63,9 +106,7 @@ const CustomFieldsRow = memo(function CustomFieldsRow({ {visibility} </Table.Cell> <Table.Cell withTruncatedText onClick={preventClickPropagation}> - <Button small primary danger onClick={onDelete} title={t('Delete')}> - <Icon name='trash' size='x16'/> - </Button> + <RemoveCustomFieldButton _id={_id} reload={reload}/> </Table.Cell> </Table.Row>; }); @@ -74,7 +115,7 @@ const CustomFieldsTableContainer = () => { const t = useTranslation(); const [params, setParams] = useState(() => ({ current: 0, itemsPerPage: 25 })); - const { data, state } = useEndpointDataExperimental(`livechat/custom-fields?count=${ params.itemsPerPage }&offset=${ params.current }`); + const { data, state, reload } = useEndpointDataExperimental(`livechat/custom-fields?count=${ params.itemsPerPage }&offset=${ params.current }`); if (state === ENDPOINT_STATES.ERROR) { return <Callout> @@ -87,10 +128,11 @@ const CustomFieldsTableContainer = () => { totalCustomFields={data?.total} params={params} onChangeParams={setParams} + reload={reload} />; }; -export function CustomFieldsTable({ customFields, totalCustomFields, params, onChangeParams }) { +export function CustomFieldsTable({ customFields, totalCustomFields, params, onChangeParams, reload }) { const t = useTranslation(); const [ref, onMediumBreakpoint] = useResizeInlineBreakpoint([600], 200); @@ -117,7 +159,7 @@ export function CustomFieldsTable({ customFields, totalCustomFields, params, onC params={params} setParams={onChangeParams} > - {(props) => <CustomFieldsRow key={props._id} medium={onMediumBreakpoint} {...props} />} + {(props) => <CustomFieldsRow key={props._id} medium={onMediumBreakpoint} reload={reload} {...props} />} </GenericTable>; } diff --git a/client/omnichannel/managers/ManagersRoute.js b/client/omnichannel/managers/ManagersRoute.js index ae175bedafcd35099958f9ad9f5a3e152bdffc31..273eb2cacc9467e9854e2ed9dcc60c0497c72e16 100644 --- a/client/omnichannel/managers/ManagersRoute.js +++ b/client/omnichannel/managers/ManagersRoute.js @@ -1,7 +1,7 @@ import { useDebouncedValue, useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useMemo, useCallback, useState } from 'react'; -import { Box, Table, Icon } from '@rocket.chat/fuselage'; +import { Box, Table, Icon, Button } from '@rocket.chat/fuselage'; import { Th } from '../../components/GenericTable'; import { useTranslation } from '../../contexts/TranslationContext'; @@ -13,6 +13,7 @@ import ManagersPage from './ManagersPage'; import UserAvatar from '../../components/basic/avatar/UserAvatar'; export function RemoveManagerButton({ _id, reload }) { + const t = useTranslation(); const deleteAction = useEndpointAction('DELETE', `livechat/users/manager/${ _id }`); const handleRemoveClick = useMutableCallback(async () => { @@ -21,7 +22,11 @@ export function RemoveManagerButton({ _id, reload }) { reload(); } }); - return <Table.Cell fontScale='p1' clickable={true} color='hint' onClick={handleRemoveClick} withTruncatedText><Icon name='trash' size='x20'/></Table.Cell>; + return <Table.Cell fontScale='p1' clickable={true} color='hint' onClick={handleRemoveClick} withTruncatedText> + <Button small ghost title={t('Remove')} onClick={handleRemoveClick}> + <Icon name='trash' size='x16'/> + </Button> + </Table.Cell>; } const sortDir = (sortDir) => (sortDir === 'asc' ? 1 : -1); diff --git a/client/omnichannel/triggers/TriggersTable.js b/client/omnichannel/triggers/TriggersTable.js index 8e668f9d0172618b4ce04d171241ea6057380fe5..cdf8c7e2891f876e46bc30cb3f4d4b8b47d53d4d 100644 --- a/client/omnichannel/triggers/TriggersTable.js +++ b/client/omnichannel/triggers/TriggersTable.js @@ -1,5 +1,5 @@ -import { Table, Callout, Icon, Button } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { Table, Callout, Icon } from '@rocket.chat/fuselage'; +import { useMutableCallback, Button } from '@rocket.chat/fuselage-hooks'; import React, { useState, memo, useMemo } from 'react'; import GenericTable from '../../components/GenericTable'; @@ -79,8 +79,8 @@ const TriggersRow = memo(function TriggersRow(props) { {enabled ? t('Yes') : t('No')} </Table.Cell> <Table.Cell withTruncatedText> - <Button onClick={handleDelete} small> - <Icon name='trash' size='x16' /> + <Button small ghost title={t('Remove')} onClick={handleDelete}> + <Icon name='trash' size='x16'/> </Button> </Table.Cell> </Table.Row>; @@ -131,7 +131,10 @@ export function TriggersTable({ triggers, totalTriggers, params, onChangeParams, <GenericTable.HeaderCell> {t('Enabled')} </GenericTable.HeaderCell> - <GenericTable.HeaderCell width='x60'/> + <GenericTable.HeaderCell width='x60'> + {t('Remove')} + </GenericTable.HeaderCell> + </>} results={triggers} total={totalTriggers} diff --git a/ee/client/omnichannel/BusinessHoursTable.js b/ee/client/omnichannel/BusinessHoursTable.js index 511f33f37a796f6794dd947debebaccc6d8f4605..86baa7e377bbc175f1ded9f42a0a0d8d847ceaf9 100644 --- a/ee/client/omnichannel/BusinessHoursTable.js +++ b/ee/client/omnichannel/BusinessHoursTable.js @@ -1,4 +1,4 @@ -import { Table, Callout, Box, TextInput, Icon } from '@rocket.chat/fuselage'; +import { Table, Callout, Box, TextInput, Icon, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useState, memo, useMemo, useEffect } from 'react'; @@ -7,6 +7,48 @@ import { useRoute } from '../../../client/contexts/RouterContext'; import { useTranslation } from '../../../client/contexts/TranslationContext'; import { useResizeInlineBreakpoint } from '../../../client/hooks/useResizeInlineBreakpoint'; import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../../client/hooks/useEndpointDataExperimental'; +import DeleteWarningModal from '../../../client/omnichannel/DeleteWarningModal'; +import { useSetModal } from '../../../client/contexts/ModalContext'; +import { useToastMessageDispatch } from '../../../client/contexts/ToastMessagesContext'; +import { useMethod } from '../../../client/contexts/ServerContext'; + + +export function RemoveBusinessHourButton({ _id, type, reload }) { + const removeBusinessHour = useMethod('livechat:removeBusinessHour'); + const setModal = useSetModal(); + const dispatchToastMessage = useToastMessageDispatch(); + const t = useTranslation(); + + const handleRemoveClick = useMutableCallback(async () => { + try { + await removeBusinessHour(_id, type); + } catch (error) { + console.log(error); + } + reload(); + }); + + const handleDelete = useMutableCallback((e) => { + e.stopPropagation(); + const onBusinessHour = async () => { + try { + await handleRemoveClick(); + dispatchToastMessage({ type: 'success', message: t('Business_Hour_Removed') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + setModal(); + }; + + setModal(<DeleteWarningModal onDelete={onBusinessHour} onCancel={() => setModal()}/>); + }); + + return <Table.Cell fontScale='p1' color='hint' onClick={handleDelete} withTruncatedText> + <Button small ghost title={t('Remove')} onClick={handleDelete}> + <Icon name='trash' size='x16'/> + </Button> + </Table.Cell>; +} const FilterByText = memo(({ setFilter, ...props }) => { const t = useTranslation(); @@ -32,6 +74,7 @@ const BusinessHoursRow = memo(function BusinessHoursRow(props) { workHours, active, type, + reload, } = props; const t = useTranslation(); @@ -61,6 +104,10 @@ const BusinessHoursRow = memo(function BusinessHoursRow(props) { return acc; }, []), [t, workHours]); + const preventClickPropagation = (e) => { + e.stopPropagation(); + }; + return <Table.Row key={_id} role='link' @@ -81,6 +128,9 @@ const BusinessHoursRow = memo(function BusinessHoursRow(props) { <Table.Cell withTruncatedText> {active ? t('Yes') : t('No')} </Table.Cell> + {name && <Table.Cell withTruncatedText onClick={preventClickPropagation}> + <RemoveBusinessHourButton _id={_id} reload={reload} type={type}/> + </Table.Cell>} </Table.Row>; }); @@ -88,7 +138,7 @@ const BusinessHoursTableContainer = () => { const t = useTranslation(); const [params, setParams] = useState(() => ({ current: 0, itemsPerPage: 25, text: '' })); - const { data, state } = useEndpointDataExperimental(`livechat/business-hours.list?count=${ params.itemsPerPage }&offset=${ params.current }&name=${ params.text }`); + const { data, state, reload } = useEndpointDataExperimental(`livechat/business-hours.list?count=${ params.itemsPerPage }&offset=${ params.current }&name=${ params.text }`); if (state === ENDPOINT_STATES.ERROR) { return <Callout> @@ -101,10 +151,11 @@ const BusinessHoursTableContainer = () => { totalbusinessHours={data?.total} params={params} onChangeParams={setParams} + reload={reload} />; }; -export function BusinessHoursTable({ businessHours, totalbusinessHours, params, onChangeParams }) { +export function BusinessHoursTable({ businessHours, totalbusinessHours, params, onChangeParams, reload }) { const t = useTranslation(); const [ref, onMediumBreakpoint] = useResizeInlineBreakpoint([600], 200); @@ -124,6 +175,9 @@ export function BusinessHoursTable({ businessHours, totalbusinessHours, params, <GenericTable.HeaderCell width='x100'> {t('Enabled')} </GenericTable.HeaderCell> + <GenericTable.HeaderCell width='x100'> + {t('Remove')} + </GenericTable.HeaderCell> </>} results={businessHours} total={totalbusinessHours} @@ -131,7 +185,7 @@ export function BusinessHoursTable({ businessHours, totalbusinessHours, params, setParams={onChangeParams} FilterComponent={FilterByText} > - {(props) => <BusinessHoursRow key={props._id} medium={onMediumBreakpoint} {...props} />} + {(props) => <BusinessHoursRow key={props._id} medium={onMediumBreakpoint} reload={reload} {...props} />} </GenericTable>; } diff --git a/ee/client/omnichannel/additionalForms/BusinessHoursMultiple.js b/ee/client/omnichannel/additionalForms/BusinessHoursMultiple.js index 437c3066bd2b863348af93105da9ad07474ffc2c..6f9e23ecadb76ccf6abed68e16ac6eb743788a28 100644 --- a/ee/client/omnichannel/additionalForms/BusinessHoursMultiple.js +++ b/ee/client/omnichannel/additionalForms/BusinessHoursMultiple.js @@ -66,7 +66,7 @@ export const BusinessHoursMultiple = ({ values = {}, handlers = {}, className, d <Field className={className}> <Field.Label>{t('Departments')}</Field.Label> <Field.Row> - <MultiSelectFiltered options={departmentList} value={departments} onChange={handleDepartments} placeholder={t('Departments')}/> + <MultiSelectFiltered options={departmentList} value={departments} onChange={handleDepartments} maxWidth='100%' placeholder={t('Departments')}/> </Field.Row> </Field> </>; diff --git a/ee/client/omnichannel/priorities/EditPriority.js b/ee/client/omnichannel/priorities/EditPriority.js index 018b6aee385b0ff67fb953d3ee3a6dd9cfe04889..62aca4968722e6f5931200175bfd80a193c23100 100644 --- a/ee/client/omnichannel/priorities/EditPriority.js +++ b/ee/client/omnichannel/priorities/EditPriority.js @@ -22,7 +22,7 @@ export function PriorityEditWithData({ priorityId, reload }) { return <FormSkeleton/>; } - if (error) { + if (error || !data || !data.priorities) { return <Callout m='x16' type='danger'>{t('Not_Available')}</Callout>; } diff --git a/ee/client/omnichannel/priorities/PrioritiesRoute.js b/ee/client/omnichannel/priorities/PrioritiesRoute.js index e2026774765b778f760328ae6a969c62e04b1b59..3035dca2d89f80f00b78a1b82ab5ac50eced3542 100644 --- a/ee/client/omnichannel/priorities/PrioritiesRoute.js +++ b/ee/client/omnichannel/priorities/PrioritiesRoute.js @@ -2,7 +2,7 @@ import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useMemo, useCallback, useState } from 'react'; -import { Table, Icon } from '@rocket.chat/fuselage'; +import { Table, Icon, Button } from '@rocket.chat/fuselage'; import { Th } from '../../../../client/components/GenericTable'; import { useTranslation } from '../../../../client/contexts/TranslationContext'; @@ -14,13 +14,18 @@ import { useRouteParameter, useRoute } from '../../../../client/contexts/RouterC import VerticalBar from '../../../../client/components/basic/VerticalBar'; import PrioritiesPage from './PrioritiesPage'; import { PriorityEditWithData, PriorityNew } from './EditPriority'; +import DeleteWarningModal from '../../../../client/omnichannel/DeleteWarningModal'; +import { useSetModal } from '../../../../client/contexts/ModalContext'; +import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext'; export function RemovePriorityButton({ _id, reload }) { const removePriority = useMethod('livechat:removePriority'); + const setModal = useSetModal(); + const dispatchToastMessage = useToastMessageDispatch(); + const t = useTranslation(); - const handleRemoveClick = useMutableCallback(async (e) => { - e.preventDefault(); - e.stopPropagation(); + + const handleRemoveClick = useMutableCallback(async () => { try { await removePriority(_id); } catch (error) { @@ -29,7 +34,26 @@ export function RemovePriorityButton({ _id, reload }) { reload(); }); - return <Table.Cell fontScale='p1' color='hint' onClick={handleRemoveClick} withTruncatedText><Icon name='trash' size='x20'/></Table.Cell>; + const handleDelete = useMutableCallback((e) => { + e.stopPropagation(); + const onDeleteAgent = async () => { + try { + await handleRemoveClick(); + dispatchToastMessage({ type: 'success', message: t('Priority_removed') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + setModal(); + }; + + setModal(<DeleteWarningModal onDelete={onDeleteAgent} onCancel={() => setModal()}/>); + }); + + return <Table.Cell fontScale='p1' color='hint' withTruncatedText> + <Button small ghost title={t('Remove')} onClick={handleDelete}> + <Icon name='trash' size='x16'/> + </Button> + </Table.Cell>; } const sortDir = (sortDir) => (sortDir === 'asc' ? 1 : -1); diff --git a/ee/client/omnichannel/routes.js b/ee/client/omnichannel/routes.js index e574369c8d5210a8b1ca714c13a70baeeeb80f91..570314b03f7a58ecc343dce7aa1e6207dfb38209 100644 --- a/ee/client/omnichannel/routes.js +++ b/ee/client/omnichannel/routes.js @@ -5,7 +5,7 @@ registerOmnichannelRoute('/monitors', { lazyRouteComponent: () => import('./MonitorsPage'), }); -registerOmnichannelRoute('/priorities', { +registerOmnichannelRoute('/priorities/:context?/:id?', { name: 'omnichannel-priorities', lazyRouteComponent: () => import('./priorities/PrioritiesRoute'), }); diff --git a/ee/client/omnichannel/tags/EditTag.js b/ee/client/omnichannel/tags/EditTag.js index 9b06ef0a8b7dea3c534c1d9c9b0bbdbf6c29f467..91d6202c6ff6ac2c24f65ab839dedbbf33239112 100644 --- a/ee/client/omnichannel/tags/EditTag.js +++ b/ee/client/omnichannel/tags/EditTag.js @@ -114,7 +114,7 @@ export function TagEdit({ data, tagId, isNew, availableDepartments, reload, ...p <Field> <Field.Label>{t('Departments')}</Field.Label> <Field.Row> - <MultiSelect options={options} value={departments} error={hasUnsavedChanges && departmentError} placeholder={t('Select_an_option')} onChange={handleDepartments} flexGrow={1}/> + <MultiSelect options={options} value={departments} error={hasUnsavedChanges && departmentError} maxWidth='100%' placeholder={t('Select_an_option')} onChange={handleDepartments} flexGrow={1}/> </Field.Row> </Field> diff --git a/ee/client/omnichannel/tags/TagsRoute.js b/ee/client/omnichannel/tags/TagsRoute.js index 2e80716ec92a3d08c23223fb1b96af5e197b5d3d..3a80935b3e40466ab9bb521015173d5d18331e5b 100644 --- a/ee/client/omnichannel/tags/TagsRoute.js +++ b/ee/client/omnichannel/tags/TagsRoute.js @@ -2,7 +2,7 @@ import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useMemo, useCallback, useState } from 'react'; -import { Table, Icon } from '@rocket.chat/fuselage'; +import { Table, Icon, Button } from '@rocket.chat/fuselage'; import { Th } from '../../../../client/components/GenericTable'; import { useTranslation } from '../../../../client/contexts/TranslationContext'; @@ -14,15 +14,18 @@ import { useRouteParameter, useRoute } from '../../../../client/contexts/RouterC import VerticalBar from '../../../../client/components/basic/VerticalBar'; import TagsPage from './TagsPage'; import { TagEditWithData, TagNew } from './EditTag'; +import DeleteWarningModal from '../../../../client/omnichannel/DeleteWarningModal'; +import { useSetModal } from '../../../../client/contexts/ModalContext'; +import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext'; export function RemoveTagButton({ _id, reload }) { const removeTag = useMethod('livechat:removeTag'); const tagsRoute = useRoute('omnichannel-tags'); + const setModal = useSetModal(); + const dispatchToastMessage = useToastMessageDispatch(); + const t = useTranslation(); - - const handleRemoveClick = useMutableCallback(async (e) => { - e.preventDefault(); - e.stopPropagation(); + const handleRemoveClick = useMutableCallback(async () => { try { await removeTag(_id); } catch (error) { @@ -32,7 +35,26 @@ export function RemoveTagButton({ _id, reload }) { reload(); }); - return <Table.Cell fontScale='p1' color='hint' onClick={handleRemoveClick} withTruncatedText><Icon name='trash' size='x20'/></Table.Cell>; + const handleDelete = useMutableCallback((e) => { + e.stopPropagation(); + const onDeleteAgent = async () => { + try { + await handleRemoveClick(); + dispatchToastMessage({ type: 'success', message: t('Tag_removed') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + setModal(); + }; + + setModal(<DeleteWarningModal onDelete={onDeleteAgent} onCancel={() => setModal()}/>); + }); + + return <Table.Cell fontScale='p1' color='hint' onClick={handleDelete} withTruncatedText> + <Button small ghost title={t('Remove')} onClick={handleDelete}> + <Icon name='trash' size='x16'/> + </Button> + </Table.Cell>; } const sortDir = (sortDir) => (sortDir === 'asc' ? 1 : -1); diff --git a/ee/client/omnichannel/units/EditUnit.js b/ee/client/omnichannel/units/EditUnit.js index cb073d0e1b938acd630eb42ae805826443b5f2c8..d932cb2e949bcaf078a23eac365e6b99626e915e 100644 --- a/ee/client/omnichannel/units/EditUnit.js +++ b/ee/client/omnichannel/units/EditUnit.js @@ -65,13 +65,14 @@ export function UnitEdit({ data, unitId, isNew, availableDepartments, availableM const monOptions = useMemo(() => (availableMonitors && availableMonitors.monitors ? availableMonitors.monitors.map(({ _id, name }) => [_id, name || _id]) : []), [availableMonitors]); const currUnitMonitors = useMemo(() => (unitMonitors && unitMonitors.monitors ? unitMonitors.monitors.map(({ monitorId }) => monitorId) : []), [unitMonitors]); const visibilityOpts = [['public', t('Public')], ['private', t('Private')]]; - const unitDepartments = useMemo(() => (availableDepartments && availableDepartments.departments ? availableDepartments.departments.map((department) => { + const unitDepartments = useMemo(() => (availableDepartments && (availableDepartments.departments && unitId) ? availableDepartments.departments.map((department) => { let result; if (department.parentId === unitId) { result = department._id; } return result; - }) : []), [availableDepartments, unitId]); + }).filter((department) => !!department) : []), [availableDepartments, unitId]); + const { values, handlers, hasUnsavedChanges } = useForm({ name: unit.name, visibility: unit.visibility, departments: unitDepartments, monitors: currUnitMonitors }); @@ -141,13 +142,13 @@ export function UnitEdit({ data, unitId, isNew, availableDepartments, availableM <Field> <Field.Label>{t('Departments')}</Field.Label> <Field.Row> - <MultiSelect options={depOptions} value={departments} error={hasUnsavedChanges && departmentError} placeholder={t('Select_an_option')} onChange={handleDepartments} flexGrow={1}/> + <MultiSelect options={depOptions} value={departments} error={hasUnsavedChanges && departmentError} maxWidth='100%' placeholder={t('Select_an_option')} onChange={handleDepartments} flexGrow={1}/> </Field.Row> </Field> <Field> <Field.Label>{t('Monitors')}</Field.Label> <Field.Row> - <MultiSelect options={monOptions} value={monitors} error={hasUnsavedChanges && unitMonitorsError} placeholder={t('Select_an_option')} onChange={handleMonitors} flexGrow={1}/> + <MultiSelect options={monOptions} value={monitors} error={hasUnsavedChanges && unitMonitorsError} maxWidth='100%' placeholder={t('Select_an_option')} onChange={handleMonitors} flexGrow={1}/> </Field.Row> </Field> diff --git a/ee/client/omnichannel/units/UnitsRoute.js b/ee/client/omnichannel/units/UnitsRoute.js index 5ecf84af24314b630bc2b2d328a577ca7c4d7b1b..a3863db82692a29bc8015d1919785676156d46f8 100644 --- a/ee/client/omnichannel/units/UnitsRoute.js +++ b/ee/client/omnichannel/units/UnitsRoute.js @@ -2,7 +2,7 @@ import { useDebouncedValue, useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useMemo, useCallback, useState } from 'react'; -import { Table, Icon } from '@rocket.chat/fuselage'; +import { Table, Icon, Button } from '@rocket.chat/fuselage'; import { Th } from '../../../../client/components/GenericTable'; import { useTranslation } from '../../../../client/contexts/TranslationContext'; @@ -14,15 +14,19 @@ import { useRouteParameter, useRoute } from '../../../../client/contexts/RouterC import VerticalBar from '../../../../client/components/basic/VerticalBar'; import UnitsPage from './UnitsPage'; import { UnitEditWithData, UnitNew } from './EditUnit'; +import DeleteWarningModal from '../../../../client/omnichannel/DeleteWarningModal'; +import { useSetModal } from '../../../../client/contexts/ModalContext'; +import { useToastMessageDispatch } from '../../../../client/contexts/ToastMessagesContext'; + export function RemoveUnitButton({ _id, reload }) { const removeUnit = useMethod('livechat:removeUnit'); const unitsRoute = useRoute('omnichannel-units'); + const setModal = useSetModal(); + const dispatchToastMessage = useToastMessageDispatch(); + const t = useTranslation(); - - const handleRemoveClick = useMutableCallback(async (e) => { - e.preventDefault(); - e.stopPropagation(); + const handleRemoveClick = useMutableCallback(async () => { try { await removeUnit(_id); } catch (error) { @@ -32,7 +36,26 @@ export function RemoveUnitButton({ _id, reload }) { reload(); }); - return <Table.Cell fontScale='p1' color='hint' onClick={handleRemoveClick} withTruncatedText><Icon name='trash' size='x20'/></Table.Cell>; + const handleDelete = useMutableCallback((e) => { + e.stopPropagation(); + const onDeleteAgent = async () => { + try { + await handleRemoveClick(); + dispatchToastMessage({ type: 'success', message: t('Unit_removed') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + setModal(); + }; + + setModal(<DeleteWarningModal onDelete={onDeleteAgent} onCancel={() => setModal()}/>); + }); + + return <Table.Cell fontScale='p1' color='hint' withTruncatedText> + <Button small ghost title={t('Remove')} onClick={handleDelete}> + <Icon name='trash' size='x16'/> + </Button> + </Table.Cell>; } const sortDir = (sortDir) => (sortDir === 'asc' ? 1 : -1); diff --git a/imports/client/hepburn b/imports/client/hepburn new file mode 120000 index 0000000000000000000000000000000000000000..f439383d3132e74c41507bc10b54f6cd94514b85 --- /dev/null +++ b/imports/client/hepburn @@ -0,0 +1 @@ +../../node_modules/hepburn \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 98b1e6d0d6a07fe723e4f90d3daa3827187491dc..7e556848ee7683284b48774aa76887e4db9bc0f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5028,9 +5028,9 @@ } }, "@rocket.chat/apps-engine": { - "version": "1.17.0-beta.3629", - "resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.17.0-beta.3629.tgz", - "integrity": "sha512-pQMIJ1JOlRp5wzBm6LgTWPz6Yob0whExfJJ1XcFaq1JV6FBDg6sIyehMsYpop5z8EWZHBstZZc5duN9ixxDZmw==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.17.0.tgz", + "integrity": "sha512-4jsN9RUQR9zvZnOr4IZj9bnZR5/kAuSpKJ3JwqTv/DXBeHRSrCCCX0EDoZh5akmQu6x9tRpAXd1DRJWRfI5/Jw==", "requires": { "adm-zip": "^0.4.9", "cryptiles": "^4.1.3", @@ -5123,9 +5123,9 @@ "integrity": "sha512-V/lq5xdIxx016pKB8kSQuQ+IKLC0ULdkZ72M/DEMU1DqXahWDEJeRiBppOO8YN5/mw5INaf8asWOqxJfIFld0w==" }, "@rocket.chat/livechat": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@rocket.chat/livechat/-/livechat-1.7.0.tgz", - "integrity": "sha512-CISpxnHuCAPgEhLlbdm7naYlh54r0elkvhRaQgzz/ztuANGlroEsucwZgj50izgGC1udiQiO0eYy7lQp8gTqiA==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@rocket.chat/livechat/-/livechat-1.7.2.tgz", + "integrity": "sha512-CikbwDG2ohvnQbSlQIYBe9YUmIyrBgGYtPUXFAI5J9zp6UFg2S0icO2wAN7O3IJJl/RKOVKyWO+3/L83DypVVw==", "dev": true, "requires": { "@kossnocorp/desvg": "^0.2.0", @@ -15335,9 +15335,9 @@ "integrity": "sha512-lcWy3AXDRJOD7MplwZMmNSRM//kZtJaLz4n6D1P5z9wEmZGBKhJRBIr1Xs9KNQJmdXPblvgffynYji4iylUTcA==" }, "date-fns": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.15.0.tgz", - "integrity": "sha512-ZCPzAMJZn3rNUvvQIMlXhDr4A+Ar07eLeGsGREoWU19a3Pqf5oYa+ccd+B3F6XVtQY6HANMFdOQ8A+ipFnvJdQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.0.tgz", + "integrity": "sha512-DWTRyfOA85sZ4IiXPHhiRIOs3fW5U6Msrp+gElXARa6EpoQTXPyHQmh7hr+ssw2nx9FtOQWnAMJKgL5vaJqILw==", "dev": true }, "date-now": { @@ -17890,7 +17890,7 @@ }, "chownr": { "version": "1.1.1", - "resolved": false, + "resolved": "", "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true, "optional": true @@ -17925,7 +17925,7 @@ }, "debug": { "version": "4.1.1", - "resolved": false, + "resolved": "", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "optional": true, @@ -17956,7 +17956,7 @@ }, "fs-minipass": { "version": "1.2.5", - "resolved": false, + "resolved": "", "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, @@ -17990,7 +17990,7 @@ }, "glob": { "version": "7.1.3", - "resolved": false, + "resolved": "", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "optional": true, @@ -18022,7 +18022,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": false, + "resolved": "", "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, @@ -18043,7 +18043,7 @@ }, "inherits": { "version": "2.0.3", - "resolved": false, + "resolved": "", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true, "optional": true @@ -18091,7 +18091,7 @@ }, "minipass": { "version": "2.3.5", - "resolved": false, + "resolved": "", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, "optional": true, @@ -18102,7 +18102,7 @@ }, "minizlib": { "version": "1.2.1", - "resolved": false, + "resolved": "", "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "dev": true, "optional": true, @@ -18122,7 +18122,7 @@ }, "ms": { "version": "2.1.1", - "resolved": false, + "resolved": "", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true, "optional": true @@ -18136,7 +18136,7 @@ }, "needle": { "version": "2.3.0", - "resolved": false, + "resolved": "", "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "dev": true, "optional": true, @@ -18148,7 +18148,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": false, + "resolved": "", "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, "optional": true, @@ -18178,14 +18178,14 @@ }, "npm-bundled": { "version": "1.0.6", - "resolved": false, + "resolved": "", "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.1", - "resolved": false, + "resolved": "", "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "dev": true, "optional": true, @@ -18265,7 +18265,7 @@ }, "process-nextick-args": { "version": "2.0.0", - "resolved": false, + "resolved": "", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true @@ -18310,7 +18310,7 @@ }, "rimraf": { "version": "2.6.3", - "resolved": false, + "resolved": "", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "optional": true, @@ -18341,7 +18341,7 @@ }, "semver": { "version": "5.7.0", - "resolved": false, + "resolved": "", "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true, "optional": true @@ -18401,7 +18401,7 @@ }, "tar": { "version": "4.4.8", - "resolved": false, + "resolved": "", "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "dev": true, "optional": true, @@ -18441,7 +18441,7 @@ }, "yallist": { "version": "3.0.3", - "resolved": false, + "resolved": "", "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true, "optional": true @@ -27366,9 +27366,9 @@ } }, "preact": { - "version": "10.4.7", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.4.7.tgz", - "integrity": "sha512-DtnnPbOm7oxW7Sxf5Co+KSIOxo7bGm0vLfJN/wGey7G2sAGKnGP5+bFyE2YIgutMISQl6xFVTsOd6l/Au88VVw==", + "version": "10.4.8", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.4.8.tgz", + "integrity": "sha512-uVLeEAyRsCkUEFhVHlOu17OxcrwC7+hTGZ08kBoLBiGHiZooUZuibQnphgMKftw/rqYntNMyhVCPqQhcyAGHag==", "dev": true }, "preact-i18nline": { diff --git a/package.json b/package.json index 46e1fa0d730433487f9a8b1b50867a977c98c6a3..14c9f7d6e88221972d4b5385477702f23530f91b 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@babel/preset-react": "^7.10.4", "@octokit/rest": "^16.43.2", "@rocket.chat/eslint-config": "^0.3.0", - "@rocket.chat/livechat": "^1.7.0", + "@rocket.chat/livechat": "^1.7.2", "@settlin/spacebars-loader": "^1.0.8", "@storybook/addon-actions": "^5.3.19", "@storybook/addon-knobs": "^5.3.19", @@ -129,7 +129,7 @@ "@nivo/heatmap": "^0.61.0", "@nivo/line": "^0.61.1", "@nivo/pie": "^0.61.1", - "@rocket.chat/apps-engine": "1.17.0-beta.3629", + "@rocket.chat/apps-engine": "1.17.0", "@rocket.chat/css-in-js": "^0.14.1", "@rocket.chat/fuselage": "^0.14.1", "@rocket.chat/fuselage-hooks": "^0.14.1", diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 46dadd084ee26558b6dfd44bf714de4ed1dd7742..f6b4b89e037482d909300fb64bc8f57e8f275c17 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1132,6 +1132,7 @@ "Custom_Emoji_Has_Been_Deleted": "The custom emoji has been deleted.", "Custom_Emoji_Info": "Custom Emoji Info", "Custom_Emoji_Updated_Successfully": "Custom emoji updated successfully", + "Custom_Field_Removed": "Custom Field Removed", "Custom_Fields": "Custom Fields", "Custom_oauth_helper": "When setting up your OAuth Provider, you'll have to inform a Callback URL. Use <pre>%s</pre> .", "Custom_oauth_unique_name": "Custom oauth unique name", @@ -3214,6 +3215,8 @@ "Send_request_on": "Send Request on", "Agent_messages": "Agent Messages", "Chat_close": "Chat Close", + "Chats_removed": "Chats Removed", + "Chat_removed": "Chat Removed", "Chat_queued": "Chat Queued", "Chat_start": "Chat Start", "Chat_taken": "Chat Taken",