diff --git a/app/controllers/api/v1/accounts/agents_controller.rb b/app/controllers/api/v1/accounts/agents_controller.rb index 438944f04..d0e7093b9 100644 --- a/app/controllers/api/v1/accounts/agents_controller.rb +++ b/app/controllers/api/v1/accounts/agents_controller.rb @@ -23,7 +23,9 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController end def update - @agent.update!(agent_params.slice(:name).compact) + user_attrs = agent_params.slice(:name).compact + user_attrs[:ui_settings] = merged_ui_settings if agent_params[:ui_settings].present? + @agent.update!(user_attrs) if user_attrs.any? @agent.current_account_user.update!(agent_params.slice(*account_user_attributes).compact) end @@ -72,13 +74,19 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController end def allowed_agent_params - [:name, :email, :role, :availability, :auto_offline] + [:name, :email, :role, :availability, :auto_offline, { ui_settings: [:aggressive_alert_inbox_ids_mode, { aggressive_alert_inbox_ids: [] }] }] end def agent_params params.require(:agent).permit(allowed_agent_params) end + def merged_ui_settings + existing = @agent.ui_settings || {} + incoming = agent_params[:ui_settings].to_h.deep_stringify_keys + existing.merge(incoming) + end + def new_agent_params params.require(:agent).permit(:email, :name, :role, :availability, :auto_offline) end diff --git a/app/javascript/dashboard/components/app/AggressiveConversationBanner.vue b/app/javascript/dashboard/components/app/AggressiveConversationBanner.vue index 03e6e96cd..27445b199 100644 --- a/app/javascript/dashboard/components/app/AggressiveConversationBanner.vue +++ b/app/javascript/dashboard/components/app/AggressiveConversationBanner.vue @@ -17,7 +17,18 @@ export default { ...mapGetters({ currentAccountId: 'getCurrentAccountId', allConversations: 'getAllConversations', + currentUser: 'getCurrentUser', }), + allowedInboxIds() { + // null → sem filtro (todas); array → só essas. + const raw = + this.currentUser && + this.currentUser.ui_settings && + this.currentUser.ui_settings.aggressive_alert_inbox_ids; + if (raw == null) return null; + if (!Array.isArray(raw)) return null; + return raw.map(id => Number(id)); + }, hasAlerts() { return this.alerts.length > 0; }, @@ -78,7 +89,14 @@ export default { // quando o usuário só abriu a aba sem receber mensagem ao vivo. allConversations: { handler(conversations) { - inactivityAlertTracker.hydrateFromConversations(conversations); + const allowed = this.allowedInboxIds; + const filtered = + allowed === null + ? conversations + : (conversations || []).filter(c => + allowed.includes(Number(c && c.inbox_id)) + ); + inactivityAlertTracker.hydrateFromConversations(filtered); }, immediate: true, }, diff --git a/app/javascript/dashboard/helper/actionCable.js b/app/javascript/dashboard/helper/actionCable.js index e1fe9a99c..acd0952df 100644 --- a/app/javascript/dashboard/helper/actionCable.js +++ b/app/javascript/dashboard/helper/actionCable.js @@ -133,12 +133,14 @@ class ActionCableConnector extends BaseActionCableConnector { const isIncoming = messageType === 0 || messageType === 'incoming'; const conversationStatus = conversation && conversation.status; if (isIncoming && conversationStatus === 'open') { + const inboxId = conversation && conversation.inbox_id; + if (!this.isInboxAllowedForUser(inboxId)) return; const contactName = conversation && conversation.meta && conversation.meta.sender ? conversation.meta.sender.name : ''; const inbox = this.app.$store.getters['inboxes/getInbox'] - ? this.app.$store.getters['inboxes/getInbox'](conversation.inbox_id) + ? this.app.$store.getters['inboxes/getInbox'](inboxId) : null; const inboxName = inbox && inbox.name ? inbox.name : ''; inactivityAlertTracker.onClientMessage({ @@ -174,6 +176,22 @@ class ActionCableConnector extends BaseActionCableConnector { return accountEnabled && userEnabled; }; + // Filtra alertas por inbox conforme a preferência do user. + // ui_settings.aggressive_alert_inbox_ids: + // - null/undefined → todas as inboxes (default, legado) + // - [] (vazio) → nenhuma inbox (silenciou tudo) + // - [1, 2, 3] → só essas inboxes + isInboxAllowedForUser = inboxId => { + if (inboxId == null) return true; + const user = this.app.$store.getters.getCurrentUser; + const allowed = + user && user.ui_settings && user.ui_settings.aggressive_alert_inbox_ids; + if (allowed == null) return true; + if (!Array.isArray(allowed)) return true; + // Inbox ids podem vir como number no evento e string no ui_settings. + return allowed.some(id => Number(id) === Number(inboxId)); + }; + // eslint-disable-next-line class-methods-use-this onReload = () => window.location.reload(); @@ -194,6 +212,7 @@ class ActionCableConnector extends BaseActionCableConnector { maybeTriggerAggressiveAlert = data => { if (!data || data.status !== 'open') return; if (!this.isAggressiveAlertEnabled()) return; + if (!this.isInboxAllowedForUser(data.inbox_id)) return; const store = this.app.$store; const contactName = data.meta && data.meta.sender ? data.meta.sender.name : ''; diff --git a/app/javascript/dashboard/i18n/locale/en/agentMgmt.json b/app/javascript/dashboard/i18n/locale/en/agentMgmt.json index 448994e69..b97e41814 100644 --- a/app/javascript/dashboard/i18n/locale/en/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/agentMgmt.json @@ -94,6 +94,13 @@ "ADMIN_SUCCESS_MESSAGE": "An email with reset password instructions has been sent to the agent", "SUCCESS_MESSAGE": "Agent password reset successfully", "ERROR_MESSAGE": "Could not connect to Woot Server, Please try again later" + }, + "AGGRESSIVE_ALERT": { + "LABEL": "Aggressive alert — inboxes", + "DESCRIPTION": "Choose which inboxes will trigger the reopened/inactivity banner for this agent.", + "ALL_INBOXES": "All inboxes", + "PICK_INBOXES": "Select inboxes", + "NONE_WARNING": "No inbox selected — this agent will not see the aggressive alert." } }, "SEARCH": { diff --git a/app/javascript/dashboard/i18n/locale/pt_BR/agentMgmt.json b/app/javascript/dashboard/i18n/locale/pt_BR/agentMgmt.json index 87c112cec..af3bbd3ef 100644 --- a/app/javascript/dashboard/i18n/locale/pt_BR/agentMgmt.json +++ b/app/javascript/dashboard/i18n/locale/pt_BR/agentMgmt.json @@ -94,6 +94,13 @@ "ADMIN_SUCCESS_MESSAGE": "Um e-mail com instruções de redefinição de senha foi enviado para o agente", "SUCCESS_MESSAGE": "Senha do agente redefinida com sucesso", "ERROR_MESSAGE": "Não foi possível conectar ao servidor Woot, por favor tente novamente mais tarde" + }, + "AGGRESSIVE_ALERT": { + "LABEL": "Alerta agressivo — caixas de entrada", + "DESCRIPTION": "Escolha em quais caixas de entrada este agente verá o banner de conversa reaberta e de inatividade.", + "ALL_INBOXES": "Em todas as caixas de entrada", + "PICK_INBOXES": "Selecione as caixas de entrada", + "NONE_WARNING": "Nenhuma caixa selecionada — este agente não verá o alerta agressivo." } }, "SEARCH": { diff --git a/app/javascript/dashboard/routes/dashboard/settings/agents/EditAgent.vue b/app/javascript/dashboard/routes/dashboard/settings/agents/EditAgent.vue index 40b510c48..d477cab32 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/agents/EditAgent.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/agents/EditAgent.vue @@ -6,6 +6,7 @@ import { useStore, useMapGetter } from 'dashboard/composables/store'; import { useI18n } from 'vue-i18n'; import { useAlert } from 'dashboard/composables'; import Button from 'dashboard/components-next/button/Button.vue'; +import Multiselect from 'vue-multiselect'; import Auth from '../../../../api/auth'; import wootConstants from 'dashboard/constants/globals'; @@ -38,6 +39,10 @@ const props = defineProps({ type: Number, default: null, }, + uiSettings: { + type: Object, + default: () => ({}), + }, }); const emit = defineEmits(['close']); @@ -52,6 +57,27 @@ const agentAvailability = ref(props.availability); const selectedRoleId = ref(props.customRoleId || props.type); const agentCredentials = ref({ email: props.email }); +// --- Alerta agressivo por inbox ------------------------------------------- +// ui_settings.aggressive_alert_inbox_ids: +// null/undefined → todas (default, legado) +// [] → nenhuma (silenciou tudo) +// [1, 2, 3] → só essas +const initialInboxIds = props.uiSettings?.aggressive_alert_inbox_ids; +const alertAllInboxes = ref( + initialInboxIds === null || + initialInboxIds === undefined || + !Array.isArray(initialInboxIds) +); + +const inboxes = useMapGetter('inboxes/getInboxes'); +const selectedAlertInboxes = ref( + Array.isArray(initialInboxIds) && inboxes.value + ? inboxes.value.filter(i => + initialInboxIds.map(id => Number(id)).includes(Number(i.id)) + ) + : [] +); + const rules = { agentName: { required, minLength: minLength(1) }, selectedRoleId: { required }, @@ -135,6 +161,12 @@ const editAgent = async () => { payload.custom_role_id = null; } + payload.ui_settings = { + aggressive_alert_inbox_ids: alertAllInboxes.value + ? null + : selectedAlertInboxes.value.map(i => Number(i.id)), + }; + await store.dispatch('agents/update', payload); useAlert(t('AGENT_MGMT.EDIT.API.SUCCESS_MESSAGE')); emit('close'); @@ -204,6 +236,47 @@ const resetPassword = async () => { +
+ {{ $t('AGENT_MGMT.EDIT.AGGRESSIVE_ALERT.DESCRIPTION') }} +
+ + + ++ {{ $t('AGENT_MGMT.EDIT.AGGRESSIVE_ALERT.NONE_WARNING') }} +
+