TT#109763 Fix OfficeHours in the new CallForwarding implementation

AC:
Can see "Office Hours"-Condition working properly according to the Kamailio time periods
Can save and see the same periods after loaded the data again

In addition:
* added logic to automatic detecion and seting "Same Time..." checkbox state based on the time ranges data in the timeset instead of timeset name
* added time ranges validation
* reproduced notification about invalid timeset which was in the old UI
* fixed popup layout to support long list of time ranges

Change-Id: Ic3a7b75a1d47c576beece660ca3fa56b9cc5b386
mr9.4
Sergii Leonenko 5 years ago
parent 3d9a5d8ace
commit 56efed61f2

@ -242,88 +242,22 @@ export async function cfUpdateTimeSetWeekdays (timeSetId, weekdays) {
}) })
} }
function cfNormaliseOfficeHours (timesPerWeekday) { export async function cfCreateOfficeHours (subscriberId, times) {
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({ return post({
resource: 'cftimesets', resource: 'cftimesets',
body: { body: {
subscriber_id: subscriberId, subscriber_id: subscriberId,
name: 'csc-office-hours-' + v4(), name: 'csc-office-hours-' + v4(),
times: cfNormaliseOfficeHours(timesPerWeekday) times: times
}
})
}
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) { export async function cfUpdateOfficeHours (timeSetId, times) {
return patchReplace({ return patchReplace({
resource: 'cftimesets', resource: 'cftimesets',
resourceId: timeSetId, resourceId: timeSetId,
fieldPath: 'times', fieldPath: 'times',
value: cfNormaliseOfficeHoursSameTimes(times, weekdays) value: times
}) })
} }

@ -7,129 +7,152 @@
@back="back" @back="back"
@close="$emit('close')" @close="$emit('close')"
> >
<div <template
class="row justify-center q-pt-md" v-if="invalidTimeset"
> >
<q-checkbox <q-banner
v-model="sameTimes" rounded
:label="$t('Same time for selected days')" dense
/> class="bg-red-8 text-white q-pt-md q-ma-md half-screen-width"
</div> >
<q-list <template v-slot:avatar>
dense <q-icon name="date_range" />
</template>
{{ $t('The "{timeset}" timeset contains incompatible values. You can resolve this by deleting it and recreating from the scratch.', { timeset: timeSet.name }) }}
</q-banner>
</template>
<template
v-else
> >
<q-item <div
class="q-mb-md q-mt-md" class="row justify-center q-pt-md"
>
<q-checkbox
v-model="sameTimes"
:label="$t('Same time for selected days')"
:disable="$v.$invalid"
/>
</div>
<div
class="row q-ma-md"
> >
<q-item-section> <q-item-section>
<csc-cf-selection-weekdays <csc-cf-selection-weekdays
v-model="weekdays" v-model="weekdays"
:tabs="!sameTimes" :tabs="!sameTimes"
:disable="$v.$invalid"
/> />
</q-item-section> </q-item-section>
</q-item> </div>
<q-item <q-list
v-for="(time, index) in times" dense
:key="index" class="scroll time-range-list-height"
> >
<q-item-section> <q-item
<csc-input v-for="(v, index) in $v.currentDayTimeRanges.$each.$iter"
v-model="times[index].from" :key="index"
dense >
:label="$t('Start time')" <q-item-section>
mask="##:##" <csc-input
fill-mask v-model="v.from.$model"
:disable="disabled" dense
> :label="$t('Start time')"
<template mask="##:##"
v-slot:append fill-mask
:disable="disabled"
:error="v.from.$invalid"
:error-message="timeValidationErrMsg(v.from)"
> >
<q-btn <template
icon="access_time" v-slot:append
dense
flat
color="primary"
> >
<q-popup-proxy <q-btn
ref="startTimePopup" icon="access_time"
dense
flat
color="primary"
> >
<q-time <q-popup-proxy
v-model="times[index].from" ref="startTimePopup"
flat >
now-btn <q-time
square v-model="v.from.$model"
format24h flat
text-color="dark" now-btn
color="primary" square
@input="$refs.startTimePopup[index].hide()" format24h
/> text-color="dark"
</q-popup-proxy> color="primary"
</q-btn> @input="$refs.startTimePopup[index].hide()"
</template> />
</csc-input> </q-popup-proxy>
</q-item-section> </q-btn>
<q-item-section> </template>
<csc-input </csc-input>
v-model="times[index].to" </q-item-section>
dense <q-item-section>
:label="$t('End time')" <csc-input
mask="##:##" v-model="v.to.$model"
fill-mask dense
:disable="disabled" :label="$t('End time')"
> mask="##:##"
<template fill-mask
v-slot:append :disable="disabled"
:error="v.to.$invalid"
:error-message="timeValidationErrMsg(v.to)"
> >
<q-btn <template
icon="access_time" v-slot:append
dense
flat
color="primary"
> >
<q-popup-proxy <q-btn
ref="endTimePopup" icon="access_time"
dense
flat
color="primary"
> >
<q-time <q-popup-proxy
v-model="times[index].to" ref="endTimePopup"
flat >
now-btn <q-time
square v-model="v.to.$model"
format24h flat
text-color="dark" now-btn
color="primary" square
@input="$refs.endTimePopup[index].hide()" format24h
/> text-color="dark"
</q-popup-proxy> color="primary"
</q-btn> @input="$refs.endTimePopup[index].hide()"
</template> />
</csc-input> </q-popup-proxy>
</q-item-section> </q-btn>
<q-item-section </template>
side </csc-input>
> </q-item-section>
<q-btn <q-item-section
flat side
dense >
color="negative" <q-btn
icon="delete" flat
:disable="index === 0 || disabled" dense
@click="removeTime(index)" color="negative"
/> icon="delete"
</q-item-section> :disable="currentDayTimeRanges.length < 2 || disabled"
</q-item> @click="removeTimeRangeDialog(index)"
<q-item> />
<q-item-section> </q-item-section>
<q-btn </q-item>
color="primary" </q-list>
icon="add" <div class="row justify-center">
flat <q-btn
:label="$t('Add time')" color="primary"
:disable="disabled" icon="add"
@click="addTime" flat
/> :label="$t('Add time range')"
</q-item-section> :disable="disabled"
</q-item> @click="addTimeRange"
</q-list> />
</div>
</template>
<template <template
v-slot:actions v-slot:actions
> >
@ -143,11 +166,12 @@
@click="deleteTimeSetEvent" @click="deleteTimeSetEvent"
/> />
<q-btn <q-btn
v-if="!invalidTimeset"
:label="$t('Save')" :label="$t('Save')"
flat flat
color="primary" color="primary"
icon="check" icon="check"
:disable="disabled" :disable="disabled || $v.$invalid"
@click="createTimeSetOfficeHoursEvent" @click="createTimeSetOfficeHoursEvent"
/> />
</template> </template>
@ -155,11 +179,23 @@
</template> </template>
<script> <script>
import _ from 'lodash'
import CscCfGroupCondition from 'components/call-forwarding/CscCfGroupCondition' import CscCfGroupCondition from 'components/call-forwarding/CscCfGroupCondition'
import CscInput from 'components/form/CscInput' import CscInput from 'components/form/CscInput'
import CscCfSelectionWeekdays from 'components/call-forwarding/CscCfSelectionWeekdays' import CscCfSelectionWeekdays from 'components/call-forwarding/CscCfSelectionWeekdays'
import { DAY_MAP, DEFAULT_WEEKDAYS } from 'src/filters/time-set' import { DEFAULT_WEEKDAYS } from 'src/filters/time-set'
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
import {
humanTimesetToKamailio, isTimeStrValid,
kamailioTimesetToHuman, timeStrToMinutes
} from 'src/helpers/kamailio-timesets-converter'
import { showGlobalError, showGlobalWarning } from 'src/helpers/ui'
import { or } from 'vuelidate/lib/validators'
function isTimeStrEmpty (val) {
return val === '' || val === '__:__'
}
export default { export default {
name: 'CscCfGroupConditionOfficeHours', name: 'CscCfGroupConditionOfficeHours',
components: { components: {
@ -191,51 +227,37 @@ export default {
}, },
data () { data () {
return { return {
sameTimes: this.isSameTimes(), invalidTimeset: false,
sameTimes: true,
weekdays: DEFAULT_WEEKDAYS, weekdays: DEFAULT_WEEKDAYS,
timesAll: [{ timeRangesByDay: this.getInitialTimeRanges(),
from: '', timeRangesForAll: [this.getEmptyTimeRange()]
to: '' }
}], },
timesDay1: [{ validations: {
from: '', currentDayTimeRanges: {
to: '' $each: {
}], from: {
timesDay2: [{ validTime: or(isTimeStrEmpty, isTimeStrValid),
from: '', bothFilled: (val, vm) => (isTimeStrEmpty(val) && isTimeStrEmpty(vm.to)) || (!isTimeStrEmpty(val) && !isTimeStrEmpty(vm.to))
to: '' },
}], to: {
timesDay3: [{ validTime: or(isTimeStrEmpty, isTimeStrValid),
from: '', notInversed: (val, vm) => isTimeStrEmpty(vm.from) || !isTimeStrValid(vm.from) || isTimeStrEmpty(vm.to) || (isTimeStrValid(vm.to) && timeStrToMinutes(vm.from) <= timeStrToMinutes(vm.to))
to: '' }
}], }
timesDay4: [{
from: '',
to: ''
}],
timesDay5: [{
from: '',
to: ''
}],
timesDay6: [{
from: '',
to: ''
}],
timesDay7: [{
from: '',
to: ''
}]
} }
}, },
computed: { computed: {
disabled () { disabled () {
return this.sameTimes && this.weekdays.length === 0 return this.weekdays.length === 0
}, },
times () { currentDayTimeRanges () {
if (this.sameTimes) { if (this.sameTimes) {
return this.timesAll return this.timeRangesForAll
} else { } else {
return this['timesDay' + this.weekdays[0]] const currentDay = this.weekdays[0]
return this.timeRangesByDay[currentDay]
} }
} }
}, },
@ -243,8 +265,10 @@ export default {
timeSet () { timeSet () {
this.transformTimeSet() this.transformTimeSet()
}, },
sameTimes (sameTimes) { sameTimes () {
this.transformTimeSet() if (this.weekdays.length === 0) {
this.weekdays = DEFAULT_WEEKDAYS
}
} }
}, },
mounted () { mounted () {
@ -252,8 +276,6 @@ export default {
}, },
methods: { methods: {
...mapActions('callForwarding', [ ...mapActions('callForwarding', [
'createOfficeHoursSameTimes',
'updateOfficeHoursSameTimes',
'createOfficeHours', 'createOfficeHours',
'updateOfficeHours', 'updateOfficeHours',
'deleteTimeSet' 'deleteTimeSet'
@ -261,128 +283,195 @@ export default {
back () { back () {
this.$emit('back') this.$emit('back')
}, },
isSameTimes () { getEmptyTimeRange () {
if (this.timeSet) { return { from: '', to: '' }
return this.timeSet.name.startsWith('csc-office-hours-same-times') },
getInitialTimeRanges () {
const result = {}
for (let day = 1; day <= 7; day++) {
result[day] = [
this.getEmptyTimeRange()
]
} }
return true return result
},
reset () {
this.sameTimes = true
this.weekdays = DEFAULT_WEEKDAYS
this.timeRangesByDay = this.getInitialTimeRanges()
this.timeRangesForAll = [this.getEmptyTimeRange()]
}, },
transformTimeSet () { transformTimeSet () {
if (this.timeSet && this.timeSet.name.startsWith('csc-office-hours-same-times')) { let humanTimeRanges = []
const weekdays = new Set() try {
const times = new Set() humanTimeRanges = kamailioTimesetToHuman(this.timeSet?.times)
this.timeSet.times.forEach((time) => { } catch (e) {
if (time.wday !== null && time.hour !== null && time.minute !== null) { this.reset()
weekdays.add(parseInt(time.wday)) this.invalidTimeset = true
times.add(time.hour + ':' + time.minute) console.info(e)
} return
}) }
this.weekdays = Array.from(weekdays)
const timesAll = [] if (humanTimeRanges.length === 0) {
Array.from(times).forEach((time) => { this.reset()
const timeParts = time.split(':') } else {
const hourParts = timeParts[0].split('-') this.timeRangesByDay = humanTimeRanges.reduce(
const minuteParts = timeParts[1].split('-') (acc, hTimeRangeItem) => {
timesAll.push({ let dayTimeRanges = acc[hTimeRangeItem.weekday]
from: hourParts[0] + ':' + minuteParts[0], if (dayTimeRanges === undefined) {
to: hourParts[1] + ':' + minuteParts[1] dayTimeRanges = []
}) acc[hTimeRangeItem.weekday] = dayTimeRanges
}) } else if (dayTimeRanges[0]?.from === '' && dayTimeRanges[0]?.to === '') {
this.timesAll = timesAll dayTimeRanges.shift()
} else if (this.timeSet && this.timeSet.name.startsWith('csc-office-hours')) { }
DAY_MAP.forEach((day) => { dayTimeRanges.push({
this['timesDay' + day] = [] from: hTimeRangeItem.from.padStart(5, '0'),
}) to: hTimeRangeItem.to.padStart(5, '0')
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]
}) })
return acc
},
this.getInitialTimeRanges()
)
// trying to detect equal set of time ranges for different weekdays.
const timeRangesMap = {}
humanTimeRanges.forEach(({ weekday, from, to }) => {
const rangeStr = `${from} - ${to}`
let mapItem = timeRangesMap[rangeStr]
if (!mapItem) {
mapItem = new Set()
timeRangesMap[rangeStr] = mapItem
} }
mapItem.add(weekday)
}) })
DAY_MAP.forEach((day) => { const weekdaysListWithTheSameTimeRanges = [...Object.values(timeRangesMap).reduce((acc, weekdaysSet) => {
if (this['timesDay' + day].length === 0) { const weekdaysStr = [...weekdaysSet.keys()].sort().join()
this['timesDay' + day] = [{ acc.add(weekdaysStr)
from: '', return acc
to: '' },
}] new Set()
} )]
})
// So, if we have only one weekdays list it means we can set mark "sameTimes" to True
if (weekdaysListWithTheSameTimeRanges.length === 1) {
this.sameTimes = true
this.weekdays = weekdaysListWithTheSameTimeRanges[0].split(',').map(day => Number(day))
this.timeRangesForAll = _.cloneDeep(this.timeRangesByDay[this.weekdays[0]])
} else {
this.sameTimes = false
this.weekdays = humanTimeRanges[0] ? [humanTimeRanges[0].weekday] : DEFAULT_WEEKDAYS
}
} }
}, },
addTime () { addTimeRange () {
if (!this.sameTimes) { this.currentDayTimeRanges.push(this.getEmptyTimeRange())
this['timesDay' + this.weekdays[0]].push({ },
from: '', removeTimeRangeDialog (index) {
to: '' const timeRange = this.currentDayTimeRanges[index]
}) if (isTimeStrEmpty(timeRange.from) && isTimeStrEmpty(timeRange.to)) {
this.removeTimeRange(index)
} else { } else {
this.timesAll.push({ this.$q.dialog({
from: '', title: this.$t('Remove time range'),
to: '' message: this.$t('You are about to delete time range "{from} - {to}"', timeRange),
color: 'negative',
cancel: true,
persistent: true
}).onOk(data => {
this.removeTimeRange(index)
}) })
} }
}, },
removeTime (index) { removeTimeRange (index) {
if (!this.sameTimes) { this.currentDayTimeRanges.splice(index, 1)
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 () { timeValidationErrMsg (field) {
DAY_MAP.forEach((day) => { if (!field.validTime) {
this['timesDay' + day] = [{ return this.$t('Time is invalid')
from: '', }
to: '' if (field.bothFilled === false) {
}] return this.$t('Start and End time should be set')
}) }
if (field.notInversed === false) {
return this.$t('Start time should be less than End time')
}
}, },
async createTimeSetOfficeHoursEvent () { getDataAsHumanReadableTimeSets () {
const payload = { function isTimeRangeFilled ({ from, to }) {
mapping: this.mapping return typeof from === 'string' && typeof to === 'string' &&
isTimeStrValid(from) && isTimeStrValid(to)
} }
if (this.timeSet) {
payload.id = this.timeSet.id function processTimeRanges (timeRanges, dayNum) {
return timeRanges
.filter(isTimeRangeFilled)
.map(timeRange => {
return {
weekday: dayNum,
from: timeRange.from,
to: timeRange.to
}
})
} }
if (!this.sameTimes) {
payload.times = [ let hTimeSets = []
this.timesDay1, if (this.sameTimes) {
this.timesDay2, // duplicating time ranges for each selected day
this.timesDay3, hTimeSets = this.weekdays.flatMap(dayNum => {
this.timesDay4, return processTimeRanges(this.timeRangesForAll, dayNum)
this.timesDay5, })
this.timesDay6,
this.timesDay7
]
} else { } else {
payload.times = this.timesAll hTimeSets = Object.entries(this.timeRangesByDay).flatMap(([day, timeRanges]) => {
payload.weekdays = this.weekdays const dayNum = Number(day)
return processTimeRanges(timeRanges, dayNum)
})
} }
if (this.timeSet && this.timeSet.name.startsWith('csc-office-hours-same-times') && this.sameTimes) { return hTimeSets
await this.updateOfficeHoursSameTimes(payload) },
} else if (this.timeSet && this.timeSet.name.startsWith('csc-office-hours-same-times') && !this.sameTimes) { async createTimeSetOfficeHoursEvent () {
await this.createOfficeHours(payload) try {
} else if (this.timeSet && this.timeSet.name.startsWith('csc-office-hours') && this.sameTimes) { const payload = {
await this.createOfficeHoursSameTimes(payload) mapping: this.mapping
} else if (this.timeSet && this.timeSet.name.startsWith('csc-office-hours') && !this.sameTimes) { }
await this.updateOfficeHours(payload) if (this.timeSet) {
} else if (!this.timeSet && this.sameTimes) { payload.id = this.timeSet.id
await this.createOfficeHoursSameTimes(payload) }
} else { payload.times = humanTimesetToKamailio(this.getDataAsHumanReadableTimeSets())
await this.createOfficeHours(payload) if (payload.times.length > 0) {
if (this.timeSet) {
await this.updateOfficeHours(payload)
} else {
await this.createOfficeHours(payload)
}
this.$emit('close')
} else {
showGlobalWarning(this.$t('No data to save. Please provide at least one time range.'))
}
} catch (e) {
showGlobalError(e)
} }
this.$emit('close')
}, },
async deleteTimeSetEvent () { async deleteTimeSetEvent () {
await this.deleteTimeSet({ try {
mapping: this.mapping, await this.deleteTimeSet({
id: this.timeSet.id mapping: this.mapping,
}) id: this.timeSet.id
})
} catch (e) {
showGlobalError(e)
}
} }
} }
} }
</script> </script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
.half-screen-width
width 50vw
.time-range-list-height
// NOTE: 65vh is a default max-height for q-dialog. Other magic numbers are sum of another elements heights, like headers, buttons etc
max-height calc(65vh - 250px)
</style>

@ -149,7 +149,8 @@
@save="updateAnnouncementEvent({ @save="updateAnnouncementEvent({
destinationIndex: destinationIndex, destinationIndex: destinationIndex,
destinationSetId: destinationSet.id destinationSetId: destinationSet.id
})"> })"
>
<q-select <q-select
v-model="announcement" v-model="announcement"
emit-value emit-value

@ -1,5 +1,7 @@
<template> <template>
<div> <div
class="weekdays-selection-component"
>
<div <div
v-if="!tabs" v-if="!tabs"
class="row justify-around" class="row justify-around"
@ -14,6 +16,7 @@
unelevated unelevated
:color="(isSelected(day))?'primary':'dark'" :color="(isSelected(day))?'primary':'dark'"
:text-color="(isSelected(day))?'dark':'primary'" :text-color="(isSelected(day))?'dark':'primary'"
:disable="disable"
@click="toggle(day)" @click="toggle(day)"
/> />
</div> </div>
@ -37,6 +40,7 @@
outside-arrows outside-arrows
mobile-arrows mobile-arrows
no-caps no-caps
:disable="disable"
/> />
</q-tabs> </q-tabs>
<q-separator <q-separator
@ -49,7 +53,7 @@
<script> <script>
import { import {
DAY_MAP, DAY_MAP,
DAY_NAME_MAP getDayNameByNumber
} from 'src/filters/time-set' } from 'src/filters/time-set'
export default { export default {
@ -62,6 +66,10 @@ export default {
tabs: { tabs: {
type: Boolean, type: Boolean,
default: false default: false
},
disable: {
type: Boolean,
default: false
} }
}, },
data () { data () {
@ -75,14 +83,14 @@ export default {
return 'tab-' + this.selectedWeekdays[0] return 'tab-' + this.selectedWeekdays[0]
}, },
set (tab) { set (tab) {
this.selectedWeekdays = [parseInt(tab.replace('tab-', ''))] this.selectedWeekdays = [Number(tab.replace('tab-', ''))]
} }
}, },
days () { days () {
const options = [] const options = []
DAY_MAP.forEach((day, index) => { DAY_MAP.forEach((day, index) => {
options.push({ options.push({
label: DAY_NAME_MAP[index].substr(0, 2), label: getDayNameByNumber(index, true),
value: day value: day
}) })
}) })
@ -111,3 +119,10 @@ export default {
} }
} }
</script> </script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
.weekdays-selection-component
// Note: the magic number for height is the max component height in buttons mode.
// It makes the height of our component stable in any mode (tab \ buttons)
min-height 42px
</style>

@ -1,17 +1,8 @@
import { i18n } from 'boot/i18n' import { i18n } from 'boot/i18n'
import { getKamailioRangeElements } from 'src/helpers/kamailio-timesets-converter'
export const DAY_MAP = [2, 3, 4, 5, 6, 7, 1] 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 = [ export const DEFAULT_WEEKDAYS = [
DAY_MAP[0], DAY_MAP[0],
DAY_MAP[1], DAY_MAP[1],
@ -20,6 +11,29 @@ export const DEFAULT_WEEKDAYS = [
DAY_MAP[4] DAY_MAP[4]
] ]
export function getDayNameByNumber (dayNumber, isShortName = false) {
const daysNamesMap = [
i18n.t('Monday'),
i18n.t('Tuesday'),
i18n.t('Wednesday'),
i18n.t('Thursday'),
i18n.t('Friday'),
i18n.t('Saturday'),
i18n.t('Sunday')
]
// NOTE: in some languages the short days names may be not just first two letters of the full day's name
const daysShortNamesMap = [
i18n.t('Mo'),
i18n.t('Tu'),
i18n.t('We'),
i18n.t('Th'),
i18n.t('Fr'),
i18n.t('Sa'),
i18n.t('Su')
]
return isShortName ? daysShortNamesMap[dayNumber] : daysNamesMap[dayNumber]
}
export function timeSetDateExact (times) { export function timeSetDateExact (times) {
return times[0].year + '/' + times[0].month + '/' + times[0].mday return times[0].year + '/' + times[0].month + '/' + times[0].mday
} }
@ -37,43 +51,25 @@ export function timeSetWeekdays (times) {
return DAY_MAP.indexOf(parseInt(time.wday)) return DAY_MAP.indexOf(parseInt(time.wday))
}) })
mappedWeekdays.sort() mappedWeekdays.sort()
let weekdays = '' const weekdaysStr = mappedWeekdays.map(weekday => getDayNameByNumber(weekday)).join(', ')
mappedWeekdays.forEach((weekday, index) => { return weekdaysStr
if (index > 0) {
weekdays = weekdays + ', '
}
weekdays = weekdays + DAY_NAME_MAP[weekday]
})
return weekdays
} }
export function timeSetOfficeHoursSameTime (times) { export function timeSetOfficeHoursSameTime (times) {
const weekdays = new Set() const weekdays = new Set()
let weekdaysStr = '' times.forEach(kamailioTimeRange => {
times.forEach((time) => { const wdayRange = getKamailioRangeElements(kamailioTimeRange.wday)[0]
weekdays.add(parseInt(time.wday)) for (let day = wdayRange.from; day <= wdayRange.to; day++) {
}) // To have Monday the first day of a week we are mapping days to change order
const weekdaysSorted = Array.from(weekdays) weekdays.add(DAY_MAP.indexOf(day))
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)]
}) })
const weekdaysSorted = Array.from(weekdays)
weekdaysSorted.sort()
const weekdaysStr = weekdaysSorted.map(weekday => getDayNameByNumber(weekday)).join(', ')
return weekdaysStr return weekdaysStr
} }
export function timeSetTimes () { export function timeSetTimes () {
return '...'
} }

