TT#75404 CF: As a Customer, I want to add phone numbers in order to forward calls to these numbers

Change-Id: Ibc557c446156c55db229043c18b345066c32a867
changes/25/38625/3
Carlo Venusino 6 years ago
parent baf1383b89
commit 8554bf10cc

@ -64,15 +64,11 @@
<style lang="stylus" rel="stylesheet/stylus">
@import '../themes/quasar.variables.styl';
.csc-page
min-height 100vh
position relative
margin 0
padding 0
.csc-page-content
min-height 100vh
padding $flex-gutter-lg
padding-top 24px
padding-bottom $call-footer-height
padding 0
.csc-page.csc-page-mobile
.csc-page-content
padding-left $flex-gutter-sm * 1.4
@ -81,4 +77,6 @@
.csc-page-content
padding-left 0
padding-right 0
.csc-page.csc-simple-page
padding $flex-gutter-lg
</style>

@ -0,0 +1,187 @@
<template>
<div
class="csc-cf-group"
>
<div
v-if="group.id !== 'unconditional'"
class="csc-cf-group-title"
>
{{ group.title }}
</div>
<div
v-for="(destination, index) in group.destinations"
:key="genKey()"
>
<csc-new-call-forward-destination
:destination="destination"
:index="index"
:groupId="group.id"
/>
</div>
<div class="row csc-cf-destination-cont">
<div class="col col-xs-12 col-md-4 text-right"></div>
<div
class="col col-xs-12 col-md-2 text-left"
v-if="showAddDestBtn"
>
<div
class='csc-cf-destination-add-destination'
@click="addDestination"
>
<q-icon
name="add"
color="primary"
size="24px"
/>
{{ $t('pages.newCallForward.addDestinationLabel') }}
<q-spinner-dots
v-if="destinationInCreation"
class="csc-call-spinner"
color="primary"
:size="24"
/>
</div>
</div>
<div class="col col-xs-12 col-md-6 "></div>
</div>
<!-- <div
class="row"
>
<div
class="csc-cf-destination-label col col-4"
>
{{ $t('pages.newCallForward.allCallsForwardedTo') }}
</div>
<div
class="csc-cf-destination-value col col-2"
>
<span
class="csc-text-action"
>
{{ $t('pages.newCallForward.addDestinationLabel') }}
<q-popover
ref="groupMenu"
:disable="true"
>
<q-list
link
no-border
>
<q-item
>
<q-item-side
icon="number"
/>
<q-item-main>
Number
</q-item-main>
</q-item>
</q-list>
</q-popover>
</span>
</div>
<div
class="csc-cf-destination-actions col col-6"
>
<q-icon
name="delete"
color="negative"
size="24px"
/>
</div>
</div> -->
</div>
</template>
<script>
import {
QSpinnerDots,
QIcon,
QPopover,
QList,
QItem,
QItemMain,
QItemSide
} from 'quasar-framework'
import CscNewCallForwardDestination from './CscNewCallForwardDestination'
export default {
name: 'csc-cf-group',
props: [
'group'
],
components: {
QSpinnerDots,
QIcon,
QPopover,
QList,
QItem,
QItemMain,
QItemSide,
CscNewCallForwardDestination
},
data () {
return {
destinationInCreation: false
};
},
computed: {
showAddDestBtn(){
const destinations = this.group.destinations;
for(let dest of destinations){
if(dest && dest.simple_destination && dest.simple_destination.length < 2){
return false;
}
}
return true;
}
},
methods: {
// we need to generate key because destinations have no id
genKey(){
return Math.random();
},
async addDestination(){
this.destinationInCreation = true;
await this.$store.dispatch('newCallForward/addDestination', {
forwardGroupId: this.group.id,
destination: " "
});
await this.$store.dispatch('newCallForward/loadForwardGroups');
this.destinationInCreation = false;
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../../themes/app.common.styl'
.csc-cf-group
width 100%
.csc-cf-group-title
text-align right
.csc-cf-destination-label
text-align right
.csc-cf-destination-value
text-align center
.csc-cf-destination-actions
text-align left
.csc-cf-destination-add-destination
padding-left 25px
width 250px
white-space nowrap
overflow hidden
text-overflow ellipsis
color $primary
cursor pointer
</style>

@ -3,113 +3,103 @@
class="csc-simple-page"
>
<div
class="row"
class="csc-cf-row row"
>
<div
class="col col-xs-12 col-md-4"
class="col col-xs-12 col-md-4 text-right"
>
{{toggleLabel}}
{{ toggleLabel }}
</div>
<div
class="col col-xs-12 col-md-2"
class="col col-xs-12 col-md-2 text-left csc-cf-self-number-cont"
>
{{subscriberDisplayName}}
{{ primaryNumber | number }}
</div>
<div
class="col col-xs-12 col-md-6"
>
<q-field class="csc-cf-field-toggle">
<q-toggle
:value="primaryNumberEnabled"
:left-label="true"
@input="togglePrimaryNumber()"
/>
</q-field>
</div>
></div>
</div>
<div class="row">
<div class="csc-cf-row row">
<div
class="col col-xs-12 col-md-4"
class="column col col-xs-12 col-md-4 items-end"
>
<q-btn flat class="csc-cf-flat-btn">
+ {{ $t('pages.newCallForward.forwardBtnLabel') }}
</q-btn>
<q-popover
ref="destinationType"
anchor="center right"
<div
class="csc-text-action"
@click="addForward"
>
<q-list
link
class="no-border"
>
<q-item>
<q-item-main
:label="this.$t('pages.newCallForward.numberLabel')"
@click="showForm()"
/>
</q-item>
<q-item>
<q-item-main
:label="this.$t('pages.newCallForward.voiceMailLabel')"
@click="addVoicemail()"
/>
</q-item>
</q-list>
</q-popover>
<q-icon
name="add"
color="primary"
size="24px"
/>
{{ $t('pages.newCallForward.forwardBtnLabel') }}
<q-spinner-dots
v-if="groupInCreation"
color="primary"
:size="24"
/>
</div>
</div>
</div>
<div
class="row csc-cf-destinations-cont"
class="csc-cf-row row"
v-for="(forwardGroup, item) in forwardGroups"
:key="forwardGroup.id"
>
<div
class="col col-xs-12 col-md-12"
>
<csc-new-call-forward-destination
v-for="(destination, index) in destinations"
:key="index"
:index="index"
:destination="destination"
/>
</div>
<csc-cf-group
:group="forwardGroup"
/>
</div>
<div
class="row"
>
<div class="csc-cf-row row">
<div
class="col col-xs-12 col-md-6"
class="column col col-xs-12 col-md-4"
>
<csc-new-call-forward-add-destination-form
class="csc-list-form col-xs-12 col-md-4 col-lg-6"
ref="addDestinationForm"
<q-spinner-dots
v-if="groupsLoading"
class="csc-call-spinner"
color="primary"
:size="24"
/>
</div>
</div>
</csc-page>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
import {
// mapState,
mapGetters,
// mapMutations
} from 'vuex'
import {
QSpinnerDots,
QField,
QToggle,
QBtn,
QPopover,
QList,
QItem,
QItemMain
QItemMain,
QIcon
} from 'quasar-framework'
import CscPage from '../../CscPage'
import CscNewCallForwardDestination from './CscNewCallForwardDestination'
import CscNewCallForwardAddDestinationForm from './CscNewCallForwardAddDestinationForm'
import CscCfGroup from "./CscCallForwardGroup";
export default {
components: {
CscCfGroup,
CscPage,
CscNewCallForwardDestination,
CscNewCallForwardAddDestinationForm,
QSpinnerDots,
QField,
QToggle,
QBtn,
@ -117,23 +107,28 @@
QList,
QItem,
QItemMain,
QIcon
},
data () {
return {};
return {
groupInCreation: false,
groupsLoading: false
};
},
async mounted(){
await this.$store.dispatch('newCallForward/loadDestinationsets');
const unconditionalDestSet = await this.$store.dispatch('newCallForward/getDestinationSetByName', 'csc-unconditional');
if(unconditionalDestSet){
this.$store.dispatch('newCallForward/loadDestinations', unconditionalDestSet.destinations);
}
this.groupsLoading = true;
await this.$store.dispatch('newCallForward/loadForwardGroups');
this.groupsLoading = false;
},
computed: {
...mapState('newCallForward', []),
// ...mapState('newCallForward', [
// 'forwardGroups'
// ]),
...mapGetters('newCallForward', [
'primaryNumber',
'subscriberDisplayName',
'destinations'
'forwardGroups'
]),
primaryNumberEnabled(){
return true;
@ -143,12 +138,21 @@
}
},
methods: {
async addForward(){
this.groupInCreation = true;
const unconditionalFwdGroup = await this.$store.dispatch('newCallForward/getForwardGroupByName', 'unconditional');
if(!unconditionalFwdGroup){
await this.$store.dispatch('newCallForward/addForwardGroup', 'unconditional');
await this.$store.dispatch('newCallForward/loadForwardGroups');
}
this.groupInCreation = false;
},
togglePrimaryNumber(){},
showForm(){
this.$refs.destinationType.close();
this.$refs.addDestinationForm.add();
},
addVoicemail(){}
addVoicemail(){},
}
}
</script>
@ -162,4 +166,13 @@
margin-top 25px
.csc-cf-field-toggle
margin-top 0px;
.csc-call-spinner
margin-left auto
.csc-cf-self-number-cont
padding-left 30px
width 150px
white-space nowrap
overflow hidden
text-overflow ellipsis
</style>

@ -96,8 +96,8 @@
},
methods: {
async save() {
const destinationSetName = 'csc-unconditional'; // gonna be dynamic
const getDestinationSetByName = await this.$store.dispatch('newCallForward/getDestinationSetByName', destinationSetName);
const forwardGroupName = 'unconditional'; // gonna be dynamic
const forwardGroup = await this.$store.dispatch('newCallForward/getForwardGroupByName', forwardGroupName);
if (this.numberError || this.saveDisabled) {
showGlobalError(this.$t('validationErrors.generic'));
@ -105,22 +105,22 @@
else if(Number.isInteger(this.destinationIndex)){ // edit mode
this.$store.dispatch('newCallForward/editDestination',{
index: this.destinationIndex,
destinationSetId: getDestinationSetByName.id,
forwardGroupId: forwardGroup.id,
destination: this.number
});
}
else { // new destination
let destinationSetId;
if(!getDestinationSetByName){
destinationSetId = await this.$store.dispatch('newCallForward/addDestinationSet', destinationSetName);
await this.$store.dispatch('newCallForward/loadDestinationsets'); // keeps local data updated
let forwardGroupId;
if(!forwardGroup){
forwardGroupId = await this.$store.dispatch('newCallForward/addForwardGroup', forwardGroupName);
await this.$store.dispatch('newCallForward/loadForwardGroups'); // keeps local data updated
}
else{
destinationSetId = getDestinationSetByName.id;
forwardGroupId = forwardGroup.id;
}
this.$store.dispatch('newCallForward/addDestination', {
destinationSetId: destinationSetId,
forwardGroupId: forwardGroupId,
destination: this.number
});
}

@ -1,55 +1,60 @@
<template>
<div class="row csc-cf-destination-cont">
<div class="col col-xs-12 col-md-4 ">
{{ $t('pages.newCallForward.destinationTimeoutLabel') }}
<span class='csc-cf-timeout'>
{{this.destinationTimeout}}
<q-popover
ref="timeoutForm"
self="top left"
class="csc-cf-timeout-form"
>
<q-slider
v-model="destinationTimeout"
label
label-always
:step="5"
:min="0"
:max="120"
@change="saveTimeout()"
/>
</q-popover>
</span>
{{ $t('pages.newCallForward.destinationNumberLabel') }}
</div>
<div class="col col-xs-12 col-md-2 ">
<div class="row csc-cf-destination-cont">
<div class="col col-xs-12 col-md-4 text-right">
{{ $t('pages.newCallForward.destinationTimeoutLabel') }}
<span class='csc-cf-timeout'>
{{this.destinationTimeout}}
<q-popover
ref="timeoutForm"
self="top left"
class="csc-cf-timeout-form"
@close="saveTimeout()"
>
<q-slider
v-model="destinationTimeout"
label
label-always
:step="5"
:min="0"
:max="300"
snap
/>
</q-popover>
</span>
{{ $t('pages.newCallForward.destinationNumberLabel') }}
</div>
<div class="col text-left csc-cf-dest-number-cont">
<span class='csc-cf-destination'>
{{this.destinationNumber}}
<q-popover
ref="numberForm"
anchor="top right"
class="csc-cf-number-form"
@open="showNumberForm()"
>
<csc-new-call-forward-add-destination-form
ref="addDestinationForm"
:index="this.destinationIndex"
:destination="this.destinationNumber"
/>
</q-popover>
</span>
</div>
<div class="col col-xs-12 col-md-2 ">
<!-- TODO add remove btn -->
</div>
<div class='csc-cf-destination'>
{{ !this.destinationNumber || this.destinationNumber.length < 2
? $t('pages.newCallForward.destinationLabel')
: this.destinationNumber}}
<q-popover
ref="numberForm"
anchor="top right"
class="csc-cf-number-form"
@open="showNumberForm()"
>
<csc-new-call-forward-add-destination-form
ref="addDestinationForm"
:index="this.destinationIndex"
:destination="this.destinationNumber"
/>
</q-popover>
</div>
</div>
<div class="col col-xs-12 col-md-5 ">
<!-- TODO add remove btn -->
</div>
</div>
</template>
<script>
import {
// QField,
// QToggle,
QIcon,
QBtn,
QPopover,
QSlider,
@ -57,11 +62,12 @@
QItem,
QItemMain
} from 'quasar-framework'
import { mapGetters } from 'vuex'
// import { mapGetters } from 'vuex'
import CscNewCallForwardAddDestinationForm from './CscNewCallForwardAddDestinationForm'
export default {
name: 'csc-new-call-forward-destination',
components: {
QIcon,
QBtn,
QPopover,
QSlider,
@ -71,6 +77,7 @@
CscNewCallForwardAddDestinationForm
},
props: [
'groupId',
'destination',
'index'
],
@ -84,11 +91,6 @@
destinationIndex: null
}
},
computed: {
...mapGetters('newCallForward', [
'destinations'
])
},
methods: {
updateValues(destination){
this.destinationTimeout = destination.timeout;
@ -99,22 +101,12 @@
this.$refs.addDestinationForm.add();
},
async saveTimeout(){
const destinationSetName = 'csc-unconditional'; // gonna be dynamic
const getDestinationSetByName = await this.$store.dispatch('newCallForward/getDestinationSetByName', destinationSetName);
this.$store.dispatch('newCallForward/editTimeout', {
index: this.destinationIndex,
timeout: this.destinationTimeout,
destinationSetId: getDestinationSetByName.id
forwardGroupId: this.groupId
});
}
},
watch: {
destinations(){
if(Number.isInteger(this.destinationIndex)){
this.updateValues(this.destinations[this.destinationIndex])
}
}
}
}
</script>
@ -122,15 +114,23 @@
<style lang="stylus" rel="stylesheet/stylus">
@import '../../../themes/app.common.styl'
.csc-cf-destination-cont
width 100%
padding 5px
.csc-cf-timeout,
.csc-cf-destination
width 100px
white-space nowrap
overflow hidden
text-overflow ellipsis
color $primary
cursor pointer
.csc-cf-timeout-form,
.csc-cf-number-form
min-width 120px
padding 0 15px 0 15px
padding 0 20px 0 20px
.csc-cf-dest-number-cont
padding-left 30px
</style>

@ -26,7 +26,9 @@
QField,
QInput
} from 'quasar-framework'
import { isPhone } from '../../../helpers/validation'
import {
userInfoAndEmpty
} from '../../../helpers/validation'
import {
maxLength,
required
@ -41,7 +43,7 @@
},
mounted(){
if(this.prefilled){
this.inputValue = this.prefilled;
this.inputValue = this.prefilled === " " ? "" : this.prefilled;
}
},
@ -53,7 +55,7 @@
},
validations: {
inputValue: {
isPhone,
userInfoAndEmpty,
maxLength: maxLength(64),
required
}
@ -75,7 +77,7 @@
maxLength: this.$v.inputValue.$params.maxLength.max
});
}
else if (!this.$v.inputValue.isPhone) {
else if (!this.$v.inputValue.userInfoAndEmpty) {
return this.$t('validationErrors.inputValidNumber');
}
},

