Improved permanent task highlighting in nav bar.

This commit is contained in:
Martin Edenhofer 2016-04-30 00:21:39 +02:00
parent e6540720a3
commit 81e27f8901
24 changed files with 256 additions and 174 deletions

View file

@ -130,6 +130,11 @@ class App.Controller extends Spine.Controller
return if !$('#navigation').is(':visible')
$('#navigation').addClass('hide')
updateNavMenu: =>
delay = ->
App.Event.trigger('menu:render')
@delay(delay, 200)
scrollTo: (x = 0, y = 0, delay = 0) ->
a = ->
window.scrollTo(x, y)

View file

@ -6,7 +6,7 @@ class App.ChannelChat extends App.Controller
'click .js-selectBrowserWidth': 'selectBrowserWidth'
'click .js-swatch': 'usePaletteColor'
'click .js-toggle-chat': 'toggleChat'
'click .js-chatSetting': 'toggleChatSetting'
'change .js-chatSetting input': 'toggleChatSetting'
'click .js-eyedropper': 'pickColor'
elements:
@ -307,12 +307,7 @@ class App.ChannelChat extends App.Controller
toggleChatSetting: =>
value = @chatSetting.prop('checked')
setting = App.Setting.findByAttribute('name', 'chat')
setting.state_current = { value: value }
setting.save()
@Config.set('chat', value)
delay = -> App.Event.trigger('ui:rerender')
@delay(delay, 200)
App.Setting.set('chat', value)
updateParams: =>
quote = (value) ->

View file

@ -3,7 +3,7 @@ class App.ChannelForm extends App.Controller
events:
'change form.js-params': 'updateParams'
'keyup form.js-params': 'updateParams'
'click .js-formSetting': 'toggleFormSetting'
'change .js-formSetting input': 'toggleFormSetting'
elements:
'.js-paramsBlock': 'paramsBlock'
@ -16,10 +16,11 @@ class App.ChannelForm extends App.Controller
render: =>
App.Setting.unsubscribe(@subscribeId)
setting = App.Setting.findByAttribute('name', 'form_ticket_create')
setting = App.Setting.get('form_ticket_create')
@html App.view('channel/form')(
baseurl: window.location.origin
formSetting: setting.state_current.value
formSetting: setting
)
@paramsBlock.each (i, block) ->
@ -49,8 +50,6 @@ class App.ChannelForm extends App.Controller
toggleFormSetting: =>
value = @formSetting.prop('checked')
setting = App.Setting.findByAttribute('name', 'form_ticket_create')
setting.state_current = { value: value }
setting.save()
App.Setting.set('form_ticket_create', value)
App.Config.set( 'Form', { prio: 2000, name: 'Form', parent: '#channels', target: '#channels/form', controller: App.ChannelForm, role: ['Admin'] }, 'NavBarAdmin' )

View file

