diff --git a/app/model/Device.js b/app/model/Device.js index ee95a839..ea088afa 100644 --- a/app/model/Device.js +++ b/app/model/Device.js @@ -7,17 +7,84 @@ Ext.define('NgcpCsc.model.Device', { }, { name: 'device', type: 'string' - },{ + }, { name: 'mac', type: 'string' - },{ - name: 'status', - type: 'string' }, { name: 'image', type: 'string' }, { - name: 'destinations', + name: 'image', + type: 'string' + },{ + name: 'image', + type: 'string' + },{ + name: 'seats', type: 'auto' + }, { + name: 'imageWithButtons', + depends: ['image'], + persist: false, + convert: function(v, rec) { + var retVal = "
"; + var shiftPosition = false; + Ext.each(rec.get('seats'), function(seat) { + if (seat.name) { + var buttonCls = 'assigned-button'; + var btnPos = "top:" + seat.position.top + ";left:" + seat.position.left; + var rectPos, + top = seat.position.top, + left = seat.position.left, + lineWidth = shiftPosition ? "35%" : "20%", + lineHeight = shiftPosition ? "18%" : "10%", + lineTop = top, + lineLeft = seat.position.left, + lineCls = seat.position.anchor == 'left' || seat.position.anchor == 'right' ? 'connection-left-right' : 'connection-top-bottom'; + switch (seat.position.anchor) { + case "left": + lineHeight = "1%"; + left = (parseInt(seat.position.left.split('%')[0]) + (shiftPosition ? 30 : 10)).toString() + "%"; + top = (parseInt(seat.position.top.split('%')[0]) - 1).toString() + "%"; + break; + case "right": + left = (parseInt(seat.position.left.split('%')[0]) - (shiftPosition ? 35 : 20)).toString() + "%"; + top = (parseInt(seat.position.top.split('%')[0]) - 1).toString() + "%"; + lineLeft = left; + lineHeight = "1%"; + break; + case "top": + lineLeft = (parseInt(seat.position.left.split('%')[0]) + 1).toString() + "%"; + top = (parseInt(seat.position.top.split('%')[0]) + (shiftPosition ? 14 : 7)).toString() + "%"; + break; + case "bottom": + lineLeft = (parseInt(seat.position.left.split('%')[0]) + 1).toString() + "%"; + top = (parseInt(seat.position.top.split('%')[0]) - (shiftPosition ? 14 : 7)).toString() + "%"; + break; + } + + retVal += '
' + + seat.order + ' | ' + seat.name + '
'; + } else { + var top = (parseInt(seat.position.top.split('%')[0]) - 0.5).toString() + "%"; + var btnPos = "top:" + top + ";left:" + seat.position.left; + var buttonCls = 'free-button'; + retVal += '
'; + + } + shiftPosition = !shiftPosition; + }); + return retVal; + } }] }); diff --git a/app/model/DevicesListItem.js b/app/model/DevicesListItem.js index 6cd7158a..aef3836a 100644 --- a/app/model/DevicesListItem.js +++ b/app/model/DevicesListItem.js @@ -7,5 +7,11 @@ Ext.define('NgcpCsc.model.DevicesListItem', { }, { name: 'name', type: 'string' + },{ + name: 'img', + type: 'string' + },{ + name: 'destinations', + type: 'auto' }] }); diff --git a/app/utils/locales.js b/app/utils/locales.js index ce564e3a..fcc8ae4b 100644 --- a/app/utils/locales.js +++ b/app/utils/locales.js @@ -1541,11 +1541,11 @@ Ext.define('Ngcp.csc.locales', { sp: 'ADD NEW DEVICE' }, device: { - en: 'Device', - it: 'Device', - de: 'Device', - fr: 'Device', - sp: 'Device' + en: 'Phone Model:', + it: 'Phone Model:', + de: 'Phone Model:', + fr: 'Phone Model:', + sp: 'Phone Model:' }, device_profile: { en: 'Device profile', @@ -1562,11 +1562,11 @@ Ext.define('Ngcp.csc.locales', { sp: 'Destination' }, extension: { - en: 'Extension:', - it: 'Extension:', - de: 'Extension:', - fr: 'Extension:', - sp: 'Extension:' + en: 'Extension', + it: 'Extension', + de: 'Extension', + fr: 'Extension', + sp: 'Extension' }, mac: { en: 'Mac address:', @@ -1631,6 +1631,13 @@ Ext.define('Ngcp.csc.locales', { fr: 'Name:', sp: 'Name:' }, + station_name: { + en: 'Station name:', + it: 'Station name:', + de: 'Station name:', + fr: 'Station name:', + sp: 'Station name:' + }, enter_new_name: { en: 'Enter new name', it: 'Enter new name', @@ -1701,6 +1708,13 @@ Ext.define('Ngcp.csc.locales', { fr: 'Enter new status', sp: 'Enter new status' }, + enter_new_extension: { + en: 'Enter new extension', + it: 'Enter new extension', + de: 'Enter new extension', + fr: 'Enter new extension', + sp: 'Enter new extension' + }, cancel_operation: { en: 'Cancel operation', it: 'Cancel operation', @@ -1786,6 +1800,32 @@ Ext.define('Ngcp.csc.locales', { fr: 'Auto attendants for ', sp: 'Auto attendants for ' } + }, + devices: { + delete_assignment:{ + en: 'Do you really want to unassign button {0}?', + it: 'Do you really want to unassign button {0}?', + de: 'Do you really want to unassign button {0}?', + fr: 'Do you really want to unassign button {0}?', + sp: 'Do you really want to unassign button {0}?' + }, + tooltip:{ + click: { + en: 'Click to edit or remove', + it: 'Click to edit or remove', + de: 'Click to edit or remove', + fr: 'Click to edit or remove', + sp: 'Click to edit or remove' + }, + clicktoassign: { + en: 'Click to assign', + it: 'Click to assign', + de: 'Click to assign', + fr: 'Click to assign', + sp: 'Click to assign' + }, + } + } }, common: { diff --git a/classic/sass/src/view/core/GridCards.scss b/classic/sass/src/view/core/GridCards.scss index f36c0472..756d10d6 100644 --- a/classic/sass/src/view/core/GridCards.scss +++ b/classic/sass/src/view/core/GridCards.scss @@ -49,26 +49,6 @@ $grid-row-cell-focus-border-color: '#fff'; width: 100%; color: $base-color; - .img-row { - position:relative; - padding: 20; - img { - width: 100%; - } - .number-circle { - position:absolute; - border-radius: 50%; - width: 30px; - height: 30px; - padding: 4px; - background: #fff; - border: 2px solid $base-color; - color: $base-color; - text-align: center; - font-size: 16px; - } - } - .fa { margin-right: 10px; color: $base-color; diff --git a/classic/sass/src/view/main/Main.scss b/classic/sass/src/view/main/Main.scss index 6b0c5d29..fd65a9ae 100644 --- a/classic/sass/src/view/main/Main.scss +++ b/classic/sass/src/view/main/Main.scss @@ -46,6 +46,8 @@ $row-indicator-over-color: transparent ); + + // override of extjs classes to add css variables defined in sass/var/view/main: .x-form-text-default { color: var(--color); diff --git a/classic/sass/src/view/pages/pbxconfig/devices/Devices.scss b/classic/sass/src/view/pages/pbxconfig/devices/Devices.scss new file mode 100644 index 00000000..74e90efc --- /dev/null +++ b/classic/sass/src/view/pages/pbxconfig/devices/Devices.scss @@ -0,0 +1,60 @@ +.devices { + .x-panel-body { + background: white; + } + .device-img { + display: block; + } + + .circle-number { + position:absolute; + border-radius: 50%; + width: 12px; + height: 12px; + border: 2px #fff; + color: #fff; + text-align: center; + font-size: 12px; + } + .assigned-button { + @extend .circle-number; + background: red; + } + .free-button { + @extend .circle-number; + background: yellow; + } + .button-info { + position: absolute; + background: white; + border: 1px solid red; + padding: 0px 5px 0 5px; + z-index: 1; + min-width: 60px; + font-size: 11px; + } + .connection-left-right { + margin-top: 5px; + position: absolute; + border-top: 1px solid red; + background-color:rgba(255,0,0,0); + } + .connection-top-bottom { + position: absolute; + border-left: 1px solid red; + background-color:rgba(255,0,0,0); + } + .edit-panel { + background: #F6F6F6; + } + .seat-name { + margin-left: 20px; + } + .x-btn-group-default-framed { + border-color: #F6F6F6; + border-width: 0px; + } + .devices-seat-fieldset { + border: 1px solid red !important; + } +} diff --git a/classic/src/view/pages/pbxconfig/PbxConfig.js b/classic/src/view/pages/pbxconfig/PbxConfig.js index 1f20c993..d4f913fb 100644 --- a/classic/src/view/pages/pbxconfig/PbxConfig.js +++ b/classic/src/view/pages/pbxconfig/PbxConfig.js @@ -27,7 +27,6 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfig', { }] }] }]; - this.callParent(); } }); diff --git a/classic/src/view/pages/pbxconfig/PbxConfigController.js b/classic/src/view/pages/pbxconfig/PbxConfigController.js index 08a20f18..fcf68688 100644 --- a/classic/src/view/pages/pbxconfig/PbxConfigController.js +++ b/classic/src/view/pages/pbxconfig/PbxConfigController.js @@ -11,7 +11,7 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { } }, - onRouteChange: function () { + onRouteChange: function() { var vm = this.getViewModel(); switch (window.location.hash) { case '#pbxconfig/seats': @@ -26,7 +26,7 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { }; }, - onEnterPressed: function (field, el) { + onEnterPressed: function(field, el) { var me = this; if (el.getKey() == el.ENTER) { var currentRoute = window.location.hash; @@ -45,7 +45,7 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { }; }, - nameRenderer: function (value, record) { + nameRenderer: function(value, record) { if (value == '') { return Ngcp.csc.locales.pbxconfig.enter_new_name[localStorage.getItem('languageSelected')]; } else { @@ -74,9 +74,32 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { var store = Ext.getStore(storeName); var plugin = view.grid.getPlugin('rowwidget' + storeName); plugin.toggleRow(store.indexOf(record), record); - Ext.defer(function () { + Ext.defer(function() { view.grid.updateLayout(); }, 50); + if (currentRoute == '#pbxconfig/devices') { + var grid = this.lookupReference('devicesGrid'); + var nodes = plugin.view.getNodes(); + + grid.getSelectionModel().select(record); + store.each(function(rec, index){ + var node = Ext.fly(nodes[index]); + if(rec.get('id') !== record.get('id') && node.getHeight() > 50){ + plugin.toggleRow(index, store.getAt(index)); // collapse all cards but the active one + } + }); + + } + }, + + // Workaround, to prevent row from collapsings + keepRowExpanded: function(grid, rec) { + var currentRoute = window.location.hash; + var storeName = this.getStoreFromRoute(currentRoute); + var plugin = grid.getPlugin('rowwidget' + storeName); + var store = Ext.getStore(storeName); + plugin.toggleRow(store.indexOf(rec), rec); + plugin.toggleRow(store.indexOf(rec), rec); }, getFieldComponent: function(view, key, id) { @@ -107,9 +130,9 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { var elClassList = el.firstChild.classList; var recId = el.id.split("-")[1]; var store = Ext.getStore(storeName); - saved === true - ? this.fireEvent('showmessage', true, Ngcp.csc.locales.pbxconfig.changes_saved[localStorage.getItem('languageSelected')]) - : this.fireEvent('showmessage', false, Ngcp.csc.locales.pbxconfig.no_changes_saved[localStorage.getItem('languageSelected')]); + saved === true ? + this.fireEvent('showmessage', true, Ngcp.csc.locales.pbxconfig.changes_saved[localStorage.getItem('languageSelected')]) : + this.fireEvent('showmessage', false, Ngcp.csc.locales.pbxconfig.no_changes_saved[localStorage.getItem('languageSelected')]); elClassList.remove(Ngcp.csc.icons.floppy.split(' ')[1]); elClassList.add(Ngcp.csc.icons.edit.split(' ')[1]); el.dataset.callback = 'editCard'; @@ -127,13 +150,15 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { var rec = store.findRecord('id', recId); var currentNameInRecord = rec.get("name"); var grid = this.lookupReference(storeName.toLowerCase() + 'Grid'); - var plugin = grid.getPlugin('rowwidget' + storeName); var form = Ext.ComponentQuery.query('#' + storeName.toLowerCase() + '-' + recId)[0]; - var formFields = form.query('textfield'); + var labels = form.query('label'); + var formFields = form.query('textfield, combo'); var invalidCheck = 0; for (var field in formFields) { var fieldValue = formFields[field].value; - if (Ext.isEmpty(formFields[field].value)) invalidCheck ++; + if (!formFields[field]._skipSaveValidation && Ext.isEmpty(formFields[field].value)) { + invalidCheck++; + } }; switch (invalidCheck === 0) { case true: @@ -147,9 +172,7 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { switch (rec.dirty) { case true: store.commitChanges(); - // Workaround, to prevent row from collapsing - plugin.toggleRow(store.indexOf(rec), rec); - plugin.toggleRow(store.indexOf(rec), rec); + this.keepRowExpanded(grid, rec); me.showMsgSwitchIconHideFields(storeName, el, true); break; case false: @@ -163,18 +186,41 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { }; }, - addNewEmptyRowToGrid: function (store, storeName, newId) { + addNewEmptyRowToGrid: function(store, storeName, newId) { + var newRec; switch (storeName) { case 'Seats': - store.add({ "id": newId, "name": "", "extension": "", "group": "", "numbers": "", "phone_devices": "" }); + newRec = store.add({ + "id": newId, + "name": "", + "extension": "", + "group": "", + "numbers": "", + "phone_devices": "" + }); break; case 'Groups': - store.add({ "id": newId, "name": "", "extension": "", "hunt_policy": "", "hunt_timeout": "" }); + newRec = store.add({ + "id": newId, + "name": "", + "extension": "", + "hunt_policy": "", + "hunt_timeout": "" + }); break; case 'Devices': - store.add({ "id": newId, "name": "", "device": "", "mac": "", "status": "" }); + newRec = store.add({ + "id": newId, + "name": "", + "device": "", + "mac": "", + "status": "", + "extension":"", + "extension2":"" + }); break; } + this.getView().down('grid').getSelectionModel().select(newRec); }, addPbx: function() { @@ -188,7 +234,7 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { me.addNewEmptyRowToGrid(store, storeName, newId); var rec = store.findRecord('id', newId); plugin.toggleRow(store.indexOf(rec), rec); - Ext.defer(function () { + Ext.defer(function() { me.showHideFocusFieldsById(newId, storeName, 'show'); var el = document.getElementById('edit' + storeName.slice(0, -1) + '-' + newId); var elClassList = el.firstChild.classList; @@ -200,14 +246,16 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { }, 50); }, - setFieldValue: function (cmp) { + setFieldValue: function(cmp) { var recId = cmp.id.split("-")[3]; var recKey = cmp.id.split("-")[2]; var currentRoute = window.location.hash; var storeName = this.getStoreFromRoute(currentRoute); var store = Ext.getStore(storeName); var rec = store.findRecord('id', recId); - cmp.setValue(rec.get(recKey)); + if (!cmp.getValue()) { + cmp.setValue(rec.get(recKey)); + } }, showHideFocusFieldsById: function(id, storeName, hideOrShow) { @@ -217,6 +265,7 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { var mainNameLabel = Ext.ComponentQuery.query('#' + viewName + '-label-mainname-' + id) || ''; var nameField = Ext.ComponentQuery.query('#' + viewName + '-textfield-name-' + id) || ''; var extensionField = Ext.ComponentQuery.query('#' + viewName + '-textfield-extension-' + id) || ''; + var extensionField2 = Ext.ComponentQuery.query('#' + viewName + '-textfield-extension2-' + id) || ''; var primaryNumberField = Ext.ComponentQuery.query('#' + viewName + '-combo-primary_number-' + id) || ''; var aliasNumbersField = Ext.ComponentQuery.query('#' + viewName + '-tagfield-alias_numbers-' + id) || ''; var groupsField = Ext.ComponentQuery.query('#' + viewName + '-tagfield-groups-' + id) || ''; @@ -229,6 +278,7 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { var groupsLabel = Ext.ComponentQuery.query('#' + viewName + '-label-groups-' + id) || ''; var primaryNumberLabel = Ext.ComponentQuery.query('#' + viewName + '-label-primary_number-' + id) || ''; var aliasNumbersLabel = Ext.ComponentQuery.query('#' + viewName + '-label-alias_numbers-' + id) || ''; + var extensionLabel2 = Ext.ComponentQuery.query('#' + viewName + '-label-extension2-' + id) || ''; var huntPolicyLabel = Ext.ComponentQuery.query('#' + viewName + '-label-hunt_policy-' + id) || ''; var huntTimeoutLabel = Ext.ComponentQuery.query('#' + viewName + '-label-hunt_timeout-' + id) || ''; var huntTimeoutPreLabel = Ext.ComponentQuery.query('#' + viewName + '-prelabel-hunt_timeout-' + id) || ''; @@ -274,14 +324,20 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { case 'devices': deviceLabel[0].setHidden(labelHide); macLabel[0].setHidden(labelHide); - statusLabel[0].setHidden(labelHide); + nameField[0].setHidden(fieldHide); deviceField[0].setHidden(fieldHide); macField[0].setHidden(fieldHide); - statusField[0].setHidden(fieldHide); - statusField[0].focus(); + extensionLabel[0].setHidden(labelHide); + extensionField[0].setHidden(fieldHide); + extensionLabel2[0].setHidden(labelHide); + extensionField2[0].setHidden(fieldHide); macField[0].focus(); deviceField[0].focus(); - nameField[0].focus(); + extensionField[0].focus(); + extensionField2[0].focus(); + Ext.defer(function() { + nameField[0].focus(); + }, 100) break; }; }, @@ -298,7 +354,7 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { elClassList.remove(Ngcp.csc.icons.edit.split(' ')[1]); elClassList.add(Ngcp.csc.icons.floppy.split(' ')[1]); el.dataset.callback = 'saveCard'; - Ext.defer(function () { + Ext.defer(function() { me.showHideFocusFieldsById(recId, storeName, 'show'); }, 50); }, @@ -313,7 +369,7 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { this.fireEvent('showmessage', true, Ngcp.csc.locales.common.remove_success[localStorage.getItem('languageSelected')]); }, - toggleCancelCard: function (el, state) { + toggleCancelCard: function(el, state) { var cancelCardId = el.id.replace(/edit|save/, 'cancel'); var cancelCard = document.getElementById(cancelCardId); var elClassList = cancelCard.classList; @@ -327,7 +383,7 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.PbxConfigController', { }; }, - cancelCard: function (el, abortAdd) { + cancelCard: function(el, abortAdd) { var me = this; var currentRoute = window.location.hash; var storeName = this.getStoreFromRoute(currentRoute); diff --git a/classic/src/view/pages/pbxconfig/devices/Devices.js b/classic/src/view/pages/pbxconfig/devices/Devices.js index 67575310..83b973b8 100644 --- a/classic/src/view/pages/pbxconfig/devices/Devices.js +++ b/classic/src/view/pages/pbxconfig/devices/Devices.js @@ -2,7 +2,7 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.devices.Devices', { extend: 'NgcpCsc.view.pages.pbxconfig.PbxConfig', xtype: 'devices', - + controller: 'devices', initComponent: function() { var devicesGrid = Ext.create('NgcpCsc.view.pages.pbxconfig.devices.DevicesGrid'); diff --git a/classic/src/view/pages/pbxconfig/devices/DevicesController.js b/classic/src/view/pages/pbxconfig/devices/DevicesController.js new file mode 100644 index 00000000..188a4760 --- /dev/null +++ b/classic/src/view/pages/pbxconfig/devices/DevicesController.js @@ -0,0 +1,159 @@ +Ext.define('NgcpCsc.view.pages.pbxconfig.DevicseController', { + extend: 'NgcpCsc.view.pages.pbxconfig.PbxConfigController', + alias: 'controller.devices', + onIconClicked: function(event, el) { + // override onIconClicked of PbxConfigController + if (el.dataset.onseatclick) { + Ext.Function.defer(eval('this.' + el.dataset.onseatclick), 1, this, [el]); + return; + }; + if (el.dataset.callback) { + Ext.Function.defer(eval('this.' + el.dataset.callback), 1, this, [el]); + }; + }, + onMouseEntered: function(event, el) { + if (el.dataset.onseathovered) { + Ext.Function.defer(eval('this.' + el.dataset.onseathovered), 1, this, [el]); + } + }, + seatHovered: function(el) { + this.setShowEditPanelFields(el); + }, + seatClick: function(el) { + this.setShowEditPanelFields(el, true); + }, + setShowEditPanelFields: function(el, showBtns) { + var selectedRec = this.getSelectedRec(); + var showPanel = Ext.ComponentQuery.query('#seat-show-panel-' + selectedRec.get('id'))[0]; + var editPanel = Ext.ComponentQuery.query('#seat-edit-panel-' + selectedRec.get('id'))[0]; + var order = el.id.split('-')[2]; + var seatData; + + Ext.each(selectedRec.get('seats'), function(seat) { + if (order == seat.order) { + seatData = seat; + return; + } + }); + showPanel.down('[name=editBtns]').hide(); + showPanel.down('[name=typeValue]').setHtml((seatData && seatData.type && seatData.name) ? seatData.type + ": " + seatData.name : "Unassigned"); + editPanel._user = seatData.name || null; + editPanel._type = seatData.type || null; + editPanel._order = seatData.order; + showPanel.setTitle(seatData.order); + editPanel.setTitle('Edit ' + seatData.order); + if (showBtns) { + showPanel.down('[name=editBtns]').show(); + showPanel.addCls('devices-seat-fieldset'); + }else{ + showPanel.removeCls('devices-seat-fieldset'); + } + editPanel.hide(); + showPanel.show(); + }, + deviceSelected: function(combo, rec) { + var grid = this.lookupReference('devicesGrid'); + var devicesListStore = Ext.getStore('DevicesList'); + var selectedRec = this.getSelectedRec(); + var selectedRecFromList = devicesListStore.findRecord('name', combo.getValue()); + var nameField = Ext.ComponentQuery.query('#devices-textfield-name-' + selectedRec.get('id'))[0]; + var showPanel = Ext.ComponentQuery.query('#seat-show-panel-' + selectedRec.get('id'))[0]; + var editPanel = Ext.ComponentQuery.query('#seat-edit-panel-' + selectedRec.get('id'))[0]; + + if (selectedRec) { + selectedRec.set('seats', selectedRecFromList.get('seats')) + selectedRec.set('image', selectedRecFromList.get('image')); + selectedRec.set('device', combo.getValue()); + } + this.keepRowExpanded(grid, selectedRec); + showPanel.hide(); + Ext.Function.defer(function() { + grid.getView().refresh(); + nameField.focus(); + }, 50) + }, + editSeat: function() { + var selectedRec = this.getSelectedRec(); + var showPanel = Ext.ComponentQuery.query('#seat-show-panel-' + selectedRec.get('id'))[0]; + var editPanel = Ext.ComponentQuery.query('#seat-edit-panel-' + selectedRec.get('id'))[0]; + var type = editPanel._type; + var seatName = editPanel._user; + editPanel.down('[name=typeValue]').setValue(type); + editPanel.down('[name=seat]').setValue(seatName); + showPanel.hide(); + editPanel.show(); + }, + deleteSeat: function() { + var me = this; + var selectedRec = this.getSelectedRec(); + var showPanel = Ext.ComponentQuery.query('#seat-show-panel-' + selectedRec.get('id'))[0]; + var editPanel = Ext.ComponentQuery.query('#seat-edit-panel-' + selectedRec.get('id'))[0]; + + Ext.each(selectedRec.get('seats'), function(seat) { + if (seat.order == editPanel._order) { + Ext.Msg.show({ + message: Ext.String.format(Ngcp.csc.locales.pbxconfig.devices.delete_assignment[localStorage.getItem('languageSelected')], seat.order), + buttons: Ext.Msg.YESNO, + icon: Ext.Msg.QUESTION, + fn: function(btn) { + if (btn === 'yes') { + seat.name = null; + seat.type = null; + me.commitUnsavedChanges(); + me.fireEvent('showmessage', true, Ngcp.csc.locales.pbxconfig.changes_saved[localStorage.getItem('languageSelected')]) + showPanel.hide(); + } + } + }); + return; + } + }); + + }, + saveSeat: function() { + var me = this; + var grid = this.lookupReference('devicesGrid'); + var selectedRec = this.getSelectedRec(); + var showPanel = Ext.ComponentQuery.query('#seat-show-panel-' + selectedRec.get('id'))[0]; + var editPanel = Ext.ComponentQuery.query('#seat-edit-panel-' + selectedRec.get('id'))[0]; + var type = editPanel.down('[name=typeValue]').getValue(); + var user = editPanel.down('[name=seat]').getValue(); + Ext.each(selectedRec.get('seats'), function(seat) { + if (seat.order == editPanel._order) { + // workaround; TODO improve with binding in next iterations + seat.name = user; + seat.type = type; + editPanel._user = user; + editPanel._type = type; + showPanel.down('[name=typeValue]').setHtml(type + ": " + user); + me.fireEvent('showmessage', true, Ngcp.csc.locales.pbxconfig.changes_saved[localStorage.getItem('languageSelected')]) + return; + } + }); + me.commitUnsavedChanges(); + showPanel.show(); + editPanel.hide(); + }, + commitUnsavedChanges: function() { + var grid = this.lookupReference('devicesGrid'); + var selectedRec = this.getSelectedRec(); + var showPanel = Ext.ComponentQuery.query('#seat-show-panel-' + selectedRec.get('id'))[0]; + selectedRec.set('imageWithButtons', null, {silent: true}); + grid.getStore().commitChanges(); + this.keepRowExpanded(grid, selectedRec); + Ext.Function.defer(function() { + grid.getView().refresh(); + }, 50); + }, + discardChanges: function() { + var showPanel = Ext.ComponentQuery.query('#seat-show-panel-' + this.getSelectedRec().get('id'))[0]; + var editPanel = Ext.ComponentQuery.query('#seat-edit-panel-' + this.getSelectedRec().get('id'))[0]; + showPanel.show(); + editPanel.hide(); + }, + getSelectedRec: function() { + var grid = this.lookupReference('devicesGrid'); + var selectedRec = grid.getSelectionModel().getSelection()[0]; + return selectedRec; + } +}); diff --git a/classic/src/view/pages/pbxconfig/devices/DevicesGrid.js b/classic/src/view/pages/pbxconfig/devices/DevicesGrid.js index a481f343..9013e4d7 100644 --- a/classic/src/view/pages/pbxconfig/devices/DevicesGrid.js +++ b/classic/src/view/pages/pbxconfig/devices/DevicesGrid.js @@ -6,16 +6,25 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.devices.DevicesGrid', { reference: 'devicesGrid', store: 'Devices', + viewModel: 'devices', + viewConfig: { stripeRows: false, - enableTextSelection: true + enableTextSelection: true, + preserveScrollOnRefresh: true, + preserveScrollOnReload: true }, listeners: { click: { fn: 'onIconClicked', element: 'el', - delegate: 'div.card-icon' + delegate: '.card-icon' + }, + mouseenter: { + fn: 'onMouseEntered', + element: 'el', + delegate: '.card-icon' }, cellclick: 'expandRow', rowbodyclick: 'expandRow' @@ -38,7 +47,9 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.devices.DevicesGrid', { plugins: [{ pluginId: 'rowwidgetDevices', ptype: 'rowwidget', + selectRowOnExpand: true, widget: { + cls: 'devices', xtype: 'form', defaultBindProperty: 'hidden', bind: { @@ -49,142 +60,289 @@ Ext.define('NgcpCsc.view.pages.pbxconfig.devices.DevicesGrid', { layout: 'hbox' }, items: [{ - name: 'name', - defaults: { - padding: '0 0 15 0' - }, - items: [{ - xtype: 'label', - bind: { - id: 'devices-label-mainname-{record.id}' + name: 'name', + defaults: { + padding: '0 0 15 0' }, - hidden: true, - cls: 'pbx-data-value', - text: Ngcp.csc.locales.pbxconfig.name[localStorage.getItem('languageSelected')], - width: 120 + items: [{ + xtype: 'label', + bind: { + id: 'devices-label-mainname-{record.id}' + }, + hidden: true, + cls: 'pbx-data-value', + text: Ngcp.csc.locales.pbxconfig.station_name[localStorage.getItem('languageSelected')], + width: 120 + }, { + xtype: 'textfield', + required: true, + hidden: true, + emptyText: Ngcp.csc.locales.pbxconfig.enter_new_name[localStorage.getItem('languageSelected')], + bind: { + id: 'devices-textfield-name-{record.id}' + }, + listeners: { + // Workaround. Issue when binding is used, any change in any record field triggers a + // layout break in the row which looks like row collapse, but is not + focus: { + fn: 'setFieldValue' + }, + specialkey: 'onEnterPressed' + } + }] }, { - xtype: 'textfield', - required: true, - hidden: true, - emptyText: Ngcp.csc.locales.pbxconfig.enter_new_name[localStorage.getItem('languageSelected')], - bind: { - id: 'devices-textfield-name-{record.id}' + name: 'mac', + defaults: { + padding: '0 0 15 0' }, - listeners: { - // Workaround. Issue when binding is used, any change in any record field triggers a - // layout break in the row which looks like row collapse, but is not - focus: { - fn: 'setFieldValue' + items: [{ + xtype: 'label', + cls: 'pbx-data-value', + text: Ngcp.csc.locales.pbxconfig.mac[localStorage.getItem('languageSelected')], + width: 120 + }, { + xtype: 'label', + hidden: false, + bind: { + id: 'devices-label-mac-{record.id}', + text: '{record.mac}' + } + }, { + xtype: 'textfield', + required: true, + hidden: true, + emptyText: Ngcp.csc.locales.pbxconfig.enter_new_mac_address[localStorage.getItem('languageSelected')], + bind: { + id: 'devices-textfield-mac-{record.id}' }, - specialkey: 'onEnterPressed' - } - }] - }, { - name: 'device', - defaults: { - padding: '0 0 15 0' - }, - items: [{ - xtype: 'label', - cls: 'pbx-data-value', - text: Ngcp.csc.locales.pbxconfig.device[localStorage.getItem('languageSelected')], - width: 120 - }, { - xtype: 'label', - hidden: false, - bind: { - id: 'devices-label-device-{record.id}', - text: '{record.device}' - } + listeners: { + focus: { + fn: 'setFieldValue' + }, + specialkey: 'onEnterPressed' + } + }] }, { - xtype: 'textfield', - required: true, - hidden: true, - emptyText: Ngcp.csc.locales.pbxconfig.enter_new_device[localStorage.getItem('languageSelected')], - bind: { - id: 'devices-textfield-device-{record.id}' + name: 'device', + defaults: { + padding: '0 0 15 0' }, - listeners: { - focus: { - fn: 'setFieldValue' + items: [{ + xtype: 'label', + cls: 'pbx-data-value', + text: Ngcp.csc.locales.pbxconfig.device[localStorage.getItem('languageSelected')], + width: 120 + }, { + xtype: 'label', + hidden: false, + bind: { + id: 'devices-label-device-{record.id}', + text: '{record.device}' + } + }, { + xtype: 'combo', + required: true, + autoSelect: false, + hidden: true, + emptyText: Ngcp.csc.locales.pbxconfig.enter_new_device[localStorage.getItem('languageSelected')], + bind: { + id: 'devices-textfield-device-{record.id}' }, - specialkey: 'onEnterPressed' - } - }] - }, { - name: 'mac', - defaults: { - padding: '0 0 15 0' - }, - items: [{ - xtype: 'label', - cls: 'pbx-data-value', - text: Ngcp.csc.locales.pbxconfig.mac[localStorage.getItem('languageSelected')], - width: 120 + listeners: { + focus: 'setFieldValue', + specialkey: 'onEnterPressed', + select: 'deviceSelected' + }, + store: ['Cisco Pbx 1', 'Cisco Pbx 2', 'Cisco Pbx 3'] + }] }, { - xtype: 'label', - hidden: false, - bind: { - id: 'devices-label-mac-{record.id}', - text: '{record.mac}' - } + name: 'extension', + defaults: { + padding: '0 0 15 0' + }, + items: [{ + xtype: 'label', + cls: 'pbx-data-value', + text: Ngcp.csc.locales.pbxconfig.extension[localStorage.getItem('languageSelected')] + " 1:", + width: 120 + }, { + xtype: 'label', + hidden: false, + bind: { + id: 'devices-label-extension-{record.id}', + text: '{record.extension}' + } + }, { + xtype: 'combo', + required: true, + editable: false, + _skipSaveValidation: true, + hidden: true, + emptyText: Ngcp.csc.locales.pbxconfig.enter_new_extension[localStorage.getItem('languageSelected')], + bind: { + id: 'devices-textfield-extension-{record.id}' + }, + listeners: { + focus: { + fn: 'setFieldValue' + }, + specialkey: 'onEnterPressed' + }, + store: ['Ext1', 'Ext2', 'Ext3'] + }] }, { - xtype: 'textfield', - required: true, - hidden: true, - emptyText: Ngcp.csc.locales.pbxconfig.enter_new_mac_address[localStorage.getItem('languageSelected')], - bind: { - id: 'devices-textfield-mac-{record.id}' + name: 'extension2', + editable: false, + defaults: { + padding: '0 0 15 0' }, - listeners: { - focus: { - fn: 'setFieldValue' + items: [{ + xtype: 'label', + cls: 'pbx-data-value', + text: Ngcp.csc.locales.pbxconfig.extension[localStorage.getItem('languageSelected')] + " 2:", + width: 120 + }, { + xtype: 'label', + hidden: false, + bind: { + id: 'devices-label-extension2-{record.id}', + text: '{record.extension2}' + } + }, { + xtype: 'combo', + _skipSaveValidation: true, + required: true, + hidden: true, + emptyText: Ngcp.csc.locales.pbxconfig.enter_new_extension[localStorage.getItem('languageSelected')], + bind: { + id: 'devices-textfield-extension2-{record.id}' }, - specialkey: 'onEnterPressed' - } - }] - }, { - name: 'status', - defaults: { - padding: '0 0 15 0' + listeners: { + focus: { + fn: 'setFieldValue' + }, + specialkey: 'onEnterPressed' + }, + store: ['Ext1', 'Ext2', 'Ext3'] + }] }, - items: [{ - xtype: 'label', - cls: 'pbx-data-value', - text: Ngcp.csc.locales.pbxconfig.status[localStorage.getItem('languageSelected')], - width: 120 + + + { + xtype: 'panel', + layout: { + type: 'hbox' + }, + defaults: { + margin: 20 + }, + items: [{ + cls: 'device-img', + xtype: 'label', + width: 500, + height: 500, + bind: { + html: '{record.imageWithButtons}', + hidden: '{!record.image}' + } + }, { + xtype: 'fieldset', + width: 200, + margin: '200 0 0 0', + collapsible: false, + hidden: true, + defaults: { + xtype: 'label', + labelWidth: 50, + anchor: '100%', + }, + bind: { + id: 'seat-show-panel-{record.id}' + }, + items: [{ + name: 'typeValue' + }, { + xtype: 'buttongroup', + name: 'editBtns', + columns: 3, + hidden: true, + cls: 'edit-panel', + border: 0, + defaults: { + width: 75 + }, + items: [{ + iconCls: Ngcp.csc.icons.edit, + handler: 'editSeat', + tooltip: Ngcp.csc.locales.common.edit[localStorage.getItem('languageSelected')] + }, { + iconCls: Ngcp.csc.icons.trash, + handler: 'deleteSeat', + name: 'deleteBtn', + tooltip: Ngcp.csc.locales.common.delete[localStorage.getItem('languageSelected')] + }] + }] + }, { + xtype: 'fieldset', + margin: '200 0 0 0', + cls: 'devices-seat-fieldset', + width: 200, + collapsible: false, + hidden: true, + defaults: { + labelWidth: 40, + anchor: '100%', + }, + bind: { + id: 'seat-edit-panel-{record.id}' + }, + items: [{ + xtype: 'combo', + editable: false, + name: 'typeValue', + _skipSaveValidation: true, + fieldLabel: 'Type', + store: ['Shared', 'Speed dial', 'Busy lamp', 'Private'], + allowBlank: false + }, { + xtype: 'combo', + editable: false, + _skipSaveValidation: true, + store: ['User1', 'User2', 'User3', 'User4'], + name: 'seat', + fieldLabel: 'User', + allowBlank: false + }, { + xtype: 'buttongroup', + name: 'editBtns', + columns: 3, + cls: 'edit-panel', + border: 0, + defaults: { + width: 75 + }, + items: [{ + iconCls: Ngcp.csc.icons.floppy, + handler: 'saveSeat', + tooltip: Ngcp.csc.locales.common.save[localStorage.getItem('languageSelected')] + }, { + iconCls: Ngcp.csc.icons.block, + handler: 'discardChanges', + tooltip: Ngcp.csc.locales.common.reset[localStorage.getItem('languageSelected')] + }] + }] + }] }, { xtype: 'label', - hidden: false, bind: { - id: 'devices-label-status-{record.id}', - text: '{record.status}' - } - }, { - xtype: 'textfield', - required: true, - hidden: true, - emptyText: Ngcp.csc.locales.pbxconfig.enter_new_status[localStorage.getItem('languageSelected')], - bind: { - id: 'devices-textfield-status-{record.id}' - }, - listeners: { - focus: { - fn: 'setFieldValue' - }, - specialkey: 'onEnterPressed' + html: '
' + + '
' + + '
' + + '' + + '
' } - }] - }, { - xtype: 'label', - bind: { - html: '
' + - '
' + - '
' + - '' + - '
' } - }] + ] } }] }); diff --git a/classic/src/view/pages/pbxconfig/devices/destinations/DestinationsGrid.js b/classic/src/view/pages/pbxconfig/devices/destinations/DestinationsGrid.js deleted file mode 100644 index 56876856..00000000 --- a/classic/src/view/pages/pbxconfig/devices/destinations/DestinationsGrid.js +++ /dev/null @@ -1,39 +0,0 @@ -Ext.define('NgcpCsc.view.pages.devices.DestinationsGrid', { - - extend: 'Ext.grid.Panel', - - xtype: 'destinations-grid', - - reference: 'destinationsGrid', - - store: 'Destinations', - - rowLines: false, - - viewConfig: { - stripeRows: false, - columnLines: false - }, - - plugins: { - ptype: 'cellediting', - clicksToEdit: 1 - }, - - columns: { - defaults: { - menuDisabled: true, - resizable: false - }, - items: [{ - flex: 1, - dataIndex: 'position', - text: '#' - },{ - flex: 5, - dataIndex: 'destination', - editor: 'textfield', - text: Ngcp.csc.locales.pbxconfig.destination[localStorage.getItem('languageSelected')] - }] - } -}) diff --git a/resources/data/devices.json b/resources/data/devices.json index ed5d8846..524f2b97 100644 --- a/resources/data/devices.json +++ b/resources/data/devices.json @@ -1,146 +1,189 @@ { "data": [{ "id": 1, - "name":"Device1", + "name": "Device1", "device": "Cisco Pbx 1", - "mac":"00-14-22-01-23-41", - "status": "disabled", - "image": "/resources/images/pbx.png", - "destinations": [{ - "name": "destination1", - "sound": "resources/audio/voicemail.mp3", + "mac": "00-14-22-01-23-41", + "extension": "Ext2", + "extension2": "Ext1", + "image": "/resources/images/cisco1.jpg", + "seats": [{ + "name": "User1 (130)", + "type": "Shared", "order": 1, "position": { - "top": "14%", - "left": "45%" + "top": "12%", + "left": "52%", + "anchor": "right" } }, { - "name": "destination2", - "sound": "resources/audio/voicemail.mp3", "order": 2, "position": { - "top": "18%", - "left": "45%" + "top": "16%", + "left": "52%", + "anchor": "right" } }, { - "name": "destination3", - "sound": "resources/audio/voicemail.mp3", + "name": "User3", "order": 3, + "type": "Speed dial", "position": { - "top": "22%", - "left": "45%" + "top": "19%", + "left": "52%", + "anchor": "right" } }, { - "name": "destination4", - "sound": "resources/audio/voicemail.mp3", + "name": "User4", "order": 4, + "type": "Shared", "position": { - "top": "41%", - "left": "54%" + "top": "30%", + "left": "60%", + "anchor": "top" } }, { - "name": "destination5", - "sound": "resources/audio/voicemail.mp3", + "name": "User5", "order": 5, + "type": "Speed dial", "position": { - "top": "41%", - "left": "61%" + "top": "30%", + "left": "67%", + "anchor": "top" } }, { - "name": "destination6", - "sound": "resources/audio/voicemail.mp3", + "name": "User6", "order": 6, + "type": "Busy lamp", "position": { - "top": "41%", - "left": "67%" + "top": "30%", + "left": "74%", + "anchor": "top" } }] }, { "id": 2, - "name":"Device2", + "name": "Device2", "device": "Cisco Pbx 2", - "mac":"00-14-22-01-23-42", - "status": "enabled", + "mac": "00-14-22-01-23-42", + "extension": "Ext2", + "extension2": "Ext1", "image": "/resources/images/cisco2.jpg", - "destinations": [{ - "name": "destination1", - "sound": "resources/audio/voicemail.mp3", + "seats": [{ + "name": "User1", "order": 1, + "type": "Busy lamp", "position": { - "top": "13%", - "left": "86%" + "top": "15%", + "left": "90%", + "anchor": "left" } }, { - "name": "destination2", - "sound": "resources/audio/voicemail.mp3", + "name": "User2", "order": 2, + "type": "Speed dial", "position": { - "top": "17%", - "left": "86%" + "top": "18%", + "left": "90%", + "anchor": "left" } }, { - "name": "destination3", - "sound": "resources/audio/voicemail.mp3", + "name": "User3", "order": 3, + "type": "Shared", "position": { - "top": "21%", - "left": "86%" + "top": "22%", + "left": "90%", + "anchor": "left" } }] }, { "id": 3, - "name":"Device3", + "name": "Device3", "device": "Cisco Pbx 3", - "mac":"00-14-22-01-23-43", - "status": "enabled", + "mac": "00-14-22-01-23-43", + "extension": "Ext2", + "extension2": "Ext1", "image": "/resources/images/cisco3.jpg", - "destinations": [{ - "name": "destination1", - "sound": "resources/audio/voicemail.mp3", + "seats": [{ + "name": "User1", "order": 1, + "type": "Busy lamp", "position": { - "top": "34%", - "left": "56%" + "top": "35%", + "left": "59%", + "anchor": "top" } }, { - "name": "destination2", - "sound": "resources/audio/voicemail.mp3", + "name": "User2", "order": 2, + "type": "Shared", "position": { - "top": "34%", - "left": "83%" + "top": "35%", + "left": "81%", + "anchor": "top" } }] }, { "id": 4, - "name":"Device4", - "device": "Cisco Pbx 4", - "mac":"00-14-22-01-23-44", - "status": "enabled", + "name": "Device4", + "device": "Cisco Pbx 1", + "mac": "00-14-22-01-23-44", + "extension": "Ext2", + "extension2": "Ext1", "image": "/resources/images/cisco1.jpg", - "destinations": [{ - "name": "destination1", - "sound": "resources/audio/voicemail.mp3", + "seats": [{ + "name": "User1", + "type": "Shared", "order": 1, "position": { - "top": "11%", - "left": "49%" + "top": "12%", + "left": "52%", + "anchor": "right" } }, { - "name": "destination2", - "sound": "resources/audio/voicemail.mp3", + "name": "User2", "order": 2, + "type": "Busy lamp", "position": { - "top": "22%", - "left": "49%" + "top": "16%", + "left": "52%", + "anchor": "right" } }, { - "name": "destination3", - "sound": "resources/audio/voicemail.mp3", + "name": "User3", "order": 3, + "type": "Speed dial", + "position": { + "top": "19%", + "left": "52%", + "anchor": "right" + } + }, { + "name": "User4", + "order": 4, + "type": "Shared", + "position": { + "top": "30%", + "left": "60%", + "anchor": "top" + } + }, { + "name": "User5", + "order": 5, + "type": "Speed dial", + "position": { + "top": "30%", + "left": "67%", + "anchor": "top" + } + }, { + "name": "User6", + "order": 6, + "type": "Busy lamp", "position": { - "top": "38%", - "left": "67%" + "top": "30%", + "left": "74%", + "anchor": "top" } }] }] diff --git a/resources/data/deviceslist.json b/resources/data/deviceslist.json index cb3f4bed..f42e318a 100644 --- a/resources/data/deviceslist.json +++ b/resources/data/deviceslist.json @@ -2,133 +2,115 @@ "data": [{ "id": 1, "name": "Cisco Pbx 1", - "image": "/resources/images/pbx.png", - "destinations": [{ - "name": "destination1", - "sound": "resources/audio/voicemail.mp3", + "image": "/resources/images/cisco1.jpg", + "seats": [{ + "name": "User1", + "type": "Shared", "order": 1, "position": { - "top": "14%", - "left": "45%" + "top": "12%", + "left": "52%", + "anchor": "right" } }, { - "name": "destination2", - "sound": "resources/audio/voicemail.mp3", + "name": "User2", "order": 2, + "type": "Busy lamp", "position": { - "top": "18%", - "left": "45%" + "top": "16%", + "left": "52%", + "anchor": "right" } }, { - "name": "destination3", - "sound": "resources/audio/voicemail.mp3", + "name": "User3", "order": 3, + "type": "Speed dial", "position": { - "top": "22%", - "left": "45%" + "top": "19%", + "left": "52%", + "anchor": "right" } }, { - "name": "destination4", - "sound": "resources/audio/voicemail.mp3", + "name": "User4", "order": 4, + "type": "Shared", "position": { - "top": "41%", - "left": "54%" + "top": "30%", + "left": "60%", + "anchor": "top" } }, { - "name": "destination5", - "sound": "resources/audio/voicemail.mp3", + "name": "User5", "order": 5, + "type": "Speed dial", "position": { - "top": "41%", - "left": "61%" + "top": "30%", + "left": "67%", + "anchor": "top" } }, { - "name": "destination6", - "sound": "resources/audio/voicemail.mp3", + "name": "User6", "order": 6, + "type": "Busy lamp", "position": { - "top": "41%", - "left": "67%" + "top": "30%", + "left": "74%", + "anchor": "top" } }] }, { "id": 2, "name": "Cisco Pbx 2", "image": "/resources/images/cisco2.jpg", - "destinations": [{ - "name": "destination1", - "sound": "resources/audio/voicemail.mp3", + "seats": [{ + "name": "User1", "order": 1, + "type": "Busy lamp", "position": { - "top": "13%", - "left": "86%" + "top": "15%", + "left": "90%", + "anchor": "left" } }, { - "name": "destination2", - "sound": "resources/audio/voicemail.mp3", + "name": "User2", "order": 2, + "type": "Speed dial", "position": { - "top": "17%", - "left": "86%" + "top": "18%", + "left": "90%", + "anchor": "left" } }, { - "name": "destination3", - "sound": "resources/audio/voicemail.mp3", + "name": "User3", "order": 3, + "type": "Shared", "position": { - "top": "21%", - "left": "86%" + "top": "22%", + "left": "90%", + "anchor": "left" } }] }, { "id": 3, "name": "Cisco Pbx 3", "image": "/resources/images/cisco3.jpg", - "destinations": [{ - "name": "destination1", - "sound": "resources/audio/voicemail.mp3", - "order": 1, - "position": { - "top": "34%", - "left": "56%" - } - }, { - "name": "destination2", - "sound": "resources/audio/voicemail.mp3", - "order": 2, - "position": { - "top": "34%", - "left": "83%" - } - }] - }, { - "id": 4, - "name": "Cisco Pbx 4", - "image": "/resources/images/cisco1.jpg", - "destinations": [{ - "name": "destination1", - "sound": "resources/audio/voicemail.mp3", + "seats": [{ + "name": "User1", "order": 1, + "type": "Busy lamp", "position": { - "top": "11%", - "left": "49%" + "top": "35%", + "left": "59%", + "anchor": "top" } }, { - "name": "destination2", - "sound": "resources/audio/voicemail.mp3", + "name": "User2", "order": 2, + "type": "Shared", "position": { - "top": "22%", - "left": "49%" - } - }, { - "name": "destination3", - "sound": "resources/audio/voicemail.mp3", - "order": 3, - "position": { - "top": "38%", - "left": "67%" + "top": "35%", + "left": "81%", + "anchor": "top" } }] }]