Browse Source

Init sla management feature.

Martin Edenhofer 9 years ago
parent
commit
c21f1ce93f

+ 12 - 376
app/assets/javascripts/app/controllers/_application_controller_form.js.coffee

@@ -227,284 +227,6 @@ class App.ControllerForm extends App.Controller
     if App.UiElement[attribute.tag]
       item = App.UiElement[attribute.tag].render(attribute, @params, @)
 
-    # working_hour
-    else if attribute.tag is 'time_before_last'
-      if !attribute.value
-        attribute.value = {}
-      item = $( App.view('generic/time_before_last')( attribute: attribute ) )
-      item.find( "[name=\"#{attribute.name}::direction\"]").find("option[value=\"#{attribute.value.direction}\"]").attr( 'selected', 'selected' )
-      item.find( "[name=\"#{attribute.name}::count\"]").find("option[value=\"#{attribute.value.count}\"]").attr( 'selected', 'selected' )
-      item.find( "[name=\"#{attribute.name}::area\"]").find("option[value=\"#{attribute.value.area}\"]").attr( 'selected', 'selected' )
-
-    # ticket attribute set
-    else if attribute.tag is 'ticket_attribute_set'
-
-      # list of possible attributes
-      item = $(
-        App.view('generic/ticket_attribute_manage')(
-          attribute: attribute
-        )
-      )
-
-      addShownAttribute = ( key, value ) =>
-        parts = key.split(/::/)
-        key   = parts[0]
-        type  = parts[1]
-        if key is 'tickets.title'
-          attribute_config = {
-            name:    attribute.name + '::tickets.title'
-            display: 'Title'
-            tag:     'input'
-            type:    'text'
-            null:    false
-            value:   value
-            remove:  true
-          }
-        else if key is 'tickets.group_id'
-          attribute_config = {
-            name:       attribute.name + '::tickets.group_id'
-            display:    'Group'
-            tag:        'select'
-            multiple:   false
-            null:       false
-            nulloption: false
-            relation:   'Group'
-            value:      value
-            remove:     true
-          }
-        else if key is 'tickets.owner_id' || key is 'tickets.customer_id'
-          display = 'Owner'
-          name    = 'owner_id'
-          if key is 'customer_id'
-            display = 'Customer'
-            name    = 'customer_id'
-          attribute_config = {
-            name:       attribute.name + '::tickets.' + name
-            display:    display
-            tag:        'select'
-            multiple:   false
-            null:       false
-            nulloption: false
-            relation:   'User'
-            value:      value || null
-            remove:     true
-            filter:     ( all, type ) ->
-              return all if type isnt 'collection'
-              all = _.filter( all, (item) ->
-                return if item.id is 1
-                return item
-              )
-              all.unshift( {
-                id: ''
-                name:  '--'
-              } )
-              all.unshift( {
-                id: 1
-                name:  '*** not set ***'
-              } )
-              all.unshift( {
-                id: 'current_user.id'
-                name:  '*** current user ***'
-              } )
-              all
-          }
-        else if key is 'tickets.organization_id'
-          attribute_config = {
-            name:       attribute.name + '::tickets.organization_id'
-            display:    'Organization'
-            tag:        'select'
-            multiple:   false
-            null:       false
-            nulloption: false
-            relation:   'Organization'
-            value:      value || null
-            remove:     true
-            filter:     ( all, type ) ->
-              return all if type isnt 'collection'
-              all.unshift( {
-                id: ''
-                name:  '--'
-              } )
-              all.unshift( {
-                id: 'current_user.organization_id'
-                name:  '*** organization of current user ***'
-              } )
-              all
-          }
-        else if key is 'tickets.state_id'
-          attribute_config = {
-            name:       attribute.name + '::tickets.state_id'
-            display:    'State'
-            tag:        'select'
-            multiple:   false
-            null:       false
-            nulloption: false
-            relation:   'TicketState'
-            value:      value
-            translate:  true
-            remove:     true
-          }
-        else if key is 'tickets.priority_id'
-          attribute_config = {
-            name:       attribute.name + '::tickets.priority_id'
-            display:    'Priority'
-            tag:        'select'
-            multiple:   false
-            null:       false
-            nulloption: false
-            relation:   'TicketPriority'
-            value:      value
-            translate:  true
-            remove:     true
-          }
-        else
-          attribute_config = {
-            name:       attribute.name + '::' + key
-            display:    'FIXME!'
-            tag:        'input'
-            type:       'text'
-            value:      value
-            remove:     true
-          }
-        item.find('select[name=ticket_attribute_list] option[value="' + key + '"]').hide().prop('disabled', true)
-
-        itemSub = @formGenItem( attribute_config )
-        itemSub.find('.glyphicon-minus').bind('click', (e) ->
-          e.preventDefault()
-          value = $(e.target).closest('.controls').find('[name]').attr('name')
-          if value
-            value = value.replace("#{attribute.name}::", '')
-            $(e.target).closest('.sub_attribute').find('select[name=ticket_attribute_list] option[value="' + value + '"]').show().prop('disabled', false)
-          $(@).parent().parent().parent().remove()
-        )
-#        itemSub.append('<a href=\"#\" class=\"icon-minus\"></a>')
-        item.find('.ticket_attribute_item').append( itemSub )
-
-      # list of existing attributes
-      attribute_config = {
-        name:       'ticket_attribute_list'
-        display:    'Add Attribute'
-        tag:        'select'
-        multiple:   false
-        null:       false
-#        nulloption: true
-        options: [
-          {
-            value:    ''
-            name:     '-- Ticket --'
-            selected: false
-            disable:  true
-          },
-          {
-            value:    'tickets.title'
-            name:     'Title'
-            selected: false
-            disable:  false
-          },
-          {
-            value:    'tickets.group_id'
-            name:     'Group'
-            selected: false
-            disable:  false
-          },
-          {
-            value:    'tickets.state_id'
-            name:     'State'
-            selected: false
-            disable:  false
-          },
-          {
-            value:    'tickets.priority_id'
-            name:     'Priority'
-            selected: true
-            disable:  false
-          },
-          {
-            value:    'tickets.owner_id'
-            name:     'Owner'
-            selected: true
-            disable:  false
-          },
-#         # {
-#            value:    'tag'
-#            name:     'Tag'
-#            selected: true
-#            disable:  false
-#          },
-#          {
-#            value:    '-a'
-#            name:     '-- ' + App.i18n.translateInline('Article') + ' --'
-#            selected: false
-#            disable:  true
-#          },
-#          {
-#            value:    'ticket_articles.from'
-#            name:     'From'
-#            selected: true
-#            disable:  false
-#          },
-#          {
-#            value:    'ticket_articles.to'
-#            name:     'To'
-#            selected: true
-#            disable:  false
-#          },
-#          {
-#            value:    'ticket_articles.cc'
-#            name:     'Cc'
-#            selected: true
-#            disable:  false
-#          },
-#          {
-#            value:    'ticket_articles.subject'
-#            name:     'Subject'
-#            selected: true
-#            disable:  false
-#          },
-#          {
-#            value:    'ticket_articles.body'
-#            name:     'Text'
-#            selected: true
-#            disable:  false
-#          },
-          {
-            value:    '-c'
-            name:     '-- ' + App.i18n.translateInline('Customer') + ' --'
-            selected: false
-            disable:  true
-          },
-          {
-            value:    'customers.id'
-            name:     'Customer'
-            selected: true
-            disable:  false
-          },
-          {
-            value:    'organization.id'
-            name:     'Organization'
-            selected: true
-            disable:  false
-          },
-        ]
-        default:    ''
-        translate:  true
-        class:      'medium'
-        add:        true
-      }
-      list = @formGenItem( attribute_config )
-      list.find('.glyphicon-plus').bind('click', (e) ->
-        e.preventDefault()
-        value = $(e.target).closest('.controls').find('[name=ticket_attribute_list]').val()
-        addShownAttribute( value, '' )
-      )
-      item.find('.ticket_attribute_list').prepend( list )
-
-      # list of shown attributes
-      show = []
-      if attribute.value
-        for key, value of attribute.value
-          addShownAttribute( key, value )
-
     # ticket attribute selection
     else if attribute.tag is 'ticket_attribute_selection'
 
