Moved to new table api.
This commit is contained in:
parent
7a0e95014c
commit
e417001822
19 changed files with 687 additions and 555 deletions
|
@ -71,7 +71,7 @@ class App.ControllerGenericEdit extends App.ControllerModal
|
|||
|
||||
submit: (e) ->
|
||||
e.preventDefault()
|
||||
params = @formParam(e.target)
|
||||
params = @formParam(e.target)
|
||||
@item.load(params)
|
||||
|
||||
# validate
|
||||
|
@ -169,20 +169,14 @@ class App.ControllerGenericIndex extends App.Controller
|
|||
objects: objects
|
||||
overview: overview
|
||||
attributes: attributes
|
||||
groupBy: 'state'
|
||||
bindRow:
|
||||
events:
|
||||
'click': @edit
|
||||
)
|
||||
|
||||
binds = {}
|
||||
for item in attributes
|
||||
if item.dataType
|
||||
if !binds[item.dataType]
|
||||
callback = item.callback || @edit
|
||||
@el.on( 'click', "[data-type=#{item.dataType}]", callback )
|
||||
binds[item.dataType] = true
|
||||
|
||||
edit: (e) =>
|
||||
edit: (id, e) =>
|
||||
e.preventDefault()
|
||||
item = $(e.target).item( App[ @genericObject ] )
|
||||
item = App[ @genericObject ].find(id)
|
||||
|
||||
if @editCallback
|
||||
@editCallback(item)
|
||||
|
|
|
@ -4,48 +4,64 @@ class App.ControllerTable extends App.Controller
|
|||
@[key] = value
|
||||
|
||||
@table = @tableGen(params)
|
||||
|
||||
if @el
|
||||
@el.append( @table )
|
||||
|
||||
###
|
||||
|
||||
# table simple based on model
|
||||
|
||||
rowClick = -> (id, e)
|
||||
e.preventDefault()
|
||||
console.log('rowClick', id)
|
||||
rowMouseover = -> (id, e)
|
||||
e.preventDefault()
|
||||
console.log('rowMouseover', id)
|
||||
rowMouseout = -> (id, e)
|
||||
e.preventDefault()
|
||||
console.log('rowMouseout', id)
|
||||
rowDblClick = -> (id, e)
|
||||
e.preventDefault()
|
||||
console.log('rowDblClick', id)
|
||||
|
||||
colClick = -> (id, e)
|
||||
e.preventDefault()
|
||||
console.log('colClick', e.target)
|
||||
|
||||
checkboxClick = -> (id, e)
|
||||
e.preventDefault()
|
||||
console.log('checkboxClick', e.target)
|
||||
|
||||
new App.ControllerTable(
|
||||
header: ['Host', 'User', 'Adapter', 'Active']
|
||||
overview: ['host', 'user', 'adapter', 'active']
|
||||
model: App.Channel
|
||||
objects: data
|
||||
groupBy: 'group'
|
||||
checkbox: false
|
||||
radio: false
|
||||
bindRow:
|
||||
events:
|
||||
'click': rowClick
|
||||
'mouseover': rowMouseover
|
||||
'mouseout': rowMouseout
|
||||
'dblclick': rowDblClick
|
||||
bindCol:
|
||||
host:
|
||||
events:
|
||||
'click': colEvent
|
||||
bindCheckbox:
|
||||
events:
|
||||
'click': rowClick
|
||||
'mouseover': rowMouseover
|
||||
'mouseout': rowMouseout
|
||||
'dblclick': rowDblClick
|
||||
)
|
||||
|
||||
new App.ControllerTable(
|
||||
overview_extended: [
|
||||
{ name: 'number', link: true }
|
||||
{ name: 'title', link: true }
|
||||
{ name: 'customer', class: 'user-popover', data: { id: true } }
|
||||
{ name: 'state', translate: true }
|
||||
{ name: 'priority', translate: true }
|
||||
{ name: 'group' },
|
||||
{ name: 'owner', class: 'user-popover', data: { id: true } }
|
||||
{ name: 'created_at', callback: @frontendTime }
|
||||
{ name: 'last_contact', callback: @frontendTime }
|
||||
{ name: 'last_contact_agent', callback: @frontendTime }
|
||||
{ name: 'last_contact_customer', callback: @frontendTime }
|
||||
{ name: 'first_response', callback: @frontendTime }
|
||||
{ name: 'close_time', callback: @frontendTime }
|
||||
],
|
||||
model: App.Ticket
|
||||
objects: tickets
|
||||
checkbox: false
|
||||
radio: false
|
||||
)
|
||||
|
||||
###
|
||||
|
||||
tableGen: (data) ->
|
||||
overview = data.overview || data.model.configure_overview || []
|
||||
attributes = data.attributes || data.model.configure_attributes || {}
|
||||
header = data.header
|
||||
destroy = data.model.configure_delete
|
||||
|
||||
# check if table is empty
|
||||
|
@ -53,108 +69,132 @@ class App.ControllerTable extends App.Controller
|
|||
table = '<p>-' + App.i18n.translateContent( 'none' ) + '-</p>'
|
||||
return $(table)
|
||||
|
||||
# define table header
|
||||
if header
|
||||
header_new = []
|
||||
for key in header
|
||||
header_new.push {
|
||||
display: key
|
||||
}
|
||||
header = header_new
|
||||
else if !data.overview_extended
|
||||
header = []
|
||||
for row in overview
|
||||
found = false
|
||||
if attributes
|
||||
for attribute in attributes
|
||||
if row is attribute.name
|
||||
found = true
|
||||
header.push attribute
|
||||
else
|
||||
rowWithoutId = row + '_id'
|
||||
if rowWithoutId is attribute.name
|
||||
found = true
|
||||
header.push attribute
|
||||
if !found
|
||||
header.push {
|
||||
name: row
|
||||
display: row
|
||||
}
|
||||
# group by
|
||||
if data.groupBy
|
||||
|
||||
# collect data of col. types
|
||||
dataTypesForCols = []
|
||||
for row in overview
|
||||
# remove group by attribute from header
|
||||
overview = _.filter(
|
||||
overview
|
||||
(item) =>
|
||||
return item if item isnt data.groupBy
|
||||
return
|
||||
)
|
||||
|
||||
if !_.isEmpty(attributes)
|
||||
for attribute in attributes
|
||||
found = false
|
||||
if row is attribute.name
|
||||
found = true
|
||||
dataTypesAttribute = _.clone(attribute)
|
||||
else if row + '_id' is attribute.name
|
||||
found = true
|
||||
dataTypesAttribute = _.clone(attribute)
|
||||
dataTypesAttribute['name'] = row
|
||||
if found
|
||||
dataTypesAttribute['type'] = 'link'
|
||||
if !dataTypesAttribute['dataType']
|
||||
dataTypesAttribute['dataType'] = 'edit'
|
||||
dataTypesForCols.push dataTypesAttribute
|
||||
else
|
||||
dataTypesForCols.push {
|
||||
name: row
|
||||
type: 'link'
|
||||
dataType: 'edit'
|
||||
}
|
||||
# get new order
|
||||
groupObjects = _.groupBy(
|
||||
data.objects
|
||||
(item) =>
|
||||
return '' if !item[data.groupBy]
|
||||
return item[data.groupBy].displayName() if item[data.groupBy].displayName
|
||||
item[data.groupBy]
|
||||
)
|
||||
groupOrder = []
|
||||
for group, value of groupObjects
|
||||
groupOrder.push group
|
||||
|
||||
# extended table format
|
||||
if data.overview_extended
|
||||
if !header
|
||||
header = []
|
||||
for row in data.overview_extended
|
||||
for attribute in attributes
|
||||
if row.name is attribute.name
|
||||
header.push attribute
|
||||
else
|
||||
rowWithoutId = row.name + '_id'
|
||||
if rowWithoutId is attribute.name
|
||||
header.push attribute
|
||||
# sort new groups
|
||||
groupOrder = _.sortBy(
|
||||
groupOrder
|
||||
(item) =>
|
||||
item
|
||||
)
|
||||
|
||||
dataTypesForCols = data.overview_extended
|
||||
# create new data array
|
||||
data.objects = []
|
||||
for group in groupOrder
|
||||
data.objects = data.objects.concat groupObjects[group]
|
||||
groupObjects[group] = [] # release old array
|
||||
|
||||
# generate content data
|
||||
for object in data.objects
|
||||
# get header data
|
||||
header = []
|
||||
for item in overview
|
||||
headerFound = false
|
||||
for attribute in attributes
|
||||
if attribute.name is item
|
||||
headerFound = true
|
||||
header.push attribute
|
||||
else
|
||||
rowWithoutId = item + '_id'
|
||||
if attribute.name is rowWithoutId
|
||||
headerFound = true
|
||||
header.push attribute
|
||||
|
||||
# check if info for each col. is already there
|
||||
for row in dataTypesForCols
|
||||
|
||||
# lookup relation
|
||||
if !object[row.name]
|
||||
rowWithoutId = row.name + '_id'
|
||||
for attribute in attributes
|
||||
if rowWithoutId is attribute.name
|
||||
if attribute.relation && App[ attribute.relation ]
|
||||
if App[ attribute.relation ].exists( object[rowWithoutId] )
|
||||
record = App[ attribute.relation ].find( object[rowWithoutId] )
|
||||
object[row.name] = record.name
|
||||
|
||||
@log 'debug', 'table', 'header', header, 'overview', dataTypesForCols, 'objects', data.objects
|
||||
# get content
|
||||
@log 'debug', 'table', 'header', header, 'overview', 'objects', data.objects
|
||||
table = App.view('generic/table')(
|
||||
header: header
|
||||
overview: dataTypesForCols
|
||||
objects: data.objects
|
||||
checkbox: data.checkbox
|
||||
radio: data.radio
|
||||
groupBy: data.groupBy
|
||||
destroy: destroy
|
||||
callbacks: data.callbackAttributes
|
||||
)
|
||||
|
||||
# convert to jquery object
|
||||
table = $(table)
|
||||
|
||||
cursorMap =
|
||||
click: 'pointer'
|
||||
dblclick: 'pointer'
|
||||
#mouseover: 'alias'
|
||||
|
||||
# bind col.
|
||||
if data.bindCol
|
||||
for name, item of data.bindCol
|
||||
if item.events
|
||||
position = 0
|
||||
if data.checkbox
|
||||
position += 1
|
||||
hit = false
|
||||
|
||||
for headerName in header
|
||||
if !hit
|
||||
position += 1
|
||||
if headerName.name is name || headerName.name is "#{name}_id"
|
||||
hit = true
|
||||
|
||||
if hit
|
||||
for event, callback of item.events
|
||||
do (table, event, callback) =>
|
||||
if cursorMap[event]
|
||||
table.find("tbody > tr > td:nth-child(#{position}) > span").css( 'cursor', cursorMap[event] )
|
||||
table.on( event, "tbody > tr > td:nth-child(#{position}) > span",
|
||||
(e) =>
|
||||
e.stopPropagation()
|
||||
id = $(e.target).parents('tr').data('id')
|
||||
callback(id, e)
|
||||
)
|
||||
|
||||
# bind row
|
||||
if data.bindRow
|
||||
if data.bindRow.events
|
||||
for event, callback of data.bindRow.events
|
||||
do (table, event, callback) =>
|
||||
if cursorMap[event]
|
||||
table.find('tbody > tr').css( 'cursor', cursorMap[event] )
|
||||
table.on( event, 'tbody > tr',
|
||||
(e) =>
|
||||
id = $(e.target).parents('tr').data('id')
|
||||
callback(id, e)
|
||||
)
|
||||
|
||||
# bind bindCheckbox
|
||||
if data.bindCheckbox
|
||||
if data.bindCheckbox.events
|
||||
for event, callback of data.bindCheckbox.events
|
||||
do (table, event, callback) =>
|
||||
table.delegate('input[name="bulk"]', event, (e) ->
|
||||
e.stopPropagation()
|
||||
id = $(e.target).parents('tr').data('id')
|
||||
checked = $(e.target).prop('checked')
|
||||
callback(id, checked, e)
|
||||
)
|
||||
|
||||
# bind on delete dialog
|
||||
if data.model && destroy
|
||||
table.delegate('[data-type="destroy"]', 'click', (e) ->
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
itemId = $(e.target).parents('tr').data('id')
|
||||
item = data.model.find(itemId)
|
||||
|
@ -165,11 +205,24 @@ class App.ControllerTable extends App.Controller
|
|||
|
||||
# enable checkbox bulk selection
|
||||
if data.checkbox
|
||||
table.delegate('[name="bulk_all"]', 'click', (e) ->
|
||||
table.delegate('input[name="bulk_all"]', 'click', (e) ->
|
||||
e.stopPropagation()
|
||||
if $(e.target).prop('checked')
|
||||
$(e.target).parents().find('[name="bulk"]').prop( 'checked', true );
|
||||
$(e.target).parents('table').find('[name="bulk"]').each( ->
|
||||
if !$(@).prop('checked')
|
||||
#$(@).prop('checked', true)
|
||||
$(@).trigger('click')
|
||||
)
|
||||
else
|
||||
$(e.target).parents().find('[name="bulk"]').prop( 'checked', false );
|
||||
$(e.target).parents('table').find('[name="bulk"]').each( ->
|
||||
if $(@).prop('checked')
|
||||
#$(@).prop('checked', false)
|
||||
$(@).trigger('click')
|
||||
)
|
||||
)
|
||||
|
||||
return table
|
||||
time = =>
|
||||
@frontendTimeUpdate()
|
||||
@delay(time, 80)
|
||||
|
||||
table
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
$.fn.item = (genericObject) ->
|
||||
elementID = $(@).data('id')
|
||||
elementID or= $(@).parents('[data-id]').data('id')
|
||||
genericObject.find(elementID)
|
||||
|
||||
class App.ChannelEmail extends App.ControllerTabs
|
||||
constructor: ->
|
||||
super
|
||||
|
@ -46,7 +41,6 @@ class App.ChannelEmail extends App.ControllerTabs
|
|||
class App.ChannelEmailFilter extends App.Controller
|
||||
events:
|
||||
'click [data-type=new]': 'new'
|
||||
'click [data-type=edit]': 'edit'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
@ -59,9 +53,12 @@ class App.ChannelEmailFilter extends App.Controller
|
|||
template = $( '<div><div class="overview"></div><a data-type="new" class="btn btn-default">' + App.i18n.translateContent('New') + '</a></div>' )
|
||||
|
||||
new App.ControllerTable(
|
||||
el: template.find('.overview'),
|
||||
model: App.PostmasterFilter,
|
||||
objects: data,
|
||||
el: template.find('.overview')
|
||||
model: App.PostmasterFilter
|
||||
objects: data
|
||||
bindRow:
|
||||
events:
|
||||
'click': @edit
|
||||
)
|
||||
@html template
|
||||
|
||||
|
@ -69,10 +66,9 @@ class App.ChannelEmailFilter extends App.Controller
|
|||
e.preventDefault()
|
||||
new App.ChannelEmailFilterEdit( {} )
|
||||
|
||||
edit: (e) =>
|
||||
edit: (id, e) =>
|
||||
e.preventDefault()
|
||||
item = $(e.target).item( App.PostmasterFilter )
|
||||
new App.ChannelEmailFilterEdit( object: item )
|
||||
new App.ChannelEmailFilterEdit( object: App.PostmasterFilter.find(id) )
|
||||
|
||||
class App.ChannelEmailFilterEdit extends App.ControllerModal
|
||||
constructor: ->
|
||||
|
@ -135,7 +131,6 @@ class App.ChannelEmailFilterEdit extends App.ControllerModal
|
|||
class App.ChannelEmailAddress extends App.Controller
|
||||
events:
|
||||
'click [data-type=new]': 'new'
|
||||
'click [data-type=edit]': 'edit'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
@ -151,6 +146,9 @@ class App.ChannelEmailAddress extends App.Controller
|
|||
el: template.find('.overview')
|
||||
model: App.EmailAddress
|
||||
objects: data
|
||||
bindRow:
|
||||
events:
|
||||
'click': @edit
|
||||
)
|
||||
|
||||
@html template
|
||||
|
@ -159,9 +157,9 @@ class App.ChannelEmailAddress extends App.Controller
|
|||
e.preventDefault()
|
||||
new App.ChannelEmailAddressEdit( {} )
|
||||
|
||||
edit: (e) =>
|
||||
edit: (id, e) =>
|
||||
e.preventDefault()
|
||||
item = $(e.target).item( App.EmailAddress )
|
||||
item = App.EmailAddress.find(id)
|
||||
new App.ChannelEmailAddressEdit( object: item )
|
||||
|
||||
class App.ChannelEmailAddressEdit extends App.ControllerModal
|
||||
|
@ -223,7 +221,6 @@ class App.ChannelEmailAddressEdit extends App.ControllerModal
|
|||
class App.ChannelEmailSignature extends App.Controller
|
||||
events:
|
||||
'click [data-type=new]': 'new'
|
||||
'click [data-type=edit]': 'edit'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
@ -238,17 +235,19 @@ class App.ChannelEmailSignature extends App.Controller
|
|||
el: template.find('.overview')
|
||||
model: App.Signature
|
||||
objects: data
|
||||
bindRow:
|
||||
events:
|
||||
'click': @edit
|
||||
)
|
||||
|
||||
@html template
|
||||
|
||||
new: (e) =>
|
||||
e.preventDefault()
|
||||
new App.ChannelEmailSignatureEdit( {} )
|
||||
|
||||
edit: (e) =>
|
||||
edit: (id, e) =>
|
||||
e.preventDefault()
|
||||
item = $(e.target).item( App.Signature )
|
||||
item = App.Signature.find(id)
|
||||
new App.ChannelEmailSignatureEdit( object: item )
|
||||
|
||||
class App.ChannelEmailSignatureEdit extends App.ControllerModal
|
||||
|
@ -310,31 +309,23 @@ class App.ChannelEmailSignatureEdit extends App.ControllerModal
|
|||
class App.ChannelEmailInbound extends App.Controller
|
||||
events:
|
||||
'click [data-type=new]': 'new'
|
||||
'click [data-type=edit]': 'edit'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
App.Channel.subscribe( @render, initFetch: true )
|
||||
|
||||
render: =>
|
||||
channels = App.Channel.all()
|
||||
|
||||
data = []
|
||||
for channel in channels
|
||||
if channel.area is 'Email::Inbound'
|
||||
channel.host = channel.options['host']
|
||||
channel.user = channel.options['user']
|
||||
data.push channel
|
||||
channels = App.Channel.search( filter: { area: 'Email::Inbound' } )
|
||||
|
||||
template = $( '<div><div class="overview"></div><a data-type="new" class="btn btn-default">' + App.i18n.translateContent('New') + '</a></div>' )
|
||||
|
||||
new App.ControllerTable(
|
||||
el: template.find('.overview'),
|
||||
header: ['Host', 'User', 'Adapter', 'Active'],
|
||||
overview: ['host', 'user', 'adapter', 'active'],
|
||||
model: App.Channel,
|
||||
objects: data,
|
||||
el: template.find('.overview')
|
||||
model: App.Channel
|
||||
objects: channels
|
||||
bindRow:
|
||||
events:
|
||||
'click': @edit
|
||||
)
|
||||
@html template
|
||||
|
||||
|
@ -342,9 +333,9 @@ class App.ChannelEmailInbound extends App.Controller
|
|||
e.preventDefault()
|
||||
new App.ChannelEmailInboundEdit( {} )
|
||||
|
||||
edit: (e) =>
|
||||
edit: (id, e) =>
|
||||
e.preventDefault()
|
||||
item = $(e.target).item( App.Channel )
|
||||
item = App.Channel.find(id)
|
||||
new App.ChannelEmailInboundEdit( object: item )
|
||||
|
||||
|
||||
|
@ -354,29 +345,13 @@ class App.ChannelEmailInboundEdit extends App.ControllerModal
|
|||
@render(@object)
|
||||
|
||||
render: (data = {}) ->
|
||||
|
||||
if !data['options']
|
||||
data['options'] = {}
|
||||
data['options']['ssl'] = true
|
||||
data['active'] = true
|
||||
|
||||
configure_attributes = [
|
||||
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: { IMAP: 'IMAP', POP3: 'POP3' } , class: 'span4', default: data['adapter'] },
|
||||
{ name: 'host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, class: 'span4', autocapitalize: false, default: (data['options']&&data['options']['host']) },
|
||||
{ name: 'user', display: 'User', tag: 'input', type: 'text', limit: 120, null: false, class: 'span4', autocapitalize: false, default: (data['options']&&data['options']['user']) },
|
||||
{ name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, class: 'span4', autocapitalize: false, default: (data['options']&&data['options']['password']) },
|
||||
{ name: 'ssl', display: 'SSL', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' }, translate: true, class: 'span4', default: (data['options']&&data['options']['ssl']) },
|
||||
{ name: 'folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, class: 'span4', autocapitalize: false, default: (data['options']&&data['options']['folder']) },
|
||||
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: false, filter: @edit_form, nulloption: false, relation: 'Group', class: 'span4', default: data['group_id'] },
|
||||
{ name: 'active', display: 'Active', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' } , translate: true, class: 'span4', default: data['active'] },
|
||||
]
|
||||
if @object
|
||||
@html App.view('generic/admin/edit')(
|
||||
head: 'Email Channel'
|
||||
)
|
||||
@form = new App.ControllerForm(
|
||||
el: @el.find('#object_edit')
|
||||
model: { configure_attributes: configure_attributes, className: '' }
|
||||
model: App.Channel
|
||||
autofocus: true
|
||||
)
|
||||
else
|
||||
|
@ -385,7 +360,7 @@ class App.ChannelEmailInboundEdit extends App.ControllerModal
|
|||
)
|
||||
@form = new App.ControllerForm(
|
||||
el: @el.find('#object_new')
|
||||
model: { configure_attributes: configure_attributes, className: '' }
|
||||
model: App.Channel
|
||||
autofocus: true
|
||||
)
|
||||
@modalShow()
|
||||
|
@ -395,21 +370,10 @@ class App.ChannelEmailInboundEdit extends App.ControllerModal
|
|||
|
||||
# get params
|
||||
params = @formParam(e.target)
|
||||
params['area'] = 'Email::Inbound'
|
||||
|
||||
object = @object || new App.Channel
|
||||
object.load(
|
||||
area: 'Email::Inbound'
|
||||
adapter: params['adapter']
|
||||
group_id: params['group_id']
|
||||
options: {
|
||||
host: params['host']
|
||||
user: params['user']
|
||||
password: params['password']
|
||||
ssl: params['ssl']
|
||||
folder: params['folder']
|
||||
},
|
||||
active: params['active']
|
||||
)
|
||||
object.load(params)
|
||||
|
||||
# validate form
|
||||
errors = @form.validate( params )
|
||||
|
@ -463,7 +427,7 @@ class App.ChannelEmailOutbound extends App.Controller
|
|||
channel_used = channel
|
||||
|
||||
configure_attributes = [
|
||||
{ name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters , class: 'span4', default: adapter_used },
|
||||
{ name: 'adapter', display: 'Send Mails via', tag: 'select', multiple: false, null: false, options: adapters , default: adapter_used },
|
||||
]
|
||||
new App.ControllerForm(
|
||||
el: @el.find('#form-email-adapter'),
|
||||
|
@ -476,10 +440,10 @@ class App.ChannelEmailOutbound extends App.Controller
|
|||
|
||||
if adapter_used is 'SMTP'
|
||||
configure_attributes = [
|
||||
{ name: 'host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, class: 'span4', autocapitalize: false, default: (channel_used['options']&&channel_used['options']['host']) },
|
||||
{ name: 'user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, class: 'span4', autocapitalize: false, default: (channel_used['options']&&channel_used['options']['user']) },
|
||||
{ name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, class: 'span4', autocapitalize: false, default: (channel_used['options']&&channel_used['options']['password']) },
|
||||
{ name: 'ssl', display: 'SSL', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' } , class: 'span4', translate: true, default: (channel_used['options']&&channel_used['options']['ssl']) },
|
||||
{ name: 'host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['host']) },
|
||||
{ name: 'user', display: 'User', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['user']) },
|
||||
{ name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: true, autocapitalize: false, default: (channel_used['options']&&channel_used['options']['password']) },
|
||||
{ name: 'ssl', display: 'SSL', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' } , translate: true, default: (channel_used['options']&&channel_used['options']['ssl']) },
|
||||
{ name: 'port', display: 'Port', tag: 'input', type: 'text', limit: 5, null: false, class: 'span1', autocapitalize: false, default: ((channel_used['options']&&channel_used['options']['port']) || 25) },
|
||||
]
|
||||
@form = new App.ControllerForm(
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
class App.DashboardTicket extends App.Controller
|
||||
events:
|
||||
'click [data-type=edit]': 'zoom'
|
||||
'click [data-type=settings]': 'settings'
|
||||
'click [data-type=page]': 'page'
|
||||
|
||||
|
@ -18,11 +17,11 @@ class App.DashboardTicket extends App.Controller
|
|||
# render
|
||||
@fetch()
|
||||
|
||||
fetch: =>
|
||||
fetch: (force) =>
|
||||
|
||||
# use cache of first page
|
||||
cache = App.Store.get( @key )
|
||||
if cache
|
||||
if !force && cache
|
||||
@load( cache )
|
||||
|
||||
# init fetch via ajax, all other updates on time via websockets
|
||||
|
@ -55,12 +54,13 @@ class App.DashboardTicket extends App.Controller
|
|||
App.Overview.unbind('local:rerender')
|
||||
App.Overview.bind 'local:rerender', (record) =>
|
||||
@log 'notice', 'rerender...', record
|
||||
data.overview = record
|
||||
@render(data)
|
||||
|
||||
App.Overview.unbind('local:refetch')
|
||||
App.Overview.bind 'local:refetch', (record) =>
|
||||
@log 'notice', 'refetch...', record
|
||||
@fetch()
|
||||
@fetch(true)
|
||||
|
||||
@render( data )
|
||||
|
||||
|
@ -72,8 +72,8 @@ class App.DashboardTicket extends App.Controller
|
|||
@overview = data.overview
|
||||
@tickets_count = data.tickets_count
|
||||
@ticket_ids = data.ticket_ids
|
||||
# FIXME 10
|
||||
pages_total = parseInt( ( @tickets_count / 10 ) + 0.99999 ) || 1
|
||||
per_page = @overview.view.per_page || 10
|
||||
pages_total = parseInt( ( @tickets_count / per_page ) + 0.99999 ) || 1
|
||||
html = App.view('dashboard/ticket')(
|
||||
overview: @overview,
|
||||
pages_total: pages_total,
|
||||
|
@ -92,13 +92,39 @@ class App.DashboardTicket extends App.Controller
|
|||
if @ticket_ids[ i - 1 ]
|
||||
@tickets_in_table.push App.Ticket.retrieve( @ticket_ids[ i - 1 ] )
|
||||
|
||||
shown_all_attributes = @ticketTableAttributes( App.Overview.find(@overview.id).view.d )
|
||||
openTicket = (id,e) =>
|
||||
ticket = App.Ticket.retrieve(id)
|
||||
@navigate ticket.uiUrl()
|
||||
callbackTicketTitleAdd = (value, object, attribute, attributes, refObject) =>
|
||||
attribute.title = object.title
|
||||
callbackLinkToTicket = (value, object, attribute, attributes, refObject) =>
|
||||
attribute.link = object.uiUrl()
|
||||
callbackResetLink = (value, object, attribute, attributes, refObject) =>
|
||||
attribute.link = undefined
|
||||
callbackUserPopover = (value, object, attribute, attributes, refObject) =>
|
||||
attribute.class = 'user-popover'
|
||||
attribute.data =
|
||||
id: refObject.id
|
||||
|
||||
new App.ControllerTable(
|
||||
overview: @overview.view.d
|
||||
el: html.find('.table-overview'),
|
||||
overview_extended: shown_all_attributes,
|
||||
model: App.Ticket,
|
||||
model: App.Ticket
|
||||
objects: @tickets_in_table,
|
||||
checkbox: false,
|
||||
checkbox: false
|
||||
groupBy: @overview.group_by
|
||||
bindRow:
|
||||
events:
|
||||
'click': openTicket
|
||||
callbackAttributes:
|
||||
customer_id:
|
||||
[ callbackResetLink, callbackUserPopover ]
|
||||
owner_id:
|
||||
[ callbackResetLink, callbackUserPopover ]
|
||||
title:
|
||||
[ callbackLinkToTicket, callbackTicketTitleAdd ]
|
||||
number:
|
||||
[ callbackLinkToTicket, callbackTicketTitleAdd ]
|
||||
)
|
||||
|
||||
@html html
|
||||
|
@ -122,8 +148,9 @@ class App.DashboardTicket extends App.Controller
|
|||
|
||||
settings: (e) =>
|
||||
e.preventDefault()
|
||||
new Settings(
|
||||
overview: App.Overview.find(@overview.id)
|
||||
new App.OverviewSettings(
|
||||
overview_id: @overview.id
|
||||
view_mode: 'd'
|
||||
)
|
||||
|
||||
page: (e) =>
|
||||
|
@ -132,139 +159,3 @@ class App.DashboardTicket extends App.Controller
|
|||
@start_page = id
|
||||
@fetch()
|
||||
|
||||
class Settings extends App.ControllerModal
|
||||
constructor: ->
|
||||
super
|
||||
@render()
|
||||
|
||||
render: ->
|
||||
|
||||
@html App.view('dashboard/ticket_settings')(
|
||||
overview: @overview,
|
||||
)
|
||||
@configure_attributes_article = [
|
||||
# { name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', },
|
||||
# { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
|
||||
# { name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', default: '9', class: 'medium', item_class: 'pull-left' },
|
||||
# { name: 'internal', display: 'Visibility', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: 'pull-left' },
|
||||
{
|
||||
name: 'per_page',
|
||||
display: 'Items per page',
|
||||
tag: 'select',
|
||||
multiple: false,
|
||||
null: false,
|
||||
# default: @overview.view.d.per_page,
|
||||
options: {
|
||||
5: 5,
|
||||
10: 10,
|
||||
15: 15,
|
||||
20: 20,
|
||||
},
|
||||
class: 'medium',
|
||||
# item_class: 'pull-left',
|
||||
},
|
||||
{
|
||||
name: 'attributes',
|
||||
display: 'Attributes',
|
||||
tag: 'checkbox',
|
||||
default: @overview.view.d,
|
||||
null: false,
|
||||
translate: true
|
||||
options: {
|
||||
number: 'Number'
|
||||
title: 'Title'
|
||||
customer: 'Customer'
|
||||
state: 'State'
|
||||
priority: 'Priority'
|
||||
group: 'Group'
|
||||
owner: 'Owner'
|
||||
created_at: 'Age'
|
||||
last_contact: 'Last Contact'
|
||||
last_contact_agent: 'Last Contact Agent'
|
||||
last_contact_customer: 'Last Contact Customer'
|
||||
first_response: 'First Response'
|
||||
close_time: 'Close Time'
|
||||
escalation_time: 'Escalation in'
|
||||
article_count: 'Article Count'
|
||||
},
|
||||
class: 'medium',
|
||||
# item_class: 'pull-left',
|
||||
},
|
||||
{
|
||||
name: 'order_by',
|
||||
display: 'Order',
|
||||
tag: 'select',
|
||||
default: @overview.order.by,
|
||||
null: false,
|
||||
translate: true
|
||||
options: {
|
||||
number: 'Number'
|
||||
title: 'Title'
|
||||
customer: 'Customer'
|
||||
state: 'State'
|
||||
priority: 'Priority'
|
||||
group: 'Group'
|
||||
owner: 'Owner'
|
||||
created_at: 'Age'
|
||||
last_contact: 'Last Contact'
|
||||
last_contact_agent: 'Last Contact Agent'
|
||||
last_contact_customer: 'Last Contact Customer'
|
||||
first_response: 'First Response'
|
||||
close_time: 'Close Time'
|
||||
escalation_time: 'Escalation in'
|
||||
article_count: 'Article Count'
|
||||
},
|
||||
class: 'medium',
|
||||
},
|
||||
{
|
||||
name: 'order_by_direction',
|
||||
display: 'Direction',
|
||||
tag: 'select',
|
||||
default: @overview.order.direction,
|
||||
null: false,
|
||||
translate: true
|
||||
options: {
|
||||
ASC: 'up',
|
||||
DESC: 'down',
|
||||
},
|
||||
class: 'medium',
|
||||
},
|
||||
]
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @el.find('#form-setting'),
|
||||
model: { configure_attributes: @configure_attributes_article },
|
||||
autofocus: false,
|
||||
)
|
||||
|
||||
@modalShow()
|
||||
|
||||
submit: (e) =>
|
||||
e.preventDefault()
|
||||
params = @formParam(e.target)
|
||||
|
||||
# check if refetch is needed
|
||||
@reload_needed = 0
|
||||
if @overview.view['d']['per_page'] isnt params['per_page']
|
||||
@overview.view['d']['per_page'] = params['per_page']
|
||||
@reload_needed = 1
|
||||
|
||||
if @overview.order['by'] isnt params['order_by']
|
||||
@overview.order['by'] = params['order_by']
|
||||
@reload_needed = 1
|
||||
|
||||
if @overview.order['direction'] isnt params['order_by_direction']
|
||||
@overview.order['direction'] = params['order_by_direction']
|
||||
@reload_needed = 1
|
||||
|
||||
@overview.view['d'] = params['attributes']
|
||||
|
||||
@overview.save(
|
||||
done: =>
|
||||
if @reload_needed
|
||||
@overview.trigger('local:refetch')
|
||||
else
|
||||
@overview.trigger('local:rerender')
|
||||
)
|
||||
|
||||
@modalHide()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
class Index extends App.Controller
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
@render()
|
||||
|
||||
render: ->
|
||||
|
@ -55,11 +54,11 @@ class Table extends App.ControllerContent
|
|||
# render
|
||||
@fetch()
|
||||
|
||||
fetch: =>
|
||||
fetch: (force) =>
|
||||
|
||||
# use cache of first page
|
||||
cache = App.Store.get( @key )
|
||||
if cache
|
||||
if !force && cache
|
||||
@load(cache)
|
||||
|
||||
# init fetch via ajax, all other updates on time via websockets
|
||||
|
@ -106,7 +105,7 @@ class Table extends App.ControllerContent
|
|||
App.Overview.unbind('local:refetch')
|
||||
App.Overview.bind 'local:refetch', (record) =>
|
||||
@log 'notice', 'refetch...', record
|
||||
@fetch()
|
||||
@fetch(true)
|
||||
|
||||
@ticket_list_show = []
|
||||
for ticket_id in @ticket_ids
|
||||
|
@ -123,6 +122,8 @@ class Table extends App.ControllerContent
|
|||
|
||||
render: ->
|
||||
|
||||
|
||||
|
||||
# if customer and no ticket exists, show the following message only
|
||||
if !@ticket_list_show[0] && @isRole('Customer')
|
||||
@html App.view('customer_not_ticket_exists')()
|
||||
|
@ -131,6 +132,7 @@ class Table extends App.ControllerContent
|
|||
@selected = @bulkGetSelected()
|
||||
|
||||
# set page title
|
||||
@overview = App.Overview.find( @overview.id )
|
||||
@title @overview.name
|
||||
|
||||
# render init page
|
||||
|
@ -182,27 +184,51 @@ class Table extends App.ControllerContent
|
|||
)
|
||||
@el.find('.table-overview').append(table)
|
||||
else
|
||||
shown_all_attributes = @ticketTableAttributes( App.Overview.find( @overview.id ).view.s )
|
||||
groupBy = undefined
|
||||
if @overview.group_by
|
||||
group_by =
|
||||
name: @overview.group_by
|
||||
id: @overview.group_by + '_id'
|
||||
openTicket = (id,e) =>
|
||||
ticket = App.Ticket.retrieve(id)
|
||||
@navigate ticket.uiUrl()
|
||||
callbackTicketTitleAdd = (value, object, attribute, attributes, refObject) =>
|
||||
attribute.title = object.title
|
||||
callbackLinkToTicket = (value, object, attribute, attributes, refObject) =>
|
||||
attribute.link = object.uiUrl()
|
||||
callbackResetLink = (value, object, attribute, attributes, refObject) =>
|
||||
attribute.link = undefined
|
||||
callbackUserPopover = (value, object, attribute, attributes, refObject) =>
|
||||
attribute.class = 'user-popover'
|
||||
attribute.data =
|
||||
id: refObject.id
|
||||
callbackCheckbox = (id, checked, e) =>
|
||||
if @el.find('table').find('input[name="bulk"]:checked').length == 0
|
||||
@el.find('.bulk-action').addClass('hide')
|
||||
else
|
||||
@el.find('.bulk-action').removeClass('hide')
|
||||
|
||||
# remove group by attribute from show attributes list
|
||||
shown_all_attributes = _.filter(
|
||||
shown_all_attributes
|
||||
(item) =>
|
||||
return item if item.name isnt @overview.group_by
|
||||
return
|
||||
)
|
||||
new App.ControllerTable(
|
||||
el: @el.find('.table-overview')
|
||||
overview_extended: shown_all_attributes
|
||||
model: App.Ticket
|
||||
objects: @ticket_list_show
|
||||
checkbox: checkbox
|
||||
groupBy: group_by
|
||||
overview: @overview.view.s
|
||||
el: @el.find('.table-overview')
|
||||
model: App.Ticket
|
||||
objects: @ticket_list_show
|
||||
checkbox: checkbox
|
||||
groupBy: @overview.group_by
|
||||
bindRow:
|
||||
events:
|
||||
'click': openTicket
|
||||
#bindCol:
|
||||
# customer_id:
|
||||
# events:
|
||||
# 'mouseover': popOver
|
||||
callbackAttributes:
|
||||
customer_id:
|
||||
[ callbackResetLink, callbackUserPopover ]
|
||||
owner_id:
|
||||
[ callbackResetLink, callbackUserPopover ]
|
||||
title:
|
||||
[ callbackLinkToTicket, callbackTicketTitleAdd ]
|
||||
number:
|
||||
[ callbackLinkToTicket, callbackTicketTitleAdd ]
|
||||
bindCheckbox:
|
||||
events:
|
||||
'click': callbackCheckbox
|
||||
)
|
||||
|
||||
@bulkSetSelected( @selected )
|
||||
|
@ -215,12 +241,13 @@ class Table extends App.ControllerContent
|
|||
|
||||
# start bulk action observ
|
||||
@el.find('.bulk-action').append( @bulk_form() )
|
||||
if @el.find('.table-overview').find('[name="bulk"]:checked').length isnt 0
|
||||
if @el.find('.table-overview').find('input[name="bulk"]:checked').length isnt 0
|
||||
@el.find('.bulk-action').removeClass('hide')
|
||||
|
||||
# show/hide bulk action
|
||||
@el.find('.table-overview').delegate('[name="bulk"], [name="bulk_all"]', 'click', (e) =>
|
||||
if @el.find('.table-overview').find('[name="bulk"]:checked').length == 0
|
||||
@el.find('.table-overview').delegate('input[name="bulk"], input[name="bulk_all"]', 'click', (e) =>
|
||||
console.log('YES')
|
||||
if @el.find('.table-overview').find('input[name="bulk"]:checked').length == 0
|
||||
|
||||
# hide
|
||||
@el.find('.bulk-action').addClass('hide')
|
||||
|
@ -324,6 +351,7 @@ class Table extends App.ControllerContent
|
|||
@fetch()
|
||||
)
|
||||
)
|
||||
@el.find('.table-overview').find('[name="bulk"]:checked').prop('checked', false)
|
||||
App.Event.trigger 'notify', {
|
||||
type: 'success'
|
||||
msg: App.i18n.translateContent('Bulk-Action executed!')
|
||||
|
@ -343,14 +371,15 @@ class Table extends App.ControllerContent
|
|||
|
||||
settings: (e) =>
|
||||
e.preventDefault()
|
||||
new Settings(
|
||||
overview: App.Overview.find(@overview.id),
|
||||
view_mode: @view_mode,
|
||||
new App.OverviewSettings(
|
||||
overview_id: @overview.id
|
||||
view_mode: @view_mode
|
||||
)
|
||||
|
||||
class Settings extends App.ControllerModal
|
||||
class App.OverviewSettings extends App.ControllerModal
|
||||
constructor: ->
|
||||
super
|
||||
@overview = App.Overview.find(@overview_id)
|
||||
@render()
|
||||
|
||||
render: ->
|
||||
|
@ -358,119 +387,154 @@ class Settings extends App.ControllerModal
|
|||
@html App.view('dashboard/ticket_settings')(
|
||||
overview: @overview,
|
||||
)
|
||||
@configure_attributes_article = [
|
||||
# { name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', },
|
||||
# { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, class: 'span7', item_class: 'hide' },
|
||||
# { name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: true, relation: 'TicketArticleType', default: '9', class: 'medium', item_class: 'pull-left' },
|
||||
# { name: 'internal', display: 'Visibility', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium', item_class: 'pull-left' },
|
||||
{
|
||||
name: 'per_page'
|
||||
display: 'Items per page'
|
||||
tag: 'select'
|
||||
multiple: false
|
||||
null: false
|
||||
# default: @overview.view[@view_mode].per_page
|
||||
options:
|
||||
15: 15
|
||||
20: 20
|
||||
25: 25
|
||||
30: 30
|
||||
35: 35
|
||||
class: 'medium'
|
||||
# item_class: 'pull-left'
|
||||
},
|
||||
{
|
||||
name: 'attributes'
|
||||
display: 'Attributes'
|
||||
tag: 'checkbox'
|
||||
default: @overview.view[@view_mode]
|
||||
null: false
|
||||
translate: true
|
||||
options:
|
||||
# true: 'internal'
|
||||
# false: 'public'
|
||||
number: 'Number'
|
||||
title: 'Title'
|
||||
customer: 'Customer'
|
||||
state: 'State'
|
||||
priority: 'Priority'
|
||||
group: 'Group'
|
||||
owner: 'Owner'
|
||||
created_at: 'Age'
|
||||
last_contact: 'Last Contact'
|
||||
last_contact_agent: 'Last Contact Agent'
|
||||
last_contact_customer: 'Last Contact Customer'
|
||||
first_response: 'First Response'
|
||||
close_time: 'Close Time'
|
||||
escalation_time: 'Escalation in'
|
||||
article_count: 'Article Count'
|
||||
class: 'medium'
|
||||
},
|
||||
{
|
||||
name: 'order_by'
|
||||
display: 'Order'
|
||||
tag: 'select'
|
||||
default: @overview.order.by
|
||||
null: false
|
||||
translate: true
|
||||
options:
|
||||
number: 'Number'
|
||||
title: 'Title'
|
||||
customer: 'Customer'
|
||||
state: 'State'
|
||||
priority: 'Priority'
|
||||
group: 'Group'
|
||||
owner: 'Owner'
|
||||
created_at: 'Age'
|
||||
last_contact: 'Last Contact'
|
||||
last_contact_agent: 'Last Contact Agent'
|
||||
last_contact_customer: 'Last Contact Customer'
|
||||
first_response: 'First Response'
|
||||
close_time: 'Close Time'
|
||||
escalation_time: 'Escalation in'
|
||||
article_count: 'Article Count'
|
||||
class: 'medium'
|
||||
},
|
||||
{
|
||||
name: 'order_by_direction'
|
||||
display: 'Direction'
|
||||
tag: 'select'
|
||||
default: @overview.order.direction
|
||||
null: false
|
||||
translate: true
|
||||
options:
|
||||
ASC: 'up'
|
||||
DESC: 'down'
|
||||
class: 'medium'
|
||||
},
|
||||
{
|
||||
name: 'group_by'
|
||||
display: 'Group by'
|
||||
tag: 'select'
|
||||
default: @overview.group_by
|
||||
null: true
|
||||
nulloption: true
|
||||
translate: true
|
||||
options:
|
||||
customer: 'Customer'
|
||||
state: 'State'
|
||||
priority: 'Priority'
|
||||
group: 'Group'
|
||||
owner: 'Owner'
|
||||
class: 'medium'
|
||||
},
|
||||
# {
|
||||
# name: 'condition',
|
||||
# display: 'Conditions',
|
||||
# tag: 'select',
|
||||
# multiple: false,
|
||||
# null: false,
|
||||
# relation: 'TicketArticleType',
|
||||
# default: '9',
|
||||
# class: 'medium',
|
||||
# item_class: 'pull-left',
|
||||
# },
|
||||
]
|
||||
@configure_attributes_article = []
|
||||
if @view_mode is 'd'
|
||||
@configure_attributes_article.push({
|
||||
name: 'view::per_page',
|
||||
display: 'Items per page',
|
||||
tag: 'select',
|
||||
multiple: false,
|
||||
null: false,
|
||||
default: @overview.view.per_page
|
||||
options: {
|
||||
5: ' 5'
|
||||
10: '10'
|
||||
15: '15'
|
||||
20: '20'
|
||||
25: '25'
|
||||
},
|
||||
class: 'medium',
|
||||
})
|
||||
@configure_attributes_article.push({
|
||||
name: "view::#{@view_mode}"
|
||||
display: 'Attributes'
|
||||
tag: 'checkbox'
|
||||
default: @overview.view[@view_mode]
|
||||
null: false
|
||||
translate: true
|
||||
options: [
|
||||
{
|
||||
value: 'number'
|
||||
name: 'Number'
|
||||
},
|
||||
{
|
||||
value: 'title'
|
||||
name: 'Title'
|
||||
},
|
||||
{
|
||||
value: 'customer'
|
||||
name: 'Customer'
|
||||
},
|
||||
{
|
||||
value: 'organization'
|
||||
name: 'Organization'
|
||||
},
|
||||
{
|
||||
value: 'state'
|
||||
name: 'State'
|
||||
},
|
||||
{
|
||||
value: 'priority'
|
||||
name: 'Priority'
|
||||
},
|
||||
{
|
||||
value: 'group'
|
||||
name: 'Group'
|
||||
},
|
||||
{
|
||||
value: 'owner'
|
||||
name: 'Owner'
|
||||
},
|
||||
{
|
||||
value: 'created_at'
|
||||
name: 'Age'
|
||||
},
|
||||
{
|
||||
value: 'last_contact'
|
||||
name: 'Last Contact'
|
||||
},
|
||||
{
|
||||
value: 'last_contact_agent'
|
||||
name: 'Last Contact Agent'
|
||||
},
|
||||
{
|
||||
value: 'last_contact_customer'
|
||||
name: 'Last Contact Customer'
|
||||
},
|
||||
{
|
||||
value: 'first_response'
|
||||
name: 'First Response'
|
||||
},
|
||||
{
|
||||
value: 'close_time'
|
||||
name: 'Close Time'
|
||||
},
|
||||
{
|
||||
value: 'escalation_time'
|
||||
name: 'Escalation in'
|
||||
},
|
||||
{
|
||||
value: 'article_count'
|
||||
name: 'Article Count'
|
||||
},
|
||||
]
|
||||
class: 'medium'
|
||||
},
|
||||
{
|
||||
name: 'order::by'
|
||||
display: 'Order'
|
||||
tag: 'select'
|
||||
default: @overview.order.by
|
||||
null: false
|
||||
translate: true
|
||||
options:
|
||||
number: 'Number'
|
||||
title: 'Title'
|
||||
customer: 'Customer'
|
||||
organization: 'Organization'
|
||||
state: 'State'
|
||||
priority: 'Priority'
|
||||
group: 'Group'
|
||||
owner: 'Owner'
|
||||
created_at: 'Age'
|
||||
last_contact: 'Last Contact'
|
||||
last_contact_agent: 'Last Contact Agent'
|
||||
last_contact_customer: 'Last Contact Customer'
|
||||
first_response: 'First Response'
|
||||
close_time: 'Close Time'
|
||||
escalation_time: 'Escalation in'
|
||||
article_count: 'Article Count'
|
||||
class: 'medium'
|
||||
},
|
||||
{
|
||||
name: 'order::direction'
|
||||
display: 'Direction'
|
||||
tag: 'select'
|
||||
default: @overview.order.direction
|
||||
null: false
|
||||
translate: true
|
||||
options:
|
||||
ASC: 'up'
|
||||
DESC: 'down'
|
||||
class: 'medium'
|
||||
},
|
||||
{
|
||||
name: 'group_by'
|
||||
display: 'Group by'
|
||||
tag: 'select'
|
||||
default: @overview.group_by
|
||||
null: true
|
||||
nulloption: true
|
||||
translate: true
|
||||
options:
|
||||
customer: 'Customer'
|
||||
organization: 'Organization'
|
||||
state: 'State'
|
||||
priority: 'Priority'
|
||||
group: 'Group'
|
||||
owner: 'Owner'
|
||||
class: 'medium'
|
||||
})
|
||||
|
||||
new App.ControllerForm(
|
||||
el: @el.find('#form-setting')
|
||||
|
@ -486,19 +550,18 @@ class Settings extends App.ControllerModal
|
|||
|
||||
# check if refetch is needed
|
||||
@reload_needed = 0
|
||||
if @overview.order['by'] isnt params['order_by']
|
||||
@overview.order['by'] = params['order_by']
|
||||
if @overview.order.by isnt params.order.by
|
||||
@overview.order.by = params.order.by
|
||||
@reload_needed = 1
|
||||
|
||||
if @overview.order['direction'] isnt params['order_by_direction']
|
||||
@overview.order['direction'] = params['order_by_direction']
|
||||
if @overview.order.direction isnt params.order.direction
|
||||
@overview.order.direction = params.order.direction
|
||||
@reload_needed = 1
|
||||
|
||||
if @overview['group_by'] isnt params['group_by']
|
||||
@overview['group_by'] = params['group_by']
|
||||
@reload_needed = 1
|
||||
for key, value of params.view
|
||||
@overview.view[key] = value
|
||||
|
||||
@overview.view[@view_mode] = params['attributes']
|
||||
@overview.group_by = params.group_by
|
||||
|
||||
@overview.save(
|
||||
done: =>
|
||||
|
|
|
@ -144,7 +144,10 @@ class _i18nSingleton extends Spine.Module
|
|||
translate: ( string, args... ) =>
|
||||
|
||||
# return '' on undefined
|
||||
if typeof string is 'boolean'
|
||||
string = string.toString()
|
||||
return '' if string is undefined
|
||||
return '' if string is ''
|
||||
|
||||
# return translation
|
||||
if @map[string] isnt undefined
|
||||
|
|
|
@ -16,6 +16,9 @@ class App.Model extends Spine.Model
|
|||
uiUrl: ->
|
||||
'#'
|
||||
|
||||
translate: ->
|
||||
App[ @constructor.className ].configure_translate
|
||||
|
||||
objectDisplayName: ->
|
||||
@constructor.className
|
||||
|
||||
|
@ -67,11 +70,24 @@ class App.Model extends Spine.Model
|
|||
if !attribute.readonly
|
||||
|
||||
# check required // if null is defined && null is false
|
||||
if 'null' of attribute && !attribute[null]
|
||||
if 'null' of attribute && !attribute[null]
|
||||
|
||||
# key exists not in hash || value is '' || value is undefined
|
||||
if !( attribute.name of data['params'] ) || data['params'][attribute.name] is '' || data['params'][attribute.name] is undefined
|
||||
errors[attribute.name] = 'is required'
|
||||
# check :: fields
|
||||
parts = attribute.name.split '::'
|
||||
if parts[0] && !parts[1]
|
||||
|
||||
# key exists not in hash || value is '' || value is undefined
|
||||
if !( attribute.name of data['params'] ) || data['params'][attribute.name] is '' || data['params'][attribute.name] is undefined
|
||||
errors[attribute.name] = 'is required'
|
||||
|
||||
else if parts[0] && parts[1] && !parts[2]
|
||||
|
||||
# key exists not in hash || value is '' || value is undefined
|
||||
if !data.params[parts[0]] || !( parts[1] of data.params[parts[0]] ) || data.params[parts[0]][parts[1]] is '' || data.params[parts[0]][parts[1]] is undefined
|
||||
errors[attribute.name] = 'is required'
|
||||
|
||||
else
|
||||
throw "can't parse '#{attribute.name}'"
|
||||
|
||||
# check confirm password
|
||||
if attribute.type is 'password' && data['params'][attribute.name] && "#{attribute.name}_confirm" of data['params']
|
||||
|
@ -82,7 +98,9 @@ class App.Model extends Spine.Model
|
|||
errors["#{attribute.name}_confirm"] = ''
|
||||
|
||||
# return error object
|
||||
return errors if !_.isEmpty(errors)
|
||||
if !_.isEmpty(errors)
|
||||
console.log 'error', 'validation vailed', errors
|
||||
return errors
|
||||
|
||||
# return no errors
|
||||
return
|
||||
|
@ -327,5 +345,3 @@ class App.Model extends Spine.Model
|
|||
return
|
||||
)
|
||||
collection
|
||||
|
||||
|
||||
|
|
|
@ -2,4 +2,25 @@ class App.Channel extends App.Model
|
|||
@configure 'Channel', 'adapter', 'area', 'options', 'group_id', 'active', 'updated_at'
|
||||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/channels'
|
||||
@configure_delete = true
|
||||
@configure_delete = true
|
||||
|
||||
@configure_attributes = [
|
||||
{ name: 'adapter', display: 'Type', tag: 'select', multiple: false, null: false, options: { IMAP: 'IMAP', POP3: 'POP3' } },
|
||||
{ name: 'options::host', display: 'Host', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false },
|
||||
{ name: 'options::user', display: 'User', tag: 'input', type: 'text', limit: 120, null: false, autocapitalize: false },
|
||||
{ name: 'options::password', display: 'Password', tag: 'input', type: 'password', limit: 120, null: false, autocapitalize: false },
|
||||
{ name: 'options::ssl', display: 'SSL', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' }, translate: true, default: true},
|
||||
{ name: 'options::folder', display: 'Folder', tag: 'input', type: 'text', limit: 120, null: true, autocapitalize: false },
|
||||
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: false, nulloption: true, relation: 'Group' },
|
||||
{ name: 'active', display: 'Active', tag: 'select', multiple: false, null: false, options: { true: 'yes', false: 'no' }, translate: true, default: true },
|
||||
]
|
||||
@configure_overview = [
|
||||
'adapter', 'options::host', 'options::user', 'group'
|
||||
]
|
||||
|
||||
@_fillUp: (data) ->
|
||||
|
||||
# group
|
||||
data.group = App.Group.find( data.group_id )
|
||||
|
||||
data
|
|
@ -18,21 +18,64 @@ class App.Overview extends App.Model
|
|||
default: ['number', 'title', 'state', 'created_at']
|
||||
null: false
|
||||
translate: true
|
||||
options:
|
||||
number: 'Number'
|
||||
title: 'Title'
|
||||
customer: 'Customer'
|
||||
state: 'State'
|
||||
priority: 'Priority'
|
||||
group: 'Group'
|
||||
owner: 'Owner'
|
||||
created_at: 'Age'
|
||||
last_contact: 'Last Contact'
|
||||
last_contact_agent: 'Last Contact Agent'
|
||||
last_contact_customer: 'Last Contact Customer'
|
||||
first_response: 'First Response'
|
||||
close_time: 'Close Time'
|
||||
article_count: 'Article Count'
|
||||
options: [
|
||||
{
|
||||
value: 'number'
|
||||
name: 'Number'
|
||||
},
|
||||
{
|
||||
value: 'title'
|
||||
name: 'Title'
|
||||
},
|
||||
{
|
||||
value: 'customer'
|
||||
name: 'Customer'
|
||||
},
|
||||
{
|
||||
value: 'state'
|
||||
name: 'State'
|
||||
},
|
||||
{
|
||||
value: 'priority'
|
||||
name: 'Priority'
|
||||
},
|
||||
{
|
||||
value: 'group'
|
||||
name: 'Group'
|
||||
},
|
||||
{
|
||||
value: 'owner'
|
||||
name: 'Owner'
|
||||
},
|
||||
{
|
||||
value: 'created_at'
|
||||
name: 'Age'
|
||||
},
|
||||
{
|
||||
value: 'last_contact'
|
||||
name: 'Last Contact'
|
||||
},
|
||||
{
|
||||
value: 'last_contact_agent'
|
||||
name: 'Last Contact Agent'
|
||||
},
|
||||
{
|
||||
value: 'last_contact_customer'
|
||||
name: 'Last Contact Customer'
|
||||
},
|
||||
{
|
||||
value: 'first_response'
|
||||
name: 'First Response'
|
||||
},
|
||||
{
|
||||
value: 'close_time'
|
||||
name: 'Close Time'
|
||||
},
|
||||
{
|
||||
value: 'article_count'
|
||||
name: 'Article Count'
|
||||
},
|
||||
]
|
||||
class: 'medium'
|
||||
},
|
||||
|
||||
|
|
|
@ -5,19 +5,21 @@ class App.Ticket extends App.Model
|
|||
@configure_attributes = [
|
||||
{ name: 'number', display: '#', tag: 'input', type: 'text', limit: 100, null: true, read_only: true, style: 'width: 8%' },
|
||||
{ name: 'customer_id', display: 'Customer', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8', autocapitalize: false, help: 'Select the customer of the Ticket or create one.', link: '<a href="" class="customer_new">»</a>' },
|
||||
{ name: 'organization_id', display: 'Organization', tagreadonly: 1 },
|
||||
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, limit: 100, null: false, class: 'span8', relation: 'Group', style: 'width: 10%' },
|
||||
{ name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, limit: 100, null: true, class: 'span8', relation: 'User', style: 'width: 12%' },
|
||||
{ name: 'title', display: 'Title', tag: 'input', type: 'text', limit: 100, null: false, class: 'span8' },
|
||||
{ name: 'state_id', display: 'State', tag: 'select', multiple: false, null: false, relation: 'TicketState', default: 'new', class: 'medium', style: 'width: 12%' },
|
||||
{ name: 'priority_id', display: 'Priority', tag: 'select', multiple: false, null: false, relation: 'TicketPriority', default: '2 normal', class: 'medium', style: 'width: 12%' },
|
||||
{ name: 'created_at', display: 'Created', tag: 'time', style: 'width: 12%' },
|
||||
{ name: 'last_contact', display: 'Last contact', tag: 'time', null: true, style: 'width: 12%' },
|
||||
{ name: 'last_contact_agent', display: 'Last contact (Agent)', tag: 'time', null: true, style: 'width: 12%' },
|
||||
{ name: 'last_contact_customer', display: 'Last contact (Customer)', tag: 'time', null: true, style: 'width: 12%' },
|
||||
{ name: 'first_response', display: 'First response', tag: 'time', null: true, style: 'width: 12%' },
|
||||
{ name: 'close_time', display: 'Close time', tag: 'time', null: true, style: 'width: 12%' },
|
||||
{ name: 'escalation_time', display: 'Escalation in', tag: 'time', null: true, style: 'width: 12%' },
|
||||
{ name: 'last_contact', display: 'Last contact', type: 'time', null: true, style: 'width: 12%' },
|
||||
{ name: 'last_contact_agent', display: 'Last contact (Agent)', type: 'time', null: true, style: 'width: 12%' },
|
||||
{ name: 'last_contact_customer', display: 'Last contact (Customer)', type: 'time', null: true, style: 'width: 12%' },
|
||||
{ name: 'first_response', display: 'First response', type: 'time', null: true, style: 'width: 12%' },
|
||||
{ name: 'close_time', display: 'Close time', type: 'time', null: true, style: 'width: 12%' },
|
||||
{ name: 'escalation_time', display: 'Escalation in', type: 'time', null: true, style: 'width: 12%', class: 'escalation' },
|
||||
{ name: 'article_count', display: 'Article#', style: 'width: 12%' },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', style: 'width: 12%', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', style: 'width: 12%', readonly: 1 },
|
||||
]
|
||||
|
||||
uiUrl: ->
|
||||
|
@ -41,6 +43,13 @@ class App.Ticket extends App.Model
|
|||
else
|
||||
data.customer = App.User.find( data.customer_id )
|
||||
|
||||
# organization_id
|
||||
if data.organization_id
|
||||
if !App.Organization.exists( data.organization_id )
|
||||
console.error("Can't find user for data.organization_id #{data.organization_id} for ticket #{data.id}")
|
||||
else
|
||||
data.organization = App.Organization.find( data.organization_id )
|
||||
|
||||
# owner
|
||||
if data.owner_id
|
||||
if !App.User.exists( data.owner_id )
|
||||
|
|
|
@ -2,3 +2,4 @@ class App.TicketArticleSender extends App.Model
|
|||
@configure 'TicketArticleSender', 'name', 'updated_at'
|
||||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/ticket_article_senders'
|
||||
@configure_translate = true
|
|
@ -2,3 +2,4 @@ class App.TicketArticleType extends App.Model
|
|||
@configure 'TicketArticleType', 'name', 'updated_at'
|
||||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/ticket_article_types'
|
||||
@configure_translate = true
|
|
@ -2,3 +2,13 @@ class App.TicketPriority extends App.Model
|
|||
@configure 'TicketPriority', 'name', 'note', 'active', 'updated_at'
|
||||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/ticket_priorities'
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, translate: true },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
|
||||
]
|
||||
@configure_translate = true
|
||||
@configure_overview = [
|
||||
'name',
|
||||
]
|
|
@ -2,3 +2,13 @@ class App.TicketState extends App.Model
|
|||
@configure 'TicketState', 'name', 'note', 'active'
|
||||
@extend Spine.Model.Ajax
|
||||
@url: @apiPath + '/ticket_states'
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, translate: true },
|
||||
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false },
|
||||
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 },
|
||||
]
|
||||
@configure_translate = true
|
||||
@configure_overview = [
|
||||
'name',
|
||||
]
|
|
@ -7,8 +7,8 @@
|
|||
<% if @radio: %>
|
||||
<th style="width: 22px"></th>
|
||||
<% end %>
|
||||
<% for row in @header: %>
|
||||
<th <% if row.style: %>style="<%= row.style %>"<% end %>><%- @T( row.display ) %></th>
|
||||
<% for item in @header: %>
|
||||
<th <% if item.style: %>style="<%= item.style %>"<% end %>><%- @T( item.display ) %></th>
|
||||
<% end %>
|
||||
<% if @destroy: %>
|
||||
<th class="span2"><%- @T('Delete') %></th>
|
||||
|
@ -16,17 +16,25 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% position = 0 %>
|
||||
<% length = @header.length %>
|
||||
<% if @checkbox || @radio: %>
|
||||
<% length++ %>
|
||||
<% end %>
|
||||
<% position = 0 %>
|
||||
<% groupLast = '' %>
|
||||
<% for object in @objects: %>
|
||||
<% if @groupBy: %>
|
||||
<% if groupLast isnt object[@groupBy.id]: %>
|
||||
<tr class=""><td colspan="<%= length %>"><b><%- @P( object[@groupBy.name] ) %></b></td></tr>
|
||||
<% groupLast = object[@groupBy.id] %>
|
||||
<% if object[@groupBy] && object[@groupBy].displayName: %>
|
||||
<% groupByName = object[@groupBy].displayName() %>
|
||||
<% if object[@groupBy].translate(): %>
|
||||
<% groupByName = @T(groupByName) %>
|
||||
<% end %>
|
||||
<% else: %>
|
||||
<% groupByName = object[@groupBy] || '-' %>
|
||||
<% end %>
|
||||
<% if groupLast isnt groupByName: %>
|
||||
<tr class=""><td colspan="<%= length %>"><b><%- @P( groupByName ) %></b></td></tr>
|
||||
<% groupLast = groupByName %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% position++ %>
|
||||
|
@ -37,33 +45,53 @@
|
|||
<% if @radio: %>
|
||||
<td><input type="radio" value="<%= object.id %>" name="radio"/></td>
|
||||
<% end %>
|
||||
<% for row in @overview: %>
|
||||
<% displayName = @P( object[row.name], row ) %>
|
||||
<% if row.translate: %><% displayName = @T( displayName ) %><% end %>
|
||||
<% if row.title: %>
|
||||
<% displayNameTitle = displayName %>
|
||||
<% if object[row.title]: %>
|
||||
<% displayNameTitle = @P( object[row.title], row ) %>
|
||||
<% end %>
|
||||
<% for item in @header: %>
|
||||
<% translation = false %>
|
||||
<% value = object[item.name] %>
|
||||
<% item_id = item.name.substr(item.name.length-3, item.name.length) %>
|
||||
<% if item_id is '_id' && object[ item.name.substr(0, item.name.length-3) ]: %>
|
||||
<% value = object[ item.name.substr(0, item.name.length-3) ] %>
|
||||
<% refObject = object[ item.name.substr(0, item.name.length-3) ] %>
|
||||
<% end %>
|
||||
<td <% if row.title: %>title="<%= displayNameTitle %>"<% end %>>
|
||||
<% if row.type is 'link': %>
|
||||
<a href="#" data-type="<%= row.dataType %>" <% if row.class: %>class="<%= row.class %>"<% end %>>
|
||||
<% else: %>
|
||||
<span <% if row.class: %>class="<%= row.class %>"<% end %> <% if row.data && row.data.id: %>data-id="<%= object[row.name].id %>"<% end %>>
|
||||
<% end %>
|
||||
<% if row.translate || row.callback: %><%- displayName %><% else: %><%= displayName %><% end %>
|
||||
<% if row.type is 'link': %></a><% else: %></span><% end %>
|
||||
<% if !value: %>
|
||||
<% parts = item.name.split '::' %>
|
||||
<% if parts[0] && parts[1] && object[ parts[0] ]: %>
|
||||
<% value = object[ parts[0] ][ parts[1] ] %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if value && value.displayNameLong : %>
|
||||
<% translation = true %>
|
||||
<% value = value.displayNameLong() %>
|
||||
<% else if value && value.displayName : %>
|
||||
<% translation = true %>
|
||||
<% value = value.displayName() %>
|
||||
<% end %>
|
||||
<% item_clone = item %>
|
||||
<% if @callbacks: %>
|
||||
<% for attribute, callbacksAll of @callbacks: %>
|
||||
<% if attribute is item.name || attribute is item_id: %>
|
||||
<% for callback in callbacksAll: %>
|
||||
<% callback( value, object, item_clone, @header, refObject ) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% #console.log('HH', item_clone.name, item_clone.type, item_clone.translate, item_clone, object.translate(), refObject, translation) %>
|
||||
<td>
|
||||
<% if item_clone.link: %><a href="<%- item_clone.link %>"><% end %>
|
||||
<% if item_clone.translate || ( translation && !refObject && object.translate && object.translate() ) || ( translation && refObject && refObject.translate && refObject.translate() ) : %>
|
||||
<span <% if item_clone.class: %>class="<%= item_clone.class %>"<% end %>><%- @T( @P( value, item_clone ) ) %></span>
|
||||
<% else if item_clone.type is 'time': %>
|
||||
<span class="humanTimeFromNow <% if item_clone.class: %><%= item_clone.class %><% end %>" data-time="<%- object[item_clone.name] %>">?</span>
|
||||
<% else: %>
|
||||
<span <% if item_clone.class: %>class="<%= item_clone.class %>"<% end %> <% if item_clone.title: %>title="<%= item_clone.title %>"<% end %> <% if item_clone.data: %>data-id="<%= item_clone.data.id %><% end %>"><%= @P(value) %></span>
|
||||
<% end %>
|
||||
<% if item_clone.link: %></a><% end %>
|
||||
</td>
|
||||
<% end %>
|
||||
<!--
|
||||
<td><%= object.updated_at %></td>
|
||||
-->
|
||||
<% if @destroy: %>
|
||||
<td>
|
||||
<a href="#" class="glyphicon glyphicon-trash" data-type="destroy"></a>
|
||||
</td>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if @destroy: %>
|
||||
<td><a href="#" class="glyphicon glyphicon-trash" data-type="destroy"></a></td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
|
|
|
@ -16,6 +16,13 @@ class TestsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# GET /tests/table
|
||||
def table
|
||||
respond_to do |format|
|
||||
format.html # index.html.erb
|
||||
end
|
||||
end
|
||||
|
||||
# GET /test/wait
|
||||
def wait
|
||||
sleep params[:sec].to_i
|
||||
|
|
17
app/views/tests/table.html.erb
Normal file
17
app/views/tests/table.html.erb
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
<link rel="stylesheet" href="/assets/tests/qunit-1.10.0.css">
|
||||
<script src="/assets/tests/qunit-1.10.0.js"></script>
|
||||
<script src="/assets/tests/table.js"></script>
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
padding-top: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
|
||||
<div id="qunit"></div>
|
||||
|
||||
<div id="table"></div>
|
|
@ -2,6 +2,7 @@ Zammad::Application.routes.draw do
|
|||
|
||||
match '/tests-core', :to => 'tests#core', :via => :get
|
||||
match '/tests-form', :to => 'tests#form', :via => :get
|
||||
match '/tests-table', :to => 'tests#table', :via => :get
|
||||
match '/tests/wait/:sec', :to => 'tests#wait', :via => :get
|
||||
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
# encoding: utf-8
|
||||
require 'browser_test_helper'
|
||||
|
||||
class UnitTest < TestCase
|
||||
class AAbUnitTest < TestCase
|
||||
def test_core
|
||||
tests = [
|
||||
{
|
Loading…
Reference in a new issue