diff --git a/packages/rocketchat-livechat/client/collections/AgentUsers.js b/packages/rocketchat-livechat/client/collections/AgentUsers.js new file mode 100644 index 000000000000..e571fad61d2b --- /dev/null +++ b/packages/rocketchat-livechat/client/collections/AgentUsers.js @@ -0,0 +1 @@ +this.AgentUsers = new Mongo.Collection('agentUsers'); diff --git a/packages/rocketchat-livechat/client/lib/LivechatDepartment.js b/packages/rocketchat-livechat/client/collections/LivechatDepartment.js similarity index 100% rename from packages/rocketchat-livechat/client/lib/LivechatDepartment.js rename to packages/rocketchat-livechat/client/collections/LivechatDepartment.js diff --git a/packages/rocketchat-livechat/client/collections/LivechatDepartmentAgents.js b/packages/rocketchat-livechat/client/collections/LivechatDepartmentAgents.js new file mode 100644 index 000000000000..08ea1741134b --- /dev/null +++ b/packages/rocketchat-livechat/client/collections/LivechatDepartmentAgents.js @@ -0,0 +1 @@ +this.LivechatDepartmentAgents = new Mongo.Collection('rocketchat_livechat_department_agents'); diff --git a/packages/rocketchat-livechat/client/lib/LivechatTrigger.js b/packages/rocketchat-livechat/client/collections/LivechatTrigger.js similarity index 100% rename from packages/rocketchat-livechat/client/lib/LivechatTrigger.js rename to packages/rocketchat-livechat/client/collections/LivechatTrigger.js diff --git a/packages/rocketchat-livechat/client/stylesheets/livechat.less b/packages/rocketchat-livechat/client/stylesheets/livechat.less index dc12fb153ec4..b389c38d398b 100644 --- a/packages/rocketchat-livechat/client/stylesheets/livechat.less +++ b/packages/rocketchat-livechat/client/stylesheets/livechat.less @@ -411,3 +411,29 @@ } } } + +.department-agents { + list-style-type: none; + + li { + display: inline-block; + background-color: #DDD; + border-radius: 10px; + padding: 2px 8px 2px 2px; + margin: 1px 0; + cursor: pointer; + + .icon-plus-circled { + opacity: 0.5; + font-size: 0.8rem; + } + } +} + +.agent-info { + input[type='text'] { + width: auto; + line-height: 24px; + height: 24px; + } +} diff --git a/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html b/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html index 5a82ac77c719..fccbaa7f0718 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html +++ b/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.html @@ -24,34 +24,51 @@

{{_ "Agents"}}

-
- - -
-
- - - - - - - - - {{#if agents.length}} - {{#each agents}} - - - - - {{/each}} - {{else}} + +
+ {{_ "Available_agents"}} + + +
+ +
+ {{_ "Selected_agents"}} + +
+
{{_ "Username"}}{{_ "Delete"}}
{{username}}
+ - + + + + - {{/if}} - -
{{_ "There_are_no_agents_added_to_this_department_yet"}}{{_ "Username"}}{{_ "Count"}}{{_ "Order"}} 
-
+ + + {{#if selectedAgents}} + {{#each selectedAgents}} + + {{username}} + + + + + {{/each}} + {{else}} + + {{_ "There_are_no_agents_added_to_this_department_yet"}} + + {{/if}} + + + + + +
diff --git a/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js b/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js index d4ff6d0fcac6..6f5017e0ca2e 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js +++ b/packages/rocketchat-livechat/client/views/app/livechatDepartmentForm.js @@ -1,10 +1,16 @@ Template.livechatDepartmentForm.helpers({ department() { - // return Template.instance().department && !_.isEmpty(Template.instance().department.get()) ? Template.instance().department.get() : { enabled: true }; return Template.instance().department.get(); }, agents() { return Template.instance().department && !_.isEmpty(Template.instance().department.get()) ? Template.instance().department.get().agents : [] + }, + selectedAgents() { + return _.sortBy(Template.instance().selectedAgents.get(), 'username'); + }, + availableAgents() { + var selected = _.pluck(Template.instance().selectedAgents.get(), 'username'); + return AgentUsers.find({ username: { $nin: selected }}, { sort: { username: 1 } }); } }); @@ -29,16 +35,22 @@ Template.livechatDepartmentForm.events({ var oldBtnValue = $btn.html(); $btn.html(t('Saving')); - agents = instance.department && !_.isEmpty(instance.department.get()) ? instance.department.get().agents : []; - - departmentData = { + var departmentData = { enabled: enabled === "1" ? true : false, name: name.trim(), - description: description.trim(), - agents: agents - } + description: description.trim() + }; + + var departmentAgents = []; + + instance.selectedAgents.get().forEach((agent) => { + agent.count = instance.$('.count-' + agent.agentId).val(); + agent.order = instance.$('.order-' + agent.agentId).val(); + + departmentAgents.push(agent); + }); - Meteor.call('livechat:saveDepartment', _id, departmentData, function(error, result) { + Meteor.call('livechat:saveDepartment', _id, departmentData, departmentAgents, function(error, result) { $btn.html(oldBtnValue); if (error) { return toastr.error(t(error.reason || error.error)); @@ -54,59 +66,44 @@ Template.livechatDepartmentForm.events({ FlowRouter.go('livechat-departments'); }, - 'click button.add-agent' (e, instance) { + 'click .remove-agent' (e, instance) { e.preventDefault(); - var $btn = $(e.currentTarget); - - var $agent = instance.$('input[name=agent]') - - if ($agent.val().trim() === '') { - return toastr.error(t('Please_fill_a_username')); - } - - var oldBtnValue = $btn.html(); - $btn.html(t('Saving')); - Meteor.call('livechat:searchAgent', $agent.val(), function(error, user) { - $btn.html(oldBtnValue); - if (error) { - return toastr.error(t(error.reason || error.error)); - } - department = instance.department.get() || {}; - if (department.agents === undefined || !_.isArray(department.agents)) { - department.agents = []; - } - if (!_.findWhere(department.agents, { _id: user._id })) { - department.agents.push(user); - } - instance.department.set(department); - $agent.val(''); - }); + var selectedAgents = instance.selectedAgents.get(); + selectedAgents = _.reject(selectedAgents, (agent) => { return agent._id === this._id }); + instance.selectedAgents.set(selectedAgents); }, - 'click a.remove-agent' (e, instance) { - e.preventDefault(); - department = instance.department.get(); - department.agents = _.reject(department.agents, (agent) => { return agent._id === this._id }); - instance.department.set(department); - }, - - 'keydown input[name=agent]' (e, instance) { - if (e.keyCode === 13) { - e.preventDefault(); - $("button.add-agent").click(); - } + 'click .available-agents li' (e, instance) { + var selectedAgents = instance.selectedAgents.get(); + var agent = _.clone(this); + agent.agentId = this._id; + delete agent._id; + selectedAgents.push(agent); + instance.selectedAgents.set(selectedAgents); } }); Template.livechatDepartmentForm.onCreated(function() { this.department = new ReactiveVar({ enabled: true }); + this.selectedAgents = new ReactiveVar([]); + + this.subscribe('livechat:agents'); + this.autorun(() => { var sub = this.subscribe('livechat:departments', FlowRouter.getParam('_id')); if (sub.ready()) { department = LivechatDepartment.findOne({ _id: FlowRouter.getParam('_id') }); if (department) { this.department.set(department); + + this.subscribe('livechat:departmentAgents', department._id, () => { + var newSelectedAgents = []; + LivechatDepartmentAgents.find({ departmentId: department._id }).forEach((agent) => { + newSelectedAgents.push(agent); + }); + this.selectedAgents.set(newSelectedAgents); + }); } } }); diff --git a/packages/rocketchat-livechat/client/views/app/livechatDepartments.js b/packages/rocketchat-livechat/client/views/app/livechatDepartments.js index eba12b82bc14..e5d0746485dc 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatDepartments.js +++ b/packages/rocketchat-livechat/client/views/app/livechatDepartments.js @@ -1,11 +1,6 @@ Template.livechatDepartments.helpers({ "departments": () => { return LivechatDepartment.find(); - }, - "numAgents"() { - if (Array.isArray(this.agents)) { - return this.agents.length; - } } }); diff --git a/packages/rocketchat-livechat/client/views/app/livechatUsers.js b/packages/rocketchat-livechat/client/views/app/livechatUsers.js index 5e29f332b938..eeb2d1dfa783 100644 --- a/packages/rocketchat-livechat/client/views/app/livechatUsers.js +++ b/packages/rocketchat-livechat/client/views/app/livechatUsers.js @@ -1,8 +1,6 @@ -var AgentUsers; var ManagerUsers; Meteor.startup(function() { - AgentUsers = new Mongo.Collection('agentUsers'); ManagerUsers = new Mongo.Collection('managerUsers'); }); diff --git a/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html b/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html index a859f5385a50..15d3ed86810d 100644 --- a/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html +++ b/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.html @@ -10,7 +10,7 @@

{{_ "Livechat"}}

  • {{_ "User_management"}} - {{_ "Departments"}} + {{_ "Departments"}} {{_ "Triggers"}} {{_ "Installation"}} {{_ "Appearance"}} diff --git a/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.js b/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.js index 4e2578cea84e..f7a8f2f0accf 100644 --- a/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.js +++ b/packages/rocketchat-livechat/client/views/sideNav/livechatFlex.js @@ -1,7 +1,7 @@ Template.livechatFlex.helpers({ - active (route) { + active (...routes) { FlowRouter.watchPathChange(); - if (FlowRouter.current().route.name === route) { + if (routes.indexOf(FlowRouter.current().route.name) !== -1) { return 'active'; } } diff --git a/packages/rocketchat-livechat/i18n/en.i18n.json b/packages/rocketchat-livechat/i18n/en.i18n.json index 072aeddc3204..754decc34491 100644 --- a/packages/rocketchat-livechat/i18n/en.i18n.json +++ b/packages/rocketchat-livechat/i18n/en.i18n.json @@ -4,9 +4,11 @@ "Add_manager" : "Add manager", "Agent_added" : "Agent added", "Agent_removed" : "Agent removed", + "Available_agents" : "Available agents", "Back" : "Back", "Closed" : "Closed", "Copy_to_clipboard" : "Copy to clipboard", + "Count" : "Count", "Dashboard" : "Dashboard", "Department_not_found" : "Department not found", "Department_removed" : "Department removed", @@ -32,10 +34,12 @@ "New_Department" : "New Department", "Num_Agents" : "# Agents", "Opened" : "Opened", + "Order" : "Order", "Please_fill_a_name" : "Please fill a name", "Please_fill_a_username" : "Please fill a username", "Please_select_enabled_yes_or_no" : "Please select an option for Enabled", "Saved" : "Saved", + "Selected_agents" : "Selected agents", "Send_a_message" : "Send a message", "Theme" : "Theme", "There_are_no_agents_added_to_this_department_yet" : "There are no agents added to this department yet.", @@ -48,4 +52,4 @@ "Username_not_found" : "Username not found", "Visitor_page_URL" : "Visitor page URL", "Visitor_time_on_site" : "Visitor time on site" -} \ No newline at end of file +} diff --git a/packages/rocketchat-livechat/package.js b/packages/rocketchat-livechat/package.js index b692038ae8d6..f6b14d25635b 100644 --- a/packages/rocketchat-livechat/package.js +++ b/packages/rocketchat-livechat/package.js @@ -38,6 +38,12 @@ Package.onUse(function(api) { api.addFiles('client/stylesheets/livechat.less', 'client'); + // collections + api.addFiles('client/collections/AgentUsers.js', 'client'); + api.addFiles('client/collections/LivechatDepartment.js', 'client'); + api.addFiles('client/collections/LivechatDepartmentAgents.js', 'client'); + api.addFiles('client/collections/LivechatTrigger.js', 'client'); + // client views api.addFiles('client/views/app/livechatAppearance.html', 'client'); api.addFiles('client/views/app/livechatAppearance.js', 'client'); @@ -80,13 +86,14 @@ Package.onUse(function(api) { api.addFiles('server/models/Users.js', 'server'); api.addFiles('server/models/Rooms.js', 'server'); api.addFiles('server/models/LivechatDepartment.js', 'server'); + api.addFiles('server/models/LivechatDepartmentAgents.js', 'server'); api.addFiles('server/models/LivechatTrigger.js', 'server'); - // collections - api.addFiles('client/lib/LivechatDepartment.js', 'client'); - api.addFiles('client/lib/LivechatTrigger.js', 'client'); + // server lib + api.addFiles('server/lib/getNextAgent.js', 'server'); // publications + api.addFiles('server/publications/departmentAgents.js', 'server'); api.addFiles('server/publications/livechatAgents.js', 'server'); api.addFiles('server/publications/livechatManagers.js', 'server'); api.addFiles('server/publications/livechatDepartments.js', 'server'); diff --git a/packages/rocketchat-livechat/server/lib/getNextAgent.js b/packages/rocketchat-livechat/server/lib/getNextAgent.js index 1e9aba08673c..3259ab642d93 100644 --- a/packages/rocketchat-livechat/server/lib/getNextAgent.js +++ b/packages/rocketchat-livechat/server/lib/getNextAgent.js @@ -1,27 +1,8 @@ this.getNextAgent = function(department) { var agentFilter = {}; - // find agents from that department if (department) { - var agents = RocketChat.models.LivechatDepartment.getNextAgent(department); - - if (!agents) { - return; - } - - // sort = { - // count: 1, - // order: 1, - // 'user.name': 1 - // } - - // update = { - // $inc: { - // count: 1 - // } - // } - - // queueUser = findAndModify query, sort, update + return RocketChat.models.LivechatDepartment.getNextAgent(department); } else { return RocketChat.models.Users.getNextAgent(); } diff --git a/packages/rocketchat-livechat/server/methods/saveDepartment.js b/packages/rocketchat-livechat/server/methods/saveDepartment.js index de3f656817bc..2f626c395720 100644 --- a/packages/rocketchat-livechat/server/methods/saveDepartment.js +++ b/packages/rocketchat-livechat/server/methods/saveDepartment.js @@ -1,5 +1,5 @@ Meteor.methods({ - 'livechat:saveDepartment' (_id, departmentData) { + 'livechat:saveDepartment' (_id, departmentData, departmentAgents) { if (!Meteor.userId() || !RocketChat.authz.hasPermission(Meteor.userId(), 'view-livechat-manager')) { throw new Meteor.Error("not-authorized"); } @@ -17,6 +17,6 @@ Meteor.methods({ } } - return RocketChat.models.LivechatDepartment.createOrUpdateDepartment(_id, departmentData.enabled, departmentData.name, departmentData.description, departmentData.agents); + return RocketChat.models.LivechatDepartment.createOrUpdateDepartment(_id, departmentData.enabled, departmentData.name, departmentData.description, departmentAgents); } }); diff --git a/packages/rocketchat-livechat/server/models/LivechatDepartment.js b/packages/rocketchat-livechat/server/models/LivechatDepartment.js index 41141b8d68ef..d474415ea963 100644 --- a/packages/rocketchat-livechat/server/models/LivechatDepartment.js +++ b/packages/rocketchat-livechat/server/models/LivechatDepartment.js @@ -19,23 +19,42 @@ class LivechatDepartment extends RocketChat.models._Base { return this.find(query, options); } - // UPSERT createOrUpdateDepartment(_id, enabled, name, description, agents, extraData) { - record = { + var agents = [].concat(agents); + + var record = { enabled: enabled, name: name, description: description, - agents: [] - } + numAgents: agents.length + }; + + _.extend(record, extraData); - if (!_.isEmpty(agents)) { - for (agent of agents) { - record.agents.push({ _id: agent._id, username: agent.username }); - } + if (_id) { + this.update({ _id: _id }, { $set: record }); + } else { + _id = this.insert(record); } - _.extend(record, extraData); - this.upsert({ _id: _id }, { $set: record }); + var savedAgents = _.pluck(RocketChat.models.LivechatDepartmentAgents.findByDepartmentId(_id).fetch(), 'agentId'); + var agentsToSave = _.pluck(agents, 'agentId'); + + // remove other agents + _.difference(savedAgents, agentsToSave).forEach((agentId) => { + RocketChat.models.LivechatDepartmentAgents.removeByDepartmentIdAndAgentId(_id, agentId); + }); + + agents.forEach((agent) => { + RocketChat.models.LivechatDepartmentAgents.saveAgent({ + agentId: agent.agentId, + departmentId: _id, + username: agent.username, + count: parseInt(agent.count), + order: parseInt(agent.order) + }); + }); + return _.extend(record, { _id: _id }); } diff --git a/packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js b/packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js new file mode 100644 index 000000000000..a4d56fa7ba82 --- /dev/null +++ b/packages/rocketchat-livechat/server/models/LivechatDepartmentAgents.js @@ -0,0 +1,71 @@ +/** + * Livechat Department model + */ +class LivechatDepartmentAgents extends RocketChat.models._Base { + constructor() { + super(); + this._initModel('livechat_department_agents'); + } + + findByDepartmentId(departmentId) { + return this.find({ departmentId: departmentId }); + } + + saveAgent(agent) { + if (agent._id) { + return this.update({ _id: _id }, { $set: agent }); + } else { + return this.upsert({ + agentId: agent.agentId, + departmentId: agent.departmentId + }, { + $set: { + username: agent.username, + count: parseInt(agent.count), + order: parseInt(agent.order) + } + }); + } + } + + removeByDepartmentIdAndAgentId(departmentId, agentId) { + this.remove({ departmentId: departmentId, agentId: agentId }); + } + + getNextAgentForDepartment(departmentId) { + var agents = this.findByDepartmentId(departmentId).fetch(); + + if (agents.length === 0) { + return; + } + + var onlineUsers = RocketChat.models.Users.findOnlineUserFromList(_.pluck(agents, 'username')); + + var onlineUsernames = _.pluck(onlineUsers.fetch(), 'username'); + + var query = { + departmentId: departmentId, + username: { + $in: onlineUsernames + } + }; + + var sort = { + count: 1, + sort: 1, + username: 1 + }; + var update = { + $inc: { + count: 1 + } + }; + + var collectionObj = this.model.rawCollection(); + var findAndModify = Meteor.wrapAsync(collectionObj.findAndModify, collectionObj); + + return findAndModify(query, sort, update); + } +} + +RocketChat.models.LivechatDepartmentAgents = new LivechatDepartmentAgents(); diff --git a/packages/rocketchat-livechat/server/publications/departmentAgents.js b/packages/rocketchat-livechat/server/publications/departmentAgents.js new file mode 100644 index 000000000000..b09f4fe3dd9c --- /dev/null +++ b/packages/rocketchat-livechat/server/publications/departmentAgents.js @@ -0,0 +1,11 @@ +Meteor.publish('livechat:departmentAgents', function(departmentId) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); + } + + if (!RocketChat.authz.hasPermission(this.userId, 'view-livechat-manager')) { + throw new Meteor.Error('not-authorized'); + } + + return RocketChat.models.LivechatDepartmentAgents.find({ departmentId: departmentId }); +});