diff --git a/package.json b/package.json index b87eeee2..b7238863 100644 --- a/package.json +++ b/package.json @@ -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": [ diff --git a/src/api/call-forwarding.js b/src/api/call-forwarding.js new file mode 100644 index 00000000..32f638ad --- /dev/null +++ b/src/api/call-forwarding.js @@ -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) + }) +} diff --git a/src/api/common.js b/src/api/common.js index eb4c8ca7..08b56f03 100644 --- a/src/api/common.js +++ b/src/api/common.js @@ -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 } diff --git a/src/api/pbx-config.js b/src/api/pbx-config.js index de8b4956..eafc7696 100644 --- a/src/api/pbx-config.js +++ b/src/api/pbx-config.js @@ -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' diff --git a/src/boot/filters.js b/src/boot/filters.js index 2361801f..080c0bf2 100644 --- a/src/boot/filters.js +++ b/src/boot/filters.js @@ -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) } diff --git a/src/boot/vue-wait.js b/src/boot/vue-wait.js index 2d55c002..3b2337ce 100644 --- a/src/boot/vue-wait.js +++ b/src/boot/vue-wait.js @@ -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, diff --git a/src/components/CscMainMenuTop.vue b/src/components/CscMainMenuTop.vue index 30e83da8..d7137d5c 100644 --- a/src/components/CscMainMenuTop.vue +++ b/src/components/CscMainMenuTop.vue @@ -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', diff --git a/src/components/CscPopupMenuItem.vue b/src/components/CscPopupMenuItem.vue index b4f19383..65b86066 100644 --- a/src/components/CscPopupMenuItem.vue +++ b/src/components/CscPopupMenuItem.vue @@ -1,6 +1,6 @@ + + diff --git a/src/components/call-forwarding/CscCfConditionPopupCallFrom.vue b/src/components/call-forwarding/CscCfConditionPopupCallFrom.vue new file mode 100644 index 00000000..1bf4abe9 --- /dev/null +++ b/src/components/call-forwarding/CscCfConditionPopupCallFrom.vue @@ -0,0 +1,96 @@ + + + diff --git a/src/components/call-forwarding/CscCfConditionPopupCallNotFrom.vue b/src/components/call-forwarding/CscCfConditionPopupCallNotFrom.vue new file mode 100644 index 00000000..1f1021bc --- /dev/null +++ b/src/components/call-forwarding/CscCfConditionPopupCallNotFrom.vue @@ -0,0 +1,96 @@ + + + diff --git a/src/components/call-forwarding/CscCfConditionPopupDate.vue b/src/components/call-forwarding/CscCfConditionPopupDate.vue new file mode 100644 index 00000000..f487894d --- /dev/null +++ b/src/components/call-forwarding/CscCfConditionPopupDate.vue @@ -0,0 +1,55 @@ + + + diff --git a/src/components/call-forwarding/CscCfConditionPopupDateRange.vue b/src/components/call-forwarding/CscCfConditionPopupDateRange.vue new file mode 100644 index 00000000..9ae5ac0b --- /dev/null +++ b/src/components/call-forwarding/CscCfConditionPopupDateRange.vue @@ -0,0 +1,55 @@ + + + diff --git a/src/components/call-forwarding/CscCfConditionPopupOfficeHours.vue b/src/components/call-forwarding/CscCfConditionPopupOfficeHours.vue new file mode 100644 index 00000000..96c57109 --- /dev/null +++ b/src/components/call-forwarding/CscCfConditionPopupOfficeHours.vue @@ -0,0 +1,55 @@ + + + diff --git a/src/components/call-forwarding/CscCfConditionPopupWeekdays.vue b/src/components/call-forwarding/CscCfConditionPopupWeekdays.vue new file mode 100644 index 00000000..75bff613 --- /dev/null +++ b/src/components/call-forwarding/CscCfConditionPopupWeekdays.vue @@ -0,0 +1,55 @@ + + + diff --git a/src/components/call-forwarding/CscCfGroup.vue b/src/components/call-forwarding/CscCfGroup.vue new file mode 100644 index 00000000..01fe8bb2 --- /dev/null +++ b/src/components/call-forwarding/CscCfGroup.vue @@ -0,0 +1,91 @@ + + diff --git a/src/components/call-forwarding/CscCfGroupCondition.vue b/src/components/call-forwarding/CscCfGroupCondition.vue new file mode 100644 index 00000000..4b9bdac9 --- /dev/null +++ b/src/components/call-forwarding/CscCfGroupCondition.vue @@ -0,0 +1,90 @@ + + + diff --git a/src/components/call-forwarding/CscCfGroupConditionDate.vue b/src/components/call-forwarding/CscCfGroupConditionDate.vue new file mode 100644 index 00000000..78d84a2f --- /dev/null +++ b/src/components/call-forwarding/CscCfGroupConditionDate.vue @@ -0,0 +1,123 @@ + + diff --git a/src/components/call-forwarding/CscCfGroupConditionDateRange.vue b/src/components/call-forwarding/CscCfGroupConditionDateRange.vue new file mode 100644 index 00000000..910115db --- /dev/null +++ b/src/components/call-forwarding/CscCfGroupConditionDateRange.vue @@ -0,0 +1,137 @@ + + diff --git a/src/components/call-forwarding/CscCfGroupConditionMenu.vue b/src/components/call-forwarding/CscCfGroupConditionMenu.vue new file mode 100644 index 00000000..38c3b336 --- /dev/null +++ b/src/components/call-forwarding/CscCfGroupConditionMenu.vue @@ -0,0 +1,80 @@ + + + diff --git a/src/components/call-forwarding/CscCfGroupConditionOfficeHours.vue b/src/components/call-forwarding/CscCfGroupConditionOfficeHours.vue new file mode 100644 index 00000000..cc3ff3a3 --- /dev/null +++ b/src/components/call-forwarding/CscCfGroupConditionOfficeHours.vue @@ -0,0 +1,388 @@ + + + diff --git a/src/components/call-forwarding/CscCfGroupConditionSourceSetCreate.vue b/src/components/call-forwarding/CscCfGroupConditionSourceSetCreate.vue new file mode 100644 index 00000000..4920545e --- /dev/null +++ b/src/components/call-forwarding/CscCfGroupConditionSourceSetCreate.vue @@ -0,0 +1,229 @@ + + + diff --git a/src/components/call-forwarding/CscCfGroupConditionSourceSetSelect.vue b/src/components/call-forwarding/CscCfGroupConditionSourceSetSelect.vue new file mode 100644 index 00000000..7913cd7a --- /dev/null +++ b/src/components/call-forwarding/CscCfGroupConditionSourceSetSelect.vue @@ -0,0 +1,103 @@ + + + diff --git a/src/components/call-forwarding/CscCfGroupConditionWeekdays.vue b/src/components/call-forwarding/CscCfGroupConditionWeekdays.vue new file mode 100644 index 00000000..23424682 --- /dev/null +++ b/src/components/call-forwarding/CscCfGroupConditionWeekdays.vue @@ -0,0 +1,113 @@ + + diff --git a/src/components/call-forwarding/CscCfGroupItem.vue b/src/components/call-forwarding/CscCfGroupItem.vue new file mode 100644 index 00000000..80429479 --- /dev/null +++ b/src/components/call-forwarding/CscCfGroupItem.vue @@ -0,0 +1,307 @@ + + + diff --git a/src/components/call-forwarding/CscCfGroupItemPrimaryNumber.vue b/src/components/call-forwarding/CscCfGroupItemPrimaryNumber.vue new file mode 100644 index 00000000..7f5fb543 --- /dev/null +++ b/src/components/call-forwarding/CscCfGroupItemPrimaryNumber.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/components/call-forwarding/CscCfGroupTitle.vue b/src/components/call-forwarding/CscCfGroupTitle.vue new file mode 100644 index 00000000..9acdfac0 --- /dev/null +++ b/src/components/call-forwarding/CscCfGroupTitle.vue @@ -0,0 +1,366 @@ + + + diff --git a/src/components/call-forwarding/CscCfSelectionWeekdays.vue b/src/components/call-forwarding/CscCfSelectionWeekdays.vue new file mode 100644 index 00000000..3976ddf3 --- /dev/null +++ b/src/components/call-forwarding/CscCfSelectionWeekdays.vue @@ -0,0 +1,113 @@ + + + diff --git a/src/components/call-forwarding/CscCfSourceSetSelection.vue b/src/components/call-forwarding/CscCfSourceSetSelection.vue new file mode 100644 index 00000000..ac410250 --- /dev/null +++ b/src/components/call-forwarding/CscCfSourceSetSelection.vue @@ -0,0 +1,75 @@ + + diff --git a/src/components/form/CscInput.vue b/src/components/form/CscInput.vue index 61a17a9e..b7262e6b 100644 --- a/src/components/form/CscInput.vue +++ b/src/components/form/CscInput.vue @@ -29,7 +29,7 @@ /> { + 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 () { + +} diff --git a/src/i18n/en.json b/src/i18n/en.json index 78ab2831..2597731e 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -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": { diff --git a/src/layouts/CscLayoutMain.vue b/src/layouts/CscLayoutMain.vue index 05cbf41e..6e41b7bb 100644 --- a/src/layouts/CscLayoutMain.vue +++ b/src/layouts/CscLayoutMain.vue @@ -147,17 +147,6 @@ :is-pbx-admin="isPbxAdmin" :is-pbx-configuration="isPbxConfiguration" /> - + + +
+
+ + + + + {{ $t('Always') }} + + + + + + +
+
+
+ + diff --git a/src/router/routes.js b/src/router/routes.js index 3c7185ed..512b0c75 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -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, diff --git a/src/store/call-forwarding/actions.js b/src/store/call-forwarding/actions.js new file mode 100644 index 00000000..3c1e8906 --- /dev/null +++ b/src/store/call-forwarding/actions.js @@ -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 }) +} diff --git a/src/store/call-forwarding/getters.js b/src/store/call-forwarding/getters.js new file mode 100644 index 00000000..0bdf1501 --- /dev/null +++ b/src/store/call-forwarding/getters.js @@ -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 +} diff --git a/src/store/call-forwarding/index.js b/src/store/call-forwarding/index.js new file mode 100644 index 00000000..04179f02 --- /dev/null +++ b/src/store/call-forwarding/index.js @@ -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 +} diff --git a/src/store/call-forwarding/mutations.js b/src/store/call-forwarding/mutations.js new file mode 100644 index 00000000..93cda72b --- /dev/null +++ b/src/store/call-forwarding/mutations.js @@ -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 + } +} diff --git a/src/store/call-forwarding/state.js b/src/store/call-forwarding/state.js new file mode 100644 index 00000000..63606512 --- /dev/null +++ b/src/store/call-forwarding/state.js @@ -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: {} + } +} diff --git a/src/store/index.js b/src/store/index.js index 5b27ac10..ecd0bdea 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -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: { diff --git a/src/store/user.js b/src/store/user.js index 039bfcb3..1774fdd6 100644 --- a/src/store/user.js +++ b/src/store/user.js @@ -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: { diff --git a/yarn.lock b/yarn.lock index 469cf522..4a8c7cac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"