@ -55,12 +55,7 @@ class App.ControllerIntegrationBase extends App.Controller
msg: App.i18n.translateContent('Update successful!')
timeout: 2000
}
if @preferences
if @preferences.render
App.Event.trigger( 'ui:rerender' )
if @preferences.session_check
App.Auth.loginCheck()
App.Setting.preferencesPost(@)
fail: (settings, details) ->
App.Event.trigger 'notify', {
@ -73,25 +68,4 @@ class App.ControllerIntegrationBase extends App.Controller
value =
items: [params]
App.Setting.set(
@featureConfig,
value,
done: ->
App.Event.trigger 'notify', {
type: 'success'
msg: App.i18n.translateContent('Update successful!')
timeout: 2000
}
if @preferences
if @preferences.render
App.Event.trigger( 'ui:rerender' )
if @preferences.session_check
App.Auth.loginCheck()
fail: (settings, details) ->
App.Event.trigger 'notify', {
type: 'error'
msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!')
timeout: 2000
}
)
App.Setting.set(@featureConfig, value)

View file

@ -108,13 +108,8 @@ class App.SettingsAreaItem extends App.Controller
}
# rerender ui || get new collections and session data
if @setting.preferences
if @setting.preferences.render
ui.render()
App.Event.trigger( 'ui:rerender' )
App.Setting.preferencesPost(@setting)
if @setting.preferences.session_check
App.Auth.loginCheck()
fail: (settings, details) ->
ui.formEnable(e)
App.Event.trigger 'notify', {
@ -158,10 +153,10 @@ class App.SettingsAreaLogo extends App.Controller
if file.size && file.size > 1024 * 1024 * maxSiteInMb
App.Event.trigger 'notify', {
type: 'error'
msg: App.i18n.translateContent('File too big, max. %s MB allowed.', maxSiteInMb )
msg: App.i18n.translateContent('File too big, max. %s MB allowed.', maxSiteInMb)
timeout: 2000
}
@logoPreview.attr( 'src', '' )
@logoPreview.attr('src', '')
return
reader.readAsDataURL(file)
@ -209,4 +204,4 @@ class App.SettingsAreaLogo extends App.Controller
)
# add resized image
App.ImageService.resizeForApp( @params.logo, @logoPreview.width(), @logoPreview.height(), store )
App.ImageService.resizeForApp(@params.logo, @logoPreview.width(), @logoPreview.height(), store)

View file

@ -72,13 +72,8 @@ class App.SettingsForm extends App.Controller
timeout: 2000
}
# rerender ui || get new collections and session data
if @preferences
if @preferences.render
App.Event.trigger( 'ui:rerender' )
if @preferences.session_check
App.Auth.loginCheck()
# rerender ui || get new collections and session data
App.Setting.preferencesPost(@)
fail: (settings, details) ->
App.Event.trigger 'notify', {

View file

@ -86,8 +86,7 @@ class App.CustomerChat extends App.Controller
)
featureActive: =>
if @Config.get('chat')
return true
return true if @Config.get('chat')
false
render: ->
@ -164,6 +163,10 @@ class App.CustomerChat extends App.Controller
@title 'Customer Chat', true
@navupdate '#customer_chat'
active: (state) =>
return @shown if state is undefined
@shown = state
counter: =>
counter = 0
@ -239,11 +242,6 @@ class App.CustomerChat extends App.Controller
@stopPushState()
@pushState()
updateNavMenu: =>
delay = ->
App.Event.trigger('menu:render')
@delay(delay, 200, 'updateNavMenu')
updateMeta: =>
if @meta.waiting_chat_count && @maxChatWindows > @windowCount()
@acceptChatElement.addClass('is-clickable is-blinking')

View file

