Mantas Masalskis 3 лет назад
Родитель
Сommit
c1d467aa3d

+ 29 - 6
app/assets/javascripts/app/controllers/agent_ticket_create.coffee

@@ -132,6 +132,14 @@ class App.TicketCreate extends App.Controller
     @$('[name="cc"], [name="group_id"], [name="customer_id"]').on('change', =>
       @updateSecurityOptions()
     )
+
+    @listenTo(App.Group, 'refresh', =>
+      @sidebarWidget.render(@params())
+    )
+
+    @$('[name="group_id"]').bind('change', =>
+      @sidebarWidget.render(@params())
+    )
     @updateSecurityOptions()
 
     # show cc
@@ -174,6 +182,7 @@ class App.TicketCreate extends App.Controller
     @navupdate("#ticket/create/id/#{@id}#{@split}", type: 'menu')
     @autosaveStart()
     @controllerBind('ticket_create_rerender', (template) => @renderQueue(template))
+    @controllerBind('ticket_create_import_draft_attachments', @importDraftAttachments)
 
     # initially hide sidebar on mobile
     if window.matchMedia('(max-width: 767px)').matches
@@ -183,6 +192,7 @@ class App.TicketCreate extends App.Controller
   hide: =>
     @autosaveStop()
     @controllerUnbind('ticket_create_rerender', (template) => @renderQueue(template))
+    @controllerUnbind('ticket_create_import_draft_attachments')
 
   changed: =>
     return true if @hasAttachments()
@@ -283,6 +293,18 @@ class App.TicketCreate extends App.Controller
     return if !@formMeta
     App.QueueManager.run(@queueKey)
 
+  importDraftAttachments: (options = {}) =>
+    @ajax
+      id: 'import_attachments'
+      type: 'POST'
+      url: "#{@apiPath}/tickets/shared_drafts/#{options.shared_draft_id}/import_attachments"
+      data: JSON.stringify({ form_id: @formId })
+      processData: true
+      success: (data, status, xhr) ->
+        App.Event.trigger(options.callbackName, { success: true, attachments: data.attachments })
+      error: ->
+        App.Event.trigger(options.callbackName, { success: false })
+
   updateTaskManagerAttachments: (attribute, attachments) =>
     taskData = App.TaskManager.get(@taskKey)
     return if _.isEmpty(taskData)
