TT#102400 Rework current state of CallForwarding

AC:
Can add forwarding
Can alter forwarding
Can remove forwarding
Can enable forwarding
Can disable forwarding
Can enable that primary number rings
Can disable that primary number rings
Can forward to Number
Can forward to Voicebox
Can forward to Fax2Mail
Can forward to ManagerSecretary
Can forward to Conference
Can create SourceSet
Can assign number to SourceSet
Can remove number from SourceSet
Can change name of the SourceSet
Can search for existing SourceSets
Can assign an existing SourceSet
Can assign TimeSet (Date)
Can delete TimeSet (Date)
Can assign TimeSet (Date range)
Can delete TimeSet (Date range)
Can assign TimeSet (Weekdays)
Can delete TimeSet (Weekdays)
Can assign TimeSet (Office Hours)
Can delete TimeSet (Office Hours)

Change-Id: If5e5267e229a20947e0278212f59349d9e2eb7be
pull/4/head
Hans-Peter Herzog 5 years ago
parent 1c839a7f84
commit c3a278f992

@ -21,7 +21,8 @@
"test:unit:watch": "jest --watch",
"test:unit:watchAll": "jest --watchAll",
"serve:test:coverage": "quasar serve test/jest/coverage/lcov-report/ --port 8788",
"concurrently:dev:jest": "concurrently \"quasar dev\" \"jest --watch\""
"concurrently:dev:jest": "concurrently \"quasar dev\" \"jest --watch\"",
"new:store": "quasar new store"
},
"dependencies": {
"@quasar/extras": "^1.9.10",
@ -57,6 +58,7 @@
"eslint-plugin-vue": "^6.1.2",
"generate-password": "^1.5.1",
"parseuri": "^0.0.6",
"uuid": "8.3.1",
"vue-wait": "1.4.8"
},
"browserslist": [

@ -0,0 +1,323 @@
import {
del,
get,
getList, patchReplace, post, putMinimal
} from 'src/api/common'
import {
v4
} from 'uuid'
export async function cfLoadMappings (subscriberId) {
return get({
resource: 'cfmappings',
resourceId: subscriberId
})
}
export async function cfLoadDestinationSets (subscriberId) {
return getList({
resource: 'cfdestinationsets',
all: true,
params: {
subscriber_id: subscriberId
}
})
}
export async function cfLoadSourceSets (subscriberId) {
return getList({
resource: 'cfsourcesets',
params: {
subscriber_id: subscriberId
}
})
}
export async function cfLoadTimeSets (subscriberId) {
return getList({
resource: 'cftimesets',
params: {
subscriber_id: subscriberId
}
})
}
export async function cfLoadMappingsFull (subscriberId) {
return await Promise.all([
cfLoadMappings(subscriberId),
cfLoadDestinationSets(subscriberId),
cfLoadSourceSets(subscriberId),
cfLoadTimeSets(subscriberId)
])
}
export async function cfCreateDestinationSet (payload) {
return post({
resource: 'cfdestinationsets',
body: payload
})
}
export async function cfDeleteDestinationSet (id) {
return del({
resource: 'cfdestinationsets',
resourceId: id
})
}
export async function cfCreateSourceSet (id, payload) {
const sources = []
payload.numbers.forEach((number) => {
sources.push({
source: number
})
})
return post({
resource: 'cfsourcesets',
body: {
name: payload.name,
subscriber_id: id,
is_regex: true,
sources: sources,
mode: payload.mode
}
})
}
export async function cfUpdateSourceSet (id, payload) {
const sources = []
payload.numbers.forEach((number) => {
sources.push({
source: number
})
})
return putMinimal({
resource: 'cfsourcesets',
resourceId: payload.id,
body: {
name: payload.name,
subscriber_id: id,
is_regex: true,
sources: sources,
mode: payload.mode
}
})
}
export async function cfDeleteSourceSet (id) {
return del({
resource: 'cfsourcesets',
resourceId: id
})
}
export async function cfCreateTimeSetDate (subscriberId, date) {
return post({
resource: 'cftimesets',
body: {
subscriber_id: subscriberId,
name: 'csc-date-exact-' + v4(),
times: [
{
minute: null,
month: date.month,
hour: null,
mday: date.date,
year: date.year,
wday: null
}
]
}
})
}
export async function cfUpdateTimeSetDate (timeSetId, date) {
return patchReplace({
resource: 'cftimesets',
resourceId: timeSetId,
fieldPath: 'times',
value: [
{
minute: null,
month: date.month,
hour: null,
mday: date.date,
year: date.year,
wday: null
}
]
})
}
export async function cfDeleteTimeSet (timesetId) {
return del({
resource: 'cftimesets',
resourceId: timesetId
})
}
export async function cfCreateTimeSetDateRange (subscriberId, date) {
return post({
resource: 'cftimesets',
body: {
subscriber_id: subscriberId,
name: 'csc-date-range-' + v4(),
times: [
{
minute: null,
month: date.from.month + '-' + date.to.month,
hour: null,
mday: date.from.date + '-' + date.to.date,
year: date.from.year + '-' + date.to.year,
wday: null
}
]
}
})
}
export async function cfUpdateTimeSetDateRange (timeSetId, date) {
return patchReplace({
resource: 'cftimesets',
resourceId: timeSetId,
fieldPath: 'times',
value: [
{
minute: null,
month: date.from.month + '-' + date.to.month,
hour: null,
mday: date.from.date + '-' + date.to.date,
year: date.from.year + '-' + date.to.year,
wday: null
}
]
})
}
export async function cfCreateTimeSetWeekdays (subscriberId, weekdays) {
const times = []
weekdays.forEach((weekday) => {
times.push({
minute: null,
month: null,
hour: null,
mday: null,
year: null,
wday: weekday
})
})
return post({
resource: 'cftimesets',
body: {
subscriber_id: subscriberId,
name: 'csc-weekdays-' + v4(),
times: times
}
})
}
export async function cfUpdateTimeSetWeekdays (timeSetId, weekdays) {
const times = []
weekdays.forEach((weekday) => {
times.push({
minute: null,
month: null,
hour: null,
mday: null,
year: null,
wday: weekday
})
})
return patchReplace({
resource: 'cftimesets',
resourceId: timeSetId,
fieldPath: 'times',
value: times
})
}
function cfNormaliseOfficeHours (timesPerWeekday) {
const normalisedTimes = []
timesPerWeekday.forEach((times, index) => {
times.forEach((time) => {
if (time.from !== '' && time.to !== '') {
const fromParts = time.from.split(':')
const toParts = time.to.split(':')
if (fromParts[0] !== '__' && fromParts[1] !== '__' && toParts[0] !== '__' && toParts[1] !== '__') {
normalisedTimes.push({
minute: fromParts[1] + '-' + toParts[1],
month: null,
hour: fromParts[0] + '-' + toParts[0],
mday: null,
year: null,
wday: (index + 1)
})
}
}
})
})
return normalisedTimes
}
export async function cfCreateOfficeHours (subscriberId, timesPerWeekday) {
return post({
resource: 'cftimesets',
body: {
subscriber_id: subscriberId,
name: 'csc-office-hours-' + v4(),
times: cfNormaliseOfficeHours(timesPerWeekday)
}
})
}
export async function cfUpdateOfficeHours (timeSetId, timesPerWeekday) {
return patchReplace({
resource: 'cftimesets',
resourceId: timeSetId,
fieldPath: 'times',
value: cfNormaliseOfficeHours(timesPerWeekday)
})
}
function cfNormaliseOfficeHoursSameTimes (times, weekdays) {
const normalisedTimes = []
weekdays.forEach((weekday) => {
times.forEach((time) => {
if (time.from !== '' && time.to !== '') {
const fromParts = time.from.split(':')
const toParts = time.to.split(':')
if (fromParts[0] !== '__' && fromParts[1] !== '__' && toParts[0] !== '__' && toParts[1] !== '__') {
normalisedTimes.push({
minute: fromParts[1] + '-' + toParts[1],
month: null,
hour: fromParts[0] + '-' + toParts[0],
mday: null,
year: null,
wday: weekday
})
}
}
})
})
return normalisedTimes
}
export async function cfCreateOfficeHoursSameTimes (subscriberId, times, weekdays) {
return post({
resource: 'cftimesets',
body: {
subscriber_id: subscriberId,
name: 'csc-office-hours-same-times-' + v4(),
times: cfNormaliseOfficeHoursSameTimes(times, weekdays)
}
})
}
export async function cfUpdateOfficeHoursSameTimes (timeSetId, times, weekdays) {
return patchReplace({
resource: 'cftimesets',
resourceId: timeSetId,
fieldPath: 'times',
value: cfNormaliseOfficeHoursSameTimes(times, weekdays)
})
}

@ -63,7 +63,7 @@ export async function getList (options) {
options.params.rows = LIST_ALL_ROWS
}
if (options.resource !== undefined) {
options.path = 'api/' + options.resource
options.path = 'api/' + options.resource + '/'
options.root = '_embedded.ngcp:' + options.resource
}
const firstRes = await Vue.http.get(options.path, {
@ -217,8 +217,11 @@ export async function post (options) {
const res = await Vue.http.post(path, options.body, {
headers: options.headers
})
if (options.headers.Prefer === Prefer.representation) {
const hasBody = res.body !== undefined && res.body !== null && res.body !== ''
if (hasBody) {
return normalizeEntity(getJsonBody(res.body))
} else if (!hasBody && res.headers.has('Location')) {
return _.last(res.headers.get('Location').split('/'))
} else {
return null
}

@ -4,7 +4,9 @@ import Vue from 'vue'
import {
getSubscribers
} from './subscriber'
import uuid from 'uuid'
import {
v4
} from 'uuid'
import {
getList,
get,
@ -12,7 +14,7 @@ import {
patchRemove
} from './common'
export const createId = uuid.v4
export const createId = v4
export const PBX_CONFIG_ORDER_BY = 'create_timestamp'
export const PBX_CONFIG_ORDER_DIRECTION = 'desc'

@ -16,6 +16,12 @@ import WholeCurrency from 'src/filters/currency'
import {
displayName
} from 'src/filters/subscriber'
import {
timeSetDateExact,
timeSetDateRange, timeSetOfficeHoursSameTime,
timeSetTimes,
timeSetWeekdays
} from 'src/filters/time-set'
export default () => {
Vue.filter('number', NumberFilter)
@ -30,4 +36,9 @@ export default () => {
Vue.filter('displayName', displayName)
Vue.filter('time', time)
Vue.filter('weekday', weekday)
Vue.filter('timeSetDateExact', timeSetDateExact)
Vue.filter('timeSetWeekdays', timeSetWeekdays)
Vue.filter('timeSetDateRange', timeSetDateRange)
Vue.filter('timeSetOfficeHoursSameTime', timeSetOfficeHoursSameTime)
Vue.filter('timeSetTimes', timeSetTimes)
}

@ -1,6 +1,6 @@
import VueWait from 'vue-wait'
export default ({ Vue, app }) => {
export default ({ Vue, app, store }) => {
Vue.use(VueWait)
app.wait = new VueWait({
useVuex: true,

@ -71,30 +71,10 @@ export default {
visible: true
},
{
to: '/user/call-forwarding',
icon: 'phone_forwarded',
label: this.$t('navigation.callForward.title'),
opened: this.isCallForward,
visible: true,
children: [
{
to: '/user/call-forward/always',
icon: 'check_circle',
label: this.$t('navigation.callForward.always'),
visible: true
},
{
to: '/user/call-forward/company-hours',
icon: 'schedule',
label: this.$t('navigation.callForward.companyHours'),
visible: true
},
{
to: '/user/call-forward/after-hours',
icon: 'watch_later',
label: this.$t('navigation.callForward.afterHours'),
visible: true
}
]
visible: true
},
{
icon: 'block',

@ -1,6 +1,6 @@
<template>
<q-item
v-close-popup
v-close-popup="closePopup"
clickable
v-bind="$attrs"
v-on="$listeners"
@ -41,6 +41,10 @@ export default {
sublabel: {
type: String,
default: ''
},
closePopup: {
type: Boolean,
default: true
}
}
}

@ -0,0 +1,202 @@
<template>
<q-popup-proxy
ref="popup"
persistent
anchor="bottom middle"
self="top middle"
@before-show="beforeShow"
>
<csc-cf-group-condition-menu
v-if="internalStep === 'menu'"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@step="internalStep=$event"
@close="closePopup"
/>
<csc-cf-group-condition-source-set-create
v-if="internalStep === 'call-from'"
mode="whitelist"
:title="$t('call from ...')"
icon="person_add"
:back-button="true"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@back="internalStep='menu'"
@select="internalStep='call-from-select'"
@close="closePopup"
/>
<csc-cf-group-condition-source-set-select
v-if="internalStep === 'call-from-select'"
mode="whitelist"
:title="$t('call from ...')"
icon="person_add"
:create-label="$t('Create List')"
:back-button="true"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@back="internalStep='call-from'"
@create="internalStep='call-from'"
@close="closePopup"
/>
<csc-cf-group-condition-source-set-create
v-if="internalStep === 'call-not-from'"
mode="blacklist"
:title="$t('call not from ...')"
icon="person_add_disabled"
:back-button="true"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@back="internalStep='menu'"
@select="internalStep='call-not-from-select'"
@close="closePopup"
/>
<csc-cf-group-condition-source-set-select
v-if="internalStep === 'call-not-from-select'"
mode="blacklist"
:title="$t('call not from ...')"
icon="person_add_disabled"
:create-label="$t('Create List')"
:back-button="true"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@back="internalStep='call-not-from'"
@create="internalStep='call-not-from'"
@close="closePopup"
/>
<csc-cf-group-condition-date
v-if="internalStep === 'date-is'"
:back-button="true"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@back="internalStep='menu'"
@close="closePopup"
/>
<csc-cf-group-condition-date-range
v-if="internalStep === 'date-range-is'"
:back-button="true"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@back="internalStep='menu'"
@close="closePopup"
/>
<csc-cf-group-condition-weekdays
v-if="internalStep === 'date-weekdays'"
:back-button="true"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@back="internalStep='menu'"
@close="closePopup"
/>
<csc-cf-group-condition-office-hours
v-if="internalStep.startsWith('office-hours')"
:step="internalStep"
:back-button="true"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@navigate="navigate"
@back="internalStep='menu'"
@close="closePopup"
/>
</q-popup-proxy>
</template>
<script>
import CscCfGroupConditionMenu from 'components/call-forwarding/CscCfGroupConditionMenu'
import CscCfGroupConditionSourceSetCreate from 'components/call-forwarding/CscCfGroupConditionSourceSetCreate'
import CscCfGroupConditionSourceSetSelect from 'components/call-forwarding/CscCfGroupConditionSourceSetSelect'
import CscCfGroupConditionDate from 'components/call-forwarding/CscCfGroupConditionDate'
import CscCfGroupConditionDateRange from 'components/call-forwarding/CscCfGroupConditionDateRange'
import CscCfGroupConditionWeekdays from 'components/call-forwarding/CscCfGroupConditionWeekdays'
import CscCfGroupConditionOfficeHours from 'components/call-forwarding/CscCfGroupConditionOfficeHours'
export default {
name: 'CscCfConditionPopupAll',
components: {
CscCfGroupConditionOfficeHours,
CscCfGroupConditionWeekdays,
CscCfGroupConditionDateRange,
CscCfGroupConditionDate,
CscCfGroupConditionSourceSetSelect,
CscCfGroupConditionSourceSetCreate,
CscCfGroupConditionMenu
},
props: {
step: {
type: String,
required: true
},
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
}
},
data () {
return {
closed: false,
internalStep: this.step,
selectedSourceSet: null
}
},
watch: {
internalStep () {
if (!this.closed) {
this.$refs.popup.hide()
this.$nextTick(() => {
this.$refs.popup.show()
})
}
}
},
methods: {
beforeShow () {
this.closed = false
},
closePopup () {
this.closed = true
this.internalStep = 'menu'
this.$refs.popup.hide()
},
openPopup () {
if (!this.closed) {
this.$refs.popup.hide()
this.$nextTick(() => {
this.$refs.popup.show()
})
}
},
navigate (step) {
this.internalStep = step
this.openPopup()
}
}
}
</script>

@ -0,0 +1,96 @@
<template>
<q-popup-proxy
ref="popup"
persistent
anchor="bottom middle"
self="top middle"
@before-show="beforeShow"
>
<csc-cf-group-condition-source-set-create
v-if="internalStep === 'call-from'"
mode="whitelist"
:title="$t('call from ...')"
icon="person_add"
:back-button="false"
:delete-button="true"
:unassign-button="true"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@select="internalStep='call-from-select'"
@close="closePopup"
/>
<csc-cf-group-condition-source-set-select
v-if="internalStep === 'call-from-select'"
mode="whitelist"
:title="$t('call from ...')"
icon="person_add"
:create-label="$t('Edit List')"
:back-button="true"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@back="internalStep='call-from'"
@create="internalStep='call-from'"
@close="closePopup"
/>
</q-popup-proxy>
</template>
<script>
import CscCfGroupConditionSourceSetCreate from 'components/call-forwarding/CscCfGroupConditionSourceSetCreate'
import CscCfGroupConditionSourceSetSelect from 'components/call-forwarding/CscCfGroupConditionSourceSetSelect'
export default {
name: 'CscCfConditionPopupCallFrom',
components: {
CscCfGroupConditionSourceSetSelect,
CscCfGroupConditionSourceSetCreate
},
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
}
},
data () {
return {
internalStep: 'call-from',
selectedSourceSet: null
}
},
watch: {
internalStep () {
if (!this.closed) {
this.$refs.popup.hide()
this.$nextTick(() => {
this.$refs.popup.show()
})
}
}
},
methods: {
beforeShow () {
this.closed = false
},
closePopup () {
this.closed = true
this.internalStep = 'call-from'
this.$refs.popup.hide()
}
}
}
</script>

@ -0,0 +1,96 @@
<template>
<q-popup-proxy
ref="popup"
persistent
anchor="bottom middle"
self="top middle"
@before-show="beforeShow"
>
<csc-cf-group-condition-source-set-create
v-if="internalStep === 'call-not-from'"
mode="blacklist"
:title="$t('call not from ...')"
icon="person_add_disabled"
:back-button="false"
:delete-button="true"
:unassign-button="true"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@select="internalStep='call-not-from-select'"
@close="closePopup"
/>
<csc-cf-group-condition-source-set-select
v-if="internalStep === 'call-not-from-select'"
mode="blacklist"
:title="$t('call not from ...')"
icon="person_add_disabled"
:create-label="$t('Edit List')"
:back-button="true"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@back="internalStep='call-not-from'"
@create="internalStep='call-not-from'"
@close="closePopup"
/>
</q-popup-proxy>
</template>
<script>
import CscCfGroupConditionSourceSetCreate from 'components/call-forwarding/CscCfGroupConditionSourceSetCreate'
import CscCfGroupConditionSourceSetSelect from 'components/call-forwarding/CscCfGroupConditionSourceSetSelect'
export default {
name: 'CscCfConditionPopupCallNotFrom',
components: {
CscCfGroupConditionSourceSetSelect,
CscCfGroupConditionSourceSetCreate
},
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
}
},
data () {
return {
internalStep: 'call-not-from',
selectedSourceSet: null
}
},
watch: {
internalStep () {
if (!this.closed) {
this.$refs.popup.hide()
this.$nextTick(() => {
this.$refs.popup.show()
})
}
}
},
methods: {
beforeShow () {
this.closed = false
},
closePopup () {
this.closed = true
this.internalStep = 'call-not-from'
this.$refs.popup.hide()
}
}
}
</script>

@ -0,0 +1,55 @@
<template>
<q-popup-proxy
ref="popup"
persistent
anchor="bottom middle"
self="top middle"
@before-show="beforeShow"
>
<csc-cf-group-condition-date
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
:delete-button="true"
@close="closePopup"
/>
</q-popup-proxy>
</template>
<script>
import CscCfGroupConditionDate from 'components/call-forwarding/CscCfGroupConditionDate'
export default {
name: 'CscCfConditionPopupDate',
components: {
CscCfGroupConditionDate
},
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
}
},
methods: {
beforeShow () {
this.closed = false
},
closePopup () {
this.closed = true
this.$refs.popup.hide()
}
}
}
</script>

@ -0,0 +1,55 @@
<template>
<q-popup-proxy
ref="popup"
persistent
anchor="bottom middle"
self="top middle"
@before-show="beforeShow"
>
<csc-cf-group-condition-date-range
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
:delete-button="true"
@close="closePopup"
/>
</q-popup-proxy>
</template>
<script>
import CscCfGroupConditionDateRange from 'components/call-forwarding/CscCfGroupConditionDateRange'
export default {
name: 'CscCfConditionPopupDateRange',
components: {
CscCfGroupConditionDateRange
},
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
}
},
methods: {
beforeShow () {
this.closed = false
},
closePopup () {
this.closed = true
this.$refs.popup.hide()
}
}
}
</script>

@ -0,0 +1,55 @@
<template>
<q-popup-proxy
ref="popup"
persistent
anchor="bottom middle"
self="top middle"
@before-show="beforeShow"
>
<csc-cf-group-condition-office-hours
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
:delete-button="true"
@close="closePopup"
/>
</q-popup-proxy>
</template>
<script>
import CscCfGroupConditionOfficeHours from 'components/call-forwarding/CscCfGroupConditionOfficeHours'
export default {
name: 'CscCfConditionPopupOfficeHours',
components: {
CscCfGroupConditionOfficeHours
},
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
}
},
methods: {
beforeShow () {
this.closed = false
},
closePopup () {
this.closed = true
this.$refs.popup.hide()
}
}
}
</script>

