TT#100756 CallRecordings - As Customer, I want to filter CallRecordings by time range

- Integrate date range input in CallRecording list view
- Adjust data fetching part
- Specify UX flow

Change-Id: I666aaa397a09ec9a5c2c63e76485e7fc4a797a46
mr9.5.2
Carlo Venusino 5 years ago
parent cd6e98a14a
commit 6bc5155662

@ -0,0 +1,273 @@
<template>
<div>
<div
class="row justify-center full-width q-gutter-x-sm"
>
<div
class="col-xs-12 col-md-2"
>
<q-select
v-model="filterTypeModel"
dense
:options="filterTypeOptions"
:label="$t('Filter by')"
:disable="loading"
data-cy="filter-type"
/>
</div>
<div
v-if="filterType === 'timerange'"
class="row col-xs-12 col-md-4"
>
<q-input
v-model="dateStartFilter"
class="q-pr-sm col-6"
dense
:disable="loading || filterType === null"
:label="$t('Start time')"
>
<template v-slot:prepend>
<q-icon
name="event"
class="cursor-pointer"
>
<q-popup-proxy
transition-show="scale"
transition-hide="scale"
@hide="addFilter('startTime', dateStartFilter)"
>
<q-date
v-model="dateStartFilter"
mask="YYYY-MM-DD HH:mm"
format24h
>
<div class="row items-center justify-end">
<q-btn
v-close-popup
:label="$t('Close')"
color="primary"
flat
/>
</div>
</q-date>
</q-popup-proxy>
</q-icon>
</template>
<template v-slot:append>
<q-icon
name="access_time"
class="cursor-pointer"
>
<q-popup-proxy
transition-show="scale"
transition-hide="scale"
@hide="addFilter('startTime', dateStartFilter)"
>
<q-time
v-model="dateStartFilter"
mask="YYYY-MM-DD HH:mm"
format24h
>
<div class="row items-center justify-end">
<q-btn
v-close-popup
:label="$t('Close')"
color="primary"
flat
/>
</div>
</q-time>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<q-input
v-model="dateEndFilter"
class="col-6"
dense
:disable="loading || filterType === null"
:label="$t('End time')"
@input="triggerFilter"
>
<template v-slot:prepend>
<q-icon
name="event"
class="cursor-pointer"
>
<q-popup-proxy
transition-show="scale"
transition-hide="scale"
@hide="addFilter('endTime', dateEndFilter)"
>
<q-date
v-model="dateEndFilter"
mask="YYYY-MM-DD HH:mm"
>
<div class="row items-center justify-end">
<q-btn
v-close-popup
:label="$t('Close')"
color="primary"
flat
/>
</div>
</q-date>
</q-popup-proxy>
</q-icon>
</template>
<template v-slot:append>
<q-icon
name="access_time"
class="cursor-pointer"
>
<q-popup-proxy
transition-show="scale"
transition-hide="scale"
@hide="addFilter('endTime', dateEndFilter)"
>
<q-time
v-model="dateEndFilter"
mask="YYYY-MM-DD HH:mm"
format24h
>
<div class="row items-center justify-end">
<q-btn
v-close-popup
:label="$t('Close')"
color="primary"
flat
/>
</div>
</q-time>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
</div>
<div
class="row justify-center full-width q-gutter-x-sm"
>
<div
class="col-xs-12 col-md-4"
>
<q-chip
v-for="({ filterInfo, id }) in filtersList"
:key="id"
:label="filterInfo"
:disable="false"
icon="filter_alt"
removable
dense
color="primary"
text-color="dark"
data-cy="filter-applied-item"
@remove="removeFilter(id)"
/>
</div>
</div>
</div>
</template>
<script>
import _ from 'lodash'
export default {
name: 'CscCallRecordingFilters',
props: {
loading: {
type: Boolean,
default: false
}
},
data () {
return {
filterTypeModel: null,
typedFilter: null,
dateStartFilter: null,
dateEndFilter: null,
filters: []
}
},
computed: {
filterType () {
return this.filterTypeModel && this.filterTypeModel.value
},
filterTypeOptions () {
return [
{
label: this.$t('Timerange'),
value: 'timerange'
}
]
},
filtersList () {
return this.filters.map((filterItem) => {
const filterDisplayValue = filterItem.value
let filterName
switch (filterItem.name) {
case 'startTime':
filterName = this.$t('Start time')
break
case 'endTime' :
filterName = this.$t('End time')
break
default:
filterName = this.filterTypeOptions.find(option => option.value === filterItem.name).label
}
return {
id: filterItem.name,
filterInfo: filterName + ': ' + filterDisplayValue
}
})
}
},
watch: {
filterTypeModel () {
this.resetFilters()
}
},
methods: {
triggerFilter (data) {
this.addFilter(this.filterTypeModel?.value, this.typedFilter)
},
removeFilter (name) {
this.filters = this.filters.filter(item => item.name !== name)
this.filter()
},
removeFilters () {
if (this.filters.length > 0) {
this.filters = []
this.filter()
}
},
addFilter (name, value) {
const valueTrimmed = _.trim(value)
if (valueTrimmed) {
this.resetFilters()
this.filters = this.filters.filter(item => item.name !== name)
const filter = {
name: name,
value: valueTrimmed
}
this.filters.push(filter)
this.filter()
}
},
filter () {
const params = {}
this.filters.forEach(filter => {
params[filter.name] = filter.value
})
this.$emit('filter', params)
},
resetFilters () {
this.typedFilter = null
this.dateStartFilter = null
this.dateEndFilter = null
}
}
}
</script>

