ticket_overview.coffee 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608
  1. class App.TicketOverview extends App.Controller
  2. className: 'overviews'
  3. activeFocus: 'nav'
  4. mouse:
  5. x: null
  6. y: null
  7. batchAnimationPaused: false
  8. elements:
  9. '.js-batch-overlay': 'batchOverlay'
  10. '.js-batch-overlay-backdrop': 'batchOverlayBackdrop'
  11. '.js-batch-cancel': 'batchCancel'
  12. '.js-batch-macro-circle': 'batchMacroCircle'
  13. '.js-batch-assign-circle': 'batchAssignCircle'
  14. '.js-batch-assign': 'batchAssign'
  15. '.js-batch-assign-inner': 'batchAssignInner'
  16. '.js-batch-assign-group': 'batchAssignGroup'
  17. '.js-batch-assign-group-name': 'batchAssignGroupName'
  18. '.js-batch-assign-group-inner': 'batchAssignGroupInner'
  19. '.js-batch-macro': 'batchMacro'
  20. '.main': 'mainContent'
  21. events:
  22. 'mousedown .item': 'startDragItem'
  23. 'mouseenter .js-batch-hover-target': 'highlightBatchEntry'
  24. 'mouseleave .js-batch-hover-target': 'unhighlightBatchEntry'
  25. constructor: ->
  26. super
  27. @batchSupport = @permissionCheck('ticket.agent')
  28. @render()
  29. # rerender view, e. g. on language change
  30. @bind 'ui:rerender', =>
  31. @renderBatchOverlay()
  32. startDragItem: (event) =>
  33. return if !@batchSupport
  34. @grabbedItem = $(event.currentTarget)
  35. offset = @grabbedItem.offset()
  36. @batchDragger = $(App.view('ticket_overview/batch_dragger')())
  37. @grabbedItemClone = @grabbedItem.clone()
  38. @grabbedItemClone.data('offset', @grabbedItem.offset())
  39. @grabbedItemClone.addClass('batch-dragger-item js-main-item')
  40. @batchDragger.append @grabbedItemClone
  41. @batchDragger.data
  42. startX: event.pageX
  43. startY: event.pageY
  44. dx: Math.min(event.pageX - offset.left, 180)
  45. dy: event.pageY - offset.top
  46. moved: false
  47. $(document).on 'mousemove.item', @dragItem
  48. $(document).one 'mouseup.item', @endDragItem
  49. # TODO: fire @cancelDrag on ESC
  50. dragItem: (event) =>
  51. pos = @batchDragger.data()
  52. threshold = 3
  53. x = event.pageX - pos.dx
  54. y = event.pageY - pos.dy
  55. dir = if event.pageY > pos.startY then 1 else -1
  56. if !pos.moved
  57. if Math.abs(event.pageY - pos.startY) > threshold || Math.abs(event.pageX - pos.startX) > threshold
  58. @batchDragger.data 'moved', true
  59. @el.addClass('u-no-userselect')
  60. # check grabbed items batch checkbox to make sure its checked
  61. # (could be grabbed without checking the checkbox it)
  62. @grabbedItemWasntChecked = !@grabbedItem.find('[name="bulk"]').prop('checked')
  63. @grabbedItem.find('[name="bulk"]').prop('checked', true)
  64. @grabbedItemClone.find('[name="bulk"]').prop('checked', true)
  65. additionalItems = @el.find('[name="bulk"]:checked').parents('.item').not(@grabbedItem)
  66. additionalItemsClones = additionalItems.clone()
  67. @draggedItems = @grabbedItemClone.add(additionalItemsClones)
  68. # store offsets for later use
  69. additionalItemsClones.each (i, item) -> $(@).data('offset', additionalItems.eq(i).offset())
  70. @batchDragger.prepend additionalItemsClones.addClass('batch-dragger-item').get().reverse()
  71. if(additionalItemsClones.length)
  72. @batchDragger.find('.js-batch-dragger-count').text(@draggedItems.length)
  73. @renderOptions()
  74. $('#app').append @batchDragger
  75. @draggedItems.each (i, item) ->
  76. dx = $(item).data('offset').left - $(item).offset().left - x
  77. dy = $(item).data('offset').top - $(item).offset().top - y
  78. $.Velocity.hook item, 'translateX', "#{dx}px"
  79. $.Velocity.hook item, 'translateY', "#{dy}px"
  80. @alignDraggedItems(-dir)
  81. @mouseY = event.pageY
  82. @showBatchOverlay()
  83. else
  84. return
  85. event.preventDefault()
  86. $.Velocity.hook @batchDragger, 'translateX', "#{x}px"
  87. $.Velocity.hook @batchDragger, 'translateY', "#{y}px"
  88. endDragItem: (event) =>
  89. $(document).off 'mousemove.item'
  90. $(document).off 'mouseup.item'
  91. pos = @batchDragger.data()
  92. @clearDelay('clear-hovered-batch-entry')
  93. if !@hoveredBatchEntry
  94. @cleanUpDrag()
  95. return
  96. $.Velocity.hook @batchDragger, 'transformOriginX', "#{pos.dx}px"
  97. $.Velocity.hook @batchDragger, 'transformOriginY', "#{pos.dy}px"
  98. @hoveredBatchEntry.velocity
  99. properties:
  100. scale: 1.1
  101. options:
  102. duration: 200
  103. complete: =>
  104. if !@hoveredBatchEntry
  105. @cleanUpDrag()
  106. return
  107. @hoveredBatchEntry.velocity 'reverse',
  108. duration: 200
  109. complete: =>
  110. if !@hoveredBatchEntry
  111. @cleanUpDrag()
  112. return
  113. # clean scale
  114. action = @hoveredBatchEntry.attr('data-action')
  115. id = @hoveredBatchEntry.attr('data-id')
  116. groupId = @hoveredBatchEntry.attr('data-group-id')
  117. items = @el.find('[name="bulk"]:checked')
  118. @hoveredBatchEntry.removeAttr('style')
  119. @cleanUpDrag(true)
  120. @performBatchAction items, action, id, groupId
  121. @batchDragger.velocity
  122. properties:
  123. scale: 0
  124. options:
  125. duration: 200
  126. cancelDrag: ->
  127. $(document).off 'mousemove.item'
  128. $(document).off 'mouseup.item'
  129. @cleanUpDrag()
  130. cleanUpDrag: (success) ->
  131. @hideBatchOverlay()
  132. @el.removeClass('u-no-userselect')
  133. $('.batch-dragger').remove()
  134. @hoveredBatchEntry = null
  135. if @grabbedItemWasntChecked
  136. @grabbedItem.find('[name="bulk"]').prop('checked', false)
  137. if success
  138. # uncheck all checked items
  139. @el.find('[name="bulk"]:checked').prop('checked', false)
  140. @el.find('[name="bulk_all"]').prop('checked', false)
  141. alignDraggedItems: (dir) ->
  142. @draggedItems.velocity
  143. properties:
  144. translateX: 0
  145. translateY: (i) => dir * i * @batchDragger.height()/2
  146. options:
  147. easing: 'ease-in-out'
  148. duration: 300
  149. @batchDragger.find('.js-batch-dragger-count').velocity
  150. properties:
  151. translateY: if dir < 0 then 0 else -@batchDragger.height()+8
  152. options:
  153. easing: 'ease-in-out'
  154. duration: 300
  155. performBatchAction: (items, action, id, groupId) ->
  156. if action is 'macro'
  157. @batchCount = items.length
  158. @batchCountIndex = 0
  159. macro = App.Macro.find(id)
  160. for item in items
  161. #console.log "perform action #{action} with id #{id} on ", $(item).val()
  162. ticket = App.Ticket.find($(item).val())
  163. App.Ticket.macro(
  164. macro: macro.perform
  165. ticket: ticket
  166. )
  167. ticket.save(
  168. done: (r) =>
  169. @batchCountIndex++
  170. # refresh view after all tickets are proceeded
  171. if @batchCountIndex == @batchCount
  172. App.Event.trigger('overview:fetch')
  173. )
  174. return
  175. if action is 'user_assign'
  176. @batchCount = items.length
  177. @batchCountIndex = 0
  178. for item in items
  179. #console.log "perform action #{action} with id #{id} on ", $(item).val()
  180. ticket = App.Ticket.find($(item).val())
  181. ticket.owner_id = id
  182. if !_.isEmpty(groupId)
  183. ticket.group_id = groupId
  184. ticket.save(
  185. done: (r) =>
  186. @batchCountIndex++
  187. # refresh view after all tickets are proceeded
  188. if @batchCountIndex == @batchCount
  189. App.Event.trigger('overview:fetch')
  190. )
  191. return
  192. if action is 'group_assign'
  193. @batchCount = items.length
  194. @batchCountIndex = 0
  195. for item in items
  196. #console.log "perform action #{action} with id #{id} on ", $(item).val()
  197. ticket = App.Ticket.find($(item).val())
  198. ticket.group_id = id
  199. ticket.save(
  200. done: (r) =>
  201. @batchCountIndex++
  202. # refresh view after all tickets are proceeded
  203. if @batchCountIndex == @batchCount
  204. App.Event.trigger('overview:fetch')
  205. )
  206. return
  207. showBatchOverlay: ->
  208. @batchOverlay.addClass('is-visible')
  209. $('html').css('overflow', 'hidden')
  210. @batchOverlayBackdrop.velocity { opacity: [1, 0] }, { duration: 500 }
  211. @batchMacroOffset = @batchMacro.offset().top + @batchMacro.outerHeight()
  212. @batchAssignOffset = @batchAssign.offset().top
  213. @batchOverlayShown = true
  214. $(document).on 'mousemove.batchoverlay', @controlBatchOverlay
  215. hideBatchOverlay: ->
  216. $(document).off 'mousemove.batchoverlay'
  217. @batchOverlayShown = false
  218. @batchOverlayBackdrop.velocity { opacity: [0, 1] }, { duration: 300, queue: false }
  219. @hideBatchCircles =>
  220. @batchOverlay.removeClass('is-visible')
  221. $('html').css('overflow', '')
  222. if @batchAssignShown
  223. @hideBatchAssign()
  224. if @batchMacroShown
  225. @hideBatchMacro()
  226. if @batchAssignGroupShown
  227. @hideBatchAssignGroup()
  228. controlBatchOverlay: (event) =>
  229. return if @batchAnimationPaused
  230. # store to detect if the mouse is hovering a drag-action entry
  231. # after an animation ended -> @highlightBatchEntryAtMousePosition
  232. @mouse.x = event.pageX
  233. @mouse.y = event.pageY
  234. if @batchAssignGroupShown && @batchAssignGroupOffset != undefined
  235. if @mouse.y < @batchAssignGroupOffset
  236. @hideBatchAssignGroup()
  237. @batchAnimationPaused = true
  238. return
  239. if @mouse.y <= @batchMacroOffset
  240. mouseInArea = 'top'
  241. else if @mouse.y > @batchMacroOffset && @mouse.y <= @batchAssignOffset
  242. mouseInArea = 'middle'
  243. else
  244. mouseInArea = 'bottom'
  245. switch mouseInArea
  246. when 'top'
  247. if !@batchMacroShown
  248. @hideBatchCircles()
  249. @showBatchMacro()
  250. @alignDraggedItems(1)
  251. when 'middle'
  252. if @batchAssignShown
  253. @hideBatchAssign()
  254. if @batchMacroShown
  255. @hideBatchMacro()
  256. if !@batchCirclesShown
  257. @showBatchCircles()
  258. when 'bottom'
  259. if !@batchAssignShown
  260. @hideBatchCircles()
  261. @showBatchAssign()
  262. @alignDraggedItems(-1)
  263. showBatchCircles: ->
  264. @batchCirclesShown = true
  265. @batchMacroCircle.velocity
  266. properties:
  267. translateY: [0, '-150%']
  268. opacity: [1, 0]
  269. options:
  270. easing: [1,-.55,.2,1.37]
  271. duration: 500
  272. visibility: 'visible'
  273. delay: 200
  274. @batchAssignCircle.velocity
  275. properties:
  276. translateY: [0, '150%']
  277. opacity: [1, 0]
  278. options:
  279. easing: [1,-.55,.2,1.37]
  280. duration: 500
  281. visibility: 'visible'
  282. delay: 200
  283. hideBatchCircles: (callback) ->
  284. @batchMacroCircle.velocity
  285. properties:
  286. translateY: ['-150%', 0]
  287. opacity: [0, 1]
  288. options:
  289. duration: 300
  290. visibility: 'hidden'
  291. queue: false
  292. @batchAssignCircle.velocity
  293. properties:
  294. translateY: ['150%', 0]
  295. opacity: [0, 1]
  296. options:
  297. duration: 300
  298. complete: callback
  299. visibility: 'hidden'
  300. queue: false
  301. @batchCirclesShown = false
  302. showBatchAssign: ->
  303. return if !@batchOverlayShown # user might have dropped the item already
  304. @batchAssignShown = true
  305. @batchCancel.css
  306. top: 0
  307. bottom: @batchAssign.height()
  308. @batchAssign.velocity
  309. properties:
  310. translateY: [0, '100%']
  311. opacity: [1, 0]
  312. options:
  313. easing: [1,-.55,.2,1.37]
  314. duration: 500
  315. visibility: 'visible'
  316. complete: @highlightBatchEntryAtMousePosition
  317. @batchCancel.velocity
  318. properties:
  319. translateY: [0, '100%']
  320. opacity: [1, 0]
  321. options:
  322. easing: [1,-.55,.2,1.37]
  323. duration: 500
  324. visibility: 'visible'
  325. hideBatchAssign: ->
  326. @batchAssign.velocity
  327. properties:
  328. translateY: ['100%', 0]
  329. opacity: [0, 1]
  330. options:
  331. duration: 300
  332. visibility: 'hidden'
  333. queue: false
  334. complete: =>
  335. $.Velocity.hook @batchAssign, 'translateY', '0%'
  336. @batchCancel.velocity
  337. properties:
  338. translateY: ['100%', 0]
  339. opacity: [0, 1]
  340. options:
  341. duration: 300
  342. visibility: 'hidden'
  343. queue: false
  344. @batchAssignShown = false
  345. showBatchAssignGroup: =>
  346. return if !@batchOverlayShown # user might have dropped the item already
  347. @batchAssignGroupShown = true
  348. groupId = @hoveredBatchEntry.attr('data-id')
  349. group = App.Group.find(groupId)
  350. users = []
  351. for user_id in _.uniq(group.user_ids)
  352. if App.User.exists(user_id)
  353. user = App.User.find(user_id)
  354. if user.active is true
  355. users.push user
  356. @batchAssignGroupName.text group.displayName()
  357. @batchAssignGroupInner.html $(App.view('ticket_overview/batch_overlay_user_group')(
  358. users: users
  359. groups: []
  360. groupId: groupId
  361. ))
  362. # then adjust the size of the group that it almost overlaps the batch-assign box
  363. @batchAssignGroupInner.height(@batchAssignInner.height())
  364. @batchAssignGroup.velocity
  365. properties:
  366. translateY: [0, '100%']
  367. opacity: [1, 0]
  368. options:
  369. easing: [1,-.55,.2,1.37]
  370. duration: 700
  371. visibility: 'visible'
  372. complete: =>
  373. @highlightBatchEntryAtMousePosition()
  374. @batchAssignGroupOffset = @batchAssignGroup.offset().top
  375. hideBatchAssignGroup: ->
  376. @batchAssignGroup.velocity
  377. properties:
  378. translateY: ['100%', 0]
  379. opacity: [0, 1]
  380. options:
  381. duration: 300
  382. visibility: 'hidden'
  383. queue: false
  384. complete: =>
  385. @batchAssignGroupShown = false
  386. @batchAssignGroupHovered = false
  387. setTimeout (=> @batchAnimationPaused = false), 1000
  388. @batchAssignGroupOffset = undefined
  389. showBatchMacro: ->
  390. return if !@batchOverlayShown # user might have dropped the item already
  391. @batchMacroShown = true
  392. @batchCancel.css
  393. bottom: 0
  394. top: @batchMacro.height()
  395. @batchMacro.velocity
  396. properties:
  397. translateY: [0, '-100%']
  398. opacity: [1, 0]
  399. options:
  400. easing: [1,-.55,.2,1.37]
  401. duration: 500
  402. visibility: 'visible'
  403. complete: @highlightBatchEntryAtMousePosition
  404. @batchCancel.velocity
  405. properties:
  406. translateY: [0, '-100%']
  407. opacity: [1, 0]
  408. options:
  409. easing: [1,-.55,.2,1.37]
  410. duration: 500
  411. visibility: 'visible'
  412. hideBatchMacro: ->
  413. @batchMacro.velocity
  414. properties:
  415. translateY: ['-100%', 0]
  416. opacity: [0, 1]
  417. options:
  418. duration: 300
  419. visibility: 'hidden'
  420. queue: false
  421. complete: =>
  422. $.Velocity.hook @batchMacro, 'translateY', '0%'
  423. @batchCancel.velocity
  424. properties:
  425. translateY: ['-100%', 0]
  426. opacity: [0, 1]
  427. options:
  428. duration: 300
  429. visibility: 'hidden'
  430. queue: false
  431. @batchMacroShown = false
  432. highlightBatchEntryAtMousePosition: =>
  433. entryAtPoint = $(document.elementFromPoint(@mouse.x, @mouse.y)).closest('.js-batch-overlay-entry .avatar')
  434. if(entryAtPoint.length)
  435. @hoveredBatchEntry = entryAtPoint.closest('.js-batch-overlay-entry').addClass('is-hovered')
  436. highlightBatchEntry: (event) ->
  437. @clearDelay('clear-hovered-batch-entry')
  438. @hoveredBatchEntry = $(event.currentTarget).closest('.js-batch-overlay-entry').addClass('is-hovered')
  439. if @hoveredBatchEntry.attr('data-action') is 'group_assign'
  440. @batchAssignGroupHintTimeout = setTimeout @blinkBatchEntry, 800
  441. @batchAssignGroupTimeout = setTimeout @showBatchAssignGroup, 900
  442. unhighlightBatchEntry: (event) ->
  443. return if !@hoveredBatchEntry
  444. if @hoveredBatchEntry.attr('data-action') is 'group_assign'
  445. if @batchAssignGroupTimeout
  446. clearTimeout @batchAssignGroupTimeout
  447. if @batchAssignGroupHintTimeout
  448. clearTimeout @batchAssignGroupHintTimeout
  449. @hoveredBatchEntry.removeClass('is-hovered')
  450. delay = =>
  451. @hoveredBatchEntry = null
  452. @delay(delay, 800, 'clear-hovered-batch-entry')
  453. blinkBatchEntry: =>
  454. @hoveredBatchEntry
  455. .velocity({ opacity: [0.5, 1] }, { duration: 120 })
  456. .velocity({ opacity: [1, 0.5] }, { duration: 60, delay: 40 })
  457. .velocity({ opacity: [0.5, 1] }, { duration: 120 })
  458. .velocity({ opacity: [1, 0.5] }, { duration: 60, delay: 40 })
  459. render: ->
  460. elLocal = $(App.view('ticket_overview/index')())
  461. @navBarControllerVertical = new Navbar(
  462. el: elLocal.find('.overview-header')
  463. view: @view
  464. vertical: true
  465. )
  466. @navBarController = new Navbar(
  467. el: elLocal.filter('.sidebar')
  468. view: @view
  469. )
  470. @contentController = new Table(
  471. el: elLocal.find('.overview-table')
  472. view: @view
  473. keyboardOn: @keyboardOn
  474. keyboardOff: @keyboardOff
  475. )
  476. @renderBatchOverlay(elLocal.filter('.js-batch-overlay'))
  477. @html elLocal
  478. @el.find('.main').on('click', =>
  479. @activeFocus = 'overview'
  480. )
  481. @el.find('.sidebar').on('click', =>
  482. @activeFocus = 'nav'
  483. )
  484. @bind('overview:fetch', =>
  485. return if !@view
  486. update = =>
  487. App.OverviewListCollection.fetch(@view)
  488. @delay(update, 2800, 'overview:fetch')
  489. )
  490. renderBatchOverlay: (elLocal) =>
  491. if elLocal
  492. elLocal.html( App.view('ticket_overview/batch_overlay')() )
  493. return
  494. @batchOverlay.html( App.view('ticket_overview/batch_overlay')() )
  495. @refreshElements()
  496. renderOptions: =>
  497. macros = App.Macro.findAllByAttribute('active', true)
  498. groups = App.Group.findAllByAttribute('active', true)
  499. users = []
  500. items = @el.find('[name="bulk"]:checked')
  501. # find all possible owners for selected tickets
  502. possibleUsers = {}
  503. possibleUserGroups = {}
  504. for item in items
  505. #console.log "selected items with id ", $(item).val()
  506. ticket = App.Ticket.find($(item).val())
  507. if !possibleUserGroups[ticket.group_id.toString()]
  508. group = App.Group.find(ticket.group_id)
  509. for user_id in group.user_ids
  510. if !possibleUserGroups[ticket.group_id.toString()]
  511. possibleUsers[user_id.toString()] = true
  512. else
  513. hit = false
  514. for user_id, exists of possibleUsers
  515. if possibleUsers[user_id.toString()]
  516. hit = true
  517. if !hit
  518. delete possibleUsers[user_id.toString()]
  519. possibleUserGroups[ticket.group_id.toString()] = true
  520. for user_id, _exists of possibleUsers
  521. if App.User.exists(user_id)
  522. user = App.User.find(user_id)
  523. if user.active is true
  524. users.push user
  525. for group in groups
  526. valid_user_ids = []
  527. for user_id in _.uniq(group.user_ids)
  528. if App.User.exists(user_id)
  529. if App.User.find(user_id).active is true
  530. valid_user_ids.push user_id
  531. group.valid_user_ids = valid_user_ids
  532. @batchAssignInner.html $(App.view('ticket_overview/batch_overlay_user_group')(
  533. users: users
  534. groups: groups
  535. ))
  536. @batchMacro.html $(App.view('ticket_overview/batch_overlay_macro')(
  537. macros: macros
  538. ))
  539. active: (state) =>
  540. return @shown if state is undefined
  541. @shown = state
  542. url: =>
  543. "#ticket/view/#{@view}"
  544. show: (params) =>
  545. @keyboardOn()
  546. # highlight navbar
  547. @navupdate '#ticket/view'
  548. # redirect to last overview if we got called in first level
  549. @view = params['view']
  550. if !@view && @viewLast
  551. @navigate "ticket/view/#{@viewLast}", true
  552. return
  553. # build nav bar
  554. if @navBarController
  555. @navBarController.update
  556. view: @view
  557. activeState: true
  558. if @navBarControllerVertical
  559. @navBarControllerVertical.update
  560. view: @view
  561. activeState: true
  562. # do not rerender overview if current overview is requested again
  563. return if @viewLast is @view
  564. # remember last view
  565. @viewLast = @view
  566. # build content
  567. @contentController = new Table(
  568. el: @$('.overview-table')
  569. view: @view
  570. keyboardOn: @keyboardOn
  571. keyboardOff: @keyboardOff
  572. )
  573. hide: =>
  574. @keyboardOff()
  575. if @navBarController
  576. @navBarController.active(false)
  577. if @navBarControllerVertical
  578. @navBarControllerVertical.active(false)
  579. setPosition: (position) =>
  580. @$('.main').scrollTop(position)
  581. currentPosition: =>
  582. @$('.main').scrollTop()
  583. changed: ->
  584. false
  585. release: ->
  586. @keyboardOff()
  587. super
  588. keyboardOn: =>
  589. $(window).off 'keydown.overview_navigation'
  590. $(window).on 'keydown.overview_navigation', @listNavigate
  591. keyboardOff: ->
  592. $(window).off 'keydown.overview_navigation'
  593. listNavigate: (e) =>
  594. # ignore if focus is in bulk action
  595. return if $(e.target).is('textarea, input, select')
  596. if e.keyCode is 38 # up
  597. e.preventDefault()
  598. @nudge(e, -1)
  599. return
  600. else if e.keyCode is 40 # down
  601. e.preventDefault()
  602. @nudge(e, 1)
  603. return
  604. else if e.keyCode is 32 # space
  605. e.preventDefault()
  606. if @activeFocus is 'overview'
  607. @$('.table-overview table tbody tr.is-hover td.js-checkbox-field label input').first().click()
  608. else if e.keyCode is 9 # tab
  609. e.preventDefault()
  610. if @activeFocus is 'nav'
  611. @activeFocus = 'overview'
  612. @nudge(e, 1)
  613. else
  614. @activeFocus = 'nav'
  615. else if e.keyCode is 13 # enter
  616. if @activeFocus is 'overview'
  617. location = @$('.table-overview table tbody tr.is-hover a').first().attr('href')
  618. if location
  619. @navigate location
  620. nudge: (e, position) ->
  621. if @activeFocus is 'overview'
  622. items = @$('.table-overview table tbody')
  623. current = items.find('tr.is-hover')
  624. if !current.size()
  625. items.find('tr').first().addClass('is-hover')
  626. return
  627. if position is 1
  628. next = current.next('tr')
  629. if next.size()
  630. current.removeClass('is-hover')
  631. next.addClass('is-hover')
  632. else
  633. prev = current.prev('tr')
  634. if prev.size()
  635. current.removeClass('is-hover')
  636. prev.addClass('is-hover')
  637. if next
  638. @scrollToIfNeeded(next, true)
  639. if prev
  640. @scrollToIfNeeded(prev, true)
  641. else
  642. # get current
  643. items = @$('.sidebar')
  644. current = items.find('li.active')
  645. if !current.size()
  646. location = items.find('li a').first().attr('href')
  647. if location
  648. @navigate location
  649. return
  650. if position is 1
  651. next = current.next('li')
  652. if next.size()
  653. @navigate next.find('a').attr('href')
  654. else
  655. prev = current.prev('li')
  656. if prev.size()
  657. @navigate prev.find('a').attr('href')
  658. if next
  659. @scrollToIfNeeded(next, true)
  660. if prev
  661. @scrollToIfNeeded(prev, true)
  662. class Navbar extends App.Controller
  663. elements:
  664. '.js-tabsHolder': 'tabsHolder'
  665. '.js-tabsClone': 'clone'
  666. '.js-tabClone': 'tabClone'
  667. '.js-tabs': 'tabs'
  668. '.js-tab': 'tab'
  669. '.js-dropdown': 'dropdown'
  670. '.js-toggle': 'dropdownToggle'
  671. '.js-dropdownItem': 'dropdownItem'
  672. events:
  673. 'click .js-tab': 'activate'
  674. 'click .js-dropdownItem': 'navigateTo'
  675. 'hide.bs.dropdown': 'onDropdownHide'
  676. 'show.bs.dropdown': 'onDropdownShow'
  677. constructor: ->
  678. super
  679. @bindId = App.OverviewIndexCollection.bind(@render)
  680. # rerender view, e. g. on language change
  681. @bind 'ui:rerender', =>
  682. @render(App.OverviewIndexCollection.get())
  683. if @vertical
  684. $(window).on 'resize.navbar', @autoFoldTabs
  685. navigateTo: (event) ->
  686. location.hash = $(event.currentTarget).attr('data-target')
  687. onDropdownShow: =>
  688. @dropdownToggle.addClass('active')
  689. onDropdownHide: =>
  690. @dropdownToggle.removeClass('active')
  691. activate: (event) =>
  692. @tab.removeClass('active')
  693. $(event.currentTarget).addClass('active')
  694. release: =>
  695. if @vertical
  696. $(window).off 'resize.navbar', @autoFoldTabs
  697. App.OverviewIndexCollection.unbindById(@bindId)
  698. autoFoldTabs: =>
  699. items = App.OverviewIndexCollection.get()
  700. @html App.view("agent_ticket_view/navbar#{ if @vertical then '_vertical' }")
  701. items: items
  702. isAgent: @permissionCheck('ticket.agent')
  703. while @clone.width() > @tabsHolder.width()
  704. @tabClone.not('.hide').last().addClass('hide')
  705. @tab.not('.hide').last().addClass('hide')
  706. @dropdownItem.filter('.hide').last().removeClass('hide')
  707. # if all tabs are visible
  708. # remove dropdown and dropdown button
  709. if @dropdownItem.not('.hide').size() is 0
  710. @dropdown.remove()
  711. @dropdownToggle.remove()
  712. active: (state) =>
  713. @activeState = state
  714. update: (params = {}) ->
  715. for key, value of params
  716. @[key] = value
  717. @render(App.OverviewIndexCollection.get())
  718. render: (data) =>
  719. return if !data
  720. content = @el.closest('.content')
  721. if _.isArray(data) && _.isEmpty(data)
  722. content.find('.sidebar').addClass('hide')
  723. content.find('.main').addClass('hide')
  724. content.find('.js-error').removeClass('hide')
  725. @renderScreenError(
  726. el: @el.closest('.content').find('.js-error')
  727. detail: 'Currently no overview is assigned to your roles. Please contact your administrator.'
  728. objectName: 'Ticket'
  729. )
  730. return
  731. content.find('.sidebar').removeClass('hide')
  732. content.find('.main').removeClass('hide')
  733. content.find('.js-error').addClass('hide')
  734. # do not show vertical navigation if only one tab exists
  735. if @vertical
  736. if data && data.length <= 1
  737. @el.addClass('hidden')
  738. else
  739. @el.removeClass('hidden')
  740. # set page title
  741. if @activeState && @view && !@vertical
  742. for item in data
  743. if item.link is @view
  744. @title item.name, true
  745. # redirect to first view
  746. if @activeState && !@view && !@vertical
  747. view = data[0].link
  748. @navigate "ticket/view/#{view}", true
  749. return
  750. # add new views
  751. for item in data
  752. item.target = "#ticket/view/#{item.link}"
  753. if item.link is @view
  754. item.active = true
  755. activeOverview = item
  756. else
  757. item.active = false
  758. @html App.view("agent_ticket_view/navbar#{ if @vertical then '_vertical' else '' }")
  759. items: data
  760. if @vertical
  761. @autoFoldTabs()
  762. class Table extends App.Controller
  763. events:
  764. 'click [data-type=settings]': 'settings'
  765. 'click [data-type=viewmode]': 'viewmode'
  766. constructor: ->
  767. super
  768. if @view
  769. @bindId = App.OverviewListCollection.bind(@view, @updateTable)
  770. # rerender view, e. g. on langauge change
  771. @bind 'ui:rerender', =>
  772. return if !@authenticateCheck()
  773. return if !@view
  774. @render(App.OverviewListCollection.get(@view))
  775. release: =>
  776. if @bindId
  777. App.OverviewListCollection.unbind(@bindId)
  778. update: (params) =>
  779. for key, value of params
  780. @[key] = value
  781. return if !@view
  782. if @view
  783. if @bindId
  784. App.OverviewListCollection.unbind(@bindId)
  785. @bindId = App.OverviewListCollection.bind(@view, @updateTable)
  786. updateTable: (data) =>
  787. if !@table
  788. @render(data)
  789. return
  790. # use cache
  791. overview = data.overview
  792. tickets = data.tickets
  793. return if !overview && !tickets
  794. # get ticket list
  795. ticketListShow = []
  796. for ticket in tickets
  797. ticketListShow.push App.Ticket.find(ticket.id)
  798. @overview = App.Overview.find(overview.id)
  799. @table.update(
  800. overviewAttributes: @overview.view.s
  801. objects: ticketListShow
  802. groupBy: @overview.group_by
  803. orderBy: @overview.order.by
  804. orderDirection: @overview.order.direction
  805. )
  806. render: (data) =>
  807. return if !data
  808. # use cache
  809. overview = data.overview
  810. tickets = data.tickets
  811. return if !overview && !tickets
  812. @view_mode = App.LocalStorage.get("mode:#{@view}", @Session.get('id')) || 's'
  813. console.log 'notice', 'view:', @view, @view_mode
  814. # get ticket list
  815. ticketListShow = []
  816. for ticket in tickets
  817. ticketListShow.push App.Ticket.find(ticket.id)
  818. # if customer and no ticket exists, show the following message only
  819. if !ticketListShow[0] && @permissionCheck('ticket.customer')
  820. @html App.view('customer_not_ticket_exists')()
  821. return
  822. # set page title
  823. @overview = App.Overview.find(overview.id)
  824. # render init page
  825. checkbox = true
  826. edit = false
  827. if @permissionCheck('admin.overview')
  828. edit = true
  829. if @permissionCheck('ticket.customer')
  830. checkbox = false
  831. edit = false
  832. view_modes = [
  833. {
  834. name: 'S'
  835. type: 's'
  836. class: 'active' if @view_mode is 's'
  837. },
  838. {
  839. name: 'M'
  840. type: 'm'
  841. class: 'active' if @view_mode is 'm'
  842. }
  843. ]
  844. if @permissionCheck('ticket.customer')
  845. view_modes = []
  846. html = App.view('agent_ticket_view/content')(
  847. overview: @overview
  848. view_modes: view_modes
  849. edit: edit
  850. )
  851. html = $(html)
  852. @html html
  853. # create table/overview
  854. table = ''
  855. if @view_mode is 'm'
  856. table = App.view('agent_ticket_view/detail')(
  857. overview: @overview
  858. objects: ticketListShow
  859. checkbox: checkbox
  860. )
  861. table = $(table)
  862. table.delegate('[name="bulk_all"]', 'change', (e) ->
  863. if $(e.currentTarget).prop('checked')
  864. $(e.currentTarget).closest('table').find('[name="bulk"]').prop('checked', true)
  865. else
  866. $(e.currentTarget).closest('table').find('[name="bulk"]').prop('checked', false)
  867. )
  868. @$('.table-overview').append(table)
  869. else
  870. openTicket = (id,e) =>
  871. # open ticket via task manager to provide task with overview info
  872. ticket = App.Ticket.findNative(id)
  873. App.TaskManager.execute(
  874. key: "Ticket-#{ticket.id}"
  875. controller: 'TicketZoom'
  876. params:
  877. ticket_id: ticket.id
  878. overview_id: @overview.id
  879. show: true
  880. )
  881. @navigate ticket.uiUrl()
  882. callbackTicketTitleAdd = (value, object, attribute, attributes) ->
  883. attribute.title = object.title
  884. value
  885. callbackLinkToTicket = (value, object, attribute, attributes) ->
  886. attribute.link = object.uiUrl()
  887. value
  888. callbackUserPopover = (value, object, attribute, attributes) ->
  889. return value if !object
  890. refObjectId = undefined
  891. if attribute.name is 'customer_id'
  892. refObjectId = object.customer_id
  893. if attribute.name is 'owner_id'
  894. refObjectId = object.owner_id
  895. return value if !refObjectId
  896. attribute.class = 'user-popover'
  897. attribute.data =
  898. id: refObjectId
  899. value
  900. callbackOrganizationPopover = (value, object, attribute, attributes) ->
  901. return value if !object
  902. return value if !object.organization_id
  903. attribute.class = 'organization-popover'
  904. attribute.data =
  905. id: object.organization_id
  906. value
  907. callbackCheckbox = (id, checked, e) =>
  908. if @$('table').find('input[name="bulk"]:checked').length == 0
  909. @bulkForm.hide()
  910. else
  911. @bulkForm.show()
  912. if @lastChecked && e.shiftKey
  913. # check items in a row
  914. currentItem = $(e.currentTarget).parents('.item')
  915. lastCheckedItem = $(@lastChecked).parents('.item')
  916. items = currentItem.parent().children()
  917. if currentItem.index() > lastCheckedItem.index()
  918. # current item is below last checked item
  919. startId = lastCheckedItem.index()
  920. endId = currentItem.index()
  921. else
  922. # current item is above last checked item
  923. startId = currentItem.index()
  924. endId = lastCheckedItem.index()
  925. items.slice(startId+1, endId).find('[name="bulk"]').prop('checked', (-> !@checked))
  926. @lastChecked = e.currentTarget
  927. callbackIconHeader = (headers) ->
  928. attribute =
  929. name: 'icon'
  930. display: ''
  931. translation: false
  932. width: '28px'
  933. displayWidth:28
  934. unresizable: true
  935. headers.unshift(0)
  936. headers[0] = attribute
  937. headers
  938. callbackIcon = (value, object, attribute, header) ->
  939. value = ' '
  940. attribute.class = object.iconClass()
  941. attribute.link = ''
  942. attribute.title = object.iconTitle()
  943. value
  944. @table = new App.ControllerTable(
  945. tableId: "ticket_overview_#{@overview.id}"
  946. overview: @overview.view.s
  947. el: @$('.table-overview')
  948. model: App.Ticket
  949. objects: ticketListShow
  950. checkbox: checkbox
  951. groupBy: @overview.group_by
  952. orderBy: @overview.order.by
  953. orderDirection: @overview.order.direction
  954. class: 'table--light'
  955. bindRow:
  956. events:
  957. 'click': openTicket
  958. #bindCol:
  959. # customer_id:
  960. # events:
  961. # 'mouseover': popOver
  962. callbackHeader: [ callbackIconHeader ]
  963. callbackAttributes:
  964. icon:
  965. [ callbackIcon ]
  966. customer_id:
  967. [ callbackUserPopover ]
  968. organization_id:
  969. [ callbackOrganizationPopover ]
  970. owner_id:
  971. [ callbackUserPopover ]
  972. title:
  973. [ callbackLinkToTicket, callbackTicketTitleAdd ]
  974. number:
  975. [ callbackLinkToTicket, callbackTicketTitleAdd ]
  976. bindCheckbox:
  977. events:
  978. 'click': callbackCheckbox
  979. )
  980. # start user popups
  981. @userPopups()
  982. # start organization popups
  983. @organizationPopups()
  984. @bulkForm = new BulkForm(
  985. holder: @el
  986. view: @view
  987. )
  988. # start bulk action observ
  989. @el.append(@bulkForm.el)
  990. if @$('.table-overview').find('input[name="bulk"]:checked').length isnt 0
  991. @bulkForm.show()
  992. # show/hide bulk action
  993. @$('.table-overview').delegate('input[name="bulk"], input[name="bulk_all"]', 'change', (e) =>
  994. if @$('.table-overview').find('input[name="bulk"]:checked').length == 0
  995. @bulkForm.hide()
  996. @bulkForm.reset()
  997. else
  998. @bulkForm.show()
  999. )
  1000. # deselect bulk_all if one item is uncheck observ
  1001. @$('.table-overview').delegate('[name="bulk"]', 'change', (e) =>
  1002. bulkAll = @$('.table-overview').find('[name="bulk_all"]')
  1003. checkedCount = @$('.table-overview').find('input[name="bulk"]:checked').length
  1004. checkboxCount = @$('.table-overview').find('input[name="bulk"]').length
  1005. if checkedCount is 0
  1006. bulkAll.prop('indeterminate', false)
  1007. bulkAll.prop('checked', false)
  1008. else
  1009. if checkedCount is checkboxCount
  1010. bulkAll.prop('indeterminate', false)
  1011. bulkAll.prop('checked', true)
  1012. else
  1013. bulkAll.prop('checked', false)
  1014. bulkAll.prop('indeterminate', true)
  1015. )
  1016. viewmode: (e) =>
  1017. e.preventDefault()
  1018. @view_mode = $(e.target).data('mode')
  1019. App.LocalStorage.set("mode:#{@view}", @view_mode, @Session.get('id'))
  1020. @fetch()
  1021. #@render()
  1022. settings: (e) =>
  1023. e.preventDefault()
  1024. @keyboardOff()
  1025. new App.OverviewSettings(
  1026. overview_id: @overview.id
  1027. view_mode: @view_mode
  1028. container: @el.closest('.content')
  1029. onCloseCallback: @keyboardOn
  1030. )
  1031. class BulkForm extends App.Controller
  1032. className: 'bulkAction hide'
  1033. events:
  1034. 'submit form': 'submit'
  1035. 'click .js-submit': 'submit'
  1036. 'click .js-confirm': 'confirm'
  1037. 'click .js-cancel': 'reset'
  1038. constructor: ->
  1039. super
  1040. @configure_attributes_ticket = []
  1041. used_attributes = ['state_id', 'pending_time', 'priority_id', 'group_id', 'owner_id']
  1042. attributesClean = App.Ticket.attributesGet('edit')
  1043. for attributeName, attribute of attributesClean
  1044. if _.contains(used_attributes, attributeName)
  1045. localAttribute = clone(attribute)
  1046. localAttribute.nulloption = true
  1047. localAttribute.default = ''
  1048. localAttribute.null = true
  1049. @configure_attributes_ticket.push localAttribute
  1050. @holder = @options.holder
  1051. @visible = false
  1052. load = (data) =>
  1053. App.Collection.loadAssets(data.assets)
  1054. @formMeta = data.form_meta
  1055. @render()
  1056. @bindId = App.TicketCreateCollection.bind(load)
  1057. release: =>
  1058. App.TicketCreateCollection.unbind(@bindId)
  1059. render: ->
  1060. @el.css('right', App.Utils.getScrollBarWidth())
  1061. @html(App.view('agent_ticket_view/bulk')())
  1062. handlers = @Config.get('TicketZoomFormHandler')
  1063. new App.ControllerForm(
  1064. el: @$('#form-ticket-bulk')
  1065. model:
  1066. configure_attributes: @configure_attributes_ticket
  1067. className: 'create'
  1068. labelClass: 'input-group-addon'
  1069. handlersConfig: handlers
  1070. params: {}
  1071. filter: @formMeta.filter
  1072. formMeta: @formMeta
  1073. noFieldset: true
  1074. )
  1075. new App.ControllerForm(
  1076. el: @$('#form-ticket-bulk-comment')
  1077. model:
  1078. configure_attributes: [{ name: 'body', display: 'Comment', tag: 'textarea', rows: 4, null: true, upload: false, item_class: 'flex' }]
  1079. className: 'create'
  1080. labelClass: 'input-group-addon'
  1081. noFieldset: true
  1082. )
  1083. @confirm_attributes = [
  1084. { name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', filter: @articleTypeFilter, default: '9', translate: true, class: 'medium' }
  1085. { name: 'internal', display: 'Visibility', tag: 'select', null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: '', default: false }
  1086. ]
  1087. new App.ControllerForm(
  1088. el: @$('#form-ticket-bulk-typeVisibility')
  1089. model:
  1090. configure_attributes: @confirm_attributes
  1091. className: 'create'
  1092. labelClass: 'input-group-addon'
  1093. noFieldset: true
  1094. )
  1095. articleTypeFilter: (items) ->
  1096. for item in items
  1097. if item.name is 'note'
  1098. return [item]
  1099. items
  1100. confirm: =>
  1101. @$('.js-action-step').addClass('hide')
  1102. @$('.js-confirm-step').removeClass('hide')
  1103. @makeSpaceForTableRows()
  1104. # need a delay because of the click event
  1105. setTimeout ( => @$('.textarea.form-group textarea').focus() ), 0
  1106. reset: =>
  1107. @cancel()
  1108. if @visible
  1109. @makeSpaceForTableRows()
  1110. cancel: =>
  1111. @$('.js-action-step').removeClass('hide')
  1112. @$('.js-confirm-step').addClass('hide')
  1113. show: =>
  1114. @el.removeClass('hide')
  1115. @visible = true
  1116. @makeSpaceForTableRows()
  1117. hide: =>
  1118. @el.addClass('hide')
  1119. @visible = false
  1120. @removeSpaceForTableRows()
  1121. makeSpaceForTableRows: =>
  1122. height = @el.height()
  1123. scrollParent = @holder.scrollParent()
  1124. isScrolledToBottom = scrollParent.prop('scrollHeight') is scrollParent.scrollTop() + scrollParent.outerHeight()
  1125. @holder.css('margin-bottom', height)
  1126. if isScrolledToBottom
  1127. scrollParent.scrollTop scrollParent.prop('scrollHeight') - scrollParent.outerHeight()
  1128. removeSpaceForTableRows: =>
  1129. @holder.css('margin-bottom', 0)
  1130. ticketMergeParams: (params) ->
  1131. ticketUpdate = {}
  1132. for item of params
  1133. if params[item] != '' && params[item] != null
  1134. ticketUpdate[item] = params[item]
  1135. # in case if a group is selected, set also the selected owner (maybe nobody)
  1136. if params.group_id != '' && params.group_id != null
  1137. ticketUpdate.owner_id = params.owner_id
  1138. ticketUpdate
  1139. submit: (e) =>
  1140. e.preventDefault()
  1141. @bulkCount = @holder.find('.table-overview').find('[name="bulk"]:checked').length
  1142. if @bulkCount is 0
  1143. App.Event.trigger 'notify', {
  1144. type: 'error'
  1145. msg: App.i18n.translateContent('At least one object must be selected.')
  1146. }
  1147. return
  1148. ticket_ids = []
  1149. @holder.find('.table-overview').find('[name="bulk"]:checked').each( (index, element) ->
  1150. ticket_id = $(element).val()
  1151. ticket_ids.push ticket_id
  1152. )
  1153. params = @formParam(e.target)
  1154. for ticket_id in ticket_ids
  1155. ticket = App.Ticket.find(ticket_id)
  1156. ticketUpdate = @ticketMergeParams(params)
  1157. ticket.load(ticketUpdate)
  1158. # if title is empty - ticket can't processed, set ?
  1159. if _.isEmpty(ticket.title)
  1160. ticket.title = '-'
  1161. # validate ticket
  1162. errors = ticket.validate(
  1163. screen: 'edit'
  1164. )
  1165. if errors
  1166. @log 'error', 'update', errors
  1167. errorString = ''
  1168. for key, error of errors
  1169. errorString += "#{key}: #{error}"
  1170. @formValidate(
  1171. form: e.target
  1172. errors: errors
  1173. screen: 'edit'
  1174. )
  1175. App.Event.trigger 'notify', {
  1176. type: 'error'
  1177. msg: App.i18n.translateContent('Bulk action stopped %s!', errorString)
  1178. }
  1179. @cancel()
  1180. return
  1181. @bulkCountIndex = 0
  1182. for ticket_id in ticket_ids
  1183. ticket = App.Ticket.find(ticket_id)
  1184. # update ticket
  1185. ticketUpdate = @ticketMergeParams(params)
  1186. # validate article
  1187. if params['body']
  1188. article = new App.TicketArticle
  1189. params.from = @Session.get().displayName()
  1190. params.ticket_id = ticket.id
  1191. params.form_id = @form_id
  1192. sender = App.TicketArticleSender.findByAttribute('name', 'Agent')
  1193. type = App.TicketArticleType.find(params['type_id'])
  1194. params.sender_id = sender.id
  1195. if !params['internal']
  1196. params['internal'] = false
  1197. @log 'notice', 'update article', params, sender
  1198. article.load(params)
  1199. errors = article.validate()
  1200. if errors
  1201. @log 'error', 'update article', errors
  1202. @formEnable(e)
  1203. return
  1204. ticket.load(ticketUpdate)
  1205. # if title is empty - ticket can't processed, set ?
  1206. if _.isEmpty(ticket.title)
  1207. ticket.title = '-'
  1208. ticket.save(
  1209. done: (r) =>
  1210. @bulkCountIndex++
  1211. # reset form after save
  1212. if article
  1213. article.save(
  1214. fail: (r) =>
  1215. @log 'error', 'update article', r
  1216. )
  1217. # refresh view after all tickets are proceeded
  1218. if @bulkCountIndex == @bulkCount
  1219. @render()
  1220. @hide()
  1221. # fetch overview data again
  1222. App.Event.trigger('overview:fetch')
  1223. fail: (r) =>
  1224. @bulkCountIndex++
  1225. @log 'error', 'update ticket', r
  1226. App.Event.trigger 'notify', {
  1227. type: 'error'
  1228. msg: App.i18n.translateContent('Can\'t update Ticket %s!', ticket.number)
  1229. }
  1230. )
  1231. @holder.find('.table-overview').find('[name="bulk"]:checked').prop('checked', false)
  1232. App.Event.trigger 'notify', {
  1233. type: 'success'
  1234. msg: App.i18n.translateContent('Bulk action executed!')
  1235. }
  1236. class App.OverviewSettings extends App.ControllerModal
  1237. buttonClose: true
  1238. buttonCancel: true
  1239. buttonSubmit: true
  1240. headPrefix: 'Edit'
  1241. content: =>
  1242. @overview = App.Overview.find(@overview_id)
  1243. @head = @overview.name
  1244. @configure_attributes_article = []
  1245. if @view_mode is 'd'
  1246. @configure_attributes_article.push({
  1247. name: 'view::per_page'
  1248. display: 'Items per page'
  1249. tag: 'select'
  1250. multiple: false
  1251. null: false
  1252. default: @overview.view.per_page
  1253. options: {
  1254. 5: ' 5'
  1255. 10: '10'
  1256. 15: '15'
  1257. 20: '20'
  1258. 25: '25'
  1259. },
  1260. })
  1261. @configure_attributes_article.push({
  1262. name: "view::#{@view_mode}"
  1263. display: 'Attributes'
  1264. tag: 'checkboxTicketAttributes'
  1265. default: @overview.view[@view_mode]
  1266. null: false
  1267. translate: true
  1268. sortBy: null
  1269. },
  1270. {
  1271. name: 'order::by'
  1272. display: 'Order'
  1273. tag: 'selectTicketAttributes'
  1274. default: @overview.order.by
  1275. null: false
  1276. translate: true
  1277. sortBy: null
  1278. },
  1279. {
  1280. name: 'order::direction'
  1281. display: 'Direction'
  1282. tag: 'select'
  1283. default: @overview.order.direction
  1284. null: false
  1285. translate: true
  1286. options:
  1287. ASC: 'up'
  1288. DESC: 'down'
  1289. },
  1290. {
  1291. name: 'group_by'
  1292. display: 'Group by'
  1293. tag: 'select'
  1294. default: @overview.group_by
  1295. null: true
  1296. nulloption: true
  1297. translate: true
  1298. options: App.Overview.groupByAttributes()
  1299. })
  1300. controller = new App.ControllerForm(
  1301. model: { configure_attributes: @configure_attributes_article }
  1302. autofocus: false
  1303. )
  1304. controller.form
  1305. onClose: =>
  1306. if @onCloseCallback
  1307. @onCloseCallback()
  1308. onSubmit: (e) =>
  1309. params = @formParam(e.target)
  1310. # check if re-fetch is needed
  1311. @reload_needed = false
  1312. if @overview.order.by isnt params.order.by
  1313. @overview.order.by = params.order.by
  1314. @reload_needed = true
  1315. if @overview.order.direction isnt params.order.direction
  1316. @overview.order.direction = params.order.direction
  1317. @reload_needed = true
  1318. for key, value of params.view
  1319. @overview.view[key] = value
  1320. @overview.group_by = params.group_by
  1321. @overview.save(
  1322. done: =>
  1323. # fetch overview data again
  1324. if @reload_needed
  1325. App.OverviewListCollection.fetch(@overview.link)
  1326. else
  1327. App.OverviewIndexCollection.trigger()
  1328. App.OverviewListCollection.trigger(@overview.link)
  1329. # close modal
  1330. @close()
  1331. )
  1332. class TicketOverviewRouter extends App.ControllerPermanent
  1333. requiredPermission: ['ticket.agent', 'ticket.customer']
  1334. constructor: (params) ->
  1335. super
  1336. # cleanup params
  1337. clean_params =
  1338. view: params.view
  1339. App.TaskManager.execute(
  1340. key: 'TicketOverview'
  1341. controller: 'TicketOverview'
  1342. params: clean_params
  1343. show: true
  1344. persistent: true
  1345. )
  1346. App.Config.set('ticket/view', TicketOverviewRouter, 'Routes')
  1347. App.Config.set('ticket/view/:view', TicketOverviewRouter, 'Routes')
  1348. App.Config.set('TicketOverview', { controller: 'TicketOverview', permission: ['ticket.agent', 'ticket.customer'] }, 'permanentTask')
  1349. App.Config.set('TicketOverview', { prio: 1000, parent: '', name: 'Overviews', target: '#ticket/view', key: 'TicketOverview', permission: ['ticket.agent', 'ticket.customer'], class: 'overviews' }, 'NavBar')