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 Vue from 'vue';
import { getNumbers, assignNumbers } from './user';
import { createSubscriber, deleteSubscriber, setDisplayName,
setPbxExtension, setPbxHuntPolicy, setPbxHuntTimeout,
setPbxGroupMemberIds, setPbxGroupIds, getSubscribers, getSubscriber } from './subscriber';
import {
getNumbers,
assignNumbers
} from './user';
import {
createSubscriber,
deleteSubscriber,
setDisplayName,
setPbxExtension,
setPbxHuntPolicy,
setPbxHuntTimeout,
setPbxGroupMemberIds,
setPbxGroupIds,
getSubscribers,
getSubscriber,
getSubscribersByCallQueueEnabled
} from './subscriber';
import uuid from 'uuid';
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) {
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
padding-top $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
color $main-menu-icon-color
.q-item-label
@ -539,4 +543,8 @@
padding 0
#csc-header
left $main-menu-minimized-width
.mobile
.layout-aside-left
width auto
right 0
</style>

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

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

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

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

@ -80,7 +80,8 @@
"subTitle": "Groups, Devices",
"groups": "Groups",
"seats": "Seats",
"devices": "Devices"
"devices": "Devices",
"callQueues": "Call Queues"
},
"voicebox": {
"title": "Voicebox",
@ -345,6 +346,7 @@
"noStationName": "Could not find any device with station name matching the filter criteria",
"noGroups": "No groups created yet",
"noSeats": "No seats created yet",
"noCallQueues": "No call queues created yet",
"toasts": {
"changedFieldToast": "Changed {type} to {name}",
"updatedAliasNumbersToast": "Alias numbers field updated!",
@ -385,7 +387,10 @@
"createSeat": "Create seat",
"noGroupAssigned": "No groups",
"noSeatAssigned": "No seats",
"createGroup": "Create group"
"createGroup": "Create group",
"queueLength": "Queue Length",
"wrapUpTime": "Wrap Up Time",
"queueExtensionName": "Group/Seat/Pilot"
},
"callBlocking": {
"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 PbxConfigurationSeats from './components/pages/PbxConfiguration/CscPbxSeats'
import PbxConfigurationDevices from './components/pages/PbxConfiguration/CscPbxDevices'
import PbxConfigurationCallQueues from './components/pages/PbxConfiguration/CscPbxCallQueues'
import Voicebox from './components/pages/Voicebox/Voicebox';
import Login from './components/Login'
import Error404 from './components/Error404'
@ -126,6 +127,14 @@ export default [
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',
component: Voicebox,

@ -30,7 +30,8 @@ import {
setIdentifier,
setProfile,
getGroup,
getSeat
getSeat,
getCallQueueConfigurations
} from '../../api/pbx-config'
export default {
@ -416,5 +417,14 @@ export default {
context.commit('resetStationNameFilter');
context.commit('resetMacAddressFilter');
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) {
return state.chipStationNameFilter;
},
callQueueGroupsAndSeats(state) {
return state.callQueueGroupsAndSeats;
}
}

@ -404,5 +404,21 @@ export default {
seatReloadingFailed(state, err) {
state.seatReloadingState = RequestState.failed;
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,
chipModelFilter: null,
chipMacAddressFilter: null,
chipStationNameFilter: null
chipStationNameFilter: null,
callQueueGroupsAndSeats: []
}

@ -28,14 +28,6 @@
.csc-item-value:last-child:after
content ''
.csc-list-item
position relative
.csc-list-item-main
padding-top 16px
.csc-item-title
text-overflow ellipsis
.csc-item-label
font-weight normal
@ -108,6 +100,7 @@
align-items center
color $body-color
padding $flex-gutter-sm
padding-left $flex-gutter-sm * 1.4
.q-item-label
color $body-color
span
@ -116,6 +109,12 @@
color $body-color
span
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
padding $flex-gutter-sm
.csc-username
@ -164,3 +163,24 @@
.q-inner-loading
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-aside-background = $main-menu-background
$layout-aside-shadow = $no-shadow
$layout-aside-left-width = 260px
$layout-aside-left-width = 280px
$layout-aside-right-width = 320px
$layout-footer-shadow = $no-shadow
@ -92,3 +92,5 @@ $call-footer-height-mobile = $call-footer-height * 1.4
$popover-box-shadow = $shadow-1
$loading-background = $main-menu-background
$chip-color = $dark

Loading…
Cancel
Save