diff --git a/app/assets/javascripts/app/controllers/_default_navbar.coffee b/app/assets/javascripts/app/controllers/_default_navbar.coffee
index 049fe1ae2..df8a05fe9 100644
--- a/app/assets/javascripts/app/controllers/_default_navbar.coffee
+++ b/app/assets/javascripts/app/controllers/_default_navbar.coffee
@@ -15,5 +15,5 @@ App.Config.set( 'Admin', { prio: 9000, parent: '', name: 'Admin', translate: tru
App.Config.set( 'New', { prio: 20000, parent: '', name: 'New', translate: true, target: '#new', class: 'add' }, 'NavBarRight' )
App.Config.set( 'Misc', { prio: 90000, parent: '', name: 'Tools', translate: true, target: '#tools', child: true, class: 'tools' }, 'NavBar' )
-#App.Config.set( 'Misc1', { prio: 1600, parent: '#tools', name: 'Test 1', target: '#test1', role: [ 'Admin' ] }, 'NavBar' )
-#App.Config.set( 'Misc2', { prio: 1700, parent: '#tools', name: 'Test 2', target: '#test2', role: [ 'Admin' ] }, 'NavBar' )
+App.Config.set( 'Misc1', { prio: 1600, parent: '#tools', name: 'Test 1', target: '#test1', role: [ 'Admin' ] }, 'NavBar' )
+App.Config.set( 'Misc2', { prio: 1700, parent: '#tools', name: 'Test 2', target: '#test2', role: [ 'Admin' ] }, 'NavBar' )
diff --git a/app/assets/javascripts/app/controllers/layout_ref.coffee b/app/assets/javascripts/app/controllers/layout_ref.coffee
index 777175dfb..bc1fca40f 100644
--- a/app/assets/javascripts/app/controllers/layout_ref.coffee
+++ b/app/assets/javascripts/app/controllers/layout_ref.coffee
@@ -1657,4 +1657,370 @@ class PrimaryEmailRef extends App.ControllerContent
App.Config.set( 'layout_ref/primary_email', PrimaryEmailRef, 'Routes' )
+class App.CustomerChatRef extends App.ControllerContent
+ @extend Spine.Events
+
+ questions: [
+ {
+ question: "Der dümmste Bauer hat die dicksten ..?"
+ answers: ["Kartoffeln"]
+ },
+ {
+ question: "Welchen Wein besang einst Udo Jürgens?"
+ answers: ["griechisch"]
+ },
+ {
+ question: "Was behandelt ein Logopäde?"
+ answers: ["Sprachstörung"]
+ },
+ {
+ question: "In welcher Stadt ist das Porsche Stammwerk?"
+ answers: ["Stuttgart"]
+ },
+ {
+ question: "Wer erfand den legendären C64-Computer?"
+ answers: ["Commodore"]
+ },
+ {
+ question: 'Im Englischen steht "Lost And Found" für ..?'
+ answers: ["Fundbüro"]
+ },
+ {
+ question: 'Welches Möbelstück ist und war besonders in Sigmund Freuds Arbeitszimmer bekannt?'
+ answers: ["Couch"]
+ },
+ {
+ question: 'Wenn es einem gut geht, lebt man "wie die Made im .."?'
+ answers: ["Speck"]
+ },
+ {
+ question: 'Von welcher Sportart handelt der US-amerikanische Film "Rocky"?'
+ answers: ["Boxen"]
+ },
+ {
+ question: 'Wo soll man hingehen, wenn man sich weit entfernen soll? Dahin wo ..?'
+ answers: ["Pfeffer", "wächst"]
+ },
+ {
+ question: 'Welches internationale Autokennzeichen hat Spanien?'
+ answers: ["ES"]
+ },
+ {
+ question: 'Wenn man sich ärgert sagt man "Verdammt und .."?'
+ answers: ["zugenäht"]
+ },
+ {
+ question: 'Bei welchem Spiel muss man ohne zu zittern Stäbchen sammeln?'
+ answers: ["Mikado"]
+ },
+ {
+ question: 'Wann wurde Znuny gegründet?'
+ answers: ["2012"]
+ }
+ ]
+
+ constructor: ->
+ super
+
+ @i = 0
+ @chatWindows = []
+ @totalQuestions = 7
+ @answered = 0
+ @correct = 0
+ @wrong = 0
+ @maxChats = 4;
+
+ @render()
+
+ render: ->
+ @html App.view('layout_ref/customer_chat')()
+
+ @addChat()
+
+ # @testChat @chatWindows[0], 100
+ @initQuiz()
+
+ testChat: (chat, count) ->
+ for i in [0..count]
+ text = @questions[Math.floor(Math.random() * @questions.length)].question
+ chat.addMessage text, if i % 2 then 'customer' else 'agent'
+
+ addChat: ->
+ chat = new chatWindowRef
+ name: "Quizmaster-#{ ++@i }"
+
+ @on 'layout-has-changed', @propagateLayoutChange
+
+ @$('.chat-workspace').append(chat.el)
+ @chatWindows.push chat
+
+ propagateLayoutChange: (event) =>
+ # adjust scroll position on layoutChange
+ console.log "propagateLayoutChange", event
+
+ for chat in @chatWindows
+ chat.trigger 'layout-changed'
+
+ initQuiz: ->
+ @chatWindows[0].addStatusMessage('To start the quiz type Start ')
+ @chatWindows[0].bind "answer", @startQuiz
+
+ startQuiz: (answer) =>
+ return false unless answer is "Start"
+
+ @chatWindows[0].unbind "answer"
+
+ @nextQuestion()
+
+ nextQuestion: ->
+ if not @questions.length
+ @currentChat.addStatusMessage("Du hast #{ @correct } von #{ @totalQuestions } Fragen richtig beantwortet!")
+ for chat in @chatWindows
+ chat.unbind "answer"
+ if chat is not @currentChat
+ chat.goOffline()
+ return
+
+ if @chatWindows.length < @maxChats and Math.random() < 0.2
+ @addChat()
+ randomWindowId = @chatWindows.length-1
+ else
+ # maybe take a chat offline
+ if @chatWindows.length > 1 and Math.random() > 0.85
+ randomWindowId = Math.floor(Math.random()*@chatWindows.length)
+ [killedChat] = @chatWindows.splice randomWindowId, 1
+ killedChat.goOffline()
+
+ randomWindowId = Math.floor(Math.random()*@chatWindows.length)
+
+ randomQuestionId = Math.floor(Math.random()*@questions.length)
+
+ @currentQuestion = @questions.splice(randomQuestionId, 1)[0]
+
+ newChat = @chatWindows[randomWindowId]
+
+ messageDelay = 500
+
+ if newChat != @currentChat
+ @currentChat.unbind("answer") if @currentChat
+ @currentChat = newChat
+ @currentChat.bind "answer", @onQuestionAnswer
+ messageDelay = 1500
+
+ @currentChat.showWritingLoader()
+
+ setTimeout @currentChat.receiveMessage, messageDelay + Math.random() * 1000, @currentQuestion.question
+
+ onQuestionAnswer: (answer) =>
+ match = false
+
+ for text in @currentQuestion.answers
+ if answer.match( new RegExp(text,'i') )
+ match = true
+
+ @answered++
+
+ if match
+ @correct++
+ @currentChat.receiveMessage _.shuffle(['😀','😃','😊','😍','😎','😏','👍','😌','😇','👌'])[0]
+ else
+ @wrong++
+ @currentChat.receiveMessage _.shuffle(['👎','💩','😰','😩','😦','😧','😟','😠','😡','😞','😢','😒','😕'])[0]
+
+ if @answerd is @totalQuestions
+ @finishQuiz()
+ else
+ @nextQuestion()
+
+
+
+App.Config.set( 'layout_ref/customer_chat', App.CustomerChatRef, 'Routes' )
+
+App.Config.set( 'Chat', { prio: 300, parent: '', name: 'Customer Chat', target: '#layout_ref/customer_chat', switch: true, counter: true, role: ['Agent'], class: 'chat' }, 'NavBar' )
+# App.Config.set( 'Chat', { controller: 'CustomerChatRef', authentication: true }, 'permanentTask' )
+
+
+class chatWindowRef extends Spine.Controller
+ @extend Spine.Events
+
+ className: 'chat-window'
+
+ events:
+ 'keydown .js-customerChatInput': 'onKeydown'
+ 'focus .js-customerChatInput': 'clearUnread'
+ 'click': 'clearUnread'
+ 'click .js-send': 'sendMessage'
+ 'click .js-close': 'close'
+
+ elements:
+ '.js-customerChatInput': 'input'
+ '.js-status': 'status'
+ '.js-body': 'body'
+ '.js-scrollHolder': 'scrollHolder'
+
+ sound:
+ message: new Audio('assets/sounds/chat_message.mp3')
+ window: new Audio('assets/sounds/chat_new.mp3')
+
+ constructor: ->
+ super
+
+ @showTimeEveryXMinutes = 1
+ @lastTimestamp
+ @lastAddedType
+ @render()
+ @sound.window.play()
+
+ @on 'layout-change', @scrollToBottom
+
+ render: ->
+ @html App.view('layout_ref/customer_chat_window')
+ name: @options.name
+
+ @el.one 'transitionend', @onTransitionend
+
+ # make sure animation will run
+ setTimeout (=> @el.addClass('is-open')), 0
+
+ # @addMessage 'Hello. My name is Roger, how can I help you?', 'agent'
+
+ onTransitionend: (event) =>
+ # chat window is done with animation - adjust scroll-bars
+ # of sibling chat windows
+ @trigger 'layout-has-changed'
+
+ if event.data and event.data.callback
+ event.data.callback()
+
+ close: =>
+ @el.one 'transitionend', { callback: @release }, @onTransitionend
+ @el.removeClass('is-open')
+
+ release: =>
+ @trigger 'closed'
+ super
+
+ clearUnread: =>
+ @$('.chat-message--new').removeClass('chat-message--new')
+ @updateModified(false)
+
+ onKeydown: (event) =>
+ TABKEY = 9;
+ ENTERKEY = 13;
+
+ switch event.keyCode
+ when TABKEY
+ allChatInputs = $('.js-customerChatInput').not('[disabled="disabled"]')
+ chatCount = allChatInputs.size()
+ index = allChatInputs.index(@input)
+
+ if chatCount > 1
+ switch index
+ when chatCount-1
+ if !event.shiftKey
+ # State: tab without shift on last input
+ # Jump to first input
+ event.preventDefault()
+ allChatInputs.eq(0).focus()
+ when 0
+ if event.shiftKey
+ # State: tab with shift on first input
+ # Jump to last input
+ event.preventDefault()
+ allChatInputs.eq(chatCount-1).focus()
+
+ when ENTERKEY
+ if !event.shiftKey
+ event.preventDefault()
+ @sendMessage()
+
+ sendMessage: =>
+ return if !@input.html()
+
+ @addMessage @input.html(), 'agent'
+
+ @trigger "answer", @input.html()
+
+ @input.html('')
+
+ updateModified: (state) =>
+ @status.toggleClass('is-modified', state)
+
+ receiveMessage: (message) =>
+ isFocused = @input.is(':focus')
+
+ @removeWritingLoader()
+ @addMessage(message, 'customer', !isFocused)
+
+ if !isFocused
+ @updateModified(true)
+ @sound.message.play()
+
+ addMessage: (message, sender, isNew) =>
+ @maybeAddTimestamp()
+
+ @lastAddedType = sender
+
+ @body.append App.view('layout_ref/customer_chat_message')
+ message: message
+ sender: sender
+ isNew: isNew
+ timestamp: Date.now()
+
+ @scrollToBottom()
+
+ showWritingLoader: =>
+ @maybeAddTimestamp()
+ @body.append App.view('layout_ref/customer_chat_loader')()
+
+ @scrollToBottom()
+
+ removeWritingLoader: =>
+ @$('.js-loader').remove()
+
+ goOffline: =>
+ @addStatusMessage("#{ @options.name } 's connection got closed")
+ @status.attr('data-status', 'offline')
+ @el.addClass('is-offline')
+ @input.attr('disabled', true)
+
+ maybeAddTimestamp: ->
+ timestamp = Date.now()
+
+ if !@lastTimestamp or timestamp - @lastTimestamp > @showTimeEveryXMinutes * 60000
+ label = 'Today'
+ time = new Date().toTimeString().substr(0,5)
+ if @lastAddedType is 'timestamp'
+ # update last time
+ @updateLastTimestamp label, time
+ @lastTimestamp = timestamp
+ else
+ @addTimestamp label, time
+ @lastTimestamp = timestamp
+ @lastAddedType = 'timestamp'
+
+ addTimestamp: (label, time) =>
+ @body.append App.view('layout_ref/customer_chat_timestamp')
+ label: label
+ time: time
+
+ updateLastTimestamp: (label, time) ->
+ @body
+ .find('.js-timestamp')
+ .last()
+ .replaceWith App.view('layout_ref/customer_chat_timestamp')
+ label: label
+ time: time
+
+ addStatusMessage: (message) ->
+ @body.append App.view('layout_ref/customer_chat_status_message')
+ message: message
+
+ @scrollToBottom()
+
+ scrollToBottom: ->
+ @scrollHolder.scrollTop(@scrollHolder.prop('scrollHeight'))
+
+
+
App.Config.set( 'LayoutRef', { prio: 1700, parent: '#current_user', name: 'Layout Reference', translate: true, target: '#layout_ref', role: [ 'Admin' ] }, 'NavBarRight' )
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/agent_ticket_view/navbar.jst.eco b/app/assets/javascripts/app/views/agent_ticket_view/navbar.jst.eco
index b9378d406..b49617456 100644
--- a/app/assets/javascripts/app/views/agent_ticket_view/navbar.jst.eco
+++ b/app/assets/javascripts/app/views/agent_ticket_view/navbar.jst.eco
@@ -4,7 +4,7 @@
class="active"<% end %>>
<%- @T(item.name) %>
- <%= item.count %>
+ <%= item.count %>
<% end %>
diff --git a/app/assets/javascripts/app/views/layout_ref/customer_chat.jst.eco b/app/assets/javascripts/app/views/layout_ref/customer_chat.jst.eco
new file mode 100644
index 000000000..4381c7a4d
--- /dev/null
+++ b/app/assets/javascripts/app/views/layout_ref/customer_chat.jst.eco
@@ -0,0 +1,24 @@
+
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/layout_ref/customer_chat_loader.jst.eco b/app/assets/javascripts/app/views/layout_ref/customer_chat_loader.jst.eco
new file mode 100644
index 000000000..bc76c0e36
--- /dev/null
+++ b/app/assets/javascripts/app/views/layout_ref/customer_chat_loader.jst.eco
@@ -0,0 +1,7 @@
+
+
+ <%- @Icon('loading') %>
+ <%- @Icon('loading') %>
+ <%- @Icon('loading') %>
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/layout_ref/customer_chat_message.jst.eco b/app/assets/javascripts/app/views/layout_ref/customer_chat_message.jst.eco
new file mode 100644
index 000000000..096477697
--- /dev/null
+++ b/app/assets/javascripts/app/views/layout_ref/customer_chat_message.jst.eco
@@ -0,0 +1 @@
+<%- @message %>
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/layout_ref/customer_chat_status_message.jst.eco b/app/assets/javascripts/app/views/layout_ref/customer_chat_status_message.jst.eco
new file mode 100644
index 000000000..eff398aa6
--- /dev/null
+++ b/app/assets/javascripts/app/views/layout_ref/customer_chat_status_message.jst.eco
@@ -0,0 +1 @@
+<%- @message %>
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/layout_ref/customer_chat_timestamp.jst.eco b/app/assets/javascripts/app/views/layout_ref/customer_chat_timestamp.jst.eco
new file mode 100644
index 000000000..c3b688a8e
--- /dev/null
+++ b/app/assets/javascripts/app/views/layout_ref/customer_chat_timestamp.jst.eco
@@ -0,0 +1 @@
+<%= @label %> <%= @time %>
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/layout_ref/customer_chat_window.jst.eco b/app/assets/javascripts/app/views/layout_ref/customer_chat_window.jst.eco
new file mode 100644
index 000000000..183d31f4d
--- /dev/null
+++ b/app/assets/javascripts/app/views/layout_ref/customer_chat_window.jst.eco
@@ -0,0 +1,20 @@
+
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/layout_ref/index.jst.eco b/app/assets/javascripts/app/views/layout_ref/index.jst.eco
index 14be56e32..516f25930 100644
--- a/app/assets/javascripts/app/views/layout_ref/index.jst.eco
+++ b/app/assets/javascripts/app/views/layout_ref/index.jst.eco
@@ -4,6 +4,7 @@
<% else: %>
-
+ >
<%- @Icon(item.class, 'nav-icon') %>
<%- @T(item.name) %>
+ <% if item.counter: %>
+
+ <% end %>
+ <% if item.switch: %>
+
+
+
+
+ <% end %>
<% end %>
diff --git a/app/assets/javascripts/app/views/navigation/personal.jst.eco b/app/assets/javascripts/app/views/navigation/personal.jst.eco
index 2a341186c..a8ea0e95a 100644
--- a/app/assets/javascripts/app/views/navigation/personal.jst.eco
+++ b/app/assets/javascripts/app/views/navigation/personal.jst.eco
@@ -20,7 +20,7 @@
<% if item.translate: %><%- @T( item.name ) %><% else: %><%= item.name %><% end %>
- <% if item['count'] isnt undefined: %><%= item['count'] %> <% end %>
+ <% if item['count'] isnt undefined: %><%= item['count'] %> <% end %>
<% if item.iconClass: %><%- @Icon(item.iconClass) %><% end %>
diff --git a/app/assets/stylesheets/bootstrap.css b/app/assets/stylesheets/bootstrap.css
index c0f58c00f..ac66a06a7 100644
--- a/app/assets/stylesheets/bootstrap.css
+++ b/app/assets/stylesheets/bootstrap.css
@@ -3513,51 +3513,6 @@ a.label:focus {
.label-danger[href]:focus {
background-color: #c9302c;
}
-.badge {
- display: inline-block;
- min-width: 10px;
- padding: 3px 7px;
- font-size: 12px;
- font-weight: bold;
- line-height: 1;
- color: #fff;
- text-align: center;
- white-space: nowrap;
- vertical-align: baseline;
- background-color: #777;
- border-radius: 10px;
-}
-.badge:empty {
- display: none;
-}
-.btn .badge {
- position: relative;
- top: -1px;
-}
-.btn-xs .badge {
- top: 0;
- padding: 1px 5px;
-}
-a.badge:hover,
-a.badge:focus {
- color: #fff;
- text-decoration: none;
- cursor: pointer;
-}
-.list-group-item.active > .badge,
-.nav-pills > .active > a > .badge {
- color: #337ab7;
- background-color: #fff;
-}
-.list-group-item > .badge {
- float: right;
-}
-.list-group-item > .badge + .badge {
- margin-right: 5px;
-}
-.nav-pills > li > a > .badge {
- margin-left: 3px;
-}
.jumbotron {
padding: 30px 15px;
margin-bottom: 30px;
diff --git a/app/assets/stylesheets/font.css b/app/assets/stylesheets/font.css
index d583574a6..566c3e7bd 100755
--- a/app/assets/stylesheets/font.css
+++ b/app/assets/stylesheets/font.css
@@ -1,35 +1,39 @@
@font-face {
- font-family: 'Fira Sans';
- src: url('fonts/FiraSans-Bold.eot');
- src: url('fonts/FiraSans-Bold.woff2') format('woff2'),
- url('fonts/FiraSans-Bold.woff') format('woff'),
- url('fonts/FiraSans-Bold.ttf') format('truetype');
- font-weight: bold;
- font-style: normal;
+ font-family: 'Fira Sans';
+ src: url('fonts/FiraSans-Bold.eot');
+ src: url('fonts/FiraSans-Bold.woff2') format('woff2'),
+ url('fonts/FiraSans-Bold.woff') format('woff'),
+ url('fonts/FiraSans-Bold.ttf') format('truetype');
+ font-weight: bold;
+ font-style: normal;
}
-
-
-
@font-face {
- font-family: 'Fira Sans';
- src: url('fonts/FiraSans-Regular.eot');
- src: url('fonts/FiraSans-Regular.woff2') format('woff2'),
- url('fonts/FiraSans-Regular.woff') format('woff'),
- url('fonts/FiraSans-Regular.ttf') format('truetype');
- font-weight: normal;
- font-style: normal;
+ font-family: 'Fira Sans';
+ src: url('fonts/FiraSans-Regular.eot');
+ src: url('fonts/FiraSans-Regular.woff2') format('woff2'),
+ url('fonts/FiraSans-Regular.woff') format('woff'),
+ url('fonts/FiraSans-Regular.ttf') format('truetype');
+ font-weight: normal;
+ font-style: normal;
}
-
-
+@font-face {
+ font-family: 'Fira Sans';
+ src: url('fonts/FiraSans-Medium.eot');
+ src: url('fonts/FiraSans-Medium.woff2') format('woff2'),
+ url('fonts/FiraSans-Medium.woff') format('woff'),
+ url('fonts/FiraSans-Medium.ttf') format('truetype');
+ font-weight: 500;
+ font-style: normal;
+}
@font-face {
- font-family: 'Fira Sans';
- src: url('fonts/FiraSans-Light.eot');
- src: url('fonts/FiraSans-Light.woff2') format('woff2'),
- url('fonts/FiraSans-Light.woff') format('woff'),
- url('fonts/FiraSans-Light.ttf') format('truetype');
- font-weight: 300;
- font-style: normal;
+ font-family: 'Fira Sans';
+ src: url('fonts/FiraSans-Light.eot');
+ src: url('fonts/FiraSans-Light.woff2') format('woff2'),
+ url('fonts/FiraSans-Light.woff') format('woff'),
+ url('fonts/FiraSans-Light.ttf') format('truetype');
+ font-weight: 300;
+ font-style: normal;
}
\ No newline at end of file
diff --git a/app/assets/stylesheets/svg-dimensions.css b/app/assets/stylesheets/svg-dimensions.css
index e698d73d9..a81438545 100644
--- a/app/assets/stylesheets/svg-dimensions.css
+++ b/app/assets/stylesheets/svg-dimensions.css
@@ -2,6 +2,7 @@
.icon-arrow-left { width: 7px; height: 13px; }
.icon-arrow-right { width: 7px; height: 13px; }
.icon-arrow-up { width: 13px; height: 7px; }
+.icon-chat { width: 24px; height: 24px; }
.icon-checkbox-checked { width: 11px; height: 11px; }
.icon-checkbox { width: 11px; height: 11px; }
.icon-checkmark { width: 16px; height: 14px; }
@@ -55,9 +56,6 @@
.icon-phone { width: 17px; height: 17px; }
.icon-plus-small { width: 16px; height: 16px; }
.icon-plus { width: 20px; height: 20px; }
-.icon-priority-modified-inner-circle { width: 16px; height: 16px; }
-.icon-priority-modified-outer-circle { width: 16px; height: 16px; }
-.icon-priority { width: 16px; height: 16px; }
.icon-radio-checked { width: 11px; height: 11px; }
.icon-radio { width: 11px; height: 11px; }
.icon-received-calls { width: 17px; height: 17px; }
@@ -67,11 +65,11 @@
.icon-reply { width: 16px; height: 16px; }
.icon-signout { width: 15px; height: 19px; }
.icon-split { width: 16px; height: 16px; }
+.icon-status-modified-inner-circle { width: 16px; height: 16px; }
+.icon-status-modified-outer-circle { width: 16px; height: 16px; }
.icon-status { width: 16px; height: 16px; }
.icon-stopwatch { width: 77px; height: 83px; }
.icon-switchView { width: 19px; height: 18px; }
-.icon-task-state-modified-inner-circle { width: 16px; height: 16px; }
-.icon-task-state-modified-outer-circle { width: 16px; height: 16px; }
.icon-task-state { width: 16px; height: 16px; }
.icon-team { width: 16px; height: 16px; }
.icon-templates { width: 24px; height: 24px; }
diff --git a/app/assets/stylesheets/zammad.scss b/app/assets/stylesheets/zammad.scss
index c03e3980f..1a52d8bc0 100644
--- a/app/assets/stylesheets/zammad.scss
+++ b/app/assets/stylesheets/zammad.scss
@@ -46,6 +46,10 @@ p {
}
}
+strong {
+ font-weight: 500;
+}
+
.text-muted {
color: hsl(60,1%,74%);
}
@@ -319,6 +323,11 @@ span[data-tooltip]:hover:before {
&:focus {
box-shadow: 0 0 0 3px hsl(201,62%,90%);
}
+
+ &.btn--small {
+ padding-top: 5px;
+ padding-bottom: 4px;
+ }
&.btn--slim {
padding-left: 12px;
@@ -356,7 +365,7 @@ span[data-tooltip]:hover:before {
font-size: 12px;
letter-spacing: 0.05em;
height: 31px;
- padding: 6px 11px !important;
+ padding: 2px 11px 0 !important;
display: inline-flex;
align-items: center;
@@ -589,6 +598,78 @@ span[data-tooltip]:hover:before {
}
}
+.status-fields {
+ display: flex;
+}
+
+.status-field {
+ background: hsl(210,7%,95%);
+ color: hsl(0,0%,66%);
+ padding: 8px 10px 6px;
+ border: 1px solid hsl(0,0%,91%);
+ box-shadow: 0 1px rgba(0,0,0,.02) inset;
+
+ &:not(:last-child):not(:only-child) {
+ border-right: none;
+ }
+
+ &:first-child {
+ border-radius: 5px 0 0 5px;
+ }
+
+ &:last-child {
+ border-radius: 0 5px 5px 0;
+ }
+
+ &:only-child {
+ border-radius: 5px;
+ }
+
+ .badge {
+ background: hsl(210,5%,77%);
+ }
+}
+
+.badge {
+ display: inline-block;
+ min-width: 18px;
+ padding: 3px 5px;
+ font-size: 12px;
+ font-weight: 500;
+ line-height: 1;
+ color: #fff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: top;
+ border-radius: 9px;
+ background: hsl(198,18%,86%);
+ margin-right: 3px;
+
+
+ &:empty {
+ display: none;
+ }
+
+ &.badge--big {
+ min-width: 22px;
+ font-size: 14px;
+ border-radius: 11px;
+ padding: 5px 7px 3px;
+ }
+
+ &.badge--text {
+ min-width: 0;
+ padding: 0;
+ margin-right: 5px;
+ font-size: inherit;
+ font-weight: normal;
+ text-align: left;
+ color: #d0d2d3;
+ background: none;
+ border-radius: 0;
+ }
+}
+
table {
table-layout: fixed;
@@ -864,6 +945,7 @@ label,
/* circumventing the label:not(.inline-label) selector because it's too strong */
.inline-label,
+.label.label-success,
.label.label-warning,
.label.label-danger {
font-size: inherit;
@@ -882,11 +964,16 @@ label,
cursor: pointer;
}
+.label.label-success,
.label.label-warning,
.label.label-danger {
background: none;
}
+.label.label-success {
+ color: $supergood-color;
+}
+
.label.label-warning {
color: $ok-color;
}
@@ -1130,12 +1217,12 @@ textarea,
.form-control,
.checkbox.form-group .checkbox {
display: block;
- padding: 6px 12px;
+ padding: 7px 12px;
width: 100%;
height: 41px;
font-size: 14px;
font-weight: normal;
- line-height: 27px;
+ line-height: 25px;
color: #555;
background: white;
border: 1px solid hsl(0, 0%, 90%);
@@ -1146,15 +1233,19 @@ textarea,
appearance: none;
&.form-control--small {
- padding: 0 8px;
+ padding: 5px 8px 4px;
height: 31px;
- line-height: 29px;
+ line-height: 20px;
}
&.form-control--inline {
display: inline-block;
width: auto;
}
+
+ &.form-control--multiline {
+ height: auto;
+ }
}
input[type=url] {
@@ -1575,6 +1666,17 @@ kbd {
.page-header-title h1 {
margin-top: 9px;
+ margin-bottom: 7px;
+}
+
+.page-header-center {
+ justify-self: center;
+ padding-left: 9px;
+ margin: 0 auto;
+
+ & + .page-header-meta {
+ margin-left: 0;
+ }
}
.page-header-meta {
@@ -1991,6 +2093,21 @@ ol.tabs li {
}
}
+.icon-status-modified-inner-circle {
+ position: absolute;
+ left: 0;
+ top: 0;
+ will-change: opacity;
+ transform: translateZ(0);
+ animation: fade 1.8s ease-in-out infinite;
+}
+
+@keyframes fade {
+ 54% { opacity: 1 }
+ 90% { opacity: 0 }
+ to { opacity: 1 }
+}
+
.icon-checkbox,
.icon-checkbox-checked {
fill: white;
@@ -2164,6 +2281,16 @@ footer {
flex-shrink: 0;
}
+ .main-navigation .badge {
+ background: $ok-color;
+ color: hsl(233,10%,16%);
+ margin-right: 8px;
+ }
+
+ .main-navigation .zammad-switch {
+ height: 22px;
+ }
+
.main-navigation > li > a {
padding: 0 15px;
height: 48px;
@@ -2326,21 +2453,6 @@ footer {
height: 12px;
}
- .nav-tab-icon .modified-inner-circle {
- position: absolute;
- left: 0;
- top: 0;
- will-change: opacity;
- transform: translateZ(0);
- animation: fade 1.8s ease-in-out infinite;
- }
-
- @keyframes fade {
- 54% { opacity: 1 }
- 90% { opacity: 0 }
- to { opacity: 1 }
- }
-
.nav-tab-icon .icon.icon-loading {
animation: rotateplane 1.2s infinite ease-in-out;
fill: $supergood-color;
@@ -2866,18 +2978,6 @@ footer {
border-top: none;
}
- .badge {
- min-width: 0;
- padding: 0;
- margin-right: 5px;
- font-size: inherit;
- font-weight: normal;
- text-align: left;
- color: #d0d2d3;
- background: none;
- border-radius: 0;
- }
-
.nav-pills > li > a > .badge {
margin-left: auto;
}
@@ -3162,7 +3262,7 @@ footer {
}
.time.stat-widget .stat-amount {
- margin-top: 8px;
+ margin-top: 12px;
text-align: center;
font-size: 30px;
color: white;
@@ -5843,11 +5943,36 @@ output {
}
}
-.zammad-switch label {
- position: relative;
- width: 48px;
+.zammad-switch {
+ width: 50px;
height: 30px;
border-radius: 15px;
+ overflow: hidden;
+
+ &.zammad-switch--small {
+ width: 40px;
+ height: 24px;
+ border-radius: 12px;
+ }
+
+ &.zammad-switch--dark {
+ label {
+ background: hsl(234,10%,5%);
+ }
+ label:before {
+ background: hsl(233,10%,10%);
+ }
+ label:after {
+ background: hsl(234,10%,19%);
+ }
+ }
+}
+
+.zammad-switch label {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ border-radius: inherit;
outline: none;
cursor: pointer;
background: hsl(0,0%,90%);
@@ -5859,7 +5984,7 @@ output {
}
.zammad-switch input:checked + label {
- background: hsl(200,71%,59%);
+ background: $supergood-color;
}
.zammad-switch input:focus + label {
@@ -5876,8 +6001,8 @@ output {
}
.zammad-switch label:before {
- width: 46px;
- height: 28px;
+ width: calc(100% - 2px);
+ height: calc(100% - 2px);
left: 1px;
top: 1px;
border-radius: inherit;
@@ -5891,9 +6016,9 @@ output {
}
.zammad-switch label:after {
- width: 28px;
- height: 28px;
- border-radius: 100%;
+ width: calc(60% - 2px);
+ height: calc(100% - 2px);
+ border-radius: inherit;
left: 1px;
top: 1px;
box-shadow:
@@ -5903,7 +6028,7 @@ output {
}
.zammad-switch input:checked + label:after {
- transform: translateX(18px);
+ transform: translateX(70%);
}
.horizontal-filter-text {
@@ -6545,6 +6670,222 @@ output {
}
}
+.chat {
+ background: white;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+
+ .page-header {
+ margin: 15px 6px 5px;
+ }
+}
+
+.chat-workspace {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 0 0 10px;
+ margin: 0 -4px;
+ flex: 1;
+}
+
+.chat-window {
+ flex: 0 1 0;
+ overflow: hidden;
+ padding: 10px;
+ display: flex;
+ flex-direction: column;
+ color: hsl(0,0%,33%);
+ transition: all 500ms;
+ transform: scale(0);
+
+ &.is-open {
+ flex: 1 0 25%;
+ transform: scale(1);
+ }
+
+ &.is-offline {
+ .chat-body-holder,
+ .chat-controls {
+ opacity: 0.5;
+ }
+ }
+}
+
+.chat-header {
+ padding: 12px 40px;
+ background: hsl(210,8%,95%);
+ border: 1px solid hsl(0,0%,91%);
+ border-radius: 3px 3px 0 0;
+ position: relative;
+ line-height: 13px;
+ text-align: center;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+
+.chat-status,
+.chat-close {
+ position: absolute;
+ top: 0;
+ padding-top: inherit;
+ padding-bottom: inherit;
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+.chat-status {
+ left: 0;
+ top: -1px;
+
+ &[data-status='online'] .icon {
+ fill: $supergood-color;
+ }
+
+ &[data-status='offline'] .icon {
+ fill: $superbad-color;
+ }
+
+ .icon-status-modified-inner-circle,
+ .icon-status-modified-outer-circle {
+ display: none;
+ }
+
+ &.is-modified {
+ .icon-status {
+ display: none;
+ }
+
+ .icon-status-modified-inner-circle,
+ .icon-status-modified-outer-circle {
+ display: block;
+ }
+ }
+}
+
+.chat-status-holder {
+ position: relative;
+}
+
+.chat-close {
+ right: 0;
+ cursor: pointer;
+
+ .icon {
+ fill: hsl(197,19%,78%);
+ }
+
+ &:hover {
+ background: hsl(197,18%,96%);
+ }
+}
+
+.chat-body-holder {
+ flex: 1;
+ background: hsl(210,17%,98%);
+ font-size: 13px;
+ line-height: 18px;
+ overflow: auto;
+ border-right: 1px solid hsl(0,0%,91%);
+ border-left: 1px solid hsl(0,0%,91%);
+ position: relative;
+}
+
+.chat-body {
+ padding: 10px;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ position: absolute;
+ width: 100%;
+ top: 0;
+ left: 0;
+}
+
+.chat-timestamp {
+ font-size: 12px;
+ color: hsl(10,5%,78%);
+ margin-bottom: 4px;
+ align-self: center;
+}
+
+.chat-timestamp-label {
+ font-weight: 500;
+}
+
+.chat-message {
+ max-width: 90%;
+ background: white;
+ padding: 6px 12px;
+ border-radius: 16px;
+ margin-bottom: 4px;
+}
+
+.chat-message--customer.chat-message--new {
+ font-weight: 500;
+}
+
+.chat-message--agent {
+ margin-left: auto;
+ background: hsl(199,44%,93%);
+ align-self: flex-end;
+}
+
+.chat-message--agent + .chat-message--customer,
+.chat-message--customer + .chat-message--agent {
+ margin-top: 10px;
+}
+
+.chat-status-message {
+ align-self: center;
+ background: hsl(197,18%,92%);
+ padding: 6px 12px;
+ margin: 4px 0 10px;
+ border-radius: 3px;
+}
+
+.chat-loader {
+ margin-right: -4px;
+
+ .icon {
+ fill: hsl(0,0%,90%);
+ margin-left: -4px;
+ vertical-align: middle;
+ animation: fadeInDown 300ms both;
+ animation: ease-in-out load-fade 600ms infinite alternate;
+ }
+
+ .icon + .icon {
+ animation-delay: .13s;
+ }
+
+ .icon + .icon + .icon {
+ animation-delay: .26s;
+ }
+}
+
+@keyframes load-fade {
+ from { opacity: .5; transform: scale(0.6); }
+ 67% { opacity: 1; transform: scale(1); }
+}
+
+.chat-controls {
+ display: flex;
+ align-items: flex-start;
+ padding: 10px;
+ border: 1px solid hsl(0,0%,91%);
+ border-radius: 0 0 3px 3px;
+ flex-shrink: 0;
+}
+
+.chat-input {
+ margin-right: 10px;
+ max-height: 50vh;
+ overflow: auto;
+}
+
/*
----------------
diff --git a/contrib/icon-sprite.sketch b/contrib/icon-sprite.sketch
index 180d078ca..606966415 100644
Binary files a/contrib/icon-sprite.sketch and b/contrib/icon-sprite.sketch differ
diff --git a/public/assets/fonts/FiraSans-Medium.eot b/public/assets/fonts/FiraSans-Medium.eot
new file mode 100644
index 000000000..00f4b18bd
Binary files /dev/null and b/public/assets/fonts/FiraSans-Medium.eot differ
diff --git a/public/assets/fonts/FiraSans-Medium.ttf b/public/assets/fonts/FiraSans-Medium.ttf
new file mode 100644
index 000000000..317f4e25f
Binary files /dev/null and b/public/assets/fonts/FiraSans-Medium.ttf differ
diff --git a/public/assets/fonts/FiraSans-Medium.woff b/public/assets/fonts/FiraSans-Medium.woff
new file mode 100644
index 000000000..158036094
Binary files /dev/null and b/public/assets/fonts/FiraSans-Medium.woff differ
diff --git a/public/assets/fonts/FiraSans-Medium.woff2 b/public/assets/fonts/FiraSans-Medium.woff2
new file mode 100644
index 000000000..7b02cf985
Binary files /dev/null and b/public/assets/fonts/FiraSans-Medium.woff2 differ
diff --git a/public/assets/images/icons.svg b/public/assets/images/icons.svg
index 7654ecba8..0c72838de 100644
--- a/public/assets/images/icons.svg
+++ b/public/assets/images/icons.svg
@@ -1 +1 @@
-arrow-down arrow-left arrow-right arrow-up checkbox-checked checkbox checkmark clipboard clock cloud cog crown dashboard diagonal-cross download email-button email facebook-button facebook full-logo google-button group help important in-process line-left-arrow line-right-arrow linkedin-button list loading lock-open lock logo logotype long-arrow-right magnifier marker message minus-small minus mood-bad mood-good mood-ok mood-super-bad mood-supergood note one-ticket organization outbound-calls overviews package paperclip pen person phone plus-small plus priority-modified-inner-circle priority-modified-outer-circle priority radio-checked radio received-calls reload reopening reply-all reply signout split status stopwatch switchView task-state-modified-inner-circle task-state-modified-outer-circle task-state team templates tools total-tickets trash user
\ No newline at end of file
+arrow-down arrow-left arrow-right arrow-up chat checkbox-checked checkbox checkmark clipboard clock cloud cog crown dashboard diagonal-cross download email-button email facebook-button facebook full-logo google-button group help important in-process line-left-arrow line-right-arrow linkedin-button list loading lock-open lock logo logotype long-arrow-right magnifier marker message minus-small minus mood-bad mood-good mood-ok mood-super-bad mood-supergood note one-ticket organization outbound-calls overviews package paperclip pen person phone plus-small plus radio-checked radio received-calls reload reopening reply-all reply signout split status-modified-inner-circle status-modified-outer-circle status stopwatch switchView task-state team templates tools total-tickets trash user
\ No newline at end of file
diff --git a/public/assets/images/icons/chat.svg b/public/assets/images/icons/chat.svg
new file mode 100644
index 000000000..4af80d383
--- /dev/null
+++ b/public/assets/images/icons/chat.svg
@@ -0,0 +1,12 @@
+
+
+
+ chat
+ Created with Sketch.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/assets/images/icons/dashboard.svg b/public/assets/images/icons/dashboard.svg
index d23b84d68..376c4e89c 100644
--- a/public/assets/images/icons/dashboard.svg
+++ b/public/assets/images/icons/dashboard.svg
@@ -6,7 +6,7 @@
-
+
\ No newline at end of file
diff --git a/public/assets/images/icons/priority.svg b/public/assets/images/icons/priority.svg
deleted file mode 100644
index 45380990b..000000000
--- a/public/assets/images/icons/priority.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
- priority
- Created with Sketch.
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/public/assets/images/icons/priority-modified-inner-circle.svg b/public/assets/images/icons/status-modified-inner-circle.svg
similarity index 81%
rename from public/assets/images/icons/priority-modified-inner-circle.svg
rename to public/assets/images/icons/status-modified-inner-circle.svg
index 73be5d488..fa9d049e7 100644
--- a/public/assets/images/icons/priority-modified-inner-circle.svg
+++ b/public/assets/images/icons/status-modified-inner-circle.svg
@@ -1,11 +1,11 @@
- priority-modified-inner-circle
+ status-modified-inner-circle
Created with Sketch.
-
+
diff --git a/public/assets/images/icons/priority-modified-outer-circle.svg b/public/assets/images/icons/status-modified-outer-circle.svg
similarity index 85%
rename from public/assets/images/icons/priority-modified-outer-circle.svg
rename to public/assets/images/icons/status-modified-outer-circle.svg
index 84586a4ee..c5750ceea 100644
--- a/public/assets/images/icons/priority-modified-outer-circle.svg
+++ b/public/assets/images/icons/status-modified-outer-circle.svg
@@ -1,11 +1,11 @@
- priority-modified-outer-circle
+ status-modified-outer-circle
Created with Sketch.
-
+
diff --git a/public/assets/images/icons/task-state-modified-inner-circle.svg b/public/assets/images/icons/task-state-modified-inner-circle.svg
deleted file mode 100644
index 6dc061a82..000000000
--- a/public/assets/images/icons/task-state-modified-inner-circle.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
- task-state-modified-inner-circle
- Created with Sketch.
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/public/assets/images/icons/task-state-modified-outer-circle.svg b/public/assets/images/icons/task-state-modified-outer-circle.svg
deleted file mode 100644
index ce86edb3b..000000000
--- a/public/assets/images/icons/task-state-modified-outer-circle.svg
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
- task-state-modified-outer-circle
- Created with Sketch.
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/public/assets/sounds/chat_message.mp3 b/public/assets/sounds/chat_message.mp3
new file mode 100644
index 000000000..48fb9698f
Binary files /dev/null and b/public/assets/sounds/chat_message.mp3 differ
diff --git a/public/assets/sounds/chat_new.mp3 b/public/assets/sounds/chat_new.mp3
new file mode 100644
index 000000000..2a833add6
Binary files /dev/null and b/public/assets/sounds/chat_new.mp3 differ