From 8b921560529e43f624cfdb2596dff17d1c127dc4 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 26 Aug 2020 14:25:10 -0300 Subject: [PATCH 01/21] wip --- client/omnichannel/departments/AgentEdit.js | 229 ++++++++++++++++++ .../departments/DepartmentsPage.js | 72 ++++++ .../departments/DepartmentsRoute.js | 118 +++++++++ client/omnichannel/departments/Skeleton.js | 11 + client/omnichannel/routes.js | 5 + packages/rocketchat-i18n/i18n/en.i18n.json | 1 + 6 files changed, 436 insertions(+) create mode 100644 client/omnichannel/departments/AgentEdit.js create mode 100644 client/omnichannel/departments/DepartmentsPage.js create mode 100644 client/omnichannel/departments/DepartmentsRoute.js create mode 100644 client/omnichannel/departments/Skeleton.js diff --git a/client/omnichannel/departments/AgentEdit.js b/client/omnichannel/departments/AgentEdit.js new file mode 100644 index 0000000000000..f7d0627785218 --- /dev/null +++ b/client/omnichannel/departments/AgentEdit.js @@ -0,0 +1,229 @@ +import React, { useMemo, useRef, useState } from 'react'; +import { Field, TextInput, NumberInput, SelectFiltered, Box, MultiSelect, Icon, Select, ToggleSwitch, TextAreaInput } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useSubscription } from 'use-subscription'; + +import { useMethod } from '../../contexts/ServerContext'; +import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; +import { useTranslation } from '../../contexts/TranslationContext'; +import VerticalBar from '../../components/basic/VerticalBar'; +import { UserInfo } from '../../components/basic/UserInfo'; +import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../hooks/useEndpointDataExperimental'; +import { FormSkeleton } from './Skeleton'; +import { useForm } from '../../hooks/useForm'; +import { getUserEmailAddress } from '../../helpers/getUserEmailAddress'; +import { useRoute } from '../../contexts/RouterContext'; +import { formsSubscription } from '../additionalForms'; +import Page from '../../components/basic/Page'; + +export default function EditDepartmentWithData({ id, reload }) { + const t = useTranslation(); + const { data, state, error } = useEndpointDataExperimental(`livechat/department/${ id }`) || {}; + // const { data: userDepartments, state: userDepartmentsState, error: userDepartmentsError } = useEndpointDataExperimental(`livechat/agents/${ id }/departments`); + // const { data: availableDepartments, state: availableDepartmentsState, error: availableDepartmentsError } = useEndpointDataExperimental('livechat/department'); + + if ([state].includes(ENDPOINT_STATES.LOADING)) { + return ; + } + + if (error) { + return {t('User_not_found')}; + } + + return ; +} + +// abandonedRoomsCloseCustomMessage: "fuk u" +// chatClosingTags: ["asd"] +// departmentsAllowedToForward: "" +// description: "Binus is gud" +// email: "asd@asd.com" +// enabled: true +// maxNumberSimultaneousChat: "3" +// name: "binus" +// numAgents: 2 +// offlineMessageChannelName: "" +// requestTagBeforeClosingChat: false +// showOnOfflineForm: true +// showOnRegistration: true +// visitorInactivityTimeoutInSeconds: "12" +// waitingQueueMessage: "Wait for binus" +// _id: "xmF2DLvaLfgorggK5" +// _updatedAt: "2020-08-24T21:22:38.276Z" + +// rooms.autocomplete.channelAndPrivate + +const useQuery = ({ name }) => useMemo(() => ({ selector: JSON.stringify({ name }) }), [name]); + +export function EditDepartment({ data, userDepartments, availableDepartments, id, reload, title }) { + const t = useTranslation(); + const agentsRoute = useRoute('omnichannel-departments'); + + const { department } = data || { department: {} }; + + const { values, handlers, hasUnsavedChanges } = useForm({ + name: department.name, + email: department.email, + description: department.description, + enabled: department.enabled, + maxNumberSimultaneousChat: department.maxNumberSimultaneousChat, + showOnRegistration: department.showOnRegistration, + showOnOfflineForm: department.showOnOfflineForm, + abandonedRoomsCloseCustomMessage: department.abandonedRoomsCloseCustomMessage, + chatClosingTags: department.chatClosingTags, + numAgents: department.numAgents, + requestTagBeforeClosingChat: department.requestTagBeforeClosingChat, + offlineMessageChannelName: department.offlineMessageChannelName, + visitorInactivityTimeoutInSeconds: department.visitorInactivityTimeoutInSeconds, + waitingQueueMessage: department.waitingQueueMessage, + }); + const { + handleName, + handleEmail, + handleDescription, + handleEnabled, + handleMaxNumberSimultaneousChat, + handleShowOnRegistration, + handleShowOnOfflineForm, + handleAbandonedRoomsCloseCustomMessage, + handleChatClosingTags, + handleNumAgents, + handleRequestTagBeforeClosingChat, + handleOfflineMessageChannelName, + handleVisitorInactivityTimeoutInSeconds, + handleWaitingQueueMessage, + } = handlers; + const { + name, + email, + description, + enabled, + maxNumberSimultaneousChat, + showOnRegistration, + showOnOfflineForm, + abandonedRoomsCloseCustomMessage, + chatClosingTags, + numAgents, + requestTagBeforeClosingChat, + offlineMessageChannelName, + visitorInactivityTimeoutInSeconds, + waitingQueueMessage, + } = values; + + // const defaultValidations = { + // enabled: Boolean, + // name: String, + // description: Match.Optional(String), + // showOnRegistration: Boolean, + // email: String, + // showOnOfflineForm: Boolean, + // requestTagBeforeClosingChat: Match.Optional(Boolean), + // chatClosingTags: Match.Optional([String]), + // }; + + // // The Livechat Form department support addition/custom fields, so those fields need to be added before validating + // Object.keys(departmentData).forEach((field) => { + // if (!defaultValidations.hasOwnProperty(field)) { + // defaultValidations[field] = Match.OneOf(String, Match.Integer, Boolean); + // } + // }); + + // check(departmentData, defaultValidations); + // check(departmentAgents, Match.Maybe({ + // upsert: Match.Maybe(Array), + // remove: Match.Maybe(Array), + // })); + + const query = useQuery({ offlineMessageChannelName }); + + const { data: autoCompleteChannels } = useEndpointDataExperimental('rooms.autocomplete.channelAndPrivate', query) || {}; + + const channelOpts = useMemo(() => (autoCompleteChannels && autoCompleteChannels.items ? autoCompleteChannels.items.map(({ _id, name }) => [_id, name || _id]) : []), [autoCompleteChannels]); + + console.log(channelOpts); + + const saveAgentInfo = useMethod('livechat:saveDepartment'); + + const dispatchToastMessage = useToastMessageDispatch(); + + const handleSave = useMutableCallback(async () => { + try { + await saveAgentInfo(id); + dispatchToastMessage({ type: 'success', message: t('saved') }); + agentsRoute.push({}); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + console.log(error); + } + }); + + return + + + + + {t('Enabled')} + + + + + + {t('Name')} + + + + + + {t('Description')} + + + + + + {t('Show_on_registration_page')} + + + + + + {t('Email')} + + } onChange={handleEmail}/> + + + + {t('Show_on_offline_page')} + + + + + + {t('Livechat_DepartmentOfflineMessageToChannel')} + + + + + + {t('Max_number_of_chats_per_agent')} + + + + + + {t('How_long_to_wait_to_consider_visitor_abandonment_in_seconds')} + + + + + + {t('Waiting_queue_message')} + + + + + + + ; +} + + diff --git a/client/omnichannel/departments/DepartmentsPage.js b/client/omnichannel/departments/DepartmentsPage.js new file mode 100644 index 0000000000000..0d4ad6187a5cd --- /dev/null +++ b/client/omnichannel/departments/DepartmentsPage.js @@ -0,0 +1,72 @@ +import React, { useState, useEffect } from 'react'; +import { TextInput, Button, Box, Icon } from '@rocket.chat/fuselage'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + +import Page from '../../components/basic/Page'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useEndpointAction } from '../../hooks/useEndpointAction'; +import { GenericTable } from '../../components/GenericTable'; +import { UserAutoComplete } from '../../components/basic/AutoComplete'; + +const FilterByText = ({ setFilter, ...props }) => { + const t = useTranslation(); + const [text, setText] = useState(''); + + const handleChange = useMutableCallback((event) => setText(event.currentTarget.value)); + const onSubmit = useMutableCallback((e) => e.preventDefault()); + + useEffect(() => { + setFilter({ text }); + }, [setFilter, text]); + return + } onChange={handleChange} value={text} /> + ; +}; + + +function AddDepartment({ reload, ...props }) { + const t = useTranslation(); + const [username, setUsername] = useState(); + + const saveAction = useEndpointAction('POST', 'livechat/users/department', { username }); + + const handleSave = useMutableCallback(async () => { + if (!username) { + return; + } + const result = await saveAction(); + if (!result.success) { + return; + } + reload(); + setUsername(); + }); + return + + + ; +} + +function DepartmentsPage({ + data, + reload, + header, + setParams, + params, + title, + renderRow, + children, +}) { + return + + + + + + + + {children} + ; +} + +export default DepartmentsPage; diff --git a/client/omnichannel/departments/DepartmentsRoute.js b/client/omnichannel/departments/DepartmentsRoute.js new file mode 100644 index 0000000000000..c02d83081d3ad --- /dev/null +++ b/client/omnichannel/departments/DepartmentsRoute.js @@ -0,0 +1,118 @@ + +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 { Th } from '../../components/GenericTable'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useEndpointDataExperimental } from '../../hooks/useEndpointDataExperimental'; +import { useEndpointAction } from '../../hooks/useEndpointAction'; +import { usePermission } from '../../contexts/AuthorizationContext'; +import NotAuthorizedPage from '../../components/NotAuthorizedPage'; +import DepartmentsPage from './DepartmentsPage'; +import EditDepartmentWithData from './AgentEdit'; +// import AgentInfo from './AgentInfo'; +import UserAvatar from '../../components/basic/avatar/UserAvatar'; +import { useRouteParameter, useRoute } from '../../contexts/RouterContext'; +import VerticalBar from '../../components/basic/VerticalBar'; + +export function RemoveAgentButton({ _id, reload }) { + const deleteAction = useEndpointAction('DELETE', `livechat/department/${ _id }`); + + const handleRemoveClick = useMutableCallback(async (e) => { + e.preventDefault(); + const result = await deleteAction(); + if (result.success === true) { + reload(); + } + }); + + return ; +} + +const sortDir = (sortDir) => (sortDir === 'asc' ? 1 : -1); + +const useQuery = ({ text, itemsPerPage, current }, [column, direction]) => useMemo(() => ({ + fields: JSON.stringify({ name: 1, username: 1, emails: 1, avatarETag: 1 }), + text, + sort: JSON.stringify({ [column]: sortDir(direction), usernames: column === 'name' ? sortDir(direction) : undefined }), + ...itemsPerPage && { count: itemsPerPage }, + ...current && { offset: current }, +}), [text, itemsPerPage, current, column, direction]); + +function AgentsRoute() { + const t = useTranslation(); + const canViewAgents = usePermission('manage-livechat-departments'); + + const [params, setParams] = useState({ text: '', current: 0, itemsPerPage: 25 }); + const [sort, setSort] = useState(['name', 'asc']); + + const debouncedParams = useDebouncedValue(params, 500); + const debouncedSort = useDebouncedValue(sort, 500); + const query = useQuery(debouncedParams, debouncedSort); + const departmentsRoute = useRoute('omnichannel-departments'); + const context = useRouteParameter('context'); + const id = useRouteParameter('id'); + + const onHeaderClick = useMutableCallback((id) => { + const [sortBy, sortDirection] = sort; + + if (sortBy === id) { + setSort([id, sortDirection === 'asc' ? 'desc' : 'asc']); + return; + } + setSort([id, 'asc']); + }); + + const onRowClick = useMutableCallback((id) => () => departmentsRoute.push({ + context: 'edit', + id, + })); + + const { data, reload } = useEndpointDataExperimental('livechat/department', query) || {}; + console.log(data); + + + const header = useMemo(() => [ + {t('Name')}, + {t('Description')}, + {t('Num_Agents')}, + {t('Enabled')}, + {t('Show_on_registration_page')}, + {t('Remove')}, + ].filter(Boolean), [sort, onHeaderClick, t]); + + const renderRow = useCallback(({ name, _id, description, numAgents, enabled, showOnRegistration }) => + {name} + {description} + {numAgents || '0'} + {enabled ? t('Yes') : t('No')} + {showOnRegistration ? t('Yes') : t('No')} + + , [onRowClick, t, reload]); + + if (!canViewAgents) { + return ; + } + + if (context === 'edit' || context === 'new') { + return ; + } + + + return + ; +} + +export default AgentsRoute; diff --git a/client/omnichannel/departments/Skeleton.js b/client/omnichannel/departments/Skeleton.js new file mode 100644 index 0000000000000..2fc5e9097c149 --- /dev/null +++ b/client/omnichannel/departments/Skeleton.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { Box, Skeleton } from '@rocket.chat/fuselage'; + +export const FormSkeleton = (props) => + + + + + + +; diff --git a/client/omnichannel/routes.js b/client/omnichannel/routes.js index 9def7135deab4..fa2743167e563 100644 --- a/client/omnichannel/routes.js +++ b/client/omnichannel/routes.js @@ -73,3 +73,8 @@ registerOmnichannelRoute('/current', { name: 'omnichannel-current-chats', lazyRouteComponent: () => import('./currentChats/CurrentChatsRoute'), }); + +registerOmnichannelRoute('/departments/:context?/:id?', { + name: 'omnichannel-departments', + lazyRouteComponent: () => import('./departments/DepartmentsRoute'), +}); diff --git a/packages/rocketchat-i18n/i18n/en.i18n.json b/packages/rocketchat-i18n/i18n/en.i18n.json index 6cc3ad660783d..94be80098e0a5 100644 --- a/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/packages/rocketchat-i18n/i18n/en.i18n.json @@ -1824,6 +1824,7 @@ "How_knowledgeable_was_the_chat_agent": "How knowledgeable was the chat agent?", "How_long_to_wait_after_agent_goes_offline": "How Long to Wait After Agent Goes Offline", "How_long_to_wait_to_consider_visitor_abandonment": "How Long to Wait to Consider Visitor Abandonment?", + "How_long_to_wait_to_consider_visitor_abandonment_in_seconds": "How Long to Wait to Consider Visitor Abandonment?", "How_responsive_was_the_chat_agent": "How responsive was the chat agent?", "How_satisfied_were_you_with_this_chat": "How satisfied were you with this chat?", "How_to_handle_open_sessions_when_agent_goes_offline": "How to Handle Open Sessions When Agent Goes Offline", From 6e34e484a452aafc6efa46726db1d5be9a74372b Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 31 Aug 2020 17:12:25 -0300 Subject: [PATCH 02/21] wip --- client/omnichannel/departments/AgentEdit.js | 37 ++++++++++++--------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/client/omnichannel/departments/AgentEdit.js b/client/omnichannel/departments/AgentEdit.js index f7d0627785218..af38b13a155d6 100644 --- a/client/omnichannel/departments/AgentEdit.js +++ b/client/omnichannel/departments/AgentEdit.js @@ -1,5 +1,5 @@ import React, { useMemo, useRef, useState } from 'react'; -import { Field, TextInput, NumberInput, SelectFiltered, Box, MultiSelect, Icon, Select, ToggleSwitch, TextAreaInput } from '@rocket.chat/fuselage'; +import { Field, TextInput, NumberInput, SelectFiltered, Box, MultiSelect, Icon, Select, ToggleSwitch, TextAreaInput, ButtonGroup, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useSubscription } from 'use-subscription'; @@ -62,20 +62,19 @@ export function EditDepartment({ data, userDepartments, availableDepartments, id const { department } = data || { department: {} }; const { values, handlers, hasUnsavedChanges } = useForm({ - name: department.name, - email: department.email, - description: department.description, - enabled: department.enabled, - maxNumberSimultaneousChat: department.maxNumberSimultaneousChat, - showOnRegistration: department.showOnRegistration, - showOnOfflineForm: department.showOnOfflineForm, - abandonedRoomsCloseCustomMessage: department.abandonedRoomsCloseCustomMessage, - chatClosingTags: department.chatClosingTags, - numAgents: department.numAgents, - requestTagBeforeClosingChat: department.requestTagBeforeClosingChat, - offlineMessageChannelName: department.offlineMessageChannelName, - visitorInactivityTimeoutInSeconds: department.visitorInactivityTimeoutInSeconds, - waitingQueueMessage: department.waitingQueueMessage, + name: (department && department.name) || '', + email: (department && department.email) || '', + description: (department && department.description) || '', + enabled: (department && department.enabled) || '', + maxNumberSimultaneousChat: (department && department.maxNumberSimultaneousChat) || undefined, + showOnRegistration: (department && department.showOnRegistration) || true, + showOnOfflineForm: (department && department.showOnOfflineForm) || true, + abandonedRoomsCloseCustomMessage: (department && department.abandonedRoomsCloseCustomMessage) || '', + chatClosingTags: (department && department.chatClosingTags) || '', + requestTagBeforeClosingChat: (department && department.requestTagBeforeClosingChat) || false, + offlineMessageChannelName: (department && department.offlineMessageChannelName) || '', + visitorInactivityTimeoutInSeconds: (department && department.visitorInactivityTimeoutInSeconds) || undefined, + waitingQueueMessage: (department && department.waitingQueueMessage) || '', }); const { handleName, @@ -159,7 +158,13 @@ export function EditDepartment({ data, userDepartments, availableDepartments, id return - + + + + + {t('Enabled')} From c43ed83a494d6eaed51fccd832004fa7231037be Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 8 Sep 2020 12:20:02 -0300 Subject: [PATCH 03/21] more work in progress --- client/components/basic/AutoCompleteAgent.js | 5 +- .../{AgentEdit.js => DepartmentEdit.js} | 21 ++- .../departments/DepartmentsAgentsTable.js | 169 ++++++++++++++++++ .../departments/DepartmentsRoute.js | 2 +- 4 files changed, 190 insertions(+), 7 deletions(-) rename client/omnichannel/departments/{AgentEdit.js => DepartmentEdit.js} (91%) create mode 100644 client/omnichannel/departments/DepartmentsAgentsTable.js diff --git a/client/components/basic/AutoCompleteAgent.js b/client/components/basic/AutoCompleteAgent.js index 5e42fdd0df5fb..c070dda62b485 100644 --- a/client/components/basic/AutoCompleteAgent.js +++ b/client/components/basic/AutoCompleteAgent.js @@ -9,7 +9,8 @@ export const AutoCompleteAgent = React.memo((props) => { 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]); + const options = useMemo(() => (data && [...data.users.map((user) => ({ value: user._id, label: user.name }))]) || [], [data]); + const optionsWithAll = 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 { setFilter={setFilter} renderSelected={({ label }) => <>{label}} renderItem={({ value, ...props }) => + + {t('Department')}: + + + + + {t('Agents')}: + + ; } - - diff --git a/client/omnichannel/departments/DepartmentsAgentsTable.js b/client/omnichannel/departments/DepartmentsAgentsTable.js new file mode 100644 index 0000000000000..06b95648403e6 --- /dev/null +++ b/client/omnichannel/departments/DepartmentsAgentsTable.js @@ -0,0 +1,169 @@ + +import { useDebouncedValue, useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import React, { useMemo, useCallback, useState, useEffect } from 'react'; +import { Box, Table, Icon, Button, NumberInput } from '@rocket.chat/fuselage'; + +import { Th, GenericTable } from '../../components/GenericTable'; +import { useTranslation } from '../../contexts/TranslationContext'; +import { useEndpointDataExperimental } from '../../hooks/useEndpointDataExperimental'; +import { useEndpointAction } from '../../hooks/useEndpointAction'; +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'; +import { AutoCompleteAgent } from '../../components/basic/AutoCompleteAgent'; + +function AddAgent({ agentList, setAgentList, ...props }) { + const t = useTranslation(); + const [userId, setUserId] = useState(); + const getAgent = useEndpointAction('GET', `livechat/users/agent/${ userId }`); + const dispatchToastMessage = useToastMessageDispatch(); + + const handleAgent = useMutableCallback((e) => setUserId(e)); + console.log(userId); + + const handleSave = useMutableCallback(async () => { + if (!userId) { + return; + } + const { user } = await getAgent(); + + if (agentList.filter((e) => e.agentId === userId).length === 0) { + setAgentList([user, ...agentList]); + setUserId(); + } else { + dispatchToastMessage({ type: 'error', message: t('Already_selected') }); + } + }); + return + + + ; +} + +function AgentsPage({ + data, + reload, + header, + setParams, + params, + renderRow, + children, + agentList, + setAgentList, +}) { + console.log(data); + return + + + {children} + ; +} + + +export function RemoveAgentButton({ _id, setAgentList, agentList }) { + const setModal = useSetModal(); + const dispatchToastMessage = useToastMessageDispatch(); + const t = useTranslation(); + + const handleDelete = useMutableCallback((e) => { + e.stopPropagation(); + const onDeleteAgent = async () => { + setAgentList([...agentList.filter((e) => e._id !== _id)]); + dispatchToastMessage({ type: 'success', message: t('Agent_removed') }); + setModal(); + }; + + setModal( setModal()}/>); + }); + + return + + ; +} + +export function Count({ agentId, setAgentList, agentList }) { + const t = useTranslation(); + const [agentCount, setAgentCount] = useState(agentList.find((agent) => agent.agentId === agentId).count); + const [updatedList, setUpdatedList] = useState(agentList); + + useMemo(() => setAgentList(updatedList), [setAgentList, updatedList]); + + const changeList = () => { + setUpdatedList(agentList.map((agent) => { + if (agent.agentId === agentId) { + agent.count = agentCount; + } + return agent; + })); + debugger + console.log('NEWLIST', updatedList); + // setAgentList(newList); + // debugger + // console.log(agentList); + }; + console.log(agentCount); + + const handleCount = useMutableCallback(async (e) => { + setAgentCount(Number(e.currentTarget.value)); + changeList(); + }); + + return + + ; +} + +const sortDir = (sortDir) => (sortDir === 'asc' ? 1 : -1); + +function DepartmentsAgentsTable({ _id, agents }) { + const t = useTranslation(); + + console.log(agents); + + const [agentList, setAgentList] = useState(agents || []); + const [data, setData] = useState({}); + + useMemo(() => setData({ users: agentList }), [agentList]); + + const mediaQuery = useMediaQuery('(min-width: 1024px)'); + + const header = useMemo(() => [ + {t('Name')}, + {t('Username')}, + {t('Email')}, + {t('Remove')}, + ].filter(Boolean), [t]); + + const renderRow = useCallback(({ emails, agentId, username, name, avatarETag }) => + + + + + + {name || username} + {!mediaQuery && name && {`@${ username }`} } + + + + + + + + , [ agentList, mediaQuery]); + + return + ; +} + +export default DepartmentsAgentsTable; diff --git a/client/omnichannel/departments/DepartmentsRoute.js b/client/omnichannel/departments/DepartmentsRoute.js index c02d83081d3ad..6d883b0489f34 100644 --- a/client/omnichannel/departments/DepartmentsRoute.js +++ b/client/omnichannel/departments/DepartmentsRoute.js @@ -10,7 +10,7 @@ import { useEndpointAction } from '../../hooks/useEndpointAction'; import { usePermission } from '../../contexts/AuthorizationContext'; import NotAuthorizedPage from '../../components/NotAuthorizedPage'; import DepartmentsPage from './DepartmentsPage'; -import EditDepartmentWithData from './AgentEdit'; +import EditDepartmentWithData from './DepartmentEdit'; // import AgentInfo from './AgentInfo'; import UserAvatar from '../../components/basic/avatar/UserAvatar'; import { useRouteParameter, useRoute } from '../../contexts/RouterContext'; From 8b826a1f141356bb15019e67f0e062432413c63d Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 8 Sep 2020 12:23:46 -0300 Subject: [PATCH 04/21] lint --- .../omnichannel/departments/DepartmentEdit.js | 27 ++++++++----------- .../departments/DepartmentsAgentsTable.js | 19 +++++-------- .../departments/DepartmentsRoute.js | 6 ++--- 3 files changed, 19 insertions(+), 33 deletions(-) diff --git a/client/omnichannel/departments/DepartmentEdit.js b/client/omnichannel/departments/DepartmentEdit.js index 9ed8e85d4f810..872920291d479 100644 --- a/client/omnichannel/departments/DepartmentEdit.js +++ b/client/omnichannel/departments/DepartmentEdit.js @@ -1,20 +1,15 @@ /* eslint-disable complexity */ -import React, { useMemo, useRef, useState } from 'react'; -import { Field, TextInput, NumberInput, SelectFiltered, Box, MultiSelect, Icon, Divider, ToggleSwitch, TextAreaInput, ButtonGroup, Button } from '@rocket.chat/fuselage'; +import React, { useMemo } from 'react'; +import { Field, TextInput, NumberInput, SelectFiltered, Box, Icon, Divider, ToggleSwitch, TextAreaInput, ButtonGroup, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useSubscription } from 'use-subscription'; import { useMethod } from '../../contexts/ServerContext'; import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; import { useTranslation } from '../../contexts/TranslationContext'; -import VerticalBar from '../../components/basic/VerticalBar'; -import { UserInfo } from '../../components/basic/UserInfo'; import { useEndpointDataExperimental, ENDPOINT_STATES } from '../../hooks/useEndpointDataExperimental'; import { FormSkeleton } from './Skeleton'; import { useForm } from '../../hooks/useForm'; -import { getUserEmailAddress } from '../../helpers/getUserEmailAddress'; import { useRoute } from '../../contexts/RouterContext'; -import { formsSubscription } from '../additionalForms'; import Page from '../../components/basic/Page'; import { AutoCompleteDepartment } from '../../components/basic/AutoCompleteDepartment'; import DepartmentsAgentsTable from './DepartmentsAgentsTable'; @@ -58,7 +53,7 @@ export default function EditDepartmentWithData({ id, reload }) { const useQuery = ({ name }) => useMemo(() => ({ selector: JSON.stringify({ name }) }), [name]); -export function EditDepartment({ data, userDepartments, availableDepartments, id, reload, title }) { +export function EditDepartment({ data, id, title }) { const t = useTranslation(); const agentsRoute = useRoute('omnichannel-departments'); @@ -88,10 +83,10 @@ export function EditDepartment({ data, userDepartments, availableDepartments, id handleMaxNumberSimultaneousChat, handleShowOnRegistration, handleShowOnOfflineForm, - handleAbandonedRoomsCloseCustomMessage, - handleChatClosingTags, - handleNumAgents, - handleRequestTagBeforeClosingChat, + // handleAbandonedRoomsCloseCustomMessage, + // handleChatClosingTags, + // handleNumAgents, + // handleRequestTagBeforeClosingChat, handleOfflineMessageChannelName, handleVisitorInactivityTimeoutInSeconds, handleWaitingQueueMessage, @@ -105,10 +100,10 @@ export function EditDepartment({ data, userDepartments, availableDepartments, id maxNumberSimultaneousChat, showOnRegistration, showOnOfflineForm, - abandonedRoomsCloseCustomMessage, - chatClosingTags, - numAgents, - requestTagBeforeClosingChat, + // abandonedRoomsCloseCustomMessage, + // chatClosingTags, + // numAgents, + // requestTagBeforeClosingChat, offlineMessageChannelName, visitorInactivityTimeoutInSeconds, waitingQueueMessage, diff --git a/client/omnichannel/departments/DepartmentsAgentsTable.js b/client/omnichannel/departments/DepartmentsAgentsTable.js index 06b95648403e6..9ab45d1e8fc15 100644 --- a/client/omnichannel/departments/DepartmentsAgentsTable.js +++ b/client/omnichannel/departments/DepartmentsAgentsTable.js @@ -1,15 +1,12 @@ -import { useDebouncedValue, useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import React, { useMemo, useCallback, useState, useEffect } from 'react'; +import { useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import React, { useMemo, useCallback, useState } from 'react'; import { Box, Table, Icon, Button, NumberInput } from '@rocket.chat/fuselage'; import { Th, GenericTable } from '../../components/GenericTable'; import { useTranslation } from '../../contexts/TranslationContext'; -import { useEndpointDataExperimental } from '../../hooks/useEndpointDataExperimental'; import { useEndpointAction } from '../../hooks/useEndpointAction'; 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'; @@ -88,7 +85,7 @@ export function RemoveAgentButton({ _id, setAgentList, agentList }) { export function Count({ agentId, setAgentList, agentList }) { const t = useTranslation(); - const [agentCount, setAgentCount] = useState(agentList.find((agent) => agent.agentId === agentId).count); + const [agentCount, setAgentCount] = useState(agentList.find((agent) => agent.agentId === agentId).counta); const [updatedList, setUpdatedList] = useState(agentList); useMemo(() => setAgentList(updatedList), [setAgentList, updatedList]); @@ -100,8 +97,6 @@ export function Count({ agentId, setAgentList, agentList }) { } return agent; })); - debugger - console.log('NEWLIST', updatedList); // setAgentList(newList); // debugger // console.log(agentList); @@ -118,9 +113,7 @@ export function Count({ agentId, setAgentList, agentList }) { ; } -const sortDir = (sortDir) => (sortDir === 'asc' ? 1 : -1); - -function DepartmentsAgentsTable({ _id, agents }) { +function DepartmentsAgentsTable({ agents }) { const t = useTranslation(); console.log(agents); @@ -139,7 +132,7 @@ function DepartmentsAgentsTable({ _id, agents }) { {t('Remove')}, ].filter(Boolean), [t]); - const renderRow = useCallback(({ emails, agentId, username, name, avatarETag }) => + const renderRow = useCallback(({ agentId, username, name, avatarETag }) => @@ -154,7 +147,7 @@ function DepartmentsAgentsTable({ _id, agents }) { - , [ agentList, mediaQuery]); + , [agentList, mediaQuery]); return Date: Tue, 15 Sep 2020 18:05:58 -0300 Subject: [PATCH 05/21] More wip --- .../omnichannel/departments/DepartmentEdit.js | 173 +++++++++++++----- .../departments/DepartmentsAgentsTable.js | 64 +++++-- .../departments/DepartmentsRoute.js | 21 +-- .../DepartmentBusinessHours.js | 22 +++ .../additionalForms/DepartmentForwarding.js | 23 +++ .../additionalForms/EeNumberInput.js | 18 ++ .../additionalForms/EeTextAreaInput.js | 18 ++ .../additionalForms/EeTextInput.js | 18 ++ .../omnichannel/additionalForms/register.js | 5 + 9 files changed, 282 insertions(+), 80 deletions(-) create mode 100644 ee/client/omnichannel/additionalForms/DepartmentBusinessHours.js create mode 100644 ee/client/omnichannel/additionalForms/DepartmentForwarding.js create mode 100644 ee/client/omnichannel/additionalForms/EeNumberInput.js create mode 100644 ee/client/omnichannel/additionalForms/EeTextAreaInput.js create mode 100644 ee/client/omnichannel/additionalForms/EeTextInput.js diff --git a/client/omnichannel/departments/DepartmentEdit.js b/client/omnichannel/departments/DepartmentEdit.js index 872920291d479..e4858cb47a234 100644 --- a/client/omnichannel/departments/DepartmentEdit.js +++ b/client/omnichannel/departments/DepartmentEdit.js @@ -1,7 +1,8 @@ /* eslint-disable complexity */ -import React, { useMemo } from 'react'; -import { Field, TextInput, NumberInput, SelectFiltered, Box, Icon, Divider, ToggleSwitch, TextAreaInput, ButtonGroup, Button } from '@rocket.chat/fuselage'; +import React, { useMemo, useState } from 'react'; +import { Field, TextInput, Chip, SelectFiltered, Box, Icon, Divider, ToggleSwitch, TextAreaInput, ButtonGroup, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useSubscription } from 'use-subscription'; import { useMethod } from '../../contexts/ServerContext'; import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; @@ -13,6 +14,8 @@ import { useRoute } from '../../contexts/RouterContext'; import Page from '../../components/basic/Page'; import { AutoCompleteDepartment } from '../../components/basic/AutoCompleteDepartment'; import DepartmentsAgentsTable from './DepartmentsAgentsTable'; +import { formsSubscription } from '../additionalForms'; + export default function EditDepartmentWithData({ id, reload }) { const t = useTranslation(); @@ -56,9 +59,32 @@ const useQuery = ({ name }) => useMemo(() => ({ selector: JSON.stringify({ name export function EditDepartment({ data, id, title }) { const t = useTranslation(); const agentsRoute = useRoute('omnichannel-departments'); + const eeForms = useSubscription(formsSubscription); + + const { + useEeNumberInput = () => {}, + useEeTextInput = () => {}, + useEeTextAreaInput = () => {}, + useDepartmentForwarding = () => {}, + useDepartmentBusinessHours = () => {}, + } = eeForms; + + const MaxChats = useEeNumberInput(); + const VisitorInactivity = useEeNumberInput(); + const WaitingQueueMessageInput = useEeTextAreaInput(); + const AbandonedMessageInput = useEeTextInput(); + const DepartmentForwarding = useDepartmentForwarding(); + const DepartmentBusinessHours = useDepartmentBusinessHours(); + const [agentList, setAgentList] = useState([]); + const { department } = data || { department: {} }; + const [tags, setTags] = useState(department && department.chatClosingTags); + const [tagsText, setTagsText] = useState(); + + console.log(data); + const { values, handlers, hasUnsavedChanges } = useForm({ name: (department && department.name) || '', email: (department && department.email) || '', @@ -68,12 +94,11 @@ export function EditDepartment({ data, id, title }) { showOnRegistration: (department && department.showOnRegistration) || true, showOnOfflineForm: (department && department.showOnOfflineForm) || true, abandonedRoomsCloseCustomMessage: (department && department.abandonedRoomsCloseCustomMessage) || '', - chatClosingTags: (department && department.chatClosingTags) || '', requestTagBeforeClosingChat: (department && department.requestTagBeforeClosingChat) || false, offlineMessageChannelName: (department && department.offlineMessageChannelName) || '', visitorInactivityTimeoutInSeconds: (department && department.visitorInactivityTimeoutInSeconds) || undefined, waitingQueueMessage: (department && department.waitingQueueMessage) || '', - forwardDepartment: (department && department.forwardDepartment) || '', + departmentsAllowedToForward: (department && department.departmentsAllowedToForward) || [], }); const { handleName, @@ -83,14 +108,12 @@ export function EditDepartment({ data, id, title }) { handleMaxNumberSimultaneousChat, handleShowOnRegistration, handleShowOnOfflineForm, - // handleAbandonedRoomsCloseCustomMessage, - // handleChatClosingTags, - // handleNumAgents, - // handleRequestTagBeforeClosingChat, + handleAbandonedRoomsCloseCustomMessage, + handleRequestTagBeforeClosingChat, handleOfflineMessageChannelName, handleVisitorInactivityTimeoutInSeconds, handleWaitingQueueMessage, - handleForwardDepartment, + handleDepartmentsAllowedToForward, } = handlers; const { name, @@ -100,16 +123,28 @@ export function EditDepartment({ data, id, title }) { maxNumberSimultaneousChat, showOnRegistration, showOnOfflineForm, - // abandonedRoomsCloseCustomMessage, - // chatClosingTags, - // numAgents, - // requestTagBeforeClosingChat, + abandonedRoomsCloseCustomMessage, + requestTagBeforeClosingChat, offlineMessageChannelName, visitorInactivityTimeoutInSeconds, waitingQueueMessage, - forwardDepartment, + departmentsAllowedToForward, } = values; + const handleTagChipClick = (tag) => () => { + setTags((tags) => tags.filter((_tag) => _tag !== tag)); + }; + + const handleTagTextSubmit = useMutableCallback(() => { + if (!tags.includes(tagsText)) { + setTags([...tags, tagsText]); + setTagsText(''); + } + }); + + const handleTagTextChange = useMutableCallback((e) => { + setTagsText(e.target.value); + }); // const defaultValidations = { // enabled: Boolean, // name: String, @@ -138,17 +173,54 @@ export function EditDepartment({ data, id, title }) { const { data: autoCompleteChannels } = useEndpointDataExperimental('rooms.autocomplete.channelAndPrivate', query) || {}; - const channelOpts = useMemo(() => (autoCompleteChannels && autoCompleteChannels.items ? autoCompleteChannels.items.map(({ _id, name }) => [_id, name || _id]) : []), [autoCompleteChannels]); + const channelOpts = useMemo(() => (autoCompleteChannels && autoCompleteChannels.items ? autoCompleteChannels.items.map(({ name }) => [name, name]) : []), [autoCompleteChannels]); console.log(channelOpts); - const saveAgentInfo = useMethod('livechat:saveDepartment'); + const saveDepartmentInfo = useMethod('livechat:saveDepartment'); const dispatchToastMessage = useToastMessageDispatch(); + // enabled: true, + // name: 'asdasdasdasdasd', + // description: 'asdsdasdad', + // showOnRegistration: true, + // showOnOfflineForm: true, + // requestTagBeforeClosingChat: true, + // email: 'asdadss@gmail.com', + // chatClosingTags: [ 'pinus' ], + // offlineMessageChannelName: 'general', + // maxNumberSimultaneousChat: '10', + // visitorInactivityTimeoutInSeconds: '20', + // abandonedRoomsCloseCustomMessage: 'fuk u', + // waitingQueueMessage: 'fuk me', + // departmentsAllowedToForward: 'inNc2tPHTbyBo2dmt,8rbimWYR4HLLqii3t' + + const nameError = useMemo(() => (!name || name.length === 0 ? t('The_field_is_required', 'name') : undefined), [name, t]); + const emailError = useMemo(() => (!email || email.length === 0 ? t('The_field_is_required', 'email') : undefined), [email, t]); + const tagError = useMemo(() => ((requestTagBeforeClosingChat && !tags) || (requestTagBeforeClosingChat && tags.length === 0) ? t('The_field_is_required', 'tags') : undefined), [tags, t]); + + const handleSave = useMutableCallback(async () => { + const payload = { + enabled, + name, + description, + showOnRegistration, + showOnOfflineForm, + requestTagBeforeClosingChat, + email, + chatClosingTags: tags, + offlineMessageChannelName, + maxNumberSimultaneousChat, + visitorInactivityTimeoutInSeconds, + abandonedRoomsCloseCustomMessage, + waitingQueueMessage, + departmentsAllowedToForward: departmentsAllowedToForward && departmentsAllowedToForward[0], + }; + console.log(agentList); try { - await saveAgentInfo(id); + await saveDepartmentInfo(id, payload, agentList); dispatchToastMessage({ type: 'success', message: t('saved') }); agentsRoute.push({}); } catch (error) { @@ -161,80 +233,83 @@ export function EditDepartment({ data, id, title }) { - - + {t('Enabled')} - + - + {t('Name')} - + - + {t('Description')} - + - + {t('Show_on_registration_page')} - + {t('Email')} - } onChange={handleEmail}/> + } onChange={handleEmail} placeholder={t('Email')} /> - + {t('Show_on_offline_page')} - + {t('Livechat_DepartmentOfflineMessageToChannel')} - + - - {t('Max_number_of_chats_per_agent')} + {MaxChats && } + {VisitorInactivity && } + {AbandonedMessageInput && } + {WaitingQueueMessageInput && } + {DepartmentForwarding && } + + {t('Request_tag_before_closing_chat')} - + - - {t('How_long_to_wait_to_consider_visitor_abandonment_in_seconds')} + + {t('Tag')} - + + + {t('Conversation_closing_tags_description')} + {tags && tags.length > 0 && + {tags.map((tag, i) => {tag})} + } - - {t('Waiting_queue_message')} - - - - - - {t('Department')}: - - + {DepartmentBusinessHours && } - + {t('Agents')}: - + diff --git a/client/omnichannel/departments/DepartmentsAgentsTable.js b/client/omnichannel/departments/DepartmentsAgentsTable.js index 9ab45d1e8fc15..1595cb1a26338 100644 --- a/client/omnichannel/departments/DepartmentsAgentsTable.js +++ b/client/omnichannel/departments/DepartmentsAgentsTable.js @@ -1,6 +1,6 @@ import { useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import React, { useMemo, useCallback, useState } from 'react'; +import React, { useMemo, useCallback, useState, useLayoutEffect } from 'react'; import { Box, Table, Icon, Button, NumberInput } from '@rocket.chat/fuselage'; import { Th, GenericTable } from '../../components/GenericTable'; @@ -60,7 +60,7 @@ function AgentsPage({ } -export function RemoveAgentButton({ _id, setAgentList, agentList }) { +export function RemoveAgentButton({ agentId, setAgentList, agentList }) { const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); const t = useTranslation(); @@ -68,7 +68,10 @@ export function RemoveAgentButton({ _id, setAgentList, agentList }) { const handleDelete = useMutableCallback((e) => { e.stopPropagation(); const onDeleteAgent = async () => { - setAgentList([...agentList.filter((e) => e._id !== _id)]); + const newList = agentList.filter((listItem) => listItem.agentId !== agentId); + console.log(newList); + debugger + setAgentList(newList); dispatchToastMessage({ type: 'success', message: t('Agent_removed') }); setModal(); }; @@ -85,35 +88,55 @@ export function RemoveAgentButton({ _id, setAgentList, agentList }) { export function Count({ agentId, setAgentList, agentList }) { const t = useTranslation(); - const [agentCount, setAgentCount] = useState(agentList.find((agent) => agent.agentId === agentId).counta); + const [agentCount, setAgentCount] = useState(agentList.find((agent) => agent.agentId === agentId).count || 0); const [updatedList, setUpdatedList] = useState(agentList); - useMemo(() => setAgentList(updatedList), [setAgentList, updatedList]); + useLayoutEffect(() => setAgentList(updatedList), [setAgentList, updatedList]); - const changeList = () => { + console.log(agentCount); + + const handleCount = useMutableCallback(async (e) => { + const countValue = Number(e.currentTarget.value); + setAgentCount(countValue); setUpdatedList(agentList.map((agent) => { if (agent.agentId === agentId) { - agent.count = agentCount; + agent.count = countValue; } return agent; })); - // setAgentList(newList); - // debugger - // console.log(agentList); - }; - console.log(agentCount); + }); - const handleCount = useMutableCallback(async (e) => { - setAgentCount(Number(e.currentTarget.value)); - changeList(); + return + + ; +} + +export function Order({ agentId, setAgentList, agentList }) { + const t = useTranslation(); + const [agentOrder, setAgentOrder] = useState(agentList.find((agent) => agent.agentId === agentId).order || 0); + const [updatedList, setUpdatedList] = useState(agentList); + + useLayoutEffect(() => setAgentList(updatedList), [setAgentList, updatedList]); + + console.log(agentOrder); + + const handleOrder = useMutableCallback(async (e) => { + const orderValue = Number(e.currentTarget.value); + setAgentOrder(orderValue); + setUpdatedList(agentList.map((agent) => { + if (agent.agentId === agentId) { + agent.order = orderValue; + } + return agent; + })); }); return - + ; } -function DepartmentsAgentsTable({ agents }) { +function DepartmentsAgentsTable({ agents, setAgentListFinal }) { const t = useTranslation(); console.log(agents); @@ -122,13 +145,14 @@ function DepartmentsAgentsTable({ agents }) { const [data, setData] = useState({}); useMemo(() => setData({ users: agentList }), [agentList]); + useMemo(() => setAgentListFinal(agentList && agentList.users), [agentList, setAgentListFinal]); const mediaQuery = useMediaQuery('(min-width: 1024px)'); const header = useMemo(() => [ {t('Name')}, - {t('Username')}, - {t('Email')}, + {t('Count')}, + {t('Order')}, {t('Remove')}, ].filter(Boolean), [t]); @@ -145,7 +169,7 @@ function DepartmentsAgentsTable({ agents }) { - + , [agentList, mediaQuery]); diff --git a/client/omnichannel/departments/DepartmentsRoute.js b/client/omnichannel/departments/DepartmentsRoute.js index 1efc4b1d1cc3e..1a4429942ec92 100644 --- a/client/omnichannel/departments/DepartmentsRoute.js +++ b/client/omnichannel/departments/DepartmentsRoute.js @@ -11,10 +11,9 @@ import { usePermission } from '../../contexts/AuthorizationContext'; import NotAuthorizedPage from '../../components/NotAuthorizedPage'; import DepartmentsPage from './DepartmentsPage'; import EditDepartmentWithData from './DepartmentEdit'; -// import AgentInfo from './AgentInfo'; import { useRouteParameter, useRoute } from '../../contexts/RouterContext'; -export function RemoveAgentButton({ _id, reload }) { +export function RemoveDepartmentButton({ _id, reload }) { const deleteAction = useEndpointAction('DELETE', `livechat/department/${ _id }`); const handleRemoveClick = useMutableCallback(async (e) => { @@ -38,9 +37,9 @@ const useQuery = ({ text, itemsPerPage, current }, [column, direction]) => useMe ...current && { offset: current }, }), [text, itemsPerPage, current, column, direction]); -function AgentsRoute() { +function DepartmentsRoute() { const t = useTranslation(); - const canViewAgents = usePermission('manage-livechat-departments'); + const canViewDepartments = usePermission('manage-livechat-departments'); const [params, setParams] = useState({ text: '', current: 0, itemsPerPage: 25 }); const [sort, setSort] = useState(['name', 'asc']); @@ -74,22 +73,22 @@ function AgentsRoute() { const header = useMemo(() => [ {t('Name')}, {t('Description')}, - {t('Num_Agents')}, + {t('Num_Departments')}, {t('Enabled')}, {t('Show_on_registration_page')}, {t('Remove')}, ].filter(Boolean), [sort, onHeaderClick, t]); - const renderRow = useCallback(({ name, _id, description, numAgents, enabled, showOnRegistration }) => + const renderRow = useCallback(({ name, _id, description, numDepartments, enabled, showOnRegistration }) => {name} {description} - {numAgents || '0'} + {numDepartments || '0'} {enabled ? t('Yes') : t('No')} {showOnRegistration ? t('Yes') : t('No')} - + , [onRowClick, t, reload]); - if (!canViewAgents) { + if (!canViewDepartments) { return ; } @@ -97,7 +96,7 @@ function AgentsRoute() { return ; + title={context === 'edit' ? t('Edit_department') : t('New_department')} />; } @@ -113,4 +112,4 @@ function AgentsRoute() { ; } -export default AgentsRoute; +export default DepartmentsRoute; diff --git a/ee/client/omnichannel/additionalForms/DepartmentBusinessHours.js b/ee/client/omnichannel/additionalForms/DepartmentBusinessHours.js new file mode 100644 index 0000000000000..49ea87039a197 --- /dev/null +++ b/ee/client/omnichannel/additionalForms/DepartmentBusinessHours.js @@ -0,0 +1,22 @@ +import React, { useMemo } from 'react'; +import { Field, TextInput } from '@rocket.chat/fuselage'; +// import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + +import { useTranslation } from '../../../../client/contexts/TranslationContext'; +import { useEndpointDataExperimental } from '../../../../client/hooks/useEndpointDataExperimental'; + +export const DepartmentBusinessHours = ({ bhId }) => { + const t = useTranslation(); + const { data } = useEndpointDataExperimental('livechat/business-hour', useMemo(() => ({ _id: bhId, type: 'custom' }), [bhId])); + + const name = data && data.businessHour && data.businessHour.name; + + return + {t('Business_Hour')} + + + + ; +}; + +export default DepartmentBusinessHours; diff --git a/ee/client/omnichannel/additionalForms/DepartmentForwarding.js b/ee/client/omnichannel/additionalForms/DepartmentForwarding.js new file mode 100644 index 0000000000000..c8b40cdd250d9 --- /dev/null +++ b/ee/client/omnichannel/additionalForms/DepartmentForwarding.js @@ -0,0 +1,23 @@ +import React, { useMemo } from 'react'; +import { Field, MultiSelectFiltered } from '@rocket.chat/fuselage'; +// import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + +import { useTranslation } from '../../../../client/contexts/TranslationContext'; +import { useEndpointDataExperimental } from '../../../../client/hooks/useEndpointDataExperimental'; + +export const DepartmentForwarding = ({ value, handler, label, placeholder }) => { + const t = useTranslation(); + const { data } = useEndpointDataExperimental('livechat/department'); + + const options = useMemo(() => (data && [...data.departments.map((department) => [department._id, department.name])]) || [], [data]); + + return + {t(label)} + + + + {t('List_of_departments_for_forward_description')} + ; +}; + +export default DepartmentForwarding; diff --git a/ee/client/omnichannel/additionalForms/EeNumberInput.js b/ee/client/omnichannel/additionalForms/EeNumberInput.js new file mode 100644 index 0000000000000..f3dca042d3389 --- /dev/null +++ b/ee/client/omnichannel/additionalForms/EeNumberInput.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { NumberInput, Field } from '@rocket.chat/fuselage'; +// import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + +import { useTranslation } from '../../../../client/contexts/TranslationContext'; + +export const EeNumberInput = ({ value, handler, label, placeholder }) => { + const t = useTranslation(); + + return + {t(label)} + + + + ; +}; + +export default EeNumberInput; diff --git a/ee/client/omnichannel/additionalForms/EeTextAreaInput.js b/ee/client/omnichannel/additionalForms/EeTextAreaInput.js new file mode 100644 index 0000000000000..aa302699d25a5 --- /dev/null +++ b/ee/client/omnichannel/additionalForms/EeTextAreaInput.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { TextAreaInput, Field } from '@rocket.chat/fuselage'; +// import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + +import { useTranslation } from '../../../../client/contexts/TranslationContext'; + +export const EeTextAreaInput = ({ value, handler, label, placeholder }) => { + const t = useTranslation(); + + return + {t(label)} + + + + ; +}; + +export default EeTextAreaInput; diff --git a/ee/client/omnichannel/additionalForms/EeTextInput.js b/ee/client/omnichannel/additionalForms/EeTextInput.js new file mode 100644 index 0000000000000..078d25d651c2b --- /dev/null +++ b/ee/client/omnichannel/additionalForms/EeTextInput.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { TextInput, Field } from '@rocket.chat/fuselage'; +// import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + +import { useTranslation } from '../../../../client/contexts/TranslationContext'; + +export const EeTextInput = ({ value, handler, label, placeholder }) => { + const t = useTranslation(); + + return + {t(label)} + + + + ; +}; + +export default EeTextInput; diff --git a/ee/client/omnichannel/additionalForms/register.js b/ee/client/omnichannel/additionalForms/register.js index 8865d8609052d..fd8b5c698ca30 100644 --- a/ee/client/omnichannel/additionalForms/register.js +++ b/ee/client/omnichannel/additionalForms/register.js @@ -11,6 +11,11 @@ hasLicense('livechat-enterprise').then((enabled) => { registerForm({ useCustomFieldsAdditionalForm: () => useMemo(() => lazy(() => import('./CustomFieldsAdditionalForm')), []) }); registerForm({ useMaxChatsPerAgent: () => useMemo(() => lazy(() => import('./MaxChatsPerAgent')), []) }); registerForm({ useMaxChatsPerAgentDisplay: () => useMemo(() => lazy(() => import('./MaxChatsPerAgentDisplay')), []) }); + registerForm({ useEeNumberInput: () => useMemo(() => lazy(() => import('./EeNumberInput')), []) }); + registerForm({ useEeTextAreaInput: () => useMemo(() => lazy(() => import('./EeTextAreaInput')), []) }); + registerForm({ useEeTextInput: () => useMemo(() => lazy(() => import('./EeTextInput')), []) }); + registerForm({ useDepartmentForwarding: () => useMemo(() => lazy(() => import('./DepartmentForwarding')), []) }); + registerForm({ useDepartmentBusinessHours: () => useMemo(() => lazy(() => import('./DepartmentBusinessHours')), []) }); registerForm({ useCustomFieldsAdditionalForm: () => useMemo(() => lazy(() => import('./CustomFieldsAdditionalForm')), []) }); registerForm({ useBusinessHoursTimeZone: () => useMemo(() => lazy(() => import('./BusinessHoursTimeZone')), []) }); registerForm({ useBusinessHoursMultiple: () => useMemo(() => lazy(() => import('./BusinessHoursMultiple')), []) }); From e2cdc0bc5fdfeffc54491db6f49f01d24e838d71 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 15 Sep 2020 18:07:02 -0300 Subject: [PATCH 06/21] lint --- client/omnichannel/departments/DepartmentEdit.js | 3 +-- client/omnichannel/departments/DepartmentsAgentsTable.js | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/client/omnichannel/departments/DepartmentEdit.js b/client/omnichannel/departments/DepartmentEdit.js index e4858cb47a234..4184d9ade378d 100644 --- a/client/omnichannel/departments/DepartmentEdit.js +++ b/client/omnichannel/departments/DepartmentEdit.js @@ -12,7 +12,6 @@ import { FormSkeleton } from './Skeleton'; import { useForm } from '../../hooks/useForm'; import { useRoute } from '../../contexts/RouterContext'; import Page from '../../components/basic/Page'; -import { AutoCompleteDepartment } from '../../components/basic/AutoCompleteDepartment'; import DepartmentsAgentsTable from './DepartmentsAgentsTable'; import { formsSubscription } from '../additionalForms'; @@ -85,7 +84,7 @@ export function EditDepartment({ data, id, title }) { console.log(data); - const { values, handlers, hasUnsavedChanges } = useForm({ + const { values, handlers } = useForm({ name: (department && department.name) || '', email: (department && department.email) || '', description: (department && department.description) || '', diff --git a/client/omnichannel/departments/DepartmentsAgentsTable.js b/client/omnichannel/departments/DepartmentsAgentsTable.js index 1595cb1a26338..937241c2dd184 100644 --- a/client/omnichannel/departments/DepartmentsAgentsTable.js +++ b/client/omnichannel/departments/DepartmentsAgentsTable.js @@ -70,7 +70,6 @@ export function RemoveAgentButton({ agentId, setAgentList, agentList }) { const onDeleteAgent = async () => { const newList = agentList.filter((listItem) => listItem.agentId !== agentId); console.log(newList); - debugger setAgentList(newList); dispatchToastMessage({ type: 'success', message: t('Agent_removed') }); setModal(); From d42faed39ca507569007a078e60a789c1e6667ca Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 21 Sep 2020 16:54:25 -0300 Subject: [PATCH 07/21] Add correct buttons and fix some errors --- .../omnichannel/departments/DepartmentEdit.js | 4 +- .../departments/DepartmentsPage.js | 41 ++++++------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/client/omnichannel/departments/DepartmentEdit.js b/client/omnichannel/departments/DepartmentEdit.js index 4184d9ade378d..59727b291591f 100644 --- a/client/omnichannel/departments/DepartmentEdit.js +++ b/client/omnichannel/departments/DepartmentEdit.js @@ -79,7 +79,7 @@ export function EditDepartment({ data, id, title }) { const { department } = data || { department: {} }; - const [tags, setTags] = useState(department && department.chatClosingTags); + const [tags, setTags] = useState((department && department.chatClosingTags) || []); const [tagsText, setTagsText] = useState(); console.log(data); @@ -88,7 +88,7 @@ export function EditDepartment({ data, id, title }) { name: (department && department.name) || '', email: (department && department.email) || '', description: (department && department.description) || '', - enabled: (department && department.enabled) || '', + enabled: (department && department.enabled) || true, maxNumberSimultaneousChat: (department && department.maxNumberSimultaneousChat) || undefined, showOnRegistration: (department && department.showOnRegistration) || true, showOnOfflineForm: (department && department.showOnOfflineForm) || true, diff --git a/client/omnichannel/departments/DepartmentsPage.js b/client/omnichannel/departments/DepartmentsPage.js index 0d4ad6187a5cd..a3995b05ed8a7 100644 --- a/client/omnichannel/departments/DepartmentsPage.js +++ b/client/omnichannel/departments/DepartmentsPage.js @@ -4,9 +4,9 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import Page from '../../components/basic/Page'; import { useTranslation } from '../../contexts/TranslationContext'; -import { useEndpointAction } from '../../hooks/useEndpointAction'; import { GenericTable } from '../../components/GenericTable'; -import { UserAutoComplete } from '../../components/basic/AutoComplete'; +import { useRoute } from '../../contexts/RouterContext'; + const FilterByText = ({ setFilter, ...props }) => { const t = useTranslation(); @@ -23,33 +23,8 @@ const FilterByText = ({ setFilter, ...props }) => { ; }; - -function AddDepartment({ reload, ...props }) { - const t = useTranslation(); - const [username, setUsername] = useState(); - - const saveAction = useEndpointAction('POST', 'livechat/users/department', { username }); - - const handleSave = useMutableCallback(async () => { - if (!username) { - return; - } - const result = await saveAction(); - if (!result.success) { - return; - } - reload(); - setUsername(); - }); - return - - - ; -} - function DepartmentsPage({ data, - reload, header, setParams, params, @@ -57,10 +32,18 @@ function DepartmentsPage({ renderRow, children, }) { + const departmentsRoute = useRoute('omnichannel-departments'); + + const onAddNew = useMutableCallback(() => departmentsRoute.push({ + context: 'new', + })); return - - + + + From bae5a2404791ba35e3eb3b1fba159154a1eff951 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 21 Sep 2020 17:05:51 -0300 Subject: [PATCH 08/21] fix import --- client/omnichannel/departments/DepartmentsAgentsTable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/omnichannel/departments/DepartmentsAgentsTable.js b/client/omnichannel/departments/DepartmentsAgentsTable.js index 937241c2dd184..9088fb30df016 100644 --- a/client/omnichannel/departments/DepartmentsAgentsTable.js +++ b/client/omnichannel/departments/DepartmentsAgentsTable.js @@ -7,7 +7,7 @@ import { Th, GenericTable } from '../../components/GenericTable'; import { useTranslation } from '../../contexts/TranslationContext'; import { useEndpointAction } from '../../hooks/useEndpointAction'; import UserAvatar from '../../components/basic/avatar/UserAvatar'; -import DeleteWarningModal from '../DeleteWarningModal'; +import DeleteWarningModal from '../../components/DeleteWarningModal'; import { useSetModal } from '../../contexts/ModalContext'; import { useToastMessageDispatch } from '../../contexts/ToastMessagesContext'; import { AutoCompleteAgent } from '../../components/basic/AutoCompleteAgent'; From a06f54a933201f375b80c2b58082a35700bce5d7 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 21 Sep 2020 17:17:43 -0300 Subject: [PATCH 09/21] Fix error with empty department agents --- client/omnichannel/departments/DepartmentsAgentsTable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/omnichannel/departments/DepartmentsAgentsTable.js b/client/omnichannel/departments/DepartmentsAgentsTable.js index 9088fb30df016..a846a8b86bf68 100644 --- a/client/omnichannel/departments/DepartmentsAgentsTable.js +++ b/client/omnichannel/departments/DepartmentsAgentsTable.js @@ -144,7 +144,7 @@ function DepartmentsAgentsTable({ agents, setAgentListFinal }) { const [data, setData] = useState({}); useMemo(() => setData({ users: agentList }), [agentList]); - useMemo(() => setAgentListFinal(agentList && agentList.users), [agentList, setAgentListFinal]); + useMemo(() => setAgentListFinal((agentList && agentList.users) || []), [agentList, setAgentListFinal]); const mediaQuery = useMediaQuery('(min-width: 1024px)'); From af468cd282806af6d9f1b98403380d2cd843a7ee Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 21 Sep 2020 17:22:25 -0300 Subject: [PATCH 10/21] fix title and tags --- client/omnichannel/departments/DepartmentEdit.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/omnichannel/departments/DepartmentEdit.js b/client/omnichannel/departments/DepartmentEdit.js index 59727b291591f..4087749a8202c 100644 --- a/client/omnichannel/departments/DepartmentEdit.js +++ b/client/omnichannel/departments/DepartmentEdit.js @@ -16,7 +16,7 @@ import DepartmentsAgentsTable from './DepartmentsAgentsTable'; import { formsSubscription } from '../additionalForms'; -export default function EditDepartmentWithData({ id, reload }) { +export default function EditDepartmentWithData({ id, reload, title }) { const t = useTranslation(); const { data, state, error } = useEndpointDataExperimental(`livechat/department/${ id }`) || {}; // const { data: userDepartments, state: userDepartmentsState, error: userDepartmentsError } = useEndpointDataExperimental(`livechat/agents/${ id }/departments`); @@ -30,7 +30,7 @@ export default function EditDepartmentWithData({ id, reload }) { return {t('User_not_found')}; } console.log(data); - return ; + return ; } // abandonedRoomsCloseCustomMessage: "fuk u" @@ -291,8 +291,8 @@ export function EditDepartment({ data, id, title }) { - - {t('Tag')} + {requestTagBeforeClosingChat && + {t('Conversation_closing_tags')} + - - {t('Enabled')} - - - - - - {t('Name')} - - - - - - {t('Description')} - - - - - - {t('Show_on_registration_page')} - - - - - - {t('Email')} - - } onChange={handleEmail} placeholder={t('Email')} /> - - - - {t('Show_on_offline_page')} - - - - - - {t('Livechat_DepartmentOfflineMessageToChannel')} - - - - - {MaxChats && } - {VisitorInactivity && } - {AbandonedMessageInput && } - {WaitingQueueMessageInput && } - {DepartmentForwarding && } - - {t('Request_tag_before_closing_chat')} - - - - - {requestTagBeforeClosingChat && - {t('Conversation_closing_tags')} - - - - - {t('Conversation_closing_tags_description')} - {tags && tags.length > 0 && - {tags.map((tag, i) => {tag})} - } - } - {DepartmentBusinessHours && } - - - {t('Agents')}: - - + + + {t('Enabled')} + + + + + + {t('Name')}* + + + + + + {t('Description')} + + + + + + {t('Show_on_registration_page')} + + + + + + {t('Email')}* + + } onChange={handleEmail} placeholder={t('Email')} /> + + + + {t('Show_on_offline_page')} + + + + + + {t('Livechat_DepartmentOfflineMessageToChannel')} + + + + + {MaxChats && } + {VisitorInactivity && } + {AbandonedMessageInput && } + {WaitingQueueMessageInput && } + {DepartmentForwarding && } + + {t('Request_tag_before_closing_chat')} + + + + + {requestTagBeforeClosingChat && + {t('Conversation_closing_tags')}* + + + + + {t('Conversation_closing_tags_description')} + {tags && tags.length > 0 && + {tags.map((tag, i) => {tag})} + } + } + {DepartmentBusinessHours && } + + + {t('Agents')}: + + + ; diff --git a/client/omnichannel/departments/DepartmentsAgentsTable.js b/client/omnichannel/departments/DepartmentsAgentsTable.js index a846a8b86bf68..ae7c6e1d9f384 100644 --- a/client/omnichannel/departments/DepartmentsAgentsTable.js +++ b/client/omnichannel/departments/DepartmentsAgentsTable.js @@ -1,6 +1,6 @@ import { useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import React, { useMemo, useCallback, useState, useLayoutEffect } from 'react'; +import React, { useCallback, useState, useLayoutEffect, useEffect } from 'react'; import { Box, Table, Icon, Button, NumberInput } from '@rocket.chat/fuselage'; import { Th, GenericTable } from '../../components/GenericTable'; @@ -19,7 +19,6 @@ function AddAgent({ agentList, setAgentList, ...props }) { const dispatchToastMessage = useToastMessageDispatch(); const handleAgent = useMutableCallback((e) => setUserId(e)); - console.log(userId); const handleSave = useMutableCallback(async () => { if (!userId) { @@ -27,11 +26,11 @@ function AddAgent({ agentList, setAgentList, ...props }) { } const { user } = await getAgent(); - if (agentList.filter((e) => e.agentId === userId).length === 0) { + if (agentList.filter((e) => e._id === user._id).length === 0) { setAgentList([user, ...agentList]); setUserId(); } else { - dispatchToastMessage({ type: 'error', message: t('Already_selected') }); + dispatchToastMessage({ type: 'error', message: t('This_agent_was_already_selected') }); } }); return @@ -43,7 +42,6 @@ function AddAgent({ agentList, setAgentList, ...props }) { function AgentsPage({ data, reload, - header, setParams, params, renderRow, @@ -51,12 +49,20 @@ function AgentsPage({ agentList, setAgentList, }) { - console.log(data); - return - + const t = useTranslation(); + + const [header] = useState(() => [ + {t('Name')}, + {t('Count')}, + {t('Order')}, + {t('Remove')}, + ]); + + return <> + {children} - ; + ; } @@ -69,7 +75,6 @@ export function RemoveAgentButton({ agentId, setAgentList, agentList }) { e.stopPropagation(); const onDeleteAgent = async () => { const newList = agentList.filter((listItem) => listItem.agentId !== agentId); - console.log(newList); setAgentList(newList); dispatchToastMessage({ type: 'success', message: t('Agent_removed') }); setModal(); @@ -78,11 +83,7 @@ export function RemoveAgentButton({ agentId, setAgentList, agentList }) { setModal( setModal()}/>); }); - return - - ; + return ; } export function Count({ agentId, setAgentList, agentList }) { @@ -92,8 +93,6 @@ export function Count({ agentId, setAgentList, agentList }) { useLayoutEffect(() => setAgentList(updatedList), [setAgentList, updatedList]); - console.log(agentCount); - const handleCount = useMutableCallback(async (e) => { const countValue = Number(e.currentTarget.value); setAgentCount(countValue); @@ -105,9 +104,7 @@ export function Count({ agentId, setAgentList, agentList }) { })); }); - return - - ; + return ; } export function Order({ agentId, setAgentList, agentList }) { @@ -117,8 +114,6 @@ export function Order({ agentId, setAgentList, agentList }) { useLayoutEffect(() => setAgentList(updatedList), [setAgentList, updatedList]); - console.log(agentOrder); - const handleOrder = useMutableCallback(async (e) => { const orderValue = Number(e.currentTarget.value); setAgentOrder(orderValue); @@ -130,31 +125,18 @@ export function Order({ agentId, setAgentList, agentList }) { })); }); - return - - ; + return ; } function DepartmentsAgentsTable({ agents, setAgentListFinal }) { - const t = useTranslation(); - - console.log(agents); - const [agentList, setAgentList] = useState(agents || []); const [data, setData] = useState({}); - useMemo(() => setData({ users: agentList }), [agentList]); - useMemo(() => setAgentListFinal((agentList && agentList.users) || []), [agentList, setAgentListFinal]); + useEffect(() => setData({ users: agentList }), [agentList]); + useEffect(() => setAgentListFinal((agentList && agentList.users) || []), [agentList, setAgentListFinal]); const mediaQuery = useMediaQuery('(min-width: 1024px)'); - const header = useMemo(() => [ - {t('Name')}, - {t('Count')}, - {t('Order')}, - {t('Remove')}, - ].filter(Boolean), [t]); - const renderRow = useCallback(({ agentId, username, name, avatarETag }) => @@ -167,14 +149,19 @@ function DepartmentsAgentsTable({ agents, setAgentListFinal }) { - - - + + + + + + + + + , [agentList, mediaQuery]); return [ {t('Name')}, From d852856f644711bc0206d811abe21a82bbef0aba Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Tue, 22 Sep 2020 11:27:44 -0300 Subject: [PATCH 15/21] Fix submit --- client/omnichannel/departments/DepartmentEdit.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/omnichannel/departments/DepartmentEdit.js b/client/omnichannel/departments/DepartmentEdit.js index 2bd70c511bab8..a51dd557f908f 100644 --- a/client/omnichannel/departments/DepartmentEdit.js +++ b/client/omnichannel/departments/DepartmentEdit.js @@ -188,7 +188,8 @@ export function EditDepartment({ data, id, title, reload }) { useComponentDidUpdate(() => setEmailError(!email ? t('The_field_is_required', 'email') : ''), [t, email]); useComponentDidUpdate(() => setTagError(requestTagBeforeClosingChat && (!tags || tags.length === 0) ? t('The_field_is_required', 'name') : ''), [t, tags]); - const handleSubmit = useMutableCallback(async () => { + const handleSubmit = useMutableCallback(async (e) => { + e.preventDefault(); let error = false; if (!name) { setNameError(t('The_field_is_required', 'name')); @@ -257,7 +258,7 @@ export function EditDepartment({ data, id, title, reload }) { - + From 60e8186385920bbfaa584d8f52af9c5d5e9ff531 Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Tue, 22 Sep 2020 12:03:32 -0300 Subject: [PATCH 16/21] Refactor table --- .../departments/DepartmentsAgentsTable.js | 97 ++++++++----------- 1 file changed, 40 insertions(+), 57 deletions(-) diff --git a/client/omnichannel/departments/DepartmentsAgentsTable.js b/client/omnichannel/departments/DepartmentsAgentsTable.js index ae7c6e1d9f384..fd1a7a18118e8 100644 --- a/client/omnichannel/departments/DepartmentsAgentsTable.js +++ b/client/omnichannel/departments/DepartmentsAgentsTable.js @@ -39,33 +39,6 @@ function AddAgent({ agentList, setAgentList, ...props }) { ; } -function AgentsPage({ - data, - reload, - setParams, - params, - renderRow, - children, - agentList, - setAgentList, -}) { - const t = useTranslation(); - - const [header] = useState(() => [ - {t('Name')}, - {t('Count')}, - {t('Order')}, - {t('Remove')}, - ]); - - return <> - - - {children} - ; -} - - export function RemoveAgentButton({ agentId, setAgentList, agentList }) { const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); @@ -128,7 +101,31 @@ export function Order({ agentId, setAgentList, agentList }) { return ; } +const AgentRow = React.memo(({ agentId, username, name, avatarETag, mediaQuery, agentList, setAgentList }) => + + + + + + {name || username} + {!mediaQuery && name && {`@${ username }`} } + + + + + + + + + + + + + +); + function DepartmentsAgentsTable({ agents, setAgentListFinal }) { + const t = useTranslation(); const [agentList, setAgentList] = useState(agents || []); const [data, setData] = useState({}); @@ -137,36 +134,22 @@ function DepartmentsAgentsTable({ agents, setAgentListFinal }) { const mediaQuery = useMediaQuery('(min-width: 1024px)'); - const renderRow = useCallback(({ agentId, username, name, avatarETag }) => - - - - - - {name || username} - {!mediaQuery && name && {`@${ username }`} } - - - - - - - - - - - - - - , [agentList, mediaQuery]); - - return - ; + return <> + + + {t('Name')} + {t('Count')} + {t('Order')} + {t('Remove')} + } + results={data && (data.users || data.agents)} + total={data && data.total} + pi='x24' + > + {({ props }) => } + + ; } export default DepartmentsAgentsTable; From a243b084c919b919f5cb3dfb3e1934fb03b48d63 Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Tue, 22 Sep 2020 14:29:29 -0300 Subject: [PATCH 17/21] Fix agent list editing --- .../omnichannel/departments/DepartmentEdit.js | 63 +++---------------- .../departments/DepartmentsAgentsTable.js | 28 +++------ 2 files changed, 18 insertions(+), 73 deletions(-) diff --git a/client/omnichannel/departments/DepartmentEdit.js b/client/omnichannel/departments/DepartmentEdit.js index a51dd557f908f..0a4d65fa50fcd 100644 --- a/client/omnichannel/departments/DepartmentEdit.js +++ b/client/omnichannel/departments/DepartmentEdit.js @@ -1,5 +1,5 @@ /* eslint-disable complexity */ -import React, { useMemo, useState } from 'react'; +import React, { useMemo, useState, useRef } from 'react'; import { FieldGroup, Field, TextInput, Chip, SelectFiltered, Box, Icon, Divider, ToggleSwitch, TextAreaInput, ButtonGroup, Button } from '@rocket.chat/fuselage'; import { useMutableCallback, useUniqueId } from '@rocket.chat/fuselage-hooks'; import { useSubscription } from 'use-subscription'; @@ -21,8 +21,6 @@ import { useComponentDidUpdate } from '../../hooks/useComponentDidUpdate'; export default function EditDepartmentWithData({ id, reload, title }) { const t = useTranslation(); const { data, state, error } = useEndpointDataExperimental(`livechat/department/${ id }`) || {}; - // const { data: userDepartments, state: userDepartmentsState, error: userDepartmentsError } = useEndpointDataExperimental(`livechat/agents/${ id }/departments`); - // const { data: availableDepartments, state: availableDepartmentsState, error: availableDepartmentsError } = useEndpointDataExperimental('livechat/department'); if ([state].includes(ENDPOINT_STATES.LOADING)) { return ; @@ -34,33 +32,13 @@ export default function EditDepartmentWithData({ id, reload, title }) { return ; } -// abandonedRoomsCloseCustomMessage: "fuk u" -// chatClosingTags: ["asd"] -// departmentsAllowedToForward: "" -// description: "Binus is gud" -// email: "asd@asd.com" -// enabled: true -// maxNumberSimultaneousChat: "3" -// name: "binus" -// numAgents: 2 -// offlineMessageChannelName: "" -// requestTagBeforeClosingChat: false -// showOnOfflineForm: true -// showOnRegistration: true -// visitorInactivityTimeoutInSeconds: "12" -// waitingQueueMessage: "Wait for binus" -// _id: "xmF2DLvaLfgorggK5" -// _updatedAt: "2020-08-24T21:22:38.276Z" - -// rooms.autocomplete.channelAndPrivate - const useQuery = ({ name }) => useMemo(() => ({ selector: JSON.stringify({ name }) }), [name]); export function EditDepartment({ data, id, title, reload }) { const t = useTranslation(); const agentsRoute = useRoute('omnichannel-departments'); const eeForms = useSubscription(formsSubscription); - const initialAgents = (data && data.agents) || []; + const initialAgents = useRef((data && data.agents) || []); const { useEeNumberInput = () => {}, @@ -143,29 +121,6 @@ export function EditDepartment({ data, id, title, reload }) { const handleTagTextChange = useMutableCallback((e) => { setTagsText(e.target.value); }); - // const defaultValidations = { - // enabled: Boolean, - // name: String, - // description: Match.Optional(String), - // showOnRegistration: Boolean, - // email: String, - // showOnOfflineForm: Boolean, - // requestTagBeforeClosingChat: Match.Optional(Boolean), - // chatClosingTags: Match.Optional([String]), - // }; - - // // The Livechat Form department support addition/custom fields, so those fields need to be added before validating - // Object.keys(departmentData).forEach((field) => { - // if (!defaultValidations.hasOwnProperty(field)) { - // defaultValidations[field] = Match.OneOf(String, Match.Integer, Boolean); - // } - // }); - - // check(departmentData, defaultValidations); - // check(departmentAgents, Match.Maybe({ - // upsert: Match.Maybe(Array), - // remove: Match.Maybe(Array), - // })); const query = useQuery({ offlineMessageChannelName }); @@ -225,14 +180,12 @@ export function EditDepartment({ data, id, title, reload }) { departmentsAllowedToForward: departmentsAllowedToForward && departmentsAllowedToForward[0], }; - const finalAgentList = agentList.map((agent) => { - agent.agentId = agent._id; - return agent; - }); - const agentListPayload = { - upsert: finalAgentList.filter((agent) => !initialAgents.some((initialAgent) => initialAgent._id === agent._id)), - remove: initialAgents.filter((initialAgent) => !finalAgentList.some((agent) => initialAgent._id === agent._id)), + upsert: agentList.filter((agent) => !initialAgents.current.some((initialAgent) => initialAgent._id === agent._id + && agent.count === initialAgent.count + && agent.order === initialAgent.order, + )), + remove: initialAgents.current.filter((initialAgent) => !agentList.some((agent) => initialAgent._id === agent._id)), }; try { @@ -240,7 +193,7 @@ export function EditDepartment({ data, id, title, reload }) { await saveDepartmentInfo(id, payload, []); await saveDepartmentAgentsInfoOnEdit(agentListPayload); } else { - await saveDepartmentInfo(id, payload, finalAgentList); + await saveDepartmentInfo(id, payload, agentList); } dispatchToastMessage({ type: 'success', message: t('saved') }); reload(); diff --git a/client/omnichannel/departments/DepartmentsAgentsTable.js b/client/omnichannel/departments/DepartmentsAgentsTable.js index fd1a7a18118e8..8f62ac635bafa 100644 --- a/client/omnichannel/departments/DepartmentsAgentsTable.js +++ b/client/omnichannel/departments/DepartmentsAgentsTable.js @@ -1,6 +1,6 @@ import { useMediaQuery, useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import React, { useCallback, useState, useLayoutEffect, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import { Box, Table, Icon, Button, NumberInput } from '@rocket.chat/fuselage'; import { Th, GenericTable } from '../../components/GenericTable'; @@ -26,8 +26,8 @@ function AddAgent({ agentList, setAgentList, ...props }) { } const { user } = await getAgent(); - if (agentList.filter((e) => e._id === user._id).length === 0) { - setAgentList([user, ...agentList]); + if (agentList.filter((e) => e.agentId === user._id).length === 0) { + setAgentList([{ ...user, agentId: user._id }, ...agentList]); setUserId(); } else { dispatchToastMessage({ type: 'error', message: t('This_agent_was_already_selected') }); @@ -62,14 +62,11 @@ export function RemoveAgentButton({ agentId, setAgentList, agentList }) { export function Count({ agentId, setAgentList, agentList }) { const t = useTranslation(); const [agentCount, setAgentCount] = useState(agentList.find((agent) => agent.agentId === agentId).count || 0); - const [updatedList, setUpdatedList] = useState(agentList); - - useLayoutEffect(() => setAgentList(updatedList), [setAgentList, updatedList]); const handleCount = useMutableCallback(async (e) => { const countValue = Number(e.currentTarget.value); setAgentCount(countValue); - setUpdatedList(agentList.map((agent) => { + setAgentList(agentList.map((agent) => { if (agent.agentId === agentId) { agent.count = countValue; } @@ -83,14 +80,11 @@ export function Count({ agentId, setAgentList, agentList }) { export function Order({ agentId, setAgentList, agentList }) { const t = useTranslation(); const [agentOrder, setAgentOrder] = useState(agentList.find((agent) => agent.agentId === agentId).order || 0); - const [updatedList, setUpdatedList] = useState(agentList); - - useLayoutEffect(() => setAgentList(updatedList), [setAgentList, updatedList]); const handleOrder = useMutableCallback(async (e) => { const orderValue = Number(e.currentTarget.value); setAgentOrder(orderValue); - setUpdatedList(agentList.map((agent) => { + setAgentList(agentList.map((agent) => { if (agent.agentId === agentId) { agent.order = orderValue; } @@ -126,11 +120,9 @@ const AgentRow = React.memo(({ agentId, username, name, avatarETag, mediaQuery, function DepartmentsAgentsTable({ agents, setAgentListFinal }) { const t = useTranslation(); - const [agentList, setAgentList] = useState(agents || []); - const [data, setData] = useState({}); + const [agentList, setAgentList] = useState((agents && JSON.parse(JSON.stringify(agents))) || []); - useEffect(() => setData({ users: agentList }), [agentList]); - useEffect(() => setAgentListFinal((agentList && agentList.users) || []), [agentList, setAgentListFinal]); + useEffect(() => setAgentListFinal(agentList), [agentList, setAgentListFinal]); const mediaQuery = useMediaQuery('(min-width: 1024px)'); @@ -143,11 +135,11 @@ function DepartmentsAgentsTable({ agents, setAgentListFinal }) { {t('Order')} {t('Remove')} } - results={data && (data.users || data.agents)} - total={data && data.total} + results={agentList} + total={agentList?.length} pi='x24' > - {({ props }) => } + {(props) => } ; } From 72155dd1d51cb6a59135c6c266993e18aab345e8 Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Tue, 22 Sep 2020 15:28:55 -0300 Subject: [PATCH 18/21] Fix width, added back button --- .../omnichannel/departments/DepartmentEdit.js | 49 ++++++++++++------- .../departments/DepartmentsRoute.js | 1 + 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/client/omnichannel/departments/DepartmentEdit.js b/client/omnichannel/departments/DepartmentEdit.js index 0a4d65fa50fcd..11239c0733741 100644 --- a/client/omnichannel/departments/DepartmentEdit.js +++ b/client/omnichannel/departments/DepartmentEdit.js @@ -40,6 +40,8 @@ export function EditDepartment({ data, id, title, reload }) { const eeForms = useSubscription(formsSubscription); const initialAgents = useRef((data && data.agents) || []); + const router = useRoute('omnichannel-departments'); + const { useEeNumberInput = () => {}, useEeTextInput = () => {}, @@ -203,6 +205,10 @@ export function EditDepartment({ data, id, title, reload }) { } }); + const handleReturn = useMutableCallback(() => { + router.push({}); + }); + const invalidForm = !name || !email || (requestTagBeforeClosingChat && (!tags || tags.length === 0)); const formId = useUniqueId(); @@ -211,16 +217,19 @@ export function EditDepartment({ data, id, title, reload }) { + - + - {t('Enabled')} - - - + + {t('Enabled')} + + + + {t('Name')}* @@ -235,10 +244,12 @@ export function EditDepartment({ data, id, title, reload }) { - {t('Show_on_registration_page')} - - - + + {t('Show_on_registration_page')} + + + + {t('Email')}* @@ -247,10 +258,12 @@ export function EditDepartment({ data, id, title, reload }) { - {t('Show_on_offline_page')} - - - + + {t('Show_on_offline_page')} + + + + {t('Livechat_DepartmentOfflineMessageToChannel')} @@ -264,10 +277,12 @@ export function EditDepartment({ data, id, title, reload }) { {WaitingQueueMessageInput && } {DepartmentForwarding && } - {t('Request_tag_before_closing_chat')} - - - + + {t('Request_tag_before_closing_chat')} + + + + {requestTagBeforeClosingChat && {t('Conversation_closing_tags')}* diff --git a/client/omnichannel/departments/DepartmentsRoute.js b/client/omnichannel/departments/DepartmentsRoute.js index 947b4ac6cfd33..6b2e694cd2634 100644 --- a/client/omnichannel/departments/DepartmentsRoute.js +++ b/client/omnichannel/departments/DepartmentsRoute.js @@ -18,6 +18,7 @@ export function RemoveDepartmentButton({ _id, reload }) { const handleRemoveClick = useMutableCallback(async (e) => { e.preventDefault(); + e.stopPropagation(); const result = await deleteAction(); if (result.success === true) { reload(); From 98464880f479ca98628af4bf6695f7ac3a91f504 Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Tue, 22 Sep 2020 15:32:53 -0300 Subject: [PATCH 19/21] Fix width --- client/omnichannel/departments/DepartmentEdit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/omnichannel/departments/DepartmentEdit.js b/client/omnichannel/departments/DepartmentEdit.js index 11239c0733741..0d1911394e953 100644 --- a/client/omnichannel/departments/DepartmentEdit.js +++ b/client/omnichannel/departments/DepartmentEdit.js @@ -222,7 +222,7 @@ export function EditDepartment({ data, id, title, reload }) { - + {t('Enabled')} From 7c5413f0780151dedb9fcab8afeeaab23603826b Mon Sep 17 00:00:00 2001 From: Gabriel Henriques Date: Tue, 22 Sep 2020 15:37:20 -0300 Subject: [PATCH 20/21] fix numAgents --- client/omnichannel/departments/DepartmentsRoute.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/omnichannel/departments/DepartmentsRoute.js b/client/omnichannel/departments/DepartmentsRoute.js index 6b2e694cd2634..1a6dd222b5469 100644 --- a/client/omnichannel/departments/DepartmentsRoute.js +++ b/client/omnichannel/departments/DepartmentsRoute.js @@ -72,16 +72,16 @@ function DepartmentsRoute() { const header = useMemo(() => [ {t('Name')}, {t('Description')}, - {t('Num_Departments')}, + {t('Num_Agents')}, {t('Enabled')}, {t('Show_on_registration_page')}, {t('Remove')}, ].filter(Boolean), [sort, onHeaderClick, t]); - const renderRow = useCallback(({ name, _id, description, numDepartments, enabled, showOnRegistration }) => + const renderRow = useCallback(({ name, _id, description, numAgents, enabled, showOnRegistration }) => {name} {description} - {numDepartments || '0'} + {numAgents || '0'} {enabled ? t('Yes') : t('No')} {showOnRegistration ? t('Yes') : t('No')} From 5f6bcc7427046488076a89ef18ec8a7a639dfc5d Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 22 Sep 2020 17:24:32 -0300 Subject: [PATCH 21/21] remove old files --- app/livechat/client/views/admin.js | 2 - .../views/app/livechatDepartmentForm.html | 223 ----------- .../views/app/livechatDepartmentForm.js | 367 ------------------ .../client/views/app/livechatDepartments.html | 59 --- .../client/views/app/livechatDepartments.js | 111 ------ .../livechatDepartmentCustomFieldsForm.html | 65 ---- .../livechatDepartmentCustomFieldsForm.js | 72 ---- .../views/app/registerCustomTemplates.js | 1 - 8 files changed, 900 deletions(-) delete mode 100644 app/livechat/client/views/app/livechatDepartmentForm.html delete mode 100644 app/livechat/client/views/app/livechatDepartmentForm.js delete mode 100644 app/livechat/client/views/app/livechatDepartments.html delete mode 100644 app/livechat/client/views/app/livechatDepartments.js delete mode 100644 ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.html delete mode 100644 ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.js diff --git a/app/livechat/client/views/admin.js b/app/livechat/client/views/admin.js index 78a7e39c94d51..71d284db22688 100644 --- a/app/livechat/client/views/admin.js +++ b/app/livechat/client/views/admin.js @@ -1,3 +1 @@ import './app/livechatDashboard.html'; -import './app/livechatDepartmentForm'; -import './app/livechatDepartments'; diff --git a/app/livechat/client/views/app/livechatDepartmentForm.html b/app/livechat/client/views/app/livechatDepartmentForm.html deleted file mode 100644 index d846f4e129ca9..0000000000000 --- a/app/livechat/client/views/app/livechatDepartmentForm.html +++ /dev/null @@ -1,223 +0,0 @@ - diff --git a/app/livechat/client/views/app/livechatDepartmentForm.js b/app/livechat/client/views/app/livechatDepartmentForm.js deleted file mode 100644 index cb4c8f5e0cb4c..0000000000000 --- a/app/livechat/client/views/app/livechatDepartmentForm.js +++ /dev/null @@ -1,367 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { FlowRouter } from 'meteor/kadira:flow-router'; -import { Template } from 'meteor/templating'; -import _ from 'underscore'; -import toastr from 'toastr'; - -import { TabBar, RocketChatTabBar } from '../../../../ui-utils'; -import { t, handleError } from '../../../../utils'; -import { hasPermission } from '../../../../authorization'; -import { getCustomFormTemplate } from './customTemplates/register'; -import './livechatDepartmentForm.html'; -import { APIClient, roomTypes } from '../../../../utils/client'; - -const LIST_SIZE = 50; - -const saveDepartmentsAgents = async (_id, instance) => { - const upsert = [...instance.agentsToUpsert.values()]; - const remove = [...instance.agentsToRemove.values()]; - if (!upsert.length && !remove.length) { - return; - } - return APIClient.v1.post(`livechat/department/${ _id }/agents`, { - upsert, - remove, - }); -}; - -Template.livechatDepartmentForm.helpers({ - department() { - return Template.instance().department.get(); - }, - agents() { - return Template.instance().department && !_.isEmpty(Template.instance().department.get()) ? Template.instance().department.get().agents : []; - }, - departmentAgents() { - return _.sortBy(Template.instance().departmentAgents.get(), 'username'); - }, - showOnRegistration(value) { - const department = Template.instance().department.get(); - return department.showOnRegistration === value || (department.showOnRegistration === undefined && value === true); - }, - showOnOfflineForm(value) { - const department = Template.instance().department.get(); - return department.showOnOfflineForm === value || (department.showOnOfflineForm === undefined && value === true); - }, - requestTagBeforeClosingChat() { - const department = Template.instance().department.get(); - return !!(department && department.requestTagBeforeClosingChat); - }, - customFieldsTemplate() { - return getCustomFormTemplate('livechatDepartmentForm'); - }, - data() { - return { id: FlowRouter.getParam('_id') }; - }, - exceptionsAgents() { - return _.pluck(Template.instance().departmentAgents.get(), 'username'); - }, - agentModifier() { - return (filter, text = '') => { - const f = filter.get(); - return `@${ - f.length === 0 - ? text - : text.replace( - new RegExp(filter.get()), - (part) => `${ part }`, - ) - }`; - }; - }, - agentConditions() { - return { roles: 'livechat-agent' }; - }, - onSelectAgents() { - return Template.instance().onSelectAgents; - }, - selectedAgents() { - return Template.instance().selectedAgents.get(); - }, - onClickTagAgents() { - return Template.instance().onClickTagAgents; - }, - flexData() { - return { - tabBar: Template.instance().tabBar, - data: Template.instance().tabBarData.get(), - }; - }, - tabBarVisible() { - return Object.values(TabBar.buttons.get()) - .some((button) => button.groups - .some((group) => group.startsWith('livechat-department'))); - }, - chatClosingTags() { - return Template.instance().chatClosingTags.get(); - }, - availableDepartmentTags() { - return Template.instance().availableDepartmentTags.get(); - }, - hasAvailableTags() { - return [...Template.instance().availableTags.get()].length > 0; - }, - hasChatClosingTags() { - return [...Template.instance().chatClosingTags.get()].length > 0; - }, - onTableScroll() { - const instance = Template.instance(); - return function(currentTarget) { - if (currentTarget.offsetHeight + currentTarget.scrollTop < currentTarget.scrollHeight - 100) { - return; - } - const agents = instance.departmentAgents.get(); - if (instance.total.get() > agents.length) { - instance.offset.set(instance.offset.get() + LIST_SIZE); - } - }; - }, - onClickTagOfflineMessageChannel() { - return Template.instance().onClickTagOfflineMessageChannel; - }, - selectedOfflineMessageChannel() { - return Template.instance().offlineMessageChannel.get(); - }, - onSelectOfflineMessageChannel() { - return Template.instance().onSelectOfflineMessageChannel; - }, - offlineMessageChannelModifier() { - return (filter, text = '') => { - const f = filter.get(); - return `#${ f.length === 0 ? text : text.replace(new RegExp(filter.get()), (part) => `${ part }`) }`; - }; - }, - channelSelector() { - return (expression) => ({ name: expression }); - }, -}); - -Template.livechatDepartmentForm.events({ - 'submit #department-form'(e, instance) { - e.preventDefault(); - const $btn = instance.$('button.save'); - - let departmentData; - - const _id = $(e.currentTarget).data('id'); - - if (hasPermission('manage-livechat-departments')) { - const enabled = instance.$('input[name=enabled]:checked').val(); - const name = instance.$('input[name=name]').val(); - const description = instance.$('textarea[name=description]').val(); - const showOnRegistration = instance.$('input[name=showOnRegistration]:checked').val(); - const email = instance.$('input[name=email]').val(); - const showOnOfflineForm = instance.$('input[name=showOnOfflineForm]:checked').val(); - const requestTagBeforeClosingChat = instance.$('input[name=requestTagBeforeClosingChat]:checked').val(); - const chatClosingTags = instance.chatClosingTags.get(); - const [offlineMessageChannel] = instance.offlineMessageChannel.get(); - const offlineMessageChannelName = (offlineMessageChannel && roomTypes.getRoomName(offlineMessageChannel.t, offlineMessageChannel)) || ''; - - if (enabled !== '1' && enabled !== '0') { - return toastr.error(t('Please_select_enabled_yes_or_no')); - } - - if (name.trim() === '') { - return toastr.error(t('Please_fill_a_name')); - } - - if (email.trim() === '' && showOnOfflineForm === '1') { - return toastr.error(t('Please_fill_an_email')); - } - - departmentData = { - enabled: enabled === '1', - name: name.trim(), - description: description.trim(), - showOnRegistration: showOnRegistration === '1', - showOnOfflineForm: showOnOfflineForm === '1', - requestTagBeforeClosingChat: requestTagBeforeClosingChat === '1', - email: email.trim(), - chatClosingTags, - offlineMessageChannelName, - }; - } - - const oldBtnValue = $btn.html(); - $btn.html(t('Saving')); - - instance.$('.customFormField').each((i, el) => { - const elField = instance.$(el); - const name = elField.attr('name'); - departmentData[name] = elField.val(); - }); - - if (hasPermission('manage-livechat-departments')) { - Meteor.call('livechat:saveDepartment', _id, departmentData, [], async function(err, result) { - $btn.html(oldBtnValue); - if (err) { - return handleError(err); - } - - await saveDepartmentsAgents(result._id, instance); - toastr.success(t('Saved')); - FlowRouter.go('livechat-departments'); - }); - } else if (hasPermission('add-livechat-department-agents')) { - saveDepartmentsAgents(_id, instance); - } else { - throw new Error(t('error-not-authorized')); - } - }, - - 'click .add-agent'(e, instance) { - e.preventDefault(); - - const users = instance.selectedAgents.get(); - users.forEach(async (user) => { - const { _id, username } = user; - - const departmentAgents = instance.departmentAgents.get(); - if (departmentAgents.find(({ agentId }) => agentId === _id)) { - return toastr.error(t('This_agent_was_already_selected')); - } - const newAgent = _.clone(user); - newAgent.agentId = _id; - delete newAgent._id; - if (instance.agentsToRemove.has(newAgent.agentId)) { - instance.agentsToRemove.delete(newAgent.agentId); - } - instance.agentsToUpsert.set(newAgent.agentId, { ...newAgent, count: 0, order: 0 }); - departmentAgents.push(newAgent); - instance.departmentAgents.set(departmentAgents); - instance.selectedAgents.set(instance.selectedAgents.get().filter((user) => user.username !== username)); - }); - }, - - 'click button.back'(e/* , instance*/) { - e.preventDefault(); - FlowRouter.go('livechat-departments'); - }, - - 'click .remove-agent'(e, instance) { - e.preventDefault(); - if (instance.agentsToUpsert.has(this.agentId)) { - instance.agentsToUpsert.delete(this.agentId); - } - instance.agentsToRemove.set(this.agentId, this); - - instance.departmentAgents.set(instance.departmentAgents.get().filter((agent) => agent.agentId !== this.agentId)); - }, - - 'keyup .count'(event, instance) { - const agent = instance.agentsToUpsert.get(this.agentId) || this; - instance.agentsToUpsert.set(this.agentId, { ...agent, count: parseInt(event.currentTarget.value) || 0 }); - }, - - 'keyup .order'(event, instance) { - const agent = instance.agentsToUpsert.get(this.agentId) || this; - instance.agentsToUpsert.set(this.agentId, { ...agent, order: parseInt(event.currentTarget.value) || 0 }); - }, - - 'click #addTag'(e, instance) { - e.stopPropagation(); - e.preventDefault(); - - const isSelect = [...instance.availableTags.get()].length > 0; - const elId = isSelect ? '#tagSelect' : '#tagInput'; - const elDefault = isSelect ? 'placeholder' : ''; - - const tag = $(elId).val(); - const chatClosingTags = [...instance.chatClosingTags.get()]; - if (tag === '' || chatClosingTags.indexOf(tag) > -1) { - return; - } - - chatClosingTags.push(tag); - instance.chatClosingTags.set(chatClosingTags); - $(elId).val(elDefault); - }, - - 'click .remove-tag'(e, instance) { - e.stopPropagation(); - e.preventDefault(); - - const chatClosingTags = [...instance.chatClosingTags.get()].filter((el) => el !== this.valueOf()); - instance.chatClosingTags.set(chatClosingTags); - }, -}); - -Template.livechatDepartmentForm.onCreated(async function() { - this.agentsToUpsert = new Map(); - this.agentsToRemove = new Map(); - this.department = new ReactiveVar({ enabled: true }); - this.departmentAgents = new ReactiveVar([]); - this.selectedAgents = new ReactiveVar([]); - this.tabBar = new RocketChatTabBar(); - this.tabBar.showGroup(FlowRouter.current().route.name); - this.tabBarData = new ReactiveVar(); - this.chatClosingTags = new ReactiveVar([]); - this.availableTags = new ReactiveVar([]); - this.availableDepartmentTags = new ReactiveVar([]); - this.offset = new ReactiveVar(0); - this.total = new ReactiveVar(0); - this.offlineMessageChannel = new ReactiveVar([]); - - - this.onClickTagOfflineMessageChannel = () => { - this.offlineMessageChannel.set([]); - }; - - this.onSelectOfflineMessageChannel = async ({ item }) => { - const { room } = await APIClient.v1.get(`rooms.info?roomId=${ item._id }`); - room.text = room.name; - this.offlineMessageChannel.set([room]); - }; - this.onSelectAgents = ({ item: agent }) => { - this.selectedAgents.set([agent]); - }; - - this.onClickTagAgents = ({ username }) => { - this.selectedAgents.set(this.selectedAgents.get().filter((user) => user.username !== username)); - }; - - this.loadAvailableTags = (departmentId) => { - Meteor.call('livechat:getTagsList', (err, tagsList) => { - this.availableTags.set(tagsList || []); - const tags = this.availableTags.get(); - const availableTags = tags - .filter(({ departments }) => departments.length === 0 || departments.indexOf(departmentId) > -1) - .map(({ name }) => name); - this.availableDepartmentTags.set(availableTags); - }); - }; - this.autorun(async () => { - const offset = this.offset.get(); - const { agents, total } = await APIClient.v1.get(`livechat/department/${ FlowRouter.getParam('_id') }/agents?count=${ LIST_SIZE }&offset=${ offset }`); - this.total.set(total); - if (offset === 0) { - this.departmentAgents.set(agents); - } else { - this.departmentAgents.set(this.departmentAgents.get().concat(agents)); - } - }); - - this.autorun(async () => { - const id = FlowRouter.getParam('_id'); - if (id) { - const { department } = await APIClient.v1.get(`livechat/department/${ FlowRouter.getParam('_id') }?includeAgents=false`); - this.department.set(department); - this.chatClosingTags.set((department && department.chatClosingTags) || []); - this.loadAvailableTags(id); - } - }); - - this.autorun(async () => { - const department = this.department.get(); - let offlineChannel = []; - if (department?.offlineMessageChannelName) { - const { room } = await APIClient.v1.get(`rooms.info?roomName=${ department?.offlineMessageChannelName }`); - if (room) { - room.text = room.name; - offlineChannel = [{ ...room }]; - } - } - this.offlineMessageChannel.set(offlineChannel); - }); -}); diff --git a/app/livechat/client/views/app/livechatDepartments.html b/app/livechat/client/views/app/livechatDepartments.html deleted file mode 100644 index cb3963f6076cf..0000000000000 --- a/app/livechat/client/views/app/livechatDepartments.html +++ /dev/null @@ -1,59 +0,0 @@ - diff --git a/app/livechat/client/views/app/livechatDepartments.js b/app/livechat/client/views/app/livechatDepartments.js deleted file mode 100644 index 97b50f7516033..0000000000000 --- a/app/livechat/client/views/app/livechatDepartments.js +++ /dev/null @@ -1,111 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { FlowRouter } from 'meteor/kadira:flow-router'; -import { Template } from 'meteor/templating'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { ReactiveDict } from 'meteor/reactive-dict'; -import _ from 'underscore'; - -import { modal } from '../../../../ui-utils'; -import { t, handleError } from '../../../../utils'; -import './livechatDepartments.html'; -import { APIClient } from '../../../../utils/client'; - -Template.livechatDepartments.helpers({ - departments() { - return Template.instance().departments.get(); - }, - isLoading() { - return Template.instance().state.get('loading'); - }, - isReady() { - const instance = Template.instance(); - return instance.ready && instance.ready.get(); - }, - onTableScroll() { - const instance = Template.instance(); - return function(currentTarget) { - if ( - currentTarget.offsetHeight + currentTarget.scrollTop - >= currentTarget.scrollHeight - 100 - ) { - return instance.limit.set(instance.limit.get() + 50); - } - }; - }, -}); - -const DEBOUNCE_TIME_FOR_SEARCH_DEPARTMENTS_IN_MS = 300; - -Template.livechatDepartments.events({ - 'click .remove-department'(e, instance) { - e.preventDefault(); - e.stopPropagation(); - - modal.open({ - title: t('Are_you_sure'), - type: 'warning', - showCancelButton: true, - confirmButtonColor: '#DD6B55', - confirmButtonText: t('Yes'), - cancelButtonText: t('Cancel'), - closeOnConfirm: false, - html: false, - }, () => { - Meteor.call('livechat:removeDepartment', this._id, (error/* , result*/) => { - if (error) { - return handleError(error); - } - instance.departments.set(instance.departments.curValue.filter((department) => department._id !== this._id)); - modal.open({ - title: t('Removed'), - text: t('Department_removed'), - type: 'success', - timer: 1000, - showConfirmButton: false, - }); - }); - }); - }, - - 'click .department-info'(e/* , instance*/) { - e.preventDefault(); - FlowRouter.go('livechat-department-edit', { _id: this._id }); - }, - - 'keydown #departments-filter'(e) { - if (e.which === 13) { - e.stopPropagation(); - e.preventDefault(); - } - }, - 'keyup #departments-filter': _.debounce((e, t) => { - e.stopPropagation(); - e.preventDefault(); - t.filter.set(e.currentTarget.value); - }, DEBOUNCE_TIME_FOR_SEARCH_DEPARTMENTS_IN_MS), -}); - -Template.livechatDepartments.onCreated(function() { - const instance = this; - this.limit = new ReactiveVar(50); - this.filter = new ReactiveVar(''); - this.state = new ReactiveDict({ - loading: false, - }); - this.ready = new ReactiveVar(true); - this.departments = new ReactiveVar([]); - - this.autorun(async function() { - const limit = instance.limit.get(); - const filter = instance.filter.get(); - let baseUrl = `livechat/department?count=${ limit }`; - - if (filter) { - baseUrl += `&text=${ encodeURIComponent(filter) }`; - } - - const { departments } = await APIClient.v1.get(baseUrl); - instance.departments.set(departments); - instance.ready.set(true); - }); -}); diff --git a/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.html b/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.html deleted file mode 100644 index 0938352ada185..0000000000000 --- a/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.html +++ /dev/null @@ -1,65 +0,0 @@ - diff --git a/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.js b/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.js deleted file mode 100644 index fbe1ac0b7b895..0000000000000 --- a/ee/app/livechat-enterprise/client/views/app/customTemplates/livechatDepartmentCustomFieldsForm.js +++ /dev/null @@ -1,72 +0,0 @@ -import { ReactiveVar } from 'meteor/reactive-var'; -import { Template } from 'meteor/templating'; - -import { APIClient, mountArrayQueryParameters } from '../../../../../../../app/utils/client'; -import './livechatDepartmentCustomFieldsForm.html'; -import { LivechatBusinessHourTypes } from '../../../../../../../definition/ILivechatBusinessHour'; - -Template.livechatDepartmentCustomFieldsForm.helpers({ - department() { - return Template.instance().department.get(); - }, - departmentModifier() { - return (filter, text = '') => { - const f = filter.get(); - return `${ f.length === 0 ? text : text.replace(new RegExp(filter.get(), 'i'), (part) => `${ part }`) }`; - }; - }, - onClickTagDepartment() { - return Template.instance().onClickTagDepartment; - }, - selectedDepartments() { - return Template.instance().selectedDepartments.get(); - }, - selectedDepartmentsIds() { - return Template.instance().selectedDepartments.get().map((dept) => dept._id); - }, - onSelectDepartments() { - return Template.instance().onSelectDepartments; - }, - exceptionsDepartments() { - const department = Template.instance().department.get(); - return [department && department._id, ...Template.instance().selectedDepartments.get().map((dept) => dept._id)]; - }, - businessHourName() { - const businessHour = Template.instance().businessHour.get(); - return businessHour?.name; - }, -}); - -Template.livechatDepartmentCustomFieldsForm.onCreated(function() { - this.businessHour = new ReactiveVar({}); - this.selectedDepartments = new ReactiveVar([]); - const { id: _id, department: contextDepartment } = this.data; - - this.department = new ReactiveVar(contextDepartment); - this.onSelectDepartments = ({ item: department }) => { - department.text = department.name; - this.selectedDepartments.set(this.selectedDepartments.get().concat(department)); - }; - - this.onClickTagDepartment = (department) => { - this.selectedDepartments.set(this.selectedDepartments.get().filter((dept) => dept._id !== department._id)); - }; - - if (!contextDepartment && _id) { - this.autorun(async () => { - const { department } = await APIClient.v1.get(`livechat/department/${ _id }?includeAgents=false`); - if (department.departmentsAllowedToForward) { - const { departments } = await APIClient.v1.get(`livechat/department.listByIds?${ mountArrayQueryParameters('ids', department.departmentsAllowedToForward) }&fields=${ JSON.stringify({ fields: { name: 1 } }) }`); - this.selectedDepartments.set(departments.map((dept) => ({ - _id: dept._id, - text: dept.name, - }))); - } - if (department.businessHourId) { - const { businessHour } = await APIClient.v1.get(`livechat/business-hour?_id=${ department.businessHourId }&type=${ LivechatBusinessHourTypes.CUSTOM }`); - this.businessHour.set(businessHour); - } - this.department.set(department); - }); - } -}); diff --git a/ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js b/ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js index 1b693862b45bc..50aa6c187cfae 100644 --- a/ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js +++ b/ee/app/livechat-enterprise/client/views/app/registerCustomTemplates.js @@ -1,5 +1,4 @@ import { addCustomFormTemplate } from '../../../../../../app/livechat/client/views/app/customTemplates/register'; -import './customTemplates/livechatDepartmentCustomFieldsForm'; import './customTemplates/visitorEditCustomFieldsForm'; import './customTemplates/visitorInfoCustomForm';