diff --git a/app/javascript/dashboard/api/reports.js b/app/javascript/dashboard/api/reports.js index 9f7875c79..9abe68405 100644 --- a/app/javascript/dashboard/api/reports.js +++ b/app/javascript/dashboard/api/reports.js @@ -6,15 +6,15 @@ class ReportsAPI extends ApiClient { super('reports', { accountScoped: true, apiVersion: 'v2' }); } - getAccountReports(metric, since, until) { + getReports(metric, since, until, type = 'account', id) { return axios.get(`${this.url}`, { - params: { metric, since, until, type: 'account' }, + params: { metric, since, until, type, id }, }); } - getAccountSummary(since, until) { + getSummary(since, until, type = 'account', id) { return axios.get(`${this.url}/summary`, { - params: { since, until, type: 'account' }, + params: { since, until, type, id }, }); } @@ -23,6 +23,18 @@ class ReportsAPI extends ApiClient { params: { since, until }, }); } + + getLabelReports(since, until) { + return axios.get(`${this.url}/labels`, { + params: { since, until }, + }); + } + + getInboxReports(since, until) { + return axios.get(`${this.url}/inboxes`, { + params: { since, until }, + }); + } } export default new ReportsAPI(); diff --git a/app/javascript/dashboard/api/specs/reports.spec.js b/app/javascript/dashboard/api/specs/reports.spec.js index 72d5b7a90..81b4d8429 100644 --- a/app/javascript/dashboard/api/specs/reports.spec.js +++ b/app/javascript/dashboard/api/specs/reports.spec.js @@ -11,39 +11,34 @@ describe('#Reports API', () => { expect(reportsAPI).toHaveProperty('create'); expect(reportsAPI).toHaveProperty('update'); expect(reportsAPI).toHaveProperty('delete'); - expect(reportsAPI).toHaveProperty('getAccountReports'); - expect(reportsAPI).toHaveProperty('getAccountSummary'); + expect(reportsAPI).toHaveProperty('getReports'); + expect(reportsAPI).toHaveProperty('getSummary'); expect(reportsAPI).toHaveProperty('getAgentReports'); + expect(reportsAPI).toHaveProperty('getLabelReports'); + expect(reportsAPI).toHaveProperty('getInboxReports'); }); describeWithAPIMock('API calls', context => { it('#getAccountReports', () => { - reportsAPI.getAccountReports( - 'conversations_count', - 1621103400, - 1621621800 - ); - expect(context.axiosMock.get).toHaveBeenCalledWith( - '/api/v2/reports', - { - params: { - metric: 'conversations_count', - since: 1621103400, - until: 1621621800, - type: 'account' - }, - } - ); + reportsAPI.getReports('conversations_count', 1621103400, 1621621800); + expect(context.axiosMock.get).toHaveBeenCalledWith('/api/v2/reports', { + params: { + metric: 'conversations_count', + since: 1621103400, + until: 1621621800, + type: 'account', + }, + }); }); it('#getAccountSummary', () => { - reportsAPI.getAccountSummary(1621103400, 1621621800); + reportsAPI.getSummary(1621103400, 1621621800); expect(context.axiosMock.get).toHaveBeenCalledWith( '/api/v2/reports/summary', { params: { since: 1621103400, until: 1621621800, - type: 'account' + type: 'account', }, } ); @@ -61,5 +56,31 @@ describe('#Reports API', () => { } ); }); + + it('#getLabelReports', () => { + reportsAPI.getLabelReports(1621103400, 1621621800); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v2/reports/labels', + { + params: { + since: 1621103400, + until: 1621621800, + }, + } + ); + }); + + it('#getInboxReports', () => { + reportsAPI.getInboxReports(1621103400, 1621621800); + expect(context.axiosMock.get).toHaveBeenCalledWith( + '/api/v2/reports/inboxes', + { + params: { + since: 1621103400, + until: 1621621800, + }, + } + ); + }); }); }); diff --git a/app/javascript/dashboard/assets/scss/widgets/_report.scss b/app/javascript/dashboard/assets/scss/widgets/_report.scss index ca013a39d..0ee598245 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_report.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_report.scss @@ -32,7 +32,6 @@ } } - .report-bar { @include margin(-1px $zero); @include background-white; diff --git a/app/javascript/dashboard/assets/scss/widgets/_reports.scss b/app/javascript/dashboard/assets/scss/widgets/_reports.scss new file mode 100644 index 000000000..7280b6d4d --- /dev/null +++ b/app/javascript/dashboard/assets/scss/widgets/_reports.scss @@ -0,0 +1,30 @@ +.date-picker { + margin-left: var(--space-smaller); +} + +.margin-left-small { + margin-left: var(--space-smaller); +} + +.reports-option__rounded--item { + border-radius: 100%; + height: var(--space-two); + width: var(--space-two); +} + +.reports-option__item { + flex-shrink: 0; + margin-right: var(--space-small); +} + +.reports-option__label--swatch { + border: 1px solid var(--color-border); +} + +.margin-right-small { + margin-right: var(--space-small); +} + +.display-flex { + display: flex; +} diff --git a/app/javascript/dashboard/i18n/default-sidebar.js b/app/javascript/dashboard/i18n/default-sidebar.js index 5dbe18f54..4f4a237a9 100644 --- a/app/javascript/dashboard/i18n/default-sidebar.js +++ b/app/javascript/dashboard/i18n/default-sidebar.js @@ -1,245 +1,13 @@ -import { frontendURL } from '../helper/URLHelper'; +import common from './sidebarItems/common'; +import contacts from './sidebarItems/contacts'; +import reports from './sidebarItems/reports'; +import campaigns from './sidebarItems/campaigns'; +import settings from './sidebarItems/settings'; export const getSidebarItems = accountId => ({ - common: { - routes: [ - 'home', - 'inbox_dashboard', - 'inbox_conversation', - 'conversation_through_inbox', - 'notifications_dashboard', - 'profile_settings', - 'profile_settings_index', - 'label_conversations', - 'conversations_through_label', - 'team_conversations', - 'conversations_through_team', - 'notifications_index', - ], - menuItems: { - assignedToMe: { - icon: 'ion-chatbox-working', - label: 'CONVERSATIONS', - hasSubMenu: false, - key: '', - toState: frontendURL(`accounts/${accountId}/dashboard`), - toolTip: 'Conversation from all subscribed inboxes', - toStateName: 'home', - }, - contacts: { - icon: 'ion-person', - label: 'CONTACTS', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/contacts`), - toStateName: 'contacts_dashboard', - }, - notifications: { - icon: 'ion-ios-bell', - label: 'NOTIFICATIONS', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/notifications`), - toStateName: 'notifications_dashboard', - }, - report: { - icon: 'ion-arrow-graph-up-right', - label: 'REPORTS', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/reports`), - toStateName: 'settings_account_reports', - }, - campaigns: { - icon: 'ion-speakerphone', - label: 'CAMPAIGNS', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/campaigns`), - toStateName: 'settings_account_campaigns', - }, - settings: { - icon: 'ion-settings', - label: 'SETTINGS', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/settings`), - toStateName: 'settings_home', - }, - }, - }, - contacts: { - routes: [ - 'contacts_dashboard', - 'contacts_dashboard_manage', - 'contacts_labels_dashboard', - ], - menuItems: { - back: { - icon: 'ion-ios-arrow-back', - label: 'HOME', - hasSubMenu: false, - toStateName: 'home', - toState: frontendURL(`accounts/${accountId}/dashboard`), - }, - contacts: { - icon: 'ion-person', - label: 'ALL_CONTACTS', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/contacts`), - toStateName: 'contacts_dashboard', - }, - }, - }, - reports: { - routes: ['settings_account_reports', 'csat_reports'], - menuItems: { - back: { - icon: 'ion-ios-arrow-back', - label: 'HOME', - hasSubMenu: false, - toStateName: 'home', - toState: frontendURL(`accounts/${accountId}/dashboard`), - }, - reportOverview: { - icon: 'ion-arrow-graph-up-right', - label: 'REPORTS_OVERVIEW', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/reports/overview`), - toStateName: 'settings_account_reports', - }, - csatReports: { - icon: 'ion-happy', - label: 'CSAT', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/reports/csat`), - toStateName: 'csat_reports', - }, - }, - }, - campaigns: { - routes: ['settings_account_campaigns', 'one_off'], - menuItems: { - back: { - icon: 'ion-ios-arrow-back', - label: 'HOME', - hasSubMenu: false, - toStateName: 'home', - toState: frontendURL(`accounts/${accountId}/dashboard`), - }, - ongoingCampaigns: { - icon: 'ion-arrow-swap', - label: 'ONGOING', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/campaigns/ongoing`), - toStateName: 'settings_account_campaigns', - }, - onOffCampaigns: { - icon: 'ion-radio-waves', - label: 'ONE_OFF', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/campaigns/one_off`), - toStateName: 'one_off', - }, - }, - }, - settings: { - routes: [ - 'agent_list', - 'canned_list', - 'labels_list', - 'settings_inbox', - 'attributes_list', - 'settings_inbox_new', - 'settings_inbox_list', - 'settings_inbox_show', - 'settings_inboxes_page_channel', - 'settings_inboxes_add_agents', - 'settings_inbox_finish', - 'settings_integrations', - 'settings_integrations_webhook', - 'settings_integrations_integration', - 'settings_applications', - 'settings_applications_webhook', - 'settings_applications_integration', - 'general_settings', - 'general_settings_index', - 'settings_teams_list', - 'settings_teams_new', - 'settings_teams_add_agents', - 'settings_teams_finish', - 'settings_teams_edit', - 'settings_teams_edit_members', - 'settings_teams_edit_finish', - ], - menuItems: { - back: { - icon: 'ion-ios-arrow-back', - label: 'HOME', - hasSubMenu: false, - toStateName: 'home', - toState: frontendURL(`accounts/${accountId}/dashboard`), - }, - agents: { - icon: 'ion-person-stalker', - label: 'AGENTS', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/settings/agents/list`), - toStateName: 'agent_list', - }, - teams: { - icon: 'ion-ios-people', - label: 'TEAMS', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/settings/teams/list`), - toStateName: 'settings_teams_list', - }, - inboxes: { - icon: 'ion-archive', - label: 'INBOXES', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/settings/inboxes/list`), - toStateName: 'settings_inbox_list', - }, - labels: { - icon: 'ion-pricetags', - label: 'LABELS', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/settings/labels/list`), - toStateName: 'labels_list', - }, - attributes: { - icon: 'ion-code', - label: 'ATTRIBUTES', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/settings/attributes/list`), - toStateName: 'attributes_list', - }, - cannedResponses: { - icon: 'ion-chatbox-working', - label: 'CANNED_RESPONSES', - hasSubMenu: false, - toState: frontendURL( - `accounts/${accountId}/settings/canned-response/list` - ), - toStateName: 'canned_list', - }, - settings_integrations: { - icon: 'ion-flash', - label: 'INTEGRATIONS', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/settings/integrations`), - toStateName: 'settings_integrations', - }, - settings_applications: { - icon: 'ion-asterisk', - label: 'APPLICATIONS', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/settings/applications`), - toStateName: 'settings_applications', - }, - general_settings_index: { - icon: 'ion-gear-a', - label: 'ACCOUNT_SETTINGS', - hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/settings/general`), - toStateName: 'general_settings_index', - }, - }, - }, + common: common(accountId), + contacts: contacts(accountId), + reports: reports(accountId), + campaigns: campaigns(accountId), + settings: settings(accountId), }); diff --git a/app/javascript/dashboard/i18n/locale/en/report.json b/app/javascript/dashboard/i18n/locale/en/report.json index b4c205477..b1885a2aa 100644 --- a/app/javascript/dashboard/i18n/locale/en/report.json +++ b/app/javascript/dashboard/i18n/locale/en/report.json @@ -61,6 +61,195 @@ "PLACEHOLDER": "Select date range" } }, + "AGENT_REPORTS": { + "HEADER": "Agents Overview", + "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", + "METRICS": { + "CONVERSATIONS": { + "NAME": "Conversations", + "DESC": "( Total )" + }, + "INCOMING_MESSAGES": { + "NAME": "Incoming Messages", + "DESC": "( Total )" + }, + "OUTGOING_MESSAGES": { + "NAME": "Outgoing Messages", + "DESC": "( Total )" + }, + "FIRST_RESPONSE_TIME": { + "NAME": "First response time", + "DESC": "( Avg )" + }, + "RESOLUTION_TIME": { + "NAME": "Resolution Time", + "DESC": "( Avg )" + }, + "RESOLUTION_COUNT": { + "NAME": "Resolution Count", + "DESC": "( Total )" + } + }, + "DATE_RANGE": [ + { + "id": 0, + "name": "Last 7 days" + }, + { + "id": 1, + "name": "Last 30 days" + }, + { + "id": 2, + "name": "Last 3 months" + }, + { + "id": 3, + "name": "Last 6 months" + }, + { + "id": 4, + "name": "Last year" + }, + { + "id": 5, + "name": "Custom date range" + } + ], + "CUSTOM_DATE_RANGE": { + "CONFIRM": "Apply", + "PLACEHOLDER": "Select date range" + } + }, + "LABEL_REPORTS": { + "HEADER": "Labels Overview", + "LOADING_CHART": "Loading chart data...", + "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", + "METRICS": { + "CONVERSATIONS": { + "NAME": "Conversations", + "DESC": "( Total )" + }, + "INCOMING_MESSAGES": { + "NAME": "Incoming Messages", + "DESC": "( Total )" + }, + "OUTGOING_MESSAGES": { + "NAME": "Outgoing Messages", + "DESC": "( Total )" + }, + "FIRST_RESPONSE_TIME": { + "NAME": "First response time", + "DESC": "( Avg )" + }, + "RESOLUTION_TIME": { + "NAME": "Resolution Time", + "DESC": "( Avg )" + }, + "RESOLUTION_COUNT": { + "NAME": "Resolution Count", + "DESC": "( Total )" + } + }, + "DATE_RANGE": [ + { + "id": 0, + "name": "Last 7 days" + }, + { + "id": 1, + "name": "Last 30 days" + }, + { + "id": 2, + "name": "Last 3 months" + }, + { + "id": 3, + "name": "Last 6 months" + }, + { + "id": 4, + "name": "Last year" + }, + { + "id": 5, + "name": "Custom date range" + } + ], + "CUSTOM_DATE_RANGE": { + "CONFIRM": "Apply", + "PLACEHOLDER": "Select date range" + } + }, + "INBOX_REPORTS": { + "HEADER": "Inbox Overview", + "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", + "FILTER_DROPDOWN_LABEL": "Select Inbox", + "METRICS": { + "CONVERSATIONS": { + "NAME": "Conversations", + "DESC": "( Total )" + }, + "INCOMING_MESSAGES": { + "NAME": "Incoming Messages", + "DESC": "( Total )" + }, + "OUTGOING_MESSAGES": { + "NAME": "Outgoing Messages", + "DESC": "( Total )" + }, + "FIRST_RESPONSE_TIME": { + "NAME": "First response time", + "DESC": "( Avg )" + }, + "RESOLUTION_TIME": { + "NAME": "Resolution Time", + "DESC": "( Avg )" + }, + "RESOLUTION_COUNT": { + "NAME": "Resolution Count", + "DESC": "( Total )" + } + }, + "DATE_RANGE": [ + { + "id": 0, + "name": "Last 7 days" + }, + { + "id": 1, + "name": "Last 30 days" + }, + { + "id": 2, + "name": "Last 3 months" + }, + { + "id": 3, + "name": "Last 6 months" + }, + { + "id": 4, + "name": "Last year" + }, + { + "id": 5, + "name": "Custom date range" + } + ], + "CUSTOM_DATE_RANGE": { + "CONFIRM": "Apply", + "PLACEHOLDER": "Select date range" + } + }, "CSAT_REPORTS": { "HEADER": "CSAT Reports", "NO_RECORDS": "There are no CSAT survey responses available.", @@ -78,13 +267,13 @@ "TOOLTIP": "Total number of responses collected" }, "SATISFACTION_SCORE": { - "LABEL": "Satisfaction score", - "TOOLTIP": "Total number of positive responses / Total number of responses * 100" + "LABEL": "Satisfaction score", + "TOOLTIP": "Total number of positive responses / Total number of responses * 100" }, "RESPONSE_RATE": { - "LABEL": "Response rate", - "TOOLTIP": "Total number of responses / Total number of CSAT survey messages sent * 100" + "LABEL": "Response rate", + "TOOLTIP": "Total number of responses / Total number of CSAT survey messages sent * 100" } } } -} +} \ No newline at end of file diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json index 0d8b593c9..98ad27840 100644 --- a/app/javascript/dashboard/i18n/locale/en/settings.json +++ b/app/javascript/dashboard/i18n/locale/en/settings.json @@ -146,7 +146,10 @@ "CSAT": "CSAT", "CAMPAIGNS": "Campaigns", "ONGOING": "Ongoing", - "ONE_OFF": "One off" + "ONE_OFF": "One off", + "REPORTS_AGENT": "Agents", + "REPORTS_LABEL": "Labels", + "REPORTS_INBOX": "Inbox" }, "CREATE_ACCOUNT": { "NO_ACCOUNT_WARNING": "Uh oh! We could not find any Chatwoot accounts. Please create a new account to continue.", diff --git a/app/javascript/dashboard/i18n/sidebarItems/campaigns.js b/app/javascript/dashboard/i18n/sidebarItems/campaigns.js new file mode 100644 index 000000000..54c359e8e --- /dev/null +++ b/app/javascript/dashboard/i18n/sidebarItems/campaigns.js @@ -0,0 +1,30 @@ +import { frontendURL } from '../../helper/URLHelper'; + +const campaigns = accountId => ({ + routes: ['settings_account_campaigns', 'one_off'], + menuItems: { + back: { + icon: 'ion-ios-arrow-back', + label: 'HOME', + hasSubMenu: false, + toStateName: 'home', + toState: frontendURL(`accounts/${accountId}/dashboard`), + }, + ongoingCampaigns: { + icon: 'ion-arrow-swap', + label: 'ONGOING', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/campaigns/ongoing`), + toStateName: 'settings_account_campaigns', + }, + onOffCampaigns: { + icon: 'ion-radio-waves', + label: 'ONE_OFF', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/campaigns/one_off`), + toStateName: 'one_off', + }, + }, +}); + +export default campaigns; diff --git a/app/javascript/dashboard/i18n/sidebarItems/common.js b/app/javascript/dashboard/i18n/sidebarItems/common.js new file mode 100644 index 000000000..665b813e5 --- /dev/null +++ b/app/javascript/dashboard/i18n/sidebarItems/common.js @@ -0,0 +1,66 @@ +import { frontendURL } from '../../helper/URLHelper'; + +const common = accountId => ({ + routes: [ + 'home', + 'inbox_dashboard', + 'inbox_conversation', + 'conversation_through_inbox', + 'notifications_dashboard', + 'profile_settings', + 'profile_settings_index', + 'label_conversations', + 'conversations_through_label', + 'team_conversations', + 'conversations_through_team', + 'notifications_index', + ], + menuItems: { + assignedToMe: { + icon: 'ion-chatbox-working', + label: 'CONVERSATIONS', + hasSubMenu: false, + key: '', + toState: frontendURL(`accounts/${accountId}/dashboard`), + toolTip: 'Conversation from all subscribed inboxes', + toStateName: 'home', + }, + contacts: { + icon: 'ion-person', + label: 'CONTACTS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/contacts`), + toStateName: 'contacts_dashboard', + }, + notifications: { + icon: 'ion-ios-bell', + label: 'NOTIFICATIONS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/notifications`), + toStateName: 'notifications_dashboard', + }, + report: { + icon: 'ion-arrow-graph-up-right', + label: 'REPORTS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/reports`), + toStateName: 'settings_account_reports', + }, + campaigns: { + icon: 'ion-speakerphone', + label: 'CAMPAIGNS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/campaigns`), + toStateName: 'settings_account_campaigns', + }, + settings: { + icon: 'ion-settings', + label: 'SETTINGS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/settings`), + toStateName: 'settings_home', + }, + }, +}); + +export default common; diff --git a/app/javascript/dashboard/i18n/sidebarItems/contacts.js b/app/javascript/dashboard/i18n/sidebarItems/contacts.js new file mode 100644 index 000000000..de858d5db --- /dev/null +++ b/app/javascript/dashboard/i18n/sidebarItems/contacts.js @@ -0,0 +1,27 @@ +import { frontendURL } from '../../helper/URLHelper'; + +const contacts = accountId => ({ + routes: [ + 'contacts_dashboard', + 'contacts_dashboard_manage', + 'contacts_labels_dashboard', + ], + menuItems: { + back: { + icon: 'ion-ios-arrow-back', + label: 'HOME', + hasSubMenu: false, + toStateName: 'home', + toState: frontendURL(`accounts/${accountId}/dashboard`), + }, + contacts: { + icon: 'ion-person', + label: 'ALL_CONTACTS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/contacts`), + toStateName: 'contacts_dashboard', + }, + }, +}); + +export default contacts; diff --git a/app/javascript/dashboard/i18n/sidebarItems/reports.js b/app/javascript/dashboard/i18n/sidebarItems/reports.js new file mode 100644 index 000000000..a37536760 --- /dev/null +++ b/app/javascript/dashboard/i18n/sidebarItems/reports.js @@ -0,0 +1,57 @@ +import { frontendURL } from '../../helper/URLHelper'; + +const reports = accountId => ({ + routes: [ + 'settings_account_reports', + 'csat_reports', + 'agent_reports', + 'label_reports', + 'inbox_reports', + ], + menuItems: { + back: { + icon: 'ion-ios-arrow-back', + label: 'HOME', + hasSubMenu: false, + toStateName: 'home', + toState: frontendURL(`accounts/${accountId}/dashboard`), + }, + reportOverview: { + icon: 'ion-arrow-graph-up-right', + label: 'REPORTS_OVERVIEW', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/reports/overview`), + toStateName: 'settings_account_reports', + }, + csatReports: { + icon: 'ion-happy', + label: 'CSAT', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/reports/csat`), + toStateName: 'csat_reports', + }, + agentReports: { + icon: 'ion-ios-people', + label: 'REPORTS_AGENT', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/reports/agent`), + toStateName: 'agent_reports', + }, + labelReports: { + icon: 'ion-pricetags', + label: 'REPORTS_LABEL', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/reports/label`), + toStateName: 'label_reports', + }, + inboxReports: { + icon: 'ion-archive', + label: 'REPORTS_INBOX', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/reports/inboxes`), + toStateName: 'inbox_reports', + }, + }, +}); + +export default reports; diff --git a/app/javascript/dashboard/i18n/sidebarItems/settings.js b/app/javascript/dashboard/i18n/sidebarItems/settings.js new file mode 100644 index 000000000..406b8fa10 --- /dev/null +++ b/app/javascript/dashboard/i18n/sidebarItems/settings.js @@ -0,0 +1,108 @@ +import { frontendURL } from '../../helper/URLHelper'; + +const settings = accountId => ({ + routes: [ + 'agent_list', + 'canned_list', + 'labels_list', + 'settings_inbox', + 'attributes_list', + 'settings_inbox_new', + 'settings_inbox_list', + 'settings_inbox_show', + 'settings_inboxes_page_channel', + 'settings_inboxes_add_agents', + 'settings_inbox_finish', + 'settings_integrations', + 'settings_integrations_webhook', + 'settings_integrations_integration', + 'settings_applications', + 'settings_applications_webhook', + 'settings_applications_integration', + 'general_settings', + 'general_settings_index', + 'settings_teams_list', + 'settings_teams_new', + 'settings_teams_add_agents', + 'settings_teams_finish', + 'settings_teams_edit', + 'settings_teams_edit_members', + 'settings_teams_edit_finish', + ], + menuItems: { + back: { + icon: 'ion-ios-arrow-back', + label: 'HOME', + hasSubMenu: false, + toStateName: 'home', + toState: frontendURL(`accounts/${accountId}/dashboard`), + }, + agents: { + icon: 'ion-person-stalker', + label: 'AGENTS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/settings/agents/list`), + toStateName: 'agent_list', + }, + teams: { + icon: 'ion-ios-people', + label: 'TEAMS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/settings/teams/list`), + toStateName: 'settings_teams_list', + }, + inboxes: { + icon: 'ion-archive', + label: 'INBOXES', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/settings/inboxes/list`), + toStateName: 'settings_inbox_list', + }, + labels: { + icon: 'ion-pricetags', + label: 'LABELS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/settings/labels/list`), + toStateName: 'labels_list', + }, + attributes: { + icon: 'ion-code', + label: 'ATTRIBUTES', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/settings/attributes/list`), + toStateName: 'attributes_list', + }, + cannedResponses: { + icon: 'ion-chatbox-working', + label: 'CANNED_RESPONSES', + hasSubMenu: false, + toState: frontendURL( + `accounts/${accountId}/settings/canned-response/list` + ), + toStateName: 'canned_list', + }, + settings_integrations: { + icon: 'ion-flash', + label: 'INTEGRATIONS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/settings/integrations`), + toStateName: 'settings_integrations', + }, + settings_applications: { + icon: 'ion-asterisk', + label: 'APPLICATIONS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/settings/applications`), + toStateName: 'settings_applications', + }, + general_settings_index: { + icon: 'ion-gear-a', + label: 'ACCOUNT_SETTINGS', + hasSubMenu: false, + toState: frontendURL(`accounts/${accountId}/settings/general`), + toStateName: 'general_settings_index', + }, + }, +}); + +export default settings; diff --git a/app/javascript/dashboard/routes/dashboard/Dashboard.vue b/app/javascript/dashboard/routes/dashboard/Dashboard.vue index 1b0744bbd..e4c525488 100644 --- a/app/javascript/dashboard/routes/dashboard/Dashboard.vue +++ b/app/javascript/dashboard/routes/dashboard/Dashboard.vue @@ -2,7 +2,7 @@
- +
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/AgentReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/AgentReports.vue new file mode 100644 index 000000000..cd8ff4421 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/AgentReports.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/InboxReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/InboxReports.vue new file mode 100644 index 000000000..0ee04ff0b --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/InboxReports.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/LabelReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/LabelReports.vue new file mode 100644 index 000000000..0a2e25dd9 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/LabelReports.vue @@ -0,0 +1,19 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportFilters.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportFilters.vue new file mode 100644 index 000000000..b60804661 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/ReportFilters.vue @@ -0,0 +1,224 @@ + + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue new file mode 100644 index 000000000..64821f62e --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/WootReports.vue @@ -0,0 +1,199 @@ + + + diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js b/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js index 737c91e7b..96e9062fa 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js +++ b/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js @@ -1,4 +1,7 @@ import Index from './Index'; +import AgentReports from './AgentReports'; +import LabelReports from './LabelReports'; +import InboxReports from './InboxReports'; import CsatResponses from './CsatResponses'; import SettingsContent from '../Wrapper'; import { frontendURL } from '../../../../helper/URLHelper'; @@ -41,5 +44,53 @@ export default { }, ], }, + { + path: frontendURL('accounts/:accountId/reports'), + component: SettingsContent, + props: { + headerTitle: 'AGENT_REPORTS.HEADER', + icon: 'ion-people', + }, + children: [ + { + path: 'agent', + name: 'agent_reports', + roles: ['administrator'], + component: AgentReports, + }, + ], + }, + { + path: frontendURL('accounts/:accountId/reports'), + component: SettingsContent, + props: { + headerTitle: 'LABEL_REPORTS.HEADER', + icon: 'ion-pricetags', + }, + children: [ + { + path: 'label', + name: 'label_reports', + roles: ['administrator'], + component: LabelReports, + }, + ], + }, + { + path: frontendURL('accounts/:accountId/reports'), + component: SettingsContent, + props: { + headerTitle: 'INBOX_REPORTS.HEADER', + icon: 'ion-archive', + }, + children: [ + { + path: 'inboxes', + name: 'inbox_reports', + roles: ['administrator'], + component: InboxReports, + }, + ], + }, ], }; diff --git a/app/javascript/dashboard/store/modules/reports.js b/app/javascript/dashboard/store/modules/reports.js index 7b47583a3..828e8114f 100644 --- a/app/javascript/dashboard/store/modules/reports.js +++ b/app/javascript/dashboard/store/modules/reports.js @@ -36,10 +36,12 @@ const getters = { export const actions = { fetchAccountReport({ commit }, reportObj) { commit(types.default.TOGGLE_ACCOUNT_REPORT_LOADING, true); - Report.getAccountReports( + Report.getReports( reportObj.metric, reportObj.from, - reportObj.to + reportObj.to, + reportObj.type, + reportObj.id ).then(accountReport => { let { data } = accountReport; data = data.filter( @@ -60,7 +62,12 @@ export const actions = { }); }, fetchAccountSummary({ commit }, reportObj) { - Report.getAccountSummary(reportObj.from, reportObj.to) + Report.getSummary( + reportObj.from, + reportObj.to, + reportObj.type, + reportObj.id + ) .then(accountSummary => { commit(types.default.SET_ACCOUNT_SUMMARY, accountSummary.data); }) @@ -85,6 +92,40 @@ export const actions = { console.error(error); }); }, + downloadLabelReports(_, reportObj) { + return Report.getLabelReports(reportObj.from, reportObj.to) + .then(response => { + let csvContent = 'data:text/csv;charset=utf-8,' + response.data; + var encodedUri = encodeURI(csvContent); + var downloadLink = document.createElement('a'); + downloadLink.href = encodedUri; + downloadLink.download = reportObj.fileName; + + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); + }) + .catch(error => { + console.error(error); + }); + }, + downloadInboxReports(_, reportObj) { + return Report.getInboxReports(reportObj.from, reportObj.to) + .then(response => { + let csvContent = 'data:text/csv;charset=utf-8,' + response.data; + var encodedUri = encodeURI(csvContent); + var downloadLink = document.createElement('a'); + downloadLink.href = encodedUri; + downloadLink.download = reportObj.fileName; + + document.body.appendChild(downloadLink); + downloadLink.click(); + // document.body.removeChild(downloadLink); + }) + .catch(error => { + console.error(error); + }); + }, }; const mutations = { diff --git a/app/javascript/dashboard/store/modules/specs/reports/actions.spec.js b/app/javascript/dashboard/store/modules/specs/reports/actions.spec.js index 57eea00f9..10ccbdcd3 100644 --- a/app/javascript/dashboard/store/modules/specs/reports/actions.spec.js +++ b/app/javascript/dashboard/store/modules/specs/reports/actions.spec.js @@ -5,7 +5,17 @@ global.open = jest.fn(); global.axios = axios; jest.mock('axios'); +const createElementSpy = () => { + const element = document.createElement('a'); + jest.spyOn(document, 'createElement').mockImplementation(() => element); + return element; +}; + describe('#actions', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + describe('#downloadAgentReports', () => { it('open CSV download prompt if API is success', async () => { axios.get.mockResolvedValue({ @@ -17,15 +27,55 @@ describe('#actions', () => { to: 1630504922510, fileName: 'agent-report-01-09-2021.csv', }; - const mockDownloadElement = document.createElement('a'); - jest - .spyOn(document, 'createElement') - .mockImplementation(() => mockDownloadElement); + const mockAgentDownloadElement = createElementSpy(); await actions.downloadAgentReports(1, param); - expect(mockDownloadElement.href).toEqual( + expect(mockAgentDownloadElement.href).toEqual( 'data:text/csv;charset=utf-8,Agent%20name,Conversations%20count,Avg%20first%20response%20time%20(Minutes),Avg%20resolution%20time%20(Minutes)%0A%20%20%20%20%20%20%20%20Pranav,36,114,28411' ); - expect(mockDownloadElement.download).toEqual(param.fileName); + expect(mockAgentDownloadElement.download).toEqual(param.fileName); + }); + }); + + describe('#downloadLabelReports', () => { + it('open CSV download prompt if API is success', async () => { + axios.get.mockResolvedValue({ + data: `Label Title,Conversations count,Avg first response time (Minutes),Avg resolution time (Minutes) + website,0,0,0`, + }); + const param = { + from: 1632335400, + to: 1632853800, + type: 'label', + fileName: 'label-report-01-09-2021.csv', + }; + const mockLabelDownloadElement = createElementSpy(); + await actions.downloadLabelReports(1, param); + expect(mockLabelDownloadElement.href).toEqual( + 'data:text/csv;charset=utf-8,Label%20Title,Conversations%20count,Avg%20first%20response%20time%20(Minutes),Avg%20resolution%20time%20(Minutes)%0A%20%20%20%20%20%20%20%20website,0,0,0' + ); + expect(mockLabelDownloadElement.download).toEqual(param.fileName); + }); + }); + + describe('#downloadInboxReports', () => { + it('open CSV download prompt if API is success', async () => { + axios.get.mockResolvedValue({ + data: `Inbox name,Conversations count,Avg first response time (Minutes),Avg resolution time (Minutes) + Fayaz,2,127,0 + EMa,0,0,0 + Twillio WA,0,0,0`, + }); + const param = { + from: 1631039400, + to: 1635013800, + fileName: 'inbox-report-24-10-2021.csv', + }; + const mockInboxDownloadElement = createElementSpy(); + await actions.downloadInboxReports(1, param); + expect(mockInboxDownloadElement.href).toEqual( + 'data:text/csv;charset=utf-8,Inbox%20name,Conversations%20count,Avg%20first%20response%20time%20(Minutes),Avg%20resolution%20time%20(Minutes)%0A%20%20%20%20%20%20%20%20Fayaz,2,127,0%0A%20%20%20%20%20%20%20%20EMa,0,0,0%0A%20%20%20%20%20%20%20%20Twillio%20WA,0,0,0' + ); + expect(mockInboxDownloadElement.download).toEqual(param.fileName); }); }); });