ticket_overview.coffee 46 KB

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