@@ -948,92 +670,8 @@ class App.ControllerForm extends App.Controller
         for key, value of attribute.value
           addShownAttribute( key, value )
 
-    # timeplan
-    else if attribute.tag is 'timeplan'
-      item = $( App.view('generic/timeplan')( attribute: attribute ) )
-      attribute_config = {
-        name:     "#{attribute.name}::days"
-        tag:      'select'
-        multiple: true
-        null:     false
-        options:  [
-          {
-            value:    'mon'
-            name:     'Monday'
-            selected: false
-            disable:  false
-          },
-          {
-            value:    'tue'
-            name:     'Tuesday'
-            selected: false
-            disable:  false
-          },
-          {
-            value:    'wed'
-            name:     'Wednesday'
-            selected: false
-            disable:  false
-          },
-          {
-            value:    'thu'
-            name:     'Thursday'
-            selected: false
-            disable:  false
-          },
-          {
-            value:    'fri'
-            name:     'Friday'
-            selected: false
-            disable:  false
-          },
-          {
-            value:    'sat'
-            name:     'Saturday'
-            selected: false
-            disable:  false
-          },
-          {
-            value:    'sun'
-            name:     'Sunday'
-            selected: false
-            disable:  false
-          },
-        ]
-        default:  attribute.default?.days
-      }
-      item.find('.days').append( @formGenItem( attribute_config ) )
-
-      hours = {}
-      for hour in [0..23]
-        localHour = "0#{hour}"
-        hours[hour] = localHour.substr(localHour.length-2,2)
-      attribute_config = {
-        name:     "#{attribute.name}::hours"
-        tag:      'select'
-        multiple: true
-        null:     false
-        options:  hours
-        default:  attribute.default?.hours
-      }
-      item.find('.hours').append( @formGenItem( attribute_config ) )
-
-      minutes = {}
-      for minute in [0..5]
-        minutes["#{minute}0"] = "#{minute}0"
-      attribute_config = {
-        name:     "#{attribute.name}::minutes"
-        tag:      'select'
-        multiple: true
-        null:     false
-        options:  minutes
-        default:  attribute.default?.miuntes
-      }
-      item.find('.minutes').append( @formGenItem( attribute_config ) )
-
-    # input
     else