@ -0,0 +1,55 @@
<template>
<q-popup-proxy
ref="popup"
persistent
anchor="bottom middle"
self="top middle"
@before-show="beforeShow"
>
<csc-cf-group-condition-weekdays
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
:delete-button="true"
@close="closePopup"
/>
</q-popup-proxy>
</template>
<script>
import CscCfGroupConditionWeekdays from 'components/call-forwarding/CscCfGroupConditionWeekdays'
export default {
name: 'CscCfConditionPopupWeekdays',
components: {
CscCfGroupConditionWeekdays
},
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
}
},
methods: {
beforeShow () {
this.closed = false
},
closePopup () {
this.closed = true
this.$refs.popup.hide()
}
}
}
</script>

@ -0,0 +1,91 @@
<template>
<q-list
class="relative-position"
dense
separator
>
<csc-cf-group-title
ref="cfGroupTitle"
:loading="loading"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
/>
<template
v-if="destinationSet"
>
<csc-cf-group-item-primary-number
v-if="mapping.type === 'cft'"
:loading="loading"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
/>
<csc-cf-group-item
v-for="(destination, destinationIndex) in destinationSet.destinations"
:key="destinationIndex"
:loading="loading"
:destination="destination"
:destination-previous="(destinationIndex > 0)?destinationSet.destinations[destinationIndex - 1]:null"
:destination-index="destinationIndex"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
@delete-last="$refs.cfGroupTitle.deleteMappingEvent(mapping)"
/>
</template>
<q-inner-loading
:showing="$wait.is(waitIdentifier)"
color="primary"
class="bg-main-menu"
>
<csc-spinner />
</q-inner-loading>
</q-list>
</template>
<script>
import CscCfGroupTitle from 'components/call-forwarding/CscCfGroupTitle'
import CscCfGroupItem from 'components/call-forwarding/CscCfGroupItem'
import CscSpinner from 'components/CscSpinner'
import CscCfGroupItemPrimaryNumber from 'components/call-forwarding/CscCfGroupItemPrimaryNumber'
export default {
name: 'CscCfGroup',
components: {
CscCfGroupItemPrimaryNumber,
CscSpinner,
CscCfGroupItem,
CscCfGroupTitle
},
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
},
loading: {
type: Boolean,
default: false
}
},
computed: {
waitIdentifier () {
return 'csc-cf-group-' + this.destinationSet.id
}
}
}
</script>