@ -1,6 +1,12 @@
export default function(number, extension) {
let constructedNumber = "" + number.cc + number.ac + number.sn;
let constructedNumber = "";
if(number !== null && number.is_devid && number.is_devid === true) {
constructedNumber = "" + number.devid_alias;
}
else if (number !== null) {
constructedNumber = "" + number.cc + number.ac + number.sn;
}
if (extension) {
return constructedNumber.replace(new RegExp(extension + '$'), '');
}

@ -216,7 +216,10 @@
"numberLabel": "Number",
"voiceMailLabel": "Voicemail",
"destinationTimeoutLabel": "Then after ",
"destinationNumberLabel": "seconds forwarded to"
"destinationNumberLabel": "seconds forwarded to",
"destinationLabel": "Destination",
"addDestinationLabel": "Add destination",
"allCallsForwardedTo": "All calls forwarded to"
},
"callForward": {
"titles": {

@ -4,6 +4,7 @@ import Vue from 'vue'
// import _ from 'lodash';
// import { RequestState } from './common'
// import { i18n } from '../i18n';
import {
// getSourcesets,
getDestinationsets,
@ -31,111 +32,156 @@ import {
// updateOwnPhoneTimeout
} from '../api/call-forward';
const ForwardGroup = {
unconditional: 'unconditional'
};
export default {
namespaced: true,
state: {
destinationsets:[],
destinations: [],
forwardGroups: []
},
getters: {
primaryNumber(state, getters, rootState, rootGetters) {
let subscriber = rootGetters['user/getSubscriber'];
if(subscriber !== null) {
return subscriber.primary_number;
}
else {
return null;
}
},
subscriberDisplayName(state, getters, rootState, rootGetters) {
return rootGetters['user/getUsername'];
},
destinations(state) {
return state.destinations;
},
destinationsets(state){
return state.destinationsets;
// destinations(state) {
// return state.destinations;
// },
forwardGroups(state){
return state.forwardGroups;
}
},
mutations: {
addDestination(state, destination){
state.destinations.push(destination);
addDestination(state, forwardGroupId, destination){
let group = state.forwardGroups.find((group)=>{
return group.id === forwardGroupId;
});
group.destinations.push(destination);
},
editDestination(state, data){
let destination = state.destinations.slice(data.index, data.index+1)[0];
let group = state.forwardGroups.find((group)=>{
return group.id === data.forwardGroupId;
});
let destination = group.destinations.slice(data.index, data.index+1)[0];
destination.simple_destination = data.destination;
destination.destination = data.destination;
Vue.set(state.destinations, data.index, destination)
Vue.set(group.destinations, data.index, destination)
},
editTimeout(state, data){
let destination = state.destinations.slice(data.index, data.index+1)[0];
let group = state.forwardGroups.find((group)=>{
return group.id === data.forwardGroupId;
});
let destination = group.destinations.slice(data.index, data.index+1)[0];
destination.timeout = data.timeout;
Vue.set(state.destinations, data.index, destination)
Vue.set(group.destinations, data.index, destination)
},
loadDestinationsets(state, destinationsets){
state.destinationsets = destinationsets;
// loadDestinationsets(state, destinationsets){
// state.destinationsets = destinationsets;
// },
loadForwardGroups(state, forwardGroups){
state.forwardGroups = forwardGroups;
},
loadDestinations(state, destinations){
state.destinations = destinations;
}
// loadDestinations(state, destinations){
// state.destinations = destinations;
// },
},
actions: {
async loadDestinationsets(context) {
async loadForwardGroups(context) {
try{
const destinationsets = await getDestinationsets(localStorage.getItem('subscriberId'));
context.commit('loadDestinationsets', destinationsets);
const forwardGroups = await getDestinationsets(localStorage.getItem('subscriberId'));
context.commit('loadForwardGroups', forwardGroups);
}
catch(err){
console.log(err)
}
},
loadDestinations(context, destinations){
context.commit('loadDestinations', destinations);
},
async addDestinationSet(context, name) {
// loadDestinations(context, destinations){
// context.commit('loadDestinations', destinations);
// },
async addForwardGroup(context, name) {
try{
const newDestinationset = await addNewDestinationsetWithName(name);
return newDestinationset;
const destination = {
"announcement_id": null,
"simple_destination": " ",
"destination": " ",
"priority": 1,
"timeout": 20
};
const newForwardGroupId = await addNewDestinationsetWithName(ForwardGroup[name]);
await addDestinationToDestinationset({
id: newForwardGroupId,
data: [destination]
});
return newForwardGroupId;
}
catch(err){
console.log(err)
}
},
getDestinationSetByName(context, name){
let destinationsets = context.getters.destinationsets;
destinationsets = destinationsets.filter(($destinationset) => {
return $destinationset.name === name;
getForwardGroupByName(context, name){
let forwardGroups = context.getters.forwardGroups;
forwardGroups = forwardGroups.filter(($forwardGroup) => {
return $forwardGroup.name === name;
});
return destinationsets.length > 0 ? destinationsets[0] : null;
return forwardGroups.length > 0 ? forwardGroups[0] : null;
},
async addDestination(context, data){
try{
let group = context.state.forwardGroups.find((group)=>{
return group.id === data.forwardGroupId;
});
const destination = {
"announcement_id": null,
"simple_destination": data.destination,
"destination": data.destination,
"priority": 1,
"timeout": 10
"timeout": 20
};
await addDestinationToDestinationset({
id: data.destinationSetId,
data: [...context.state.destinations, destination]
id: data.forwardGroupId,
data: [...group.destinations, destination]
});
context.commit('addDestination', destination);
// context.commit('addDestination', group.id, destination);
}
catch(err){
console.log(err);
}
},
async editDestination(context, data){
let destination = context.state.destinations.slice(data.index, data.index+1)[0];
let group = context.state.forwardGroups.find((group)=>{
return group.id === data.forwardGroupId;
});
let destination = group.destinations.slice(data.index, data.index+1)[0];
destination.simple_destination = data.destination;
destination.destination = data.destination;
context.commit('editDestination', data);
await addDestinationToDestinationset({
id: data.destinationSetId,
data: context.state.destinations
id: data.forwardGroupId,
data: group.destinations
});
},
async editTimeout(context, data){
let destination = context.state.destinations.slice(data.index, data.index+1)[0];
let group = context.state.forwardGroups.find((group)=>{
return group.id === data.forwardGroupId;
});
let destination = group.destinations.slice(data.index, data.index+1)[0];
destination.timeout = data.timeout;
context.commit('editTimeout', data);
await addDestinationToDestinationset({
id: data.destinationSetId,
data: context.state.destinations
id: group.id,
data: group.destinations
});
}
}

@ -308,3 +308,11 @@ input.q-input-target
position relative
left 50% !important
transform translateX(-50%)
.csc-text-action
color $primary
cursor pointer
font-weight bold
.csc-cf-row
margin-bottom $flex-gutter-sm

Loading…
Cancel
Save