@ -1,4 +1,3 @@
// /* eslint-disable */
/* /*
humanReadableTimeset = [{ humanReadableTimeset = [{
@ -32,40 +31,39 @@ kamailioTimeset = [{
] ]
*/ */
export function getTimeStrElements(timeStr) { export function getTimeStrElements (timeStr) {
const [hours, minutes] = timeStr.split(':').map(t => parseInt(t, 10)) const [hours, minutes] = timeStr.split(':').map(t => Number(t))
return { hours, minutes } return { hours, minutes }
} }
/** /**
* @param timeStr - a string with hour and minutes divided by colon, like "0:00" or "23:59" * @param timeStr - a string with hour and minutes divided by colon, like "0:00" or "23:59"
* @returns {number} * @returns {number}
*/ */
export function timeStrToMinutes(timeStr) { export function timeStrToMinutes (timeStr) {
const { hours, minutes } = getTimeStrElements(timeStr) const { hours, minutes } = getTimeStrElements(timeStr)
return hours * 60 + minutes return hours * 60 + minutes
} }
export function isTimeStrValid(timeStr) { export function isTimeStrValid (timeStr) {
if (typeof timeStr === 'string') { if (typeof timeStr === 'string') {
const { hours, minutes } = getTimeStrElements(timeStr) const { hours, minutes } = getTimeStrElements(timeStr)
return hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59 return hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59
} }
return false return false
} }
export function validateHumanTimesets(hTimeset) { export function validateHumanTimesets (hTimeset) {
hTimeset.forEach(timesetItem => { hTimeset.forEach(timesetItem => {
const { weekday, from, to } = timesetItem const { weekday, from, to } = timesetItem
if (typeof weekday !== 'number' || isNaN(weekday) || weekday < 1 || weekday > 7 || if (typeof weekday !== 'number' || isNaN(weekday) || weekday < 1 || weekday > 7 ||
!isTimeStrValid(from) || !isTimeStrValid(to) !isTimeStrValid(from) || !isTimeStrValid(to)
) { ) {
throw Error('A human timeset item has invalid format: ' + JSON.stringify(timesetItem)) throw Error('A human timeset item has invalid format: ' + JSON.stringify(timesetItem))
} } else if (timeStrToMinutes(from) > timeStrToMinutes(to)) {
else if (timeStrToMinutes(from) > timeStrToMinutes(to)) { throw Error('A human timeset item should have "from" time < or = "to" time: ' + JSON.stringify(timesetItem))
throw Error('A human timeset item should have "from" time < or = "to" time: ' + JSON.stringify(timesetItem)) }
} })
})
} }
/** /**
@ -74,264 +72,284 @@ export function validateHumanTimesets(hTimeset) {
* @param hTimeset {humanReadableTimeset} * @param hTimeset {humanReadableTimeset}
* @returns {humanReadableTimeset} * @returns {humanReadableTimeset}
*/ */
export function getHumanTimesetsNormalized(hTimeset = []) { export function getHumanTimesetsNormalized (hTimeset = []) {
//sort timeset by "weekday" and "from" columns // sort timeset by "weekday" and "from" columns
//clone input data to prevent original data object mutation // clone input data to prevent original data object mutation
const hTimesetCloned = hTimeset.map(i => ({...i})) const hTimesetCloned = hTimeset.map(i => ({ ...i }))
const htSorted = hTimesetCloned.sort((a, b) => { const htSorted = hTimesetCloned.sort((a, b) => {
const dayDiff = a.weekday - b.weekday const dayDiff = a.weekday - b.weekday
if (dayDiff) if (dayDiff) {
return dayDiff return dayDiff
else } else {
return timeStrToMinutes(a.from) - timeStrToMinutes(b.from) return timeStrToMinutes(a.from) - timeStrToMinutes(b.from)
}) }
})
//combine, merge periods for a day. For example 0:00-2:00 and 1:00-4:00 should be merged into 0:00-4:00
//Important: the timeset should be sorted by "weekday" and "from"! // combine, merge periods for a day. For example 0:00-2:00 and 1:00-4:00 should be merged into 0:00-4:00
const htNormalized = htSorted.reduce((acc, currentItem) => { // Important: the timeset should be sorted by "weekday" and "from"!
const prevItem = acc.pop() const htNormalized = htSorted.reduce((acc, currentItem) => {
if (!prevItem) { const prevItem = acc.pop()
acc.push(currentItem) if (!prevItem) {
} acc.push(currentItem)
else if (prevItem.weekday !== currentItem.weekday) { } else if (prevItem.weekday !== currentItem.weekday) {
acc.push(prevItem) acc.push(prevItem)
acc.push(currentItem) acc.push(currentItem)
} } else {
else { const prevItemMinutes = { fromM: timeStrToMinutes(prevItem.from), toM: timeStrToMinutes(prevItem.to) }
const prevItemMinutes = { fromM: timeStrToMinutes(prevItem.from), toM: timeStrToMinutes(prevItem.to) } const currentItemMinutes = { fromM: timeStrToMinutes(currentItem.from), toM: timeStrToMinutes(currentItem.to) }
const currentItemMinutes = { fromM: timeStrToMinutes(currentItem.from), toM: timeStrToMinutes(currentItem.to) }
if (prevItemMinutes.fromM <= currentItemMinutes.fromM && currentItemMinutes.toM <= prevItemMinutes.toM) {
if (prevItemMinutes.fromM <= currentItemMinutes.fromM && currentItemMinutes.toM <= prevItemMinutes.toM) { // current time range is completely inside previous time range --> just skipping current timeSet item
//current time range is completely inside previous time range --> just skipping current timeSet item acc.push(prevItem)
acc.push(prevItem) } else if (prevItemMinutes.toM < currentItemMinutes.fromM) {
} // current time range is not part of previous time range --> adding both as separate time ranges
else if (prevItemMinutes.toM < currentItemMinutes.fromM) { acc.push(prevItem)
//current time range is not part of previous time range --> adding both as separate time ranges acc.push(currentItem)
acc.push(prevItem) } else if (prevItemMinutes.toM >= currentItemMinutes.fromM) {
acc.push(currentItem) // current time range is the next chunk\part of the previous time range
} // OR they are intercepting --> extending\combining previous time range with current time range
else if (prevItemMinutes.toM >= currentItemMinutes.fromM) { prevItem.to = currentItem.to
//current time range is the next chunk\part of the previous time range acc.push(prevItem)
// OR they are intercepting --> extending\combining previous time range with current time range } else {
prevItem.to = currentItem.to console.info('Acc:', acc, 'prevItem:', prevItem, 'currentItem:', currentItem)
acc.push(prevItem) console.info('prevItemMinutes:', prevItemMinutes, 'currentItemMinutes:', currentItemMinutes)
} throw Error('Internal error in "getHumanTimesetsNormalized"')
else { }
console.info('Acc:', acc, 'prevItem:', prevItem, 'currentItem:', currentItem) }
console.info('prevItemMinutes:', prevItemMinutes, 'currentItemMinutes:', currentItemMinutes) return acc
throw Error('Internal error in "getHumanTimesetsNormalized"') }, [])
}
} return htNormalized
return acc
}, [])
return htNormalized
} }
export function humanTimesetToKamailio(hTimeset = []) { export function humanTimesetToKamailio (hTimeset = []) {
validateHumanTimesets(hTimeset) validateHumanTimesets(hTimeset)
const htNormalized = getHumanTimesetsNormalized(hTimeset) const htNormalized = getHumanTimesetsNormalized(hTimeset)
const kamailioTimesetRaw = htNormalized.map(timesetItem => { const kamailioTimesetRaw = htNormalized.map(timesetItem => {
const { weekday, from } = timesetItem const { weekday, from } = timesetItem
const to = timesetItem.to === '23:59' ? '24:00' : timesetItem.to const to = timesetItem.to === '23:59' ? '24:00' : timesetItem.to
const fromHM = getTimeStrElements(from) const fromHM = getTimeStrElements(from)
const toHM = getTimeStrElements(to) const toHM = getTimeStrElements(to)
const result = [] const result = []
if (fromHM.hours === toHM.hours) { if (fromHM.hours === toHM.hours) {
if (fromHM.minutes === toHM.minutes || fromHM.minutes + 1 === toHM.minutes) if (fromHM.minutes === toHM.minutes || fromHM.minutes + 1 === toHM.minutes) {
result.push({ wday: weekday, hour: `${fromHM.hours}`, minute: `${fromHM.minutes}` }) result.push({ wday: weekday, hour: `${fromHM.hours}`, minute: `${fromHM.minutes}` })
else } else {
result.push({ wday: weekday, hour: `${fromHM.hours}`, minute: `${fromHM.minutes}-${toHM.minutes-1}` }) result.push({ wday: weekday, hour: `${fromHM.hours}`, minute: `${fromHM.minutes}-${toHM.minutes - 1}` })
} }
else { } else {
//NOTE: any human readable time range for a day can be represented as next set of Kamailio ranges // NOTE: any human readable time range for a day can be represented as next set of Kamailio ranges
//for example "1:10-5:35" can be represented as "1:10-2:00", "2:00-5:00", "5:00-5:35" and than coded in Kamailio format // for example "1:10-5:35" can be represented as "1:10-2:00", "2:00-5:00", "5:00-5:35" and than coded in Kamailio format
// [ // [
// { wday: weekday, hour: `${fromHM.hours}`, minute: `${fromHM.minutes}-59` }, // { wday: weekday, hour: `${fromHM.hours}`, minute: `${fromHM.minutes}-59` },
// { wday: weekday, hour: `${fromHM.hours+1}-${toHM.hours-1}` }, // { wday: weekday, hour: `${fromHM.hours+1}-${toHM.hours-1}` },
// { wday: weekday, hour: `${toHM.hours}`, minute: `0-${toHM.minutes-1}` } // { wday: weekday, hour: `${toHM.hours}`, minute: `0-${toHM.minutes-1}` }
// ] // ]
//but code below will output a little more optimized version // but code below will output a little more optimized version
//"Starting" range // "Starting" range
if (fromHM.minutes > 0) { if (fromHM.minutes > 0) {
result.push({ wday: weekday, hour: `${fromHM.hours}`, minute: `${fromHM.minutes}-59` }) result.push({ wday: weekday, hour: `${fromHM.hours}`, minute: `${fromHM.minutes}-59` })
} } else fromHM.hours -= 1
else fromHM.hours -= 1
// "Middle" range
//"Middle" range if (fromHM.hours + 1 <= toHM.hours - 1) {
if (fromHM.hours + 1 <= toHM.hours-1) { result.push({ wday: weekday, hour: `${fromHM.hours + 1}-${toHM.hours - 1}` })
result.push({ wday: weekday, hour: `${fromHM.hours + 1}-${toHM.hours - 1}` }) }
}
// "Ending" range
//"Ending" range if (toHM.minutes > 0) {
if (toHM.minutes > 0) { result.push({ wday: weekday, hour: `${toHM.hours}`, minute: `0-${toHM.minutes - 1}` })
result.push({ wday: weekday, hour: `${toHM.hours}`, minute: `0-${toHM.minutes-1}` }) }
} }
} return result
return result })
})
const kamailioTimeset = kamailioTimesetRaw.reduce((acc, item) => {
const kamailioTimeset = kamailioTimesetRaw.reduce((acc, item) => { const optimizedItemRanges = item.map(item => {
const optimizedItemRanges = item.map(item => { // if minute or hour contains range like "a-a" convert to just "a"
//if minute or hour contains range like "a-a" convert to just "a" if (item.hour) {
if (item.hour) { const [hourF, hourT] = item.hour.split('-')
let [hourF, hourT] = item.hour.split('-') if (hourF === hourT) item.hour = hourF
if (hourF === hourT) item.hour = hourF }
}
if (item.minute) {
if (item.minute) { const [minuteF, minuteT] = item.minute.split('-')
let [minuteF, minuteT] = item.minute.split('-') if (minuteF === minuteT) item.minute = minuteF
if (minuteF === minuteT) item.minute = minuteF }
}
return item
return item })
}) // similar to flatMap
//similar to flatMap return [...acc, ...optimizedItemRanges]
return [...acc, ...optimizedItemRanges] }, [])
}, []) .reduce((acc, item) => {
.reduce((acc, item) => { // combine the same time ranges by "wday"
//combine the same time ranges by "wday" if (acc.length === 0) {
if (acc.length === 0) acc.push(item)
acc.push(item) } else {
else { const mergeCandidate = acc.find(accItem =>
const mergeCandidate = acc.find(accItem => accItem.hour === item.hour &&
accItem.hour === item.hour && accItem.minute === item.minute &&
accItem.minute === item.minute && getKamailioRangeElements(accItem.wday)[0].to + 1 === getKamailioRangeElements(item.wday)[0].from
getKamailioRangeElements(accItem.wday)[0].to + 1 === getKamailioRangeElements(item.wday)[0].from )
) if (mergeCandidate) {
if (mergeCandidate) { const mergeCandidateWday = getKamailioRangeElements(mergeCandidate.wday)[0]
const mergeCandidateWday = getKamailioRangeElements(mergeCandidate.wday)[0] mergeCandidate.wday = `${mergeCandidateWday.from}-${mergeCandidateWday.to + 1}`
mergeCandidate.wday = `${mergeCandidateWday.from}-${mergeCandidateWday.to + 1}` } else {
} acc.push(item)
else { }
acc.push(item) }
} return acc
} }, [])
return acc
}, []) return kamailioTimeset
return kamailioTimeset
} }
//--------------- // ---------------
// TODO: does it used?
export function getSimpleRangeElements (rangeStr = '') {
const range = String(rangeStr).trim()
const rangeElements = range.split('-').map(r => r.trim()).filter(r => r.length).map(r => Number(r))
if (rangeElements.length === 0) {
return undefined
} else {
if (rangeElements.length > 2) {
throw Error('Invalid range format: "' + rangeStr + '"')
}
return {
from: rangeElements[0],
to: rangeElements.length === 2 ? rangeElements[1] : rangeElements[0]
}
}
}
export function getKamailioRangeElements(kamailioRangeStr = '') { export function getKamailioRangeElements (kamailioRangeStr = '') {
const ranges = String(kamailioRangeStr).trim().split(' ').map(r => r.trim()).filter(r => r.length) const ranges = String(kamailioRangeStr).trim().split(' ').map(r => r.trim()).filter(r => r.length)
return ranges.map(r => { return ranges.map(r => {
const rangeElements = r.split('-').map(r => r.trim()).map(r => parseInt(r, 10)) const rangeElements = r.split('-').map(r => r.trim()).map(r => Number(r))
if (rangeElements.length > 2) if (rangeElements.length > 2) {
throw Error('Invalid Kamailio range format: "' + kamailioRangeStr + '"') throw Error('Invalid Kamailio range format: "' + kamailioRangeStr + '"')
}
return { return {
from: rangeElements[0], from: rangeElements[0],
to: rangeElements.length === 2 ? rangeElements[1] : rangeElements[0] to: rangeElements.length === 2 ? rangeElements[1] : rangeElements[0]
} }
}) })
} }
export function validateKamailioRange(kamailioRangeStr = '', minValue, maxValue) { export function validateKamailioRange (kamailioRangeStr = '', minValue, maxValue) {
const rangeElements = getKamailioRangeElements(kamailioRangeStr) const rangeElements = getKamailioRangeElements(kamailioRangeStr)
if (rangeElements.length === 0) if (rangeElements.length === 0) {
throw Error('Kamailio range should not be empty') throw Error('Kamailio range should not be empty')
else if (rangeElements.length > 1) } else if (rangeElements.length > 1) {
throw Error('Kamailio multiple ranges are not supported: "' + kamailioRangeStr + '"') throw Error('Kamailio multiple ranges are not supported: "' + kamailioRangeStr + '"')
else { } else {
if (isNaN(rangeElements[0].from) || isNaN(rangeElements[0].to)) if (isNaN(rangeElements[0].from) || isNaN(rangeElements[0].to)) {
throw Error('Kamailio range has invalid characters or a wrong format: "' + kamailioRangeStr + '"') throw Error('Kamailio range has invalid characters or a wrong format: "' + kamailioRangeStr + '"')
if (rangeElements[0].from > rangeElements[0].to) }
throw Error('Kamailio reversed ranges are not supported: "' + kamailioRangeStr + '"') if (rangeElements[0].from > rangeElements[0].to) {
if (minValue !== undefined && maxValue !== undefined) { throw Error('Kamailio reversed ranges are not supported: "' + kamailioRangeStr + '"')
if (rangeElements[0].from < minValue || maxValue < rangeElements[0].from || }
rangeElements[0].to < minValue || maxValue < rangeElements[0].to) { if (minValue !== undefined && maxValue !== undefined) {
throw Error(`Kamailio range elements are out of allowed values range (${minValue}..${maxValue}) : "${kamailioRangeStr}"`) if (rangeElements[0].from < minValue || maxValue < rangeElements[0].from ||
} rangeElements[0].to < minValue || maxValue < rangeElements[0].to) {
} throw Error(`Kamailio range elements are out of allowed values range (${minValue}..${maxValue}) : "${kamailioRangeStr}"`)
} }
}
}
} }
export function validateKamailioTimesets(kTimeset) { export function validateKamailioTimesets (kTimeset) {
kTimeset.forEach(timesetItem => { kTimeset.forEach(timesetItem => {
let { wday, hour, minute } = timesetItem let { wday, hour, minute } = timesetItem
wday = (wday === null || wday === undefined) ? '' : String(wday).trim() wday = (wday === null || wday === undefined) ? '' : String(wday).trim()
hour = (hour === null || hour === undefined) ? '' : String(hour).trim() hour = (hour === null || hour === undefined) ? '' : String(hour).trim()
minute = (minute === null || minute === undefined) ? '' : String(minute).trim() minute = (minute === null || minute === undefined) ? '' : String(minute).trim()
if (wday !== '') if (wday !== '') {
validateKamailioRange(wday, 1, 7) validateKamailioRange(wday, 1, 7)
else } else {
throw Error('A Kamailio timeset should have "wday" range: ' + JSON.stringify(timesetItem)) throw Error('A Kamailio timeset should have "wday" range: ' + JSON.stringify(timesetItem))
if (hour !== '') }
validateKamailioRange(hour, 0, 23) if (hour !== '') {
if (minute !== '') validateKamailioRange(hour, 0, 23)
validateKamailioRange(minute, 0, 59) }
if (minute !== '') {
Object.entries(timesetItem) validateKamailioRange(minute, 0, 59)
.filter(([key]) => !['wday', 'hour', 'minute'].includes(key)) }
.forEach(([key, value]) => {
if (!(value === null || value === undefined || String(value).trim().length === 0)) Object.entries(timesetItem)
throw Error(`The "${key}" scale of Kamailio timesets is not supported: ${JSON.stringify(timesetItem)}`) .filter(([key]) => !['wday', 'hour', 'minute'].includes(key))
}) .forEach(([key, value]) => {
}) if (!(value === null || value === undefined || String(value).trim().length === 0)) {
throw Error(`The "${key}" scale of Kamailio timesets is not supported: ${JSON.stringify(timesetItem)}`)
}
})
})
} }
export function kamailioTimesetToHuman(kTimeset = []) { export function kamailioTimesetToHuman (kTimeset = []) {
validateKamailioTimesets(kTimeset) validateKamailioTimesets(kTimeset)
//convert Kamailio timeset into Human readable format // convert Kamailio timeset into Human readable format
const hTimesetRaw = kTimeset.map(timesetItem => { const hTimesetRaw = kTimeset.map(timesetItem => {
let { wday, hour, minute } = timesetItem let { wday, hour, minute } = timesetItem
hour = (hour === null || hour === undefined) ? '' : String(hour).trim() hour = (hour === null || hour === undefined) ? '' : String(hour).trim()
minute = (minute === null || minute === undefined) ? '' : String(minute).trim() minute = (minute === null || minute === undefined) ? '' : String(minute).trim()
wday = getKamailioRangeElements(wday)[0] wday = getKamailioRangeElements(wday)[0]
if (hour !== '') if (hour !== '') {
hour = getKamailioRangeElements(hour)[0] hour = getKamailioRangeElements(hour)[0]
else } else {
hour = { from: 0, to: 23 } hour = { from: 0, to: 23 }
if (minute !== '') }
minute = getKamailioRangeElements(minute)[0] if (minute !== '') {
else minute = getKamailioRangeElements(minute)[0]
minute = { from: 0, to: 59 } } else {
minute = { from: 0, to: 59 }
const nestedRules = [wday, hour, minute].reverse() }
const rulesOutput = nestedRules.reduce((acc, range) => {
const newAcc = [] const nestedRules = [wday, hour, minute].reverse()
if (acc.length === 0) { const rulesOutput = nestedRules.reduce((acc, range) => {
newAcc.push({ from: String(range.from), to: String(range.to + 1) }) const newAcc = []
} if (acc.length === 0) {
else { newAcc.push({ from: String(range.from), to: String(range.to + 1) })
for (let i = range.from; i <= range.to; i++) { } else {
acc.forEach(accItem => for (let i = range.from; i <= range.to; i++) {
newAcc.push({ from: [i, accItem.from].join('_'), to: [i, accItem.to].join('_') }) acc.forEach(accItem =>
) newAcc.push({ from: [i, accItem.from].join('_'), to: [i, accItem.to].join('_') })
} )
} }
return newAcc }
}, []) return newAcc
const hTimeset = rulesOutput.map(ruleOutput => { }, [])
const [fromWday, fromHour, fromMinute] = ruleOutput.from.split('_').map(i => Number(i)) const hTimeset = rulesOutput.map(ruleOutput => {
const [, toHour, toMinute] = ruleOutput.to.split('_').map(i => Number(i)) const [fromWday, fromHour, fromMinute] = ruleOutput.from.split('_').map(i => Number(i))
const from = [fromHour, fromMinute] const [, toHour, toMinute] = ruleOutput.to.split('_').map(i => Number(i))
.map((i, index) => String(i).padStart((index === 1) ? 2 : 1, '0')).join(':') const from = [fromHour, fromMinute]
const to = [ .map((i, index) => String(i).padStart((index === 1) ? 2 : 1, '0')).join(':')
(toMinute === 60) ? toHour + 1 : toHour, const to = [
(toMinute === 60) ? 0 : toMinute (toMinute === 60) ? toHour + 1 : toHour,
].map((i, index) => String(i).padStart((index === 1) ? 2 : 1, '0')).join(':') (toMinute === 60) ? 0 : toMinute
].map((i, index) => String(i).padStart((index === 1) ? 2 : 1, '0')).join(':')
return {
weekday: fromWday, return {
from, weekday: fromWday,
to: (to === '24:00') ? '23:59': to from,
} to: (to === '24:00') ? '23:59' : to
}) }
})
return hTimeset
}) return hTimeset
.reduce((acc, item) => [...acc, ...item], []) })
.reduce((acc, item) => [...acc, ...item], [])
return getHumanTimesetsNormalized(hTimesetRaw)
return getHumanTimesetsNormalized(hTimesetRaw)
} }

