MT#55940 Add possibility to configure "user defined buttons" on phones via CSC panel

Change-Id: I88b0f3fff5396f7b4ec93bfda9efd1143742131d
pull/27/merge
Hugo Zigha 3 years ago
parent 4c80215e00
commit 0308c9ee95

@ -102,13 +102,11 @@
<csc-pbx-device-config <csc-pbx-device-config
v-if="modelImage" v-if="modelImage"
:device="device" :device="device"
:profile="profile"
:model="model" :model="model"
:model-image="modelImage" :model-image="modelImage"
:loading="subscribersLoading" :loading="subscribersLoading"
:subscribers="subscribers" :subscribers="subscribers"
:subscriber-map="subscriberMap" :subscriber-map="subscriberMap"
:subscriber-options="subscriberOptions"
@keysChanged="saveKeys" @keysChanged="saveKeys"
/> />
</template> </template>

@ -30,67 +30,17 @@
</div> </div>
</div> </div>
<div <div
v-show="keyOverlayActive" v-if="keyOverlayActive"
class="csc-pbx-device-config-key-overlay animate-fade" class="csc-pbx-device-config-key-overlay animate-fade"
> >
<div <csc-pbx-device-config-key-form
class="csc-device-key-title row justify-center items-center" :selected-line="selectedLine"
> :selected-key="selectedKey"
<q-icon :subsriber-map="subscriberMap"
class="csc-device-key-title-icon" :loading="isDeviceLoading(device.id)"
name="touch_app" @closeKeyOverlay="keyOverlayActive = false"
size="24px" @onSave="onSave"
/>
<div
class="column"
>
<div
class="csc-device-key-title-main"
>
{{ selectedKeySetName }}: {{ $t('Lamp/Key') }} {{ selectedKeyNumber }}
</div>
</div>
</div>
<csc-pbx-auto-attendant-selection
:value="selectedKeySubscriber"
:options="subscriberOptions"
@input="keySubscriberChanged"
/> />
<q-select
v-show="selectedKeySubscriber !== null && selectedKeySubscriber.value !== null"
ref="selectType"
v-model="selectedKeyType"
emit-value
map-options
:label="$t('Lamp/Key')"
:options="typeOptions"
@input="keyTypeChanged"
>
<template
v-slot:prepend
>
<q-icon
name="radio_button_checked"
/>
</template>
</q-select>
<div
class="row justify-center actions"
>
<div
class="column"
>
<q-btn
flat
icon="clear"
color="white"
:big="isMobile"
@click="closeKeyOverlay()"
>
{{ $t('Close') }}
</q-btn>
</div>
</div>
</div> </div>
<q-resize-observer <q-resize-observer
@resize="windowResize" @resize="windowResize"
@ -100,28 +50,22 @@
<script> <script>
import _ from 'lodash' import _ from 'lodash'
import CscPbxAutoAttendantSelection from './CscPbxAutoAttendantSelection' import CscPbxDeviceConfigKeyForm from './CscPbxDeviceConfigKeyForm'
import {
Platform
} from 'quasar'
import { import {
BoundingBox2D BoundingBox2D
} from 'src/helpers/graphics' } from 'src/helpers/graphics'
import { mapGetters } from 'vuex'
export default { export default {
name: 'CscPbxDeviceConfig', name: 'CscPbxDeviceConfig',
components: { components: {
CscPbxAutoAttendantSelection CscPbxDeviceConfigKeyForm
}, },
props: { props: {
device: { device: {
type: Object, type: Object,
default: null default: null
}, },
profile: {
type: Object,
default: null
},
model: { model: {
type: Object, type: Object,
default: null default: null
@ -130,12 +74,6 @@ export default {
type: Object, type: Object,
default: null default: null
}, },
subscriberOptions: {
type: Array,
default () {
return []
}
},
subscriberMap: { subscriberMap: {
type: Object, type: Object,
default: null default: null
@ -151,101 +89,13 @@ export default {
scaledBoundingBox: null, scaledBoundingBox: null,
selectedKey: null, selectedKey: null,
selectedLine: null, selectedLine: null,
keyOverlayActive: false, keyOverlayActive: false
selectedKeyTypeData: null,
selectedLineIndex: null
} }
}, },
computed: { computed: {
selectedKeyIcon () { ...mapGetters('pbxDevices', [
if (this.selectedLine !== null) { 'isDeviceLoading'
const subscriber = this.subscriberMap[this.selectedLine.subscriber_id] ]),
if (subscriber !== null && subscriber.is_pbx_pilot === true) {
return 'person_outline'
} else if (subscriber !== null && subscriber.is_pbx_group === true) {
return 'group'
} else if (subscriber !== null) {
return 'person'
} else {
return ''
}
}
return ''
},
selectedKeySubscriber () {
const unassignedItem = this.subscriberOptions[0]
if (this.selectedLine !== null) {
const selectedOption = this.subscriberOptions.find(opt => opt.value === this.selectedLine.subscriber_id)
return selectedOption || unassignedItem
}
return unassignedItem
},
selectedKeyType: {
get () {
if (this.selectedLine !== null) {
return this.selectedLine.type
}
return _.get(this.typeOptions, '0.value', '')
},
set (type) {
this.selectedKeyTypeData = type
}
},
selectedKeySetName () {
if (this.selectedKey !== null) {
return this.selectedKey.keySet.name
}
return ''
},
selectedKeyNumber () {
if (this.selectedKey !== null) {
return (this.selectedKey.index + 1)
}
return ''
},
typeOptions () {
const options = []
if (this.selectedKey !== null && this.selectedKey.keySet.can_blf) {
options.push({
label: this.$t('Busy Lamp Field'),
value: 'blf'
})
}
if (this.selectedKey !== null && this.selectedKey.keySet.can_private) {
options.push({
label: this.$t('Private'),
value: 'private'
})
}
if (this.selectedKey !== null && this.selectedKey.keySet.can_shared) {
options.push({
label: this.$t('Shared'),
value: 'shared'
})
}
if (this.selectedKey !== null && this.selectedKey.keySet.can_speeddial) {
options.push({
label: this.$t('SpeedDial'),
value: 'speeddial'
})
}
if (this.selectedKey !== null && this.selectedKey.keySet.can_forward) {
options.push({
label: this.$t('Forward'),
value: 'forward'
})
}
if (this.selectedKey !== null && this.selectedKey.keySet.can_transfer) {
options.push({
label: this.$t('Transfer'),
value: 'transfer'
})
}
return options
},
isMobile () {
return Platform.is.mobile
},
imageUrl () { imageUrl () {
return _.get(this.modelImage, 'url', null) return _.get(this.modelImage, 'url', null)
}, },
@ -265,9 +115,6 @@ export default {
}) })
return keys return keys
}, },
lines () {
return _.get(this.device, 'lines', [])
},
canvasStyles () { canvasStyles () {
return { return {
width: this.configWidth + 'px' width: this.configWidth + 'px'
@ -283,6 +130,9 @@ export default {
left: this.imageDeltaX + 'px', left: this.imageDeltaX + 'px',
width: (this.imageWidth * this.imageScaleFactor) + 'px' width: (this.imageWidth * this.imageScaleFactor) + 'px'
} }
},
lines () {
return _.get(this.device, 'lines', [])
} }
}, },
watch: { watch: {
@ -295,7 +145,6 @@ export default {
mounted () { mounted () {
this.boundingBox = BoundingBox2D.createFromPoints(this.keys) this.boundingBox = BoundingBox2D.createFromPoints(this.keys)
this.boundingBox.addMargin(40) this.boundingBox.addMargin(40)
this.loadGroupsAndSeats()
}, },
methods: { methods: {
getLineByKey (key) { getLineByKey (key) {
@ -317,8 +166,8 @@ export default {
this.placeImage() this.placeImage()
}, },
resize () { resize () {
this.imageWidth = this.$refs.image.naturalWidth this.imageWidth = this.$refs.image?.naturalWidth
this.configWidth = this.$refs.config.clientWidth this.configWidth = this.$refs.config?.clientWidth
if (this.boundingBox !== null) { if (this.boundingBox !== null) {
if (this.boundingBox.getWidth() > this.configWidth) { if (this.boundingBox.getWidth() > this.configWidth) {
this.imageScaleFactor = this.configWidth / this.boundingBox.getWidth() this.imageScaleFactor = this.configWidth / this.boundingBox.getWidth()
@ -404,32 +253,19 @@ export default {
this.keyOverlayActive = true this.keyOverlayActive = true
this.$scrollTo(this.$parent.$el) this.$scrollTo(this.$parent.$el)
}, },
closeKeyOverlay () { onSave(newLine) {
this.keyOverlayActive = false
},
loadGroupsAndSeats () {
this.$emit('loadGroupsAndSeats')
},
keySubscriberChanged ({ value: subscriberId }) {
const newLines = [] const newLines = []
const lines = _.clone(this.lines) const lines = _.clone(this.lines)
const line = this.getLineByKey(this.selectedKey) const line = this.getLineByKey(this.selectedKey)
let changed = false if (line !== null && newLine.type === null ) {
if (line !== null && subscriberId === null) {
delete lines[line.index] delete lines[line.index]
changed = true
} else if (line !== null) { } else if (line !== null) {
_.set(lines, line.index + '.subscriber_id', subscriberId) _.set(lines, line.index + '.subscriber_id', newLine.subscriber_id)
changed = true _.set(lines, line.index + '.target_number', newLine.target_number)
} else if (subscriberId !== null) { _.set(lines, line.index + '.type', newLine.type)
newLines.push({
extension_unit: 0, } else {
key_num: this.selectedKey.index, newLines.push(newLine)
subscriber_id: subscriberId,
linerange: this.selectedKey.keySet.name,
type: this.$refs.selectType.value
})
changed = true
} }
lines.forEach((line) => { lines.forEach((line) => {
newLines.push({ newLines.push({
@ -437,30 +273,11 @@ export default {
key_num: line.key_num, key_num: line.key_num,
subscriber_id: line.subscriber_id, subscriber_id: line.subscriber_id,
linerange: line.linerange, linerange: line.linerange,
type: line.type type: line.type,
target_number: line.target_number
}) })
}) })
if (changed === true) { this.$emit('keysChanged', newLines)
this.$emit('keysChanged', newLines)
}
},
keyTypeChanged (type) {
const newLines = []
const lines = _.clone(this.lines)
const line = this.getLineByKey(this.selectedKey)
if (line != null) {
_.set(lines, line.index + '.type', type)
lines.forEach((line) => {
newLines.push({
extension_unit: line.extension_unit,
key_num: line.key_num,
subscriber_id: line.subscriber_id,
linerange: line.linerange,
type: line.type
})
})
this.$emit('keysChanged', newLines)
}
} }
} }
} }

@ -0,0 +1,370 @@
<template>
<div>
<div
class="csc-device-key-title row justify-center items-center"
>
<q-icon
class="csc-device-key-title-icon"
name="touch_app"
size="24px"
/>
<div
class="column"
>
<div
class="csc-device-key-title-main"
>
{{ selectedKeySetName }}: {{ $t('Lamp/Key') }} {{ selectedKeyNumber }}
</div>
</div>
</div>
<q-select
ref="selectType"
v-if="changes"
v-model="changes.type"
:disable="loading"
emit-value
map-options
:label="$t('Lamp/Key')"
:options="typeOptions"
@input="keyTypeChanged"
>
<template
v-slot:prepend
>
<q-icon
name="radio_button_checked"
/>
</template>
</q-select>
<q-toggle
v-if="showCustomNumberToggle"
class="q-pt-md"
v-model="hasTargetNumber"
:label="$t('Use custom number')"
:disable="loading"
data-cy="pbxdevices-target_number"
@input="targetNumberToggleChanged()"
/>
<csc-pbx-auto-attendant-selection
v-if="changes.type && !hasTargetNumber"
:value="selectedKeySubscriber"
:options="subscriberOptions"
:disable="loading"
@input="keySubscriberChanged"
/>
<csc-input
v-if="showCustomNumberToggle && hasTargetNumber"
v-model="changes.target_number"
:disable="loading"
clearable
dense
hide-bottom-space
hide-hint
:error="$v.changes.target_number.$error"
:error-message="targetNumberErrorMessage"
:label="$t('Number')"
@input="$v.changes.target_number.$touch"
/>
<csc-list-spinner
v-if="loading"
/>
<div
class="row justify-center actions"
>
<div
class="row"
>
<q-btn
flat
icon="clear"
color="white"
:big="isMobile"
:disable="loading"
@click="closeKeyOverlay()"
>
{{ $t('Close') }}
</q-btn>
<q-btn
v-if="hasTypeChanged || hasSubscriberChanged || hasTargetNumberChanged"
flat
icon="undo"
color="white"
:disable="loading"
@click="resetData()"
>
{{ $t('Reset') }}
</q-btn>
<q-btn
v-if="hasTypeChanged || hasSubscriberChanged || hasTargetNumberChanged"
:label="$t('Save')"
:disable="($v.changes.$invalid && hasTargetNumber) || loading"
flat
color="primary"
icon="check"
@click="onSave"
/>
</div>
</div>
</div>
</template>
<script>
import _ from 'lodash'
import CscPbxAutoAttendantSelection from './CscPbxAutoAttendantSelection'
import CscInput from 'components/form/CscInput'
import CscListSpinner from 'components/CscListSpinner'
import { mapState } from 'vuex'
import {
Platform
} from 'quasar'
import {
required
} from 'vuelidate/lib/validators'
export default {
name: 'CscPbxDeviceConfigKeyForm',
components: {
CscPbxAutoAttendantSelection,
CscInput,
CscListSpinner
},
props: {
selectedLine: {
type: Object,
default: null
},
selectedKey: {
type: Object,
default: null
},
subscriberMap: {
type: Object,
default: null
},
loading: {
type: Boolean,
default: false
}
},
validations: {
changes: {
target_number: {
required,
onlyChars: function (value) {
const regExpTargetNumber = new RegExp("^[*#0-9]*$");
return regExpTargetNumber.test(value)
}
}
}
},
data () {
return {
keyData: this.getKeyData(),
changes: this.getKeyData(),
hasTargetNumber: this.selectedLine?.target_number ? true : false
}
},
watch: {
selectedLine () {
this.changes = this.getKeyData()
this.keyData = this.getKeyData()
this.hasTargetNumber = this.selectedLine?.target_number ? true : false
}
},
computed: {
...mapState('pbx', [
'subscriberList'
]),
hasSubscriberChanged () {
return this.keyData.subscriber_id !== this.changes.subscriber_id
},
hasTypeChanged () {
return this.keyData.type !== this.changes.type
},
hasTargetNumberChanged () {
return this.keyData.target_number !== this.changes.target_number
},
subscriberOptions () {
const options = []
this.subscriberList.forEach((subscriber) => {
let icon = 'person'
let subscriberTypeTitle = this.$t('Seat')
if (subscriber.is_pbx_group) {
icon = 'group'
subscriberTypeTitle = this.$t('Group')
} else if (subscriber.is_pbx_pilot) {
icon = 'person_outline'
subscriberTypeTitle = this.$t('Pilot')
}
options.push({
label: subscriber.display_name || subscriber.webusername,
icon: icon,
value: subscriber.id,
subscriberTypeTitle
})
})
return options
},
selectedKeyIcon () {
if (this.selectedLine !== null) {
const subscriber = this.subscriberMap[this.selectedLine.subscriber_id]
if (subscriber !== null && subscriber.is_pbx_pilot === true) {
return 'person_outline'
} else if (subscriber !== null && subscriber.is_pbx_group === true) {
return 'group'
} else if (subscriber !== null) {
return 'person'
} else {
return ''
}
}
return ''
},
selectedKeySubscriber () {
const unassignedItem = this.subscriberOptions[0]
if (this.changes) {
const selectedOption = this.subscriberOptions.find(opt => opt?.value === this.changes.subscriber_id)
return selectedOption || unassignedItem
}
return unassignedItem
},
selectedKeySetName () {
if (this.selectedLine !== null) {
return this.selectedLine.linerange
}
return ''
},
selectedKeyNumber () {
if (this.selectedLine !== null) {
return (this.selectedLine.key_num + 1)
}
return ''
},
typeOptions () {
const options = []
options.push({
label: this.$t('N/A'),
value: null
})
if (this.selectedKey !== null && this.selectedKey.keySet.can_blf) {
options.push({
label: this.$t('Busy Lamp Field'),
value: 'blf'
})
}
if (this.selectedKey !== null && this.selectedKey.keySet.can_private) {
options.push({
label: this.$t('Private'),
value: 'private'
})
}
if (this.selectedKey !== null && this.selectedKey.keySet.can_shared) {
options.push({
label: this.$t('Shared'),
value: 'shared'
})
}
if (this.selectedKey !== null && this.selectedKey.keySet.can_speeddial) {
options.push({
label: this.$t('Speed Dial'),
value: 'speeddial'
})
}
if (this.selectedKey !== null && this.selectedKey.keySet.can_forward) {
options.push({
label: this.$t('Forward'),
value: 'forward'
})
}
if (this.selectedKey !== null && this.selectedKey.keySet.can_transfer) {
options.push({
label: this.$t('Transfer'),
value: 'transfer'
})
}
return options
},
isMobile () {
return Platform.is.mobile
},
showCustomNumberToggle () {
return this.changes.type === 'transfer' || this.changes.type === 'speeddial' || this.changes.type === 'forward'
},
targetNumberErrorMessage () {
if (!this.$v.changes.target_number.required) {
return this.$t('{field} is required', {
field: this.$t('Target number')
})
} else if (!this.$v.changes.target_number.onlyChars) {
return this.$t('{field} must consist only of numeric characters or the symbols * or #.', {
field: this.$t('Target number')
})
} else {
return ''
}
}
},
methods: {
getKeyData () {
if (this.selectedLine) {
this.hasTargetNumber = this.selectedLine.target_number ? true : false
return {
extension_unit: this.selectedLine.extension_unit,
key_num: this.selectedLine.key_num,
subscriber_id: this.selectedLine.subscriber_id,
linerange: this.selectedLine.linerange,
type: this.selectedLine.type,
target_number: this.selectedLine.target_number
}
} else {
return {
extension_unit: 0,
key_num: this.selectedKey.index,
subscriber_id: null,
linerange: this.selectedKey.keySet.name,
type: null,
target_number: null
}
}
},
closeKeyOverlay () {
this.$emit('closeKeyOverlay')
},
keyTypeChanged () {
if (this.changes.type !== null && this.changes.subscriber_id === null) {
const pbxPilot = this.subscriberList.find((subscriber) => subscriber.is_pbx_pilot)
this.changes.subscriber_id = pbxPilot ? pbxPilot.id : this.subscriberList[0].id
}
if (this.changes.type === null) {
this.changes.subscriber_id = null
this.changes.target_number = null
this.hasTargetNumber = false
}
if (!this.showCustomNumberToggle) {
this.changes.target_number = null
this.hasTargetNumber = false
}
},
keySubscriberChanged ({ value: subscriberId}) {
this.changes.subscriber_id = subscriberId
},
targetNumberToggleChanged () {
if (!this.hasTargetNumber) {
this.changes.target_number = null
} else {
const pbxPilot = this.subscriberList.find((subscriber) => subscriber.is_pbx_pilot)
this.changes.subscriber_id = pbxPilot ? pbxPilot.id : this.subscriberList[0].id
}
},
resetData () {
this.changes = _.clone(this.keyData)
this.hasTargetNumber = this.selectedLine?.target_number ? true : false
},
onSave () {
this.$emit('onSave', this.changes)
}
}
}
</script>

@ -57,6 +57,7 @@
"Call": "Call", "Call": "Call",
"Call Blocking": "Call Blocking", "Call Blocking": "Call Blocking",
"Call Forwarding": "Call Forwarding", "Call Forwarding": "Call Forwarding",
"Call Forwards": "Call Forwards",
"Call List": "Call List", "Call List": "Call List",
"Call Queue": "Call Queue", "Call Queue": "Call Queue",
"Call Queue feature": "Call Queue feature", "Call Queue feature": "Call Queue feature",
@ -171,6 +172,7 @@
"Folder : ": "Folder : ", "Folder : ": "Folder : ",
"Forgot password?": "Forgot password?", "Forgot password?": "Forgot password?",
"Format": "Format", "Format": "Format",
"Forward": "Forward",
"Forward to Auto Attendant": "Forward to Auto Attendant", "Forward to Auto Attendant": "Forward to Auto Attendant",
"Forward to Call Through": "Forward to Call Through", "Forward to Call Through": "Forward to Call Through",
"Forward to Calling Card": "Forward to Calling Card", "Forward to Calling Card": "Forward to Calling Card",
@ -246,6 +248,7 @@
"Music on Hold": "Music on Hold", "Music on Hold": "Music on Hold",
"Music on hold": "Music on hold", "Music on hold": "Music on hold",
"MyNumberList": "MyNumberList", "MyNumberList": "MyNumberList",
"N/A": "N/A",
"Name": "Name", "Name": "Name",
"Name in Fax Header for Sendfax": "Name in Fax Header for Sendfax", "Name in Fax Header for Sendfax": "Name in Fax Header for Sendfax",
"Never": "Never", "Never": "Never",
@ -308,6 +311,7 @@
"Play all files in loop": "Play all files in loop", "Play all files in loop": "Play all files in loop",
"Please fill or remove the empty slots": "Please fill or remove the empty slots", "Please fill or remove the empty slots": "Please fill or remove the empty slots",
"Please select an option": "Please select an option", "Please select an option": "Please select an option",
"Preferences": "Preferences",
"Primary Number": "Primary Number", "Primary Number": "Primary Number",
"Privacy": "Privacy", "Privacy": "Privacy",
"Private": "Private", "Private": "Private",
@ -424,6 +428,7 @@
"Sunday": "Sunday", "Sunday": "Sunday",
"Super": "Super", "Super": "Super",
"T38": "T38", "T38": "T38",
"Target number": "Target number",
"Th": "Th", "Th": "Th",
"The \"{timeset}\" timeset contains incompatible values. You can resolve this by deleting it and recreating from the scratch.": "The \"{timeset}\" timeset contains incompatible values. You can resolve this by deleting it and recreating from the scratch.", "The \"{timeset}\" timeset contains incompatible values. You can resolve this by deleting it and recreating from the scratch.": "The \"{timeset}\" timeset contains incompatible values. You can resolve this by deleting it and recreating from the scratch.",
"The Destination Email is already used": "The Destination Email is already used", "The Destination Email is already used": "The Destination Email is already used",
@ -437,6 +442,7 @@
"Timerange": "Timerange", "Timerange": "Timerange",
"To": "To", "To": "To",
"Today": "Today", "Today": "Today",
"Transfer": "Transfer",
"Tu": "Tu", "Tu": "Tu",
"Tuesday": "Tuesday", "Tuesday": "Tuesday",
"Type": "Type", "Type": "Type",
@ -458,6 +464,7 @@
"Upload": "Upload", "Upload": "Upload",
"Use RegExp": "Use RegExp", "Use RegExp": "Use RegExp",
"Use as default for all seats and groups": "Use as default for all seats and groups", "Use as default for all seats and groups": "Use as default for all seats and groups",
"Use custom number": "Use custom number",
"Use language specific preset": "Use language specific preset", "Use language specific preset": "Use language specific preset",
"User Agent": "User Agent", "User Agent": "User Agent",
"User settings": "User settings", "User settings": "User settings",
@ -550,5 +557,6 @@
"{field} must be at least {minValue} second": "{field} must be at least {minValue} second", "{field} must be at least {minValue} second": "{field} must be at least {minValue} second",
"{field} must be maximum of {maxValue} seconds": "{field} must be maximum of {maxValue} seconds", "{field} must be maximum of {maxValue} seconds": "{field} must be maximum of {maxValue} seconds",
"{field} must consist of numeric characters only": "{field} must consist of numeric characters only", "{field} must consist of numeric characters only": "{field} must consist of numeric characters only",
"{field} must consist only of numeric characters or the symbols * or #.": "{field} must consist only of numeric characters or the symbols * or #.",
"{field} must have at most {maxLength} letters": "{field} must have at most {maxLength} letters" "{field} must have at most {maxLength} letters": "{field} must have at most {maxLength} letters"
} }
Loading…
Cancel
Save