@@ -306,12 +328,13 @@ class App.TicketCreate extends App.Controller
     params.priority_id ||= App.TicketPriority.findByAttribute( 'default_create', true )?.id
 
     @html(App.view('agent_ticket_create')(
-      head:           __('New Ticket')
-      agent:          @permissionCheck('ticket.agent')
-      admin:          @permissionCheck('admin')
-      types:          @types,
-      availableTypes: @availableTypes
-      form_id:        @formId
+      head:            __('New Ticket')
+      agent:           @permissionCheck('ticket.agent')
+      admin:           @permissionCheck('admin')
+      types:           @types,
+      availableTypes:  @availableTypes
+      form_id:         @formId
+      shared_draft_id: template.shared_draft_id || params.shared_draft_id
     ))
 
     App.Ticket.configure_attributes.push {

+ 29 - 0
app/assets/javascripts/app/controllers/agent_ticket_create/sidebar_shared_draft.coffee

@@ -0,0 +1,29 @@
+class SidebarSharedDraft extends App.Controller
+  sidebarItem: =>
+    return if !@permissionCheck('ticket.agent')
+
+    group = App.Group.find @params.group_id
+
+    return if !group?.shared_drafts
+
+    @item = {
+      name: 'shared_draft'
+      badgeIcon: 'note'
+      sidebarHead: __('Shared Drafts')
+      sidebarActions: []
+      sidebarCallback: @showDrafts
+    }
+    @item
+
+  showDrafts: (el) =>
+    @el = el
+
+    # show template UI
+    new App.WidgetSharedDraft(
+      el:              el
+      taskKey:         @taskKey
+      group_id:        @params.group_id
+      active_draft_id: @params.shared_draft_id
+    )
+
+App.Config.set('400-SharedDraft', SidebarSharedDraft, 'TicketCreateSidebar')

+ 106 - 8
app/assets/javascripts/app/controllers/ticket_zoom.coffee

@@ -2,14 +2,15 @@ class App.TicketZoom extends App.Controller
   @include App.TicketNavigable
 
   elements:
-    '.main':             'main'
-    '.ticketZoom':       'ticketZoom'
-    '.scrollPageHeader': 'scrollPageHeader'
+    '.main':               'main'
+    '.ticketZoom':         'ticketZoom'
+    '.scrollPageHeader':   'scrollPageHeader'
 
   events:
     'click .js-submit':   'submit'
     'click .js-bookmark': 'bookmark'
     'click .js-reset':    'reset'
+    'click .js-draft':    'draft'
     'click .main':        'muteTask'
 
   constructor: (params) ->
@@ -187,6 +188,9 @@ class App.TicketZoom extends App.Controller
       # remember mentions
       @mentions = data.mentions
 
+      if draft = App.TicketSharedDraftZoom.findByAttribute 'ticket_id', @ticket_id
+        draft.remove(clear: true)
+
       App.Collection.loadAssets(data.assets, targetModel: 'Ticket')
 
     # get ticket
@@ -481,11 +485,13 @@ class App.TicketZoom extends App.Controller
       )
 
       @attributeBar = new App.TicketZoomAttributeBar(
-        ticket:      @ticket
-        el:          elLocal.find('.js-attributeBar')
-        overview_id: @overview_id
-        callback:    @submit
-        taskKey:     @taskKey
+        ticket:        @ticket
+        el:            elLocal.find('.js-attributeBar')
+        overview_id:   @overview_id
+        macroCallback: @submit
+        draftCallback: @saveDraft
+        draftState:    @draftState()
+        taskKey:       @taskKey
       )
       #if @shown
       #  @attributeBar.start()
@@ -965,6 +971,55 @@ class App.TicketZoom extends App.Controller
         @submitPost(e, ticket, macro)
     )
 
+  saveDraft: (e) =>
+    e.stopPropagation()
+    e.preventDefault()
+
+    params =
+      new_article:       @articleNew.params()
+      ticket_attributes: @ticketParams()
+
+    loaded_draft_id = params.new_article.shared_draft_id
+
+    params.form_id = params.new_article['form_id']
+    delete params.new_article['form_id']
+    delete params.new_article['shared_draft_id']
+
+    sharedDraft = @sharedDraft()
+
+    draftExists = sharedDraft?
+    isLoaded = loaded_draft_id == String(sharedDraft?.id)
+
+    matches = draftExists &&
+      _.isEqual(sharedDraft.new_article, params.new_article) &&
+      _.isEqual(sharedDraft.ticket_attributes, params.ticket_attributes)
+
+    if draftExists && !(isLoaded && matches)
+      new App.TicketSharedDraftOverwriteModal(
+        onShowDraft: @draft
+        onSaveDraft: =>
+          @draftSaveToServer(params)
+      )
+
+      return
+
+    @draftSaveToServer(params)
+
+  draftSaveToServer: (params) =>
+    @draftSaving()
+
+    @ajax
+      id: 'ticket_shared_draft_update'
+      type: 'PUT'
+      url: @apiPath + '/tickets/' + @ticket_id + '/shared_draft'
+      processData: true
+      data: JSON.stringify(params)
+      success: (data, status, xhr) =>
+        App.Collection.loadAssets(data.assets)
+        @draftFetched()
+      error: =>
+        @draftFetched()
+
   submitPost: (e, ticket, macro) =>
     taskAction = @$('.js-secondaryActionButtonLabel').data('type')
 