@ -19,13 +19,24 @@ export function stopLoading () {
Loading.hide() Loading.hide()
} }
export function showGlobalError (message, timeout) { export function showGlobalError (messageOrException, timeout = 3000) {
Alert.create({ let errorMessage = messageOrException
html: message, if (typeof messageOrException === 'object') {
position: 'top-center', // trying to get error message from the Axios response otherwise from the error itself
errorMessage = messageOrException?.response?.data?.message || messageOrException?.message
}
if (errorMessage === '' || errorMessage === undefined || errorMessage === null) {
errorMessage = i18n.t('Unknown error')
}
return Notify.create({
message: errorMessage,
position: 'top',
type: 'negative',
icon: 'error',
textColor: 'dark',
enter: 'bounceIn', enter: 'bounceIn',
leave: 'fadeOut', leave: 'fadeOut',
color: 'negative' timeout
}) })
} }

@ -243,6 +243,7 @@
"Forward to Voicebox": "", "Forward to Voicebox": "",
"Forwarded to": "", "Forwarded to": "",
"Forwarding": "", "Forwarding": "",
"Fr": "",
"Free": "Frei", "Free": "Frei",
"Friday": "Freitag", "Friday": "Freitag",
"From": "Von", "From": "Von",
@ -311,6 +312,7 @@
"Maximum allowed extension is {max}": "", "Maximum allowed extension is {max}": "",
"Maximum calls in queue": "Anzahl der Anrufe", "Maximum calls in queue": "Anzahl der Anrufe",
"Minimum allowed extension is {min}": "", "Minimum allowed extension is {min}": "",
"Mo": "",
"Monday": "Montag", "Monday": "Montag",
"Monthly": "", "Monthly": "",
"More than one {timeset} timeset exists. You can resolve this by resetting the {timeset} timesets.": "Zeitbasierte Anrufweiterleitung ist durch einen Administrator konfiguriert", "More than one {timeset} timeset exists. You can resolve this by resetting the {timeset} timesets.": "Zeitbasierte Anrufweiterleitung ist durch einen Administrator konfiguriert",
@ -332,6 +334,7 @@
"No Voicemails found": "Keine Voicemails gefunden", "No Voicemails found": "Keine Voicemails gefunden",
"No call goes to primary number": "", "No call goes to primary number": "",
"No call queues created yet": "Es wurden noch keine Anrufwarteschlangen erstellt.", "No call queues created yet": "Es wurden noch keine Anrufwarteschlangen erstellt.",
"No data to save. Please provide at least one time range.": "",
"No destinations created yet": "", "No destinations created yet": "",
"No devices created yet": "Noch keine Geräte angelegt", "No devices created yet": "Noch keine Geräte angelegt",
"No groups": "Keine Gruppen", "No groups": "Keine Gruppen",
@ -421,6 +424,7 @@
"Remove sound file": "Sound löschen", "Remove sound file": "Sound löschen",
"Remove sound set": "Soundset löschen", "Remove sound set": "Soundset löschen",
"Remove speed dial": "Kurzwahl löschen", "Remove speed dial": "Kurzwahl löschen",
"Remove time range": "",
"Remove voicemail": "", "Remove voicemail": "",
"Removed call queue for {callQueue} successfully": "Anrufwarteschlange für {callQueue} gelöscht", "Removed call queue for {callQueue} successfully": "Anrufwarteschlange für {callQueue} gelöscht",
"Removed destination {destination}": "Zielrufnummer {destination} entfernen", "Removed destination {destination}": "Zielrufnummer {destination} entfernen",
@ -448,6 +452,7 @@
"Ring primary number": "", "Ring primary number": "",
"Ringing at": "Klingelt", "Ringing at": "Klingelt",
"Ringing at {number}...": "{number} klingelt...", "Ringing at {number}...": "{number} klingelt...",
"Sa": "",
"Same time for all selected days": "", "Same time for all selected days": "",
"Same time for selected days": "", "Same time for selected days": "",
"Saturday": "Samstag", "Saturday": "Samstag",
@ -493,15 +498,20 @@
"Sources": "Anrufer", "Sources": "Anrufer",
"Sourceset": "Anruferliste", "Sourceset": "Anruferliste",
"Speed Dial": "Kurzwahl", "Speed Dial": "Kurzwahl",
"Start and End time should be set": "",
"Start call": "Anruf starten", "Start call": "Anruf starten",
"Start date": "", "Start date": "",
"Start new call": "Neuen Anruf starten", "Start new call": "Neuen Anruf starten",
"Start time": "", "Start time": "",
"Start time should be less than End time": "",
"Station name": "Gerätename", "Station name": "Gerätename",
"Su": "",
"Subscriber Sign In": "Subscriber Log-in", "Subscriber Sign In": "Subscriber Log-in",
"Sunday": "Sonntag", "Sunday": "Sonntag",
"Super": "Hoch", "Super": "Hoch",
"T38": "", "T38": "",
"Th": "",
"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 Notify Email is already used": "", "The Notify Email is already used": "",
"The {timeset} timeset contain reverse order of values. You can resolve this by resetting the {timeset} timeset.": "Zeitbasierte Anrufweiterleitung ist durch einen Administrator konfiguriert", "The {timeset} timeset contain reverse order of values. You can resolve this by resetting the {timeset} timeset.": "Zeitbasierte Anrufweiterleitung ist durch einen Administrator konfiguriert",
@ -517,12 +527,14 @@
"This sound set is incomplete ({amount} file is missing)": "Soundset ist unvollständig ({amount} Sound fehlt)", "This sound set is incomplete ({amount} file is missing)": "Soundset ist unvollständig ({amount} Sound fehlt)",
"This sound set is incomplete ({amount} files are missing)": "Soundset ist unvollständig ({amount} Sounds fehlen)", "This sound set is incomplete ({amount} files are missing)": "Soundset ist unvollständig ({amount} Sounds fehlen)",
"Thursday": "Donnerstag", "Thursday": "Donnerstag",
"Time is invalid": "",
"Time of the day": "Uhrzeit des Tages", "Time of the day": "Uhrzeit des Tages",
"Timeout": "Klingeldauer", "Timeout": "Klingeldauer",
"To": "An", "To": "An",
"Today": "Heute", "Today": "Heute",
"Toggled attachment successfully": "Voicemail als Anhang geändert", "Toggled attachment successfully": "Voicemail als Anhang geändert",
"Toggled deletion successfully": "Löschen der Voicemail erfolgreich geändert", "Toggled deletion successfully": "Löschen der Voicemail erfolgreich geändert",
"Tu": "",
"Tuesday": "Dienstag", "Tuesday": "Dienstag",
"Type something": "", "Type something": "",
"Unassign": "", "Unassign": "",
@ -561,6 +573,7 @@
"Voicemail downloaded successfully": "Voicemail erfolgreich heruntergeladen", "Voicemail downloaded successfully": "Voicemail erfolgreich heruntergeladen",
"Voicemails": "Voicemails", "Voicemails": "Voicemails",
"WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?": "Zeitbasierte Anrufweiterleitung deaktivieren?", "WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?": "Zeitbasierte Anrufweiterleitung deaktivieren?",
"We": "",
"Wednesday": "Mittwoch", "Wednesday": "Mittwoch",
"Weekday": "Wochentag", "Weekday": "Wochentag",
"Weekly": "", "Weekly": "",
@ -577,6 +590,7 @@
"You are about to change your login password. After the password was changed successfully, you get automatically logged out to authenticate with the new password. ": "Du bist dabei, dein Login-Passwort zu ändern. Nachdem das Passwort erfolgreich geändert wurde, wirst du automatisch abgemeldet, um dich mit dem neuen Passwort zu authentifizieren.", "You are about to change your login password. After the password was changed successfully, you get automatically logged out to authenticate with the new password. ": "Du bist dabei, dein Login-Passwort zu ändern. Nachdem das Passwort erfolgreich geändert wurde, wirst du automatisch abgemeldet, um dich mit dem neuen Passwort zu authentifizieren.",
"You are about to delete {name} sourceset": "", "You are about to delete {name} sourceset": "",
"You are about to delete {name} timeset": "", "You are about to delete {name} timeset": "",
"You are about to delete time range \"{from} - {to}\"": "",
"You are about to delete {destination} from {groupName} call forwarding": "", "You are about to delete {destination} from {groupName} call forwarding": "",
"You are about to delete {groupName} call forwarding group": "", "You are about to delete {groupName} call forwarding group": "",
"You are about to end the current call. Are you sure?": "Anruf beenden?", "You are about to end the current call. Are you sure?": "Anruf beenden?",