@ -0,0 +1,90 @@
<template>
<q-card
class="bg-dark no-padding no-margin relative-position"
>
<q-item
class="no-margin no-padding"
dense
>
<q-item-section
side
>
<q-btn
v-if="backButton"
icon="arrow_back"
flat
dense
@click="$emit('back')"
/>
</q-item-section>
<q-item-section>
<q-item-label
class="text-subtitle1"
>
<q-icon
v-if="icon"
:name="icon"
/>
{{ title }}
</q-item-label>
</q-item-section>
<q-item-section
side
>
<q-btn
icon="clear"
flat
dense
@click="$emit('close')"
/>
</q-item-section>
</q-item>
<q-card-section
class="no-padding no-margin"
>
<slot
name="default"
/>
</q-card-section>
<q-card-actions
align="right"
>
<slot
name="actions"
/>
</q-card-actions>
<q-inner-loading
:showing="loading"
>
<csc-spinner />
</q-inner-loading>
</q-card>
</template>
<script>
import CscSpinner from 'components/CscSpinner'
export default {
name: 'CscCfGroupCondition',
components: {
CscSpinner
},
props: {
title: {
type: String,
required: true
},
icon: {
type: String,
default: undefined
},
loading: {
type: Boolean,
default: false
},
backButton: {
type: Boolean,
default: false
}
}
}
</script>

@ -0,0 +1,123 @@
<template>
<csc-cf-group-condition
:title="$t('date is ...')"
icon="today"
:loading="$wait.is('csc-cf-time-set-create')"
v-bind="$attrs"
v-on="$listeners"
>
<q-date
v-model="selectedDate"
class="no-margin no-padding"
flat
square
minimal
/>
<template
v-slot:actions
>
<q-btn
v-if="deleteButton"
:label="$t('Delete')"
flat
color="negative"
icon="delete"
@click="deleteSourceSetEvent"
/>
<q-btn
:label="$t('Save')"
flat
color="primary"
icon="check"
:disable="!selectedDate"
@click="createTimeSetEvent"
/>
</template>
</csc-cf-group-condition>
</template>
<script>
import CscCfGroupCondition from 'components/call-forwarding/CscCfGroupCondition'
import { mapActions } from 'vuex'
import { timeSetDateExact } from 'src/filters/time-set'
export default {
name: 'CscCfGroupConditionDate',
components: {
CscCfGroupCondition
},
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
},
deleteButton: {
type: Boolean,
default: false
}
},
data () {
return {
selectedDate: this.formattedDate
}
},
computed: {
formattedDate () {
if (this.timeSet) {
return timeSetDateExact(this.timeSet.times)
}
return null
}
},
mounted () {
this.selectedDate = this.formattedDate
},
methods: {
...mapActions('callForwarding', [
'createTimeSetDate',
'updateTimeSetDate',
'deleteTimeSet'
]),
async createTimeSetEvent () {
const dateParts = this.selectedDate.split('/')
if (this.timeSet) {
await this.updateTimeSetDate({
mapping: this.mapping,
id: this.timeSet.id,
date: {
date: dateParts[2],
month: dateParts[1],
year: dateParts[0]
}
})
} else {
await this.createTimeSetDate({
mapping: this.mapping,
date: {
date: dateParts[2],
month: dateParts[1],
year: dateParts[0]
}
})
}
this.$emit('close')
},
async deleteSourceSetEvent () {
await this.deleteTimeSet({
mapping: this.mapping,
id: this.timeSet.id
})
}
}
}
</script>

@ -0,0 +1,137 @@
<template>
<csc-cf-group-condition
:title="$t('date range is ...')"
icon="book_online"
:loading="$wait.is('csc-cf-time-set-create')"
v-bind="$attrs"
v-on="$listeners"
>
<q-date
v-model="selectedDate"
class="no-margin no-padding"
flat
square
minimal
range
/>
<template
v-slot:actions
>
<q-btn
v-if="deleteButton"
:label="$t('Delete')"
flat
color="negative"
icon="delete"
@click="deleteSourceSetEvent"
/>
<q-btn
:label="$t('Save')"
flat
color="primary"
icon="check"
:disable="!selectedDate"
@click="createTimeSetEvent"
/>
</template>
</csc-cf-group-condition>
</template>
<script>
import CscCfGroupCondition from 'components/call-forwarding/CscCfGroupCondition'
import { mapActions } from 'vuex'
import { timeSetDateRange } from 'src/filters/time-set'
export default {
name: 'CscCfGroupConditionDateRange',
components: {
CscCfGroupCondition
},
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
},
deleteButton: {
type: Boolean,
default: false
}
},
data () {
return {
selectedDate: this.transformedDate
}
},
computed: {
formattedDate () {
if (this.timeSet) {
return timeSetDateRange(this.timeSet.times)
}
return null
},
transformedDate () {
if (this.timeSet) {
const dateRangeParts = timeSetDateRange(this.timeSet.times).split('-')
return {
from: dateRangeParts[0],
to: dateRangeParts[1]
}
} else {
return null
}
}
},
mounted () {
this.selectedDate = this.transformedDate
},
methods: {
...mapActions('callForwarding', [
'createTimeSetDateRange',
'updateTimeSetDateRange',
'deleteTimeSet'
]),
async createTimeSetEvent () {
const datePartsFrom = this.selectedDate.from.split('/')
const datePartsTo = this.selectedDate.to.split('/')
const payload = {
mapping: this.mapping,
date: {
from: {
date: datePartsFrom[2],
month: datePartsFrom[1],
year: datePartsFrom[0]
},
to: {
date: datePartsTo[2],
month: datePartsTo[1],
year: datePartsTo[0]
}
}
}
if (this.timeSet) {
payload.id = this.timeSet.id
await this.updateTimeSetDateRange(payload)
} else {
await this.createTimeSetDateRange(payload)
}
this.$emit('close')
},
async deleteSourceSetEvent () {
await this.deleteTimeSet({
mapping: this.mapping,
id: this.timeSet.id
})
}
}
}
</script>

@ -0,0 +1,80 @@
<template>
<csc-cf-group-condition
:title="$t('')"
v-on="$listeners"
>
<q-list
dense
>
<csc-popup-menu-item
icon="person_add"
:label="$t('call from ...')"
:close-popup="false"
:disable="!!sourceSet"
@click="$emit('step', 'call-from')"
/>
<csc-popup-menu-item
icon="person_add_disabled"
:label="$t('call not from ...')"
:close-popup="false"
:disable="!!sourceSet"
@click="$emit('step', 'call-not-from')"
/>
<csc-popup-menu-item
icon="today"
:label="$t('date is ...')"
:close-popup="false"
:disable="!!timeSet"
@click="$emit('step', 'date-is')"
/>
<csc-popup-menu-item
icon="book_online"
:label="$t('date range is ...')"
:close-popup="false"
:disable="!!timeSet"
@click="$emit('step', 'date-range-is')"
/>
<csc-popup-menu-item
icon="calendar_today"
:label="$t('weekdays are ...')"
:close-popup="false"
:disable="!!timeSet"
@click="$emit('step', 'date-weekdays')"
/>
<csc-popup-menu-item
icon="access_time"
:label="$t('office hours are ...')"
:close-popup="false"
:disable="!!timeSet"
@click="$emit('step', 'office-hours-times')"
/>
</q-list>
</csc-cf-group-condition>
</template>
<script>
import CscPopupMenuItem from 'components/CscPopupMenuItem'
import CscCfGroupCondition from 'components/call-forwarding/CscCfGroupCondition'
export default {
name: 'CscCfGroupConditionMenu',
components: { CscCfGroupCondition, CscPopupMenuItem },
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
}
}
}
</script>

@ -0,0 +1,388 @@
<template>
<csc-cf-group-condition
:title="$t('office hours are ...')"
icon="access_time"
:loading="$wait.is('csc-cf-time-set-create')"
v-bind="$attrs"
@back="back"
@close="$emit('close')"
>
<div
class="row justify-center q-pt-md"
>
<q-checkbox
v-model="sameTimes"
:label="$t('Same time for selected days')"
/>
</div>
<q-list
dense
>
<q-item
class="q-mb-md q-mt-md"
>
<q-item-section>
<csc-cf-selection-weekdays
v-model="weekdays"
:tabs="!sameTimes"
/>
</q-item-section>
</q-item>
<q-item
v-for="(time, index) in times"
:key="index"
>
<q-item-section>
<csc-input
v-model="times[index].from"
dense
:label="$t('Start time')"
mask="##:##"
fill-mask
:disable="disabled"
>
<template
v-slot:append
>
<q-btn
icon="access_time"
dense
flat
color="primary"
>
<q-popup-proxy
ref="startTimePopup"
>
<q-time
v-model="times[index].from"
flat
now-btn
square
format24h
text-color="dark"
color="primary"
@input="$refs.startTimePopup[index].hide()"
/>
</q-popup-proxy>
</q-btn>
</template>
</csc-input>
</q-item-section>
<q-item-section>
<csc-input
v-model="times[index].to"
dense
:label="$t('End time')"
mask="##:##"
fill-mask
:disable="disabled"
>
<template
v-slot:append
>
<q-btn
icon="access_time"
dense
flat
color="primary"
>
<q-popup-proxy
ref="endTimePopup"
>
<q-time
v-model="times[index].to"
flat
now-btn
square
format24h
text-color="dark"
color="primary"
@input="$refs.endTimePopup[index].hide()"
/>
</q-popup-proxy>
</q-btn>
</template>
</csc-input>
</q-item-section>
<q-item-section
side
>
<q-btn
flat
dense
color="negative"
icon="delete"
:disable="index === 0 || disabled"
@click="removeTime(index)"
/>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-btn
color="primary"
icon="add"
flat
:label="$t('Add time')"
:disable="disabled"
@click="addTime"
/>
</q-item-section>
</q-item>
</q-list>
<template
v-slot:actions
>
<q-btn
v-if="deleteButton"
:label="$t('Delete')"
flat
color="negative"
icon="delete"
:disable="disabled"
@click="deleteTimeSetEvent"
/>
<q-btn
:label="$t('Save')"
flat
color="primary"
icon="check"
:disable="disabled"
@click="createTimeSetOfficeHoursEvent"
/>
</template>
</csc-cf-group-condition>
</template>
<script>
import CscCfGroupCondition from 'components/call-forwarding/CscCfGroupCondition'
import CscInput from 'components/form/CscInput'
import CscCfSelectionWeekdays from 'components/call-forwarding/CscCfSelectionWeekdays'
import { DAY_MAP, DEFAULT_WEEKDAYS } from 'src/filters/time-set'
import { mapActions } from 'vuex'
export default {
name: 'CscCfGroupConditionOfficeHours',
components: {
CscCfSelectionWeekdays,
CscInput,
CscCfGroupCondition
},
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
},
deleteButton: {
type: Boolean,
default: false
}
},
data () {
return {
sameTimes: this.isSameTimes(),
weekdays: DEFAULT_WEEKDAYS,
timesAll: [{
from: '',
to: ''
}],
timesDay1: [{
from: '',
to: ''
}],
timesDay2: [{
from: '',
to: ''
}],
timesDay3: [{
from: '',
to: ''
}],
timesDay4: [{
from: '',
to: ''
}],
timesDay5: [{
from: '',
to: ''
}],
timesDay6: [{
from: '',
to: ''
}],
timesDay7: [{
from: '',
to: ''
}]
}
},
computed: {
disabled () {
return this.sameTimes && this.weekdays.length === 0
},
times () {
if (this.sameTimes) {
return this.timesAll
} else {
return this['timesDay' + this.weekdays[0]]
}
}
},
watch: {
timeSet () {
this.transformTimeSet()
},
sameTimes (sameTimes) {
this.transformTimeSet()
}
},
mounted () {
this.transformTimeSet()
},
methods: {
...mapActions('callForwarding', [
'createOfficeHoursSameTimes',
'updateOfficeHoursSameTimes',
'createOfficeHours',
'updateOfficeHours',
'deleteTimeSet'
]),
back () {
this.$emit('back')
},
isSameTimes () {
if (this.timeSet) {
return this.timeSet.name.startsWith('csc-office-hours-same-times')
}
return true
},
transformTimeSet () {
if (this.timeSet && this.timeSet.name.startsWith('csc-office-hours-same-times')) {
const weekdays = new Set()
const times = new Set()
this.timeSet.times.forEach((time) => {
if (time.wday !== null && time.hour !== null && time.minute !== null) {
weekdays.add(parseInt(time.wday))
times.add(time.hour + ':' + time.minute)
}
})
this.weekdays = Array.from(weekdays)
const timesAll = []
Array.from(times).forEach((time) => {
const timeParts = time.split(':')
const hourParts = timeParts[0].split('-')
const minuteParts = timeParts[1].split('-')
timesAll.push({
from: hourParts[0] + ':' + minuteParts[0],
to: hourParts[1] + ':' + minuteParts[1]
})
})
this.timesAll = timesAll
} else if (this.timeSet && this.timeSet.name.startsWith('csc-office-hours')) {
DAY_MAP.forEach((day) => {
this['timesDay' + day] = []
})
this.timeSet.times.forEach((time) => {
if (time.wday !== null && time.hour !== null && time.minute !== null) {
const hourParts = time.hour.split('-')
const minuteParts = time.minute.split('-')
this['timesDay' + time.wday].push({
from: hourParts[0] + ':' + minuteParts[0],
to: hourParts[1] + ':' + minuteParts[1]
})
}
})
DAY_MAP.forEach((day) => {
if (this['timesDay' + day].length === 0) {
this['timesDay' + day] = [{
from: '',
to: ''
}]
}
})
}
},
addTime () {
if (!this.sameTimes) {
this['timesDay' + this.weekdays[0]].push({
from: '',
to: ''
})
} else {
this.timesAll.push({
from: '',
to: ''
})
}
},
removeTime (index) {
if (!this.sameTimes) {
this['timesDay' + this.weekdays[0]] = this['timesDay' + this.weekdays[0]].filter((time, timeIndex) => timeIndex !== index)
} else {
this.timesAll = this.timesAll.filter((time, timeIndex) => timeIndex !== index)
}
},
resetTimesPerDay () {
DAY_MAP.forEach((day) => {
this['timesDay' + day] = [{
from: '',
to: ''
}]
})
},
async createTimeSetOfficeHoursEvent () {
const payload = {
mapping: this.mapping
}
if (this.timeSet) {
payload.id = this.timeSet.id
}
if (!this.sameTimes) {
payload.times = [
this.timesDay1,
this.timesDay2,
this.timesDay3,
this.timesDay4,
this.timesDay5,
this.timesDay6,
this.timesDay7
]
} else {
payload.times = this.timesAll
payload.weekdays = this.weekdays
}
if (this.timeSet && this.timeSet.name.startsWith('csc-office-hours-same-times') && this.sameTimes) {
await this.updateOfficeHoursSameTimes(payload)
} else if (this.timeSet && this.timeSet.name.startsWith('csc-office-hours-same-times') && !this.sameTimes) {
await this.createOfficeHours(payload)
} else if (this.timeSet && this.timeSet.name.startsWith('csc-office-hours') && this.sameTimes) {
await this.createOfficeHoursSameTimes(payload)
} else if (this.timeSet && this.timeSet.name.startsWith('csc-office-hours') && !this.sameTimes) {
await this.updateOfficeHours(payload)
} else if (!this.timeSet && this.sameTimes) {
await this.createOfficeHoursSameTimes(payload)
} else {
await this.createOfficeHours(payload)
}
this.$emit('close')
},
async deleteTimeSetEvent () {
await this.deleteTimeSet({
mapping: this.mapping,
id: this.timeSet.id
})
}
}
}
</script>

