MT#57080 Expose Phonebook in the CSC

Change-Id: Iffc7ae9ed6acda2ea7cdb742aeee51ce924d2309
mr12.0
nouhaila 2 years ago committed by Nouhaila Idrissi-Zouggari
parent a72696e878
commit 37a807dd0f

@ -55,14 +55,39 @@ export async function setPreference (id, field, value) {
}
}
}
export async function setPreferencePhonebook (id, field, value) {
if (value === undefined || value === null || value === '' || (Array.isArray(value) && !value.length)) {
await removePreferencePhonebook(id, field)
} else {
try {
await replacePreferencePhonebook(id, field, value)
} catch (err) {
const errCode = err.status + ''
if (errCode === '422') {
// eslint-disable-next-line no-useless-catch
try {
await addPreferencePhonebook(id, field, value)
} catch (innerErr) {
throw innerErr
}
} else {
throw err
}
}
}
}
export async function removePreference (id, field) {
return await patchRemove({
path: 'api/subscriberpreferences/' + id,
fieldPath: field
})
}
export async function removePreferencePhonebook (id, field) {
return await patchRemove({
path: 'api/phonebookentries/' + id,
fieldPath: field
})
}
export function addPreference (id, field, value) {
return new Promise((resolve, reject) => {
patchAdd({
@ -76,7 +101,19 @@ export function addPreference (id, field, value) {
})
})
}
export function addPreferencePhonebook (id, field, value) {
return new Promise((resolve, reject) => {
patchAdd({
path: 'api/phonebookentries/' + id,
fieldPath: field,
value: value
}).then(() => {
resolve()
}).catch((err) => {
reject(err)
})
})
}
export function addPreferenceFull (id, field, value) {
return new Promise((resolve, reject) => {
patchAddFull({
@ -104,7 +141,19 @@ export function replacePreference (id, field, value) {
})
})
}
export function replacePreferencePhonebook (id, field, value) {
return new Promise((resolve, reject) => {
patchReplace({
path: 'api/phonebookentries/' + id,
fieldPath: field,
value: value
}).then(() => {
resolve()
}).catch((err) => {
reject(err)
})
})
}
export function prependItemToArrayPreference (id, field, value) {
return new Promise((resolve, reject) => {
Promise.resolve().then(() => {
@ -652,7 +701,41 @@ export async function getSubscriberRegistrations (options) {
})
return list
}
export async function getSubscriberPhonebook (options) {
let all = false
if (options.rows === 0) {
delete options.rows
delete options.page
all = true
}
if (!options.order_by) {
delete options.order_by
delete options.order_by_direction
}
const list = await getList({
resource: 'phonebookentries',
all,
params: options
})
return list
}
export async function createPhonebook (data) {
const payLoad = {
name: data.name,
number: data.number,
shared: data.shared
}
return await Vue.http.post('api/phonebookentries/', payLoad)
}
export function setValueShared (id, value) {
return setPreferencePhonebook(id, 'shared', value)
}
export function setValueName (id, value) {
return setPreferencePhonebook(id, 'name', value)
}
export function setValueNumber (id, value) {
return setPreferencePhonebook(id, 'number', value)
}
export async function getRecordingStream (fileId) {
return await getAsBlob({
path: 'api/callrecordingfiles/' + fileId

@ -76,6 +76,12 @@ export default {
sublabel: this.$t('Calls, Faxes, VoiceMails'),
visible: this.hasSubscriberProfileAttribute(PROFILE_ATTRIBUTE_MAP.conversations)
},
{
to: '/user/subscriber-phonebook',
icon: 'fas fa-user',
label: this.$t('Subscriber Phonebook'),
visible: true
},
{
icon: 'settings_phone',
label: this.$t('Call Settings'),

@ -47,21 +47,21 @@
<span
v-if="isInitiating"
>
{{ $t('Calling {number}...', {number: callNumberFormatted}) }}</span>
{{ $t('Calling {number}...', {number: callNumberFormatted || callNumberQuery }) }}</span>
<span
v-else-if="isRinging"
>
{{ $t('Ringing at {number}...', {number: callNumberFormatted}) }}</span>
{{ $t('Ringing at {number}...', {number: callNumberFormatted || callNumberQuery }) }}</span>
<span
v-else-if="isIncoming"
>
{{ $t('Incoming call from {number}...', {number: callNumberFormatted}) }}</span>
{{ $t('Incoming call from {number}...', {number: callNumberFormatted || callNumberQuery }) }}</span>
</div>
<div
v-else-if="isEnded"
class="csc-call-error"
>
{{ endedReason | startCase }} ({{ callNumberFormatted }})
{{ endedReason | startCase }} ({{ callNumberFormatted || callNumberQuery }})
</div>
</div>
</div>
@ -86,7 +86,7 @@
size="24px"
/>
<div>
{{ $t('In call with {number}', {number: callNumberFormatted}) }}
{{ $t('In call with {number}', {number: callNumberFormatted || callNumberQuery }) }}
</div>
<q-btn
v-if="!dialpadOpened"
@ -286,7 +286,7 @@
<div
class="csc-call-info-number"
>
{{ callNumberFormatted }}
{{ callNumberFormatted || callNumberQuery }}
</div>
</div>
</div>
@ -302,7 +302,7 @@
<div
class="csc-call-info-number"
>
{{ callNumberFormatted }}
{{ callNumberFormatted || callNumberQuery }}
</div>
</div>
</div>
@ -481,6 +481,9 @@ export default {
callNumberFormatted () {
return normalizeDestination(this.callNumber)
},
callNumberQuery () {
return normalizeDestination(this.$route.query.number)
},
iconToggleMicrophone () {
if (this.microphoneEnabled) {
return 'mic'

@ -489,7 +489,7 @@ export default {
this.faxDialog = false
},
startCall (localMedia) {
if (this.callNumberInput !== '' && this.callNumberInput !== null) {
if (this.$route.query.number !== '' && this.$route.query.number !== null || this.callNumberInput !== '' && this.callNumberInput !== null) {
this.$store.dispatch('call/start', localMedia)
}
},

@ -16,7 +16,7 @@
id="csc-call-number-input"
:label="$t('Enter a number to dial')"
data-cy="csc-call-number-input"
:value="callNumberInput"
:value="callNumberInput || $route.query.number"
:readonly="dialpadOpened"
clearable
:disable="!isCallEnabled"

@ -0,0 +1,193 @@
<template>
<csc-page-sticky
id="csc-page-subscriber-phonebook"
>
<template
v-slot:header
>
<q-btn
icon="add"
color="primary"
flat
:label="$t('Add Phonebook')"
@click="openAddPhonebook()"
/>
</template>
<csc-page
class="q-pa-lg"
>
<q-table
class="no-shadow"
:columns="columns"
:data="subscriberPhonebook"
:loading="$wait.is('loadSubscriberPhonebook')"
row-key="id"
:pagination.sync="pagination"
@request="fetchPaginatedRegistrations"
>
<template v-slot:loading>
<q-inner-loading
showing
color="primary"
>
<csc-spinner />
</q-inner-loading>
</template>
<template v-slot:top-left>
<q-btn
icon="refresh"
size="sm"
flat
@click="refresh"
>
{{ $t('Refresh') }}
</q-btn>
</template>
<template v-slot:body-cell-shared="{ row }">
<td>
<q-toggle
:value=row.shared
/>
</td>
</template>
<template v-slot:body-cell-menu="{ row }">
<td>
<csc-more-menu>
<csc-popup-menu-item
icon="fas fa-phone-alt"
color="primary"
:label="$t('Call back')"
@click="homePageCall(row)"
>
</csc-popup-menu-item>
<csc-popup-menu-item
icon="fas fa-pen"
color="primary"
:label="$t('Edit')"
@click="showPhonebookDetails(row)">
</csc-popup-menu-item>
</csc-more-menu>
</td>
</template>
</q-table>
</csc-page>
</csc-page-sticky>
</template>
<script>
import { mapState } from 'vuex'
import CscPage from 'components/CscPage'
import CscMoreMenu from 'components/CscMoreMenu'
import CscPopupMenuItem from 'components/CscPopupMenuItem'
import { mapWaitingActions } from 'vue-wait'
import CscSpinner from 'components/CscSpinner'
import {LIST_DEFAULT_ROWS} from "src/api/common";
import CscPageSticky from 'components/CscPageSticky'
export default {
name: 'CscPageSubscriberPhonebook',
components: {
CscSpinner,
CscPage,
CscMoreMenu,
CscPopupMenuItem,
CscPageSticky
},
data () {
return {
data: [],
pagination: {
sortBy: 'id',
descending: false,
page: 1,
rowsPerPage: LIST_DEFAULT_ROWS,
rowsNumber: 0
}
}
},
computed: {
...mapState('user', [
'subscriberPhonebook'
]),
columns () {
return [
{
name: 'id',
required: true,
label: this.$t('Id'),
align: 'left',
field: row => row.id,
sortable: true
},
{
name: 'name',
required: true,
align: 'left',
label: this.$t('Name'),
field: row => row.name,
sortable: true
},
{
name: 'number',
required: true,
align: 'left',
label: this.$t('Number'),
field: row => row.number,
sortable: true,
},
{
name: 'shared',
required: true,
align: 'left',
label: this.$t('Shared'),
field: row => row.shared,
sortable: true,
},
{
name: 'menu',
required: true,
align: 'left',
label: '',
sortable: true
}
]
}
},
async mounted () {
await this.refresh()
},
methods: {
...mapWaitingActions('user', {
loadSubscriberPhonebook: 'loadSubscriberPhonebook'
}),
async refresh () {
await this.fetchPaginatedRegistrations({
pagination: this.pagination
})
},
async fetchPaginatedRegistrations (props) {
const { page, rowsPerPage, sortBy, descending } = props.pagination
const count = await this.loadSubscriberPhonebook({
page,
rows: rowsPerPage,
order_by: sortBy,
order_by_direction: descending ? 'desc' : 'asc'
})
this.pagination = { ...props.pagination }
this.pagination.rowsNumber = count
},
async showPhonebookDetails (row) {
this.$router.push('/user/subscriber-phonebook/'+ row.id)
},
async openAddPhonebook () {
this.$router.push('/user/subscriber-phonebook/create')
},
async homePageCall (row) {
this.$router.push({
path: '/user/home',
query: { number: row.number } })
},
}
}
</script>

@ -0,0 +1,143 @@
<template>
<csc-page-sticky
id="csc-page-subscriber-phonebook-add"
ref="pageSticky"
>
<template
v-slot:header
>
<q-breadcrumbs
class="absolute-left q-ml-md text-weight-light"
active-color="primary"
separator-color="primary"
>
<q-breadcrumbs-el
class="cursor-pointer"
to="/user/subscriber-phonebook"
:label="$t('Subscriber Phonebook')"
icon="fas fa-user"
/>
<q-breadcrumbs-el
:label="$t('Add')"
/>
</q-breadcrumbs>
</template>
<q-item
class="col col-xs-12"
>
<q-list
class="col col-xs-12"
side
top
no-wrap
>
<q-input
v-model="formData.name"
:label="$t('Name')"
:error="$v.formData.name.$error"
:error-message="nameErrorMessage"
@input="$v.formData.name.$touch"
/>
<q-input
v-model="formData.number"
:label="$t('Number')"
:error="$v.formData.number.$error"
:error-message="numberErrorMessage"
@input="$v.formData.number.$touch"
/>
<q-toggle
:label="$t('Shared')"
v-model="formData.shared"
@input="$v.formData.shared"
/>
</q-list>
</q-item>
<div class="text-center">
<q-btn
icon="clear"
color="white"
flat
:label="$t('Cancel')"
@click="cancel"
/>
<q-btn
icon="check"
:label="$t('Confirm')"
unelevated
text-color="primary"
@click="confirm"
/>
</div>
</csc-page-sticky>
</template>
<script>
import {
required
} from 'vuelidate/lib/validators'
import { mapWaitingActions } from 'vue-wait'
import CscPageSticky from 'components/CscPageSticky'
export default {
name: 'CscPageSubscriberPhonebookAdd',
components: {
CscPageSticky,
},
validations: {
formData: {
name: {
required
},
number: {
required
}
}
},
data () {
return {
formData: this.getDefaultFormData()
}
},
computed: {
nameErrorMessage () {
if (!this.$v.formData.name.required) {
return this.$t('{field} is required', {
field: this.$t('Name')
})
} else {
return ''
}
},
numberErrorMessage () {
if (!this.$v.formData.number.required) {
return this.$t('{field} is required', {
field: this.$t('Number')
})
} else {
return ''
}
}
},
methods: {
...mapWaitingActions('user', {
createPhonebookSubscriber: 'createPhonebookSubscriber',
}),
getDefaultFormData () {
return {
name: '',
number: '',
shared: false
}
},
cancel () {
this.$router.push('/user/subscriber-phonebook/')
this.$emit('cancel')
},
async confirm () {
await this.createPhonebookSubscriber(this.formData)
await this.$router.push('/user/subscriber-phonebook/')
},
}
}
</script>

@ -0,0 +1,132 @@
<template>
<csc-page-sticky
id="csc-page-subscriber-phonebook-details"
ref="pageSticky"
>
<template
v-slot:header
>
<q-breadcrumbs
class="absolute-left q-ml-md text-weight-light"
active-color="primary"
separator-color="primary"
>
<q-breadcrumbs-el
class="cursor-pointer"
to="/user/subscriber-phonebook"
:label="$t('Subscriber Phonebook')"
icon="fas fa-user"
/>
<q-breadcrumbs-el
:label="name"
/>
</q-breadcrumbs>
</template>
<q-item
class="col col-xs-12"
>
<q-list
class="col col-xs-12"
side
top
no-wrap
>
<q-input
:label="$t('Name')"
v-model="name"
/>
<q-input
:required="true"
:label="$t('Number')"
v-model="number"
/>
<q-toggle
:label="$t('Shared')"
v-model=shared
/>
</q-list>
</q-item>
<div class="text-center">
<q-btn
icon="clear"
color="white"
flat
:label="$t('Cancel')"
@click="cancel"
/>
<q-btn
icon="check"
:label="$t('Confirm')"
unelevated
text-color="primary"
@click="confirm"
/>
</div>
</csc-page-sticky>
</template>
<script>
import { mapWaitingActions } from 'vue-wait'
import CscPageSticky from 'components/CscPageSticky'
export default {
name: 'CscPageSubscriberPhonebookDetails',
components: {
CscPageSticky,
},
data () {
return {
id: this.$route.params.id,
name: '',
number: '',
shared: false,
}
},
async mounted () {
await this.getPhonebook(this.id);
},
methods: {
...mapWaitingActions('user', {
getPhonebookDetails: 'getPhonebookDetails',
getValueShared: 'getValueShared',
getValueName: 'getValueName',
getValueNumber: 'getValueNumber'
}),
async getPhonebook (id) {
const response = await this.getPhonebookDetails(id)
this.name = response.body.name
this.number = response.body.number
this.shared = response.body.shared;
},
cancel () {
this.$router.push('/user/subscriber-phonebook/')
this.$emit('cancel')
},
async changeValueName() {
await this.getValueName( {
phonebookId: this.id,
name: this.name
})
},
changeValueShared() {
this.getValueShared( {
phonebookId: this.id,
shared: this.shared
})
},
changeValueNumber() {
this.getValueNumber( {
phonebookId: this.id,
number: this.number
})
},
async confirm () {
await this.changeValueName()
await this.changeValueShared()
await this.changeValueNumber()
await this.$router.push('/user/subscriber-phonebook/')
},
}
}
</script>

@ -31,11 +31,14 @@ import CscRecoverPassword from 'src/pages/CscRecoverPassword'
import CscPageCf from 'pages/CscPageCf'
import CscPageCallSettings from 'pages/CscPageCallSettings'
import CscPageRegisteredDevices from 'pages/CscPageRegisteredDevices'
import CscPageSubscriberPhonebook from 'pages/CscPageSubscriberPhonebook'
import CscPagePbxSettingsAutoAttendant from 'pages/CscPagePbxSettingsAutoAttendant'
import CscPageDashboard from 'pages/CscPageDashboard'
import CscPagePbxSettingsMsConfigs from 'pages/CscPagePbxSettingsMsConfigs'
import CscPagePbxSettingsCallQueues from 'pages/CscPagePbxSettingsCallQueues'
import CscPagePbxSoundSetDetails from 'src/pages/CscPagePbxSoundSetDetails'
import CscPageSubscriberPhonebookDetails from 'src/pages/CscPageSubscriberPhonebookDetails'
import CscPageSubscriberPhonebookAdd from 'src/pages/CscPageSubscriberPhonebookAdd'
const getToken = (route) => {
return {
@ -84,6 +87,34 @@ export default function routes (app) {
profileAttribute: PROFILE_ATTRIBUTE_MAP.conversations
}
},
{
path: 'subscriber-phonebook',
name: 'SubscriberPhonebook',
component: CscPageSubscriberPhonebook,
meta: {
get title () {
return i18n.t('Subscriber Phonebook')
}
}
},
{
path: 'subscriber-phonebook/create',
component: CscPageSubscriberPhonebookAdd,
meta: {
get title () {
return i18n.t('Add Phonebook')
}
}
},
{
path: 'subscriber-phonebook/:id',
component: CscPageSubscriberPhonebookDetails,
meta: {
get title () {
return i18n.t('Subscriber Phonebook')
}
}
},
{
path: 'call-forwarding',
component: CscPageCf,

@ -15,8 +15,13 @@ import {
recoverPassword,
getBrandingLogo,
getSubscriberRegistrations,
getSubscriberPhonebook,
getSubscriberProfile,
changeSIPPassword
setValueShared,
setValueName,
setValueNumber,
changeSIPPassword,
createPhonebook
} from '../api/subscriber'
import { deleteJwt, getJwt, getSubscriberId, setJwt, setSubscriberId } from 'src/auth'
import QRCode from 'qrcode'
@ -56,6 +61,8 @@ export default {
resellerBranding: null,
defaultBranding: {},
subscriberRegistrations: [],
subscriberPhonebook: [],
phonebookMap: {},
platformInfo: null,
qrCode: null,
qrExpiringTime: null
@ -273,6 +280,9 @@ export default {
setSubscriberRegistrations (state, value) {
state.subscriberRegistrations = value
},
setSubscriberPhonebook (state, value) {
state.subscriberPhonebook = value
},
setProfile (state, value) {
state.profile = value
},
@ -398,9 +408,37 @@ export default {
throw err
}
},
async loadSubscriberPhonebook ({ commit, dispatch, state, rootGetters }, options) {
try {
const list = await getSubscriberPhonebook({
...options
})
commit('setSubscriberPhonebook', list.items)
return list.totalCount
} catch (err) {
commit('setSubscriberPhonebook', [])
throw err
}
},
async removeSubscriberRegistration (context, row) {
await Vue.http.delete('api/subscriberregistrations/' + row.id)
},
async getPhonebookDetails (context, id) {
const list = await Vue.http.get('api/phonebookentries/' + id)
return list
},
async getValueShared (context, options) {
await setValueShared(options.phonebookId, options.shared)
},
async getValueName (context, options) {
await setValueName(options.phonebookId, options.name)
},
async getValueNumber (context, options) {
await setValueNumber(options.phonebookId, options.number)
},
async createPhonebookSubscriber (context, data) {
await createPhonebook(data)
},
async fetchAuthToken ({ commit, state, getters }, expiringTime = 300) {
const subscriber = state.subscriber
const expireDate = date.addToDate(new Date(), { seconds: expiringTime })

Loading…
Cancel
Save