-      item = $( App.view('generic/input')( attribute: attribute ) )
+      throw "Invalid UiElement.#{attribute.tag}"
 
     if @handlers
       item.bind('change', (e) =>
@@ -1193,9 +831,9 @@ class App.ControllerForm extends App.Controller
         continue
 
       # collect all params, push it to an array if already exists
-      if param[key.name]
+      if param[key.name] isnt undefined
         if typeof param[key.name] is 'string'
-          param[key.name] = [ param[key.name], key.value]
+          param[key.name] = [param[key.name], key.value]
         else
           param[key.name].push key.value
       else
@@ -1293,17 +931,15 @@ class App.ControllerForm extends App.Controller
     inputSelectObject = {}
     for key of param
       parts = key.split '::'
-      if parts[0] && parts[1] && !parts[2]
-        if !inputSelectObject[ parts[0] ]
-          inputSelectObject[ parts[0] ] = {}
-        inputSelectObject[ parts[0] ][ parts[1] ] = param[ key ]
-        delete param[ key ]
-      if parts[0] && parts[1] && parts[2]
-        if !inputSelectObject[ parts[0] ]
+      if parts[0] && parts[1]
+        if !(parts[0] of inputSelectObject)
           inputSelectObject[ parts[0] ] = {}
-        if !inputSelectObject[ parts[0] ][ parts[1] ]
-          inputSelectObject[ parts[0] ][ parts[1] ] = {}
-        inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ] = param[ key ]
+        if !parts[2]
+          inputSelectObject[ parts[0] ][ parts[1] ] = param[ key ]
+        else
+          if !(parts[1] of inputSelectObject[ parts[0] ])
+            inputSelectObject[ parts[0] ][ parts[1] ] = {}
+          inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ] = param[ key ]
         delete param[ key ]
 
     # set new object params
