diff --git a/app/javascript/dashboard/assets/scss/widgets/_report.scss b/app/javascript/dashboard/assets/scss/widgets/_report.scss index e9ce5d0bd..d07f1dd3d 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_report.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_report.scss @@ -58,31 +58,3 @@ text-transform: capitalize; } } - -.report-bar { - @include background-white; - @include border-light; - margin: var(--space-minus-micro) 0; - padding: var(--space-small) var(--space-medium); - - .chart-container { - @include flex; - @include flex-align(center, middle); - flex-direction: column; - - div { - width: 100%; - } - - .empty-state { - color: $color-gray; - font-size: var(--font-size-default); - margin: var(--space-jumbo); - } - - .business-hours { - margin: var(--space-normal); - text-align: center; - } - } -} diff --git a/app/javascript/dashboard/assets/scss/widgets/_reports.scss b/app/javascript/dashboard/assets/scss/widgets/_reports.scss index c848bc0c8..dcd65366a 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_reports.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_reports.scss @@ -22,18 +22,6 @@ margin: 0 var(--space-small); } -.business-hours { - align-items: center; - display: flex; - justify-content: flex-start; - margin-left: auto; - padding-right: var(--space-normal); -} - -.business-hours-text { - font-size: var(--font-size-small); - margin: 0 var(--space-small); -} .switch { margin-bottom: var(--space-zero); diff --git a/app/javascript/dashboard/components/widgets/chart/BarChart.js b/app/javascript/dashboard/components/widgets/chart/BarChart.js index a4dca263b..22ee0f837 100644 --- a/app/javascript/dashboard/components/widgets/chart/BarChart.js +++ b/app/javascript/dashboard/components/widgets/chart/BarChart.js @@ -1,16 +1,20 @@ import { Bar } from 'vue-chartjs'; const fontFamily = - '-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif'; + 'PlusJakarta,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif'; const defaultChartOptions = { responsive: true, maintainAspectRatio: false, legend: { + display: false, labels: { fontFamily, }, }, + animation: { + duration: 0, + }, datasets: { bar: { barPercentage: 1.0, @@ -46,11 +50,11 @@ export default { props: { collection: { type: Object, - default: () => {}, + default: () => ({}), }, chartOptions: { type: Object, - default: () => {}, + default: () => ({}), }, }, mounted() { diff --git a/app/javascript/dashboard/i18n/locale/en/report.json b/app/javascript/dashboard/i18n/locale/en/report.json index 5858605a0..f384adad7 100644 --- a/app/javascript/dashboard/i18n/locale/en/report.json +++ b/app/javascript/dashboard/i18n/locale/en/report.json @@ -12,11 +12,11 @@ "DESC": "( Total )" }, "INCOMING_MESSAGES": { - "NAME": "Incoming Messages", + "NAME": "Messages received", "DESC": "( Total )" }, "OUTGOING_MESSAGES": { - "NAME": "Outgoing Messages", + "NAME": "Messages sent", "DESC": "( Total )" }, "FIRST_RESPONSE_TIME": { @@ -93,7 +93,6 @@ { "id": 3, "groupBy": "Month" } ], "GROUP_BY_YEAR_OPTIONS": [ - { "id": 1, "groupBy": "Day" }, { "id": 2, "groupBy": "Week" }, { "id": 3, "groupBy": "Month" }, { "id": 4, "groupBy": "Year" } diff --git a/app/javascript/dashboard/mixins/reportMixin.js b/app/javascript/dashboard/mixins/reportMixin.js index d5bb8c9cc..3a53f1196 100644 --- a/app/javascript/dashboard/mixins/reportMixin.js +++ b/app/javascript/dashboard/mixins/reportMixin.js @@ -10,42 +10,36 @@ export default { calculateTrend() { return metric_key => { if (!this.accountSummary.previous[metric_key]) return 0; + const diff = + this.accountSummary[metric_key] - + this.accountSummary.previous[metric_key]; return Math.round( - ((this.accountSummary[metric_key] - - this.accountSummary.previous[metric_key]) / - this.accountSummary.previous[metric_key]) * - 100 - ); - }; - }, - displayMetric() { - return metric_key => { - if (this.isAverageMetricType(metric_key)) { - return formatTime(this.accountSummary[metric_key]); - } - return this.accountSummary[metric_key]; - }; - }, - displayInfoText() { - return metric_key => { - if (this.metrics[this.currentSelection].KEY !== metric_key) { - return ''; - } - if (this.isAverageMetricType(metric_key)) { - const total = this.accountReport.data - .map(item => item.count) - .reduce((prev, curr) => prev + curr, 0); - return `${this.metrics[this.currentSelection].INFO_TEXT} ${total}`; - } - return ''; - }; - }, - isAverageMetricType() { - return metric_key => { - return ['avg_first_response_time', 'avg_resolution_time'].includes( - metric_key + (diff / this.accountSummary.previous[metric_key]) * 100 ); }; }, }, + methods: { + displayMetric(key) { + if (this.isAverageMetricType(key)) { + return formatTime(this.accountSummary[key]); + } + return Number(this.accountSummary[key] || '').toLocaleString(); + }, + displayInfoText(key) { + if (this.metrics[this.currentSelection].KEY !== key) { + return ''; + } + if (this.isAverageMetricType(key)) { + const total = this.accountReport.data + .map(item => item.count) + .reduce((prev, curr) => prev + curr, 0); + return `${this.metrics[this.currentSelection].INFO_TEXT} ${total}`; + } + return ''; + }, + isAverageMetricType(key) { + return ['avg_first_response_time', 'avg_resolution_time'].includes(key); + }, + }, }; diff --git a/app/javascript/dashboard/mixins/specs/reportMixin.spec.js b/app/javascript/dashboard/mixins/specs/reportMixin.spec.js index aa3c451d9..d981de4e9 100644 --- a/app/javascript/dashboard/mixins/specs/reportMixin.spec.js +++ b/app/javascript/dashboard/mixins/specs/reportMixin.spec.js @@ -23,7 +23,7 @@ describe('reportMixin', () => { mixins: [reportMixin], }; const wrapper = shallowMount(Component, { store, localVue }); - expect(wrapper.vm.displayMetric('conversations_count')).toEqual(5); + expect(wrapper.vm.displayMetric('conversations_count')).toEqual('5,000'); expect(wrapper.vm.displayMetric('avg_first_response_time')).toEqual( '3 Min 18 Sec' ); @@ -36,7 +36,7 @@ describe('reportMixin', () => { mixins: [reportMixin], }; const wrapper = shallowMount(Component, { store, localVue }); - expect(wrapper.vm.calculateTrend('conversations_count')).toEqual(25); + expect(wrapper.vm.calculateTrend('conversations_count')).toEqual(124900); expect(wrapper.vm.calculateTrend('resolutions_count')).toEqual(0); }); diff --git a/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js b/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js index 8402c3940..ab6b6fecf 100644 --- a/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js +++ b/app/javascript/dashboard/mixins/specs/reportMixinFixtures.js @@ -2,7 +2,7 @@ export default { summary: { avg_first_response_time: '198.6666666666667', avg_resolution_time: '208.3333333333333', - conversations_count: 5, + conversations_count: 5000, incoming_messages_count: 5, outgoing_messages_count: 3, previous: { diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue index d38e983a7..b04216bff 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/Index.vue @@ -13,36 +13,7 @@ :show-group-by-filter="true" @filter-change="onFilterChange" /> -
- -
-
- -
- - - {{ $t('REPORT.NO_ENOUGH_DATA') }} - -
-
+ @@ -51,11 +22,11 @@ import { mapGetters } from 'vuex'; import fromUnixTime from 'date-fns/fromUnixTime'; import format from 'date-fns/format'; import ReportFilterSelector from './components/FilterSelector'; -import { GROUP_BY_FILTER, METRIC_CHART } from './constants'; +import { GROUP_BY_FILTER } from './constants'; import reportMixin from 'dashboard/mixins/reportMixin'; import alertMixin from 'shared/mixins/alertMixin'; -import { formatTime } from '@chatwoot/utils'; import { REPORTS_EVENTS } from '../../../../helper/AnalyticsHelper/events'; +import ReportContainer from './ReportContainer.vue'; const REPORTS_KEYS = { CONVERSATIONS: 'conversations_count', @@ -70,13 +41,13 @@ export default { name: 'ConversationReports', components: { ReportFilterSelector, + ReportContainer, }, mixins: [reportMixin, alertMixin], data() { return { from: 0, to: 0, - currentSelection: 0, groupBy: GROUP_BY_FILTER[1], businessHours: false, }; @@ -86,104 +57,6 @@ export default { accountSummary: 'getAccountSummary', accountReport: 'getAccountReports', }), - collection() { - if (this.accountReport.isFetching) { - return {}; - } - if (!this.accountReport.data.length) return {}; - const labels = this.accountReport.data.map(element => { - if (this.groupBy?.period === GROUP_BY_FILTER[2].period) { - let week_date = new Date(fromUnixTime(element.timestamp)); - const first_day = week_date.getDate() - week_date.getDay(); - const last_day = first_day + 6; - - const week_first_date = new Date(week_date.setDate(first_day)); - const week_last_date = new Date(week_date.setDate(last_day)); - - return `${format(week_first_date, 'dd/MM/yy')} - ${format( - week_last_date, - 'dd/MM/yy' - )}`; - } - if (this.groupBy?.period === GROUP_BY_FILTER[3].period) { - return format(fromUnixTime(element.timestamp), 'MMM-yyyy'); - } - if (this.groupBy?.period === GROUP_BY_FILTER[4].period) { - return format(fromUnixTime(element.timestamp), 'yyyy'); - } - return format(fromUnixTime(element.timestamp), 'dd-MMM-yyyy'); - }); - - const datasets = METRIC_CHART[ - this.metrics[this.currentSelection].KEY - ].datasets.map(dataset => { - switch (dataset.type) { - case 'bar': - return { - ...dataset, - yAxisID: 'y-left', - label: this.metrics[this.currentSelection].NAME, - data: this.accountReport.data.map(element => element.value), - }; - case 'line': - return { - ...dataset, - yAxisID: 'y-right', - label: this.metrics[0].NAME, - data: this.accountReport.data.map(element => element.count), - }; - default: - return dataset; - } - }); - - return { - labels, - datasets, - }; - }, - chartOptions() { - let tooltips = {}; - if (this.isAverageMetricType(this.metrics[this.currentSelection].KEY)) { - tooltips.callbacks = { - label: tooltipItem => { - return this.$t(this.metrics[this.currentSelection].TOOLTIP_TEXT, { - metricValue: formatTime(tooltipItem.yLabel), - conversationCount: this.accountReport.data[tooltipItem.index] - .count, - }); - }, - }; - } - - return { - scales: METRIC_CHART[this.metrics[this.currentSelection].KEY].scales, - tooltips: tooltips, - }; - }, - metrics() { - const reportKeys = [ - 'CONVERSATIONS', - 'INCOMING_MESSAGES', - 'OUTGOING_MESSAGES', - 'FIRST_RESPONSE_TIME', - 'RESOLUTION_TIME', - 'RESOLUTION_COUNT', - ]; - const infoText = { - FIRST_RESPONSE_TIME: this.$t( - `REPORT.METRICS.FIRST_RESPONSE_TIME.INFO_TEXT` - ), - RESOLUTION_TIME: this.$t(`REPORT.METRICS.RESOLUTION_TIME.INFO_TEXT`), - }; - return reportKeys.map(key => ({ - NAME: this.$t(`REPORT.METRICS.${key}.NAME`), - KEY: REPORTS_KEYS[key], - DESC: this.$t(`REPORT.METRICS.${key}.DESC`), - INFO_TEXT: infoText[key], - TOOLTIP_TEXT: `REPORT.METRICS.${key}.TOOLTIP_TEXT`, - })); - }, }, methods: { fetchAllData() { @@ -198,14 +71,23 @@ export default { } }, fetchChartData() { - try { - this.$store.dispatch('fetchAccountReport', { - metric: this.metrics[this.currentSelection].KEY, - ...this.getRequestPayload(), - }); - } catch { - this.showAlert(this.$t('REPORT.DATA_FETCHING_FAILED')); - } + [ + 'CONVERSATIONS', + 'INCOMING_MESSAGES', + 'OUTGOING_MESSAGES', + 'FIRST_RESPONSE_TIME', + 'RESOLUTION_TIME', + 'RESOLUTION_COUNT', + ].forEach(async key => { + try { + await this.$store.dispatch('fetchAccountReport', { + metric: REPORTS_KEYS[key], + ...this.getRequestPayload(), + }); + } catch { + this.showAlert(this.$t('REPORT.DATA_FETCHING_FAILED')); + } + }); }, getRequestPayload() { const { from, to, groupBy, businessHours } = this; @@ -225,10 +107,6 @@ export default { )}.csv`; this.$store.dispatch('downloadAgentReports', { from, to, fileName }); }, - changeSelection(index) { - this.currentSelection = index; - this.fetchChartData(); - }, onFilterChange({ from, to, groupBy, businessHours }) { this.from = from; this.to = to; diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue new file mode 100644 index 000000000..0fa504a36 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/ReportContainer.vue @@ -0,0 +1,156 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/ChartElements/ChartStats.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ChartElements/ChartStats.vue new file mode 100644 index 000000000..400452f0b --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ChartElements/ChartStats.vue @@ -0,0 +1,49 @@ + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue index b0e032c0a..84bae6198 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/FilterSelector.vue @@ -1,43 +1,45 @@