@ -0,0 +1,229 @@
<template>
<csc-cf-group-condition
:title="$t(title)"
:loading="$wait.is('csc-cf-source-set-create')"
v-bind="$attrs"
v-on="$listeners"
>
<q-list
class="no-margin q-pa-md"
dense
>
<q-item
class="no-margin no-padding"
>
<q-item-section>
<csc-input
v-model="sourceSetNameInternal"
dense
clearable
:label="$t('Number list name')"
/>
</q-item-section>
</q-item>
<q-item
v-for="(number, index) in sourceSetNumbersInternal"
:key="index"
class="no-margin no-padding"
>
<q-item-section>
<csc-input
v-model="sourceSetNumbersInternal[index]"
dense
clearable
:label="$t('Number')"
>
<template
v-if="index > 0"
>
<q-btn
flat
dense
color="negative"
icon="delete"
@click="deleteNumber(index)"
/>
</template>
</csc-input>
</q-item-section>
</q-item>
<q-item
class="no-margin no-padding"
>
<q-item-section>
<q-btn
:label="$t('Add number')"
flat
color="primary"
icon="add"
@click="sourceSetNumbersInternal.push('')"
/>
</q-item-section>
</q-item>
</q-list>
<template
v-slot:actions
>
<q-btn
v-if="deleteButton"
:label="$t('Delete')"
flat
color="negative"
icon="delete"
@click="deleteSourceSetEvent"
/>
<q-btn
v-if="unassignButton"
:label="$t('Unassign')"
flat
color="primary"
icon="undo"
@click="unassignSourceSetEvent"
/>
<q-btn
:label="$t('Select')"
flat
color="primary"
icon="source"
@click="$emit('select')"
/>
<q-btn
:label="$t('Save')"
flat
color="primary"
icon="check"
@click="createSourceSetEvent"
/>
</template>
</csc-cf-group-condition>
</template>
<script>
import CscInput from 'components/form/CscInput'
import {
mapActions
} from 'vuex'
import CscCfGroupCondition from 'components/call-forwarding/CscCfGroupCondition'
export default {
name: 'CscCfGroupConditionSourceSetCreate',
components: {
CscCfGroupCondition,
CscInput
},
props: {
title: {
type: String,
required: true
},
mode: {
type: String,
required: true,
validator (value) {
return ['whitelist', 'blacklist'].includes(value.toLowerCase())
}
},
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
},
deleteButton: {
type: Boolean,
default: false
},
unassignButton: {
type: Boolean,
default: false
}
},
data () {
return {
sourceSetNameInternal: this.sourceSetName,
sourceSetNumbersInternal: this.sourceSetNumbers
}
},
computed: {
sourceSetNumbers () {
const numbers = []
if (this.sourceSet && this.sourceSet.sources) {
this.sourceSet.sources.forEach((source) => {
numbers.push(source.source)
})
} else {
numbers.push('')
}
return numbers
},
sourceSetName () {
let name = this.$t('MyNumberList')
if (this.sourceSet) {
name = this.sourceSet.name
}
return name
}
},
mounted () {
this.sourceSetNameInternal = this.sourceSetName
this.sourceSetNumbersInternal = this.sourceSetNumbers
},
methods: {
...mapActions('callForwarding', [
'createSourceSet',
'updateSourceSet',
'deleteSourceSet',
'unassignSourceSet'
]),
deleteNumber (index) {
this.sourceSetNumbersInternal = this.sourceSetNumbersInternal.filter((number, numberIndex) => {
return numberIndex !== index
})
},
async createSourceSetEvent () {
if (this.sourceSet) {
await this.updateSourceSet({
mapping: this.mapping,
id: this.sourceSet.id,
name: this.sourceSetNameInternal,
numbers: this.sourceSetNumbersInternal,
mode: this.mode
})
} else {
await this.createSourceSet({
mapping: this.mapping,
name: this.sourceSetNameInternal,
numbers: this.sourceSetNumbersInternal,
mode: this.mode
})
}
this.$emit('close')
},
async deleteSourceSetEvent () {
if (this.sourceSet) {
await this.deleteSourceSet({
mapping: this.mapping,
id: this.sourceSet.id
})
}
},
async unassignSourceSetEvent () {
if (this.sourceSet) {
await this.unassignSourceSet({
mapping: this.mapping,
id: this.sourceSet.id
})
}
}
}
}
</script>

@ -0,0 +1,103 @@
<template>
<csc-cf-group-condition
:title="$t(title)"
:loading="$wait.is('csc-cf-source-set-create')"
v-bind="$attrs"
v-on="$listeners"
>
<div
class="no-margin q-pa-md"
>
<csc-cf-source-set-selection
v-model="selectedSourceSet"
:mode="mode"
dense
:label="$t('Number list')"
/>
</div>
<template
v-slot:actions
>
<q-btn
:label="createLabel"
flat
color="primary"
icon="source"
@click="$emit('create')"
/>
<q-btn
:label="$t('Save')"
flat
color="primary"
icon="check"
:disable="!selectedSourceSet"
@click="selectSourceSetEvent"
/>
</template>
</csc-cf-group-condition>
</template>
<script>
import {
mapActions
} from 'vuex'
import CscCfGroupCondition from 'components/call-forwarding/CscCfGroupCondition'
import CscCfSourceSetSelection from 'components/call-forwarding/CscCfSourceSetSelection'
export default {
name: 'CscCfGroupConditionSourceSetSelect',
components: {
CscCfSourceSetSelection,
CscCfGroupCondition
},
props: {
title: {
type: String,
required: true
},
mode: {
type: String,
required: true,
validator (value) {
return ['whitelist', 'blacklist'].includes(value.toLowerCase())
}
},
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
},
createLabel: {
type: String,
required: true
}
},
data () {
return {
selectedSourceSet: null
}
},
methods: {
...mapActions('callForwarding', [
'assignSourceSet'
]),
async selectSourceSetEvent () {
await this.assignSourceSet({
mapping: this.mapping,
id: this.selectedSourceSet
})
this.$emit('close')
}
}
}
</script>

@ -0,0 +1,113 @@
<template>
<csc-cf-group-condition
:title="$t('weekdays are ...')"
icon="calendar_today"
:loading="$wait.is('csc-cf-time-set-create')"
v-bind="$attrs"
v-on="$listeners"
>
<csc-cf-selection-weekdays
v-model="selectedWeekdays"
class="q-pl-md q-pr-md q-pt-sm q-pb-sm"
/>
<template
v-slot:actions
>
<q-btn
v-if="deleteButton"
:label="$t('Delete')"
flat
color="negative"
icon="delete"
@click="deleteTimeSetEvent"
/>
<q-btn
:label="$t('Save')"
flat
color="primary"
icon="check"
:disable="selectedWeekdays && selectedWeekdays.length === 0"
@click="createTimeSetEvent"
/>
</template>
</csc-cf-group-condition>
</template>
<script>
import CscCfGroupCondition from 'components/call-forwarding/CscCfGroupCondition'
import { mapActions } from 'vuex'
import CscCfSelectionWeekdays from 'components/call-forwarding/CscCfSelectionWeekdays'
export default {
name: 'CscCfGroupConditionWeekdays',
components: {
CscCfSelectionWeekdays,
CscCfGroupCondition
},
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
},
deleteButton: {
type: Boolean,
default: false
}
},
data () {
return {
selectedWeekdays: this.weekdays
}
},
computed: {
weekdays () {
const weekdays = []
if (this.timeSet) {
this.timeSet.times.forEach((weekday) => {
weekdays.push(parseInt(weekday.wday))
})
}
return weekdays
}
},
mounted () {
this.selectedWeekdays = this.weekdays
},
methods: {
...mapActions('callForwarding', [
'createTimeSetWeekdays',
'updateTimeSetWeekdays',
'deleteTimeSet'
]),
async createTimeSetEvent () {
const payload = {
mapping: this.mapping,
weekdays: this.selectedWeekdays
}
if (this.timeSet) {
payload.id = this.timeSet.id
await this.updateTimeSetWeekdays(payload)
} else {
await this.createTimeSetWeekdays(payload)
}
this.$emit('close')
},
async deleteTimeSetEvent () {
await this.deleteTimeSet({
mapping: this.mapping,
id: this.timeSet.id
})
}
}
}
</script>