@ -243,6 +243,7 @@
"Forward to Voicebox": "Forward to Voicebox", "Forward to Voicebox": "Forward to Voicebox",
"Forwarded to": "Forwarded to", "Forwarded to": "Forwarded to",
"Forwarding": "Forwarding", "Forwarding": "Forwarding",
"Fr": "Fr",
"Free": "Free", "Free": "Free",
"Friday": "Friday", "Friday": "Friday",
"From": "From", "From": "From",
@ -311,6 +312,7 @@
"Maximum allowed extension is {max}": "Maximum allowed extension is {max}", "Maximum allowed extension is {max}": "Maximum allowed extension is {max}",
"Maximum calls in queue": "Maximum calls in queue", "Maximum calls in queue": "Maximum calls in queue",
"Minimum allowed extension is {min}": "Minimum allowed extension is {min}", "Minimum allowed extension is {min}": "Minimum allowed extension is {min}",
"Mo": "Mo",
"Monday": "Monday", "Monday": "Monday",
"Monthly": "Monthly", "Monthly": "Monthly",
"More than one {timeset} timeset exists. You can resolve this by resetting the {timeset} timesets.": "More than one {timeset} timeset exists. You can resolve this by resetting the {timeset} timesets.", "More than one {timeset} timeset exists. You can resolve this by resetting the {timeset} timesets.": "More than one {timeset} timeset exists. You can resolve this by resetting the {timeset} timesets.",
@ -332,6 +334,7 @@
"No Voicemails found": "No Voicemails found", "No Voicemails found": "No Voicemails found",
"No call goes to primary number": "No call goes to primary number", "No call goes to primary number": "No call goes to primary number",
"No call queues created yet": "No call queues created yet", "No call queues created yet": "No call queues created yet",
"No data to save. Please provide at least one time range.": "No data to save. Please provide at least one time range.",
"No destinations created yet": "No destinations created yet", "No destinations created yet": "No destinations created yet",
"No devices created yet": "No devices created yet", "No devices created yet": "No devices created yet",
"No groups": "No groups", "No groups": "No groups",
@ -421,6 +424,7 @@
"Remove sound file": "Remove sound file", "Remove sound file": "Remove sound file",
"Remove sound set": "Remove sound set", "Remove sound set": "Remove sound set",
"Remove speed dial": "Remove speed dial", "Remove speed dial": "Remove speed dial",
"Remove time range": "Remove time range",
"Remove voicemail": "Remove voicemail", "Remove voicemail": "Remove voicemail",
"Removed call queue for {callQueue} successfully": "Removed call queue for {callQueue} successfully", "Removed call queue for {callQueue} successfully": "Removed call queue for {callQueue} successfully",
"Removed destination {destination}": "Removed destination {destination}", "Removed destination {destination}": "Removed destination {destination}",
@ -448,6 +452,7 @@
"Ring primary number": "Ring primary number", "Ring primary number": "Ring primary number",
"Ringing at": "Ringing at", "Ringing at": "Ringing at",
"Ringing at {number}...": "Ringing at {number}...", "Ringing at {number}...": "Ringing at {number}...",
"Sa": "Sa",
"Same time for all selected days": "Same time for all selected days", "Same time for all selected days": "Same time for all selected days",
"Same time for selected days": "Same time for selected days", "Same time for selected days": "Same time for selected days",
"Saturday": "Saturday", "Saturday": "Saturday",
@ -493,15 +498,20 @@
"Sources": "Sources", "Sources": "Sources",
"Sourceset": "Sourceset", "Sourceset": "Sourceset",
"Speed Dial": "Speed Dial", "Speed Dial": "Speed Dial",
"Start and End time should be set": "Start and End time should be set",
"Start call": "Start call", "Start call": "Start call",
"Start date": "Start date", "Start date": "Start date",
"Start new call": "Start new call", "Start new call": "Start new call",
"Start time": "Start time", "Start time": "Start time",
"Start time should be less than End time": "Start time should be less than End time",
"Station name": "Station name", "Station name": "Station name",
"Su": "Su",
"Subscriber Sign In": "Subscriber Sign In", "Subscriber Sign In": "Subscriber Sign In",
"Sunday": "Sunday", "Sunday": "Sunday",
"Super": "Super", "Super": "Super",
"T38": "T38", "T38": "T38",
"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 Destination Email is already used": "The Destination Email is already used", "The Destination Email is already used": "The Destination Email is already used",
"The Notify Email is already used": "The Notify Email is already used", "The Notify Email is already used": "The Notify Email is already used",
"The {timeset} timeset contain reverse order of values. You can resolve this by resetting the {timeset} timeset.": "The {timeset} timeset contain reverse order of values. You can resolve this by resetting the {timeset} timeset.", "The {timeset} timeset contain reverse order of values. You can resolve this by resetting the {timeset} timeset.": "The {timeset} timeset contain reverse order of values. You can resolve this by resetting the {timeset} timeset.",
@ -517,12 +527,14 @@
"This sound set is incomplete ({amount} file is missing)": "This sound set is incomplete ({amount} file is missing)", "This sound set is incomplete ({amount} file is missing)": "This sound set is incomplete ({amount} file is missing)",
"This sound set is incomplete ({amount} files are missing)": "This sound set is incomplete ({amount} files are missing)", "This sound set is incomplete ({amount} files are missing)": "This sound set is incomplete ({amount} files are missing)",
"Thursday": "Thursday", "Thursday": "Thursday",
"Time is invalid": "Time is invalid",
"Time of the day": "Time of the day", "Time of the day": "Time of the day",
"Timeout": "Timeout", "Timeout": "Timeout",
"To": "To", "To": "To",
"Today": "Today", "Today": "Today",
"Toggled attachment successfully": "Toggled attachment successfully", "Toggled attachment successfully": "Toggled attachment successfully",
"Toggled deletion successfully": "Toggled deletion successfully", "Toggled deletion successfully": "Toggled deletion successfully",
"Tu": "Tu",
"Tuesday": "Tuesday", "Tuesday": "Tuesday",
"Type something": "Type something", "Type something": "Type something",
"Unassign": "Unassign", "Unassign": "Unassign",
@ -561,6 +573,7 @@
"Voicemail downloaded successfully": "Voicemail downloaded successfully", "Voicemail downloaded successfully": "Voicemail downloaded successfully",
"Voicemails": "Voicemails", "Voicemails": "Voicemails",
"WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?": "WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?", "WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?": "WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?",
"We": "We",
"Wednesday": "Wednesday", "Wednesday": "Wednesday",
"Weekday": "Weekday", "Weekday": "Weekday",
"Weekly": "Weekly", "Weekly": "Weekly",
@ -577,6 +590,7 @@
"You are about to change your login password. After the password was changed successfully, you get automatically logged out to authenticate with the new password. ": "You are about to change your login password. After the password was changed successfully, you get automatically logged out to authenticate with the new password. ", "You are about to change your login password. After the password was changed successfully, you get automatically logged out to authenticate with the new password. ": "You are about to change your login password. After the password was changed successfully, you get automatically logged out to authenticate with the new password. ",
"You are about to delete {name} sourceset": "You are about to delete {name} sourceset", "You are about to delete {name} sourceset": "You are about to delete {name} sourceset",
"You are about to delete {name} timeset": "You are about to delete {name} timeset", "You are about to delete {name} timeset": "You are about to delete {name} timeset",
"You are about to delete time range \"{from} - {to}\"": "You are about to delete time range \"{from} - {to}\"",
"You are about to delete {destination} from {groupName} call forwarding": "You are about to delete {destination} from {groupName} call forwarding", "You are about to delete {destination} from {groupName} call forwarding": "You are about to delete {destination} from {groupName} call forwarding",
"You are about to delete {groupName} call forwarding group": "You are about to delete {groupName} call forwarding group", "You are about to delete {groupName} call forwarding group": "You are about to delete {groupName} call forwarding group",
"You are about to end the current call. Are you sure?": "You are about to end the current call. Are you sure?", "You are about to end the current call. Are you sure?": "You are about to end the current call. Are you sure?",