@@ -1034,6 +1089,49 @@ class App.TicketZoom extends App.Controller
   bookmark: (e) ->
     $(e.currentTarget).find('.bookmark.icon').toggleClass('filled')
 
+  draft: (e) =>
+    e.preventDefault()
+
+    new App.TicketSharedDraftModal(
+      container:    @el.closest('.content')
+      hasChanges:   App.TaskManager.worker(@taskKey).changed()
+      parent:       @
+      shared_draft: @sharedDraft()
+    )
+
+  fetchDraft: ->
+    @ajax(
+      id:    "ticket_#{@ticket_id}_shared_draft"
+      type: 'GET'
+      url:    "#{@apiPath}/tickets/#{@ticket_id}/shared_draft"
+      processData: true
+      success: (data, status, xhr) =>
+        App.Collection.loadAssets(data.assets)
+        @draftFetched()
+    )
+
+  draftSaving: ->
+    @updateDraftButton(true, 'saving')
+
+  updateDraftButton: (visible, state) ->
+    button = @el.find('.js-draft')
+
+    button.toggleClass('hide', !visible)
+    button.find('.attributeBar-draft--available').toggleClass('hide', state != 'available')
+    button.find('.attributeBar-draft--saving').toggleClass('hide', state != 'saving')
+    button.attr('disabled', state == 'saving')
+
+    @el.find('.js-dropdownActionSaveDraft').attr('disabled', state == 'saving')
+
+  draftFetched: ->
+    @updateDraftButton(@sharedDraft()?, 'available')
+
+  draftState: ->
+    @sharedDraft()?
+
+  sharedDraft: ->
+    App.TicketSharedDraftZoom.findByAttribute 'ticket_id', @ticket_id
+
   reset: (e) =>
     if e
       e.preventDefault()

+ 26 - 1
app/assets/javascripts/app/controllers/ticket_zoom/article_new.coffee

@@ -53,13 +53,16 @@ class App.TicketZoomArticleNew extends App.Controller
 
       @setArticleTypePre(data.type.name, data.signaturePosition)
 
-      @openTextarea(null, true, true)
+      @openTextarea(null, true, !data.nofocus)
       for key, value of data.article
         if key is 'body'
           @$("[data-name=\"#{key}\"]").html(value)
         else
           @$("[name=\"#{key}\"]").val(value).trigger('change')
 
+
+      @$('[name=shared_draft_id]').val(data.shared_draft_id)
+
       @setArticleTypePost(data.type.name, data.signaturePosition)
 
       # set focus into field
@@ -76,6 +79,8 @@ class App.TicketZoomArticleNew extends App.Controller
       @tokanice(data.type.name)
     )
 
+    @controllerBind('ui::ticket::import_draft_attachments', @importDraftAttachments)
+
     # add article attachment
     @controllerBind('ui::ticket::addArticleAttachent', (data) =>
       return if data.ticket?.id?.toString() isnt @ticket_id.toString() && data.form_id isnt @form_id
@@ -634,6 +639,26 @@ class App.TicketZoomArticleNew extends App.Controller
       @richTextUploadDeleteCallback?(@attachments)
     )
 
+  importDraftAttachments: (options) =>
+    return if @ticket.id != options.ticket_id
+
+    @ajax
+      id: 'import_attachments'
+      type: 'POST'
+      url: "#{@apiPath}/tickets/#{@ticket.id}/shared_draft/import_attachments"
+      data: JSON.stringify({ form_id: @form_id })
+      processData: true
+      success: (data, status, xhr) =>
+        App.Event.trigger('ui::ticket::addArticleAttachent', {
+          ticket:      @ticket
+          attachments: data.attachments
+          form_id:     @form_id
+        })
+
+        App.Event.trigger(options.callbackName, { success: true })
+      error: ->
+        App.Event.trigger(options.callbackName, { success: false })
+
   actions: ->
     actionConfig = App.Config.get('TicketZoomArticleAction')
     keys = _.keys(actionConfig).sort()

