TT#47510 Extra page section for managing SoundSets

What has been done:
- TT#47510, SoundManagement: As a SubscriberAdmin, I want to have an extra page
section for managing SoundSets
- TT#47511, SoundManagement: As a SubscriberAdmin, I want to see a list of all
owned SoundSets

Change-Id: I1e1d6d49d90d1b8b5deef5689c686b921eb6f7d8
changes/90/26590/14
raxelsen 6 years ago
parent 0193351546
commit fb4ba3093b

@ -24,7 +24,7 @@ import {
removeCallQueueConfig
} from './subscriber';
import uuid from 'uuid';
import { getList, get, patchReplace } from './common'
import { getList, get, patchReplace} from './common'
var createId = uuid.v4;
@ -602,3 +602,113 @@ export function setWrapUpTimeConfig(id, wrapUpTime) {
export function getPrefs(id) {
return getPreferences(id);
}
export function getAllSoundSets(options) {
return new Promise((resolve, reject)=>{
options = options || {};
options = _.merge(options, {
path: 'api/soundsets/',
root: '_embedded.ngcp:soundsets',
all: true
});
getList(options).then((list)=>{
list.items.map((set) => {
delete set._links;
});
resolve(list);
}).catch((err)=>{
reject(err);
});
});
}
export function getSoundHandles(options) {
return new Promise((resolve, reject)=>{
options = options || {};
options = _.merge(options, {
path: 'api/soundhandles/',
root: '_embedded.ngcp:soundhandles',
all: true
});
getList(options).then((list) => {
// Ngcp-panel only lists three groups ('digits', 'music_on_hold'
// and 'pbx'). Filtering out the rest for that reason, as the
// enpoint has 11 groups total
let soundHandles = list.items.filter((handle) => {
return ['digits', 'music_on_hold', 'pbx'].indexOf(handle.group) > -1;
});
resolve(soundHandles);
}).catch((err)=>{
reject(err);
});
});
}
export function getSoundFilesBySet(options) {
return new Promise((resolve, reject)=>{
options = options || {};
options = _.merge(options, {
path: 'api/soundfiles/',
root: '_embedded.ngcp:soundfiles',
all: true
});
getList(options).then((result)=>{
let list = result.items.map((file) => {
return {
filename: file.filename,
handle: file.handle,
loopplay: file.loopplay,
id: file.id
};
});
resolve(list);
}).catch((err)=>{
reject(err);
});
});
}
export function getSoundFilesGrouped(options) {
let handles = [];
return new Promise((resolve, reject) => {
Promise.resolve().then(() => {
return getSoundHandles();
}).then((soundHandles) => {
handles = soundHandles.map((handle) => {
return {
group: handle.group,
handle: handle.handle,
filename: '',
id: null,
loopplay: null
};
});
return getSoundFilesBySet(options);
}).then((files) => {
files.forEach((file) => {
handles.forEach((handle) => {
if (file.handle === handle.handle) {
handle.filename = file.filename;
handle.id = file.id;
handle.loopplay = file.loopplay
}
});
});
return handles;
}).then((merged) => {
let groupedFiles = {
groups: _(merged)
.groupBy('group')
.map((items, group) => {
return {
name: group,
handles: items
};
}).value()
};
resolve(groupedFiles);
}).catch((err)=>{
reject(err);
});
});
}

@ -191,6 +191,17 @@
:label="$t('navigation.pbxConfiguration.callQueues')"
/>
</q-side-link>
<q-side-link
item
to="/user/pbx-configuration/sound-sets"
>
<q-item-side
icon="queue_music"
/>
<q-item-main
:label="$t('navigation.pbxConfiguration.soundSets')"
/>
</q-side-link>
</q-collapsible>
</q-list>
</template>

@ -0,0 +1,82 @@
<template>
<q-collapsible
:label="group.name"
>
<q-list
striped-odd
no-border
multiline
:highlight="!mobile"
>
<q-item
v-if="!mobile"
>
<div class="col">
Handle
</div>
<div class="col">
Filename
</div>
<div class="col">
Loopplay
</div>
</q-item>
<csc-pbx-sound-item
v-for="item in group.handles"
:key="item.id"
:item="item"
:mobile="mobile"
/>
</q-list>
</q-collapsible>
</template>
<script>
import CscPbxSoundItem from './CscPbxSoundItem'
import {
QList,
QItem,
QItemSide,
QItemMain,
QItemTile,
QBtn,
QIcon,
QCollapsible,
QCheckbox
} from 'quasar-framework'
export default {
name: 'csc-pbx-sound-group',
props: {
group: Object,
mobile: Boolean
},
components: {
CscPbxSoundItem,
QList,
QItem,
QItemSide,
QItemMain,
QItemTile,
QBtn,
QIcon,
QCollapsible,
QCheckbox
},
data () {
return {
}
},
mounted() {
},
computed: {
},
methods: {
},
watch: {
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../../themes/quasar.variables.styl';
</style>

@ -0,0 +1,129 @@
<template>
<q-item
highlight
>
<q-item-main
v-if="!mobile"
class="row"
>
<!--NOTE: Would like some feedback on this, please. Made separate styling-->
<!--for mobile/desktop, as with 60 or more items in some of the groups-->
<!--I don't see a better way to display it. Will clean up css once-->
<!--feedback is implemented-->
<div class="col">
{{ item.handle }}
</div>
<div class="col">
{{ item.filename }}
</div>
<div class="col">
<q-checkbox
readonly
:value="loop"
/>
</div>
</q-item-main>
<q-item-main
v-if="mobile"
class="sound-item-row mobile"
>
<div>
<span class="item-sublabel">
Handle:
</span>
<span class="item-values">
{{ item.handle }}
</span>
</div>
<div>
<span class="item-sublabel">
Filename:
</span>
<span class="item-values">
{{ item.filename }}
</span>
</div>
<div>
<span class="item-sublabel">
Loopplay:
</span>
<span class="item-values">
<q-checkbox
readonly
:value="loop"
/>
</span>
</div>
</q-item-main>
</q-item>
</template>
<script>
import {
QList,
QItem,
QItemSide,
QItemMain,
QItemTile,
QBtn,
QIcon,
QCollapsible,
QCheckbox
} from 'quasar-framework'
export default {
name: 'csc-pbx-sound-item',
props: {
item: Object,
mobile: Boolean
},
components: {
QList,
QItem,
QItemSide,
QItemMain,
QItemTile,
QBtn,
QIcon,
QCollapsible,
QCheckbox
},
data () {
return {
loop: this.hasLoop()
}
},
mounted() {
},
computed: {
},
methods: {
hasLoop() {
return !!this.item.loopplay;
}
},
watch: {
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../../themes/quasar.variables.styl';
@import '../../../themes/quasar.variables.styl';
.sound-item-row.mobile
padding 16px
padding-left 0px
display block
color $white
white-space nowrap
overflow hidden
font-size 16px
.item-values
color $white
.item-sublabel
color $light
</style>

@ -0,0 +1,187 @@
<template>
<q-item
:class="itemClasses"
>
<q-item-side
v-if="!expanded"
>
<q-icon
size="24px"
name="music_note"
color="white"
/>
</q-item-side>
<q-item-main>
<q-item-tile
v-if="!expanded"
class="csc-item-title"
label
>
{{ set.name }}
</q-item-tile>
<q-item-tile
v-if="!expanded"
class="csc-item-subtitle"
sublabel
>
<div>
<span class="csc-item-label">{{ $t('pbxConfig.description') }}:</span>
<span class="csc-item-value">{{ set.description }}</span>
</div>
</q-item-tile>
<q-item-tile
v-if="expanded"
class="csc-list-item-main"
>
<q-field label="Name">
<q-input
dark
readonly
:value="set.name"
/>
</q-field>
<q-field label="Description">
<q-input
dark
readonly
:value="set.description"
/>
</q-field>
</q-item-tile>
<q-item-tile
class="csc-list-collapsible"
v-if="expanded"
>
<div class="csc-sublabel">
{{ $t('pbxConfig.groups') }}
</div>
<csc-pbx-sound-group
v-for="(group, index) in set.groups"
:group="group"
:key="index"
:highlight="!mobile"
:mobile="mobile"
/>
</q-item-tile>
</q-item-main>
<q-item-side
right
class="csc-list-actions-pinned"
>
<q-item-tile>
<q-btn
:icon="titleIcon"
:big="mobile"
color="primary"
flat
@click="toggleMain()"
/>
</q-item-tile>
</q-item-side>
<q-inner-loading :visible="loading">
<q-spinner-mat
size="60px"
color="primary"
/>
</q-inner-loading>
</q-item>
</template>
<script>
import CscPbxSoundGroup from './CscPbxSoundGroup'
import {
QItem,
QItemSide,
QItemMain,
QItemTile,
QBtn,
QIcon,
QCollapsible,
QCheckbox,
QField,
QInput,
QInnerLoading,
QSpinnerMat
} from 'quasar-framework'
export default {
name: 'csc-pbx-sound-set',
props: {
set: Object,
mobile: Boolean,
loading: Boolean
},
components: {
CscPbxSoundGroup,
QItem,
QItemSide,
QItemMain,
QItemTile,
QBtn,
QIcon,
QCollapsible,
QCheckbox,
QField,
QInput,
QInnerLoading,
QSpinnerMat
},
data () {
return {
expanded: false
}
},
mounted() {
},
computed: {
itemClasses() {
let classes = ['csc-list-item'];
if (this.expanded) {
classes.push('csc-item-expanded');
}
else {
classes.push('csc-item-collapsed');
}
return classes;
},
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.styl';
.csc-list-collapsible
margin-top $flex-gutter-md
.q-item
padding-left 0
.q-item-section
padding-top 0
.q-item-icon
color $primary
.q-collapsible-sub-item
padding 0
.csc-sublabel
color $light
margin-bottom $flex-gutter-sm
</style>

@ -0,0 +1,70 @@
<template>
<csc-page
:is-list="true"
>
<q-list
striped-odd
no-border
multiline
:highlight="!isMobile"
>
<csc-pbx-sound-set
v-for="set in soundSets"
:loading="soundSetFilesLoading(set.id)"
:key="set.id"
:set="set"
:mobile="isMobile"
/>
</q-list>
<div
v-if="soundSets.length === 0 && !isSoundSetsRequesting"
class="row justify-center csc-no-entities"
>
{{ $t('pbxConfig.noSoundSets') }}
</div>
</csc-page>
</template>
<script>
import CscPage from '../../CscPage'
import CscPbxSoundSet from './CscPbxSoundSet'
import { mapGetters } from 'vuex'
import {
Platform,
QList,
QBtn
} from 'quasar-framework'
export default {
components: {
CscPage,
CscPbxSoundSet,
QList,
QBtn
},
data () {
return {
}
},
mounted() {
this.$store.dispatch('pbxConfig/listSoundSets');
},
computed: {
...mapGetters('pbxConfig', [
'soundSets',
'soundSetFilesLoading',
'isSoundSetsRequesting'
]),
isMobile() {
return !!Platform.is.mobile;
}
},
methods: {
},
watch: {
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../../themes/quasar.variables.styl';
</style>

@ -87,7 +87,8 @@
"groups": "Groups",
"seats": "Seats",
"devices": "Devices",
"callQueues": "Call Queues"
"callQueues": "Call Queues",
"soundSets": "Sound Sets"
},
"voicebox": {
"title": "Voicebox",
@ -360,6 +361,7 @@
"noGroups": "No groups created yet",
"noSeats": "No seats created yet",
"noCallQueues": "No call queues created yet",
"noSoundSets": "No sound sets created yet",
"toasts": {
"changedFieldToast": "Changed {type} to {name}",
"updatedAliasNumbersToast": "Alias numbers field updated!",
@ -410,7 +412,8 @@
"removeConfigTitle": "Remove call queue",
"removeConfigText": "You are about to remove call queue for {subscriber}",
"seconds": "seconds",
"callers": "callers"
"callers": "callers",
"description": "Description"
},
"callBlocking": {
"privacyEnabledToast": "Your number is hidden to the callee",

@ -15,6 +15,7 @@ import PbxConfigurationGroups from './components/pages/PbxConfiguration/CscPbxGr
import PbxConfigurationSeats from './components/pages/PbxConfiguration/CscPbxSeats'
import PbxConfigurationDevices from './components/pages/PbxConfiguration/CscPbxDevices'
import PbxConfigurationCallQueues from './components/pages/PbxConfiguration/CscPbxCallQueues'
import PbxConfigurationSoundSets from './components/pages/PbxConfiguration/CscPbxSoundSets'
import Voicebox from './components/pages/Voicebox/Voicebox';
import Login from './components/Login'
import Error404 from './components/Error404'
@ -135,6 +136,14 @@ export default [
subtitle: i18n.t('navigation.pbxConfiguration.callQueues')
}
},
{
path: 'pbx-configuration/sound-sets',
component: PbxConfigurationSoundSets,
meta: {
title: i18n.t('navigation.pbxConfiguration.title'),
subtitle: i18n.t('navigation.pbxConfiguration.soundSets')
}
},
{
path: 'voicebox',
component: Voicebox,

@ -37,7 +37,9 @@ import {
setWrapUpTimeConfig,
getConfig,
getPrefs,
removeCallQueue
removeCallQueue,
getAllSoundSets,
getSoundFilesGrouped
} from '../../api/pbx-config'
export default {
@ -550,5 +552,35 @@ export default {
}).catch((err)=>{
context.commit('removeItemFailed', err.message);
});
},
listSoundSets(context) {
context.commit('listSoundSetsRequesting');
getAllSoundSets().then((data) => {
context.commit('listSoundSetsSucceeded', data);
return data;
}).then((sets) => {
sets.items.forEach((set) => {
context.dispatch('loadFilesForSoundSet', set.id);
});
}).catch((err) => {
context.commit('listSoundSetsFailed', err.message)
});
},
loadFilesForSoundSet(context, id) {
let options = {
params: {
set_id: id + ''
}
}
context.commit('filesForSoundSetRequesting', id);
getSoundFilesGrouped(options).then((files) => {
let id = options.params.set_id;
context.commit('filesForSoundSetSucceeded', {
files: files,
id: id
});
}).catch((err) => {
context.commit('filesForSoundSetFailed', id, err);
})
}
}

@ -324,5 +324,16 @@ export default {
return item.id === id;
})
}
},
soundSets(state) {
return state.soundSetsOrdered;
},
soundSetFilesLoading(state) {
return (id) => {
return state.soundSetFilesStates[id] !== 'succeeded';
}
},
isSoundSetsRequesting(state) {
return state.listSoundSetsState === RequestState.requesting;
}
}

@ -471,5 +471,37 @@ export default {
id = id + "";
reactiveSet(state[type + 'States'], id, RequestState.failed);
reactiveSet(state[type + 'Errors'], id, error);
},
listSoundSetsRequesting(state) {
state.listSoundSetsState = RequestState.requesting;
state.listSoundSetsError = null;
},
listSoundSetsSucceeded(state, sets) {
state.listSoundSetsState = RequestState.succeeded;
state.listSoundSetsError = null;
state.soundSets = {};
state.soundSetsOrdered = [];
sets.items.forEach((set) => {
state.soundSets[set.id] = set;
state.soundSetsOrdered.push(set);
});
},
listSoundSetsFailed(state, error) {
state.listSoundSetsState = RequestState.failed;
state.listSoundSetsError = error;
},
filesForSoundSetRequesting(state, id) {
reactiveSet(state.soundSetFilesStates, id, RequestState.requesting);
},
filesForSoundSetSucceeded(state, options) {
let id = options.id;
reactiveSet(state.soundSetFilesStates, id, RequestState.succeeded);
reactiveSet(state.soundSetFilesErrors, id, null);
Vue.set(state.soundSets, id, Object.assign(state.soundSets[id], options.files));
},
filesForSoundSetFailed(state, id, error) {
id = id + "";
reactiveSet(state.soundSetFilesStates, id, RequestState.failed);
reactiveSet(state.soundSetFilesErrors, id, error);
}
}

@ -78,5 +78,11 @@ export default {
groupStates: {},
groupErrors: {},
seatStates: {},
seatErrors: {}
seatErrors: {},
listSoundSetsState: RequestState.initiated,
listSoundSetsError: null,
soundSets: {},
soundSetsOrdered: [],
soundSetFilesStates: {},
soundSetFilesErrors: {}
}

@ -50,4 +50,31 @@ describe('PBX Configuration Store', () => {
assert.deepEqual(state.numbers, data.numbers);
});
it('should list all Sound Sets', () => {
let state = {};
let data = {
items: [
{
contract_defaults: true,
customer_id: null,
description: 'Set description 1',
groups: [],
id: 15,
name: 'Set 1'
},
{
contract_defaults: false,
customer_id: null,
description: 'Set description 2',
groups: [],
id: 17,
name: 'Set 2'
}
]
};
PbxConfig.mutations.listSoundSetsSucceeded(state, data);
assert.equal(state.soundSets[15], data.items[0]);
assert.equal(state.soundSets[17], data.items[1]);
});
});

Loading…
Cancel
Save