mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 03:57:52 +00:00
feat: SLA report filter (#9218)
Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1 px-4 pt-4 overflow-auto">
|
||||
<div class="flex flex-col flex-1 gap-6 px-4 pt-4 overflow-auto">
|
||||
<SLAReportFilters @filter-change="onFilterChange" />
|
||||
<woot-button
|
||||
color-scheme="success"
|
||||
@@ -44,8 +44,15 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
pageNumber: 1,
|
||||
from: 0,
|
||||
to: 0,
|
||||
activeFilter: {
|
||||
from: 0,
|
||||
to: 0,
|
||||
assigned_agent_id: null,
|
||||
inbox_id: null,
|
||||
team_id: null,
|
||||
sla_policy_id: null,
|
||||
label_list: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -57,6 +64,11 @@ export default {
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('agents/get');
|
||||
this.$store.dispatch('inboxes/get');
|
||||
this.$store.dispatch('teams/get');
|
||||
this.$store.dispatch('labels/get');
|
||||
this.$store.dispatch('sla/get');
|
||||
this.fetchSLAMetrics();
|
||||
this.fetchSLAReports();
|
||||
},
|
||||
@@ -64,22 +76,17 @@ export default {
|
||||
fetchSLAReports({ pageNumber } = {}) {
|
||||
this.$store.dispatch('slaReports/get', {
|
||||
page: pageNumber || this.pageNumber,
|
||||
from: this.from,
|
||||
to: this.to,
|
||||
...this.activeFilter,
|
||||
});
|
||||
},
|
||||
fetchSLAMetrics() {
|
||||
this.$store.dispatch('slaReports/getMetrics', {
|
||||
from: this.from,
|
||||
to: this.to,
|
||||
});
|
||||
this.$store.dispatch('slaReports/getMetrics', this.activeFilter);
|
||||
},
|
||||
onPageChange(pageNumber) {
|
||||
this.fetchSLAReports({ pageNumber });
|
||||
},
|
||||
onFilterChange({ from, to }) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
onFilterChange(params) {
|
||||
this.activeFilter = params;
|
||||
this.fetchSLAReports();
|
||||
this.fetchSLAMetrics();
|
||||
},
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<script setup>
|
||||
import FilterButton from './FilterButton.vue';
|
||||
import FilterListDropdown from './FilterListDropdown.vue';
|
||||
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
activeFilterType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
enableSearch: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'toggleDropdown',
|
||||
'removeFilter',
|
||||
'addFilter',
|
||||
'closeDropdown',
|
||||
]);
|
||||
const toggleDropdown = () => emit('toggleDropdown', props.type);
|
||||
const removeFilter = () => emit('removeFilter', props.type);
|
||||
const addFilter = item => emit('addFilter', item);
|
||||
const closeDropdown = () => emit('closeDropdown');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<filter-button
|
||||
right-icon="chevron-down"
|
||||
:button-text="name"
|
||||
class="bg-slate-50 dark:bg-slate-800 hover:bg-slate-75 dark:hover:bg-slate-800"
|
||||
@click="toggleDropdown"
|
||||
>
|
||||
<template v-if="showMenu && activeFilterType === type" #dropdown>
|
||||
<filter-list-dropdown
|
||||
v-if="options"
|
||||
v-on-clickaway="closeDropdown"
|
||||
:list-items="options"
|
||||
:active-filter-id="id"
|
||||
:input-placeholder="placeholder"
|
||||
:enable-search="enableSearch"
|
||||
class="flex flex-col w-[240px] overflow-y-auto left-0 md:left-auto md:right-0 top-10"
|
||||
@click="addFilter"
|
||||
@removeFilter="removeFilter"
|
||||
/>
|
||||
</template>
|
||||
</filter-button>
|
||||
</template>
|
||||
@@ -0,0 +1,98 @@
|
||||
<script setup>
|
||||
import FilterButton from './FilterButton.vue';
|
||||
import FilterListDropdown from './FilterListDropdown.vue';
|
||||
import FilterListItemButton from './FilterListItemButton.vue';
|
||||
import FilterDropdownEmptyState from './FilterDropdownEmptyState.vue';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
menuOption: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placeholderI18nKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
enableSearch: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
emptyStateMessage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
const hoveredItemId = ref(null);
|
||||
|
||||
const showSubMenu = id => {
|
||||
hoveredItemId.value = id;
|
||||
};
|
||||
|
||||
const hideSubMenu = () => {
|
||||
hoveredItemId.value = null;
|
||||
};
|
||||
|
||||
const isHovered = id => hoveredItemId.value === id;
|
||||
|
||||
const emit = defineEmits(['toggleDropdown', 'addFilter', 'closeDropdown']);
|
||||
const toggleDropdown = () => emit('toggleDropdown');
|
||||
const addFilter = item => {
|
||||
emit('addFilter', item);
|
||||
hideSubMenu();
|
||||
};
|
||||
const closeDropdown = () => {
|
||||
hideSubMenu();
|
||||
emit('closeDropdown');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<filter-button :button-text="name" left-icon="filter" @click="toggleDropdown">
|
||||
<!-- Dropdown with search and sub-dropdown -->
|
||||
<template v-if="showMenu" #dropdown>
|
||||
<filter-list-dropdown
|
||||
v-on-clickaway="closeDropdown"
|
||||
class="left-0 md:right-0 top-10"
|
||||
>
|
||||
<template #listItem>
|
||||
<filter-dropdown-empty-state
|
||||
v-if="!menuOption.length"
|
||||
:message="emptyStateMessage"
|
||||
/>
|
||||
<filter-list-item-button
|
||||
v-for="item in menuOption"
|
||||
:key="item.id"
|
||||
:button-text="item.name"
|
||||
@mouseenter="showSubMenu(item.id)"
|
||||
@mouseleave="hideSubMenu"
|
||||
@focus="showSubMenu(item.id)"
|
||||
>
|
||||
<!-- Submenu with search and clear button -->
|
||||
<template v-if="item.options && isHovered(item.id)" #dropdown>
|
||||
<filter-list-dropdown
|
||||
:list-items="item.options"
|
||||
:input-placeholder="
|
||||
$t(`${placeholderI18nKey}.${item.type.toUpperCase()}`)
|
||||
"
|
||||
:enable-search="enableSearch"
|
||||
class="flex flex-col w-[216px] overflow-y-auto top-0 left-36"
|
||||
@click="addFilter"
|
||||
/>
|
||||
</template>
|
||||
</filter-list-item-button>
|
||||
</template>
|
||||
</filter-list-dropdown>
|
||||
</template>
|
||||
</filter-button>
|
||||
</template>
|
||||
@@ -4,23 +4,44 @@ defineProps({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<button
|
||||
class="inline-flex relative items-center p-1.5 w-fit h-8 gap-1.5 bg-white dark:bg-slate-900 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-800 active:bg-slate-75 dark:active:bg-slate-800"
|
||||
class="inline-flex relative items-center p-1.5 w-fit h-8 gap-1.5 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-800 active:bg-slate-75 dark:active:bg-slate-800"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<slot name="leftIcon" />
|
||||
<slot name="leftIcon">
|
||||
<fluent-icon
|
||||
v-if="leftIcon"
|
||||
:icon="leftIcon"
|
||||
size="18"
|
||||
class="flex-shrink-0 text-slate-900 dark:text-slate-50"
|
||||
/>
|
||||
</slot>
|
||||
<span
|
||||
v-if="buttonText"
|
||||
class="text-sm font-medium text-slate-900 dark:text-slate-50"
|
||||
class="text-sm font-medium truncate text-slate-900 dark:text-slate-50"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</span>
|
||||
<slot name="rightIcon" />
|
||||
<div v-if="$slots.dropdown" class="absolute right-0 top-10" @click.stop>
|
||||
<slot name="dropdown" />
|
||||
</div>
|
||||
<slot name="rightIcon">
|
||||
<fluent-icon
|
||||
v-if="rightIcon"
|
||||
:icon="rightIcon"
|
||||
size="18"
|
||||
class="flex-shrink-0 text-slate-900 dark:text-slate-50"
|
||||
/>
|
||||
</slot>
|
||||
|
||||
<slot name="dropdown" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
inputValue: {
|
||||
type: String,
|
||||
default: '',
|
||||
@@ -18,7 +14,7 @@ defineProps({
|
||||
<div
|
||||
class="flex items-center justify-between h-10 min-h-[40px] sticky top-0 bg-white z-10 dark:bg-slate-800 gap-2 px-3 border-b rounded-t-xl border-slate-50 dark:border-slate-700"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center w-full gap-2">
|
||||
<fluent-icon
|
||||
icon="search"
|
||||
size="18"
|
||||
@@ -32,14 +28,16 @@ defineProps({
|
||||
@input="$emit('input', $event.target.value)"
|
||||
/>
|
||||
</div>
|
||||
<!-- Clear filter button -->
|
||||
<woot-button
|
||||
v-if="!inputValue"
|
||||
size="small"
|
||||
variant="clear"
|
||||
color-scheme="primary"
|
||||
class="!px-1 !py-1.5"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
{{ buttonText }}
|
||||
{{ $t('REPORT.FILTER_ACTIONS.CLEAR_FILTER') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -14,18 +14,14 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
inputButtonText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
emptyListMessage: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
inputPlaceholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
activeFilterId: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const searchTerm = ref('');
|
||||
@@ -42,29 +38,35 @@ const filteredListItems = computed(() => {
|
||||
const isDropdownListEmpty = computed(() => {
|
||||
return !filteredListItems.value.length;
|
||||
});
|
||||
|
||||
const isFilterActive = id => {
|
||||
if (!props.activeFilterId) return false;
|
||||
return id === props.activeFilterId;
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="z-20 w-40 bg-white border shadow dark:bg-slate-800 rounded-xl border-slate-50 dark:border-slate-700/50 max-h-72"
|
||||
class="absolute z-20 w-40 bg-white border shadow dark:bg-slate-800 rounded-xl border-slate-50 dark:border-slate-700/50 max-h-[400px]"
|
||||
@click.stop
|
||||
>
|
||||
<slot name="search">
|
||||
<filter-dropdown-search
|
||||
v-if="enableSearch && listItems.length"
|
||||
:button-text="inputButtonText"
|
||||
:input-value="searchTerm"
|
||||
:input-placeholder="inputPlaceholder"
|
||||
@input="onSearch"
|
||||
@click="onSearch('')"
|
||||
@click="$emit('removeFilter')"
|
||||
/>
|
||||
</slot>
|
||||
<slot name="listItem">
|
||||
<filter-dropdown-empty-state
|
||||
v-if="isDropdownListEmpty"
|
||||
:message="emptyListMessage"
|
||||
:message="$t('REPORT.FILTER_ACTIONS.EMPTY_LIST')"
|
||||
/>
|
||||
<filter-list-item-button
|
||||
v-for="item in filteredListItems"
|
||||
:key="item.id"
|
||||
:is-active="isFilterActive(item.id)"
|
||||
:button-text="item.name"
|
||||
@click="$emit('click', item)"
|
||||
/>
|
||||
|
||||
@@ -13,7 +13,7 @@ defineProps({
|
||||
<template>
|
||||
<button
|
||||
class="relative inline-flex items-center justify-start w-full p-3 border-0 rounded-none first:rounded-t-xl last:rounded-b-xl h-11 hover:bg-slate-50 dark:hover:bg-slate-700 active:bg-slate-75 dark:active:bg-slate-800"
|
||||
@click="$emit('click')"
|
||||
@click.stop="$emit('click')"
|
||||
@mouseenter="$emit('mouseenter')"
|
||||
@mouseleave="$emit('mouseleave')"
|
||||
@focus="$emit('focus')"
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col flex-wrap items-start gap-2 md:items-center md:flex-nowrap md:flex-row"
|
||||
>
|
||||
<!-- Active filters section -->
|
||||
<div v-if="hasActiveFilters" class="flex flex-wrap gap-2 md:flex-nowrap">
|
||||
<active-filter-chip
|
||||
v-for="filter in activeFilters"
|
||||
v-bind="filter"
|
||||
:key="filter.type"
|
||||
:placeholder="
|
||||
$t(
|
||||
`SLA_REPORTS.DROPDOWN.INPUT_PLACEHOLDER.${filter.type.toUpperCase()}`
|
||||
)
|
||||
"
|
||||
:active-filter-type="activeFilterType"
|
||||
:show-menu="showSubDropdownMenu"
|
||||
enable-search
|
||||
@toggleDropdown="openActiveFilterDropdown"
|
||||
@closeDropdown="closeActiveFilterDropdown"
|
||||
@addFilter="addFilter"
|
||||
@removeFilter="removeFilter"
|
||||
/>
|
||||
</div>
|
||||
<!-- Dividing line between Active filters and Add filter button -->
|
||||
<div
|
||||
v-if="hasActiveFilters && !isAllFilterSelected"
|
||||
class="w-full h-px border md:w-px md:h-5 border-slate-75 dark:border-slate-800"
|
||||
/>
|
||||
<!-- Add filter and clear filter button -->
|
||||
<div class="flex items-center gap-2">
|
||||
<add-filter-chip
|
||||
v-if="!isAllFilterSelected"
|
||||
placeholder-i18n-key="SLA_REPORTS.DROPDOWN.INPUT_PLACEHOLDER"
|
||||
:name="$t('SLA_REPORTS.DROPDOWN.ADD_FIlTER')"
|
||||
:menu-option="filterListMenuItems"
|
||||
:show-menu="showDropdownMenu"
|
||||
:empty-state-message="$t('SLA_REPORTS.DROPDOWN.NO_FILTER')"
|
||||
@toggleDropdown="showDropdown"
|
||||
@closeDropdown="closeDropdown"
|
||||
@addFilter="addFilter"
|
||||
/>
|
||||
|
||||
<!-- Dividing line between Add filter and Clear all filter button -->
|
||||
<div
|
||||
v-if="hasActiveFilters"
|
||||
class="w-px h-5 border border-slate-75 dark:border-slate-800"
|
||||
/>
|
||||
<!-- Clear all filter button -->
|
||||
<filter-button
|
||||
v-if="hasActiveFilters"
|
||||
:button-text="$t('SLA_REPORTS.DROPDOWN.CLEAR_ALL')"
|
||||
@click="clearAllFilters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import {
|
||||
buildFilterList,
|
||||
getActiveFilter,
|
||||
getFilterType,
|
||||
} from './helpers/SLAFilterHelpers';
|
||||
import FilterButton from '../Filters/v3/FilterButton.vue';
|
||||
import ActiveFilterChip from '../Filters/v3/ActiveFilterChip.vue';
|
||||
import AddFilterChip from '../Filters/v3/AddFilterChip.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FilterButton,
|
||||
ActiveFilterChip,
|
||||
AddFilterChip,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showDropdownMenu: false,
|
||||
showSubDropdownMenu: false,
|
||||
activeFilterType: '',
|
||||
appliedFilters: {
|
||||
assigned_agent_id: null,
|
||||
inbox_id: null,
|
||||
team_id: null,
|
||||
sla_policy_id: null,
|
||||
label_list: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
agents: 'agents/getAgents',
|
||||
inboxes: 'inboxes/getInboxes',
|
||||
teams: 'teams/getTeams',
|
||||
labels: 'labels/getLabels',
|
||||
sla: 'sla/getSLA',
|
||||
}),
|
||||
filterListMenuItems() {
|
||||
const filterTypes = [
|
||||
{ id: '1', name: this.$t('SLA_REPORTS.DROPDOWN.SLA'), type: 'sla' },
|
||||
{
|
||||
id: '2',
|
||||
name: this.$t('SLA_REPORTS.DROPDOWN.INBOXES'),
|
||||
type: 'inboxes',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: this.$t('SLA_REPORTS.DROPDOWN.AGENTS'),
|
||||
type: 'agents',
|
||||
},
|
||||
{ id: '4', name: this.$t('SLA_REPORTS.DROPDOWN.TEAMS'), type: 'teams' },
|
||||
{
|
||||
id: '5',
|
||||
name: this.$t('SLA_REPORTS.DROPDOWN.LABELS'),
|
||||
type: 'labels',
|
||||
},
|
||||
];
|
||||
// Filter out the active filters from the filter list
|
||||
// We only want to show the filters that are not already applied
|
||||
// In the add filter dropdown
|
||||
const activeFilters = Object.keys(this.appliedFilters).filter(
|
||||
key => this.appliedFilters[key]
|
||||
);
|
||||
const activeFilterTypes = activeFilters.map(key =>
|
||||
getFilterType(key, 'keyToType')
|
||||
);
|
||||
return filterTypes
|
||||
.filter(({ type }) => !activeFilterTypes.includes(type))
|
||||
.map(({ id, name, type }) => ({
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
options: buildFilterList(this[type], type),
|
||||
}));
|
||||
},
|
||||
activeFilters() {
|
||||
// Get the active filters from the applied filters
|
||||
// and return the filter name, type and options
|
||||
const activeKey = Object.keys(this.appliedFilters).filter(
|
||||
key => this.appliedFilters[key]
|
||||
);
|
||||
return activeKey.map(key => {
|
||||
const filterType = getFilterType(key, 'keyToType');
|
||||
const item = getActiveFilter(
|
||||
this[filterType],
|
||||
filterType,
|
||||
this.appliedFilters[key]
|
||||
);
|
||||
return {
|
||||
id: item.id,
|
||||
name: filterType === 'labels' ? item.title : item.name,
|
||||
type: filterType,
|
||||
options: buildFilterList(this[filterType], filterType),
|
||||
};
|
||||
});
|
||||
},
|
||||
hasActiveFilters() {
|
||||
return Object.values(this.appliedFilters).some(value => value !== null);
|
||||
},
|
||||
isAllFilterSelected() {
|
||||
return !this.filterListMenuItems.length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addFilter(item) {
|
||||
const { type, id, name } = item;
|
||||
const filterKey = getFilterType(type, 'typeToKey');
|
||||
this.appliedFilters[filterKey] = type === 'labels' ? name : id;
|
||||
this.$emit('filter-change', this.appliedFilters);
|
||||
this.resetDropdown();
|
||||
},
|
||||
removeFilter(type) {
|
||||
const filterKey = getFilterType(type, 'typeToKey');
|
||||
this.appliedFilters[filterKey] = null;
|
||||
this.$emit('filter-change', this.appliedFilters);
|
||||
},
|
||||
clearAllFilters() {
|
||||
this.appliedFilters = {
|
||||
assigned_agent_id: null,
|
||||
inbox_id: null,
|
||||
team_id: null,
|
||||
sla_policy_id: null,
|
||||
label_list: null,
|
||||
};
|
||||
this.$emit('filter-change', this.appliedFilters);
|
||||
this.resetDropdown();
|
||||
},
|
||||
showDropdown() {
|
||||
this.showSubDropdownMenu = false;
|
||||
this.showDropdownMenu = !this.showDropdownMenu;
|
||||
},
|
||||
closeDropdown() {
|
||||
this.showDropdownMenu = false;
|
||||
},
|
||||
openActiveFilterDropdown(filterType) {
|
||||
this.closeDropdown();
|
||||
this.activeFilterType = filterType;
|
||||
this.showSubDropdownMenu = !this.showSubDropdownMenu;
|
||||
},
|
||||
closeActiveFilterDropdown() {
|
||||
this.activeFilterType = '';
|
||||
this.showSubDropdownMenu = false;
|
||||
},
|
||||
resetDropdown() {
|
||||
this.closeDropdown();
|
||||
this.closeActiveFilterDropdown();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,22 +1,25 @@
|
||||
<template>
|
||||
<div class="flex flex-col md:flex-row justify-between mb-4">
|
||||
<div class="md:grid flex flex-col filter-container gap-3 w-full">
|
||||
<reports-filters-date-range @on-range-change="onDateRangeChange" />
|
||||
<woot-date-range-picker
|
||||
v-if="isDateRangeSelected"
|
||||
show-range
|
||||
class="no-margin auto-width"
|
||||
:value="customDateRange"
|
||||
:confirm-text="$t('REPORT.CUSTOM_DATE_RANGE.CONFIRM')"
|
||||
:placeholder="$t('REPORT.CUSTOM_DATE_RANGE.PLACEHOLDER')"
|
||||
@change="onCustomDateRangeChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col flex-wrap w-full gap-3 md:flex-row">
|
||||
<reports-filters-date-range
|
||||
class="sm:min-w-[200px] tiny h-8"
|
||||
@on-range-change="onDateRangeChange"
|
||||
/>
|
||||
<woot-date-range-picker
|
||||
v-if="isDateRangeSelected"
|
||||
show-range
|
||||
class="no-margin auto-width sm:min-w-[240px] small h-8"
|
||||
:value="customDateRange"
|
||||
:confirm-text="$t('REPORT.CUSTOM_DATE_RANGE.CONFIRM')"
|
||||
:placeholder="$t('REPORT.CUSTOM_DATE_RANGE.PLACEHOLDER')"
|
||||
@change="onCustomDateRangeChange"
|
||||
/>
|
||||
<SLA-filter @filter-change="emitFilterChange" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import WootDateRangePicker from 'dashboard/components/ui/DateRangePicker.vue';
|
||||
import ReportsFiltersDateRange from '../Filters/DateRange.vue';
|
||||
import SLAFilter from '../SLA/SLAFilter.vue';
|
||||
import subDays from 'date-fns/subDays';
|
||||
import { DATE_RANGE_OPTIONS } from '../../constants';
|
||||
import { getUnixStartOfDay, getUnixEndOfDay } from 'helpers/DateHelper';
|
||||
@@ -25,6 +28,7 @@ export default {
|
||||
components: {
|
||||
WootDateRangePicker,
|
||||
ReportsFiltersDateRange,
|
||||
SLAFilter,
|
||||
},
|
||||
|
||||
data() {
|
||||
@@ -70,8 +74,13 @@ export default {
|
||||
this.$emit('filter-change', {
|
||||
from,
|
||||
to,
|
||||
...this.selectedGroupByFilter,
|
||||
});
|
||||
},
|
||||
emitFilterChange(params) {
|
||||
this.selectedGroupByFilter = params;
|
||||
this.emitChange();
|
||||
},
|
||||
onDateRangeChange(selectedRange) {
|
||||
this.selectedDateRange = selectedRange;
|
||||
this.emitChange();
|
||||
@@ -83,9 +92,3 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filter-container {
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
export const buildFilterList = (items, type) =>
|
||||
// Build the filter list for the dropdown
|
||||
items.map(item => ({
|
||||
id: item.id,
|
||||
name: type === 'labels' ? item.title : item.name,
|
||||
type,
|
||||
}));
|
||||
|
||||
export const getActiveFilter = (filters, type, key) => {
|
||||
// Method is used to get the active filter from the filter list
|
||||
return filters.find(filterItem =>
|
||||
type === 'labels'
|
||||
? filterItem.title === key
|
||||
: filterItem.id.toString() === key.toString()
|
||||
);
|
||||
};
|
||||
|
||||
export const getFilterType = (input, direction) => {
|
||||
// Method is used to map the filter key to the filter type
|
||||
const filterMap = {
|
||||
keyToType: {
|
||||
assigned_agent_id: 'agents',
|
||||
inbox_id: 'inboxes',
|
||||
team_id: 'teams',
|
||||
sla_policy_id: 'sla',
|
||||
label_list: 'labels',
|
||||
},
|
||||
typeToKey: {
|
||||
agents: 'assigned_agent_id',
|
||||
inboxes: 'inbox_id',
|
||||
teams: 'team_id',
|
||||
sla: 'sla_policy_id',
|
||||
labels: 'label_list',
|
||||
},
|
||||
};
|
||||
return filterMap[direction][input];
|
||||
};
|
||||
Reference in New Issue
Block a user