+ 63 - 7
app/assets/javascripts/app/controllers/ticket_zoom/attribute_bar.coffee

@@ -9,6 +9,9 @@ class App.TicketZoomAttributeBar extends App.Controller
     'mouseup .js-dropdownActionMacro':    'performTicketMacro'
     'mouseenter .js-dropdownActionMacro': 'onActionMacroMouseEnter'
     'mouseleave .js-dropdownActionMacro': 'onActionMacroMouseLeave'
+    'mouseup .js-dropdownActionSaveDraft': 'saveDraft'
+    'mouseenter .js-dropdownActionSaveDraft': 'onActionMacroMouseEnter'
+    'mouseleave .js-dropdownActionSaveDraft': 'onActionMacroMouseLeave'
     'click .js-secondaryAction':          'chooseSecondaryAction'
 
   searchCondition: {}
@@ -31,19 +34,45 @@ class App.TicketZoomAttributeBar extends App.Controller
       @render()
     )
 
+    @controllerBind('ui::ticket::updateSharedDraft', (data) =>
+      return if data.taskKey isnt @taskKey
+      @render(data)
+    )
+
+    @listenTo(App.Group, 'refresh', (refreshed_group) =>
+      selected_group_id = @el.closest('.content').find('[name=group_id]').val()
+      selected_group    = App.Group.find selected_group_id
+
+      return if !selected_group
+      return if !refreshed_group
+      return if refreshed_group.id != selected_group.id
+
+      return if @sharedDraftsEnabled == selected_group.shared_drafts
+
+      @render({ newGroupId: selected_group.id })
+    )
+
   getAction: ->
     return App.Session.get().preferences.secondaryAction || App.Config.get('ticket_secondary_action') || 'stayOnTab'
 
   release: =>
     App.Macro.unsubscribe(@subscribeId)
 
-  render: =>
-
+  render: (options = {}) =>
     # remember current reset state
     resetButtonShown = false
     if @resetButton.get(0) && !@resetButton.hasClass('hide')
       resetButtonShown = true
 
+    group                  = App.Group.find options?.newGroupId || @ticket.group_id
+    draft                  = App.TicketSharedDraftZoom.findByAttribute 'ticket_id', @ticket.id
+    accessibleGroups       = App.User.current().allGroupIds('change')
+    sharedDraftButtonShown = group?.shared_drafts && _.contains(accessibleGroups, String(group.id))
+    sharedDraftsEnabled    = group?.shared_drafts && _.contains(accessibleGroups, String(group.id))
+    sharedButtonVisible    = sharedDraftsEnabled && draft?
+
+    @sharedDraftsEnabled = sharedDraftsEnabled
+
     macros = App.Macro.getList()
 
     @macroLastUpdated = App.Macro.lastUpdatedAt()
@@ -59,11 +88,15 @@ class App.TicketZoomAttributeBar extends App.Controller
         @possibleMacros.push macro
 
     localeEl = $(App.view('ticket_zoom/attribute_bar')(
-      macros:           @possibleMacros
-      macroDisabled:    macroDisabled
-      overview_id:      @overview_id
-      resetButtonShown: resetButtonShown
+      macros:                 @possibleMacros
+      macroDisabled:          macroDisabled
+      sharedButtonVisible:    sharedButtonVisible
+      sharedDraftsDisabled:   !sharedDraftsEnabled
+      overview_id:            @overview_id
+      resetButtonShown:       resetButtonShown
+      sharedDraftButtonShown: sharedDraftButtonShown
     ))
+
     @setSecondaryAction(@secondaryAction, localeEl)
 
     if @ticket.currentView() is 'agent'
@@ -74,6 +107,26 @@ class App.TicketZoomAttributeBar extends App.Controller
 
     @html localeEl
 