@ -243,6 +243,7 @@
"Forward to Voicebox": "", "Forward to Voicebox": "",
"Forwarded to": "", "Forwarded to": "",
"Forwarding": "", "Forwarding": "",
"Fr": "",
"Free": "Libre", "Free": "Libre",
"Friday": "Viernes", "Friday": "Viernes",
"From": "De", "From": "De",
@ -311,6 +312,7 @@
"Maximum allowed extension is {max}": "", "Maximum allowed extension is {max}": "",
"Maximum calls in queue": "Máximo de llamadas en cola", "Maximum calls in queue": "Máximo de llamadas en cola",
"Minimum allowed extension is {min}": "", "Minimum allowed extension is {min}": "",
"Mo": "",
"Monday": "Lunes", "Monday": "Lunes",
"Monthly": "", "Monthly": "",
"More than one {timeset} timeset exists. You can resolve this by resetting the {timeset} timesets.": "Existe más de un conjunto de tiempos {timeset}. Puede resolver esto restableciendo los tiempos de {timeset}.", "More than one {timeset} timeset exists. You can resolve this by resetting the {timeset} timesets.": "Existe más de un conjunto de tiempos {timeset}. Puede resolver esto restableciendo los tiempos de {timeset}.",
@ -332,6 +334,7 @@
"No Voicemails found": "No se encontraron mensajes de voz", "No Voicemails found": "No se encontraron mensajes de voz",
"No call goes to primary number": "", "No call goes to primary number": "",
"No call queues created yet": "Aún no se han creado colas de llamadas", "No call queues created yet": "Aún no se han creado colas de llamadas",
"No data to save. Please provide at least one time range.": "",
"No destinations created yet": "", "No destinations created yet": "",
"No devices created yet": "Aún no se han creado dispositivos", "No devices created yet": "Aún no se han creado dispositivos",
"No groups": "No asignado a ningún grupo", "No groups": "No asignado a ningún grupo",
@ -421,6 +424,7 @@
"Remove sound file": "Remover conjunto de sonido", "Remove sound file": "Remover conjunto de sonido",
"Remove sound set": "Remover conjunto de sonido", "Remove sound set": "Remover conjunto de sonido",
"Remove speed dial": "Eliminar marcación rápida", "Remove speed dial": "Eliminar marcación rápida",
"Remove time range": "",
"Remove voicemail": "", "Remove voicemail": "",
"Removed call queue for {callQueue} successfully": "Se eliminó correctamente la cola de llamadas {callQueue}", "Removed call queue for {callQueue} successfully": "Se eliminó correctamente la cola de llamadas {callQueue}",
"Removed destination {destination}": "Destino {destination} eliminado", "Removed destination {destination}": "Destino {destination} eliminado",
@ -448,6 +452,7 @@
"Ring primary number": "", "Ring primary number": "",
"Ringing at": "Timbrando en", "Ringing at": "Timbrando en",
"Ringing at {number}...": "Timbrando ...", "Ringing at {number}...": "Timbrando ...",
"Sa": "",
"Same time for all selected days": "", "Same time for all selected days": "",
"Same time for selected days": "", "Same time for selected days": "",
"Saturday": "Sábado", "Saturday": "Sábado",
@ -493,15 +498,20 @@
"Sources": "Fuentes", "Sources": "Fuentes",
"Sourceset": "Conjunto de fuentes", "Sourceset": "Conjunto de fuentes",
"Speed Dial": "Marcación rápida", "Speed Dial": "Marcación rápida",
"Start and End time should be set": "",
"Start call": "Inicio", "Start call": "Inicio",
"Start date": "", "Start date": "",
"Start new call": "Iniciar nueva llamada", "Start new call": "Iniciar nueva llamada",
"Start time": "", "Start time": "",
"Start time should be less than End time": "",
"Station name": "Nombre de la estación", "Station name": "Nombre de la estación",
"Su": "",
"Subscriber Sign In": "Iniciar sesión de suscriptor", "Subscriber Sign In": "Iniciar sesión de suscriptor",
"Sunday": "Domingo", "Sunday": "Domingo",
"Super": "Super", "Super": "Super",
"T38": "", "T38": "",
"Th": "",
"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 Notify Email is already used": "", "The Notify Email is already used": "",
"The {timeset} timeset contain reverse order of values. You can resolve this by resetting the {timeset} timeset.": "El conjunto de tiempos {timeset} contiene el orden inverso de los valores. Puede resolver esto restableciendo el conjunto de tiempos {timeset}.", "The {timeset} timeset contain reverse order of values. You can resolve this by resetting the {timeset} timeset.": "El conjunto de tiempos {timeset} contiene el orden inverso de los valores. Puede resolver esto restableciendo el conjunto de tiempos {timeset}.",
@ -517,12 +527,14 @@
"This sound set is incomplete ({amount} file is missing)": "Este conjunto de sonidos está incompleto ({amount} falta archivo)", "This sound set is incomplete ({amount} file is missing)": "Este conjunto de sonidos está incompleto ({amount} falta archivo)",
"This sound set is incomplete ({amount} files are missing)": "Este conjunto de sonidos está incompleto ({amount} faltan archivos)", "This sound set is incomplete ({amount} files are missing)": "Este conjunto de sonidos está incompleto ({amount} faltan archivos)",
"Thursday": "Jueves", "Thursday": "Jueves",
"Time is invalid": "",
"Time of the day": "Hora del día", "Time of the day": "Hora del día",
"Timeout": "Tiempo de espera", "Timeout": "Tiempo de espera",
"To": "Para", "To": "Para",
"Today": "Hoy", "Today": "Hoy",
"Toggled attachment successfully": "Adjunto cambiado correctamente", "Toggled attachment successfully": "Adjunto cambiado correctamente",
"Toggled deletion successfully": "Eliminación cambiada correctamente", "Toggled deletion successfully": "Eliminación cambiada correctamente",
"Tu": "",
"Tuesday": "Martes", "Tuesday": "Martes",
"Type something": "", "Type something": "",
"Unassign": "", "Unassign": "",
@ -561,6 +573,7 @@
"Voicemail downloaded successfully": "Correo de voz descargado con éxito", "Voicemail downloaded successfully": "Correo de voz descargado con éxito",
"Voicemails": "Mensajes de voz", "Voicemails": "Mensajes de voz",
"WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?": "ADVERTENCIA: Al eliminar la última entrada también se eliminará este conjunto de horas y todos los destinos correspondientes. ¿Está seguro de querer hacer esto?", "WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?": "ADVERTENCIA: Al eliminar la última entrada también se eliminará este conjunto de horas y todos los destinos correspondientes. ¿Está seguro de querer hacer esto?",
"We": "",
"Wednesday": "Miércoles", "Wednesday": "Miércoles",
"Weekday": "Día de la semana", "Weekday": "Día de la semana",
"Weekly": "", "Weekly": "",
@ -577,6 +590,7 @@
"You are about to change your login password. After the password was changed successfully, you get automatically logged out to authenticate with the new password. ": "Está a punto de cambiar su contraseña de inicio de sesión. Después de que la contraseña se cambie con éxito, se cerrará la sesión automáticamente para autenticarse con la nueva contraseña.", "You are about to change your login password. After the password was changed successfully, you get automatically logged out to authenticate with the new password. ": "Está a punto de cambiar su contraseña de inicio de sesión. Después de que la contraseña se cambie con éxito, se cerrará la sesión automáticamente para autenticarse con la nueva contraseña.",
"You are about to delete {name} sourceset": "", "You are about to delete {name} sourceset": "",
"You are about to delete {name} timeset": "", "You are about to delete {name} timeset": "",
"You are about to delete time range \"{from} - {to}\"": "",
"You are about to delete {destination} from {groupName} call forwarding": "", "You are about to delete {destination} from {groupName} call forwarding": "",
"You are about to delete {groupName} call forwarding group": "", "You are about to delete {groupName} call forwarding group": "",
"You are about to end the current call. Are you sure?": "Está a punto de finalizar la llamada actual. ¿Está seguro?", "You are about to end the current call. Are you sure?": "Está a punto de finalizar la llamada actual. ¿Está seguro?",