@ -538,6 +538,7 @@
"Time is invalid": "",
"Time of the day": "Uhrzeit des Tages",
"Timeout": "Klingeldauer",
"Timerange": "",
"To": "An",
"Today": "Heute",
"Toggled attachment successfully": "Voicemail als Anhang geändert",

@ -538,6 +538,7 @@
"Time is invalid": "Time is invalid",
"Time of the day": "Time of the day",
"Timeout": "Timeout",
"Timerange": "Timerange",
"To": "To",
"Today": "Today",
"Toggled attachment successfully": "Toggled attachment successfully",

@ -538,6 +538,7 @@
"Time is invalid": "",
"Time of the day": "Hora del día",
"Timeout": "Tiempo de espera",
"Timerange": "",
"To": "Para",
"Today": "Hoy",
"Toggled attachment successfully": "Adjunto cambiado correctamente",

@ -538,6 +538,7 @@
"Time is invalid": "",
"Time of the day": "Heure de la journée",
"Timeout": "Temporisation",
"Timerange": "",
"To": "A",
"Today": "",
"Toggled attachment successfully": "Les pièces jointes ont été activées avec succès",

@ -538,6 +538,7 @@
"Time is invalid": "",
"Time of the day": "Ora del giorno",
"Timeout": "Timeout",
"Timerange": "",
"To": "A",
"Today": "Oggi",
"Toggled attachment successfully": "Opzione allegato modificata con successo",

