TT#47384 Page to manage CallQueueConfiguration

What has been done:
- TT#47384, CallQueueConfig: As a PBXAdmin, I want to have a separate
  page to be able to manage CallQueueConfigurations
- TT#47385, CallQueueConfig: As a PBXAdmin, I want to see a list of all
  active CallQueueConfigurations

Change-Id: I6ed9b1ee4a1a8518b8124a4dc22fac1988b0bc11
changes/63/24763/8
raxelsen 7 years ago committed by Hans-Peter Herzog
parent b346008d75
commit 26224ec89d

@ -1,10 +1,23 @@
import _ from 'lodash'; import _ from 'lodash';
import Vue from 'vue'; import Vue from 'vue';
import { getNumbers, assignNumbers } from './user'; import {
import { createSubscriber, deleteSubscriber, setDisplayName, getNumbers,
setPbxExtension, setPbxHuntPolicy, setPbxHuntTimeout, assignNumbers
setPbxGroupMemberIds, setPbxGroupIds, getSubscribers, getSubscriber } from './subscriber'; } from './user';
import {
createSubscriber,
deleteSubscriber,
setDisplayName,
setPbxExtension,
setPbxHuntPolicy,
setPbxHuntTimeout,
setPbxGroupMemberIds,
setPbxGroupIds,
getSubscribers,
getSubscriber,
getSubscribersByCallQueueEnabled
} from './subscriber';
import uuid from 'uuid'; import uuid from 'uuid';
import { getList, get, patchReplace } from './common' import { getList, get, patchReplace } from './common'
@ -509,3 +522,23 @@ export function setProfile(deviceId, profileId) {
}); });
}); });
} }
export function getCallQueueConfigurations() {
return new Promise((resolve, reject)=>{
getSubscribersByCallQueueEnabled().then((subscribers)=>{
let callQueues = subscribers.map((subscriber)=>{
return {
display_name: _.get(subscriber, 'display_name', null),
is_pbx_group: _.get(subscriber, 'is_pbx_group', null),
max_queue_length: _.get(subscriber, 'prefs.max_queue_length', 5),
queue_wrap_up_time: _.get(subscriber, 'prefs.queue_wrap_up_time', 10)
};
});
resolve({
items: callQueues
});
}).catch((err)=>{
reject(err);
});
});
}