@ -243,6 +243,7 @@
"Forward to Voicebox": "", "Forward to Voicebox": "",
"Forwarded to": "", "Forwarded to": "",
"Forwarding": "", "Forwarding": "",
"Fr": "",
"Free": "Gratuit", "Free": "Gratuit",
"Friday": "Vendredi", "Friday": "Vendredi",
"From": "De", "From": "De",
@ -311,6 +312,7 @@
"Maximum allowed extension is {max}": "", "Maximum allowed extension is {max}": "",
"Maximum calls in queue": "", "Maximum calls in queue": "",
"Minimum allowed extension is {min}": "", "Minimum allowed extension is {min}": "",
"Mo": "",
"Monday": "Lundi", "Monday": "Lundi",
"Monthly": "", "Monthly": "",
"More than one {timeset} timeset exists. You can resolve this by resetting the {timeset} timesets.": "Il existe déjà une plage horaire {timeset}. Vous pouvez résoudre le problème en réinitialisant la plage horaire {timeset}.", "More than one {timeset} timeset exists. You can resolve this by resetting the {timeset} timesets.": "Il existe déjà une plage horaire {timeset}. Vous pouvez résoudre le problème en réinitialisant la plage horaire {timeset}.",
@ -332,6 +334,7 @@
"No Voicemails found": "Message vocaux introuvables", "No Voicemails found": "Message vocaux introuvables",
"No call goes to primary number": "", "No call goes to primary number": "",
"No call queues created yet": "Aucune file dattente de créée", "No call queues created yet": "Aucune file dattente de créée",
"No data to save. Please provide at least one time range.": "",
"No destinations created yet": "", "No destinations created yet": "",
"No devices created yet": "Aucun poste créé", "No devices created yet": "Aucun poste créé",
"No groups": "Aucun groupe", "No groups": "Aucun groupe",
@ -421,6 +424,7 @@
"Remove sound file": "", "Remove sound file": "",
"Remove sound set": "", "Remove sound set": "",
"Remove speed dial": "Supprimer le raccourci", "Remove speed dial": "Supprimer le raccourci",
"Remove time range": "",
"Remove voicemail": "", "Remove voicemail": "",
"Removed call queue for {callQueue} successfully": "", "Removed call queue for {callQueue} successfully": "",
"Removed destination {destination}": "Destination {destination} supprimée", "Removed destination {destination}": "Destination {destination} supprimée",
@ -448,6 +452,7 @@
"Ring primary number": "", "Ring primary number": "",
"Ringing at": "Appel présenté à", "Ringing at": "Appel présenté à",
"Ringing at {number}...": "{number} sonne...", "Ringing at {number}...": "{number} sonne...",
"Sa": "",
"Same time for all selected days": "", "Same time for all selected days": "",
"Same time for selected days": "", "Same time for selected days": "",
"Saturday": "Samedi", "Saturday": "Samedi",
@ -493,15 +498,20 @@
"Sources": "Sources", "Sources": "Sources",
"Sourceset": "Liste de sources", "Sourceset": "Liste de sources",
"Speed Dial": "Raccourcis", "Speed Dial": "Raccourcis",
"Start and End time should be set": "",
"Start call": "Lancer un appel", "Start call": "Lancer un appel",
"Start date": "", "Start date": "",
"Start new call": "Lancer un appel", "Start new call": "Lancer un appel",
"Start time": "", "Start time": "",
"Start time should be less than End time": "",
"Station name": "Nom du poste", "Station name": "Nom du poste",
"Su": "",
"Subscriber Sign In": "Authentification de labonné", "Subscriber Sign In": "Authentification de labonné",
"Sunday": "Dimanche", "Sunday": "Dimanche",
"Super": "Supérieur", "Super": "Supérieur",
"T38": "", "T38": "",
"Th": "",
"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 Notify Email is already used": "", "The Notify Email is already used": "",
"The {timeset} timeset contain reverse order of values. You can resolve this by resetting the {timeset} timeset.": "Lordre des valeurs de la plage horaire {timeset} est inversé. Vous pouvez résoudre le problème en réinitialisant la plage horaire {timeset}.", "The {timeset} timeset contain reverse order of values. You can resolve this by resetting the {timeset} timeset.": "Lordre des valeurs de la plage horaire {timeset} est inversé. Vous pouvez résoudre le problème en réinitialisant la plage horaire {timeset}.",
@ -517,12 +527,14 @@
"This sound set is incomplete ({amount} file is missing)": "", "This sound set is incomplete ({amount} file is missing)": "",
"This sound set is incomplete ({amount} files are missing)": "", "This sound set is incomplete ({amount} files are missing)": "",
"Thursday": "Jeudi", "Thursday": "Jeudi",
"Time is invalid": "",
"Time of the day": "Heure de la journée", "Time of the day": "Heure de la journée",
"Timeout": "Temporisation", "Timeout": "Temporisation",
"To": "A", "To": "A",
"Today": "", "Today": "",
"Toggled attachment successfully": "Les pièces jointes ont été activées avec succès", "Toggled attachment successfully": "Les pièces jointes ont été activées avec succès",
"Toggled deletion successfully": "La suppression a été activée avec succès", "Toggled deletion successfully": "La suppression a été activée avec succès",
"Tu": "",
"Tuesday": "Mardi", "Tuesday": "Mardi",
"Type something": "", "Type something": "",
"Unassign": "", "Unassign": "",
@ -561,6 +573,7 @@
"Voicemail downloaded successfully": "Le message vocal a été téléchargé avec succès", "Voicemail downloaded successfully": "Le message vocal a été téléchargé avec succès",
"Voicemails": "Message vocaux", "Voicemails": "Message vocaux",
"WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?": "Attention: supprimer la dernière entrée dheure de renvoi supprimera aussi cet intervalle de temps ainsi que toutes les destinations. Êtes-vous sûr de vouloir faire ceci?", "WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?": "Attention: supprimer la dernière entrée dheure de renvoi supprimera aussi cet intervalle de temps ainsi que toutes les destinations. Êtes-vous sûr de vouloir faire ceci?",
"We": "",
"Wednesday": "Mercredi", "Wednesday": "Mercredi",
"Weekday": "Jour de la semaine", "Weekday": "Jour de la semaine",
"Weekly": "", "Weekly": "",
@ -577,6 +590,7 @@
"You are about to change your login password. After the password was changed successfully, you get automatically logged out to authenticate with the new password. ": "", "You are about to change your login password. After the password was changed successfully, you get automatically logged out to authenticate with the new password. ": "",
"You are about to delete {name} sourceset": "", "You are about to delete {name} sourceset": "",
"You are about to delete {name} timeset": "", "You are about to delete {name} timeset": "",
"You are about to delete time range \"{from} - {to}\"": "",
"You are about to delete {destination} from {groupName} call forwarding": "", "You are about to delete {destination} from {groupName} call forwarding": "",
"You are about to delete {groupName} call forwarding group": "", "You are about to delete {groupName} call forwarding group": "",
"You are about to end the current call. Are you sure?": "Vous êtes sur le point de raccrocher lappel. Êtes-vous sûr?", "You are about to end the current call. Are you sure?": "Vous êtes sur le point de raccrocher lappel. Êtes-vous sûr?",

