From 8f9c45ea8cb2af800d1f991d76e08a3fa4ae63c4 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Thu, 31 Jul 2025 14:58:10 +0530 Subject: [PATCH] add assignment v2 UI --- .../dashboard/api/agentCapacityPolicies.js | 66 +++ .../dashboard/api/assignmentMetrics.js | 52 ++ .../dashboard/api/assignmentPolicies.js | 35 ++ app/javascript/dashboard/api/inboxes.js | 10 + app/javascript/dashboard/api/leaves.js | 55 ++ .../components-next/button/Button.vue | 5 + .../filter/inputs/MultiSelect.vue | 1 + .../components-next/sidebar/Sidebar.vue | 6 + .../widgets/modal/ConfirmationModal.vue | 34 +- .../i18n/locale/en/assignmentSettings.json | 325 ++++++++++++ .../i18n/locale/en/generalSettings.json | 14 +- .../dashboard/i18n/locale/en/index.js | 2 + .../dashboard/i18n/locale/en/settings.json | 1 + .../dashboard/settings/assignment/Index.vue | 94 ++++ .../settings/assignment/assignment.routes.js | 121 +++++ .../assignment/capacity/AgentCapacity.vue | 233 +++++++++ .../assignment/capacity/CapacityForm.vue | 432 ++++++++++++++++ .../assignment/leaves/LeaveDetails.vue | 264 ++++++++++ .../settings/assignment/leaves/LeaveForm.vue | 230 +++++++++ .../assignment/leaves/LeaveManagement.vue | 482 ++++++++++++++++++ .../leaves/components/ApprovalModal.vue | 200 ++++++++ .../assignment/metrics/AssignmentMetrics.vue | 480 +++++++++++++++++ .../assignment/policies/PolicyForm.vue | 342 +++++++++++++ .../assignment/policies/PolicyList.vue | 415 +++++++++++++++ .../components/BaseSettingsHeader.vue | 3 + .../dashboard/settings/inbox/Settings.vue | 4 +- .../dashboard/settings/settings.routes.js | 2 + app/javascript/dashboard/store/index.js | 8 + .../store/modules/agentCapacity/actions.js | 139 +++++ .../store/modules/agentCapacity/getters.js | 30 ++ .../store/modules/agentCapacity/index.js | 23 + .../store/modules/agentCapacity/mutations.js | 60 +++ .../modules/assignmentMetrics/actions.js | 137 +++++ .../modules/assignmentMetrics/getters.js | 62 +++ .../store/modules/assignmentMetrics/index.js | 37 ++ .../modules/assignmentMetrics/mutations.js | 39 ++ .../modules/assignmentPolicies/actions.js | 67 +++ .../modules/assignmentPolicies/getters.js | 11 + .../store/modules/assignmentPolicies/index.js | 21 + .../modules/assignmentPolicies/mutations.js | 27 + .../dashboard/store/modules/leaves/actions.js | 125 +++++ .../dashboard/store/modules/leaves/getters.js | 41 ++ .../dashboard/store/modules/leaves/index.js | 27 + .../store/modules/leaves/mutations.js | 31 ++ package.json | 16 +- pnpm-lock.yaml | 77 ++- 46 files changed, 4844 insertions(+), 42 deletions(-) create mode 100644 app/javascript/dashboard/api/agentCapacityPolicies.js create mode 100644 app/javascript/dashboard/api/assignmentMetrics.js create mode 100644 app/javascript/dashboard/api/assignmentPolicies.js create mode 100644 app/javascript/dashboard/api/leaves.js create mode 100644 app/javascript/dashboard/i18n/locale/en/assignmentSettings.json create mode 100644 app/javascript/dashboard/routes/dashboard/settings/assignment/Index.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/assignment/assignment.routes.js create mode 100644 app/javascript/dashboard/routes/dashboard/settings/assignment/capacity/AgentCapacity.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/assignment/capacity/CapacityForm.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/assignment/leaves/LeaveDetails.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/assignment/leaves/LeaveForm.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/assignment/leaves/LeaveManagement.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/assignment/leaves/components/ApprovalModal.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/assignment/metrics/AssignmentMetrics.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/assignment/policies/PolicyForm.vue create mode 100644 app/javascript/dashboard/routes/dashboard/settings/assignment/policies/PolicyList.vue create mode 100644 app/javascript/dashboard/store/modules/agentCapacity/actions.js create mode 100644 app/javascript/dashboard/store/modules/agentCapacity/getters.js create mode 100644 app/javascript/dashboard/store/modules/agentCapacity/index.js create mode 100644 app/javascript/dashboard/store/modules/agentCapacity/mutations.js create mode 100644 app/javascript/dashboard/store/modules/assignmentMetrics/actions.js create mode 100644 app/javascript/dashboard/store/modules/assignmentMetrics/getters.js create mode 100644 app/javascript/dashboard/store/modules/assignmentMetrics/index.js create mode 100644 app/javascript/dashboard/store/modules/assignmentMetrics/mutations.js create mode 100644 app/javascript/dashboard/store/modules/assignmentPolicies/actions.js create mode 100644 app/javascript/dashboard/store/modules/assignmentPolicies/getters.js create mode 100644 app/javascript/dashboard/store/modules/assignmentPolicies/index.js create mode 100644 app/javascript/dashboard/store/modules/assignmentPolicies/mutations.js create mode 100644 app/javascript/dashboard/store/modules/leaves/actions.js create mode 100644 app/javascript/dashboard/store/modules/leaves/getters.js create mode 100644 app/javascript/dashboard/store/modules/leaves/index.js create mode 100644 app/javascript/dashboard/store/modules/leaves/mutations.js diff --git a/app/javascript/dashboard/api/agentCapacityPolicies.js b/app/javascript/dashboard/api/agentCapacityPolicies.js new file mode 100644 index 000000000..ebcce0b98 --- /dev/null +++ b/app/javascript/dashboard/api/agentCapacityPolicies.js @@ -0,0 +1,66 @@ +/* global axios */ +import ApiClient from './ApiClient'; + +export class AgentCapacityPoliciesAPI extends ApiClient { + constructor() { + super('agent_capacity_policies', { accountScoped: true }); + } + + // GET /api/v1/accounts/:accountId/agent_capacity_policies + get(params = {}) { + return axios.get(this.url, { params }); + } + + // GET /api/v1/accounts/:accountId/agent_capacity_policies/:id + show(id) { + return axios.get(`${this.url}/${id}`); + } + + // POST /api/v1/accounts/:accountId/agent_capacity_policies + create(data) { + return axios.post(this.url, { agent_capacity_policy: data }); + } + + // PUT /api/v1/accounts/:accountId/agent_capacity_policies/:id + update(id, data) { + return axios.put(`${this.url}/${id}`, { agent_capacity_policy: data }); + } + + // DELETE /api/v1/accounts/:accountId/agent_capacity_policies/:id + delete(id) { + return axios.delete(`${this.url}/${id}`); + } + + // POST /api/v1/accounts/:accountId/agent_capacity_policies/:id/users + assignUser(id, userId) { + return axios.post(`${this.url}/${id}/users`, { + user_id: userId, + }); + } + + // DELETE /api/v1/accounts/:accountId/agent_capacity_policies/:id/users/:userId + removeUser(id, userId) { + return axios.delete(`${this.url}/${id}/users/${userId}`); + } + + // POST /api/v1/accounts/:accountId/agent_capacity_policies/:id/inbox_limits/:inboxId + setInboxLimit(id, inboxId, conversationLimit) { + return axios.post(`${this.url}/${id}/inbox_limits/${inboxId}`, { + conversation_limit: conversationLimit, + }); + } + + // DELETE /api/v1/accounts/:accountId/agent_capacity_policies/:id/inbox_limits/:inboxId + removeInboxLimit(id, inboxId) { + return axios.delete(`${this.url}/${id}/inbox_limits/${inboxId}`); + } + + // GET /api/v1/accounts/:accountId/agents/:agentId/capacity + getAgentCapacity(accountId, agentId, inboxId = null) { + const url = `/api/v1/accounts/${accountId}/agents/${agentId}/capacity`; + const params = inboxId ? { inbox_id: inboxId } : {}; + return axios.get(url, { params }); + } +} + +export default new AgentCapacityPoliciesAPI(); diff --git a/app/javascript/dashboard/api/assignmentMetrics.js b/app/javascript/dashboard/api/assignmentMetrics.js new file mode 100644 index 000000000..afb6a9286 --- /dev/null +++ b/app/javascript/dashboard/api/assignmentMetrics.js @@ -0,0 +1,52 @@ +/* global axios */ +import ApiClient from './ApiClient'; + +export class AssignmentMetricsAPI extends ApiClient { + constructor() { + super('reports/assignment_metrics', { accountScoped: true }); + } + + // GET /api/v1/accounts/:accountId/reports/assignment_metrics + get(params = {}) { + return axios.get(this.url, { params }); + } + + // GET /api/v1/accounts/:accountId/agents/:userId/assignment_history + getAgentHistory(userId, params = {}) { + return axios.get( + `/api/v1/accounts/${this.accountId}/agents/${userId}/assignment_history`, + { params } + ); + } + + // Get all agents assignment history + getAllAgentsHistory(params = {}) { + return axios.get(this.url, { + params: { ...params, include_agent_history: true }, + }); + } + + // Get policy performance metrics + getPolicyPerformance(params = {}) { + return axios.get(this.url, { + params: { ...params, include_policy_performance: true }, + }); + } + + // Get agent utilization metrics + getAgentUtilization(params = {}) { + return axios.get(this.url, { + params: { ...params, include_utilization: true }, + }); + } + + // Export report data + exportReport(type, params = {}) { + return axios.get(`${this.url}/export`, { + params: { type, ...params }, + responseType: 'blob', + }); + } +} + +export default new AssignmentMetricsAPI(); diff --git a/app/javascript/dashboard/api/assignmentPolicies.js b/app/javascript/dashboard/api/assignmentPolicies.js new file mode 100644 index 000000000..14c0eefbc --- /dev/null +++ b/app/javascript/dashboard/api/assignmentPolicies.js @@ -0,0 +1,35 @@ +/* global axios */ +import ApiClient from './ApiClient'; + +export class AssignmentPoliciesAPI extends ApiClient { + constructor() { + super('assignment_policies', { accountScoped: true }); + } + + // GET /api/v1/accounts/:accountId/assignment_policies + get() { + return axios.get(this.url); + } + + // GET /api/v1/accounts/:accountId/assignment_policies/:id + show(id) { + return axios.get(`${this.url}/${id}`); + } + + // POST /api/v1/accounts/:accountId/assignment_policies + create(data) { + return axios.post(this.url, { assignment_policy: data }); + } + + // PUT /api/v1/accounts/:accountId/assignment_policies/:id + update(id, data) { + return axios.put(`${this.url}/${id}`, { assignment_policy: data }); + } + + // DELETE /api/v1/accounts/:accountId/assignment_policies/:id + delete(id) { + return axios.delete(`${this.url}/${id}`); + } +} + +export default new AssignmentPoliciesAPI(); diff --git a/app/javascript/dashboard/api/inboxes.js b/app/javascript/dashboard/api/inboxes.js index 361b9472f..0e3fbacf2 100644 --- a/app/javascript/dashboard/api/inboxes.js +++ b/app/javascript/dashboard/api/inboxes.js @@ -32,6 +32,16 @@ class Inboxes extends CacheEnabledApiClient { syncTemplates(inboxId) { return axios.post(`${this.url}/${inboxId}/sync_templates`); } + + assignPolicy(inboxId, policyId) { + return axios.post(`${this.url}/${inboxId}/assignment_policy`, { + assignment_policy_id: policyId, + }); + } + + removePolicy(inboxId) { + return axios.delete(`${this.url}/${inboxId}/assignment_policy`); + } } export default new Inboxes(); diff --git a/app/javascript/dashboard/api/leaves.js b/app/javascript/dashboard/api/leaves.js new file mode 100644 index 000000000..cdd7b328c --- /dev/null +++ b/app/javascript/dashboard/api/leaves.js @@ -0,0 +1,55 @@ +/* global axios */ +import ApiClient from './ApiClient'; + +export class LeavesAPI extends ApiClient { + constructor() { + super('leaves', { accountScoped: true }); + } + + // GET /api/v1/accounts/:accountId/leaves + get(params = {}) { + return axios.get(this.url, { params }); + } + + // GET /api/v1/accounts/:accountId/leaves/:id + show(id) { + return axios.get(`${this.url}/${id}`); + } + + // POST /api/v1/accounts/:accountId/leaves + create(data) { + return axios.post(this.url, { leave: data.leave, user_id: data.user_id }); + } + + // PUT /api/v1/accounts/:accountId/leaves/:id + update(id, data) { + return axios.put(`${this.url}/${id}`, { leave: data }); + } + + // DELETE /api/v1/accounts/:accountId/leaves/:id + delete(id) { + return axios.delete(`${this.url}/${id}`); + } + + // POST /api/v1/accounts/:accountId/leaves/:id/approve + approve(id, data = {}) { + return axios.post(`${this.url}/${id}/approve`, data); + } + + // POST /api/v1/accounts/:accountId/leaves/:id/reject + reject(id, data = {}) { + return axios.post(`${this.url}/${id}/reject`, data); + } + + // GET /api/v1/accounts/:accountId/leaves/my_leaves + getMyLeaves(params = {}) { + return axios.get(`${this.url}/my_leaves`, { params }); + } + + // GET /api/v1/accounts/:accountId/leaves/pending_approvals + getPendingApprovals(params = {}) { + return axios.get(`${this.url}/pending_approvals`, { params }); + } +} + +export default new LeavesAPI(); diff --git a/app/javascript/dashboard/components-next/button/Button.vue b/app/javascript/dashboard/components-next/button/Button.vue index dde1d3d9a..347c79a6d 100644 --- a/app/javascript/dashboard/components-next/button/Button.vue +++ b/app/javascript/dashboard/components-next/button/Button.vue @@ -54,6 +54,11 @@ const filteredAttrs = computed(() => { standardAttrs[key] = value; }); + // Default to type="button" to prevent accidental form submissions + if (!standardAttrs.type) { + standardAttrs.type = 'button'; + } + return standardAttrs; }); diff --git a/app/javascript/dashboard/components-next/filter/inputs/MultiSelect.vue b/app/javascript/dashboard/components-next/filter/inputs/MultiSelect.vue index be11847fb..93e455208 100644 --- a/app/javascript/dashboard/components-next/filter/inputs/MultiSelect.vue +++ b/app/javascript/dashboard/components-next/filter/inputs/MultiSelect.vue @@ -91,6 +91,7 @@ const toggleOption = option => {