@ -303,3 +303,32 @@ export function blockAnonymous(id) {
export function allowAnonymous(id) { export function allowAnonymous(id) {
return setBlockAnonymous(id, false); return setBlockAnonymous(id, false);
} }
export function getSubscribersByCallQueueEnabled() {
return new Promise((resolve, reject)=>{
let prefsByCallQueueEnabled = [];
Promise.resolve().then(()=>{
return getList({
path: 'api/subscriberpreferences/',
root: '_embedded.ngcp:subscriberpreferences',
all: true
});
}).then((prefs)=>{
let subscriberPromises = [];
prefsByCallQueueEnabled = prefs.items.filter((pref)=>{
return pref.cloud_pbx_callqueue === true;
});
prefsByCallQueueEnabled.forEach((pref)=>{
subscriberPromises.push(getSubscriber(pref.subscriber_id));
});
return Promise.all(subscriberPromises);
}).then((subscribers)=>{
subscribers.forEach((subscriber, index)=>{
subscriber.prefs = prefsByCallQueueEnabled[index];
});
resolve(subscribers);
}).catch((err)=>{
reject(err);
});
});
}

@ -425,6 +425,10 @@
.q-item .q-item
padding-top $flex-gutter-xs * 1.4 padding-top $flex-gutter-xs * 1.4
padding-bottom $flex-gutter-xs * 1.4 padding-bottom $flex-gutter-xs * 1.4
.q-item-side-left
min-width auto
.q-item-main
margin-left $flex-gutter-sm
.q-icon .q-icon
color $main-menu-icon-color color $main-menu-icon-color
.q-item-label .q-item-label
@ -539,4 +543,8 @@
padding 0 padding 0
#csc-header #csc-header
left $main-menu-minimized-width left $main-menu-minimized-width
.mobile
.layout-aside-left
width auto
right 0
</style> </style>

@ -180,6 +180,17 @@
:label="$t('navigation.pbxConfiguration.devices')" :label="$t('navigation.pbxConfiguration.devices')"
/> />
</q-side-link> </q-side-link>
<q-side-link
item
to="/user/pbx-configuration/call-queues"
>
<q-item-side
icon="queue"
/>
<q-item-main
:label="$t('navigation.pbxConfiguration.callQueues')"
/>
</q-side-link>
</q-collapsible> </q-collapsible>
</q-list> </q-list>
</template> </template>

@ -0,0 +1,166 @@
<template>
<q-item
:class="itemClasses"
>
<q-item-side
v-if="!expanded"
>
<q-icon
size="24px"
name="queue"
color="white"
/>
</q-item-side>
<q-item-main>
<q-item-tile
v-if="!expanded"
class="csc-item-title"
label
>
<q-icon
v-if="subscriber.is_pbx_group"
size="24px"
name="group"
color="white"
/>
<q-icon
v-else
size="24px"
name="person"
color="white"
/>
<span class="csc-item-label">{{ subscriber.display_name }}</span>
</q-item-tile>
<q-item-tile
v-if="!expanded"
class="csc-item-subtitle"
sublabel
>
<span class="csc-item-label">{{ $t('pbxConfig.queueLength') }}:</span>
<span class="csc-item-value">{{ subscriber.max_queue_length }}</span>
</q-item-tile>
<q-item-tile
v-if="!expanded"
class="csc-item-subtitle"
sublabel
>
<span class="csc-item-label">{{ $t('pbxConfig.wrapUpTime') }}:</span>
<span class="csc-item-value">{{ subscriber.queue_wrap_up_time }}</span>
</q-item-tile>
<q-item-tile
class="csc-list-item-main"
v-if="expanded"
>
<q-field
:label="$t('pbxConfig.queueExtensionName')">
<q-input
dark
readonly
:value="subscriber.display_name"
/>
</q-field>
<q-field
:label="$t('pbxConfig.queueLength')">
<q-input
dark
readonly
:value="subscriber.max_queue_length"
/>
</q-field>
<q-field
:label="$t('pbxConfig.wrapUpTime')">
<q-input
dark
readonly
:value="subscriber.queue_wrap_up_time"
suffix="seconds"
/>
</q-field>
</q-item-tile>
</q-item-main>
<q-item-side
right
class="csc-list-actions-pinned"
>
<q-item-tile>
<q-btn
:icon="titleIcon"
:big="isMobile"
color="primary"
flat
@click="toggleMain()"
/>
</q-item-tile>
</q-item-side>
</q-item>
</template>
<script>
import {
QField,
QInput,
QIcon,
Platform,
QBtn,
QItem,
QItemSide,
QItemMain,
QItemTile
} from 'quasar-framework'
export default {
name: 'csc-pbx-call-queue',
props: [
'subscriber'
],
data () {
return {
expanded: false
}
},
components: {
QField,
QInput,
QIcon,
QBtn,
QItem,
QItemSide,
QItemMain,
QItemTile
},
computed: {
itemClasses() {
let classes = ['csc-list-item', 'csc-pbx-call-queue'];
if (this.expanded) {
classes.push('csc-item-expanded');
}
else {
classes.push('csc-item-collapsed');
}
return classes;
},
isMobile() {
return Platform.is.mobile;
},
titleIcon() {
if(!this.expanded) {
return 'keyboard arrow down';
}
else {
return 'keyboard arrow up';
}
}
},
methods: {
toggleMain() {
this.expanded = !this.expanded;
}
},
watch: {
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../../themes/quasar.variables';
</style>

@ -0,0 +1,98 @@
<template>
<csc-page
:is-list="true"
>
<div
v-if="isListLoadingVisible"
class="row justify-center"
>
<q-spinner-dots
color="primary"
:size="40"
/>
</div>
<div>
<q-list
striped-odd
no-border
multiline
:highlight="!isMobile"
>
<csc-pbx-call-queue
v-for="(subscriber, index) in callQueueGroupsAndSeats"
:key="index"
:subscriber="subscriber"
/>
</q-list>
</div>
<div
v-if="callQueueGroupsAndSeats.length === 0 && !isListRequesting"
class="row justify-center csc-no-entities"
>
{{ $t('pbxConfig.noCallQueues') }}
</div>
</csc-page>
</template>
<script>
import CscPage from '../../CscPage'
import CscPbxCallQueue from './CscPbxCallQueue'
import { mapGetters } from 'vuex'
import {
QField,
QInput,
QIcon,
QSelect,
QChip,
QList,
QItem,
QItemSide,
QItemMain,
QItemTile,
Platform,
QSpinnerDots
} from 'quasar-framework'
export default {
components: {
CscPbxCallQueue,
CscPage,
QField,
QInput,
QIcon,
QSelect,
QChip,
QList,
QItem,
QItemSide,
QItemMain,
QItemTile,
QSpinnerDots
},
data () {
return {
}
},
mounted() {
this.$store.dispatch('pbxConfig/listCallQueueGroupsAndSeats');
},
computed: {
...mapGetters('pbxConfig', [
'callQueueGroupsAndSeats',
'isListLoadingVisible',
'isListRequesting'
]),
isMobile() {
return Platform.is.mobile;
}
},
methods: {
},
watch: {
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../../themes/quasar.variables.styl';
</style>

@ -1,34 +1,44 @@
<template> <template>
<q-item :class="itemClasses"> <q-item :class="itemClasses">
<q-item-side v-if="!expanded"> <q-item-side
<q-item-tile avatar> v-if="!expanded"
>
<q-item-tile
v-if="hasFrontImage"
avatar
>
<img :src="frontImageUrl" /> <img :src="frontImageUrl" />
</q-item-tile> </q-item-tile>
<q-icon
v-else
size="24px"
name="fa-fax"
color="white"
/>
</q-item-side> </q-item-side>
<q-item-main :style="{zIndex: 10}"> <q-item-main :style="{zIndex: 10}">
<q-item-tile <q-item-tile
v-if="!expanded" v-if="!expanded"
class="csc-item-title"
label label
> >
{{ device.station_name }} {{ device.station_name }}
</q-item-tile> </q-item-tile>
<q-item-tile <q-item-tile
v-if="!expanded" v-if="!expanded"
class="csc-item-subtitle"
sublabel sublabel
> >
<span class="gt-sm"> <span class="csc-item-label">Model:</span>
Model: <span class="csc-item-value">{{ name }}</span>
</span>
{{ name }}
</q-item-tile> </q-item-tile>
<q-item-tile <q-item-tile
v-if="!expanded" v-if="!expanded"
class="csc-item-subtitle"
sublabel sublabel
> >
<span class="gt-sm"> <span class="csc-item-label">MAC address:</span>
MAC address: <span class="csc-item-value">{{ device.identifier }}</span>
</span>
{{ device.identifier }}
</q-item-tile> </q-item-tile>
<q-item-tile <q-item-tile
v-if="expanded" v-if="expanded"
@ -195,10 +205,13 @@
}, },
computed: { computed: {
itemClasses() { itemClasses() {
let classes = ['csc-entity', 'csc-pbx-device', 'csc-list-item']; let classes = ['csc-pbx-device', 'csc-list-item'];
if (this.expanded) { if (this.expanded) {
classes.push('csc-item-expanded'); classes.push('csc-item-expanded');
} }
else {
classes.push('csc-item-collapsed');
}
return classes; return classes;
}, },
isMobile() { isMobile() {

@ -252,7 +252,6 @@
'isDeviceLoading', 'isDeviceLoading',
'deviceRemoved', 'deviceRemoved',
'groupsAndSeatsOptions', 'groupsAndSeatsOptions',
'groupsAndSeats',
'getGroupOrSeatById', 'getGroupOrSeatById',
'updatedDeviceKey', 'updatedDeviceKey',
'createDeviceRequesting', 'createDeviceRequesting',

@ -224,6 +224,9 @@
if (this.expanded) { if (this.expanded) {
classes.push('csc-item-expanded'); classes.push('csc-item-expanded');
} }
else {
classes.push('csc-item-collapsed');
}
return classes; return classes;
}, },
entityTitle() { entityTitle() {

@ -22,10 +22,8 @@
class="csc-item-subtitle" class="csc-item-subtitle"
sublabel sublabel
> >
<div>
<span class="csc-item-label">{{ $t('pbxConfig.extension') }}:</span> <span class="csc-item-label">{{ $t('pbxConfig.extension') }}:</span>
<span class="csc-item-value">{{ seat.pbx_extension }}</span> <span class="csc-item-value">{{ seat.pbx_extension }}</span>
</div>
</q-item-tile> </q-item-tile>
<q-item-tile <q-item-tile
v-if="!expanded" v-if="!expanded"
@ -41,9 +39,11 @@
</div> </div>
<div <div
class="csc-item-field" class="csc-item-field"
v-if="hasGroups"> v-if="hasGroups"
>
<span <span
class="csc-item-label"> class="csc-item-label"
>
{{ $t('pbxConfig.groups') }}: {{ $t('pbxConfig.groups') }}:
</span> </span>
<span <span
@ -142,10 +142,6 @@
import _ from 'lodash'; import _ from 'lodash';
import numberFilter from '../../../filters/number' import numberFilter from '../../../filters/number'
import { import {
QCard,
QCardTitle,
QCardMain,
QCardActions,
QField, QField,
QInput, QInput,
QIcon, QIcon,
@ -154,7 +150,6 @@
QBtn, QBtn,
QInnerLoading, QInnerLoading,
QSpinnerMat, QSpinnerMat,
QTransition,
Platform, Platform,
QItem, QItem,
QItemSide, QItemSide,
@ -177,10 +172,6 @@
} }
}, },
components: { components: {
QCard,
QCardTitle,
QCardMain,
QCardActions,
QField, QField,
QInput, QInput,
QIcon, QIcon,
@ -189,7 +180,6 @@
QBtn, QBtn,
QInnerLoading, QInnerLoading,
QSpinnerMat, QSpinnerMat,
QTransition,
QItem, QItem,
QItemSide, QItemSide,
QItemMain, QItemMain,
@ -202,6 +192,9 @@
if (this.expanded) { if (this.expanded) {
classes.push('csc-item-expanded'); classes.push('csc-item-expanded');
} }
else {
classes.push('csc-item-collapsed');
}
return classes; return classes;
}, },
entityTitle() { entityTitle() {

@ -80,7 +80,8 @@
"subTitle": "Groups, Devices", "subTitle": "Groups, Devices",
"groups": "Groups", "groups": "Groups",
"seats": "Seats", "seats": "Seats",
"devices": "Devices" "devices": "Devices",
"callQueues": "Call Queues"
}, },
"voicebox": { "voicebox": {
"title": "Voicebox", "title": "Voicebox",
@ -345,6 +346,7 @@
"noStationName": "Could not find any device with station name matching the filter criteria", "noStationName": "Could not find any device with station name matching the filter criteria",
"noGroups": "No groups created yet", "noGroups": "No groups created yet",
"noSeats": "No seats created yet", "noSeats": "No seats created yet",
"noCallQueues": "No call queues created yet",
"toasts": { "toasts": {
"changedFieldToast": "Changed {type} to {name}", "changedFieldToast": "Changed {type} to {name}",
"updatedAliasNumbersToast": "Alias numbers field updated!", "updatedAliasNumbersToast": "Alias numbers field updated!",
@ -385,7 +387,10 @@
"createSeat": "Create seat", "createSeat": "Create seat",
"noGroupAssigned": "No groups", "noGroupAssigned": "No groups",
"noSeatAssigned": "No seats", "noSeatAssigned": "No seats",
"createGroup": "Create group" "createGroup": "Create group",
"queueLength": "Queue Length",
"wrapUpTime": "Wrap Up Time",
"queueExtensionName": "Group/Seat/Pilot"
}, },
"callBlocking": { "callBlocking": {
"privacyEnabledToast": "Your number is hidden to the callee", "privacyEnabledToast": "Your number is hidden to the callee",

@ -14,6 +14,7 @@ import SpeedDial from './components/pages/SpeedDial/SpeedDial'
import PbxConfigurationGroups from './components/pages/PbxConfiguration/CscPbxGroups' import PbxConfigurationGroups from './components/pages/PbxConfiguration/CscPbxGroups'
import PbxConfigurationSeats from './components/pages/PbxConfiguration/CscPbxSeats' import PbxConfigurationSeats from './components/pages/PbxConfiguration/CscPbxSeats'
import PbxConfigurationDevices from './components/pages/PbxConfiguration/CscPbxDevices' import PbxConfigurationDevices from './components/pages/PbxConfiguration/CscPbxDevices'
import PbxConfigurationCallQueues from './components/pages/PbxConfiguration/CscPbxCallQueues'
import Voicebox from './components/pages/Voicebox/Voicebox'; import Voicebox from './components/pages/Voicebox/Voicebox';
import Login from './components/Login' import Login from './components/Login'
import Error404 from './components/Error404' import Error404 from './components/Error404'
@ -126,6 +127,14 @@ export default [
subtitle: i18n.t('navigation.pbxConfiguration.devices') subtitle: i18n.t('navigation.pbxConfiguration.devices')
} }
}, },
{
path: 'pbx-configuration/call-queues',
component: PbxConfigurationCallQueues,
meta: {
title: i18n.t('navigation.pbxConfiguration.title'),
subtitle: i18n.t('navigation.pbxConfiguration.callQueues')
}
},
{ {
path: 'voicebox', path: 'voicebox',
component: Voicebox, component: Voicebox,

@ -30,7 +30,8 @@ import {
setIdentifier, setIdentifier,
setProfile, setProfile,
getGroup, getGroup,
getSeat getSeat,
getCallQueueConfigurations
} from '../../api/pbx-config' } from '../../api/pbx-config'
export default { export default {
@ -416,5 +417,14 @@ export default {
context.commit('resetStationNameFilter'); context.commit('resetStationNameFilter');
context.commit('resetMacAddressFilter'); context.commit('resetMacAddressFilter');
context.dispatch('listDevices'); context.dispatch('listDevices');
},
listCallQueueGroupsAndSeats(context, options) {
let silent = _.get(options, 'silent', false);
context.commit('callQueueListRequesting', silent);
getCallQueueConfigurations().then((list) => {
context.commit('callQueueListSucceeded', list);
}).catch((err) => {
context.commit('callQueueListFailed', err.message);
});
} }
} }

@ -295,5 +295,8 @@ export default {
}, },
chipStationNameFilter(state) { chipStationNameFilter(state) {
return state.chipStationNameFilter; return state.chipStationNameFilter;
},
callQueueGroupsAndSeats(state) {
return state.callQueueGroupsAndSeats;
} }
} }

@ -404,5 +404,21 @@ export default {
seatReloadingFailed(state, err) { seatReloadingFailed(state, err) {
state.seatReloadingState = RequestState.failed; state.seatReloadingState = RequestState.failed;
state.seatReloadingError = err; state.seatReloadingError = err;
},
callQueueListRequesting(state, options) {
options = options || {};
state.listLoadingSilently = _.get(options, 'silent', false);
state.listState = RequestState.requesting;
state.listError = null;
state.callQueueGroupsAndSeats = [];
},
callQueueListSucceeded(state, data) {
state.listState = RequestState.succeeded;
state.listError = null;
state.callQueueGroupsAndSeats = data.items;
},
callQueueListFailed(state, error) {
state.listState = RequestState.failed;
state.listError = error;
} }
} }

@ -69,5 +69,6 @@ export default {
seatReloadingError: null, seatReloadingError: null,
chipModelFilter: null, chipModelFilter: null,
chipMacAddressFilter: null, chipMacAddressFilter: null,
chipStationNameFilter: null chipStationNameFilter: null,
callQueueGroupsAndSeats: []
} }

@ -28,14 +28,6 @@
.csc-item-value:last-child:after .csc-item-value:last-child:after
content '' content ''
.csc-list-item
position relative
.csc-list-item-main
padding-top 16px
.csc-item-title
text-overflow ellipsis
.csc-item-label .csc-item-label
font-weight normal font-weight normal
@ -108,6 +100,7 @@
align-items center align-items center
color $body-color color $body-color
padding $flex-gutter-sm padding $flex-gutter-sm
padding-left $flex-gutter-sm * 1.4
.q-item-label .q-item-label
color $body-color color $body-color
span span
@ -116,6 +109,12 @@
color $body-color color $body-color
span span
color $body-color color $body-color
.csc-list-item.csc-item-collapsed.q-item
.q-item-main
margin-left $flex-gutter-sm * 1.4
.q-item-side-left
min-width auto
.csc-list-form .csc-list-form
padding $flex-gutter-sm padding $flex-gutter-sm
.csc-username .csc-username
@ -164,3 +163,24 @@
.q-inner-loading .q-inner-loading
background $main-menu-background background $main-menu-background
.csc-item-title
font-weight bold
text-overflow ellipsis
.csc-item-label
font-weight bold
vertical-align middle
i
vertical-align middle
.csc-item-subtitle
.csc-item-label
font-weight normal
.csc-item-value
font-weight bold
.q-chip.text-white
color $dark
.q-chip-main
color $dark
.q-chip-side
color $dark

@ -64,7 +64,7 @@ $toolbar-min-height = 60px
$layout-header-shadow = $no-shadow $layout-header-shadow = $no-shadow
$layout-aside-background = $main-menu-background $layout-aside-background = $main-menu-background
$layout-aside-shadow = $no-shadow $layout-aside-shadow = $no-shadow
$layout-aside-left-width = 260px $layout-aside-left-width = 280px
$layout-aside-right-width = 320px $layout-aside-right-width = 320px
$layout-footer-shadow = $no-shadow $layout-footer-shadow = $no-shadow
@ -92,3 +92,5 @@ $call-footer-height-mobile = $call-footer-height * 1.4
$popover-box-shadow = $shadow-1 $popover-box-shadow = $shadow-1
$loading-background = $main-menu-background $loading-background = $main-menu-background
$chip-color = $dark

Loading…
Cancel
Save