TT#37302 PBXConfig: As a Customer, I want to assign groups or seats to a PBXDevice button

- Implement api method to change the type
- Implement overlay containing key details
- Implement store actions/mutations/getters
- Implement api method to assign a PBXGroup/PBXSeat to a specific key
- Implement component methods in CscPbxDeviceConfig to assign PBXGroup/PBXSeat

Change-Id: I883809abba0e9848f09e59e0332b0659c2fadca7
changes/74/21674/2
Hans-Peter Herzog 7 years ago
parent 3f026fae89
commit 93dff5b5a3

@ -75,3 +75,27 @@ export function get(options) {
});
});
}
export function patchReplace(options) {
return new Promise((resolve, reject)=>{
Vue.http.patch(options.path, [{
op: 'replace',
path: '/'+ options.fieldPath,
value: options.value
}], {
headers: {
'Content-Type': 'application/json-patch+json',
'Prefer': 'return=minimal'
}
}).then((result)=>{
resolve(result);
}).catch((err)=>{
if(err.status >= 400) {
reject(new Error(err.body.message));
}
else {
reject(err);
}
});
});
}

@ -6,7 +6,7 @@ import { createSubscriber, deleteSubscriber, setDisplayName,
setPbxExtension, setPbxHuntPolicy, setPbxHuntTimeout,
setPbxGroupMemberIds, setPbxGroupIds, getSubscribers, getSubscriber } from './subscriber';
import uuid from 'uuid';
import { getList, get } from './common'
import { getList, get, patchReplace } from './common'
var createId = uuid.v4;
@ -46,6 +46,23 @@ export function getSeats(options) {
});
}
export function getAllGroupsAndSeats(options) {
return new Promise((resolve, reject)=>{
options = options || {};
options = _.merge(options, {
params: {
all: true,
is_pbx_pilot: 0
}
});
getSubscribers(options).then((res)=>{
resolve(res);
}).catch((err)=>{
reject(err);
});
});
}
export function getPilot(options) {
return new Promise((resolve, reject)=>{
options = options || {};
@ -269,13 +286,19 @@ export function updateSeatGroups(id, seatIds) {
return setPbxGroupIds(id, seatIds);
}
export function getDeviceFull(id) {
return getDevice(id, true);
export function getDeviceFull(id, options) {
options = options || {};
options.join = true;
options.joinLines = true;
return getDevice(id, options);
}
export function getDevice(id, join) {
export function getDevice(id, options) {
return new Promise((resolve, reject)=>{
let device;
options = options || {};
let device = null;
let join = _.get(options, 'join', false);
let joinLines = _.get(options, 'joinLines', false);
Promise.resolve().then(()=>{
return get({
path: 'api/pbxdevices/' + id
@ -286,7 +309,7 @@ export function getDevice(id, join) {
let requests = [
getProfile(device.profile_id, join)
];
if(_.isArray(device.lines) && device.lines.length > 0) {
if(joinLines === true && _.isArray(device.lines) && device.lines.length > 0) {
device.lines.forEach((line)=>{
requests.push(getSubscriber(line.subscriber_id));
});
@ -382,3 +405,12 @@ export function removeDevice(id) {
});
});
}
export function updateDeviceKeys(deviceId, keys) {
return patchReplace({
path: 'api/pbxdevices/' + deviceId,
fieldPath: 'lines',
value: keys
});
}

@ -5,7 +5,7 @@
<img :src="frontImageUrl" />
</q-item-tile>
</q-item-side>
<q-item-main>
<q-item-main :style="{zIndex: 10}">
<q-item-tile v-if="!expanded" label>{{ device.station_name }}</q-item-tile>
<q-item-tile v-if="!expanded" sublabel><span class="gt-sm">Model: </span>{{ name }}</q-item-tile>
<q-item-tile v-if="!expanded" sublabel><span class="gt-sm">MAC address: </span>{{ device.identifier }}</q-item-tile>
@ -19,16 +19,18 @@
<q-field :label="$t('pbxConfig.deviceModel')">
<p>{{ name }}</p>
</q-field>
<csc-pbx-device-config :device="device" />
<csc-pbx-device-config :device="device" :groupsAndSeatsOptions="groupsAndSeatsOptions" :loading="loading"
@loadGroupsAndSeats="loadGroupsAndSeats()" @keysChanged="keysChanged"
:subscribers="subscribers" />
</q-item-tile>
</q-item-main>
<q-item-side right class="csc-item-buttons">
<q-item-side right class="csc-item-buttons" :style="{zIndex: 11}">
<q-item-tile>
<q-btn :icon="titleIcon" :big="isMobile" color="primary" slot="right" flat @click="toggleMain()" />
<q-btn icon="delete" :big="isMobile" color="negative" slot="right" flat @click="remove()" />
</q-item-tile>
</q-item-side>
<q-inner-loading :visible="loading">
<q-inner-loading v-if="loading" :visible="loading" :style="{zIndex: 12}">
<q-spinner-mat size="60px" color="primary"></q-spinner-mat>
</q-inner-loading>
</q-item>
@ -47,7 +49,9 @@
props: [
'device',
'loading',
'modelOptions'
'modelOptions',
'groupsAndSeatsOptions',
'subscribers'
],
components: {
CscPbxDeviceConfig, QCard, QCardTitle, QCardMain, QCollapsible,
@ -91,6 +95,15 @@
},
remove() {
this.$emit('remove', this.device);
},
loadGroupsAndSeats(){
this.$emit('loadGroupsAndSeats');
},
keysChanged(keys) {
this.$emit('deviceKeysChanged', {
device: this.device,
keys: keys
});
}
}
}

@ -4,22 +4,28 @@
<div ref="imageWrapper" class="csc-pbx-device-image" :style="imageWrapperStyles">
<img ref="image" :src="imageUrl" @load="imageLoaded" :style="imageStyles" />
</div>
<div :class="spotClasses(index)" v-for="(key,index) in keys"
:key="index" :style="spotPosition(key)">
<span>{{ index + 1 }}</span>
<q-popover ref="keyPopover">
<div class="csc-pbx-key-popover">
<q-field v-if="getLineByKey(index) !== null" :icon="getIconByKey(index)">
<q-input :value="getLineByKey(index).subscriber.display_name" readonly :float-label="'Key ' + (index + 1)" />
</q-field>
<q-field v-else >
<q-input value="Empty" readonly :float-label="'Key ' + (index + 1)" />
</q-field>
<q-btn icon="clear" :small="!isMobile" class="absolute-top-right"
@click="$refs.keyPopover[index].close()" flat round color="primary" />
</div>
</q-popover>
<div :class="spotClasses(index)" v-for="(key, index) in keys" :key="index"
:style="spotPosition(key)" @click="openKeyOverlay(key, index)">{{ index + 1 }}</div>
</div>
<div v-show="keyOverlayActive" class="csc-pbx-device-config-key-overlay animate-fade">
<div class="title">
<q-icon name="touch_app" size="32px"/>Key {{ selectedKeyIndex + 1 }}
</div>
<q-field :label="selectedKeyLabel" :icon="selectedKeyIcon">
<q-select ref="selectSubscriber" :value="selectedKeySubscriber" :options="groupsAndSeatsOptions"
@change="keySubscriberChanged" />
</q-field>
<q-field label="Type">
<q-select ref="selectType" v-model="selectedKeyType" :options="typeOptions"
@change="keyTypeChanged" />
</q-field>
<div class="row justify-center actions">
<div class="column">
<q-btn icon="clear" :small="!isMobile" @click="closeKeyOverlay()" flat color="negative">Close</q-btn>
</div>
</div>
<q-btn icon="clear" :small="!isMobile" class="absolute-top-right"
@click="closeKeyOverlay()" flat round color="primary" />
</div>
<q-window-resize-observable @resize="windowResize" />
</div>
@ -28,7 +34,7 @@
<script>
import _ from 'lodash'
import { QList, QItem, QItemMain, QItemTile, QTabs, QTab, Platform,
import { QList, QItem, QItemMain, QItemTile, QTabs, QTab, Platform, QSelect, QInnerLoading, QSpinnerMat,
QTabPane, QChip, QWindowResizeObservable, QModal, QBtn, QPopover, QIcon, QField, QInput } from 'quasar-framework'
import { BoundingBox2D } from '../../../helpers/graphics'
@ -37,9 +43,11 @@
props: [
'device',
'loading',
'subscribers',
'groupsAndSeatsOptions'
],
components: {
QList, QItem, QItemMain, QItemTile, QTabs, QTab, Platform,
QList, QItem, QItemMain, QItemTile, QTabs, QTab, Platform, QSelect, QInnerLoading, QSpinnerMat,
QTabPane, QChip, QWindowResizeObservable, QModal, QBtn, QPopover, QIcon, QField, QInput
},
data () {
@ -53,10 +61,76 @@
modalOpened: false,
selectedKey: null,
selectedKeyIndex: null,
selectedLine: null
selectedLine: null,
keyOverlayActive: false,
selectedKeyTypeData: null,
selectedLineIndex: null
}
},
computed: {
selectedKeyIcon() {
if(this.selectedLine !== null) {
let subscriber = this.subscribers(this.selectedLine.subscriber_id);
if(subscriber !== null && subscriber.is_pbx_group === true) {
return 'group';
}
else if (subscriber !== null){
return 'person';
}
else {
return '';
}
}
return '';
},
selectedKeyLabel() {
if(this.selectedLine !== null) {
let subscriber = this.subscribers(this.selectedLine.subscriber_id);
if(subscriber !== null && subscriber.is_pbx_group === true) {
return this.$t('pbxConfig.keyGroupLabel');
}
else if (subscriber !== null){
return this.$t('pbxConfig.keySeatLabel');
}
else {
return this.$t('pbxConfig.keyBothLabel');
}
}
return this.$t('pbxConfig.keyBothLabel');
},
selectedKeySubscriber() {
if(this.selectedLine !== null) {
return this.selectedLine.subscriber_id;
}
return null;
},
selectedKeyType: {
get() {
if(this.selectedLine !== null) {
return this.selectedLine.type;
}
return 'private';
},
set(type) {
this.selectedKeyTypeData = type;
}
},
typeOptions() {
return [
{
label: this.$t('pbxConfig.keyTypeShared'),
value: 'shared'
},
{
label: this.$t('pbxConfig.keyTypeBLF'),
value: 'blf'
},
{
label: this.$t('pbxConfig.keyTypePrivate'),
value: 'private'
}
];
},
isMobile() {
return Platform.is.mobile;
},
@ -86,28 +160,31 @@
mounted() {
this.boundingBox = BoundingBox2D.createFromPoints(this.keys);
this.boundingBox.addMargin(40);
this.loadGroupsAndSeats();
},
methods: {
getLineIndexByKey(keyIndex) {
let lineIndex = null;
let lines = _.get(this.device, 'lines', []);
if(lines.length > 0) {
lines.forEach(($line, index)=>{
if($line.key_num === keyIndex) {
lineIndex = index;
}
});
}
return lineIndex;
},
getLineByKey(key) {
let line = null;
this.device.lines.forEach(($line)=>{
if($line.key_num === key) {
line = $line;
}
});
return line;
},
openModal(key, index) {
this.$refs.modal.open();
this.selectedKey = key;
this.selectedKeyIndex = index + 1;
this.selectedLine = this.getLineByKey(index);
},
closeModal() {
this.$refs.modal.close();
this.selectedKey = null;
this.selectedKeyIndex = null;
},
windowResize() {
this.resize();
this.placeImage();
@ -199,18 +276,90 @@
}
return classes;
},
getIconByKey(key) {
let line = this.getLineByKey(key);
if(line !== null && line.subscriber.is_pbx_group === true) {
return 'group';
subscriberChanged(subscriberId) {
let clonedKey = _.clone(this.selectedKey);
clonedKey.subscriber_id = subscriberId;
this.$emit('keyChanged', {
key: clonedKey,
keyIndex: this.selectedKeyIndex
});
},
openKeyOverlay(key, index) {
this.selectedKey = key;
this.selectedKeyIndex = index;
this.selectedLine = this.getLineByKey(index);
this.keyOverlayActive = true;
},
closeKeyOverlay() {
this.keyOverlayActive = false;
},
loadGroupsAndSeats() {
this.$emit('loadGroupsAndSeats');
},
keySubscriberChanged(subscriberId) {
let newLines = [];
let lines = _.clone(_.get(this.device, 'lines', []));
let lineIndex = this.getLineIndexByKey(this.selectedKeyIndex);
let changed = false;
if(_.isNumber(lineIndex) && lineIndex < lines.length && subscriberId === null) {
delete lines[lineIndex];
changed = true;
}
else if(line !== null && line.subscriber.is_pbx_group === false) {
return 'person';
else if(_.isNumber(lineIndex) && lineIndex < lines.length) {
_.set(lines, lineIndex + '.subscriber_id', subscriberId);
changed = true;
}
else {
return ''
else if(subscriberId !== null) {
newLines.push({
extension_unit: 0,
key_num: this.selectedKeyIndex,
subscriber_id: subscriberId,
linerange: _.get(this.device, 'profile.model.linerange.0.name'),
type: this.$refs.selectType.value
});
changed = true;
}
lines.forEach((line)=>{
newLines.push({
extension_unit: line.extension_unit,
key_num: line.key_num,
subscriber_id: line.subscriber_id,
linerange: line.linerange,
type: line.type
});
});
if(changed === true && newLines.length > 0) {
this.$emit('keysChanged', newLines);
}
},
keyTypeChanged(type) {
let newLines = [];
let lines = _.clone(_.get(this.device, 'lines', []));
let lineIndex = this.getLineIndexByKey(this.selectedKeyIndex);
let changed = false;
if(_.isNumber(lineIndex) && _.isObject(lines[lineIndex])) {
_.set(lines, lineIndex + '.type', type);
changed = true;
}
if(changed === true) {
lines.forEach((line)=>{
newLines.push({
extension_unit: line.extension_unit,
key_num: line.key_num,
subscriber_id: line.subscriber_id,
linerange: line.linerange,
type: line.type
});
});
this.$emit('keysChanged', newLines);
}
}
},
watch: {
device() {
this.openKeyOverlay(this.selectedKey, this.selectedKeyIndex);
this.$forceUpdate();
}
}
}
</script>
@ -219,6 +368,27 @@
@import '../../../themes/quasar.variables';
$spotSize = 25px
.csc-pbx-device-config-key-overlay
.title
.q-icon
margin-right 8px
font-size 18px
font-weight 400
letter-spacing normal
line-height 1.8rem
margin-bottom 32px
text-align center
position absolute
top 0
left 0
right 0
bottom 0
background-color rgba(250,250,250,0.95)
z-index 10
padding 48px
.csc-pbx-device-key-details
padding 50px
position relative
@ -227,6 +397,8 @@
position relative
.spot-modal-content
position relative
.actions
padding 32px
.csc-pbx-device-image
position relative
@ -244,6 +416,9 @@
letter-spacing normal
line-height 1.8rem
.csc-pbx-device-loader
z-index 20
.csc-pbx-device-button-spot
border-radius: 50%;
width $spotSize

@ -9,7 +9,9 @@
<q-list no-border separator sparse multiline>
<q-item> </q-item>
<csc-pbx-device v-for="device in devices" :key="device.id" :device="device" @remove="removeDevice"
:modelOptions="modelOptions" :loading="isDeviceLoading(device.id)" />
:modelOptions="modelOptions" :loading="isDeviceLoading(device.id)"
:groupsAndSeatsOptions="groupsAndSeatsOptions" :subscribers="getGroupOrSeatById"
@loadGroupsAndSeats="loadGroupsAndSeats()" @deviceKeysChanged="deviceKeysChanged" />
</q-list>
<div v-if="devices.length === 0 && !isListRequesting" class="row justify-center csc-no-entities">
{{ $t('pbxConfig.noDevices') }}
@ -52,7 +54,11 @@
'listCurrentPage',
'listLastPage',
'isDeviceLoading',
'deviceRemoved'
'deviceRemoved',
'groupsAndSeatsOptions',
'groupsAndSeats',
'getGroupOrSeatById',
'updatedDeviceKey'
])
},
methods: {
@ -81,6 +87,12 @@
}
]
});
},
loadGroupsAndSeats() {
this.$store.dispatch('pbxConfig/getAllGroupsAndSeats');
},
deviceKeysChanged(data) {
this.$store.dispatch('pbxConfig/updateDeviceKeys', data);
}
},
watch: {
@ -90,6 +102,13 @@
name: device.station_name
}));
}
},
updatedDeviceKey(data) {
if(data !== null) {
showToast(this.$t('pbxConfig.toasts.updatedDeviceKeys',{
name: data.device.station_name
}));
}
}
}
}

