[NEW] Add ability to set tags in the Omnichannel room closing dialog (#17254)
parent
545e03a07d
commit
c21bfd442d
@ -0,0 +1,72 @@ |
||||
<template name="closeRoom"> |
||||
<section class="create-channel"> |
||||
<div class="create-channel__wrapper"> |
||||
<p class="create-channel__description">{{_ "Close_room_description"}}</p> |
||||
<form id="close-room" class="close-room__content"> |
||||
<div class="create-channel__inputs"> |
||||
<div class="rc-input {{#if invalidComment}}rc-input--error{{/if}}"> |
||||
<label class="rc-input__label"> |
||||
<div class="rc-input__wrapper"> |
||||
<input name="comment" id="comment" type="text" class="rc-input__element" |
||||
placeholder="{{_ "Please_add_a_comment"}}" autofocus> |
||||
</div> |
||||
</label> |
||||
{{#if invalidComment}} |
||||
<div class="rc-input__error"> |
||||
<div class="rc-input__error-icon"> |
||||
{{> icon block="rc-input__error-icon" icon="warning" classes="rc-input__error-icon-svg"}} |
||||
</div> |
||||
<div class="rc-input__error-message">{{_ "Please_add_a_comment_to_close_the_room"}}</div> |
||||
</div> |
||||
{{/if}} |
||||
</div> |
||||
<div class="rc-input {{#if invalidTags}}rc-input--error{{/if}}"> |
||||
{{#if hasAvailableTags}} |
||||
<div class="rc-form-group rc-form-group--small rc-form-group--inline"> |
||||
<select id="tagSelect" class="rc-input rc-input--small rc-form-item-inline"> |
||||
<option value="placeholder" disabled selected>{{_ "Select_tag"}}</option> |
||||
{{#each availableUserTags}} |
||||
<option value="{{_id}}">{{this}}</option> |
||||
{{/each}} |
||||
</select> |
||||
<button id="addTag" class="rc-button rc-button--primary rc-form-item-inline"><i class="icon-plus"></i></button> |
||||
</div> |
||||
{{else}} |
||||
<label class="rc-input__label"> |
||||
<div class="rc-input__wrapper"> |
||||
<div class="rc-input__icon"> |
||||
{{> icon icon='edit' }} |
||||
</div> |
||||
<input id="tagInput" class="rc-input__element" type="text" name="tags" autocomplete="off" placeholder="{{tagsPlaceHolder}}"> |
||||
</div> |
||||
</label> |
||||
{{/if}} |
||||
{{#if invalidTags}} |
||||
<div class="rc-input__error"> |
||||
<div class="rc-input__error-icon"> |
||||
{{> icon block="rc-input__error-icon" icon="warning" classes="rc-input__error-icon-svg"}} |
||||
</div> |
||||
<div class="rc-input__error-message">{{_ "error-tags-must-be-assigned-before-closing-chat"}}</div> |
||||
</div> |
||||
{{/if}} |
||||
</div> |
||||
<div class="rc-form-group rc-form-group--small"> |
||||
<ul id="tags" class="chip-container current-room-tags"> |
||||
{{#each tags}} |
||||
<li class="remove-tag" title="{{this}}"> |
||||
{{#if canRemoveTag availableUserTags this}} |
||||
<i class="icon icon-cancel-circled"></i> |
||||
{{/if}} |
||||
{{this}} |
||||
</li> |
||||
{{/each}} |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
</form> |
||||
<div class="rc-input"> |
||||
<button form="close-room" class="rc-button rc-button--primary js-close-room">{{_ " Confirm "}}</button> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
</template> |
||||
@ -0,0 +1,190 @@ |
||||
import { Meteor } from 'meteor/meteor'; |
||||
import { Template } from 'meteor/templating'; |
||||
import { ReactiveVar } from 'meteor/reactive-var'; |
||||
import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; |
||||
|
||||
import { settings } from '../../../../../settings'; |
||||
import { modal } from '../../../../../ui-utils/client'; |
||||
import { APIClient, handleError, t } from '../../../../../utils'; |
||||
import { hasRole } from '../../../../../authorization'; |
||||
import './closeRoom.html'; |
||||
|
||||
const validateRoomComment = (comment) => { |
||||
if (!settings.get('Livechat_request_comment_when_closing_conversation')) { |
||||
return true; |
||||
} |
||||
|
||||
return comment?.length > 0; |
||||
}; |
||||
|
||||
const validateRoomTags = (tagsRequired, tags) => { |
||||
if (!tagsRequired) { |
||||
return true; |
||||
} |
||||
|
||||
return tags?.length > 0; |
||||
}; |
||||
|
||||
const checkUserTagPermission = (availableUserTags = [], tag) => { |
||||
if (hasRole(Meteor.userId(), ['admin', 'livechat-manager'])) { |
||||
return true; |
||||
} |
||||
|
||||
return availableUserTags.includes(tag); |
||||
}; |
||||
|
||||
Template.closeRoom.helpers({ |
||||
invalidComment() { |
||||
return Template.instance().invalidComment.get(); |
||||
}, |
||||
tags() { |
||||
return Template.instance().tags.get(); |
||||
}, |
||||
invalidTags() { |
||||
return Template.instance().invalidTags.get(); |
||||
}, |
||||
availableUserTags() { |
||||
return Template.instance().availableUserTags.get(); |
||||
}, |
||||
tagsPlaceHolder() { |
||||
let placeholder = TAPi18n.__('Enter_a_tag'); |
||||
|
||||
if (!Template.instance().tagsRequired.get()) { |
||||
placeholder = placeholder.concat(`(${ TAPi18n.__('Optional') })`); |
||||
} |
||||
|
||||
return placeholder; |
||||
}, |
||||
hasAvailableTags() { |
||||
const tags = Template.instance().availableTags.get(); |
||||
return tags?.length > 0; |
||||
}, |
||||
canRemoveTag(availableUserTags, tag) { |
||||
return checkUserTagPermission(availableUserTags, tag); |
||||
}, |
||||
}); |
||||
|
||||
Template.closeRoom.events({ |
||||
async 'submit .close-room__content'(e, instance) { |
||||
e.preventDefault(); |
||||
e.stopPropagation(); |
||||
|
||||
const comment = instance.$('#comment').val(); |
||||
instance.invalidComment.set(!validateRoomComment(comment)); |
||||
if (instance.invalidComment.get()) { |
||||
return; |
||||
} |
||||
|
||||
const tagsRequired = instance.tagsRequired.get(); |
||||
const tags = instance.tags.get(); |
||||
|
||||
instance.invalidTags.set(!validateRoomTags(tagsRequired, tags)); |
||||
if (instance.invalidTags.get()) { |
||||
return; |
||||
} |
||||
|
||||
Meteor.call('livechat:closeRoom', this.rid, comment, { clientAction: true, tags }, function(error/* , result*/) { |
||||
if (error) { |
||||
console.log(error); |
||||
return handleError(error); |
||||
} |
||||
|
||||
modal.open({ |
||||
title: t('Chat_closed'), |
||||
text: t('Chat_closed_successfully'), |
||||
type: 'success', |
||||
timer: 1000, |
||||
showConfirmButton: false, |
||||
}); |
||||
}); |
||||
}, |
||||
'click .remove-tag'(e, instance) { |
||||
e.stopPropagation(); |
||||
e.preventDefault(); |
||||
|
||||
const tag = this.valueOf(); |
||||
const availableTags = instance.availableTags.get(); |
||||
const hasAvailableTags = availableTags?.length > 0; |
||||
const availableUserTags = instance.availableUserTags.get(); |
||||
if (hasAvailableTags && !checkUserTagPermission(availableUserTags, tag)) { |
||||
return; |
||||
} |
||||
|
||||
let tags = instance.tags.get(); |
||||
tags = tags.filter((el) => el !== tag); |
||||
instance.tags.set(tags); |
||||
}, |
||||
'click #addTag'(e, instance) { |
||||
e.stopPropagation(); |
||||
e.preventDefault(); |
||||
|
||||
if ($('#tagSelect').find(':selected').is(':disabled')) { |
||||
return; |
||||
} |
||||
|
||||
const tags = [...instance.tags.get()]; |
||||
const tagVal = $('#tagSelect').val(); |
||||
if (tagVal === '' || tags.includes(tagVal)) { |
||||
return; |
||||
} |
||||
|
||||
tags.push(tagVal); |
||||
instance.tags.set(tags); |
||||
$('#tagSelect').val('placeholder'); |
||||
}, |
||||
'keydown #tagInput'(e, instance) { |
||||
if (e.which === 13) { |
||||
e.stopPropagation(); |
||||
e.preventDefault(); |
||||
|
||||
const tags = [...instance.tags.get()]; |
||||
const tagVal = $('#tagInput').val(); |
||||
if (tagVal === '' || tags.includes(tagVal)) { |
||||
return; |
||||
} |
||||
|
||||
tags.push(tagVal); |
||||
instance.tags.set(tags); |
||||
$('#tagInput').val(''); |
||||
} |
||||
}, |
||||
}); |
||||
|
||||
Template.closeRoom.onRendered(function() { |
||||
this.find('#comment').focus(); |
||||
}); |
||||
|
||||
Template.closeRoom.onCreated(async function() { |
||||
this.tags = new ReactiveVar([]); |
||||
this.invalidComment = new ReactiveVar(false); |
||||
this.invalidTags = new ReactiveVar(false); |
||||
this.tagsRequired = new ReactiveVar(false); |
||||
this.availableTags = new ReactiveVar([]); |
||||
this.availableUserTags = new ReactiveVar([]); |
||||
this.agentDepartments = new ReactiveVar([]); |
||||
|
||||
this.onEnterTag = () => this.invalidTags.set(!validateRoomTags(this.tagsRequired.get(), this.tags.get())); |
||||
|
||||
const { rid } = Template.currentData(); |
||||
const { room } = await APIClient.v1.get(`rooms.info?roomId=${ rid }`); |
||||
this.tags.set(room?.tags || []); |
||||
|
||||
if (room?.departmentId) { |
||||
const { department } = await APIClient.v1.get(`livechat/department/${ room.departmentId }?includeAgents=false`); |
||||
this.tagsRequired.set(department?.requestTagBeforeClosingChat); |
||||
} |
||||
|
||||
const uid = Meteor.userId(); |
||||
const { departments } = await APIClient.v1.get(`livechat/agents/${ uid }/departments`); |
||||
const agentDepartments = departments.map((dept) => dept.departmentId); |
||||
this.agentDepartments.set(agentDepartments); |
||||
|
||||
Meteor.call('livechat:getTagsList', (err, tagsList) => { |
||||
this.availableTags.set(tagsList); |
||||
const isAdmin = hasRole(uid, ['admin', 'livechat-manager']); |
||||
const availableTags = tagsList |
||||
.filter(({ departments }) => isAdmin || (departments.length === 0 || departments.some((i) => agentDepartments.includes(i)))) |
||||
.map(({ name }) => name); |
||||
this.availableUserTags.set(availableTags); |
||||
}); |
||||
}); |
||||
Loading…
Reference in new issue