diff --git a/app/builders/v2/reports/timeseries/average_report_builder.rb b/app/builders/v2/reports/timeseries/average_report_builder.rb index 3e30557e1..5df718b6a 100644 --- a/app/builders/v2/reports/timeseries/average_report_builder.rb +++ b/app/builders/v2/reports/timeseries/average_report_builder.rb @@ -28,7 +28,7 @@ class V2::Reports::Timeseries::AverageReportBuilder < V2::Reports::Timeseries::B end def object_scope - scope.reporting_events.where(name: event_name, created_at: range) + scope.reporting_events.where(name: event_name, created_at: range, account_id: account.id) end def reporting_events diff --git a/app/helpers/api/v2/accounts/reports_helper.rb b/app/helpers/api/v2/accounts/reports_helper.rb index 4d5a6f115..22c51b6ef 100644 --- a/app/helpers/api/v2/accounts/reports_helper.rb +++ b/app/helpers/api/v2/accounts/reports_helper.rb @@ -1,58 +1,70 @@ module Api::V2::Accounts::ReportsHelper def generate_agents_report + reports = V2::Reports::AgentSummaryBuilder.new( + account: Current.account, + params: build_params(type: :agent) + ).build + Current.account.users.map do |agent| - agent_report = report_builder({ type: :agent, id: agent.id }).summary - [agent.name] + generate_readable_report_metrics(agent_report) + report = reports.find { |r| r[:id] == agent.id } + [agent.name] + generate_readable_report_metrics(report) end end def generate_inboxes_report + reports = V2::Reports::InboxSummaryBuilder.new( + account: Current.account, + params: build_params(type: :inbox) + ).build + Current.account.inboxes.map do |inbox| - inbox_report = generate_report({ type: :inbox, id: inbox.id }) - [inbox.name, inbox.channel&.name] + generate_readable_report_metrics(inbox_report) + report = reports.find { |r| r[:id] == inbox.id } + [inbox.name, inbox.channel&.name] + generate_readable_report_metrics(report) end end def generate_teams_report + reports = V2::Reports::TeamSummaryBuilder.new( + account: Current.account, + params: build_params(type: :team) + ).build + Current.account.teams.map do |team| - team_report = report_builder({ type: :team, id: team.id }).summary - [team.name] + generate_readable_report_metrics(team_report) + report = reports.find { |r| r[:id] == team.id } + [team.name] + generate_readable_report_metrics(report) end end def generate_labels_report Current.account.labels.map do |label| - label_report = generate_report({ type: :label, id: label.id }) + label_report = report_builder({ type: :label, id: label.id }).short_summary [label.title] + generate_readable_report_metrics(label_report) end end - def report_builder(report_params) - V2::ReportBuilder.new( - Current.account, - report_params.merge( - { - since: params[:since], - until: params[:until], - business_hours: ActiveModel::Type::Boolean.new.cast(params[:business_hours]) - } - ) + private + + def build_params(base_params) + base_params.merge( + { + since: params[:since], + until: params[:until], + business_hours: ActiveModel::Type::Boolean.new.cast(params[:business_hours]) + } ) end - def generate_report(report_params) - report_builder(report_params).short_summary + def report_builder(report_params) + V2::ReportBuilder.new(Current.account, build_params(report_params)) end - private - - def generate_readable_report_metrics(report_metric) + def generate_readable_report_metrics(report) [ - report_metric[:conversations_count], - Reports::TimeFormatPresenter.new(report_metric[:avg_first_response_time]).format, - Reports::TimeFormatPresenter.new(report_metric[:avg_resolution_time]).format, - Reports::TimeFormatPresenter.new(report_metric[:reply_time]).format, - report_metric[:resolutions_count] + report[:conversations_count], + Reports::TimeFormatPresenter.new(report[:avg_first_response_time]).format, + Reports::TimeFormatPresenter.new(report[:avg_resolution_time]).format, + Reports::TimeFormatPresenter.new(report[:avg_reply_time]).format, + report[:resolved_conversations_count] ] end end diff --git a/app/javascript/dashboard/api/summaryReports.js b/app/javascript/dashboard/api/summaryReports.js new file mode 100644 index 000000000..f772ef86f --- /dev/null +++ b/app/javascript/dashboard/api/summaryReports.js @@ -0,0 +1,40 @@ +/* global axios */ +import ApiClient from './ApiClient'; + +class SummaryReportsAPI extends ApiClient { + constructor() { + super('summary_reports', { accountScoped: true, apiVersion: 'v2' }); + } + + getTeamReports({ since, until, businessHours } = {}) { + return axios.get(`${this.url}/team`, { + params: { + since, + until, + business_hours: businessHours, + }, + }); + } + + getAgentReports({ since, until, businessHours } = {}) { + return axios.get(`${this.url}/agent`, { + params: { + since, + until, + business_hours: businessHours, + }, + }); + } + + getInboxReports({ since, until, businessHours } = {}) { + return axios.get(`${this.url}/inbox`, { + params: { + since, + until, + business_hours: businessHours, + }, + }); + } +} + +export default new SummaryReportsAPI(); diff --git a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue index 3f2f4ed6f..dcf4e9740 100644 --- a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue +++ b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue @@ -8,6 +8,7 @@ import { useStore } from 'vuex'; import { useI18n } from 'vue-i18n'; import { useStorage } from '@vueuse/core'; import { useSidebarKeyboardShortcuts } from './useSidebarKeyboardShortcuts'; +import { FEATURE_FLAGS } from 'dashboard/featureFlags'; import Button from 'dashboard/components-next/button/Button.vue'; import SidebarGroup from './SidebarGroup.vue'; @@ -36,6 +37,18 @@ const toggleShortcutModalFn = show => { } }; +const currentAccountId = useMapGetter('getCurrentAccountId'); +const isFeatureEnabledonAccount = useMapGetter( + 'accounts/isFeatureEnabledonAccount' +); + +const showV4Routes = computed(() => { + return isFeatureEnabledonAccount.value( + currentAccountId.value, + FEATURE_FLAGS.REPORT_V4 + ); +}); + useSidebarKeyboardShortcuts(toggleShortcutModalFn); // We're using localStorage to store the expanded item in the sidebar @@ -77,6 +90,59 @@ const sortedInboxes = computed(() => inboxes.value.slice().sort((a, b) => a.name.localeCompare(b.name)) ); +const newReportRoutes = [ + { + name: 'Reports Agent', + label: t('SIDEBAR.REPORTS_AGENT'), + to: accountScopedRoute('agent_reports_index'), + activeOn: ['agent_reports_show'], + }, + { + name: 'Reports Label', + label: t('SIDEBAR.REPORTS_LABEL'), + to: accountScopedRoute('label_reports'), + }, + { + name: 'Reports Inbox', + label: t('SIDEBAR.REPORTS_INBOX'), + to: accountScopedRoute('inbox_reports_index'), + activeOn: ['inbox_reports_show'], + }, + { + name: 'Reports Team', + label: t('SIDEBAR.REPORTS_TEAM'), + to: accountScopedRoute('team_reports_index'), + activeOn: ['team_reports_show'], + }, +]; + +const oldReportRoutes = [ + { + name: 'Reports Agent', + label: t('SIDEBAR.REPORTS_AGENT'), + to: accountScopedRoute('agent_reports'), + }, + { + name: 'Reports Label', + label: t('SIDEBAR.REPORTS_LABEL'), + to: accountScopedRoute('label_reports'), + }, + { + name: 'Reports Inbox', + label: t('SIDEBAR.REPORTS_INBOX'), + to: accountScopedRoute('inbox_reports'), + }, + { + name: 'Reports Team', + label: t('SIDEBAR.REPORTS_TEAM'), + to: accountScopedRoute('team_reports'), + }, +]; + +const reportRoutes = computed(() => + showV4Routes.value ? newReportRoutes : oldReportRoutes +); + const menuItems = computed(() => { return [ { @@ -265,31 +331,12 @@ const menuItems = computed(() => { label: t('SIDEBAR.REPORTS_CONVERSATION'), to: accountScopedRoute('conversation_reports'), }, + ...reportRoutes.value, { name: 'Reports CSAT', label: t('SIDEBAR.CSAT'), to: accountScopedRoute('csat_reports'), }, - { - name: 'Reports Agent', - label: t('SIDEBAR.REPORTS_AGENT'), - to: accountScopedRoute('agent_reports'), - }, - { - name: 'Reports Label', - label: t('SIDEBAR.REPORTS_LABEL'), - to: accountScopedRoute('label_reports'), - }, - { - name: 'Reports Inbox', - label: t('SIDEBAR.REPORTS_INBOX'), - to: accountScopedRoute('inbox_reports'), - }, - { - name: 'Reports Team', - label: t('SIDEBAR.REPORTS_TEAM'), - to: accountScopedRoute('team_reports'), - }, { name: 'Reports SLA', label: t('SIDEBAR.REPORTS_SLA'), diff --git a/app/javascript/dashboard/components/layout/config/sidebarItems/reports.js b/app/javascript/dashboard/components/layout/config/sidebarItems/reports.js index e7e4997fd..f9f00b88d 100644 --- a/app/javascript/dashboard/components/layout/config/sidebarItems/reports.js +++ b/app/javascript/dashboard/components/layout/config/sidebarItems/reports.js @@ -11,6 +11,7 @@ const reports = accountId => ({ 'agent_reports', 'label_reports', 'inbox_reports', + 'inbox_reports_show', 'team_reports', 'sla_reports', ], diff --git a/app/javascript/dashboard/components/table/Table.vue b/app/javascript/dashboard/components/table/Table.vue index 5936fa314..81edc4840 100644 --- a/app/javascript/dashboard/components/table/Table.vue +++ b/app/javascript/dashboard/components/table/Table.vue @@ -40,7 +40,7 @@ const headerClass = computed(() => :style="{ width: `${header.getSize()}px`, }" - class="text-left py-3 px-5 font-normal text-sm" + class="text-left py-3 px-5 font-medium text-sm text-n-slate-12" :class="headerClass" @click="header.column.getCanSort() && header.column.toggleSorting()" > diff --git a/app/javascript/dashboard/components/widgets/BackButton.vue b/app/javascript/dashboard/components/widgets/BackButton.vue index 9a5557f0b..09a22ffa3 100644 --- a/app/javascript/dashboard/components/widgets/BackButton.vue +++ b/app/javascript/dashboard/components/widgets/BackButton.vue @@ -37,8 +37,10 @@ const buttonStyleClass = props.compact > {{ buttonLabel || $t('GENERAL_SETTINGS.BACK') }} diff --git a/app/javascript/dashboard/featureFlags.js b/app/javascript/dashboard/featureFlags.js index 4417ef164..094fbfa27 100644 --- a/app/javascript/dashboard/featureFlags.js +++ b/app/javascript/dashboard/featureFlags.js @@ -33,4 +33,5 @@ export const FEATURE_FLAGS = { CAPTAIN: 'captain_integration', CUSTOM_ROLES: 'custom_roles', CHATWOOT_V4: 'chatwoot_v4', + REPORT_V4: 'report_v4', }; diff --git a/app/javascript/dashboard/i18n/locale/en/report.json b/app/javascript/dashboard/i18n/locale/en/report.json index a6389f017..c9ac00f7f 100644 --- a/app/javascript/dashboard/i18n/locale/en/report.json +++ b/app/javascript/dashboard/i18n/locale/en/report.json @@ -124,6 +124,7 @@ }, "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.", "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", @@ -258,6 +259,7 @@ }, "INBOX_REPORTS": { "HEADER": "Inbox Overview", + "DESCRIPTION": "Quickly view your inbox performance with key metrics like conversations, response times, resolution times, and resolved cases—all in one place. Click an inbox 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_INBOX_REPORTS": "Download inbox reports", @@ -325,6 +327,7 @@ }, "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.", "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", @@ -538,5 +541,15 @@ }, "VIEW_DETAILS": "View Details" } + }, + "SUMMARY_REPORTS": { + "INBOX": "Inbox", + "AGENT": "Agent", + "TEAM": "Team", + "AVG_RESOLUTION_TIME": "Avg. Resolution Time", + "AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time", + "AVG_REPLY_TIME": "Avg. Customer Waiting Time", + "RESOLUTION_COUNT": "Resolution Count", + "CONVERSATIONS": "No. of conversations" } } diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/AgentReportsIndex.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/AgentReportsIndex.vue new file mode 100644 index 000000000..b0993e93b --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/AgentReportsIndex.vue @@ -0,0 +1,35 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/AgentReportsShow.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/AgentReportsShow.vue new file mode 100644 index 000000000..211e39167 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/AgentReportsShow.vue @@ -0,0 +1,31 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/InboxReportsIndex.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/InboxReportsIndex.vue new file mode 100644 index 000000000..caf43ae20 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/InboxReportsIndex.vue @@ -0,0 +1,35 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/InboxReportsShow.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/InboxReportsShow.vue new file mode 100644 index 000000000..06b584aae --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/InboxReportsShow.vue @@ -0,0 +1,27 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/TeamReportsIndex.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/TeamReportsIndex.vue new file mode 100644 index 000000000..141243ee9 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/TeamReportsIndex.vue @@ -0,0 +1,35 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/TeamReportsShow.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/TeamReportsShow.vue new file mode 100644 index 000000000..9f346a772 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/TeamReportsShow.vue @@ -0,0 +1,27 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportFilters.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportFilters.vue index 053ae7cd7..5950edfd9 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportFilters.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportFilters.vue @@ -15,6 +15,10 @@ export default { Thumbnail, }, props: { + currentFilter: { + type: Object, + default: () => null, + }, filterItemsList: { type: Array, default: () => [], @@ -40,7 +44,7 @@ export default { ], data() { return { - currentSelectedFilter: null, + currentSelectedFilter: this.currentFilter || null, currentDateRangeSelection: { id: 0, name: this.$t('REPORT.DATE_RANGE_OPTIONS.LAST_7_DAYS'), @@ -113,7 +117,9 @@ export default { }, watch: { filterItemsList(val) { - this.currentSelectedFilter = val[0]; + this.currentSelectedFilter = !this.currentFilter + ? val[0] + : this.currentFilter; this.changeFilterSelection(); }, groupByFilterItemsList() { diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportHeader.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportHeader.vue index a42442a9d..c7643a5fe 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportHeader.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportHeader.vue @@ -1,17 +1,41 @@ diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/SummaryReportLink.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SummaryReportLink.vue new file mode 100644 index 000000000..fdb201d25 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SummaryReportLink.vue @@ -0,0 +1,20 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/SummaryReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SummaryReports.vue new file mode 100644 index 000000000..4c0c4fe64 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SummaryReports.vue @@ -0,0 +1,184 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue index b0e6b84a0..b3d66a8eb 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue @@ -54,12 +54,20 @@ export default { type: String, default: 'Download Reports', }, + hasBackButton: { + type: Boolean, + default: false, + }, + selectedItem: { + type: Object, + default: null, + }, }, data() { return { from: 0, to: 0, - selectedFilter: null, + selectedFilter: this.selectedItem, groupBy: GROUP_BY_FILTER[1], groupByfilterItemsList: GROUP_BY_OPTIONS.DAY.map(this.translateOptions), selectedGroupByFilter: null, @@ -206,7 +214,7 @@ export default {