@ -243,6 +243,7 @@
"Forward to Voicebox": "", "Forward to Voicebox": "",
"Forwarded to": "", "Forwarded to": "",
"Forwarding": "", "Forwarding": "",
"Fr": "",
"Free": "Libero", "Free": "Libero",
"Friday": "Venerdì", "Friday": "Venerdì",
"From": "Da", "From": "Da",
@ -311,6 +312,7 @@
"Maximum allowed extension is {max}": "", "Maximum allowed extension is {max}": "",
"Maximum calls in queue": "Numero massimo di chiamate nella coda", "Maximum calls in queue": "Numero massimo di chiamate nella coda",
"Minimum allowed extension is {min}": "", "Minimum allowed extension is {min}": "",
"Mo": "",
"Monday": "Lunedì", "Monday": "Lunedì",
"Monthly": "", "Monthly": "",
"More than one {timeset} timeset exists. You can resolve this by resetting the {timeset} timesets.": "Esistono più tabelle orarie {timeset}. Puoi risolvere il problema ripristinando le tabelle orarie {timeset}.", "More than one {timeset} timeset exists. You can resolve this by resetting the {timeset} timesets.": "Esistono più tabelle orarie {timeset}. Puoi risolvere il problema ripristinando le tabelle orarie {timeset}.",
@ -332,6 +334,7 @@
"No Voicemails found": "Non è stato trovato nessun Messaggio Vocale", "No Voicemails found": "Non è stato trovato nessun Messaggio Vocale",
"No call goes to primary number": "", "No call goes to primary number": "",
"No call queues created yet": "Nessuna coda creata", "No call queues created yet": "Nessuna coda creata",
"No data to save. Please provide at least one time range.": "",
"No destinations created yet": "", "No destinations created yet": "",
"No devices created yet": "Nessun dispositivo creato", "No devices created yet": "Nessun dispositivo creato",
"No groups": "Non ci sono gruppi", "No groups": "Non ci sono gruppi",
@ -421,6 +424,7 @@
"Remove sound file": "Rimuovi messaggio", "Remove sound file": "Rimuovi messaggio",
"Remove sound set": "Rimuovi set messaggi", "Remove sound set": "Rimuovi set messaggi",
"Remove speed dial": "Rimuovi selezione rapida", "Remove speed dial": "Rimuovi selezione rapida",
"Remove time range": "",
"Remove voicemail": "", "Remove voicemail": "",
"Removed call queue for {callQueue} successfully": "Coda per {callQueue} rimossa con successo", "Removed call queue for {callQueue} successfully": "Coda per {callQueue} rimossa con successo",
"Removed destination {destination}": "Destinazione {destination} rimossa", "Removed destination {destination}": "Destinazione {destination} rimossa",
@ -448,6 +452,7 @@
"Ring primary number": "", "Ring primary number": "",
"Ringing at": "Sta squillando", "Ringing at": "Sta squillando",
"Ringing at {number}...": "Sta squillando {number}...", "Ringing at {number}...": "Sta squillando {number}...",
"Sa": "",
"Same time for all selected days": "", "Same time for all selected days": "",
"Same time for selected days": "", "Same time for selected days": "",
"Saturday": "Sabato", "Saturday": "Sabato",
@ -493,15 +498,20 @@
"Sources": "Sorgenti", "Sources": "Sorgenti",
"Sourceset": "Set di pattern sorgente", "Sourceset": "Set di pattern sorgente",
"Speed Dial": "Selezione Rapida", "Speed Dial": "Selezione Rapida",
"Start and End time should be set": "",
"Start call": "Inizia chiamata", "Start call": "Inizia chiamata",
"Start date": "", "Start date": "",
"Start new call": "Inizia nuova chiamata", "Start new call": "Inizia nuova chiamata",
"Start time": "", "Start time": "",
"Start time should be less than End time": "",
"Station name": "Nome stazione", "Station name": "Nome stazione",
"Su": "",
"Subscriber Sign In": "Accedi come utente", "Subscriber Sign In": "Accedi come utente",
"Sunday": "Domenica", "Sunday": "Domenica",
"Super": "Super", "Super": "Super",
"T38": "", "T38": "",
"Th": "",
"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 Notify Email is already used": "", "The Notify Email is already used": "",
"The {timeset} timeset contain reverse order of values. You can resolve this by resetting the {timeset} timeset.": "La tabella oraria {timeset} contiene valori in ordine inverso. Puoi risolvere il problema ripristinando la tabella oraria {timeset}.", "The {timeset} timeset contain reverse order of values. You can resolve this by resetting the {timeset} timeset.": "La tabella oraria {timeset} contiene valori in ordine inverso. Puoi risolvere il problema ripristinando la tabella oraria {timeset}.",
@ -517,12 +527,14 @@
"This sound set is incomplete ({amount} file is missing)": "Questo set di messaggi è incompleto ({amount} files mancanti)", "This sound set is incomplete ({amount} file is missing)": "Questo set di messaggi è incompleto ({amount} files mancanti)",
"This sound set is incomplete ({amount} files are missing)": "Questo set di messaggi è incompleto ({amount} files mancanti)", "This sound set is incomplete ({amount} files are missing)": "Questo set di messaggi è incompleto ({amount} files mancanti)",
"Thursday": "Giovedì", "Thursday": "Giovedì",
"Time is invalid": "",
"Time of the day": "Ora del giorno", "Time of the day": "Ora del giorno",
"Timeout": "Timeout", "Timeout": "Timeout",
"To": "A", "To": "A",
"Today": "Oggi", "Today": "Oggi",
"Toggled attachment successfully": "Opzione allegato modificata con successo", "Toggled attachment successfully": "Opzione allegato modificata con successo",
"Toggled deletion successfully": "Cancellazione modificata con successo", "Toggled deletion successfully": "Cancellazione modificata con successo",
"Tu": "",
"Tuesday": "Martedì", "Tuesday": "Martedì",
"Type something": "", "Type something": "",
"Unassign": "", "Unassign": "",
@ -561,6 +573,7 @@
"Voicemail downloaded successfully": "Messaggio vocale scaricato con successo", "Voicemail downloaded successfully": "Messaggio vocale scaricato con successo",
"Voicemails": "Messaggi Vocali", "Voicemails": "Messaggi Vocali",
"WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?": "ATTENZIONE: Rimuovendo l'ultimo orario si cancellerà l'intera tabella oraria e tutte le destinazioni corrispondenti. Sei sicuro di volerlo fare?", "WARNING: Removing the last time entry will also delete this timeset and all corresponding destinations. Are you sure you want to do this?": "ATTENZIONE: Rimuovendo l'ultimo orario si cancellerà l'intera tabella oraria e tutte le destinazioni corrispondenti. Sei sicuro di volerlo fare?",
"We": "",
"Wednesday": "Mercoledì", "Wednesday": "Mercoledì",
"Weekday": "Giorno della Settimana", "Weekday": "Giorno della Settimana",
"Weekly": "", "Weekly": "",
@ -577,6 +590,7 @@
"You are about to change your login password. After the password was changed successfully, you get automatically logged out to authenticate with the new password. ": "Stai per modificare la tua password di login. Una volta che la password sarà modificata, verrai automaticamente disconnesso e dovrai accedere con la nuova password. ", "You are about to change your login password. After the password was changed successfully, you get automatically logged out to authenticate with the new password. ": "Stai per modificare la tua password di login. Una volta che la password sarà modificata, verrai automaticamente disconnesso e dovrai accedere con la nuova password. ",
"You are about to delete {name} sourceset": "", "You are about to delete {name} sourceset": "",
"You are about to delete {name} timeset": "", "You are about to delete {name} timeset": "",
"You are about to delete time range \"{from} - {to}\"": "",
"You are about to delete {destination} from {groupName} call forwarding": "", "You are about to delete {destination} from {groupName} call forwarding": "",
"You are about to delete {groupName} call forwarding group": "", "You are about to delete {groupName} call forwarding group": "",
"You are about to end the current call. Are you sure?": "Stai per terminare la chiamata in corso. Sei sicuro?", "You are about to end the current call. Are you sure?": "Stai per terminare la chiamata in corso. Sei sicuro?",