@ -1,7 +1,37 @@
<template>
<csc-page
class="q-pa-lg"
<csc-page-sticky
id="csc-page-call-recording"
>
<template
v-slot:header
>
<q-btn
v-if="!showFilters"
icon="filter_alt"
color="primary"
flat
:label="$t('Filter')"
@click="openFilters"
/>
<q-btn
v-if="showFilters"
icon="clear"
color="negative"
flat
:label="$t('Close filters')"
@click="closeFilters"
/>
</template>
<template
v-slot:toolbar
>
<csc-call-recording-filters
v-if="showFilters"
ref="filters"
class="q-mb-md q-pa-md"
@filter="filterEvent"
/>
</template>
<template>
<div class="q-pa-md">
<q-table
@ -33,16 +63,7 @@
<q-tr
:props="props"
>
<q-td auto-width>
<q-btn
size="sm"
color="primary"
round
dense
:icon="isRowExpanded(props.row.id) ? 'remove' : 'add'"
@click="updateCollapseArray(props.row.id)"
/>
</q-td>
<q-td auto-width />
<q-td
v-for="col in props.cols"
:key="col.name"
@ -51,12 +72,20 @@
{{ col.value }}
</q-td>
<q-td>
<csc-more-menu>
<csc-popup-menu-item-delete
color="negative"
@click="confirmRowDeletion(props.row.id)"
/>
</csc-more-menu>
<q-btn
color="negative"
icon="delete"
flat
@click="confirmRowDeletion(props.row.id)"
/>
<q-btn
size="md"
color="primary"
round
flat
:icon="isRowExpanded(props.row.id) ? 'expand_less' : 'expand_more'"
@click="updateCollapseArray(props.row.id)"
/>
<csc-confirmation-dialog
:key="props.row.id"
:ref="'confirmDelete-'+props.row.id"
@ -83,35 +112,45 @@
:loading="$wait.is('csc-call-recordings')"
:hide-pagination="true"
row-key="name"
class="csc-item-odd"
>
<template v-slot:header="props">
<q-tr :props="props">
<template v-slot:loading>
<q-inner-loading
showing
color="primary"
/>
</template>
<template v-slot:header="innerProps">
<q-tr :props="innerProps">
<q-th auto-width />
<q-th
v-for="col in props.cols"
v-for="col in innerProps.cols"
:key="col.name"
:props="props"
:props="innerProps"
>
{{ col.label }}
</q-th>
<q-th auto-width />
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<template v-slot:body="innerProps">
<q-tr :props="innerProps">
<q-td auto-width />
<q-td
v-for="col in props.cols"
v-for="col in innerProps.cols"
:key="col.name"
:props="props"
:props="innerProps"
>
{{ col.value }}
</q-td>
<q-td>
<q-btn
size="sm"
size="md"
color="primary"
icon="download"
dense
@click="saveFile(props.row.id)"
flat
@click="saveFile(innerProps.row.id)"
/>
</q-td>
</q-tr>
@ -123,29 +162,23 @@
</q-table>
</div>
</template>
</csc-page>
</csc-page-sticky>
</template>
<script>
import {
mapGetters
} from 'vuex'
import {
mapWaitingActions
} from 'vue-wait'
import CscPage from 'components/CscPage'
import CscMoreMenu from 'components/CscMoreMenu'
import CscPopupMenuItemDelete from 'components/CscPopupMenuItemDelete'
import CscConfirmationDialog from 'components/CscConfirmationDialog'
import { showGlobalError, showToast } from 'src/helpers/ui'
import { mapGetters } from 'vuex'
import { mapWaitingActions } from 'vue-wait'
import { saveAs } from 'file-saver'
import { showGlobalError, showToast } from 'src/helpers/ui'
import CscConfirmationDialog from 'components/CscConfirmationDialog'
import CscPageSticky from 'components/CscPageSticky'
import CscCallRecordingFilters from 'components/pages/CallRecording/CscCallRecordingFilters'
export default {
name: 'CscCallBlocking',
components: {
CscPage,
CscMoreMenu,
CscPopupMenuItemDelete,
CscConfirmationDialog
CscConfirmationDialog,
CscPageSticky,
CscCallRecordingFilters
},
data () {
return {
@ -175,36 +208,38 @@ export default {
label: '#',
align: 'left',
field: row => row.id,
format: val => `${val}`,
sortable: true
format: val => `${val}`
},
{
name: 'type',
required: true,
align: 'left',
label: this.$t('Type'),
field: row => row.type,
sortable: true
field: row => row.type
},
{
name: 'format',
required: true,
align: 'left',
label: this.$t('Format'),
field: row => row.format,
sortable: true
field: row => row.format
}
],
data: [],
rowStatus: [],
pagination: {
sortBy: 'id',
descending: false,
page: 1,
rowsPerPage: 5,
rowsNumber: 0
}
},
rowStatus: [],
filter: {
startTime: null,
endTime: null
},
showFilters: false
}
},
computed: {
@ -225,7 +260,8 @@ export default {
},
async mounted () {
await this.fetchPaginatedRecordings({
pagination: this.pagination
pagination: this.pagination,
filter: this.filter
})
},
methods: {
@ -237,15 +273,27 @@ export default {
}),
async fetchPaginatedRecordings (props) {
const { page, rowsPerPage, sortBy, descending } = props.pagination
const { startTime, endTime } = this.filter
const count = await this.fetchRecordings({
page: page,
rows: rowsPerPage,
order_by: sortBy,
order_by_direction: descending ? 'desc' : 'asc'
order_by_direction: descending ? 'desc' : 'asc',
start_time: startTime,
end_time: endTime
})
this.pagination = { ...props.pagination }
this.pagination.rowsNumber = count
},
openFilters () {
this.showFilters = true
},
closeFilters () {
if (this.$refs.filters) {
this.$refs.filters.removeFilters()
}
this.showFilters = false
},
confirmRowDeletion (rowId) {
this.$refs['confirmDelete-' + rowId].open()
},
@ -254,7 +302,8 @@ export default {
await this.deleteRecording(rowId)
showToast(this.$t('Recording successfully deleted'))
await this.fetchPaginatedRecordings({
pagination: this.pagination
pagination: this.pagination,
filter: this.filter
})
} catch (err) {
showGlobalError(this.$t('Something went wrong. Please retry later'))
@ -275,6 +324,14 @@ export default {
async saveFile (fileId) {
const file = await this.downloadRecording(fileId)
saveAs(file, 'call-recording-' + fileId + '.wav')
},
filterEvent (filter) {
this.$scrollTo(this.$parent.$el)
this.filter = filter
this.fetchPaginatedRecordings({
pagination: this.pagination,
filter: this.filter
})
}
}
}

Loading…
Cancel
Save