@ -8,6 +8,7 @@ class App.CTI extends App.Controller
return if !@isRole('CTI')
@list = []
@backends = []
@meta =
active: false
counter: 0
@ -18,40 +19,39 @@ class App.CTI extends App.Controller
@load()
App.Event.bind(
@bind('cti_event', (data) =>
if data.direction is 'in'
if data.state is 'newCall'
if @switch()
@notify(data)
return if @meta.state[data.id]
@meta.state[data.id] = true
@meta.counter += 1
@updateNavMenu()
if data.state is 'answer' || data.state is 'hangup'
return if !@meta.state[data.id]
delete @meta.state[data.id]
@meta.counter -= 1
@updateNavMenu()
'cti_event'
(data) =>
console.log('cti_event', data)
if data.direction is 'in'
if data.state is 'newCall'
if @switch()
@notify(data)
return if @meta.state[data.id]
@meta.state[data.id] = true
@meta.counter += 1
@updateNavMenu()
if data.state is 'answer' || data.state is 'hangup'
return if !@meta.state[data.id]
delete @meta.state[data.id]
@meta.counter -= 1
@updateNavMenu()
)
@bind('cti_list_push', (data) =>
if data.assets
App.Collection.loadAssets(data.assets)
if data.backends
@backends = data.backends
if data.list
@list = data.list
@render()
'cti_list_push'
)
@bind('auth', (data) =>
@meta.counter = 0
)
'cti_event'
)
App.Event.bind(
'cti_list_push'
(data) =>
if data.assets
App.Collection.loadAssets(data.assets)
if data.list
@list = data.list
@render()
'cti_list_push'
)
App.Event.bind(
'auth'
(data) =>
@meta.counter = 0
@bind('cti:reload', =>
@load()
'cti_reload'
)
# rerender view, e. g. on langauge change
@ -69,6 +69,8 @@ class App.CTI extends App.Controller
success: (data) =>
if data.assets
App.Collection.loadAssets(data.assets)
if data.backends
@backends = data.backends
if data.list
@list = data.list
@render()
@ -82,16 +84,27 @@ class App.CTI extends App.Controller
title: title
)
featureActive: =>
return true
return true if @Config.get('sipgate_integration')
false
featureActive: ->
true
render: ->
if !@isRole('CTI')
@renderScreenUnauthorized(objectName: 'CTI')
return
# check if min one backend is enabled
backendEnabled = false
for backend in @backends
if backend.enabled
backendEnabled = true
if !backendEnabled
@html App.view('cti/not_configured')(
backends: @backends
isAdmin: @isRole('Admin')
)
@updateNavMenu()
return
format = (time) ->
# Minutes and seconds
mins = ~~(time / 60)
@ -158,6 +171,10 @@ class App.CTI extends App.Controller
@title 'CTI', true
@navupdate '#cti'
active: (state) =>
return @shown if state is undefined
@shown = state
counter: =>
count = 0
for item in @list
@ -182,11 +199,6 @@ class App.CTI extends App.Controller
processData: true
)
updateNavMenu: =>
delay = ->
App.Event.trigger('menu:render')
@delay(delay, 200, 'updateNavMenu')
class CTIRouter extends App.ControllerPermanent
constructor: (params) ->
super

View file

@ -59,13 +59,11 @@ class App.Dashboard extends App.Controller
@navigate '#clues'
active: (state) =>
@activeState = state
return @shown if state is undefined
@shown = state
if state
@mayBeClues()
isActive: =>
@activeState
url: ->
'#dashboard'
@ -81,9 +79,6 @@ class App.Dashboard extends App.Controller
# highlight navbar
@navupdate '#dashboard'
hide: ->
# no
changed: ->
false
@ -112,6 +107,6 @@ class DashboardRouter extends App.ControllerPermanent
persistent: true
)
App.Config.set( 'dashboard', DashboardRouter, 'Routes' )
App.Config.set( 'Dashboard', { prio: 100, parent: '', name: 'Dashboard', target: '#dashboard', role: ['Agent'], class: 'dashboard' }, 'NavBar' )
App.Config.set( 'Dashboard', { controller: 'Dashboard', authentication: true }, 'permanentTask' )
App.Config.set('dashboard', DashboardRouter, 'Routes')
App.Config.set('Dashboard', { prio: 100, parent: '', name: 'Dashboard', target: '#dashboard', key: 'Dashboard', role: ['Agent'], class: 'dashboard' }, 'NavBar')
App.Config.set('Dashboard', { controller: 'Dashboard', authentication: true }, 'permanentTask')

View file

@ -1801,11 +1801,6 @@ class CustomerChatRef extends App.Controller
# write state
App.SessionStorage.set('chat_layout_ref', state)
updateNavMenu: =>
delay = ->
App.Event.trigger('menu:render')
@delay(delay, 200)
testChat: (chat, count) ->
for i in [0..count]
text = @questions[Math.floor(Math.random() * @questions.length)].question

View file