@ -0,0 +1,307 @@
<template>
<q-item
:disable="loading || !mapping.enabled"
>
<q-item-section
side
>
<q-icon
name="subdirectory_arrow_right"
/>
</q-item-section>
<q-item-section>
<q-item-label>
<template
v-if="destinationIndex === 0 && mapping.type !== 'cft'"
>
{{ $t('Forwarded to') }}
</template>
<template
v-else-if="destinationIndex === 0 && mapping.type === 'cft'"
>
{{ $t('After') }}
<span
class="q-pl-xs q-pr-xs text-primary text-weight-bold cursor-pointer"
style="white-space: nowrap"
>
<q-icon
name="access_time"
/>
{{ ringTimeout }}
{{ $t('seconds') }}
<q-popup-edit
v-model="changedDestinationTimeout"
buttons
@save="updateRingTimeoutEvent()"
>
<csc-input
v-model="changedDestinationTimeout"
type="number"
dense
>
<template
v-slot:prepend
>
<q-icon
name="access_time"
/>
</template>
</csc-input>
</q-popup-edit>
</span>
{{ $t('forwarded to') }}
</template>
<template
v-else
>
{{ $t('After') }}
<span
class="q-pl-xs q-pr-xs text-primary text-weight-bold cursor-pointer"
style="white-space: nowrap"
>
<q-icon
name="access_time"
/>
{{ destinationPrevious.timeout }}
{{ $t('seconds') }}
<q-popup-edit
v-model="changedDestinationTimeout"
buttons
@save="updateDestinationTimeoutEvent({
destinationTimeout: changedDestinationTimeout,
destinationIndex: destinationIndex - 1,
destinationSetId: destinationSet.id
})"
>
<csc-input
v-model="changedDestinationTimeout"
type="number"
dense
>
<template
v-slot:prepend
>
<q-icon
name="access_time"
/>
</template>
</csc-input>
</q-popup-edit>
</span>
{{ $t('forwarded to') }}
</template>
<span
v-if="destination.destination.endsWith('voicebox.local')"
class="q-pl-xs text-weight-bold"
style="white-space: nowrap"
>
<q-icon
name="voicemail"
/>
{{ $t('Voicebox') }}
</span>
<span
v-else-if="destination.destination.endsWith('fax2mail.local')"
class="q-pl-xs text-weight-bold"
style="white-space: nowrap"
>
<q-icon
name="email"
/>
{{ $t('Fax2Mail') }}
</span>
<span
v-else-if="destination.destination.endsWith('managersecretary.local')"
class="q-pl-xs text-weight-bold"
style="white-space: nowrap"
>
<q-icon
name="phone_forwarded"
/>
{{ $t('ManagerSecretary') }}
</span>
<span
v-else-if="destination.destination.endsWith('conference.local')"
class="q-pl-xs text-weight-bold"
style="white-space: nowrap"
>
<q-icon
name="groups"
/>
{{ $t('Conference') }}
</span>
<span
v-else-if="destination.destination.endsWith('app.local')"
class="q-pl-xs text-weight-bold"
style="white-space: nowrap"
>
<q-icon
name="app"
/>
{{ $t('Application') }}
</span>
<span
v-else
class="q-pl-xs text-primary text-weight-bold cursor-pointer"
style="white-space: nowrap"
>
<q-icon
name="phone_forwarded"
/>
{{ destination.simple_destination }}
<q-popup-edit
v-model="changedDestination"
buttons
@save="updateDestinationEvent({
destination: changedDestination,
destinationIndex: destinationIndex,
destinationSetId: destinationSet.id
})"
>
<csc-input
v-model="changedDestination"
dense
>
<template
v-slot:prepend
>
<q-icon
name="phone_forwarded"
/>
</template>
</csc-input>
</q-popup-edit>
</span>
</q-item-label>
</q-item-section>
<q-item-section
side
>
<csc-more-menu>
<csc-popup-menu-item-delete
@click="removeDestinationEvent({
destinationIndex: destinationIndex,
destinationSetId: destinationSet.id
})"
/>
</csc-more-menu>
</q-item-section>
<q-inner-loading
:showing="$wait.is(waitIdentifier)"
color="primary"
class="bg-main-menu"
>
<csc-spinner />
</q-inner-loading>
</q-item>
</template>
<script>
import {
mapActions,
mapGetters
} from 'vuex'
import CscMoreMenu from 'components/CscMoreMenu'
import CscPopupMenuItemDelete from 'components/CscPopupMenuItemDelete'
import CscInput from 'components/form/CscInput'
import CscSpinner from 'components/CscSpinner'
export default {
name: 'CscCfGroupItem',
components: { CscSpinner, CscInput, CscPopupMenuItemDelete, CscMoreMenu },
props: {
mapping: {
type: Object,
required: true
},
destination: {
type: Object,
required: true
},
destinationPrevious: {
type: Object,
default: null
},
destinationIndex: {
type: Number,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
},
loading: {
type: Boolean,
default: false
}
},
data () {
return {
changedDestination: this.destination.simple_destination,
changedDestinationTimeout: 0
}
},
computed: {
...mapGetters('callForwarding', [
'ringTimeout'
]),
waitIdentifier () {
return 'csc-cf-group-item-' + this.destinationSet.id + '-' + this.destinationIndex
}
},
mounted () {
if (this.mapping.type === 'cft' && this.destinationIndex === 0) {
this.changedDestinationTimeout = this.ringTimeout
} else if (this.destinationPrevious) {
this.changedDestinationTimeout = this.destinationPrevious.timeout
}
},
methods: {
...mapActions('callForwarding', [
'updateDestination',
'removeDestination',
'updateDestinationTimeout',
'updateRingTimeout'
]),
async updateDestinationEvent (payload) {
this.$wait.start(this.waitIdentifier)
await this.updateDestination(payload)
this.$wait.end(this.waitIdentifier)
},
async removeDestinationEvent (payload) {
this.$q.dialog({
title: this.$t('Delete destination'),
message: 'You are about to delete this destination',
color: 'negative',
cancel: true,
persistent: true
}).onOk(async data => {
this.$wait.start(this.waitIdentifier)
if (this.destinationSet.destinations.length > 1) {
await this.removeDestination(payload)
} else {
this.$emit('delete-last', payload)
}
this.$wait.end(this.waitIdentifier)
})
},
async updateDestinationTimeoutEvent (payload) {
this.$wait.start(this.waitIdentifier)
await this.updateDestinationTimeout(payload)
this.$wait.end(this.waitIdentifier)
},
async updateRingTimeoutEvent () {
this.$wait.start('csc-cf-mappings-full')
await this.updateRingTimeout(this.changedDestinationTimeout)
this.$wait.end('csc-cf-mappings-full')
}
}
}
</script>

@ -0,0 +1,78 @@
<template>
<q-item
:disable="loading || (mapping && !mapping.enabled)"
>
<q-item-section
side
>
<q-icon
name="subdirectory_arrow_right"
/>
</q-item-section>
<q-item-section>
<q-item-label>
{{ $t('Ring at') }}
<span
class="q-pl-sm text-weight-bold"
>
<q-icon
name="ring_volume"
/>
{{ primaryNumber | number }}
</span>
</q-item-label>
</q-item-section>
<q-inner-loading
:showing="$wait.is(waitIdentifier)"
color="primary"
class="bg-main-menu"
>
<csc-spinner />
</q-inner-loading>
</q-item>
</template>
<script>
import {
mapGetters
} from 'vuex'
import CscSpinner from 'components/CscSpinner'
export default {
name: 'CscCfGroupItemPrimaryNumber',
components: { CscSpinner },
props: {
mapping: {
type: Object,
default: undefined
},
destinationSet: {
type: Object,
default: undefined
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
},
loading: {
type: Boolean,
default: false
}
},
data () {
return {
}
},
computed: {
...mapGetters('user', [
'primaryNumber'
]),
waitIdentifier () {
return 'csc'
}
}
}
</script>

@ -0,0 +1,366 @@
<template>
<q-item
:disable="loading || $wait.is(waitIdentifier)"
>
<q-item-section
class="text-left"
>
<q-item-label
class="text-weight-bold"
>
<span
v-if="mapping.type === 'cfu' || mapping.type === 'cft'"
>
{{ $t('If available') }}
</span>
<template
v-else-if="mapping.type === 'cfna'"
>
{{ $t('If not available') }}
</template>
<template
v-else-if="mapping.type === 'cfb'"
>
{{ $t('If busy') }}
</template>
<template
v-if="sourceSet"
>
<template
v-if="sourceSet.mode === 'whitelist'"
>
{{ $t('and call from') }}
</template>
<template
v-else
>
{{ $t('and call not from') }}
</template>
<span
:class="clickableClasses"
style="white-space: nowrap"
>
<q-icon
v-if="sourceSet.mode === 'whitelist'"
name="person_add"
/>
<q-icon
v-else
name="person_add_disabled"
/>
{{ sourceSet.name }}
<csc-cf-condition-popup-call-from
v-if="sourceSet.mode === 'whitelist'"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
/>
<csc-cf-condition-popup-call-not-from
v-else
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
/>
</span>
</template>
<template
v-if="timeSet"
>
{{ $t('and') }}
<template
v-if="timeSet.name.startsWith('csc-date-exact')"
>
{{ $t('date is') }}
<span
:class="clickableClasses"
>
<q-icon
name="today"
/>
{{ timeSet.times | timeSetDateExact }}
<csc-cf-condition-popup-date
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
/>
</span>
</template>
<template
v-else-if="timeSet.name.startsWith('csc-date-range')"
>
{{ $t('date range is') }}
<span
:class="clickableClasses"
>
<q-icon
name="book_online"
/>
{{ timeSet.times | timeSetDateRange }}
<csc-cf-condition-popup-date-range
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
/>
</span>
</template>
<template
v-else-if="timeSet.name.startsWith('csc-weekdays')"
>
{{ $t('weekdays are') }}
<span
:class="clickableClasses"
>
<q-icon
name="calendar_today"
/>
{{ timeSet.times | timeSetWeekdays }}
<csc-cf-condition-popup-weekdays
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
/>
</span>
</template>
<template
v-else-if="timeSet.name.startsWith('csc-office-hours')"
>
{{ $t('office hours are') }}
<span
:class="clickableClasses"
>
<q-icon
name="access_time"
/>
{{ timeSet.times | timeSetOfficeHoursSameTime }}
<csc-cf-condition-popup-office-hours
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
/>
</span>
</template>
<span
v-else
:class="clickableClasses"
>
{{ timeSet.times | timeSetTimes }}
</span>
</template>
<template
v-if="!sourceSet || !timeSet"
>
<span>
{{ $t('and') }}
</span>
<span
:class="clickableClasses"
style="white-space: nowrap"
>
<q-icon
name="alt_route"
/>
{{ $t('condition') }}
<csc-cf-condition-popup-all
step="menu"
:mapping="mapping"
:destination-set="destinationSet"
:source-set="sourceSet"
:time-set="timeSet"
/>
</span>
</template>
</q-item-label>
</q-item-section>
<q-item-section
side
>
<csc-more-menu>
<csc-popup-menu-item
v-if="mapping.type === 'cfu'"
icon="ring_volume"
:label="$t('Ring primary number')"
@click="ringPrimaryNumberEvent"
/>
<csc-popup-menu-item
v-if="mapping.type === 'cft'"
icon="phone_disabled"
:label="$t('Do not ring primary number')"
@click="doNotRingPrimaryNumberEvent"
/>
<csc-popup-menu-item
icon="phone_forwarded"
:label="$t('Forward to Number')"
:disable="hasTermination"
@click="addDestinationEvent({
destinationSetId: destinationSet.id
})"
/>
<csc-popup-menu-item
icon="voicemail"
:label="$t('Forward to Voicebox')"
:disable="hasTermination"
@click="addDestinationEvent({
destination: 'voicebox',
destinationSetId: destinationSet.id
})"
/>
<csc-popup-menu-item
icon="email"
:label="$t('Forward to Fax2Mail')"
:disable="hasTermination"
@click="addDestinationEvent({
destination: 'fax2mail',
destinationSetId: destinationSet.id
})"
/>
<csc-popup-menu-item
icon="phone_forwarded"
:label="$t('Forward to ManagerSecretary')"
:disable="hasTermination"
@click="addDestinationEvent({
destination: 'managersecretary',
destinationSetId: destinationSet.id
})"
/>
<csc-popup-menu-item
icon="groups"
:label="$t('Forward to Conference')"
:disable="hasTermination"
@click="addDestinationEvent({
destination: 'conference',
destinationSetId: destinationSet.id
})"
/>
<csc-popup-menu-item
:icon="(mapping.enabled)?'toggle_on':'toggle_off'"
:label="(mapping.enabled)?$t('Disable'):$t('Enable')"
@click="toggleMappingEvent(mapping)"
/>
<csc-popup-menu-item-delete
@click="deleteMappingEvent(mapping)"
/>
</csc-more-menu>
</q-item-section>
</q-item>
</template>
<script>
import _ from 'lodash'
import {
mapActions
} from 'vuex'
import CscMoreMenu from 'components/CscMoreMenu'
import CscPopupMenuItemDelete from 'components/CscPopupMenuItemDelete'
import CscPopupMenuItem from 'components/CscPopupMenuItem'
import CscCfConditionPopupAll from 'components/call-forwarding/CscCfConditionPopupAll'
import CscCfConditionPopupDate from 'components/call-forwarding/CscCfConditionPopupDate'
import CscCfConditionPopupCallFrom from 'components/call-forwarding/CscCfConditionPopupCallFrom'
import CscCfConditionPopupCallNotFrom from 'components/call-forwarding/CscCfConditionPopupCallNotFrom'
import CscCfConditionPopupDateRange from 'components/call-forwarding/CscCfConditionPopupDateRange'
import CscCfConditionPopupWeekdays from 'components/call-forwarding/CscCfConditionPopupWeekdays'
import CscCfConditionPopupOfficeHours from 'components/call-forwarding/CscCfConditionPopupOfficeHours'
export default {
name: 'CscCfGroupTitle',
components: {
CscCfConditionPopupOfficeHours,
CscCfConditionPopupWeekdays,
CscCfConditionPopupDateRange,
CscCfConditionPopupCallNotFrom,
CscCfConditionPopupCallFrom,
CscCfConditionPopupDate,
CscCfConditionPopupAll,
CscPopupMenuItem,
CscPopupMenuItemDelete,
CscMoreMenu
},
props: {
mapping: {
type: Object,
required: true
},
destinationSet: {
type: Object,
required: true
},
sourceSet: {
type: Object,
default: undefined
},
timeSet: {
type: Object,
default: undefined
},
loading: {
type: Boolean,
default: false
}
},
computed: {
clickableClasses () {
return ['cursor-pointer', 'text-weight-bold', 'text-primary']
},
waitIdentifier () {
return 'csc-cf-group-' + this.destinationSet.id
},
hasTermination () {
return _.endsWith(_.last(this.destinationSet.destinations).destination, 'voicebox.local') ||
_.endsWith(_.last(this.destinationSet.destinations).destination, 'fax2mail.local') ||
_.endsWith(_.last(this.destinationSet.destinations).destination, 'managersecretary.local') ||
_.endsWith(_.last(this.destinationSet.destinations).destination, 'conference.local') ||
_.endsWith(_.last(this.destinationSet.destinations).destination, 'app.local')
}
},
methods: {
...mapActions('callForwarding', [
'deleteMapping',
'toggleMapping',
'addDestination',
'ringPrimaryNumber',
'doNotRingPrimaryNumber'
]),
async addDestinationEvent (payload) {
this.$wait.start(this.waitIdentifier)
await this.addDestination(payload)
this.$wait.end(this.waitIdentifier)
},
async toggleMappingEvent (mapping) {
this.$wait.start(this.waitIdentifier)
await this.toggleMapping(mapping)
this.$wait.end(this.waitIdentifier)
},
async deleteMappingEvent (mapping) {
this.$q.dialog({
title: this.$t('Delete forwarding'),
message: 'You are about to delete this forwarding',
color: 'negative',
cancel: true,
persistent: true
}).onOk(async data => {
this.$wait.start(this.waitIdentifier)
await this.deleteMapping(mapping)
this.$wait.end(this.waitIdentifier)
})
},
async ringPrimaryNumberEvent () {
this.$wait.start('csc-cf-mappings-full')
await this.ringPrimaryNumber()
this.$wait.end('csc-cf-mappings-full')
},
async doNotRingPrimaryNumberEvent () {
this.$wait.start('csc-cf-mappings-full')
await this.doNotRingPrimaryNumber()
this.$wait.end('csc-cf-mappings-full')
}
}
}
</script>