+    @el.find('.js-draft').popover(
+      trigger:   'hover'
+      container: 'body'
+      html:      true
+      animation: false
+      delay:     100
+      placement: 'auto'
+      sanitize:  false
+      content:   =>
+        draft     = App.TicketSharedDraftZoom.findByAttribute 'ticket_id', @ticket?.id
+        timestamp = App.ViewHelpers.humanTime(draft?.updated_at)
+        user      = App.User.find draft?.updated_by_id
+        name      = user?.displayName()
+
+        content =  App.i18n.translatePlain('Last change %s<br>by %s', timestamp, name)
+
+        # needs linebreak to align vertically without title
+        '<br>' + content
+    )
+
   start: =>
     return if !@taskbarWatcher
     @taskbarWatcher.start()
@@ -106,9 +159,12 @@ class App.TicketZoomAttributeBar extends App.Controller
     macroId = $(e.currentTarget).data('id')
     macro = App.Macro.find(macroId)
 
-    @callback(e, macro)
+    @macroCallback(e, macro)
     @closeMacroMenu()
 
+  saveDraft: (e) =>
+    @draftCallback(e)
+
   onActionMacroMouseEnter: (e) =>
     @$(e.currentTarget).addClass('is-active')
 

+ 9 - 0
app/assets/javascripts/app/controllers/ticket_zoom/form_handler_shared_draft.coffee

@@ -0,0 +1,9 @@
+class TicketZoomFormHandlerSharedDraft
+
+  # central method, is getting called on every ticket form change
+  # but only trigger event for group_id changes
+  @run: (params, attribute, attributes, classname, form, ui) ->
+    return if attribute.name isnt 'group_id'
+    App.Event.trigger('ui::ticket::updateSharedDraft', { taskKey: ui.taskKey, newGroupId: params.group_id })
+
+App.Config.set('150-ticketFormSharedDraft', TicketZoomFormHandlerSharedDraft, 'TicketZoomFormHandler')

+ 5 - 4
app/assets/javascripts/app/controllers/ticket_zoom/sidebar_ticket.coffee

@@ -9,12 +9,13 @@ class Edit extends App.Controller
       return if data.ticket_id.toString() isnt @ticket.id.toString()
 
       @ticket   = App.Ticket.find(@ticket.id)
-      @formMeta = data.form_meta
-      @render()
+      if data.form_meta
+        @formMeta = data.form_meta
+      @render(data.draft)
     )
     @render()
 
-  render: =>
+  render: (draft = {}) =>
     defaults = @ticket.attributes()
     delete defaults.article # ignore article infos
     followUpPossible = App.Group.find(defaults.group_id).follow_up_possible
@@ -45,7 +46,7 @@ class Edit extends App.Controller
       handlersConfig: handlers
       filter:         @formMeta.filter
       formMeta:       @formMeta
