Implemented feature to transfer chat sessions to another chat topic.
This commit is contained in:
parent
3c788ca426
commit
133b570fba
15 changed files with 491 additions and 160 deletions
|
@ -4,11 +4,12 @@ class App.CustomerChat extends App.Controller
|
||||||
'click .js-settings': 'settings'
|
'click .js-settings': 'settings'
|
||||||
|
|
||||||
elements:
|
elements:
|
||||||
'.js-acceptChat': 'acceptChatElement'
|
'.js-acceptChat': 'acceptChatElement'
|
||||||
'.js-badgeWaitingCustomers': 'badgeWaitingCustomers'
|
'.js-badgeWaitingCustomers': 'badgeWaitingCustomers'
|
||||||
|
'.js-totalInfo': 'totalInfo'
|
||||||
'.js-badgeChattingCustomers': 'badgeChattingCustomers'
|
'.js-badgeChattingCustomers': 'badgeChattingCustomers'
|
||||||
'.js-badgeActiveAgents': 'badgeActiveAgents'
|
'.js-badgeActiveAgents': 'badgeActiveAgents'
|
||||||
'.chat-workspace': 'workspace'
|
'.chat-workspace': 'workspace'
|
||||||
|
|
||||||
sounds:
|
sounds:
|
||||||
chat_new: new Audio('assets/sounds/chat_new.mp3')
|
chat_new: new Audio('assets/sounds/chat_new.mp3')
|
||||||
|
@ -28,7 +29,9 @@ class App.CustomerChat extends App.Controller
|
||||||
@meta =
|
@meta =
|
||||||
active: false
|
active: false
|
||||||
waiting_chat_count: 0
|
waiting_chat_count: 0
|
||||||
|
waiting_chat_count_by_chat: {}
|
||||||
waiting_chat_session_list: []
|
waiting_chat_session_list: []
|
||||||
|
waiting_chat_session_list_by_chat: {}
|
||||||
running_chat_count: 0
|
running_chat_count: 0
|
||||||
running_chat_session_list: []
|
running_chat_session_list: []
|
||||||
active_agent_count: 0
|
active_agent_count: 0
|
||||||
|
@ -99,65 +102,23 @@ class App.CustomerChat extends App.Controller
|
||||||
|
|
||||||
@html App.view('customer_chat/index')()
|
@html App.view('customer_chat/index')()
|
||||||
|
|
||||||
chatSessionList = (list) ->
|
chatSessionList: (list) ->
|
||||||
for chat_session in list
|
list = [] if !list
|
||||||
chat = App.Chat.find(chat_session.chat_id)
|
for chat_session in list
|
||||||
chat_session.name = "#{chat.displayName()} [##{chat_session.id}]"
|
chat = App.Chat.find(chat_session.chat_id)
|
||||||
chat_session.geo_data = ''
|
chat_session.name = "#{chat.displayName()} [##{chat_session.id}]"
|
||||||
if chat_session.preferences && chat_session.preferences.geo_ip
|
chat_session.geo_data = ''
|
||||||
if chat_session.preferences.geo_ip.country_name
|
if chat_session.preferences && chat_session.preferences.geo_ip
|
||||||
chat_session.geo_data += chat_session.preferences.geo_ip.country_name
|
if chat_session.preferences.geo_ip.country_name
|
||||||
if chat_session.preferences.geo_ip.city_name
|
chat_session.geo_data += chat_session.preferences.geo_ip.country_name
|
||||||
chat_session.geo_data += " #{chat_session.preferences.geo_ip.city_name}"
|
if chat_session.preferences.geo_ip.city_name
|
||||||
if chat_session.user_id
|
chat_session.geo_data += " #{chat_session.preferences.geo_ip.city_name}"
|
||||||
chat_session.user = App.User.find(chat_session.user_id)
|
if chat_session.user_id
|
||||||
App.view('customer_chat/chat_list')(
|
chat_session.user = App.User.find(chat_session.user_id)
|
||||||
chat_sessions: list
|
App.view('customer_chat/chat_list')(
|
||||||
)
|
chat_sessions: list
|
||||||
|
|
||||||
@el.find('.js-waitingCustomers .js-info').popover(
|
|
||||||
trigger: 'hover'
|
|
||||||
html: true
|
|
||||||
animation: false
|
|
||||||
delay: 0
|
|
||||||
placement: 'bottom'
|
|
||||||
container: 'body' # place in body do prevent it from animating
|
|
||||||
title: ->
|
|
||||||
App.i18n.translateContent('Waiting Customers')
|
|
||||||
content: =>
|
|
||||||
chatSessionList(@meta.waiting_chat_session_list)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@el.find('.js-chattingCustomers .js-info').popover(
|
|
||||||
trigger: 'hover'
|
|
||||||
html: true
|
|
||||||
animation: false
|
|
||||||
delay: 0
|
|
||||||
placement: 'bottom'
|
|
||||||
container: 'body'
|
|
||||||
title: ->
|
|
||||||
App.i18n.translateContent('Chatting Customers')
|
|
||||||
content: =>
|
|
||||||
chatSessionList(@meta.running_chat_session_list)
|
|
||||||
)
|
|
||||||
|
|
||||||
@el.find('.js-activeAgents .js-info').popover(
|
|
||||||
trigger: 'hover'
|
|
||||||
html: true
|
|
||||||
animation: false
|
|
||||||
delay: 0
|
|
||||||
placement: 'bottom'
|
|
||||||
container: 'body'
|
|
||||||
title: ->
|
|
||||||
App.i18n.translateContent('Active Agents')
|
|
||||||
content: =>
|
|
||||||
users = []
|
|
||||||
for user_id in @meta.active_agent_ids
|
|
||||||
users.push App.User.find(user_id)
|
|
||||||
App.view('customer_chat/user_list')(
|
|
||||||
users: users
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
show: (params) =>
|
show: (params) =>
|
||||||
@title 'Customer Chat', true
|
@title 'Customer Chat', true
|
||||||
|
@ -248,15 +209,68 @@ class App.CustomerChat extends App.Controller
|
||||||
@stopPushState()
|
@stopPushState()
|
||||||
@pushState()
|
@pushState()
|
||||||
|
|
||||||
|
activeChatTopcis: =>
|
||||||
|
preferences = @Session.get('preferences')
|
||||||
|
return [] if !preferences
|
||||||
|
return [] if !preferences.chat
|
||||||
|
return [] if !preferences.chat.active
|
||||||
|
chats = []
|
||||||
|
for chat in App.Chat.all()
|
||||||
|
if preferences.chat.active[chat.id] is 'on' || preferences.chat.active[chat.id.toString()] is 'on'
|
||||||
|
chats.push chat
|
||||||
|
chats
|
||||||
|
|
||||||
updateMeta: =>
|
updateMeta: =>
|
||||||
|
$('.popover').remove()
|
||||||
|
activeChatTopcis = @activeChatTopcis()
|
||||||
|
@$('.js-header').html(App.view('customer_chat/chat_header')(chats: activeChatTopcis))
|
||||||
|
@refreshElements()
|
||||||
if @meta.waiting_chat_count && @maxChatWindows > @windowCount()
|
if @meta.waiting_chat_count && @maxChatWindows > @windowCount()
|
||||||
@acceptChatElement.addClass('is-clickable is-blinking')
|
|
||||||
|
# activate normal button
|
||||||
|
@acceptChatElement.not('[data-chat-id]').addClass('is-active pulsate-animation')
|
||||||
|
|
||||||
|
# activate specific chat buttons
|
||||||
|
if activeChatTopcis.length > 1
|
||||||
|
for chat in activeChatTopcis
|
||||||
|
if @meta.waiting_chat_count_by_chat[chat.id]
|
||||||
|
@$(".js-header .js-acceptChat[data-chat-id=#{chat.id}]").addClass('is-active pulsate-animation').attr('disabled', false)
|
||||||
@idleTimeoutStart()
|
@idleTimeoutStart()
|
||||||
else
|
else
|
||||||
@acceptChatElement.removeClass('is-clickable is-blinking')
|
@acceptChatElement.removeClass('is-active pulsate-animation')
|
||||||
@idleTimeoutStop()
|
@idleTimeoutStop()
|
||||||
|
|
||||||
@badgeWaitingCustomers.text(@meta.waiting_chat_count)
|
if activeChatTopcis.length > 1
|
||||||
|
for chat in App.Chat.all()
|
||||||
|
do (chat) =>
|
||||||
|
@$(".js-header .js-waitingCustomers[data-chat-id=#{chat.id}] .js-badgeWaitingCustomers").text(@meta.waiting_chat_count_by_chat[chat.id])
|
||||||
|
@el.find(".js-waitingCustomers[data-chat-id=#{chat.id}] .js-info").popover(
|
||||||
|
trigger: 'hover'
|
||||||
|
html: true
|
||||||
|
animation: false
|
||||||
|
delay: 0
|
||||||
|
placement: 'bottom'
|
||||||
|
container: 'body' # place in body do prevent it from animating
|
||||||
|
title: ->
|
||||||
|
App.i18n.translateContent('Waiting Customers')
|
||||||
|
content: =>
|
||||||
|
@chatSessionList(@meta.waiting_chat_session_list_by_chat[chat.id])
|
||||||
|
)
|
||||||
|
else
|
||||||
|
@badgeWaitingCustomers.text(@meta.waiting_chat_count)
|
||||||
|
@el.find('.js-waitingCustomers .js-totalInfo').popover(
|
||||||
|
trigger: 'hover'
|
||||||
|
html: true
|
||||||
|
animation: false
|
||||||
|
delay: 0
|
||||||
|
placement: 'bottom'
|
||||||
|
container: 'body' # place in body do prevent it from animating
|
||||||
|
title: ->
|
||||||
|
App.i18n.translateContent('Waiting Customers')
|
||||||
|
content: =>
|
||||||
|
@chatSessionList(@meta.waiting_chat_session_list_by_chat[activeChatTopcis[0].id])
|
||||||
|
)
|
||||||
|
|
||||||
@badgeChattingCustomers.text(@meta.running_chat_count)
|
@badgeChattingCustomers.text(@meta.running_chat_count)
|
||||||
@badgeActiveAgents.text(@meta.active_agent_count)
|
@badgeActiveAgents.text(@meta.active_agent_count)
|
||||||
|
|
||||||
|
@ -266,6 +280,37 @@ class App.CustomerChat extends App.Controller
|
||||||
@addChat(session)
|
@addChat(session)
|
||||||
@meta.active_sessions = false
|
@meta.active_sessions = false
|
||||||
|
|
||||||
|
@el.find('.js-chattingCustomers .js-info').popover(
|
||||||
|
trigger: 'hover'
|
||||||
|
html: true
|
||||||
|
animation: false
|
||||||
|
delay: 0
|
||||||
|
placement: 'bottom'
|
||||||
|
container: 'body'
|
||||||
|
title: ->
|
||||||
|
App.i18n.translateContent('Chatting Customers')
|
||||||
|
content: =>
|
||||||
|
@chatSessionList(@meta.running_chat_session_list)
|
||||||
|
)
|
||||||
|
|
||||||
|
@el.find('.js-activeAgents .js-info').popover(
|
||||||
|
trigger: 'hover'
|
||||||
|
html: true
|
||||||
|
animation: false
|
||||||
|
delay: 0
|
||||||
|
placement: 'bottom'
|
||||||
|
container: 'body'
|
||||||
|
title: ->
|
||||||
|
App.i18n.translateContent('Active Agents')
|
||||||
|
content: =>
|
||||||
|
users = []
|
||||||
|
for user_id in @meta.active_agent_ids
|
||||||
|
users.push App.User.find(user_id)
|
||||||
|
App.view('customer_chat/user_list')(
|
||||||
|
users: users
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@updateNavMenu()
|
@updateNavMenu()
|
||||||
|
|
||||||
addChat: (session) ->
|
addChat: (session) ->
|
||||||
|
@ -298,9 +343,10 @@ class App.CustomerChat extends App.Controller
|
||||||
for session_id, chat of @chatWindows
|
for session_id, chat of @chatWindows
|
||||||
chat.trigger('layout-changed')
|
chat.trigger('layout-changed')
|
||||||
|
|
||||||
acceptChat: =>
|
acceptChat: (e) =>
|
||||||
return if @windowCount() >= @maxChatWindows
|
return if @windowCount() >= @maxChatWindows
|
||||||
App.WebSocket.send(event:'chat_session_start')
|
chat_id = $(e.currentTarget).attr('data-chat-id')
|
||||||
|
App.WebSocket.send(event:'chat_session_start', chat_id: chat_id)
|
||||||
@idleTimeoutStop()
|
@idleTimeoutStop()
|
||||||
|
|
||||||
settings: (params = {}) ->
|
settings: (params = {}) ->
|
||||||
|
@ -344,6 +390,7 @@ class ChatWindow extends App.Controller
|
||||||
'click .js-scrollHint': 'onScrollHintClick'
|
'click .js-scrollHint': 'onScrollHintClick'
|
||||||
'click .js-info': 'toggleMeta'
|
'click .js-info': 'toggleMeta'
|
||||||
'click .js-createTicket': 'ticketCreate'
|
'click .js-createTicket': 'ticketCreate'
|
||||||
|
'click .js-transferChat': 'transfer'
|
||||||
'submit .js-metaForm': 'sendMetaForm'
|
'submit .js-metaForm': 'sendMetaForm'
|
||||||
|
|
||||||
elements:
|
elements:
|
||||||
|
@ -450,6 +497,7 @@ class ChatWindow extends App.Controller
|
||||||
@html App.view('customer_chat/chat_window')(
|
@html App.view('customer_chat/chat_window')(
|
||||||
name: @name
|
name: @name
|
||||||
session: @session
|
session: @session
|
||||||
|
chats: App.Chat.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
@el.one('transitionend', @onTransitionend)
|
@el.one('transitionend', @onTransitionend)
|
||||||
|
@ -766,6 +814,12 @@ class ChatWindow extends App.Controller
|
||||||
else if showHint
|
else if showHint
|
||||||
@showScrollHint()
|
@showScrollHint()
|
||||||
|
|
||||||
|
transfer: (e) =>
|
||||||
|
e.preventDefault()
|
||||||
|
chat_id = $(e.currentTarget).attr('data-chat-id')
|
||||||
|
App.WebSocket.send(event:'chat_transfer', chat_id: chat_id, session_id: @session.id)
|
||||||
|
@close()
|
||||||
|
|
||||||
ticketCreate: (e) =>
|
ticketCreate: (e) =>
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<div class="status-fields">
|
||||||
|
<% if @chats.length > 1: %>
|
||||||
|
<div class="buttonDropdown dropdown">
|
||||||
|
<div class="status-field status-field--spacer js-acceptChat">
|
||||||
|
<span class="badge js-badgeWaitingCustomers"></span> <%- @T('Waiting Customers') %>
|
||||||
|
</div>
|
||||||
|
<div class="status-field status-field--arrow" data-toggle="dropdown">
|
||||||
|
<%- @Icon('arrow-down') %>
|
||||||
|
</div>
|
||||||
|
<ul class="dropdown-menu" role="menu">
|
||||||
|
<% for chat in @chats: %>
|
||||||
|
<li class="js-waitingCustomers js-acceptChat" disabled data-chat-id="<%= chat.id %>" role="menuitem">
|
||||||
|
<span class="badge js-badgeWaitingCustomers"></span> <%- @T('Waiting in %s', chat.name) %> <span class="flex-spacer"></span>
|
||||||
|
<div class="status-badge js-info">
|
||||||
|
<div class="info-badge"><%- @Icon('info') %></div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% else: %>
|
||||||
|
<div class="status-field js-acceptChat js-waitingCustomers">
|
||||||
|
<span class="badge js-badgeWaitingCustomers"></span> <%- @T('Waiting Customers') %>
|
||||||
|
<div class="status-badge js-totalInfo">
|
||||||
|
<div class="info-badge"><%- @Icon('info') %></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="status-field js-chattingCustomers">
|
||||||
|
<span class="badge js-badgeChattingCustomers"></span> <%- @T('Chatting Customers') %>
|
||||||
|
<div class="status-badge js-info">
|
||||||
|
<div class="info-badge"><%- @Icon('info') %></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="status-field js-activeAgents">
|
||||||
|
<span class="badge js-badgeActiveAgents"></span> <%- @T('Active Agents') %>
|
||||||
|
<div class="status-badge js-info">
|
||||||
|
<div class="info-badge"><%- @Icon('info') %></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -24,6 +24,15 @@
|
||||||
<div class="chat-body js-body"></div>
|
<div class="chat-body js-body"></div>
|
||||||
<div class="chat-body js-meta hidden">
|
<div class="chat-body js-meta hidden">
|
||||||
<% if @session: %>
|
<% if @session: %>
|
||||||
|
<%- @T('Transfer conversation to another chat:') %>
|
||||||
|
<ul>
|
||||||
|
<% for chat in @chats: %>
|
||||||
|
<% if @session.chat_id isnt chat.id: %>
|
||||||
|
<li><a data-chat-id="<%= chat.id %>" class="js-transferChat" href="#"><%- chat.name %></a>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</ul>
|
||||||
|
<hr>
|
||||||
<ul>
|
<ul>
|
||||||
<li><%- @T('Created at') %>: <%- @Ttimestamp(@session.created_at) %></li>
|
<li><%- @T('Created at') %>: <%- @Ttimestamp(@session.created_at) %></li>
|
||||||
<% if @session && @session.preferences: %>
|
<% if @session && @session.preferences: %>
|
||||||
|
|
|
@ -3,28 +3,7 @@
|
||||||
<div class="page-header-title">
|
<div class="page-header-title">
|
||||||
<h1><%- @T('Customer Chat') %></h1>
|
<h1><%- @T('Customer Chat') %></h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="page-header-center">
|
<div class="page-header-center js-header"></div>
|
||||||
<div class="status-fields">
|
|
||||||
<div class="status-field js-acceptChat js-waitingCustomers">
|
|
||||||
<span class="badge js-badgeWaitingCustomers"></span> <%- @T('Waiting Customers') %>
|
|
||||||
<div class="status-badge js-info">
|
|
||||||
<div class="info-badge"><%- @Icon('info') %></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="status-field js-chattingCustomers">
|
|
||||||
<span class="badge js-badgeChattingCustomers"></span> <%- @T('Chatting Customers') %>
|
|
||||||
<div class="status-badge js-info">
|
|
||||||
<div class="info-badge"><%- @Icon('info') %></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="status-field js-activeAgents">
|
|
||||||
<span class="badge js-badgeActiveAgents"></span> <%- @T('Active Agents') %>
|
|
||||||
<div class="status-badge js-info">
|
|
||||||
<div class="info-badge"><%- @Icon('info') %></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="page-header-meta">
|
<div class="page-header-meta">
|
||||||
<div class="btn btn--action js-settings"><%- @T('Settings') %></div>
|
<div class="btn btn--action js-settings"><%- @T('Settings') %></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -29,11 +29,11 @@
|
||||||
<%- @Icon('arrow-down', 'dropdown-arrow') %>
|
<%- @Icon('arrow-down', 'dropdown-arrow') %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="settings-list">
|
<table class="settings-list settings-list--stretch">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><%- @T('Topic') %>
|
<th><%- @T('Topic') %>
|
||||||
<th width="100%"><%- @T('Greeting') %> (<%- @T('Separate multiple values by ;') %>)
|
<th><%- @T('Greeting') %> (<%- @T('Separate multiple values by ;') %>)
|
||||||
<th><%- @T('Enabled') %>
|
<th><%- @T('Enabled') %>
|
||||||
</th></tr>
|
</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
|
@ -889,6 +889,23 @@ pre code.hljs {
|
||||||
|
|
||||||
.status-fields {
|
.status-fields {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
.dropdown li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu .status-badge {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu .badge {
|
||||||
|
margin-top: -2px;
|
||||||
|
margin-right: 7px;
|
||||||
|
background: hsla(0,0%,0%,.5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-field {
|
.status-field {
|
||||||
|
@ -896,10 +913,15 @@ pre code.hljs {
|
||||||
border: 1px solid hsl(0,0%,90%);
|
border: 1px solid hsl(0,0%,90%);
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
|
flex-shrink: 1;
|
||||||
|
min-width: 20px;
|
||||||
|
padding: 5px 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
line-height: 35px;
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
&.is-clickable {
|
&.is-active {
|
||||||
background: hsl(203,65%,55%);
|
background: hsl(203,65%,55%);
|
||||||
color: white;
|
color: white;
|
||||||
border-color: hsl(203,65%,45%);
|
border-color: hsl(203,65%,45%);
|
||||||
|
@ -908,10 +930,6 @@ pre code.hljs {
|
||||||
@extend %clickable;
|
@extend %clickable;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-blinking {
|
|
||||||
animation: pulsate 667ms ease-in-out infinite alternate;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:last-child):not(:only-child) {
|
&:not(:last-child):not(:only-child) {
|
||||||
@include bidi-style(border-right-width, 0, border-left-width, 1px);
|
@include bidi-style(border-right-width, 0, border-left-width, 1px);
|
||||||
}
|
}
|
||||||
|
@ -919,6 +937,12 @@ pre code.hljs {
|
||||||
&:first-child {
|
&:first-child {
|
||||||
border-radius: 5px 0 0 5px;
|
border-radius: 5px 0 0 5px;
|
||||||
@include rtl(border-radius, 0 5px 5px 0);
|
@include rtl(border-radius, 0 5px 5px 0);
|
||||||
|
|
||||||
|
.dropdown.open & {
|
||||||
|
border-radius: 5px 0 0 0;
|
||||||
|
|
||||||
|
@include rtl(border-radius, 0 5px 0 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
@ -931,7 +955,7 @@ pre code.hljs {
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
@include bidi-style(margin, 0 5px 0 10px, margin, 0 10px 0 5px);
|
@include bidi-style(margin, 0 7px 0 10px, margin, 0 10px 0 7px);
|
||||||
background: hsla(210,50%,10%,.24);
|
background: hsla(210,50%,10%,.24);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -942,6 +966,22 @@ pre code.hljs {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--arrow {
|
||||||
|
@extend %clickable;
|
||||||
|
border-left: none;
|
||||||
|
width: 34px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--spacer {
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-badge {
|
.status-badge {
|
||||||
|
@ -2945,6 +2985,10 @@ ol.tabs li {
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
flex: none;
|
flex: none;
|
||||||
|
|
||||||
|
&:nth-last-child(2) {
|
||||||
|
@include bidi-style(border-right-width, 0, border-right-width, 1px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7386,6 +7430,12 @@ footer {
|
||||||
|
|
||||||
.dropdown-menu > li[disabled] {
|
.dropdown-menu > li[disabled] {
|
||||||
opacity: 0.33;
|
opacity: 0.33;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.is-active {
|
||||||
|
background: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu > li > a {
|
.dropdown-menu > li > a {
|
||||||
|
@ -7404,7 +7454,7 @@ footer {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu .badge {
|
.dropdown-menu .badge--text {
|
||||||
@include bidi-style(padding-left, 10px, padding-right, 0);
|
@include bidi-style(padding-left, 10px, padding-right, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -201,6 +201,7 @@ returns
|
||||||
end
|
end
|
||||||
|
|
||||||
running_chat_session_list_local = running_chat_session_list(chat_ids)
|
running_chat_session_list_local = running_chat_session_list(chat_ids)
|
||||||
|
|
||||||
running_chat_session_list_local.each do |session|
|
running_chat_session_list_local.each do |session|
|
||||||
next if !session['user_id']
|
next if !session['user_id']
|
||||||
|
|
||||||
|
@ -211,16 +212,18 @@ returns
|
||||||
end
|
end
|
||||||
|
|
||||||
{
|
{
|
||||||
waiting_chat_count: waiting_chat_count(chat_ids),
|
waiting_chat_count: waiting_chat_count(chat_ids),
|
||||||
waiting_chat_session_list: waiting_chat_session_list(chat_ids),
|
waiting_chat_count_by_chat: waiting_chat_count_by_chat(chat_ids),
|
||||||
running_chat_count: running_chat_count(chat_ids),
|
waiting_chat_session_list: waiting_chat_session_list(chat_ids),
|
||||||
running_chat_session_list: running_chat_session_list_local,
|
waiting_chat_session_list_by_chat: waiting_chat_session_list_by_chat(chat_ids),
|
||||||
active_agent_count: active_agent_count(chat_ids),
|
running_chat_count: running_chat_count(chat_ids),
|
||||||
active_agent_ids: active_agent_ids,
|
running_chat_session_list: running_chat_session_list_local,
|
||||||
seads_available: seads_available(chat_ids),
|
active_agent_count: active_agent_count(chat_ids),
|
||||||
seads_total: seads_total(chat_ids),
|
active_agent_ids: active_agent_ids,
|
||||||
active: Chat::Agent.state(user_id),
|
seads_available: seads_available(chat_ids),
|
||||||
assets: assets,
|
seads_total: seads_total(chat_ids),
|
||||||
|
active: Chat::Agent.state(user_id),
|
||||||
|
assets: assets,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -305,6 +308,14 @@ returns
|
||||||
Chat::Session.where(state: ['waiting'], chat_id: chat_ids).count
|
Chat::Session.where(state: ['waiting'], chat_id: chat_ids).count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.waiting_chat_count_by_chat(chat_ids)
|
||||||
|
list = {}
|
||||||
|
Chat.where(active: true, id: chat_ids).pluck(:id).each do |chat_id|
|
||||||
|
list[chat_id] = Chat::Session.where(chat_id: chat_id, state: ['waiting']).count
|
||||||
|
end
|
||||||
|
list
|
||||||
|
end
|
||||||
|
|
||||||
def self.waiting_chat_session_list(chat_ids)
|
def self.waiting_chat_session_list(chat_ids)
|
||||||
sessions = []
|
sessions = []
|
||||||
Chat::Session.where(state: ['waiting'], chat_id: chat_ids).each do |session|
|
Chat::Session.where(state: ['waiting'], chat_id: chat_ids).each do |session|
|
||||||
|
@ -313,6 +324,17 @@ returns
|
||||||
sessions
|
sessions
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.waiting_chat_session_list_by_chat(chat_ids)
|
||||||
|
sessions = {}
|
||||||
|
Chat.where(active: true, id: chat_ids).pluck(:id).each do |chat_id|
|
||||||
|
Chat::Session.where(chat_id: chat_id, state: ['waiting']).each do |session|
|
||||||
|
sessions[chat_id] ||= []
|
||||||
|
sessions[chat_id].push session.attributes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
sessions
|
||||||
|
end
|
||||||
|
|
||||||
=begin
|
=begin
|
||||||
|
|
||||||
get count running sessions in given chats
|
get count running sessions in given chats
|
||||||
|
|
|
@ -22,7 +22,11 @@ return is sent as message back to peer
|
||||||
# find first in waiting list
|
# find first in waiting list
|
||||||
chat_user = User.lookup(id: @session['id'])
|
chat_user = User.lookup(id: @session['id'])
|
||||||
chat_ids = Chat.agent_active_chat_ids(chat_user)
|
chat_ids = Chat.agent_active_chat_ids(chat_user)
|
||||||
chat_session = Chat::Session.where(state: 'waiting', chat_id: chat_ids).order(created_at: :asc).first
|
chat_session = if @payload['chat_id']
|
||||||
|
Chat::Session.where(state: 'waiting', chat_id: @payload['chat_id']).order(created_at: :asc).first
|
||||||
|
else
|
||||||
|
Chat::Session.where(state: 'waiting', chat_id: chat_ids).order(created_at: :asc).first
|
||||||
|
end
|
||||||
if !chat_session
|
if !chat_session
|
||||||
return {
|
return {
|
||||||
event: 'chat_session_start',
|
event: 'chat_session_start',
|
||||||
|
@ -37,25 +41,32 @@ return is sent as message back to peer
|
||||||
chat_session.preferences[:participants] = chat_session.add_recipient(@client_id)
|
chat_session.preferences[:participants] = chat_session.add_recipient(@client_id)
|
||||||
chat_session.save
|
chat_session.save
|
||||||
|
|
||||||
# send chat_session_init to client
|
session_attributes = chat_session.attributes
|
||||||
user = chat_session.agent_user
|
session_attributes['messages'] = []
|
||||||
data = {
|
Chat::Message.where(chat_session_id: chat_session.id).order(created_at: :asc).each do |message|
|
||||||
event: 'chat_session_start',
|
session_attributes['messages'].push message.attributes
|
||||||
data: {
|
end
|
||||||
state: 'ok',
|
|
||||||
agent: user,
|
# send chat_session_init to customer client
|
||||||
session_id: chat_session.session_id,
|
if session_attributes['messages'].blank?
|
||||||
chat_id: chat_session.chat_id,
|
user = chat_session.agent_user
|
||||||
},
|
data = {
|
||||||
}
|
event: 'chat_session_start',
|
||||||
# send to customer
|
data: {
|
||||||
chat_session.send_to_recipients(data, @client_id)
|
state: 'ok',
|
||||||
|
agent: user,
|
||||||
|
session_id: chat_session.session_id,
|
||||||
|
chat_id: chat_session.chat_id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
chat_session.send_to_recipients(data, @client_id)
|
||||||
|
end
|
||||||
|
|
||||||
# send to agent
|
# send to agent
|
||||||
data = {
|
data = {
|
||||||
event: 'chat_session_start',
|
event: 'chat_session_start',
|
||||||
data: {
|
data: {
|
||||||
session: chat_session.attributes,
|
session: session_attributes,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Sessions.send(@client_id, data)
|
Sessions.send(@client_id, data)
|
||||||
|
|
39
lib/sessions/event/chat_transfer.rb
Normal file
39
lib/sessions/event/chat_transfer.rb
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
class Sessions::Event::ChatTransfer < Sessions::Event::ChatBase
|
||||||
|
|
||||||
|
def run
|
||||||
|
return super if super
|
||||||
|
return if !permission_check('chat.agent', 'chat')
|
||||||
|
|
||||||
|
# find chat session
|
||||||
|
chat_session = Chat::Session.find_by(id: @payload['session_id'])
|
||||||
|
if !chat_session
|
||||||
|
return {
|
||||||
|
event: 'chat_session_start',
|
||||||
|
data: {
|
||||||
|
state: 'failed',
|
||||||
|
message: 'No session available.',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
chat_ids_to_notify = [chat_session.chat_id, @payload['chat_id']]
|
||||||
|
chat_session.chat_id = @payload['chat_id']
|
||||||
|
chat_session.state = 'waiting'
|
||||||
|
chat_session.save
|
||||||
|
|
||||||
|
# send state update with sessions to agents
|
||||||
|
Chat.broadcast_agent_state_update(chat_ids_to_notify)
|
||||||
|
|
||||||
|
# send transfer message to client
|
||||||
|
message = {
|
||||||
|
event: 'chat_session_notice',
|
||||||
|
data: {
|
||||||
|
session_id: chat_session.session_id,
|
||||||
|
message: 'Conversation transfered into other chat. Please stay tuned.',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
chat_session.send_to_recipients(message, @client_id)
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -753,6 +753,8 @@ do($ = window.jQuery, window) ->
|
||||||
@onSessionClosed pipe.data
|
@onSessionClosed pipe.data
|
||||||
when 'chat_session_left'
|
when 'chat_session_left'
|
||||||
@onSessionClosed pipe.data
|
@onSessionClosed pipe.data
|
||||||
|
when 'chat_session_notice'
|
||||||
|
@addStatus @T(pipe.data.message)
|
||||||
when 'chat_status_customer'
|
when 'chat_status_customer'
|
||||||
switch pipe.data.state
|
switch pipe.data.state
|
||||||
when 'online'
|
when 'online'
|
||||||
|
|
|
@ -1032,6 +1032,9 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
|
||||||
case 'chat_session_left':
|
case 'chat_session_left':
|
||||||
this.onSessionClosed(pipe.data);
|
this.onSessionClosed(pipe.data);
|
||||||
break;
|
break;
|
||||||
|
case 'chat_session_notice':
|
||||||
|
this.addStatus(this.T(pipe.data.message));
|
||||||
|
break;
|
||||||
case 'chat_status_customer':
|
case 'chat_status_customer':
|
||||||
switch (pipe.data.state) {
|
switch (pipe.data.state) {
|
||||||
case 'online':
|
case 'online':
|
||||||
|
|
3
public/assets/chat/chat.min.js
vendored
3
public/assets/chat/chat.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1858,42 +1858,40 @@ figcaption {
|
||||||
}
|
}
|
||||||
-->
|
-->
|
||||||
<script>
|
<script>
|
||||||
var chat = new ZammadChat({
|
function getSearchParameters() {
|
||||||
chatId: 1,
|
var prmstr = window.location.search.substr(1);
|
||||||
host: 'ws://localhost:6042',
|
return prmstr != null && prmstr != '' ? transformToAssocArray(prmstr) : {};
|
||||||
cssUrl: 'http://localhost:5000/assets/chat/chat.css',
|
}
|
||||||
debug: true
|
function transformToAssocArray( prmstr ) {
|
||||||
});
|
var params = {};
|
||||||
|
var prmarr = prmstr.split('&');
|
||||||
|
for ( var i = 0; i < prmarr.length; i++) {
|
||||||
|
var tmparr = prmarr[i].split('=');
|
||||||
|
params[tmparr[0]] = tmparr[1];
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
var hostname = window.location.hostname;
|
||||||
|
var port = window.location.port;
|
||||||
|
var params = getSearchParameters();
|
||||||
|
var host = 'ws://'+ (location.host || 'localhost').split(':')[0] +':6042'
|
||||||
|
if (params['port']) {
|
||||||
|
host = 'ws://' + hostname + ':' + params['port']
|
||||||
|
}
|
||||||
|
cssUrl = 'http://' + hostname + ':' + port + '/assets/chat/chat.css'
|
||||||
|
|
||||||
$('.settings :input').on({
|
var chat = new ZammadChat({
|
||||||
change: function(){
|
chatId: 2,
|
||||||
switch($(this).attr('data-option')){
|
host: host,
|
||||||
case "flat":
|
cssUrl: cssUrl,
|
||||||
$('.zammad-chat').toggleClass('zammad-chat--flat', this.checked);
|
debug: true,
|
||||||
break;
|
background: '#494d52',
|
||||||
case "color":
|
flat: true,
|
||||||
setScssVariable('themeColor', this.value);
|
idleTimeout: 1,
|
||||||
updateStyle();
|
idleTimeoutIntervallCheck: 0.5,
|
||||||
break;
|
inactiveTimeout: 2,
|
||||||
case "borderRadius":
|
inactiveTimeoutIntervallCheck: 0.5,
|
||||||
setScssVariable('borderRadius', this.value + "px");
|
waitingListTimeout: 1.2,
|
||||||
updateStyle();
|
waitingListTimeoutIntervallCheck: 0.5,
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
input: function(){
|
|
||||||
switch($(this).attr('data-option')){
|
|
||||||
case "borderRadius":
|
|
||||||
$('[data-option="borderRadius"]').val(this.value);
|
|
||||||
setScssVariable('borderRadius', this.value + "px");
|
|
||||||
updateStyle();
|
|
||||||
break;
|
|
||||||
case "fontSize":
|
|
||||||
$('[data-option="fontSize"]').val(this.value);
|
|
||||||
setScssVariable('fontSize', this.value + "px");
|
|
||||||
updateStyle();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
|
@ -47,6 +47,12 @@ RSpec.describe Sessions::Event::ChatSessionStart do
|
||||||
session: { 'id' => agent.id },
|
session: { 'id' => agent.id },
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
let(:chat_message_history) do
|
||||||
|
Chat::Message.create!(
|
||||||
|
chat_session_id: chat_session.id,
|
||||||
|
content: 'some message',
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Setting.set('chat', true)
|
Setting.set('chat', true)
|
||||||
|
@ -186,4 +192,37 @@ RSpec.describe Sessions::Event::ChatSessionStart do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when starting a chat session as agent with transfered conversation' do
|
||||||
|
it 'send out chat_session_start to customer and agent with already created messages' do
|
||||||
|
chat_message_history
|
||||||
|
expect(subject_as_agent.run).to eq(nil)
|
||||||
|
messages_to_customer = Sessions.queue('customer_session_id')
|
||||||
|
expect(messages_to_customer.count).to eq(0)
|
||||||
|
|
||||||
|
messages_to_agent = Sessions.queue(client_id)
|
||||||
|
expect(messages_to_agent.count).to eq(1)
|
||||||
|
expect(messages_to_agent[0]).to include(
|
||||||
|
'event' => 'chat_session_start',
|
||||||
|
'data' => hash_including(
|
||||||
|
'session' => hash_including(
|
||||||
|
'user_id' => agent.id,
|
||||||
|
'state' => 'running',
|
||||||
|
'preferences' => hash_including(
|
||||||
|
'participants' => ['customer_session_id', client_id]
|
||||||
|
),
|
||||||
|
'messages' => array_including(
|
||||||
|
hash_including(
|
||||||
|
'content' => 'some message',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'id' => chat_session.id,
|
||||||
|
'chat_id' => chat_session.chat_id,
|
||||||
|
'session_id' => chat_session.session_id,
|
||||||
|
'name' => nil,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
84
spec/lib/sessions/event/chat_transfer_spec.rb
Normal file
84
spec/lib/sessions/event/chat_transfer_spec.rb
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Sessions::Event::ChatTransfer do
|
||||||
|
let(:client_id) { rand(123_456_789) }
|
||||||
|
let(:chat) { Chat.first }
|
||||||
|
let(:chat_transfer_into) { Chat.create!(name: 'chat 2', updated_by_id: 1, created_by_id: 1) }
|
||||||
|
let(:chat_session) do
|
||||||
|
Sessions.create('customer_session_id', { 'id' => customer.id }, {})
|
||||||
|
Sessions.queue('customer_session_id')
|
||||||
|
Chat::Session.create(
|
||||||
|
chat_id: chat.id,
|
||||||
|
user_id: nil,
|
||||||
|
preferences: { participants: ['customer_session_id'] },
|
||||||
|
state: 'running',
|
||||||
|
)
|
||||||
|
end
|
||||||
|
let!(:agent) do
|
||||||
|
create(:agent_user, preferences: { chat: { active: { chat.id.to_s => 'on' } } })
|
||||||
|
end
|
||||||
|
let!(:customer) { create(:customer_user) }
|
||||||
|
let(:subject_as_agent) do
|
||||||
|
Sessions.create(client_id, { 'id' => agent.id }, {})
|
||||||
|
Sessions.queue(client_id)
|
||||||
|
described_class.new(
|
||||||
|
payload: { 'data' => { 'session_id' => chat_session.session_id }, 'session_id' => chat_session.id, 'chat_id' => chat_transfer_into.id },
|
||||||
|
user_id: agent.id,
|
||||||
|
client_id: client_id,
|
||||||
|
clients: {},
|
||||||
|
session: { 'id' => agent.id },
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
Setting.set('chat', true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when transfering a chat session as customer' do
|
||||||
|
let(:subject_as_customer) do
|
||||||
|
Sessions.create(client_id, { 'id' => customer.id }, {})
|
||||||
|
Sessions.queue(client_id)
|
||||||
|
described_class.new(
|
||||||
|
payload: { 'data' => { 'session_id' => chat_session.session_id }, 'chat_id' => chat_transfer_into.id },
|
||||||
|
user_id: customer.id,
|
||||||
|
client_id: client_id,
|
||||||
|
clients: {},
|
||||||
|
session: { 'id' => customer.id },
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'without chat.agent permissions' do
|
||||||
|
it 'send out no_permission event to user' do
|
||||||
|
expect(subject_as_customer.run).to eq(nil)
|
||||||
|
messages = Sessions.queue(client_id)
|
||||||
|
expect(messages.count).to eq(1)
|
||||||
|
expect(messages).to eq([
|
||||||
|
'event' => 'chat_error',
|
||||||
|
'data' => {
|
||||||
|
'state' => 'no_permission'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when transfering a chat session as agent' do
|
||||||
|
it 'send out chat_session_notice to customer and agent and set chat session to waiting' do
|
||||||
|
expect(subject_as_agent.run).to eq(nil)
|
||||||
|
|
||||||
|
messages_to_customer = Sessions.queue('customer_session_id')
|
||||||
|
expect(messages_to_customer.count).to eq(1)
|
||||||
|
expect(messages_to_customer[0]).to eq(
|
||||||
|
'event' => 'chat_session_notice',
|
||||||
|
'data' => {
|
||||||
|
'message' => 'Conversation transfered into other chat. Please stay tuned.',
|
||||||
|
'session_id' => chat_session.session_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
messages_to_agent = Sessions.queue(client_id)
|
||||||
|
expect(messages_to_agent.count).to eq(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in a new issue