@ -0,0 +1,113 @@
<template>
<div>
<div
v-if="!tabs"
class="row justify-around"
>
<q-btn
v-for="(day, index) in days"
:key="day.value"
:label="$t(day.label)"
:class="(index > 0)?'q-ml-sm':''"
round
no-caps
unelevated
:color="(isSelected(day))?'primary':'dark'"
:text-color="(isSelected(day))?'dark':'primary'"
@click="toggle(day)"
/>
</div>
<div
v-else
>
<q-tabs
v-model="selectedTab"
dense
active-color="primary"
indicator-color="primary"
align="center"
>
<q-tab
v-for="(day) in days"
:key="day.value"
:name="'tab-' + day.value"
:label="$t(day.label)"
class="text-primary no-padding"
inline-label
outside-arrows
mobile-arrows
no-caps
/>
</q-tabs>
<q-separator
v-if="tabs"
/>
</div>
</div>
</template>
<script>
import {
DAY_MAP,
DAY_NAME_MAP
} from 'src/filters/time-set'
export default {
name: 'CscCfSelectionWeekdays',
props: {
value: {
type: Array,
default: () => [DAY_MAP[0]]
},
tabs: {
type: Boolean,
default: false
}
},
data () {
return {
selectedWeekdays: this.value
}
},
computed: {
selectedTab: {
get () {
return 'tab-' + this.selectedWeekdays[0]
},
set (tab) {
this.selectedWeekdays = [parseInt(tab.replace('tab-', ''))]
}
},
days () {
const options = []
DAY_MAP.forEach((day, index) => {
options.push({
label: DAY_NAME_MAP[index].substr(0, 2),
value: day
})
})
return options
}
},
watch: {
selectedWeekdays (weekdays) {
this.$emit('input', weekdays)
},
value (weekdays) {
this.selectedWeekdays = weekdays
}
},
methods: {
toggle (day) {
if (this.isSelected(day)) {
this.selectedWeekdays = this.selectedWeekdays.filter(dayValue => day.value !== dayValue)
} else {
this.selectedWeekdays.push(day.value)
}
},
isSelected (day) {
return this.selectedWeekdays.find(dayValue => day.value === dayValue)
}
}
}
</script>

@ -0,0 +1,75 @@
<template>
<q-select
:value="value"
:options="options"
emit-value
use-input
map-options
input-debounce="300"
v-bind="$attrs"
@filter="filter"
v-on="$listeners"
/>
</template>
<script>
import _ from 'lodash'
import {
mapActions,
mapState
} from 'vuex'
export default {
name: 'CscCfSourceSetSelection',
props: {
value: {
type: [String, Number],
default: undefined
},
mode: {
type: String,
required: true
}
},
data () {
return {
options: []
}
},
computed: {
...mapState('callForwarding', [
'sourceSets'
]),
allOptions () {
const options = []
if (this.sourceSets) {
this.sourceSets.forEach((sourceSet) => {
if (sourceSet.mode === this.mode) {
options.push({
value: sourceSet.id,
label: sourceSet.name
})
}
})
}
return options
}
},
methods: {
...mapActions('callForwarding', [
'loadSourceSets'
]),
async filter (value, update) {
await this.loadSourceSets()
if (value === '' || value === null || value === undefined) {
update(() => {
this.options = this.allOptions
})
} else {
update(() => {
this.options = this.allOptions.filter(sourceSet =>
_.startsWith(_.lowerCase(sourceSet.label), _.lowerCase(value)))
})
}
}
}
}
</script>

@ -29,7 +29,7 @@
/>
<q-btn
v-if="$attrs.clearable !== undefined && value !== ''"
icon="clear"
icon="backspace"
color="white"
flat
dense
@ -50,7 +50,7 @@ export default {
},
props: {
value: {
type: String,
type: [String, Number],
default: undefined
}
},