@ -55,9 +55,10 @@ class App.Navigation extends App.ControllerWidgetPermanent
@notificationWidget = undefined
renderMenu: =>
items = @getItems( navbar: @Config.get( 'NavBar' ) )
items = @getItems( navbar: @Config.get('NavBar') )
# apply counter and switch info from persistant controllers (if exists)
activeTab = {}
itemsNew = []
for item in items
shown = true
@ -70,6 +71,8 @@ class App.Navigation extends App.ControllerWidgetPermanent
item.counter = worker.counter()
if worker.switch
item.switch = worker.switch()
if worker.active && worker.active()
activeTab[item.target] = true
if worker.featureActive
if worker.featureActive()
shown = true
@ -80,16 +83,17 @@ class App.Navigation extends App.ControllerWidgetPermanent
items = itemsNew
# get open tabs to repopen on rerender
open_tab = {}
openTab = {}
@$('.open').children('a').each( (i,d) ->
href = $(d).attr('href')
open_tab[href] = true
openTab[href] = true
)
# render menu
@$('.js-menu').html App.view('navigation/menu')(
items: items
open_tab: open_tab
items: items
openTab: openTab
activeTab: activeTab
)
# bind on switch changes and execute it on controller

View file

@ -26,7 +26,6 @@ class App.OrganizationProfile extends App.Controller
meta.head = organization.displayName()
meta.title = organization.displayName()
meta.iconClass = organization.icon()
meta
url: =>
@ -41,9 +40,6 @@ class App.OrganizationProfile extends App.Controller
render: (organization) =>
# update taskbar with new meta data
App.TaskManager.touch(@task_key)
if !@doNotLog
@doNotLog = 1
@recentView('Organization', @organization_id)
@ -55,6 +51,7 @@ class App.OrganizationProfile extends App.Controller
new Object(
el: elLocal.find('.js-object-container')
organization: organization
task_key: @task_key
)
new App.TicketStats(
@ -83,6 +80,9 @@ class Object extends App.Controller
render: (organization) =>
# update taskbar with new meta data
App.TaskManager.touch(@task_key)
# get display data
organizationData = []
for attributeName, attributeConfig of App.Organization.attributesGet('view')
@ -117,6 +117,7 @@ class Object extends App.Controller
organization_id: organization.id
container: @el.closest('.content')
)
editOrganization = =>
new App.ControllerGenericEdit(
id: organization.id

View file

@ -29,10 +29,8 @@ class App.TicketOverview extends App.Controller
@delay(update, 2800, 'overview:fetch')
active: (state) =>
@activeState = state
isActive: =>
@activeState
return @shown if state is undefined
@shown = state
url: =>
"#ticket/view/#{@view}"
@ -399,7 +397,7 @@ class Table extends App.Controller
view: @view
# start bulk action observ
@el.append( @bulkForm.el )
@el.append(@bulkForm.el)
if @$('.table-overview').find('input[name="bulk"]:checked').length isnt 0
@bulkForm.show()
@ -431,7 +429,7 @@ class Table extends App.Controller
ticket_id = $(element).val()
for ticket_id_selected in ticketIDs
if ticket_id_selected is ticket_id
$(element).attr( 'checked', true )
$(element).attr('checked', true)
)
viewmode: (e) =>
@ -591,8 +589,8 @@ class BulkForm extends App.Controller
params.ticket_id = ticket.id
params.form_id = @form_id
sender = App.TicketArticleSender.findByAttribute( 'name', 'Agent' )
type = App.TicketArticleType.find( params['type_id'] )
sender = App.TicketArticleSender.findByAttribute('name', 'Agent')
type = App.TicketArticleType.find(params['type_id'])
params.sender_id = sender.id
if !params['internal']
@ -789,7 +787,7 @@ class TicketOverviewRouter extends App.ControllerPermanent
persistent: true
)
App.Config.set( 'ticket/view', TicketOverviewRouter, 'Routes' )
App.Config.set( 'ticket/view/:view', TicketOverviewRouter, 'Routes' )
App.Config.set( 'TicketOverview', { controller: 'TicketOverview', authentication: true }, 'permanentTask' )
App.Config.set( 'TicketOverview', { prio: 1000, parent: '', name: 'Overviews', target: '#ticket/view', role: ['Agent', 'Customer'], class: 'overviews' }, 'NavBar' )
App.Config.set('ticket/view', TicketOverviewRouter, 'Routes')
App.Config.set('ticket/view/:view', TicketOverviewRouter, 'Routes')
App.Config.set('TicketOverview', { controller: 'TicketOverview', authentication: true }, 'permanentTask')
App.Config.set('TicketOverview', { prio: 1000, parent: '', name: 'Overviews', target: '#ticket/view', key: 'TicketOverview', role: ['Agent', 'Customer'], class: 'overviews' }, 'NavBar')