-      params:         defaults
+      params:         _.extend(defaults, draft)
       isDisabled:     editable
       taskKey:        @taskKey
       core_workflow: {

+ 1 - 1
app/assets/javascripts/app/controllers/ticket_zoom/time_accounting.coffee

@@ -4,7 +4,7 @@ class App.TicketZoomTimeAccounting extends App.ControllerModal
   buttonSubmit: __('Account Time')
   buttonClass: 'btn--success'
   leftButtons: [{
-    className: 'btn--text btn--subtle js-skip',
+    className: 'js-skip',
     text: __('skip')
   }]
   head: __('Time Accounting')

+ 108 - 0
app/assets/javascripts/app/controllers/widget/shared_draft.coffee

@@ -0,0 +1,108 @@
+class App.WidgetSharedDraft extends App.Controller
+  constructor: ->
+    super
+    @load()
+    @subscribeId = App.TicketSharedDraftStart.subscribe(@render)
+    @render()
+
+  events:
+    'click .shared-draft-item': 'clicked'
+    'click .js-create':         'create'
+    'click .js-update':         'update'
+    'input #shared_draft_name': 'sharedDraftNameChanged'
+
+  elements:
+    '#shared_draft_name': 'sharedDraftNameInput'
+
+  render: =>
+    active_draft = App.TicketSharedDraftStart.find(@active_draft_id)
+
+    @html App.view('widget/shared_draft')(
+      shared_drafts: @visibleDrafts()
+      active_draft:  active_draft
+    )
+
+  load: =>
+    @ajax
+      id: 'shared_drafts_index'
+      type: 'GET'
+      url: @apiPath + '/tickets/shared_drafts'
+      processData: true
+      success: (data, status, xhr) =>
+        App.TicketSharedDraftStart.deleteAll()
+        App.Collection.loadAssets(data.assets)
+        @render()
+
+  visibleDrafts: ->
+    App.TicketSharedDraftStart.findAllByAttribute 'group_id', parseInt(@group_id)
+
+  clicked: (e) ->
+    shared_draft_id = e.currentTarget.getAttribute('shared-draft-id')
+    draft           = App.TicketSharedDraftStart.find shared_draft_id
+    hasChanges      = App.TaskManager.worker(@taskKey).changed()
+
+    new App.TicketSharedDraftModal(
+      container:    @el.closest('.content')
+      shared_draft: draft
+      hasChanges:   hasChanges
+      parent:       @
+    )
+
+  getParams: ->
+    form    = @formParam(@el.closest('.content').find('.ticket-create'))
+    meta    = @formParam(@el)
+    form_id = form.form_id
+
+    delete form.form_id
+
+    return false if meta.name.trim() == ''
+
+    JSON.stringify({
+      name:     meta.name
+      group_id: form.group_id
+      form_id:  form_id
+      content:  form
+    })
+
+  success: (data, status, xhr) =>
+    App.Collection.loadAssets(data.assets)
+    @render()
+
+  highlightError: ->
+    @sharedDraftNameInput
+      .addClass('has-error')
+      .focus()
+
+    false
+
+  sharedDraftNameChanged: (e) ->
+    @sharedDraftNameInput.removeClass('has-error')
+
+  create: (e) ->
+    @onAction(e,
+      id: 'shared_drafts_create'
+      type: 'POST'
+      url: @apiPath + '/tickets/shared_drafts'
+    )
+
+  update: (e) ->
+    @onAction(e,
+      id: 'shared_drafts_update'
+      type: 'PATCH'
+      url: @apiPath + '/tickets/shared_drafts/' + @active_draft_id
+    )
+
+  onAction: (e, options) ->
+    e.preventDefault()
+
+    params = @getParams()
+
+    return @highlightError() if !params
+
+    @ajax _.extend(options, { data: params, success: @success })
+
+  release: =>
+    if @subscribeId
+      App.TicketSharedDraftStart.unsubscribe(@subscribeId)
+
+    super

+ 12 - 3
app/assets/javascripts/app/controllers/widget/sidebar.coffee

@@ -102,13 +102,22 @@ class App.Sidebar extends App.Controller
   toggleTabAction: (name) =>
     return if !name
 
+    # remove active state
+    @tabs.removeClass('active')
+
+    if name == 'shared_draft'
+      draft_enabled = _.find @items, (elem) -> elem?.item?.name == 'shared_draft' and elem.sidebarItem()?
+
+      if !draft_enabled?
+        name = 'template'
+
+        available_sidebar = _.first @items, (elem) -> elem.sidebarItem()?
+        available_sidebar?.shown = true
+
     # remember sidebarState for outsite
     if @sidebarState
       @sidebarState.active = name
 
-    # remove active state
-    @tabs.removeClass('active')
-
     # add active state
     @$('.tabsSidebar-tab[data-tab=' + name + ']').addClass('active')
 

Некоторые файлы не были показаны из-за большого количества измененных файлов