@ -442,7 +442,7 @@ import CscNewCallForwardAddOfficeHoursForm from './CscNewCallForwardAddOfficeHou
import CscNewCallForwardDestinationTypeForm from './CscNewCallForwardDestinationTypeForm'
import CscNewCallForwardDateRange from './CscNewCallForwardDateRange'
export default {
name: 'CscCfGroup',
name: 'CscCallForwardGroup',
components: {
CscConfirmDialog,
CscNewCallForwardDestination,

@ -0,0 +1,79 @@
import { i18n } from 'boot/i18n'
export const DAY_MAP = [2, 3, 4, 5, 6, 7, 1]
export const DAY_NAME_MAP = [
i18n.t('Monday'),
i18n.t('Tuesday'),
i18n.t('Wednesday'),
i18n.t('Thursday'),
i18n.t('Friday'),
i18n.t('Saturday'),
i18n.t('Sunday')
]
export const DEFAULT_WEEKDAYS = [
DAY_MAP[0],
DAY_MAP[1],
DAY_MAP[2],
DAY_MAP[3],
DAY_MAP[4]
]
export function timeSetDateExact (times) {
return times[0].year + '/' + times[0].month + '/' + times[0].mday
}
export function timeSetDateRange (times) {
const years = times[0].year.split('-')
const months = times[0].month.split('-')
const dates = times[0].mday.split('-')
return years[0] + '/' + months[0] + '/' + dates[0] + '-' +
years[1] + '/' + months[1] + '/' + dates[1]
}
export function timeSetWeekdays (times) {
const mappedWeekdays = times.map((time) => {
return DAY_MAP.indexOf(parseInt(time.wday))
})
mappedWeekdays.sort()
let weekdays = ''
mappedWeekdays.forEach((weekday, index) => {
if (index > 0) {
weekdays = weekdays + ', '
}
weekdays = weekdays + DAY_NAME_MAP[weekday]
})
return weekdays
}
export function timeSetOfficeHoursSameTime (times) {
const weekdays = new Set()
let weekdaysStr = ''
times.forEach((time) => {
weekdays.add(parseInt(time.wday))
})
const weekdaysSorted = Array.from(weekdays)
weekdaysSorted.sort((a, b) => {
if (a === 1) {
return 1
} else if (a > b) {
return 1
} else if (a < b) {
return -1
} else {
return 0
}
})
weekdaysSorted.forEach((weekday, index) => {
if (index > 0) {
weekdaysStr += ', '
}
weekdaysStr += DAY_NAME_MAP[DAY_MAP.indexOf(weekday)]
})
return weekdaysStr
}
export function timeSetTimes () {
}

@ -82,7 +82,7 @@
"subTitle": "Calls, Faxes, VoiceMails"
},
"callForward": {
"title": "Call Forward",
"title": "Call Forwarding",
"subTitle": "Control your calls",
"always": "Always",
"companyHours": "Company Hours",
@ -133,7 +133,7 @@
"title": "New features"
},
"callForward": {
"title": "Call Forward"
"title": "Call Forwarding"
}
},
"userSettings": {

@ -147,17 +147,6 @@
:is-pbx-admin="isPbxAdmin"
:is-pbx-configuration="isPbxConfiguration"
/>
<csc-main-menu-new-features
id="csc-main-menu-features"
class="csc-main-menu"
:call-state-title="callStateTitle"
:call-state-subtitle="callStateSubtitle"
:is-call-forward="isCallForward"
:is-call-blocking="isCallBlocking"
:is-pbx-admin="isPbxAdmin"
:is-pbx-configuration="isPbxConfiguration"
:menu-minimized="menuMinimized"
/>
</q-drawer>
<q-page-container
id="csc-page-main"
@ -225,7 +214,6 @@ import {
getLanguageLabel
} from 'src/i18n'
import CscMainMenuTop from 'components/CscMainMenuTop'
import CscMainMenuNewFeatures from 'components/CscMainMenuNewFeatures'
import CscPopupMenu from 'components/CscPopupMenu'
import CscPopupMenuItem from 'components/CscPopupMenuItem'
@ -234,7 +222,6 @@ export default {
components: {
CscPopupMenuItem,
CscPopupMenu,
CscMainMenuNewFeatures,
CscMainMenuTop,
CscLanguageMenu,
CscCall,

@ -0,0 +1,126 @@
<template>
<csc-page-sticky
id="csc-page-call-forwarding"
class="q-pa-lg"
>
<template
v-slot:header
>
<q-btn
flat
icon="add"
color="primary"
:label="$t('Add forwarding')"
:disable="$wait.is('csc-cf-mappings-full')"
:loading="$wait.is('csc-cf-mappings-full')"
>
<csc-popup-menu>
<csc-popup-menu-item
color="primary"
:label="$t('If available')"
@click="createMapping({ type: 'cfu'})"
/>
<csc-popup-menu-item
color="primary"
:label="$t('If not available')"
@click="createMapping({ type: 'cfna'})"
/>
<csc-popup-menu-item
color="primary"
:label="$t('If busy')"
@click="createMapping({ type: 'cfb'})"
/>
</csc-popup-menu>
<template
v-slot:loading
>
<csc-spinner />
</template>
</q-btn>
</template>
<div
class="row justify-center q-pt-lg"
>
<div
id="csc-wrapper-call-forwarding"
class="col-xs-12 col-lg-8"
>
<q-list
v-if="groups.length === 0 && !$wait.is('csc-cf-mappings-full')"
dense
separator
>
<q-item
:disable="$wait.is('csc-cf-mappings-full')"
>
<q-item-section>
<q-item-label
class="text-weight-bold"
>
{{ $t('Always') }}
</q-item-label>
</q-item-section>
</q-item>
<csc-cf-group-item-primary-number />
</q-list>
<template
v-for="group in groups"
>
<csc-cf-group
:key="group.cfm_id"
class="q-mb-lg"
:loading="$wait.is('csc-cf-mappings-full')"
:mapping="group"
:destination-set="destinationSetMap[group.destinationset_id]"
:source-set="sourceSetMap[group.sourceset_id]"
:time-set="timeSetMap[group.timeset_id]"
/>
</template>
</div>
</div>
</csc-page-sticky>
</template>
<script>
import CscCfGroup from 'components/call-forwarding/CscCfGroup'
import {
mapActions,
mapState,
mapGetters
} from 'vuex'
import CscPopupMenuItem from 'components/CscPopupMenuItem'
import CscPopupMenu from 'components/CscPopupMenu'
import CscSpinner from 'components/CscSpinner'
import CscCfGroupItemPrimaryNumber from 'components/call-forwarding/CscCfGroupItemPrimaryNumber'
import CscPageSticky from 'components/CscPageSticky'
export default {
name: 'CscPageCf',
components: {
CscPageSticky,
CscCfGroupItemPrimaryNumber,
CscSpinner,
CscPopupMenu,
CscPopupMenuItem,
CscCfGroup
},
computed: {
...mapState('callForwarding', [
'mappings',
'destinationSetMap',
'sourceSetMap',
'timeSetMap'
]),
...mapGetters('callForwarding', [
'groups'
])
},
mounted () {
this.loadMappingsFull()
},
methods: {
...mapActions('callForwarding', [
'loadMappingsFull',
'createMapping'
])
}
}
</script>

@ -27,6 +27,7 @@ import CscPageFaxSettings from 'src/pages/CscPageFaxSettings'
import CscPageUserSettings from 'src/pages/CscPageUserSettings'
import CscPageError404 from 'src/pages/CscPageError404'
import CscRecoverPassword from 'src/pages/CscRecoverPassword'
import CscPageCf from 'pages/CscPageCf'
const getToken = (route) => {
return {
@ -60,6 +61,13 @@ export default function routes (app) {
path: 'new-call-forward',
component: CscPageNewCallForward
},
{
path: 'call-forwarding',
component: CscPageCf,
meta: {
title: i18n.t('Call Forwarding')
}
},
{
path: 'call-forward/always',
component: CscPageCallForwardAlways,

@ -0,0 +1,525 @@
import {
cfCreateOfficeHours, cfCreateOfficeHoursSameTimes,
cfCreateSourceSet,
cfCreateTimeSetDate,
cfCreateTimeSetDateRange,
cfCreateTimeSetWeekdays,
cfDeleteDestinationSet,
cfDeleteSourceSet,
cfDeleteTimeSet,
cfLoadDestinationSets,
cfLoadMappingsFull,
cfLoadSourceSets,
cfLoadTimeSets, cfUpdateOfficeHours, cfUpdateOfficeHoursSameTimes,
cfUpdateSourceSet,
cfUpdateTimeSetDate,
cfUpdateTimeSetDateRange,
cfUpdateTimeSetWeekdays
} from 'src/api/call-forwarding'
import {
v4
} from 'uuid'
import {
patchReplace,
patchReplaceFull,
post, put
} from 'src/api/common'
import _ from 'lodash'
const DEFAULT_RING_TIMEOUT = 60
const DEFAULT_PRIORITY = 0
const WAIT_IDENTIFIER = 'csc-cf-mappings-full'
function createDefaultDestination (destination) {
let finalDestination = 'Number'
if (destination) {
finalDestination = destination
}
return {
destination: finalDestination,
priority: DEFAULT_PRIORITY,
timeout: DEFAULT_RING_TIMEOUT
}
}
export async function loadMappingsFull ({ dispatch, commit, rootGetters }) {
dispatch('wait/start', WAIT_IDENTIFIER, { root: true })
const res = await cfLoadMappingsFull(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
mappings: res[0],
destinationSets: res[1].items,
sourceSets: res[2].items,
timeSets: res[3].items
})
dispatch('wait/end', WAIT_IDENTIFIER, { root: true })
}
export async function createMapping ({ dispatch, commit, state, rootGetters }, payload) {
dispatch('wait/start', WAIT_IDENTIFIER, { root: true })
let type = payload.type
if (payload.type === 'cfu' && state.mappings.cft && state.mappings.cft.length > 0) {
type = 'cft'
}
const mappings = _.cloneDeep(state.mappings[type])
const destinationSetId = await post({
resource: 'cfdestinationsets',
body: {
name: 'csc-' + v4(),
subscriber_id: rootGetters['user/getSubscriberId'],
destinations: [createDefaultDestination()]
}
})
mappings.push({
destinationset_id: destinationSetId
})
const res = await Promise.all([
patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: type,
value: mappings
}),
cfLoadDestinationSets(rootGetters['user/getSubscriberId'])
])
commit('dataSucceeded', {
mappings: res[0],
destinationSets: res[1].items
})
dispatch('wait/end', WAIT_IDENTIFIER, { root: true })
}
export async function deleteMapping ({ dispatch, commit, state, rootGetters }, payload) {
dispatch('wait/start', WAIT_IDENTIFIER, { root: true })
const mappings = _.cloneDeep(state.mappings[payload.type])
const updatedMappings = mappings.reduce(($updatedMappings, value, index) => {
if (index !== payload.index) {
$updatedMappings.push(value)
}
return $updatedMappings
}, [])
const patchRes = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: payload.type,
value: updatedMappings
})
await cfDeleteDestinationSet(payload.destinationset_id)
const destinationSets = await cfLoadDestinationSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
mappings: patchRes,
destinationSets: destinationSets.items
})
dispatch('wait/end', WAIT_IDENTIFIER, { root: true })
}
export async function toggleMapping ({ dispatch, commit, state, rootGetters }, payload) {
dispatch('wait/start', WAIT_IDENTIFIER, { root: true })
const updatedMappings = _.cloneDeep(state.mappings[payload.type])
updatedMappings[payload.index].enabled = !updatedMappings[payload.index].enabled
const patchRes = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: payload.type,
value: updatedMappings
})
commit('dataSucceeded', {
mappings: patchRes
})
dispatch('wait/end', WAIT_IDENTIFIER, { root: true })
}
export async function updateDestination ({ dispatch, commit, state, rootGetters }, payload) {
dispatch('wait/start', WAIT_IDENTIFIER, { root: true })
const destinations = _.cloneDeep(state.destinationSetMap[payload.destinationSetId].destinations)
destinations[payload.destinationIndex].destination = payload.destination
await patchReplace({
resource: 'cfdestinationsets',
resourceId: payload.destinationSetId,
fieldPath: 'destinations',
value: destinations
})
const destinationSets = await cfLoadDestinationSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
destinationSets: destinationSets.items
})
dispatch('wait/end', WAIT_IDENTIFIER, { root: true })
}
export async function addDestination ({ dispatch, commit, state, rootGetters }, payload) {
dispatch('wait/start', WAIT_IDENTIFIER, { root: true })
const destinations = _.cloneDeep(state.destinationSetMap[payload.destinationSetId].destinations)
destinations.push(createDefaultDestination(payload.destination))
await patchReplace({
resource: 'cfdestinationsets',
resourceId: payload.destinationSetId,
fieldPath: 'destinations',
value: destinations
})
const destinationSets = await cfLoadDestinationSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
destinationSets: destinationSets.items
})
dispatch('wait/end', WAIT_IDENTIFIER, { root: true })
}
export async function removeDestination ({ dispatch, commit, state, rootGetters }, payload) {
dispatch('wait/start', WAIT_IDENTIFIER, { root: true })
const destinations = _.cloneDeep(state.destinationSetMap[payload.destinationSetId].destinations)
const updatedDestinations = destinations.reduce(($updatedDestinations, value, index) => {
if (index !== payload.destinationIndex) {
$updatedDestinations.push(value)
}
return $updatedDestinations
}, [])
await patchReplace({
resource: 'cfdestinationsets',
resourceId: payload.destinationSetId,
fieldPath: 'destinations',
value: updatedDestinations
})
const destinationSets = await cfLoadDestinationSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
destinationSets: destinationSets.items
})
dispatch('wait/end', WAIT_IDENTIFIER, { root: true })
}
export async function updateDestinationTimeout ({ dispatch, commit, state, rootGetters }, payload) {
dispatch('wait/start', WAIT_IDENTIFIER, { root: true })
const destinations = _.cloneDeep(state.destinationSetMap[payload.destinationSetId].destinations)
destinations[payload.destinationIndex].timeout = payload.destinationTimeout
await patchReplace({
resource: 'cfdestinationsets',
resourceId: payload.destinationSetId,
fieldPath: 'destinations',
value: destinations
})
const destinationSets = await cfLoadDestinationSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
destinationSets: destinationSets.items
})
dispatch('wait/end', WAIT_IDENTIFIER, { root: true })
}
export async function loadSourceSets ({ dispatch, commit, rootGetters }) {
dispatch('wait/start', 'csc-cf-sourcesets', { root: true })
const sourceSets = await cfLoadSourceSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
sourceSets: sourceSets.items
})
dispatch('wait/end', 'csc-cf-sourcesets', { root: true })
}
export async function createSourceSet ({ dispatch, commit, rootGetters, state }, payload) {
try {
dispatch('wait/start', 'csc-cf-source-set-create', { root: true })
const sourceSetId = await cfCreateSourceSet(rootGetters['user/getSubscriberId'], payload)
const updatedMapping = _.cloneDeep(state.mappings[payload.mapping.type])
updatedMapping[payload.mapping.index].sourceset_id = sourceSetId
const updatedMappings = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: payload.mapping.type,
value: updatedMapping
})
const sourceSets = await cfLoadSourceSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
mappings: updatedMappings,
sourceSets: sourceSets.items
})
} finally {
dispatch('wait/end', 'csc-cf-source-set-create', { root: true })
}
}
export async function updateSourceSet ({ dispatch, commit, rootGetters, state }, payload) {
try {
dispatch('wait/start', 'csc-cf-source-set-create', { root: true })
await cfUpdateSourceSet(rootGetters['user/getSubscriberId'], payload)
const sourceSets = await cfLoadSourceSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
sourceSets: sourceSets.items
})
} finally {
dispatch('wait/end', 'csc-cf-source-set-create', { root: true })
}
}
export async function deleteSourceSet ({ dispatch, commit, rootGetters, state }, payload) {
try {
dispatch('wait/start', 'csc-cf-source-set-create', { root: true })
const updatedMapping = _.cloneDeep(state.mappings[payload.mapping.type])
updatedMapping[payload.mapping.index].sourceset_id = null
updatedMapping[payload.mapping.index].sourceset = null
const updatedMappings = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: payload.mapping.type,
value: updatedMapping
})
await cfDeleteSourceSet(payload.id)
const sourceSets = await cfLoadSourceSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
mappings: updatedMappings,
sourceSets: sourceSets.items
})
} finally {
dispatch('wait/end', 'csc-cf-source-set-create', { root: true })
}
}
export async function assignSourceSet ({ dispatch, commit, rootGetters, state }, payload) {
try {
dispatch('wait/start', 'csc-cf-source-set-create', { root: true })
const updatedMapping = _.cloneDeep(state.mappings[payload.mapping.type])
updatedMapping[payload.mapping.index].sourceset_id = payload.id
const updatedMappings = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: payload.mapping.type,
value: updatedMapping
})
commit('dataSucceeded', {
mappings: updatedMappings
})
} finally {
dispatch('wait/end', 'csc-cf-source-set-create', { root: true })
}
}
export async function unassignSourceSet ({ dispatch, commit, rootGetters, state }, payload) {
try {
dispatch('wait/start', 'csc-cf-source-set-create', { root: true })
const updatedMapping = _.cloneDeep(state.mappings[payload.mapping.type])
updatedMapping[payload.mapping.index].sourceset_id = null
updatedMapping[payload.mapping.index].sourceset = null
const updatedMappings = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: payload.mapping.type,
value: updatedMapping
})
commit('dataSucceeded', {
mappings: updatedMappings
})
} finally {
dispatch('wait/end', 'csc-cf-source-set-create', { root: true })
}
}
export async function createTimeSetDate ({ dispatch, commit, rootGetters, state }, payload) {
dispatch('wait/start', 'csc-cf-time-set-create', { root: true })
const timeSetId = await cfCreateTimeSetDate(rootGetters['user/getSubscriberId'], payload.date)
const updatedMapping = _.cloneDeep(state.mappings[payload.mapping.type])
updatedMapping[payload.mapping.index].timeset_id = timeSetId
const updatedMappings = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: payload.mapping.type,
value: updatedMapping
})
const timeSets = await cfLoadTimeSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
mappings: updatedMappings,
timeSets: timeSets.items
})
dispatch('wait/end', 'csc-cf-time-set-create', { root: true })
}
export async function updateTimeSetDate ({ dispatch, commit, rootGetters, state }, payload) {
dispatch('wait/start', 'csc-cf-time-set-create', { root: true })
await cfUpdateTimeSetDate(payload.id, payload.date)
const timeSets = await cfLoadTimeSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
timeSets: timeSets.items
})
dispatch('wait/end', 'csc-cf-time-set-create', { root: true })
}
export async function deleteTimeSet ({ dispatch, commit, rootGetters, state }, payload) {
dispatch('wait/start', 'csc-cf-time-set-create', { root: true })
const updatedMapping = _.cloneDeep(state.mappings[payload.mapping.type])
updatedMapping[payload.mapping.index].timeset_id = null
updatedMapping[payload.mapping.index].timeset = null
const updatedMappings = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: payload.mapping.type,
value: updatedMapping
})
await cfDeleteTimeSet(payload.id)
const timeSets = await cfLoadTimeSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
mappings: updatedMappings,
timeSets: timeSets.items
})
dispatch('wait/end', 'csc-cf-time-set-create', { root: true })
}
export async function ringPrimaryNumber ({ commit, rootGetters, state }) {
const mappings = _.cloneDeep(state.mappings)
mappings.cft = mappings.cfu
mappings.cfu = []
mappings.cft_ringtimeout = 60
const updatedMappings = await put({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
body: mappings
})
commit('dataSucceeded', {
mappings: updatedMappings
})
}
export async function doNotRingPrimaryNumber ({ commit, rootGetters, state }) {
const mappings = _.cloneDeep(state.mappings)
mappings.cfu = mappings.cft
mappings.cft = []
mappings.cft_ringtimeout = null
const updatedMappings = await put({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
body: mappings
})
commit('dataSucceeded', {
mappings: updatedMappings
})
}
export async function updateRingTimeout ({ commit, rootGetters, state }, ringTimeout) {
const updatedMappings = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: 'cft_ringtimeout',
value: ringTimeout
})
commit('dataSucceeded', {
mappings: updatedMappings
})
}
export async function createTimeSetDateRange ({ dispatch, commit, rootGetters, state }, payload) {
dispatch('wait/start', 'csc-cf-time-set-create', { root: true })
const timeSetId = await cfCreateTimeSetDateRange(rootGetters['user/getSubscriberId'], payload.date)
const updatedMapping = _.cloneDeep(state.mappings[payload.mapping.type])
updatedMapping[payload.mapping.index].timeset_id = timeSetId
const updatedMappings = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: payload.mapping.type,
value: updatedMapping
})
const timeSets = await cfLoadTimeSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
mappings: updatedMappings,
timeSets: timeSets.items
})
dispatch('wait/end', 'csc-cf-time-set-create', { root: true })
}
export async function updateTimeSetDateRange ({ dispatch, commit, rootGetters, state }, payload) {
dispatch('wait/start', 'csc-cf-time-set-create', { root: true })
await cfUpdateTimeSetDateRange(payload.id, payload.date)
const timeSets = await cfLoadTimeSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
timeSets: timeSets.items
})
dispatch('wait/end', 'csc-cf-time-set-create', { root: true })
}
export async function createTimeSetWeekdays ({ dispatch, commit, rootGetters, state }, payload) {
dispatch('wait/start', 'csc-cf-time-set-create', { root: true })
const timeSetId = await cfCreateTimeSetWeekdays(rootGetters['user/getSubscriberId'], payload.weekdays)
const updatedMapping = _.cloneDeep(state.mappings[payload.mapping.type])
updatedMapping[payload.mapping.index].timeset_id = timeSetId
const updatedMappings = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: payload.mapping.type,
value: updatedMapping
})
const timeSets = await cfLoadTimeSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
mappings: updatedMappings,
timeSets: timeSets.items
})
dispatch('wait/end', 'csc-cf-time-set-create', { root: true })
}
export async function updateTimeSetWeekdays ({ dispatch, commit, rootGetters, state }, payload) {
dispatch('wait/start', 'csc-cf-time-set-create', { root: true })
await cfUpdateTimeSetWeekdays(payload.id, payload.weekdays)
const timeSets = await cfLoadTimeSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
timeSets: timeSets.items
})
dispatch('wait/end', 'csc-cf-time-set-create', { root: true })
}
export async function createOfficeHours ({ dispatch, commit, rootGetters, state }, payload) {
dispatch('wait/start', 'csc-cf-time-set-create', { root: true })
const timeSetId = await cfCreateOfficeHours(rootGetters['user/getSubscriberId'], payload.times)
const updatedMapping = _.cloneDeep(state.mappings[payload.mapping.type])
updatedMapping[payload.mapping.index].timeset_id = timeSetId
const updatedMappings = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: payload.mapping.type,
value: updatedMapping
})
if (payload.id) {
await cfDeleteTimeSet(payload.id)
}
const timeSets = await cfLoadTimeSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
mappings: updatedMappings,
timeSets: timeSets.items
})
dispatch('wait/end', 'csc-cf-time-set-create', { root: true })
}
export async function updateOfficeHours ({ dispatch, commit, rootGetters, state }, payload) {
dispatch('wait/start', 'csc-cf-time-set-create', { root: true })
await cfUpdateOfficeHours(payload.id, payload.times)
const timeSets = await cfLoadTimeSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
timeSets: timeSets.items
})
dispatch('wait/end', 'csc-cf-time-set-create', { root: true })
}
export async function createOfficeHoursSameTimes ({ dispatch, commit, rootGetters, state }, payload) {
dispatch('wait/start', 'csc-cf-time-set-create', { root: true })
const timeSetId = await cfCreateOfficeHoursSameTimes(
rootGetters['user/getSubscriberId'],
payload.times,
payload.weekdays
)
const updatedMapping = _.cloneDeep(state.mappings[payload.mapping.type])
updatedMapping[payload.mapping.index].timeset_id = timeSetId
const updatedMappings = await patchReplaceFull({
resource: 'cfmappings',
resourceId: rootGetters['user/getSubscriberId'],
fieldPath: payload.mapping.type,
value: updatedMapping
})
if (payload.id) {
await cfDeleteTimeSet(payload.id)
}
const timeSets = await cfLoadTimeSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
mappings: updatedMappings,
timeSets: timeSets.items
})
dispatch('wait/end', 'csc-cf-time-set-create', { root: true })
}
export async function updateOfficeHoursSameTimes ({ dispatch, commit, rootGetters, state }, payload) {
dispatch('wait/start', 'csc-cf-time-set-create', { root: true })
await cfUpdateOfficeHoursSameTimes(payload.id, payload.times, payload.weekdays)
const timeSets = await cfLoadTimeSets(rootGetters['user/getSubscriberId'])
commit('dataSucceeded', {
timeSets: timeSets.items
})
dispatch('wait/end', 'csc-cf-time-set-create', { root: true })
}

