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 32a48398f..0e4dac363 100644 --- a/app/assets/javascripts/app/controllers/layout_ref.coffee +++ b/app/assets/javascripts/app/controllers/layout_ref.coffee @@ -1675,4 +1675,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 @@ +
    +
    +
    + <%- @Icon('status') %> + <%- @Icon('status-modified-outer-circle') %> + <%- @Icon('status-modified-inner-circle') %> +
    +
    +
    <%- @name %>
    +
    + <%- @Icon('diagonal-cross') %> +
    +
    +
    +
    +
    +
    +
    +
    <%= @T('Send') %>
    +
    \ 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..015f2b558 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; } @@ -1108,9 +1195,9 @@ fieldset > .form-group { margin-bottom: 0; } -.formGroup--bundle .controls { +.controls--bundle { display: flex; - + .controls-label { margin: 11px 10px 0; } @@ -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-downarrow-leftarrow-rightarrow-upcheckbox-checkedcheckboxcheckmarkclipboardclockcloudcogcrowndashboarddiagonal-crossdownloademail-buttonemailfacebook-buttonfacebookgoogle-buttongrouphelpimportantin-processline-left-arrowline-right-arrowlinkedin-buttonlistloadinglock-openlocklogotypelong-arrow-rightmagnifiermarkermessageminus-smallminusmood-badmood-goodmood-okmood-super-badmood-supergoodnoteone-ticketorganizationoutbound-callsoverviewspackagepaperclippenpersonphoneplus-smallpluspriority-modified-inner-circlepriority-modified-outer-circlepriorityradio-checkedradioreceived-callsreloadreopeningreply-allreplysignoutsplitstatusstopwatchswitchViewtask-state-modified-inner-circletask-state-modified-outer-circletask-stateteamtemplatestoolstotal-ticketstrashtwitter-buttontwitteruser \ No newline at end of file +arrow-downarrow-leftarrow-rightarrow-upchatcheckbox-checkedcheckboxcheckmarkclipboardclockcloudcogcrowndashboarddiagonal-crossdownloademail-buttonemailfacebook-buttonfacebookgoogle-buttongrouphelpimportantin-processline-left-arrowline-right-arrowlinkedin-buttonlistloadinglock-openlocklogotypelong-arrow-rightmagnifiermarkermessageminus-smallminusmood-badmood-goodmood-okmood-super-badmood-supergoodnoteone-ticketorganizationoutbound-callsoverviewspackagepaperclippenpersonphoneplus-smallplusradio-checkedradioreceived-callsreloadreopeningreply-allreplysignoutsplitstatus-modified-inner-circlestatus-modified-outer-circlestatusstopwatchswitchViewtask-stateteamtemplatestoolstotal-ticketstrashtwitter-buttontwitteruser \ 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