View file

@ -8,16 +8,13 @@ class App.UserProfile extends App.Controller
return
# fetch new data if needed
@subscribeId = App.User.full(@user_id, @render)
App.User.full(@user_id, @render)
# rerender view, e. g. on langauge change
@bind 'ui:rerender', =>
return if !@authenticate(true)
@render(App.User.fullLocal(@user_id))
release: =>
App.User.unsubscribe(@subscribeId)
meta: =>
meta =
url: @url()
@ -43,9 +40,6 @@ class App.UserProfile extends App.Controller
render: (user) =>
# update taskbar with new meta data
App.TaskManager.touch(@task_key)
if !@doNotLog
@doNotLog = 1
@recentView('User', @user_id)
@ -55,8 +49,9 @@ class App.UserProfile extends App.Controller
))
new Object(
el: elLocal.find('.js-object-container')
user: user
el: elLocal.find('.js-object-container')
user: user
task_key: @task_key
)
new App.TicketStats(
@ -70,7 +65,6 @@ class App.UserProfile extends App.Controller
genericObject: user
)
class Object extends App.Controller
events:
'focusout [contenteditable]': 'update'
@ -86,6 +80,9 @@ class Object extends App.Controller
render: (user) =>
# update taskbar with new meta data
App.TaskManager.touch(@task_key)
# get display data
userData = []
for attributeName, attributeConfig of App.User.attributesGet('view')

View file

@ -10,5 +10,29 @@ class App.Setting extends App.Model
@set: (name, value, options = {}) ->
setting = App.Setting.findByAttribute('name', name)
setting.state_current.value = value
if !options.done
options.done = ->
App.Setting.preferencesPost(@)
if !options.fail
options.fail = (settings, details) ->
App.Event.trigger 'notify', {
type: 'error'
msg: App.i18n.translateContent(details.error_human || details.error || 'Unable to update object!')
timeout: 2000
}
setting.save(options)
App.Config.set(name, value)
@preferencesPost: (setting) ->
return if !setting.preferences
if setting.preferences.render
App.Event.trigger('ui:rerender')
if setting.preferences.trigger
trigger = setting.preferences.trigger
delay = -> App.Event.trigger(trigger)
App.Delay.set(delay, 20)
if setting.preferences.session_check
App.Auth.loginCheck()

View file

@ -0,0 +1,10 @@
<div class="main flex">
<h1><%- @T('Caller log') %></h1>
<p><%- @T('Sorry, currently is no CTI backend enabled!') %></p>
<p><%- @T('We support') %>:</p>
<ul>
<% for backend in @backends: %>
<li><% if @isAdmin: %><a href="<%- backend.url %>"><% end %><%= backend.name %><% if @isAdmin: %></a><% end %>
<% end %>
</ul>
</div>

View file

@ -1,6 +1,6 @@
<% for item in @items: %>
<% if item.child: %>
<div class="dropdown<%- ' open' if @open_tab[item.target] %>">
<div class="dropdown<%- ' open' if @openTab[item.target] %>">
<a href="<%= item.target %>" class="menu-item js-<%- item.class %>MenuItem dropdown-toggle" data-toggle="dropdown">
<%- @Icon(item.class, 'menu-item-icon') %>
<span class="menu-item-name flex">
@ -21,7 +21,7 @@
</ul>
</div>
<% else: %>
<a class="menu-item js-<%- item.class %>MenuItem" href="<%= item.target %>" data-key="<%- item.key %>">
<a class="menu-item js-<%- item.class %>MenuItem <% if @activeTab[item.target]: %>is-active<% end %>" href="<%= item.target %>" data-key="<%- item.key %>">
<%- @Icon(item.class, 'menu-item-icon') %>
<span class="menu-item-name">
<%- @T(item.name) %>