@@ -1311,7 +947,7 @@ class App.ControllerForm extends App.Controller
       param[ key ] = inputSelectObject[ key ]
 
     #App.Log.notice 'ControllerForm', 'formParam', form, param
-    return param
+    param
 
   @formId: ->
     formId = new Date().getTime() + Math.floor( Math.random() * 99999 )

+ 2 - 0
app/assets/javascripts/app/controllers/_application_controller_generic.js.coffee

@@ -196,6 +196,7 @@ class App.ControllerGenericIndex extends App.Controller
       pageData:      @pageData
       genericObject: @genericObject
       container:     @container
+      large:         @large
     )
 
   new: (e) ->
@@ -204,6 +205,7 @@ class App.ControllerGenericIndex extends App.Controller
       pageData:      @pageData
       genericObject: @genericObject
       container:     @container
+      large:         @large
     )
 
   description: (e) =>

+ 18 - 0
app/assets/javascripts/app/controllers/_ui_element/autocompletion_ajax.js.coffee

@@ -0,0 +1,18 @@
+class App.UiElement.autocompletion_ajax
+  @render: (attribute, params = {}) ->
+    if params[attribute.name]
+      object = App[attribute.relation].find(params[attribute.name])
+      valueName = object.displayName()
+
+    # selectable search
+    searchableAjaxSelectObject = new App.SearchableAjaxSelect(
+      attribute:
+        value:       params[attribute.name]
+        valueName:   valueName
+        name:        attribute.name
+        id:          params.organization_id
+        placeholder: App.i18n.translateInline('Search...')
+        limt:        10
+        object:      attribute.relation
+    )
+    searchableAjaxSelectObject.element()

+ 3 - 0
app/assets/javascripts/app/controllers/_ui_element/input.js.coffee

@@ -0,0 +1,3 @@
+class App.UiElement.input
+  @render: (attribute) ->
+    $( App.view('generic/input')( attribute: attribute ) )

+ 1 - 1
app/assets/javascripts/app/controllers/_ui_element/select.js.coffee

@@ -1,5 +1,5 @@
 class App.UiElement.select extends App.UiElement.ApplicationUiElement
-  @render: (attribute, params, form_controller) ->
+  @render: (attribute, params) ->
 
     # set multiple option
     if attribute.multiple

+ 44 - 2
app/assets/javascripts/app/controllers/_ui_element/sla_times.js.coffee

@@ -1,4 +1,46 @@
 class App.UiElement.sla_times
-  @render: (attribute) ->
+  @render: (attribute, params = {}) ->
 
-    $( App.view('generic/sla_times')( attribute: attribute ) )
+    item = $( App.view('generic/sla_times')(
+      attribute: attribute
+      first_response_time: params.first_response_time
+      update_time: params.update_time
+      close_time: params.close_time
+      first_response_time_in_text: @toText(params.first_response_time)
+      update_time_in_text: @toText(params.update_time)
+      close_time_in_text: @toText(params.close_time)
+    ) )
+
+    item.find('.js-timeConvertFrom').bind('keyup', (e) =>
+      inText = $(e.target).val()
+      inMinutes = @toMinutes(inText)
+      if !inMinutes
+        $(e.target).addClass('has-error')
+      else
+        $(e.target).removeClass('has-error')
+      dest = $(e.target).closest('td').find('.js-timeConvertTo')
+      dest.val(inMinutes)
+    )
+
+    item
+
+  @toMinutes: (hh) ->
+    hh = hh.split(':')
+    hour = parseInt(hh[0])
+    minute = parseInt(hh[1])
+    return if hour is NaN
+    return if minute is NaN
+    (hour * 60) + minute
+
+  @toText: (m) ->
+    m = parseInt(m)
+    return if !m
+    minutes = m % 60
+    hours = Math.floor(m / 60)
+
+    if minutes < 10
+      minutes = "0#{minutes}"
+    if hours < 10
+      hours = "0#{hours}"
+
+    "#{hours}:#{minutes}"