@ -0,0 +1,19 @@
import _ from 'lodash'
export function groups (state) {
const types = ['cfu', 'cft', 'cfna', 'cfb']
const mappings = []
types.forEach((type) => {
state.mappings[type].forEach((mapping, index) => {
const clonedMapping = _.clone(mapping)
clonedMapping.type = type
clonedMapping.index = index
mappings.push(clonedMapping)
})
})
return mappings
}
export function ringTimeout (state) {
return state.mappings.cft_ringtimeout
}

@ -0,0 +1,12 @@
import state from './state'
import * as getters from './getters'
import * as mutations from './mutations'
import * as actions from './actions'
export default {
namespaced: true,
state,
getters,
mutations,
actions
}

@ -0,0 +1,29 @@
export function dataSucceeded (state, res) {
if (res.destinationSets) {
const destinationSetMap = {}
res.destinationSets.forEach((destinationSet) => {
destinationSetMap[destinationSet.id] = destinationSet
})
state.destinationSetMap = destinationSetMap
}
if (res.sourceSets) {
const sourceSetMap = {}
res.sourceSets.forEach((sourceSet) => {
sourceSetMap[sourceSet.id] = sourceSet
})
state.sourceSetMap = sourceSetMap
state.sourceSets = res.sourceSets
}
if (res.timeSets) {
const timeSetMap = {}
res.timeSets.forEach((timeSet) => {
timeSetMap[timeSet.id] = timeSet
})
state.timeSetMap = timeSetMap
state.timeSets = res.timeSets
}
if (res.mappings) {
state.mappings = res.mappings
}
}

@ -0,0 +1,20 @@
export default function () {
return {
mappings: {
cfb: [],
cfna: [],
cfo: [],
cfr: [],
cfs: [],
cft: [],
cft_ringtimeout: null,
cfu: []
},
destinationSets: null,
destinationSetMap: {},
sourceSets: null,
sourceSetMap: {},
timeSets: null,
timeSetMap: {}
}
}

@ -7,6 +7,7 @@ import _ from 'lodash'
import CallBlockingModule from './call-blocking'
import CallForwardModule from './call-forward'
import CallForwardingModule from './call-forwarding'
import NewCallForwardModule from './new-call-forward'
import CallModule, { errorVisibilityTimeout } from './call'
import ConversationsModule from './conversations'
@ -70,7 +71,8 @@ export default function (/* { ssrContext } */) {
pbxDevices: PbxDevicesModule,
pbxCallQueues: PbxCallQueuesModule,
pbxSoundSets: PbxSoundSetsModule,
pbxMsConfigs: PbxMsConfigsModule
pbxMsConfigs: PbxMsConfigsModule,
callForwarding: CallForwardingModule
},
state: {

@ -153,13 +153,13 @@ export default {
if (state.subscriber === null) {
return null
}
return state.subscriber.primaryNumber
return state.subscriber.primary_number
},
aliasNumbers (state) {
if (state.subscriber === null) {
return []
}
return state.subscriber.aliasNumbers
return state.subscriber.alias_numbers
}
},
mutations: {

@ -11805,6 +11805,11 @@ utils-merge@1.0.1:
resolved "https://npm-registry.sipwise.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
uuid@8.3.1:
version "8.3.1"
resolved "https://npm-registry.sipwise.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31"
integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==
uuid@^3.3.2, uuid@^3.3.3, uuid@^3.4.0:
version "3.4.0"
resolved "https://npm-registry.sipwise.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"

Loading…
Cancel
Save