View file

@ -0,0 +1,32 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class CtiController < ApplicationController
before_action :authentication_check
# list current caller log
def index
return if deny_if_not_role('CTI')
backends = [
{
name: 'sipgate.io',
enabled: Setting.get('sipgate_integration'),
url: '#system/integration/sipgate',
}
]
result = Cti::Log.log
result[:backends] = backends
render json: result
end
# set caller log to done
def done
return if deny_if_not_role('CTI')
log = Cti::Log.find(params['id'])
log.done = params['done']
log.save
render json: {}
end
end

View file

@ -4,23 +4,6 @@ require 'builder'
class Integration::SipgateController < ApplicationController
# list current caller log
def index
return if !authentication_check
return if deny_if_not_role('CTI')
render json: Cti::Log.log
end
# set caller log to done
def done
return if !authentication_check
return if deny_if_not_role('CTI')
log = Cti::Log.find(params['id'])
log.done = params['done']
log.save
render json: {}
end
# notify about inbound call / block inbound call
def in
http_log_config facility: 'sipgate.io'

View file

@ -0,0 +1,51 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
require 'signature_detection'
class Transaction::CtiCallerIdDetection
=begin
{
object: 'Ticket',
type: 'create',
object_id: 123,
via_web: true,
user_id: 123,
},
{
object: 'User',
type: 'update',
object_id: 123,
via_web: true,
changes: {
'attribute1' => [before, now],
'attribute2' => [before, now],
}
user_id: 123,
},
=end
def initialize(item, params = {})
@item = item
@params = params
end
def perform
# return if we run import mode
return if Setting.get('import_mode')
if @item[:object] == 'Ticket' && @item[:type] == 'create'
ticket = Ticket.lookup(id: @item[:object_id])
return if !ticket
Cti::CallerId.build(ticket)
end
if @item[:object] == 'User'
user = User.lookup(id: @item[:object_id])
return if !user
Cti::CallerId.build(user)
end
end
end

6
config/routes/cti.rb Normal file
View file

@ -0,0 +1,6 @@
Zammad::Application.routes.draw do
match '/api/v1/cti/log', to: 'cti#index', via: :get
match '/api/v1/cti/done/:id', to: 'cti#done', via: :post
end

View file

@ -1,7 +1,5 @@
Zammad::Application.routes.draw do
match '/api/v1/cti/log', to: 'integration/sipgate#index', via: :get
match '/api/v1/cti/done/:id', to: 'integration/sipgate#done', via: :post
match '/api/v1/sipgate/in', to: 'integration/sipgate#in', via: :post
match '/api/v1/sipgate/out', to: 'integration/sipgate#out', via: :post

View file

@ -0,0 +1,15 @@
class UpdateCti < ActiveRecord::Migration
def up
setting = Setting.find_by(name: 'sipgate_integration')
if setting
setting.preferences = { prio: 1, trigger: 'cti:reload' }
setting.save
end
setting = Setting.find_by(name: 'chat')
if setting
setting.preferences = { trigger: 'menu:render' }
setting.save
end
end
end

View file

@ -1198,7 +1198,7 @@ Setting.create_if_not_exists(
},
],
},
preferences: { render: true },
preferences: { trigger: 'menu:render' },
state: false,
frontend: true
)
@ -1851,7 +1851,7 @@ Setting.create_if_not_exists(
],
},
state: false,
preferences: { prio: 1 },
preferences: { prio: 1, trigger: 'cti:reload' },
frontend: false
)
Setting.create_if_not_exists(