+ 267 - 0
app/assets/javascripts/app/controllers/_ui_element/ticket_selector.js.coffee

@@ -0,0 +1,267 @@
+class App.UiElement.ticket_selector extends App.UiElement.ApplicationUiElement
+  @render: (attribute, params = {}) ->
+
+    # list of attributes
+    groups =
+      tickets:
+        name: 'Ticket'
+        model: 'Ticket'
+      users:
+        name: 'Customer'
+        model: 'User'
+      organizations:
+        name: 'Organization'
+        model: 'Organization'
+
+    elements =
+      tickets:
+        title:
+          tag: 'input'
+          operator: ['contains', 'contains not']
+        number:
+          tag: 'input'
+          operator: ['contains', 'contains not']
+        group_id:
+          relation: 'Group'
+          tag: 'select'
+          multible: true
+          operator: ['is', 'is not']
+        priority_id:
+          relation: 'Priority'
+          tag: 'select'
+          multible: true
+          operator: ['is', 'is not']
+        state_id:
+          relation: 'State'
+          tag: 'select'
+          multible: true
+          operator: ['is', 'is not']
+        owner_id:
+          tag: 'user_selection'
+          relation: 'User'
+          operator: ['is', 'is not']
+        customer_id:
+          tag: 'user_selection'
+          relation: 'User'
+          operator: ['is', 'is not']
+        organization_id:
+          tag: ''
+          relation: 'Organization'
+          operator: ['is', 'is not']
+        tag:
+          tag: 'tag'
+          multible: true
+          operator: ['is', 'is not']
+        created_at:
+          tag: 'timestamp'
+          operator: ['before', 'after']
+        updated_at:
+          tag: 'timestamp'
+          operator: ['before', 'after']
+        escalation_time:
+          tag: 'timestamp'
+          operator: ['before', 'after']
+      users:
+        firstname:
+          tag: 'input'
+          operator: ['contains', 'contains not']
+        lastname:
+          tag: 'input'
+          operator: ['contains', 'contains not']
+        email:
+          tag: 'input'
+          operator: ['contains', 'contains not']
+        login:
+          tag: 'input'
+          operator: ['contains', 'contains not']
+        created_at:
+          tag: 'time_selector_enhanced'
+          operator: ['before', 'after']
+        updated_at:
+          tag: 'time_selector_enhanced'
+          operator: ['before', 'after']
+      organizations:
+        name:
+          tag: 'input'
+          operator: ['contains', 'contains not']
+        shared:
+          tag: 'boolean'
+          operator: ['is', 'is not']
+        created_at:
+          tag: 'time_selector_enhanced'
+          operator: ['before', 'after']
+        updated_at:
+          tag: 'time_selector_enhanced'
+          operator: ['before', 'after']
+
+    # megre config
+    for groupKey, groupMeta of groups
+      for elementKey, elementGroup of elements
+        if elementKey is groupKey
+          configure_attributes = App[groupMeta.model].configure_attributes
+          for attributeName, attributeConfig of elementGroup
+            for attribute in configure_attributes
+              if attribute.name is attributeName
+                attributeConfig.config = attribute
+
+    selector = @buildAttributeSelector(groups, elements)
+
+    # return item
+    item = $( App.view('generic/ticket_selector')( attribute: attribute ) )
+    item.find('.js-attributeSelector').prepend(selector)
+
+    # add filter
+    item.find('.js-add').bind('click', (e) =>
+      element = $(e.target).closest('.js-filterElement')
+      elementClone = element.clone(true)
+      element.after(elementClone)
+      elementClone.find('.js-attributeSelector select').trigger('change')
+    )
+
+    # remove filter
+    item.find('.js-remove').bind('click', (e) =>
+      $(e.target).closest('.js-filterElement').remove()
+      @rebuildAttributeSelectors(item)
+    )
+
+    # change filter
+    item.find('.js-attributeSelector select').bind('change', (e) =>
+      groupAndAttribute = $(e.target).find('option:selected').attr('value')
+      elementRow = $(e.target).closest('.js-filterElement')
+
+      console.log('CHANGE', groupAndAttribute, $(e.target))
+
+      @rebuildAttributeSelectors(item, elementRow, groupAndAttribute)
+      @rebuildOperater(item, elementRow, groupAndAttribute, elements)
+      @buildValue(item, elementRow, groupAndAttribute, elements)
+    )
+
+    # build inital params
+    console.log('P', params)
+    if !_.isEmpty(params.condition)
+      selectorExists = false
+      for position of params.condition.attribute
+
+        # get stored params
+        groupAndAttribute = params.condition.attribute[position]
+        if params.condition[groupAndAttribute]
+          selectorExists = true
+          operator = params.condition[groupAndAttribute].operator
+          value = params.condition[groupAndAttribute].value
+
+          # get selector rows
+          elementFirst = item.find('.js-filterElement').first()
+          elementLast = item.find('.js-filterElement').last()
+
+          # clone, rebuild and append
+          elementClone = elementFirst.clone(true)
+          @rebuildAttributeSelectors(item, elementClone, groupAndAttribute)
+          @rebuildOperater(item, elementClone, groupAndAttribute, elements, operator)
+          @buildValue(item, elementClone, groupAndAttribute, elements, value)
+          elementLast.after(elementClone)
+
+      # remove first dummy row
+      if selectorExists
+        item.find('.js-filterElement').first().remove()
+    item
+
+  @getElementConfig: (groupAndAttribute, elements) ->
+    for elementGroup, elementConfig of elements
+      for elementKey, elementItem of elementConfig
+        if "#{elementGroup}.#{elementKey}" is groupAndAttribute
+          return elementItem
+    false
+
+  @buildValue: (elementFull, elementRow, groupAndAttribute, elements, value) ->
+
+    # do nothing if item already exists
+    name = "condition::#{groupAndAttribute}::value"
+    return if elementRow.find("[name=\"#{name}\"]").get(0)
+
+    # build new item
+    attributeConfig = @getElementConfig(groupAndAttribute, elements)
+    item = ''
+    if attributeConfig && attributeConfig.config && App.UiElement[attributeConfig.config.tag]
+      config = _.clone(attributeConfig.config)
+      config['name'] = name
+      config['value'] = value
+      if 'multiple' of config
+        config.multiple = true
+        config.nulloption = false
+      item = App.UiElement[attributeConfig.config.tag].render(config, {})
+    elementRow.find('.js-value').html(item)
+
+  @buildAttributeSelector: (groups, elements) ->
+    selection = $('<input type="hidden" name="condition::attribute"><select class="form-control"></select>')
+    for groupKey, groupMeta of groups
+      displayName = App.i18n.translateInline(groupMeta.name)
+      selection.closest('select').append("<optgroup label=\"#{displayName}\" class=\"js-#{groupKey}\"></optgroup>")
+      optgroup = selection.find("optgroup.js-#{groupKey}")
+      for elementKey, elementGroup of elements
+        if elementKey is groupKey
+          for attributeName, attributeConfig of elementGroup
+            if attributeConfig.config && attributeConfig.config.display
+              displayName = App.i18n.translateInline(attributeConfig.config.display)
+            else
+              displayName = App.i18n.translateInline(attributeName)
+            optgroup.append("<option value=\"#{groupKey}.#{attributeName}\">#{displayName}</option>")
+    selection
+
+  @rebuildAttributeSelectors: (elementFull, elementRow, groupAndAttribute) ->
+
+    # enable all
+    elementFull.find('.js-attributeSelector select option').removeAttr('disabled')
+
+    # disable all used attributes
+    elementFull.find('.js-attributeSelector select').each(->
+      keyLocal = $(@).val()
+      elementFull.find('.js-attributeSelector select option[value="' + keyLocal + '"]').attr('disabled', true)
+      elementFull.find('.js-hiddenAttribute').val(keyLocal)
+    )
+
+    # disable - if we only have one attribute
+    if elementFull.find('.js-attributeSelector select').length > 1
+      elementFull.find('.js-remove').removeClass('is-disabled')
+    else
+      elementFull.find('.js-remove').addClass('is-disabled')
+
+    # set attribute
+    if groupAndAttribute
+      elementRow.find('.js-attributeSelector select').val(groupAndAttribute)
+      elementRow.find('[name="condition::attribute"]').val("#{groupAndAttribute}")
+
+  @buildOperator: (elementFull, elementRow, groupAndAttribute, elements, current_operator) ->
+    selection = $("<select class=\"form-control\" name=\"condition::#{groupAndAttribute}::operator\"></select>")
+    attributeConfig = @getElementConfig(groupAndAttribute, elements)
+    for operator in attributeConfig.operator
+      operatorName = App.i18n.translateInline(operator)
+      selected = ''
+      if current_operator is operator
+        selected = 'selected="selected"'
+      selection.append("<option value=\"#{operator}\" #{selected}>#{operatorName}</option>")
+    selection
+
+  @rebuildOperater: (elementFull, elementRow, groupAndAttribute, elements, current_operator) ->
+    return if !groupAndAttribute
+
+    # do nothing if item already exists
+    name = "condition::#{groupAndAttribute}::operator"
+    return if elementRow.find("[name=\"#{name}\"]").get(0)
+
+    # render new operator
+    operator = @buildOperator(elementFull, elementRow, groupAndAttribute, elements, current_operator)
+    elementRow.find('.js-operator select').replaceWith(operator)
+
+  @humanText: (condition) ->
+    return [] if _.isEmpty(condition)
+    rules = []
+    for position of condition.attribute
+
+      # get stored params
+      groupAndAttribute = condition.attribute[position]
+      if condition[groupAndAttribute]
+        selectorExists = true
+        operator = condition[groupAndAttribute].operator
+        value = condition[groupAndAttribute].value
+        rules.push "Where <b>#{groupAndAttribute}</b> #{operator} <b>#{value}</b>."
+    rules