@ -1,5 +1,5 @@
import { import {
cfCreateOfficeHours, cfCreateOfficeHoursSameTimes, cfCreateOfficeHours,
cfCreateSourceSet, cfCreateSourceSet,
cfCreateTimeSetDate, cfCreateTimeSetDate,
cfCreateTimeSetDateRange, cfCreateTimeSetDateRange,
@ -10,7 +10,7 @@ import {
cfLoadDestinationSets, cfLoadDestinationSets,
cfLoadMappingsFull, cfLoadMappingsFull,
cfLoadSourceSets, cfLoadSourceSets,
cfLoadTimeSets, cfUpdateOfficeHours, cfUpdateOfficeHoursSameTimes, cfLoadTimeSets, cfUpdateOfficeHours,
cfUpdateSourceSet, cfUpdateSourceSet,
cfUpdateTimeSetDate, cfUpdateTimeSetDate,
cfUpdateTimeSetDateRange, cfUpdateTimeSetDateRange,
@ -505,42 +505,6 @@ export async function updateOfficeHours ({ dispatch, commit, rootGetters, state
dispatch('wait/end', 'csc-cf-time-set-create', { root: true }) 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 })
}
export async function loadAnnouncements ({ dispatch, commit }) { export async function loadAnnouncements ({ dispatch, commit }) {
try { try {
const announcements = await getList({ const announcements = await getList({

Loading…
Cancel
Save