feat: Refactor reports filters
This commit is contained in:
@@ -213,7 +213,7 @@ const closeDatePicker = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-on-clickaway="closeDatePicker" class="relative font-inter">
|
||||
<div class="relative flex-shrink-0 font-inter">
|
||||
<DatePickerButton
|
||||
:selected-start-date="selectedStartDate"
|
||||
:selected-end-date="selectedEndDate"
|
||||
@@ -222,6 +222,7 @@ const closeDatePicker = () => {
|
||||
/>
|
||||
<div
|
||||
v-if="showDatePicker"
|
||||
v-on-clickaway="closeDatePicker"
|
||||
class="flex absolute top-9 ltr:left-0 rtl:right-0 z-30 shadow-md select-none w-[880px] h-[490px] rounded-2xl bg-n-alpha-3 backdrop-blur-[100px] border-0 outline outline-1 outline-n-container"
|
||||
>
|
||||
<CalendarDateRange
|
||||
|
||||
@@ -46,13 +46,13 @@ const onClickSetView = (type, mode) => {
|
||||
xs
|
||||
icon="i-lucide-chevron-left"
|
||||
class="rtl:rotate-180"
|
||||
@click="onClickPrev(calendarType)"
|
||||
@click.stop="onClickPrev(calendarType)"
|
||||
/>
|
||||
<div class="flex items-center gap-1">
|
||||
<button
|
||||
v-if="firstButtonLabel"
|
||||
class="p-0 text-sm font-medium text-center text-n-slate-12 hover:text-n-brand"
|
||||
@click="onClickSetView(calendarType, viewMode)"
|
||||
@click.stop="onClickSetView(calendarType, viewMode)"
|
||||
>
|
||||
{{ firstButtonLabel }}
|
||||
</button>
|
||||
@@ -60,7 +60,7 @@ const onClickSetView = (type, mode) => {
|
||||
v-if="buttonLabel"
|
||||
class="p-0 text-sm font-medium text-center text-n-slate-12"
|
||||
:class="{ 'hover:text-n-brand': viewMode }"
|
||||
@click="onClickSetView(calendarType, YEAR)"
|
||||
@click.stop="onClickSetView(calendarType, YEAR)"
|
||||
>
|
||||
{{ buttonLabel }}
|
||||
</button>
|
||||
@@ -71,7 +71,7 @@ const onClickSetView = (type, mode) => {
|
||||
xs
|
||||
icon="i-lucide-chevron-right"
|
||||
class="rtl:rotate-180"
|
||||
@click="onClickNext(calendarType)"
|
||||
@click.stop="onClickNext(calendarType)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -78,7 +78,7 @@ const selectMonth = index => {
|
||||
'hover:bg-n-alpha-2 dark:hover:bg-n-solid-3':
|
||||
index !== activeMonthIndex,
|
||||
}"
|
||||
@click="selectMonth(index)"
|
||||
@click.stop="selectMonth(index)"
|
||||
>
|
||||
{{ month }}
|
||||
</button>
|
||||
|
||||
@@ -77,7 +77,7 @@ const selectYear = year => {
|
||||
'bg-n-brand text-white hover:bg-n-blue-10': year === activeYear,
|
||||
'hover:bg-n-alpha-2 dark:hover:bg-n-solid-3': year !== activeYear,
|
||||
}"
|
||||
@click="selectYear(year)"
|
||||
@click.stop="selectYear(year)"
|
||||
>
|
||||
{{ year }}
|
||||
</button>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { computed } from 'vue';
|
||||
import { dateRanges } from '../helpers/DatePickerHelper';
|
||||
import { format, isSameYear, isValid } from 'date-fns';
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
|
||||
const props = defineProps({
|
||||
selectedStartDate: Date,
|
||||
@@ -48,16 +49,22 @@ const openDatePicker = () => {
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="inline-flex relative items-center rounded-lg gap-2 py-1.5 px-3 h-8 bg-n-alpha-2 hover:bg-n-alpha-1 active:bg-n-alpha-1"
|
||||
class="inline-flex relative items-center rounded-lg gap-2 py-1.5 px-3 h-8 bg-n-alpha-2 hover:bg-n-alpha-1 active:bg-n-alpha-1 flex-shrink-0"
|
||||
@click="openDatePicker"
|
||||
>
|
||||
<fluent-icon class="text-n-slate-12" icon="calendar" size="16" />
|
||||
<span class="text-sm font-medium text-n-slate-12">
|
||||
<Icon
|
||||
icon="i-lucide-calendar-range"
|
||||
class="text-n-slate-11 size-3.5 flex-shrink-0"
|
||||
/>
|
||||
<span class="text-sm font-medium text-n-slate-12 truncate">
|
||||
{{ $t(activeDateRange) }}
|
||||
</span>
|
||||
<span class="text-sm font-medium text-n-slate-11">
|
||||
<span class="text-sm font-medium text-n-slate-11 truncate">
|
||||
{{ formatDateRange }}
|
||||
</span>
|
||||
<fluent-icon class="text-n-slate-12" icon="chevron-down" size="14" />
|
||||
<Icon
|
||||
icon="i-lucide-chevron-down"
|
||||
class="text-n-slate-12 size-4 flex-shrink-0"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -34,8 +34,8 @@ const value = defineModel({
|
||||
<input
|
||||
v-model="value"
|
||||
:placeholder="inputPlaceholder"
|
||||
type="text"
|
||||
class="w-full mb-0 text-sm !outline-0 bg-transparent text-n-slate-12 placeholder:text-n-slate-10 reset-base"
|
||||
type="search"
|
||||
class="w-full mb-0 text-sm !outline-0 !outline-none bg-transparent text-n-slate-12 placeholder:text-n-slate-10 reset-base"
|
||||
/>
|
||||
</div>
|
||||
<!-- Clear filter button -->
|
||||
|
||||
@@ -128,11 +128,16 @@
|
||||
},
|
||||
"AGENT_REPORTS": {
|
||||
"HEADER": "Agents Overview",
|
||||
"DESCRIPTION": "Easily track agent performance with key metrics such as conversations, response times, resolution times, and resolved cases. Click an agent’s name to learn more.",
|
||||
"DESCRIPTION": "Easily track agent performance with key metrics such as conversations, response times, resolution times, and resolved cases. Click an agent's name to learn more.",
|
||||
"LOADING_CHART": "Loading chart data...",
|
||||
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
||||
"DOWNLOAD_AGENT_REPORTS": "Download agent reports",
|
||||
"FILTER_DROPDOWN_LABEL": "Select Agent",
|
||||
"FILTERS": {
|
||||
"INPUT_PLACEHOLDER": {
|
||||
"AGENTS": "Search agents"
|
||||
}
|
||||
},
|
||||
"METRICS": {
|
||||
"CONVERSATIONS": {
|
||||
"NAME": "Conversations",
|
||||
@@ -201,6 +206,11 @@
|
||||
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
||||
"DOWNLOAD_LABEL_REPORTS": "Download label reports",
|
||||
"FILTER_DROPDOWN_LABEL": "Select Label",
|
||||
"FILTERS": {
|
||||
"INPUT_PLACEHOLDER": {
|
||||
"LABELS": "Search labels"
|
||||
}
|
||||
},
|
||||
"METRICS": {
|
||||
"CONVERSATIONS": {
|
||||
"NAME": "Conversations",
|
||||
@@ -271,6 +281,11 @@
|
||||
"FILTER_DROPDOWN_LABEL": "Select Inbox",
|
||||
"ALL_INBOXES": "All Inboxes",
|
||||
"SEARCH_INBOX": "Search Inbox",
|
||||
"FILTERS": {
|
||||
"INPUT_PLACEHOLDER": {
|
||||
"INBOXES": "Search inboxes"
|
||||
}
|
||||
},
|
||||
"METRICS": {
|
||||
"CONVERSATIONS": {
|
||||
"NAME": "Conversations",
|
||||
@@ -334,11 +349,19 @@
|
||||
},
|
||||
"TEAM_REPORTS": {
|
||||
"HEADER": "Team Overview",
|
||||
"DESCRIPTION": "Get a snapshot of your team’s performance with essential metrics, including conversations, response times, resolution times, and resolved cases. Click a team name for more details.",
|
||||
"DESCRIPTION": "Get a snapshot of your team's performance with essential metrics, including conversations, response times, resolution times, and resolved cases. Click a team name for more details.",
|
||||
"LOADING_CHART": "Loading chart data...",
|
||||
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
|
||||
"DOWNLOAD_TEAM_REPORTS": "Download team reports",
|
||||
"FILTER_DROPDOWN_LABEL": "Select Team",
|
||||
"FILTERS": {
|
||||
"ADD_FILTER": "Add filter",
|
||||
"CLEAR_ALL": "Clear all",
|
||||
"NO_FILTER": "No filters available",
|
||||
"INPUT_PLACEHOLDER": {
|
||||
"TEAMS": "Search teams"
|
||||
}
|
||||
},
|
||||
"METRICS": {
|
||||
"CONVERSATIONS": {
|
||||
"NAME": "Conversations",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import BotMetrics from './components/BotMetrics.vue';
|
||||
import ReportFilterSelector from './components/FilterSelector.vue';
|
||||
import ReportFilters from './components/ReportFilters.vue';
|
||||
import { GROUP_BY_FILTER } from './constants';
|
||||
import ReportContainer from './ReportContainer.vue';
|
||||
import { REPORTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
@@ -12,7 +12,7 @@ export default {
|
||||
components: {
|
||||
BotMetrics,
|
||||
ReportHeader,
|
||||
ReportFilterSelector,
|
||||
ReportFilters,
|
||||
ReportContainer,
|
||||
},
|
||||
data() {
|
||||
@@ -88,10 +88,10 @@ export default {
|
||||
<template>
|
||||
<ReportHeader :header-title="$t('BOT_REPORTS.HEADER')" />
|
||||
<div class="flex flex-col gap-4">
|
||||
<ReportFilterSelector
|
||||
:show-agents-filter="false"
|
||||
show-group-by-filter
|
||||
:show-business-hours-switch="false"
|
||||
<ReportFilters
|
||||
:show-entity-filter="false"
|
||||
show-group-by
|
||||
:show-business-hours="false"
|
||||
@filter-change="onFilterChange"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import V4Button from 'dashboard/components-next/button/Button.vue';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import ReportFilterSelector from './components/FilterSelector.vue';
|
||||
import ReportFilters from './components/ReportFilters.vue';
|
||||
import { GROUP_BY_FILTER } from './constants';
|
||||
import { REPORTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
import { generateFileName } from 'dashboard/helper/downloadHelper';
|
||||
@@ -22,7 +22,7 @@ export default {
|
||||
name: 'ConversationReports',
|
||||
components: {
|
||||
ReportHeader,
|
||||
ReportFilterSelector,
|
||||
ReportFilters,
|
||||
ReportContainer,
|
||||
V4Button,
|
||||
},
|
||||
@@ -115,10 +115,10 @@ export default {
|
||||
@click="downloadConversationReports"
|
||||
/>
|
||||
</ReportHeader>
|
||||
<div class="flex flex-col gap-3">
|
||||
<ReportFilterSelector
|
||||
:show-agents-filter="false"
|
||||
show-group-by-filter
|
||||
<div class="flex flex-col">
|
||||
<ReportFilters
|
||||
:show-entity-filter="false"
|
||||
show-group-by
|
||||
@filter-change="onFilterChange"
|
||||
/>
|
||||
<ReportContainer :group-by="groupBy" />
|
||||
|
||||
@@ -145,7 +145,7 @@ export default {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 px-6 py-5 shadow outline-1 outline outline-n-container rounded-xl bg-n-solid-2"
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 px-6 py-5 shadow outline-1 outline outline-n-container rounded-xl bg-n-solid-2 mt-4"
|
||||
>
|
||||
<div
|
||||
v-for="metric in metrics"
|
||||
|
||||
-228
@@ -1,228 +0,0 @@
|
||||
<script>
|
||||
import WootDateRangePicker from 'dashboard/components/ui/DateRangePicker.vue';
|
||||
import ReportsFiltersDateRange from './Filters/DateRange.vue';
|
||||
import ReportsFiltersDateGroupBy from './Filters/DateGroupBy.vue';
|
||||
import ReportsFiltersAgents from './Filters/Agents.vue';
|
||||
import ReportsFiltersLabels from './Filters/Labels.vue';
|
||||
import ReportsFiltersInboxes from './Filters/Inboxes.vue';
|
||||
import ReportsFiltersTeams from './Filters/Teams.vue';
|
||||
import ReportsFiltersRatings from './Filters/Ratings.vue';
|
||||
import subDays from 'date-fns/subDays';
|
||||
import { DATE_RANGE_OPTIONS } from '../constants';
|
||||
import { getUnixStartOfDay, getUnixEndOfDay } from 'helpers/DateHelper';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WootDateRangePicker,
|
||||
ReportsFiltersDateRange,
|
||||
ReportsFiltersDateGroupBy,
|
||||
ReportsFiltersAgents,
|
||||
ReportsFiltersLabels,
|
||||
ReportsFiltersInboxes,
|
||||
ReportsFiltersTeams,
|
||||
ReportsFiltersRatings,
|
||||
ToggleSwitch,
|
||||
},
|
||||
props: {
|
||||
showGroupByFilter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showAgentsFilter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showLabelsFilter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showInboxFilter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showRatingFilter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showTeamFilter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showBusinessHoursSwitch: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ['filterChange'],
|
||||
data() {
|
||||
return {
|
||||
// default value, need not be translated
|
||||
selectedDateRange: DATE_RANGE_OPTIONS.LAST_7_DAYS,
|
||||
selectedGroupByFilter: null,
|
||||
selectedLabel: null,
|
||||
selectedInbox: null,
|
||||
selectedTeam: null,
|
||||
selectedRating: null,
|
||||
selectedAgents: [],
|
||||
customDateRange: [new Date(), new Date()],
|
||||
businessHoursSelected: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isDateRangeSelected() {
|
||||
return (
|
||||
this.selectedDateRange.id === DATE_RANGE_OPTIONS.CUSTOM_DATE_RANGE.id
|
||||
);
|
||||
},
|
||||
isGroupByPossible() {
|
||||
return this.selectedDateRange.id !== DATE_RANGE_OPTIONS.LAST_7_DAYS.id;
|
||||
},
|
||||
to() {
|
||||
if (this.isDateRangeSelected) {
|
||||
return getUnixEndOfDay(this.customDateRange[1]);
|
||||
}
|
||||
return getUnixEndOfDay(new Date());
|
||||
},
|
||||
from() {
|
||||
if (this.isDateRangeSelected) {
|
||||
return getUnixStartOfDay(this.customDateRange[0]);
|
||||
}
|
||||
|
||||
const { offset } = this.selectedDateRange;
|
||||
const fromDate = subDays(new Date(), offset);
|
||||
return getUnixStartOfDay(fromDate);
|
||||
},
|
||||
validGroupOptions() {
|
||||
return this.selectedDateRange.groupByOptions;
|
||||
},
|
||||
validGroupBy() {
|
||||
if (!this.selectedGroupByFilter) {
|
||||
return this.validGroupOptions[0];
|
||||
}
|
||||
|
||||
const validIds = this.validGroupOptions.map(opt => opt.id);
|
||||
if (validIds.includes(this.selectedGroupByFilter.id)) {
|
||||
return this.selectedGroupByFilter;
|
||||
}
|
||||
return this.validGroupOptions[0];
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.emitChange();
|
||||
},
|
||||
methods: {
|
||||
emitChange() {
|
||||
const {
|
||||
from,
|
||||
to,
|
||||
selectedGroupByFilter: groupBy,
|
||||
businessHoursSelected: businessHours,
|
||||
selectedAgents,
|
||||
selectedLabel,
|
||||
selectedInbox,
|
||||
selectedTeam,
|
||||
selectedRating,
|
||||
} = this;
|
||||
this.$emit('filterChange', {
|
||||
from,
|
||||
to,
|
||||
groupBy,
|
||||
businessHours,
|
||||
selectedAgents,
|
||||
selectedLabel,
|
||||
selectedInbox,
|
||||
selectedTeam,
|
||||
selectedRating,
|
||||
});
|
||||
},
|
||||
onDateRangeChange(selectedRange) {
|
||||
this.selectedDateRange = selectedRange;
|
||||
this.selectedGroupByFilter = this.validGroupBy;
|
||||
this.emitChange();
|
||||
},
|
||||
onCustomDateRangeChange(value) {
|
||||
this.customDateRange = value;
|
||||
this.selectedGroupByFilter = this.validGroupBy;
|
||||
this.emitChange();
|
||||
},
|
||||
onGroupingChange(payload) {
|
||||
this.selectedGroupByFilter = payload;
|
||||
this.emitChange();
|
||||
},
|
||||
handleAgentsFilterSelection(selectedAgents) {
|
||||
this.selectedAgents = selectedAgents;
|
||||
this.emitChange();
|
||||
},
|
||||
handleLabelsFilterSelection(selectedLabel) {
|
||||
this.selectedLabel = selectedLabel;
|
||||
this.emitChange();
|
||||
},
|
||||
handleInboxFilterSelection(selectedInbox) {
|
||||
this.selectedInbox = selectedInbox;
|
||||
this.emitChange();
|
||||
},
|
||||
handleTeamFilterSelection(selectedTeam) {
|
||||
this.selectedTeam = selectedTeam;
|
||||
this.emitChange();
|
||||
},
|
||||
handleRatingFilterSelection(selectedRating) {
|
||||
this.selectedRating = selectedRating;
|
||||
this.emitChange();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col justify-between gap-3 md:flex-row">
|
||||
<div
|
||||
class="w-full grid gap-y-2 gap-x-1.5 grid-cols-[repeat(auto-fill,minmax(250px,1fr))]"
|
||||
>
|
||||
<ReportsFiltersDateRange @on-range-change="onDateRangeChange" />
|
||||
<WootDateRangePicker
|
||||
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"
|
||||
/>
|
||||
<ReportsFiltersDateGroupBy
|
||||
v-if="showGroupByFilter && isGroupByPossible"
|
||||
:valid-group-options="validGroupOptions"
|
||||
:selected-option="selectedGroupByFilter"
|
||||
@on-grouping-change="onGroupingChange"
|
||||
/>
|
||||
<ReportsFiltersAgents
|
||||
v-if="showAgentsFilter"
|
||||
@agents-filter-selection="handleAgentsFilterSelection"
|
||||
/>
|
||||
<ReportsFiltersLabels
|
||||
v-if="showLabelsFilter"
|
||||
@labels-filter-selection="handleLabelsFilterSelection"
|
||||
/>
|
||||
<ReportsFiltersTeams
|
||||
v-if="showTeamFilter"
|
||||
@team-filter-selection="handleTeamFilterSelection"
|
||||
/>
|
||||
<ReportsFiltersInboxes
|
||||
v-if="showInboxFilter"
|
||||
@inbox-filter-selection="handleInboxFilterSelection"
|
||||
/>
|
||||
<ReportsFiltersRatings
|
||||
v-if="showRatingFilter"
|
||||
@rating-filter-selection="handleRatingFilterSelection"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showBusinessHoursSwitch" class="flex items-center">
|
||||
<span class="mx-2 text-sm whitespace-nowrap">
|
||||
{{ $t('REPORT.BUSINESS_HOURS') }}
|
||||
</span>
|
||||
<span>
|
||||
<ToggleSwitch v-model="businessHoursSelected" @change="emitChange" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'ReportsFiltersAgents',
|
||||
emits: ['agentsFilterSelection'],
|
||||
data() {
|
||||
return {
|
||||
selectedOptions: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
options: 'agents/getAgents',
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('agents/get');
|
||||
},
|
||||
methods: {
|
||||
handleInput() {
|
||||
this.$emit('agentsFilterSelection', this.selectedOptions);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="multiselect-wrap--small">
|
||||
<multiselect
|
||||
v-model="selectedOptions"
|
||||
class="no-margin"
|
||||
:options="options"
|
||||
track-by="id"
|
||||
label="name"
|
||||
multiple
|
||||
:close-on-select="false"
|
||||
:clear-on-select="false"
|
||||
hide-selected
|
||||
:placeholder="$t('CSAT_REPORTS.FILTERS.AGENTS.PLACEHOLDER')"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
:deselect-label="$t('FORMS.MULTISELECT.ENTER_TO_REMOVE')"
|
||||
@update:model-value="handleInput"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
<script>
|
||||
import { GROUP_BY_OPTIONS } from '../../constants';
|
||||
|
||||
export default {
|
||||
name: 'ReportsFiltersDateGroupBy',
|
||||
props: {
|
||||
validGroupOptions: {
|
||||
type: Array,
|
||||
default: () => [GROUP_BY_OPTIONS.DAY],
|
||||
},
|
||||
selectedOption: {
|
||||
type: Object,
|
||||
default: () => GROUP_BY_OPTIONS.DAY,
|
||||
},
|
||||
},
|
||||
emits: ['onGroupingChange'],
|
||||
data() {
|
||||
return {
|
||||
currentSelectedFilter: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
translatedOptions() {
|
||||
return this.validGroupOptions.map(option => ({
|
||||
...option,
|
||||
groupBy: this.$t(option.translationKey),
|
||||
}));
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedOption: {
|
||||
handler() {
|
||||
this.currentSelectedFilter = {
|
||||
...this.selectedOption,
|
||||
groupBy: this.$t(this.selectedOption.translationKey),
|
||||
};
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
changeFilterSelection(selectedFilter) {
|
||||
this.groupByOptions = this.$emit('onGroupingChange', selectedFilter);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="multiselect-wrap--small">
|
||||
<p aria-hidden="true" class="hidden">
|
||||
{{ $t('REPORT.GROUP_BY_FILTER_DROPDOWN_LABEL') }}
|
||||
</p>
|
||||
<multiselect
|
||||
v-model="currentSelectedFilter"
|
||||
class="no-margin"
|
||||
track-by="id"
|
||||
label="groupBy"
|
||||
:placeholder="$t('REPORT.GROUP_BY_FILTER_DROPDOWN_LABEL')"
|
||||
:options="translatedOptions"
|
||||
:allow-empty="false"
|
||||
:show-labels="false"
|
||||
@select="changeFilterSelection"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { DATE_RANGE_OPTIONS } from '../../constants';
|
||||
|
||||
const emit = defineEmits(['onRangeChange']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const options = computed(() =>
|
||||
Object.values(DATE_RANGE_OPTIONS).map(option => ({
|
||||
...option,
|
||||
name: t(option.translationKey),
|
||||
}))
|
||||
);
|
||||
|
||||
const selectedId = ref(Object.values(DATE_RANGE_OPTIONS)[0].id);
|
||||
|
||||
const selectedOption = computed({
|
||||
get() {
|
||||
return options.value.find(o => o.id === selectedId.value);
|
||||
},
|
||||
set(val) {
|
||||
selectedId.value = val.id;
|
||||
},
|
||||
});
|
||||
|
||||
const updateRange = range => {
|
||||
selectedOption.value = range;
|
||||
emit('onRangeChange', range);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="multiselect-wrap--small">
|
||||
<multiselect
|
||||
v-model="selectedOption"
|
||||
class="no-margin"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="$t('FORMS.MULTISELECT.SELECT_ONE')"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:options="options"
|
||||
:searchable="false"
|
||||
:allow-empty="false"
|
||||
@select="updateRange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'ReportsFiltersInboxes',
|
||||
emits: ['inboxFilterSelection'],
|
||||
data() {
|
||||
return {
|
||||
selectedOption: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
options: 'inboxes/getInboxes',
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('inboxes/get');
|
||||
},
|
||||
methods: {
|
||||
handleInput() {
|
||||
this.$emit('inboxFilterSelection', this.selectedOption);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="multiselect-wrap--small">
|
||||
<multiselect
|
||||
v-model="selectedOption"
|
||||
class="no-margin"
|
||||
:placeholder="$t('INBOX_REPORTS.FILTER_DROPDOWN_LABEL')"
|
||||
label="name"
|
||||
track-by="id"
|
||||
:options="options"
|
||||
:option-height="24"
|
||||
:show-labels="false"
|
||||
@update:model-value="handleInput"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'ReportsFiltersLabels',
|
||||
emits: ['labelsFilterSelection'],
|
||||
data() {
|
||||
return {
|
||||
selectedOption: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
options: 'labels/getLabels',
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('labels/get');
|
||||
},
|
||||
methods: {
|
||||
handleInput() {
|
||||
this.$emit('labelsFilterSelection', this.selectedOption);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="multiselect-wrap--small">
|
||||
<multiselect
|
||||
v-model="selectedOption"
|
||||
class="no-margin"
|
||||
:placeholder="$t('LABEL_REPORTS.FILTER_DROPDOWN_LABEL')"
|
||||
label="title"
|
||||
track-by="id"
|
||||
:options="options"
|
||||
:option-height="24"
|
||||
:show-labels="false"
|
||||
@update:model-value="handleInput"
|
||||
>
|
||||
<template #singleLabel="props">
|
||||
<div class="flex items-center min-w-0 gap-2">
|
||||
<div
|
||||
:style="{ backgroundColor: props.option.color }"
|
||||
class="w-5 h-5 rounded-full"
|
||||
/>
|
||||
<span class="my-0 text-n-slate-12">
|
||||
{{ props.option.title }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #option="props">
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
:style="{ backgroundColor: props.option.color }"
|
||||
class="flex-shrink-0 w-5 h-5 border border-solid rounded-full border-n-weak"
|
||||
/>
|
||||
|
||||
<span class="my-0 text-n-slate-12 truncate min-w-0">
|
||||
{{ props.option.title }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
</template>
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
<script>
|
||||
import { CSAT_RATINGS } from 'shared/constants/messages';
|
||||
|
||||
export default {
|
||||
name: 'ReportFiltersRatings',
|
||||
emits: ['ratingFilterSelection'],
|
||||
data() {
|
||||
const translatedOptions = CSAT_RATINGS.reverse().map(option => ({
|
||||
...option,
|
||||
label: this.$t(option.translationKey),
|
||||
}));
|
||||
|
||||
return {
|
||||
selectedOption: null,
|
||||
options: translatedOptions,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleInput(selectedRating) {
|
||||
this.$emit('ratingFilterSelection', selectedRating);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="multiselect-wrap--small">
|
||||
<multiselect
|
||||
v-model="selectedOption"
|
||||
class="no-margin"
|
||||
:option-height="24"
|
||||
:placeholder="$t('FORMS.MULTISELECT.SELECT_ONE')"
|
||||
:options="options"
|
||||
:show-labels="false"
|
||||
track-by="value"
|
||||
label="label"
|
||||
@update:model-value="handleInput"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'ReportsFiltersTeams',
|
||||
emits: ['teamFilterSelection'],
|
||||
data() {
|
||||
return {
|
||||
selectedOption: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
options: 'teams/getTeams',
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('teams/get');
|
||||
},
|
||||
methods: {
|
||||
handleInput() {
|
||||
this.$emit('teamFilterSelection', this.selectedOption);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="multiselect-wrap--small">
|
||||
<multiselect
|
||||
v-model="selectedOption"
|
||||
class="no-margin"
|
||||
:placeholder="$t('TEAM_REPORTS.FILTER_DROPDOWN_LABEL')"
|
||||
label="name"
|
||||
track-by="id"
|
||||
:options="options"
|
||||
:option-height="24"
|
||||
:show-labels="false"
|
||||
@update:model-value="handleInput"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
+7
-3
@@ -8,8 +8,8 @@ const props = defineProps({
|
||||
required: true,
|
||||
},
|
||||
id: {
|
||||
type: Number,
|
||||
required: true,
|
||||
type: [Number, null],
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
@@ -35,6 +35,10 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showClearFilter: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -60,7 +64,7 @@ const closeDropdown = () => emit('closeDropdown');
|
||||
<FilterListDropdown
|
||||
v-if="options"
|
||||
v-on-clickaway="closeDropdown"
|
||||
show-clear-filter
|
||||
:show-clear-filter="showClearFilter"
|
||||
:list-items="options"
|
||||
:active-filter-id="id"
|
||||
:input-placeholder="placeholder"
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { getUnixStartOfDay, getUnixEndOfDay } from 'helpers/DateHelper';
|
||||
import subDays from 'date-fns/subDays';
|
||||
import WootDatePicker from 'dashboard/components/ui/DatePicker/DatePicker.vue';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
|
||||
const emit = defineEmits(['filterChange']);
|
||||
|
||||
const customDateRange = ref([subDays(new Date(), 6), new Date()]);
|
||||
const businessHoursSelected = ref(false);
|
||||
|
||||
const emitChange = () => {
|
||||
emit('filterChange', {
|
||||
from: getUnixStartOfDay(customDateRange.value[0]),
|
||||
to: getUnixEndOfDay(customDateRange.value[1]),
|
||||
businessHours: businessHoursSelected.value,
|
||||
});
|
||||
};
|
||||
|
||||
const onDateRangeChange = value => {
|
||||
customDateRange.value = value;
|
||||
emitChange();
|
||||
};
|
||||
|
||||
const onBusinessHoursToggle = () => {
|
||||
emitChange();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
emitChange();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col justify-between gap-3 md:flex-row">
|
||||
<div class="flex flex-col flex-wrap items-start gap-2 md:flex-row">
|
||||
<WootDatePicker @date-range-changed="onDateRangeChange" />
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="mx-2 text-sm whitespace-nowrap">
|
||||
{{ $t('REPORT.BUSINESS_HOURS') }}
|
||||
</span>
|
||||
<span>
|
||||
<ToggleSwitch
|
||||
v-model="businessHoursSelected"
|
||||
@change="onBusinessHoursToggle"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
+285
-328
@@ -1,349 +1,306 @@
|
||||
<script>
|
||||
import endOfDay from 'date-fns/endOfDay';
|
||||
import getUnixTime from 'date-fns/getUnixTime';
|
||||
import startOfDay from 'date-fns/startOfDay';
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStore } from 'vuex';
|
||||
import { getUnixStartOfDay, getUnixEndOfDay } from 'helpers/DateHelper';
|
||||
import subDays from 'date-fns/subDays';
|
||||
import Avatar from 'next/avatar/Avatar.vue';
|
||||
import WootDateRangePicker from 'dashboard/components/ui/DateRangePicker.vue';
|
||||
import differenceInDays from 'date-fns/differenceInDays';
|
||||
import ActiveFilterChip from './Filters/v3/ActiveFilterChip.vue';
|
||||
import WootDatePicker from 'dashboard/components/ui/DatePicker/DatePicker.vue';
|
||||
import ToggleSwitch from 'dashboard/components-next/switch/Switch.vue';
|
||||
|
||||
import { GROUP_BY_FILTER } from '../constants';
|
||||
const CUSTOM_DATE_RANGE_ID = 5;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WootDateRangePicker,
|
||||
Avatar,
|
||||
ToggleSwitch,
|
||||
const props = defineProps({
|
||||
filterType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
validator: value =>
|
||||
['teams', 'inboxes', 'labels', 'agents', ''].includes(value),
|
||||
},
|
||||
props: {
|
||||
currentFilter: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
filterItemsList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
groupByFilterItemsList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'agent',
|
||||
},
|
||||
selectedGroupByFilter: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
selectedItem: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
emits: [
|
||||
'businessHoursToggle',
|
||||
'dateRangeChange',
|
||||
'filterChange',
|
||||
'groupByFilterChange',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
currentSelectedFilter: this.currentFilter || null,
|
||||
currentDateRangeSelection: {
|
||||
id: 0,
|
||||
name: this.$t('REPORT.DATE_RANGE_OPTIONS.LAST_7_DAYS'),
|
||||
},
|
||||
customDateRange: [new Date(), new Date()],
|
||||
currentSelectedGroupByFilter: null,
|
||||
businessHoursSelected: false,
|
||||
};
|
||||
showGroupBy: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
computed: {
|
||||
dateRange() {
|
||||
return [
|
||||
{ id: 0, name: this.$t('REPORT.DATE_RANGE_OPTIONS.LAST_7_DAYS') },
|
||||
{ id: 1, name: this.$t('REPORT.DATE_RANGE_OPTIONS.LAST_30_DAYS') },
|
||||
{ id: 2, name: this.$t('REPORT.DATE_RANGE_OPTIONS.LAST_3_MONTHS') },
|
||||
{ id: 3, name: this.$t('REPORT.DATE_RANGE_OPTIONS.LAST_6_MONTHS') },
|
||||
{ id: 4, name: this.$t('REPORT.DATE_RANGE_OPTIONS.LAST_YEAR') },
|
||||
{ id: 5, name: this.$t('REPORT.DATE_RANGE_OPTIONS.CUSTOM_DATE_RANGE') },
|
||||
];
|
||||
},
|
||||
isDateRangeSelected() {
|
||||
return this.currentDateRangeSelection.id === CUSTOM_DATE_RANGE_ID;
|
||||
},
|
||||
to() {
|
||||
if (this.isDateRangeSelected) {
|
||||
return this.toCustomDate(this.customDateRange[1]);
|
||||
}
|
||||
return this.toCustomDate(new Date());
|
||||
},
|
||||
from() {
|
||||
if (this.isDateRangeSelected) {
|
||||
return this.fromCustomDate(this.customDateRange[0]);
|
||||
}
|
||||
const dateRange = {
|
||||
0: 6,
|
||||
1: 29,
|
||||
2: 89,
|
||||
3: 179,
|
||||
4: 364,
|
||||
};
|
||||
const diff = dateRange[this.currentDateRangeSelection.id];
|
||||
const fromDate = subDays(new Date(), diff);
|
||||
return this.fromCustomDate(fromDate);
|
||||
},
|
||||
multiselectLabel() {
|
||||
const typeLabels = {
|
||||
agent: this.$t('AGENT_REPORTS.FILTER_DROPDOWN_LABEL'),
|
||||
label: this.$t('LABEL_REPORTS.FILTER_DROPDOWN_LABEL'),
|
||||
inbox: this.$t('INBOX_REPORTS.FILTER_DROPDOWN_LABEL'),
|
||||
team: this.$t('TEAM_REPORTS.FILTER_DROPDOWN_LABEL'),
|
||||
};
|
||||
return typeLabels[this.type] || this.$t('FORMS.MULTISELECT.SELECT_ONE');
|
||||
},
|
||||
groupBy() {
|
||||
if (this.isDateRangeSelected) {
|
||||
return GROUP_BY_FILTER[4].period;
|
||||
}
|
||||
const groupRange = {
|
||||
0: GROUP_BY_FILTER[1].period,
|
||||
1: GROUP_BY_FILTER[2].period,
|
||||
2: GROUP_BY_FILTER[3].period,
|
||||
3: GROUP_BY_FILTER[3].period,
|
||||
4: GROUP_BY_FILTER[4].period,
|
||||
};
|
||||
return groupRange[this.currentDateRangeSelection.id];
|
||||
},
|
||||
notLast7Days() {
|
||||
return this.groupBy !== GROUP_BY_FILTER[1].period;
|
||||
},
|
||||
showBusinessHours: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
watch: {
|
||||
filterItemsList(val) {
|
||||
this.currentSelectedFilter = !this.currentFilter
|
||||
? val[0]
|
||||
: this.currentFilter;
|
||||
this.changeFilterSelection();
|
||||
},
|
||||
groupByFilterItemsList() {
|
||||
this.currentSelectedGroupByFilter = this.selectedGroupByFilter;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.onDateRangeChange();
|
||||
},
|
||||
methods: {
|
||||
onDateRangeChange() {
|
||||
this.$emit('dateRangeChange', {
|
||||
from: this.from,
|
||||
to: this.to,
|
||||
groupBy: this.groupBy,
|
||||
});
|
||||
},
|
||||
onBusinessHoursToggle() {
|
||||
this.$emit('businessHoursToggle', this.businessHoursSelected);
|
||||
},
|
||||
fromCustomDate(date) {
|
||||
return getUnixTime(startOfDay(date));
|
||||
},
|
||||
toCustomDate(date) {
|
||||
return getUnixTime(endOfDay(date));
|
||||
},
|
||||
changeDateSelection(selectedRange) {
|
||||
this.currentDateRangeSelection = selectedRange;
|
||||
this.onDateRangeChange();
|
||||
},
|
||||
changeFilterSelection() {
|
||||
this.$emit('filterChange', this.currentSelectedFilter);
|
||||
},
|
||||
onChange(value) {
|
||||
this.customDateRange = value;
|
||||
this.onDateRangeChange();
|
||||
},
|
||||
changeGroupByFilterSelection() {
|
||||
this.$emit('groupByFilterChange', this.currentSelectedGroupByFilter);
|
||||
},
|
||||
showEntityFilter: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['filterChange']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const store = useStore();
|
||||
|
||||
const buildReportFilterList = (items, type) => {
|
||||
if (!Array.isArray(items)) return [];
|
||||
|
||||
return items.map(item => ({
|
||||
id: item.id,
|
||||
name: item.name || item.title,
|
||||
type,
|
||||
}));
|
||||
};
|
||||
|
||||
const getReportFilterKey = filterType => {
|
||||
const keyMap = {
|
||||
teams: 'team_id',
|
||||
inboxes: 'inbox_id',
|
||||
labels: 'label_id',
|
||||
agents: 'agent_ids',
|
||||
};
|
||||
return keyMap[filterType] || '';
|
||||
};
|
||||
|
||||
const getFilterKey = () => getReportFilterKey(props.filterType);
|
||||
|
||||
const showSubDropdownMenu = ref(false);
|
||||
const showGroupByDropdown = ref(false);
|
||||
const activeFilterType = ref('');
|
||||
const customDateRange = ref([subDays(new Date(), 6), new Date()]);
|
||||
const businessHoursSelected = ref(false);
|
||||
const groupBy = ref(GROUP_BY_FILTER[1]);
|
||||
const groupByfilterItemsList = ref([{ id: 1, name: 'Day' }]);
|
||||
|
||||
const appliedFilters = ref(
|
||||
props.showEntityFilter
|
||||
? { [getFilterKey()]: props.selectedItem?.id || null }
|
||||
: {}
|
||||
);
|
||||
|
||||
const filterSource = computed(() => {
|
||||
const sources = {
|
||||
teams: store.getters['teams/getTeams'],
|
||||
inboxes: store.getters['inboxes/getInboxes'],
|
||||
labels: store.getters['labels/getLabels'],
|
||||
agents: store.getters['agents/getAgents'],
|
||||
};
|
||||
return sources[props.filterType] || [];
|
||||
});
|
||||
|
||||
const from = computed(() => getUnixStartOfDay(customDateRange.value[0]));
|
||||
const to = computed(() => getUnixEndOfDay(customDateRange.value[1]));
|
||||
|
||||
const daysDifference = computed(() => {
|
||||
return differenceInDays(customDateRange.value[1], customDateRange.value[0]);
|
||||
});
|
||||
|
||||
const isGroupByPossible = computed(() => {
|
||||
return props.showGroupBy && daysDifference.value >= 29;
|
||||
});
|
||||
|
||||
const GROUP_BY_OPTIONS = computed(() => ({
|
||||
WEEK: [
|
||||
{ id: 1, name: t('REPORT.GROUPING_OPTIONS.DAY') },
|
||||
{ id: 2, name: t('REPORT.GROUPING_OPTIONS.WEEK') },
|
||||
],
|
||||
MONTH: [
|
||||
{ id: 1, name: t('REPORT.GROUPING_OPTIONS.DAY') },
|
||||
{ id: 2, name: t('REPORT.GROUPING_OPTIONS.WEEK') },
|
||||
{ id: 3, name: t('REPORT.GROUPING_OPTIONS.MONTH') },
|
||||
],
|
||||
YEAR: [
|
||||
{ id: 2, name: t('REPORT.GROUPING_OPTIONS.WEEK') },
|
||||
{ id: 3, name: t('REPORT.GROUPING_OPTIONS.MONTH') },
|
||||
{ id: 4, name: t('REPORT.GROUPING_OPTIONS.YEAR') },
|
||||
],
|
||||
}));
|
||||
|
||||
const fetchFilterItems = () => {
|
||||
const days = daysDifference.value;
|
||||
if (days >= 364) return GROUP_BY_OPTIONS.value.YEAR;
|
||||
if (days >= 90) return GROUP_BY_OPTIONS.value.MONTH;
|
||||
if (days >= 29) return GROUP_BY_OPTIONS.value.WEEK;
|
||||
return GROUP_BY_OPTIONS.value.WEEK;
|
||||
};
|
||||
|
||||
const filterOptions = computed(() =>
|
||||
buildReportFilterList(filterSource.value, props.filterType)
|
||||
);
|
||||
|
||||
const filterPlaceholder = computed(() => {
|
||||
const placeholders = {
|
||||
teams: 'TEAM_REPORTS.FILTERS.INPUT_PLACEHOLDER.TEAMS',
|
||||
inboxes: 'INBOX_REPORTS.FILTERS.INPUT_PLACEHOLDER.INBOXES',
|
||||
labels: 'LABEL_REPORTS.FILTERS.INPUT_PLACEHOLDER.LABELS',
|
||||
agents: 'AGENT_REPORTS.FILTERS.INPUT_PLACEHOLDER.AGENTS',
|
||||
};
|
||||
return t(placeholders[props.filterType] || '');
|
||||
});
|
||||
|
||||
const defaultFilterLabel = computed(() => {
|
||||
const labelKeys = {
|
||||
teams: 'TEAM_REPORTS.FILTER_DROPDOWN_LABEL',
|
||||
inboxes: 'INBOX_REPORTS.FILTER_DROPDOWN_LABEL',
|
||||
labels: 'LABEL_REPORTS.FILTER_DROPDOWN_LABEL',
|
||||
agents: 'AGENT_REPORTS.FILTER_DROPDOWN_LABEL',
|
||||
};
|
||||
return t(labelKeys[props.filterType] || 'FORMS.MULTISELECT.SELECT_ONE');
|
||||
});
|
||||
|
||||
const selectedFilterName = computed(() => {
|
||||
const filterKey = getFilterKey();
|
||||
const selectedId = appliedFilters.value[filterKey];
|
||||
|
||||
if (!selectedId) {
|
||||
return defaultFilterLabel.value;
|
||||
}
|
||||
|
||||
const selectedItem = filterOptions.value.find(item => item.id === selectedId);
|
||||
return selectedItem?.name || defaultFilterLabel.value;
|
||||
});
|
||||
|
||||
const emitChange = () => {
|
||||
const payload = {
|
||||
from: from.value,
|
||||
to: to.value,
|
||||
businessHours: businessHoursSelected.value,
|
||||
};
|
||||
|
||||
if (props.showGroupBy && isGroupByPossible.value) {
|
||||
payload.groupBy = groupBy.value;
|
||||
}
|
||||
|
||||
if (props.showEntityFilter) {
|
||||
const filterKey = getFilterKey();
|
||||
const selectedValue = appliedFilters.value[filterKey];
|
||||
|
||||
if (selectedValue) {
|
||||
payload[props.filterType] =
|
||||
props.filterType === 'agents'
|
||||
? [{ id: selectedValue }]
|
||||
: { id: selectedValue };
|
||||
}
|
||||
}
|
||||
|
||||
emit('filterChange', payload);
|
||||
};
|
||||
|
||||
const closeActiveFilterDropdown = () => {
|
||||
showSubDropdownMenu.value = false;
|
||||
activeFilterType.value = '';
|
||||
};
|
||||
|
||||
const openActiveFilterDropdown = filterType => {
|
||||
showGroupByDropdown.value = false;
|
||||
activeFilterType.value = filterType;
|
||||
showSubDropdownMenu.value = !showSubDropdownMenu.value;
|
||||
};
|
||||
|
||||
const addFilter = item => {
|
||||
const filterKey = getFilterKey();
|
||||
appliedFilters.value[filterKey] = item.id;
|
||||
closeActiveFilterDropdown();
|
||||
emitChange();
|
||||
};
|
||||
|
||||
const removeFilter = () => {
|
||||
const filterKey = getFilterKey();
|
||||
appliedFilters.value[filterKey] = null;
|
||||
emitChange();
|
||||
};
|
||||
|
||||
const onDateRangeChange = value => {
|
||||
customDateRange.value = value;
|
||||
groupByfilterItemsList.value = fetchFilterItems();
|
||||
const filterItems = groupByfilterItemsList.value.filter(
|
||||
item => item.id === groupBy.value.id
|
||||
);
|
||||
if (filterItems.length === 0) {
|
||||
groupBy.value = GROUP_BY_FILTER[groupByfilterItemsList.value[0].id];
|
||||
}
|
||||
emitChange();
|
||||
};
|
||||
|
||||
const onBusinessHoursToggle = () => {
|
||||
emitChange();
|
||||
};
|
||||
|
||||
const onGroupByFilterChange = payload => {
|
||||
groupBy.value = GROUP_BY_FILTER[payload.id];
|
||||
showGroupByDropdown.value = false;
|
||||
emitChange();
|
||||
};
|
||||
|
||||
const toggleGroupByDropdown = () => {
|
||||
showGroupByDropdown.value = !showGroupByDropdown.value;
|
||||
};
|
||||
|
||||
const closeGroupByDropdown = () => {
|
||||
showGroupByDropdown.value = false;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
groupByfilterItemsList.value = fetchFilterItems();
|
||||
emitChange();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-y-0.5 gap-x-2">
|
||||
<div v-if="type === 'agent'" class="multiselect-wrap--small">
|
||||
<p class="mb-2 text-xs font-medium">
|
||||
{{ $t('AGENT_REPORTS.FILTER_DROPDOWN_LABEL') }}
|
||||
</p>
|
||||
<multiselect
|
||||
v-model="currentSelectedFilter"
|
||||
:placeholder="multiselectLabel"
|
||||
label="name"
|
||||
track-by="id"
|
||||
:options="filterItemsList"
|
||||
:option-height="24"
|
||||
:show-labels="false"
|
||||
@update:model-value="changeFilterSelection"
|
||||
>
|
||||
<template #singleLabel="props">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<Avatar
|
||||
:src="props.option.thumbnail"
|
||||
:status="props.option.availability_status"
|
||||
:name="props.option.name"
|
||||
:size="22"
|
||||
hide-offline-status
|
||||
rounded-full
|
||||
/>
|
||||
<span class="my-0 text-n-slate-12 truncate">{{
|
||||
props.option.name
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #options="props">
|
||||
<div class="flex items-center gap-2">
|
||||
<Avatar
|
||||
:src="props.option.thumbnail"
|
||||
:status="props.option.availability_status"
|
||||
:name="props.option.name"
|
||||
:size="22"
|
||||
hide-offline-status
|
||||
rounded-full
|
||||
/>
|
||||
<p class="my-0 text-n-slate-12">
|
||||
{{ props.option.name }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div class="flex flex-col w-full gap-3 lg:flex-row">
|
||||
<WootDatePicker @date-range-changed="onDateRangeChange" />
|
||||
|
||||
<div v-else-if="type === 'label'" class="multiselect-wrap--small">
|
||||
<p class="mb-2 text-xs font-medium">
|
||||
{{ $t('LABEL_REPORTS.FILTER_DROPDOWN_LABEL') }}
|
||||
</p>
|
||||
<multiselect
|
||||
v-model="currentSelectedFilter"
|
||||
:placeholder="multiselectLabel"
|
||||
label="title"
|
||||
track-by="id"
|
||||
:options="filterItemsList"
|
||||
:option-height="24"
|
||||
:show-labels="false"
|
||||
@update:model-value="changeFilterSelection"
|
||||
>
|
||||
<template #singleLabel="props">
|
||||
<div class="flex items-center min-w-0 gap-2">
|
||||
<div
|
||||
:style="{ backgroundColor: props.option.color }"
|
||||
class="w-5 h-5 rounded-full"
|
||||
/>
|
||||
|
||||
<span class="my-0 text-n-slate-12 truncate">
|
||||
{{ props.option.title }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #option="props">
|
||||
<div class="flex items-center min-w-0 gap-2">
|
||||
<div
|
||||
:style="{ backgroundColor: props.option.color }"
|
||||
class="flex-shrink-0 w-5 h-5 border border-solid rounded-full border-n-weak"
|
||||
/>
|
||||
|
||||
<span class="my-0 text-n-slate-12 truncate">
|
||||
{{ props.option.title }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
|
||||
<div v-else class="multiselect-wrap--small">
|
||||
<p class="mb-2 text-xs font-medium">
|
||||
<template v-if="type === 'inbox'">
|
||||
{{ $t('INBOX_REPORTS.FILTER_DROPDOWN_LABEL') }}
|
||||
</template>
|
||||
<template v-else-if="type === 'team'">
|
||||
{{ $t('TEAM_REPORTS.FILTER_DROPDOWN_LABEL') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ $t('FORMS.MULTISELECT.SELECT_ONE') }}
|
||||
</template>
|
||||
</p>
|
||||
<multiselect
|
||||
v-model="currentSelectedFilter"
|
||||
track-by="id"
|
||||
label="name"
|
||||
:placeholder="multiselectLabel"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:options="filterItemsList"
|
||||
:searchable="false"
|
||||
:allow-empty="false"
|
||||
@update:model-value="changeFilterSelection"
|
||||
<div class="flex gap-2 items-center w-full">
|
||||
<ActiveFilterChip
|
||||
v-if="showEntityFilter"
|
||||
:id="appliedFilters[getFilterKey()]"
|
||||
:name="selectedFilterName"
|
||||
:type="filterType"
|
||||
:options="filterOptions"
|
||||
:active-filter-type="activeFilterType"
|
||||
:show-menu="showSubDropdownMenu"
|
||||
:placeholder="filterPlaceholder"
|
||||
:show-clear-filter="false"
|
||||
enable-search
|
||||
@toggle-dropdown="openActiveFilterDropdown"
|
||||
@close-dropdown="closeActiveFilterDropdown"
|
||||
@add-filter="addFilter"
|
||||
@remove-filter="removeFilter"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="multiselect-wrap--small">
|
||||
<p class="mb-2 text-xs font-medium">
|
||||
{{ $t('REPORT.DURATION_FILTER_LABEL') }}
|
||||
</p>
|
||||
<multiselect
|
||||
v-model="currentDateRangeSelection"
|
||||
track-by="name"
|
||||
label="name"
|
||||
:placeholder="$t('FORMS.MULTISELECT.SELECT_ONE')"
|
||||
selected-label
|
||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||
deselect-label=""
|
||||
:options="dateRange"
|
||||
:searchable="false"
|
||||
:allow-empty="false"
|
||||
@select="changeDateSelection"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center h-10 self-center order-5 md:order-2 md:justify-self-end"
|
||||
>
|
||||
<span class="mr-2 text-sm whitespace-nowrap">
|
||||
{{ $t('REPORT.BUSINESS_HOURS') }}
|
||||
</span>
|
||||
<span>
|
||||
<ToggleSwitch
|
||||
v-model="businessHoursSelected"
|
||||
@change="onBusinessHoursToggle"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="isDateRangeSelected" class="order-3 md:order-4">
|
||||
<p class="mb-2 text-xs font-medium">
|
||||
{{ $t('REPORT.CUSTOM_DATE_RANGE.PLACEHOLDER') }}
|
||||
</p>
|
||||
<WootDateRangePicker
|
||||
show-range
|
||||
:value="customDateRange"
|
||||
:confirm-text="$t('REPORT.CUSTOM_DATE_RANGE.CONFIRM')"
|
||||
:placeholder="$t('REPORT.CUSTOM_DATE_RANGE.PLACEHOLDER')"
|
||||
class="auto-width"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="notLast7Days" class="multiselect-wrap--small order-4 md:order-5">
|
||||
<p class="mb-2 text-xs font-medium">
|
||||
{{ $t('REPORT.GROUP_BY_FILTER_DROPDOWN_LABEL') }}
|
||||
</p>
|
||||
<multiselect
|
||||
v-model="currentSelectedGroupByFilter"
|
||||
track-by="id"
|
||||
label="groupBy"
|
||||
<ActiveFilterChip
|
||||
v-if="isGroupByPossible"
|
||||
:id="groupBy?.id"
|
||||
:name="
|
||||
groupByfilterItemsList.find(item => item.id === groupBy?.id)?.name ||
|
||||
$t('REPORT.GROUP_BY_FILTER_DROPDOWN_LABEL')
|
||||
"
|
||||
type="groupBy"
|
||||
:options="groupByfilterItemsList"
|
||||
:active-filter-type="showGroupByDropdown ? 'groupBy' : ''"
|
||||
:show-menu="showGroupByDropdown"
|
||||
:placeholder="$t('REPORT.GROUP_BY_FILTER_DROPDOWN_LABEL')"
|
||||
:options="groupByFilterItemsList"
|
||||
:allow-empty="false"
|
||||
:show-labels="false"
|
||||
@update:model-value="changeGroupByFilterSelection"
|
||||
:enable-search="false"
|
||||
:show-clear-filter="false"
|
||||
@toggle-dropdown="toggleGroupByDropdown"
|
||||
@close-dropdown="closeGroupByDropdown"
|
||||
@add-filter="onGroupByFilterChange"
|
||||
@remove-filter="() => {}"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="showBusinessHours"
|
||||
class="flex items-center flex-shrink-0 ltr:ml-auto rtl:mr-auto"
|
||||
>
|
||||
<span class="mx-2 text-sm whitespace-nowrap">
|
||||
{{ $t('REPORT.BUSINESS_HOURS') }}
|
||||
</span>
|
||||
<span>
|
||||
<ToggleSwitch
|
||||
v-model="businessHoursSelected"
|
||||
@change="onBusinessHoursToggle"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import ReportFilterSelector from './FilterSelector.vue';
|
||||
import OverviewReportFilters from './OverviewReportFilters.vue';
|
||||
import { formatTime } from '@chatwoot/utils';
|
||||
import { useStore, useMapGetter } from 'dashboard/composables/store';
|
||||
import Table from 'dashboard/components/table/Table.vue';
|
||||
@@ -178,7 +178,7 @@ defineExpose({ downloadReports });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ReportFilterSelector @filter-change="onFilterChange" />
|
||||
<OverviewReportFilters @filter-change="onFilterChange" />
|
||||
<div
|
||||
class="flex-1 overflow-auto px-2 py-2 mt-5 shadow outline-1 outline outline-n-container rounded-xl bg-n-solid-2"
|
||||
>
|
||||
|
||||
+29
-86
@@ -1,31 +1,12 @@
|
||||
<script>
|
||||
import V4Button from 'dashboard/components-next/button/Button.vue';
|
||||
import { useAlert, useTrack } from 'dashboard/composables';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import ReportFilters from './ReportFilters.vue';
|
||||
import ReportContainer from '../ReportContainer.vue';
|
||||
import { GROUP_BY_FILTER } from '../constants';
|
||||
import { generateFileName } from '../../../../../helper/downloadHelper';
|
||||
import { REPORTS_EVENTS } from '../../../../../helper/AnalyticsHelper/events';
|
||||
import ReportHeader from './ReportHeader.vue';
|
||||
|
||||
const GROUP_BY_OPTIONS = {
|
||||
DAY: [{ id: 1, groupByKey: 'REPORT.GROUPING_OPTIONS.DAY' }],
|
||||
WEEK: [
|
||||
{ id: 1, groupByKey: 'REPORT.GROUPING_OPTIONS.DAY' },
|
||||
{ id: 2, groupByKey: 'REPORT.GROUPING_OPTIONS.WEEK' },
|
||||
],
|
||||
MONTH: [
|
||||
{ id: 1, groupByKey: 'REPORT.GROUPING_OPTIONS.DAY' },
|
||||
{ id: 2, groupByKey: 'REPORT.GROUPING_OPTIONS.WEEK' },
|
||||
{ id: 3, groupByKey: 'REPORT.GROUPING_OPTIONS.MONTH' },
|
||||
],
|
||||
YEAR: [
|
||||
{ id: 2, groupByKey: 'REPORT.GROUPING_OPTIONS.WEEK' },
|
||||
{ id: 3, groupByKey: 'REPORT.GROUPING_OPTIONS.MONTH' },
|
||||
{ id: 4, groupByKey: 'REPORT.GROUPING_OPTIONS.YEAR' },
|
||||
],
|
||||
};
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ReportHeader,
|
||||
@@ -69,12 +50,19 @@ export default {
|
||||
to: 0,
|
||||
selectedFilter: this.selectedItem,
|
||||
groupBy: GROUP_BY_FILTER[1],
|
||||
groupByfilterItemsList: GROUP_BY_OPTIONS.DAY.map(this.translateOptions),
|
||||
selectedGroupByFilter: null,
|
||||
businessHours: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filterType() {
|
||||
const pluralMap = {
|
||||
agent: 'agents',
|
||||
team: 'teams',
|
||||
inbox: 'inboxes',
|
||||
label: 'labels',
|
||||
};
|
||||
return pluralMap[this.type] || this.type;
|
||||
},
|
||||
filterItemsList() {
|
||||
return this.$store.getters[this.getterKey] || [];
|
||||
},
|
||||
@@ -145,69 +133,29 @@ export default {
|
||||
this.$store.dispatch(dispatchMethods[type], params);
|
||||
}
|
||||
},
|
||||
onDateRangeChange({ from, to, groupBy }) {
|
||||
// do not track filter change on inital load
|
||||
if (this.from !== 0 && this.to !== 0) {
|
||||
useTrack(REPORTS_EVENTS.FILTER_REPORT, {
|
||||
filterType: 'date',
|
||||
reportType: this.type,
|
||||
});
|
||||
}
|
||||
|
||||
onFilterChange(payload) {
|
||||
const { from, to, businessHours, groupBy } = payload;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.groupByfilterItemsList = this.fetchFilterItems(groupBy);
|
||||
const filterItems = this.groupByfilterItemsList.filter(
|
||||
item => item.id === this.groupBy.id
|
||||
);
|
||||
if (filterItems.length > 0) {
|
||||
this.selectedGroupByFilter = filterItems[0];
|
||||
this.businessHours = businessHours;
|
||||
|
||||
if (groupBy) {
|
||||
this.groupBy = groupBy;
|
||||
} else {
|
||||
this.selectedGroupByFilter = this.groupByfilterItemsList[0];
|
||||
this.groupBy = GROUP_BY_FILTER[this.selectedGroupByFilter.id];
|
||||
this.groupBy = GROUP_BY_FILTER[1];
|
||||
}
|
||||
this.fetchAllData();
|
||||
},
|
||||
onFilterChange(payload) {
|
||||
if (payload) {
|
||||
this.selectedFilter = payload;
|
||||
this.fetchAllData();
|
||||
}
|
||||
},
|
||||
onGroupByFilterChange(payload) {
|
||||
this.groupBy = GROUP_BY_FILTER[payload.id];
|
||||
this.fetchAllData();
|
||||
|
||||
useTrack(REPORTS_EVENTS.FILTER_REPORT, {
|
||||
filterType: 'groupBy',
|
||||
filterValue: this.groupBy?.period,
|
||||
reportType: this.type,
|
||||
});
|
||||
},
|
||||
fetchFilterItems(groupBy) {
|
||||
switch (groupBy) {
|
||||
case GROUP_BY_FILTER[2].period:
|
||||
return GROUP_BY_OPTIONS.WEEK.map(this.translateOptions);
|
||||
case GROUP_BY_FILTER[3].period:
|
||||
return GROUP_BY_OPTIONS.MONTH.map(this.translateOptions);
|
||||
case GROUP_BY_FILTER[4].period:
|
||||
return GROUP_BY_OPTIONS.YEAR.map(this.translateOptions);
|
||||
default:
|
||||
return GROUP_BY_OPTIONS.DAY.map(this.translateOptions);
|
||||
// Get filter value directly from filterType key
|
||||
const filterValue = payload[this.filterType];
|
||||
if (filterValue) {
|
||||
this.selectedFilter = Array.isArray(filterValue)
|
||||
? filterValue[0]
|
||||
: filterValue;
|
||||
} else {
|
||||
this.selectedFilter = null;
|
||||
}
|
||||
},
|
||||
translateOptions(opts) {
|
||||
return { id: opts.id, groupBy: this.$t(opts.groupByKey) };
|
||||
},
|
||||
onBusinessHoursToggle(value) {
|
||||
this.businessHours = value;
|
||||
this.fetchAllData();
|
||||
|
||||
useTrack(REPORTS_EVENTS.FILTER_REPORT, {
|
||||
filterType: 'businessHours',
|
||||
filterValue: value,
|
||||
reportType: this.type,
|
||||
});
|
||||
this.fetchAllData();
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -222,17 +170,12 @@ export default {
|
||||
@click="downloadReports"
|
||||
/>
|
||||
</ReportHeader>
|
||||
|
||||
<ReportFilters
|
||||
v-if="filterItemsList"
|
||||
:type="type"
|
||||
:filter-items-list="filterItemsList"
|
||||
:group-by-filter-items-list="groupByfilterItemsList"
|
||||
:selected-group-by-filter="selectedGroupByFilter"
|
||||
:current-filter="selectedFilter"
|
||||
@date-range-change="onDateRangeChange"
|
||||
:filter-type="filterType"
|
||||
:selected-item="selectedFilter"
|
||||
@filter-change="onFilterChange"
|
||||
@group-by-filter-change="onGroupByFilterChange"
|
||||
@business-hours-toggle="onBusinessHoursToggle"
|
||||
/>
|
||||
<ReportContainer
|
||||
v-if="filterItemsList.length"
|
||||
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { createStore } from 'vuex';
|
||||
import ReportsFiltersAgents from '../../Filters/Agents.vue';
|
||||
|
||||
const mockStore = createStore({
|
||||
modules: {
|
||||
agents: {
|
||||
namespaced: true,
|
||||
state: {
|
||||
agents: [],
|
||||
},
|
||||
getters: {
|
||||
getAgents: state => state.agents,
|
||||
},
|
||||
actions: {
|
||||
get: vi.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const mountParams = {
|
||||
global: {
|
||||
plugins: [mockStore],
|
||||
mocks: {
|
||||
$t: msg => msg,
|
||||
},
|
||||
stubs: ['multiselect'],
|
||||
},
|
||||
};
|
||||
|
||||
describe('ReportsFiltersAgents.vue', () => {
|
||||
it('emits "agents-filter-selection" event when handleInput is called', async () => {
|
||||
const wrapper = shallowMount(ReportsFiltersAgents, mountParams);
|
||||
|
||||
const selectedAgents = [
|
||||
{ id: 1, name: 'Agent 1' },
|
||||
{ id: 2, name: 'Agent 2' },
|
||||
];
|
||||
await wrapper.setData({ selectedOptions: selectedAgents });
|
||||
|
||||
await wrapper.vm.handleInput();
|
||||
|
||||
expect(wrapper.emitted('agentsFilterSelection')).toBeTruthy();
|
||||
expect(wrapper.emitted('agentsFilterSelection')[0]).toEqual([
|
||||
selectedAgents,
|
||||
]);
|
||||
});
|
||||
|
||||
it('dispatches the "agents/get" action when the component is mounted', () => {
|
||||
const dispatchSpy = vi.spyOn(mockStore, 'dispatch');
|
||||
|
||||
shallowMount(ReportsFiltersAgents, mountParams);
|
||||
|
||||
expect(dispatchSpy).toHaveBeenCalledWith('agents/get');
|
||||
});
|
||||
});
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import ReportsFiltersDateGroupBy from '../../Filters/DateGroupBy.vue';
|
||||
import { GROUP_BY_OPTIONS } from '../../../constants';
|
||||
|
||||
const mountParams = {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: msg => msg,
|
||||
},
|
||||
stubs: ['multiselect'],
|
||||
},
|
||||
};
|
||||
|
||||
describe('ReportsFiltersDateGroupBy.vue', () => {
|
||||
it('emits "on-grouping-change" event when changeFilterSelection is called', () => {
|
||||
const wrapper = shallowMount(ReportsFiltersDateGroupBy, mountParams);
|
||||
|
||||
const selectedFilter = GROUP_BY_OPTIONS.DAY;
|
||||
wrapper.vm.changeFilterSelection(selectedFilter);
|
||||
|
||||
expect(wrapper.emitted('onGroupingChange')).toBeTruthy();
|
||||
expect(wrapper.emitted('onGroupingChange')[0]).toEqual([selectedFilter]);
|
||||
});
|
||||
|
||||
it('updates currentSelectedFilter when selectedOption is changed', async () => {
|
||||
const wrapper = shallowMount(ReportsFiltersDateGroupBy, mountParams);
|
||||
|
||||
const newSelectedOption = GROUP_BY_OPTIONS.MONTH;
|
||||
await wrapper.setProps({ selectedOption: newSelectedOption });
|
||||
|
||||
expect(wrapper.vm.currentSelectedFilter).toEqual({
|
||||
...newSelectedOption,
|
||||
groupBy: newSelectedOption.translationKey,
|
||||
});
|
||||
});
|
||||
|
||||
it('initializes translatedOptions correctly', () => {
|
||||
const wrapper = shallowMount(ReportsFiltersDateGroupBy, mountParams);
|
||||
|
||||
const expectedOptions = wrapper.vm.validGroupOptions.map(option => ({
|
||||
...option,
|
||||
groupBy: option.translationKey,
|
||||
}));
|
||||
|
||||
expect(wrapper.vm.translatedOptions).toEqual(expectedOptions);
|
||||
});
|
||||
});
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import ReportFiltersDateRange from '../../Filters/DateRange.vue';
|
||||
import { DATE_RANGE_OPTIONS } from '../../../constants';
|
||||
|
||||
const mountParams = {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: msg => msg,
|
||||
},
|
||||
stubs: ['multiselect'],
|
||||
},
|
||||
};
|
||||
|
||||
describe('ReportFiltersDateRange.vue', () => {
|
||||
it('emits "onRangeChange" event when updateRange is called', () => {
|
||||
const wrapper = shallowMount(ReportFiltersDateRange, mountParams);
|
||||
|
||||
const selectedRange = DATE_RANGE_OPTIONS.LAST_7_DAYS;
|
||||
wrapper.vm.updateRange(selectedRange);
|
||||
|
||||
expect(wrapper.emitted('onRangeChange')).toBeTruthy();
|
||||
expect(wrapper.emitted('onRangeChange')[0]).toEqual([selectedRange]);
|
||||
});
|
||||
|
||||
it('initializes options correctly', () => {
|
||||
const wrapper = shallowMount(ReportFiltersDateRange, mountParams);
|
||||
|
||||
const expectedIds = Object.values(DATE_RANGE_OPTIONS).map(
|
||||
option => option.id
|
||||
);
|
||||
const receivedIds = wrapper.vm.options.map(option => option.id);
|
||||
|
||||
expect(receivedIds).toEqual(expectedIds);
|
||||
});
|
||||
|
||||
it('initializes selectedOption correctly', () => {
|
||||
const wrapper = shallowMount(ReportFiltersDateRange, mountParams);
|
||||
const expectedId = Object.values(DATE_RANGE_OPTIONS)[0].id;
|
||||
expect(wrapper.vm.selectedOption.id).toBe(expectedId);
|
||||
});
|
||||
});
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { createStore } from 'vuex';
|
||||
import ReportsFiltersInboxes from '../../Filters/Inboxes.vue';
|
||||
|
||||
const mountParams = {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: msg => msg,
|
||||
},
|
||||
stubs: ['multiselect'],
|
||||
},
|
||||
};
|
||||
|
||||
describe('ReportsFiltersInboxes.vue', () => {
|
||||
let store;
|
||||
let inboxesModule;
|
||||
|
||||
beforeEach(() => {
|
||||
inboxesModule = {
|
||||
namespaced: true,
|
||||
getters: {
|
||||
getInboxes: () => () => [
|
||||
{ id: 1, name: 'Inbox 1' },
|
||||
{ id: 2, name: 'Inbox 2' },
|
||||
],
|
||||
},
|
||||
actions: {
|
||||
get: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
store = createStore({
|
||||
modules: {
|
||||
inboxes: inboxesModule,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches "inboxes/get" action when component is mounted', () => {
|
||||
shallowMount(ReportsFiltersInboxes, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
...mountParams.global,
|
||||
},
|
||||
});
|
||||
expect(inboxesModule.actions.get).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('emits "inbox-filter-selection" event when handleInput is called', async () => {
|
||||
const wrapper = shallowMount(ReportsFiltersInboxes, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
...mountParams.global,
|
||||
},
|
||||
});
|
||||
|
||||
const selectedInbox = { id: 1, name: 'Inbox 1' };
|
||||
await wrapper.setData({ selectedOption: selectedInbox });
|
||||
|
||||
await wrapper.vm.handleInput();
|
||||
|
||||
expect(wrapper.emitted('inboxFilterSelection')).toBeTruthy();
|
||||
expect(wrapper.emitted('inboxFilterSelection')[0]).toEqual([selectedInbox]);
|
||||
});
|
||||
});
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { createStore } from 'vuex';
|
||||
import ReportsFiltersLabels from '../../Filters/Labels.vue';
|
||||
|
||||
const mountParams = {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: msg => msg,
|
||||
},
|
||||
stubs: ['multiselect'],
|
||||
},
|
||||
};
|
||||
|
||||
describe('ReportsFiltersLabels.vue', () => {
|
||||
let store;
|
||||
let labelsModule;
|
||||
|
||||
beforeEach(() => {
|
||||
labelsModule = {
|
||||
namespaced: true,
|
||||
getters: {
|
||||
getLabels: () => () => [
|
||||
{ id: 1, title: 'Label 1', color: 'red' },
|
||||
{ id: 2, title: 'Label 2', color: 'blue' },
|
||||
],
|
||||
},
|
||||
actions: {
|
||||
get: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
store = createStore({
|
||||
modules: {
|
||||
labels: labelsModule,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches "labels/get" action when component is mounted', () => {
|
||||
shallowMount(ReportsFiltersLabels, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
...mountParams.global,
|
||||
},
|
||||
});
|
||||
expect(labelsModule.actions.get).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('emits "labels-filter-selection" event when handleInput is called', async () => {
|
||||
const wrapper = shallowMount(ReportsFiltersLabels, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
...mountParams.global,
|
||||
},
|
||||
});
|
||||
|
||||
const selectedLabel = { id: 1, title: 'Label 1', color: 'red' };
|
||||
await wrapper.setData({ selectedOption: selectedLabel });
|
||||
|
||||
await wrapper.vm.handleInput();
|
||||
|
||||
expect(wrapper.emitted('labelsFilterSelection')).toBeTruthy();
|
||||
expect(wrapper.emitted('labelsFilterSelection')[0]).toEqual([
|
||||
selectedLabel,
|
||||
]);
|
||||
});
|
||||
});
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import ReportFiltersRatings from '../../Filters/Ratings.vue';
|
||||
import { CSAT_RATINGS } from 'shared/constants/messages';
|
||||
|
||||
const mountParams = {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: msg => msg,
|
||||
},
|
||||
stubs: ['multiselect'],
|
||||
},
|
||||
};
|
||||
|
||||
describe('ReportFiltersRatings.vue', () => {
|
||||
it('emits "rating-filter-selection" event when handleInput is called', async () => {
|
||||
const wrapper = shallowMount(ReportFiltersRatings, {
|
||||
...mountParams,
|
||||
});
|
||||
|
||||
const selectedRating = { value: 1, label: 'Rating 1' };
|
||||
await wrapper.setData({ selectedOption: selectedRating });
|
||||
|
||||
await wrapper.vm.handleInput(selectedRating);
|
||||
|
||||
expect(wrapper.emitted('ratingFilterSelection')).toBeTruthy();
|
||||
expect(wrapper.emitted('ratingFilterSelection')[0]).toEqual([
|
||||
selectedRating,
|
||||
]);
|
||||
});
|
||||
|
||||
it('initializes options correctly', () => {
|
||||
const wrapper = shallowMount(ReportFiltersRatings, {
|
||||
...mountParams,
|
||||
});
|
||||
|
||||
const expectedOptions = CSAT_RATINGS.map(option => ({
|
||||
...option,
|
||||
label: option.translationKey,
|
||||
}));
|
||||
|
||||
expect(wrapper.vm.options).toEqual(expectedOptions);
|
||||
});
|
||||
});
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { createStore } from 'vuex';
|
||||
import ReportsFiltersTeams from '../../Filters/Teams.vue';
|
||||
|
||||
const mountParams = {
|
||||
global: {
|
||||
mocks: {
|
||||
$t: msg => msg,
|
||||
},
|
||||
stubs: ['multiselect'],
|
||||
},
|
||||
};
|
||||
|
||||
describe('ReportsFiltersTeams.vue', () => {
|
||||
let store;
|
||||
let teamsModule;
|
||||
|
||||
beforeEach(() => {
|
||||
teamsModule = {
|
||||
namespaced: true,
|
||||
getters: {
|
||||
getTeams: () => () => [
|
||||
{ id: 1, name: 'Team 1' },
|
||||
{ id: 2, name: 'Team 2' },
|
||||
],
|
||||
},
|
||||
actions: {
|
||||
get: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
store = createStore({
|
||||
modules: {
|
||||
teams: teamsModule,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('dispatches "teams/get" action when component is mounted', () => {
|
||||
shallowMount(ReportsFiltersTeams, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
...mountParams,
|
||||
},
|
||||
});
|
||||
expect(teamsModule.actions.get).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('emits "team-filter-selection" event when handleInput is called', async () => {
|
||||
const wrapper = shallowMount(ReportsFiltersTeams, {
|
||||
global: {
|
||||
plugins: [store],
|
||||
...mountParams,
|
||||
},
|
||||
});
|
||||
|
||||
await wrapper.setData({ selectedOption: { id: 1, name: 'Team 1' } });
|
||||
await wrapper.vm.handleInput();
|
||||
|
||||
expect(wrapper.emitted('teamFilterSelection')).toBeTruthy();
|
||||
expect(wrapper.emitted('teamFilterSelection')[0]).toEqual([
|
||||
{ id: 1, name: 'Team 1' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -27,7 +27,7 @@ export const getters = {
|
||||
.sort((a, b) => a.title.localeCompare(b.title));
|
||||
},
|
||||
getLabelById: _state => id => {
|
||||
return _state.records.find(record => record.id === Number(id));
|
||||
return _state.records.find(record => record.id === Number(id)) || {};
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user