+ 105 - 13
app/assets/javascripts/app/controllers/sla.js.coffee

@@ -1,27 +1,119 @@
 class Index extends App.ControllerContent
+  events:
+    'click .js-new':         'new'
+    'click .js-edit':        'edit'
+    'click .js-delete':      'delete'
+    'click .js-description': 'description'
+
   constructor: ->
     super
 
     # check authentication
     return if !@authenticate()
 
-    new App.ControllerGenericIndex(
-      el: @el
-      id: @id
+    @load()
+    #@subscribeId = App.Calendar.subscribe(@render)
+
+  load: =>
+    @ajax(
+      id:   'sla_index'
+      type: 'GET'
+      url:  @apiPath + '/slas'
+      processData: true
+      success: (data, status, xhr) =>
+
+        # load assets
+        App.Collection.loadAssets(data.assets)
+
+        @render(data)
+    )
+
+  render: =>
+    slas = App.Sla.search(
+      sortBy: 'name'
+    )
+    for sla in slas
+      if sla.first_response_time
+        sla.first_response_time_in_text = @toText(sla.first_response_time)
+      if sla.update_time
+        sla.update_time_in_text = @toText(sla.update_time)
+      if sla.solution_time
+        sla.solution_time_in_text = @toText(sla.solution_time)
+      sla.rules = App.UiElement.ticket_selector.humanText(sla.condition)
+
+    # show description button, only if content exists
+    showDescription = false
+    if App.Sla.description
+      if !_.isEmpty(slas)
+        showDescription = true
+      else
+        description = marked(App.Sla.description)
+
+    @html App.view('sla/index')(
+      slas:            slas
+      showDescription: showDescription
+      description:     description
+    )
+
+  release: =>
+    if @subscribeId
+      App.Calendar.unsubscribe(@subscribeId)
+
+  new: (e) ->
+    e.preventDefault()
+    new App.ControllerGenericNew(
+      pageData:
+        title: 'SLAs'
+        object: 'Sla'
+        objects: 'SLAs'
       genericObject: 'Sla'
+      container:     @el.closest('.content')
+      callback:      @load
+      large:         true
+    )
+
+  edit: (e) ->
+    e.preventDefault()
+    id = $(e.target).closest('.action').data('id')
+    new App.ControllerGenericEdit(
+      id: id
       pageData:
-        title: 'SLA'
-        home: 'slas'
-        object: 'SLA'
+        title: 'SLAs'
+        object: 'Sla'
         objects: 'SLAs'
-        navupdate: '#slas'
-        notes: [
-#          'SLA are ...'
-        ]
-        buttons: [
-          { name: 'New SLA', 'data-type': 'new', class: 'btn--success' }
-        ]
+      genericObject: 'Sla'
+      callback:      @load
+      container:     @el.closest('.content')
+      large:         true
+    )
+
+  delete: (e) =>
+    e.preventDefault()
+    id   = $(e.target).closest('.action').data('id')
+    item = App.Sla.find(id)
+    new App.ControllerGenericDestroyConfirm(
+      item:      item
       container: @el.closest('.content')
+      callback:  @load
+    )
+
+  description: (e) =>
+    new App.ControllerGenericDescription(
+      description: App.Calendar.description
+      container:   @el.closest('.content')
     )
 
+  toText: (m) ->
+    m = parseInt(m)
+    return if !m
+    minutes = m % 60
+    hours = Math.floor(m / 60)
+
+    if minutes < 10
+      minutes = "0#{minutes}"
+    if hours < 10
+      hours = "0#{hours}"
+
+    "#{hours}:#{minutes}"
+
 App.Config.set( 'Sla', { prio: 2900, name: 'SLAs', parent: '#manage', target: '#manage/slas', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )

+ 2 - 2
app/assets/javascripts/app/controllers/users.js.coffee

@@ -1,8 +1,8 @@
 class Index extends App.Controller
   elements:
-    '.js-search' : 'searchInput'
+    '.js-search': 'searchInput'
   events:
-    'click [data-type="new"]': 'new'
+    'click [data-type=new]': 'new'
 
   constructor: ->
     super

+ 2 - 1
app/assets/javascripts/app/models/organization.js.coffee

@@ -6,8 +6,9 @@ class App.Organization extends App.Model
     { name: 'name',       display: 'Name',                tag: 'input',     type: 'text', limit: 100, null: false, info: true },
     { name: 'shared',     display: 'Shared organization', tag: 'boolean',   note: 'Customers in the organization can view each other items.', type: 'boolean', default: true, null: false, info: false },
     { name: 'note',       display: 'Note',                tag: 'textarea',  note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, info: true },
-    { name: 'updated_at', display: 'Updated',             tag: 'datetime',  readonly: 1, info: false },
     { name: 'active',     display: 'Active',              tag: 'active',    default: true, info: false },
+    { name: 'updated_at', display: 'Updated',             tag: 'datetime',  readonly: 1, info: false },
+    { name: 'created_at', display: 'Created',             tag: 'datetime',  readonly: 1, info: false },
   ]
   @configure_overview = [
     'name',

Some files were not shown because too many files changed in this diff