@ -292,11 +292,19 @@
"removedGroupToast": "Removed group {group}",
"addedSeatToast": "Added seat {seat}",
"removedSeatToast": "Removed seat {seat}",
"removedDeviceToast": "Removed device {name}"
"removedDeviceToast": "Removed device {name}",
"updatedDeviceKeys": "Updated keys of device {name}"
},
"removeDevice": "Remove device",
"removeDeviceTitle": "Remove device",
"removeDeviceText": "You are about to remove device {device}"
"removeDeviceText": "You are about to remove device {device}",
"keyEmptyLabel": "Unassigned",
"keyGroupLabel": "Group",
"keySeatLabel": "Seat",
"keyBothLabel": "Group/Seat",
"keyTypeShared": "Shared",
"keyTypeBLF": "Busy Lamp Field",
"keyTypePrivate": "Private"
},
"communication": {
"sendFax": "Send Fax",

@ -3,9 +3,9 @@
import _ from 'lodash';
import { assignNumbers } from '../../api/user';
import { addGroup, removeGroup, addSeat, removeSeat, setGroupName,
setGroupExtension, setGroupHuntPolicy, setGroupHuntTimeout,
updateGroupSeats, setSeatName, setSeatExtension, removeDevice,
updateSeatGroups, getGroupList, getSeatList, getDeviceList, getDeviceFull } from '../../api/pbx-config'
setGroupExtension, setGroupHuntPolicy, setGroupHuntTimeout, updateDeviceKeys,
updateGroupSeats, setSeatName, setSeatExtension, removeDevice, getAllGroupsAndSeats,
updateSeatGroups, getGroupList, getSeatList, getDeviceList, getDevice } from '../../api/pbx-config'
export default {
listGroups(context, options) {
@ -204,7 +204,10 @@ export default {
},
loadDevice(context, deviceId) {
context.commit('deviceRequesting', deviceId);
getDeviceFull(deviceId).then((device)=>{
getDevice(deviceId, {
join: true,
joinLines: false,
}).then((device)=>{
context.commit('deviceSucceeded', device);
}).catch((err)=>{
context.commit('deviceFailed', deviceId, err.message);
@ -218,5 +221,22 @@ export default {
}).catch((err)=>{
context.commit('deviceFailed', device.id, err.message);
});
},
getAllGroupsAndSeats(context) {
context.commit('groupsAndSeatsRequesting');
getAllGroupsAndSeats().then((list)=>{
context.commit('groupsAndSeatsSucceeded', list);
}).catch((err)=>{
context.commit('groupsAndSeatsError', err.message);
});
},
updateDeviceKeys(context, data) {
context.commit('updateDeviceKeyRequesting', data.device.id);
updateDeviceKeys(data.device.id, data.keys).then(()=>{
context.commit('updateDeviceKeySucceeded', data);
context.dispatch('loadDevice', data.device.id);
}).catch((err)=>{
context.commit('updateDeviceKeyFailed', data.device.id, err);
});
}
}

@ -2,6 +2,7 @@
import _ from 'lodash'
import { RequestState } from '../common'
import { i18n } from '../../i18n';
export default {
groups(state) {
@ -177,5 +178,39 @@ export default {
},
deviceRemoved(state) {
return state.deviceRemoved;
},
groupsAndSeats(state) {
return state.groupsAndSeats;
},
groupsAndSeatsOptions(state) {
let options = [
{
icon: 'clear',
label: i18n.t('pbxConfig.keyEmptyLabel'),
value: null
}
];
state.groupsAndSeats.forEach((item)=>{
options.push({
icon: (item.is_pbx_group === true)? 'group' : 'person',
label: item.display_name,
value: item.id
});
});
return options;
},
getGroupOrSeatById(state) {
return (id)=>{
let groupOrSeat = null;
state.groupsAndSeats.forEach(($groupOrSeat)=>{
if(id === $groupOrSeat.id) {
groupOrSeat = $groupOrSeat;
}
});
return groupOrSeat;
};
},
updatedDeviceKey(state) {
return state.updatedDeviceKey;
}
}

@ -201,5 +201,30 @@ export default {
},
lastUpdatedField(state, group) {
state.lastUpdatedField = group;
},
groupsAndSeatsRequesting(state) {
state.groupsAndSeatsState = RequestState.requesting;
state.groupsAndSeats = [];
},
groupsAndSeatsSucceeded(state, list) {
state.groupsAndSeatsState = RequestState.succeeded;
state.groupsAndSeats = list.items;
},
groupsAndSeatsError(state, error) {
state.groupsAndSeatsState = RequestState.failed;
state.groupsAndSeatsError = error;
},
updateDeviceKeyRequesting(state, deviceId) {
Vue.set(state.deviceStates, deviceId + "", RequestState.requesting);
state.updatedDeviceKey = null;
},
updateDeviceKeySucceeded(state, data) {
Vue.set(state.deviceStates, data.device.id + "", RequestState.succeeded);
state.updatedDeviceKey = data;
},
updateDeviceKeyFailed(state, deviceId, error) {
Vue.set(state.deviceStates, deviceId + "", RequestState.failed);
Vue.set(state.deviceErrors, deviceId + "", error);
state.updatedDeviceKey = null;
}
}

@ -41,5 +41,9 @@ export default {
updateAliasNumbersState: RequestState.initiated,
updateAliasNumbersItem: null,
updateGroupsAndSeatsState: RequestState.initiated,
updateGroupsAndSeatsItem: null
updateGroupsAndSeatsItem: null,
groupsAndSeats: [],
groupsAndSeatsState: RequestState.initiated,
groupsAndSeatsError: null,
updatedDeviceKey: null
}

Loading…
Cancel
Save