Compare commits

..
Author SHA1 Message Date
Cursor Agentandpranavrajs 97fd4885fc Fix: Optimize IMAP query and add timeout handling
Co-authored-by: pranavrajs <pranavrajs@gmail.com>
2025-09-09 23:44:04 +00:00
Cursor Agentandpranavrajs 8498c92fdd Fix: Optimize IMAP and Support mailbox query timeouts
Co-authored-by: pranavrajs <pranavrajs@gmail.com>
2025-09-09 23:30:38 +00:00
405 changed files with 705 additions and 27597 deletions
-7
View File
@@ -6,13 +6,6 @@
# Use `rake secret` to generate this variable
SECRET_KEY_BASE=replace_with_lengthy_secure_hex
# Active Record Encryption keys (required for MFA/2FA functionality)
# Generate these keys by running: rails db:encryption:init
# IMPORTANT: Use different keys for each environment (development, staging, production)
# ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=
# ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=
# ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=
# Replace with the URL you are planning to use for your app
FRONTEND_URL=http://0.0.0.0:3000
# To use a dedicated URL for help center pages
-99
View File
@@ -1,99 +0,0 @@
name: Run MFA Tests
permissions:
contents: read
on:
pull_request:
# If two pushes happen within a short time in the same PR, cancel the run of the oldest push
concurrency:
group: pr-${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-22.04
# Only run if MFA test keys are available
if: github.event_name == 'workflow_dispatch' || (github.repository == 'chatwoot/chatwoot' && github.actor != 'dependabot[bot]')
services:
postgres:
image: pgvector/pgvector:pg15
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ''
POSTGRES_DB: postgres
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
options: >-
--mount type=tmpfs,destination=/var/lib/postgresql/data
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis
ports:
- 6379:6379
options: --entrypoint redis-server
env:
RAILS_ENV: test
POSTGRES_HOST: localhost
# Active Record encryption keys required for MFA - test keys only, not for production use
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY: 'test_key_a6cde8f7b9c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7'
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY: 'test_key_b7def9a8c0d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d8'
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT: 'test_salt_c8efa0b9d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d9'
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Create database
run: bundle exec rake db:create
- name: Install pgvector extension
run: |
PGPASSWORD="" psql -h localhost -U postgres -d chatwoot_test -c "CREATE EXTENSION IF NOT EXISTS vector;"
- name: Seed database
run: bundle exec rake db:schema:load
- name: Run MFA-related backend tests
run: |
bundle exec rspec \
spec/services/mfa/token_service_spec.rb \
spec/services/mfa/authentication_service_spec.rb \
spec/requests/api/v1/profile/mfa_controller_spec.rb \
spec/controllers/devise_overrides/sessions_controller_spec.rb \
--profile=10 \
--format documentation
env:
NODE_OPTIONS: --openssl-legacy-provider
- name: Run MFA-related tests in user_spec
run: |
# Run specific MFA-related tests from user_spec
bundle exec rspec spec/models/user_spec.rb \
-e "two factor" \
-e "2FA" \
-e "MFA" \
-e "otp" \
-e "backup code" \
--profile=10 \
--format documentation
env:
NODE_OPTIONS: --openssl-legacy-provider
- name: Upload test logs
uses: actions/upload-artifact@v4
if: failure()
with:
name: mfa-test-logs
path: |
log/test.log
tmp/screenshots/
-4
View File
@@ -95,7 +95,3 @@ yarn-debug.log*
.claude/settings.local.json
.cursor
CLAUDE.local.md
# Histoire deployment
.netlify
.histoire
-4
View File
@@ -78,12 +78,9 @@ gem 'barnes'
gem 'devise', '>= 4.9.4'
gem 'devise-secure_password', git: 'https://github.com/chatwoot/devise-secure_password', branch: 'chatwoot'
gem 'devise_token_auth', '>= 1.2.3'
# two-factor authentication
gem 'devise-two-factor', '>= 5.0.0'
# authorization
gem 'jwt'
gem 'pundit'
# super admin
gem 'administrate', '>= 0.20.1'
gem 'administrate-field-active_storage', '>= 1.0.3'
@@ -174,7 +171,6 @@ gem 'audited', '~> 5.4', '>= 5.4.1'
# need for google auth
gem 'omniauth', '>= 2.1.2'
gem 'omniauth-saml'
gem 'omniauth-google-oauth2', '>= 1.1.3'
gem 'omniauth-rails_csrf_protection', '~> 1.0', '>= 1.0.2'
+2 -17
View File
@@ -212,11 +212,6 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise-two-factor (6.1.0)
activesupport (>= 7.0, < 8.1)
devise (~> 4.0)
railties (>= 7.0, < 8.1)
rotp (~> 6.0)
devise_token_auth (1.2.5)
bcrypt (~> 3.0)
devise (> 3.5.2, < 5)
@@ -594,9 +589,8 @@ GEM
oj (3.16.10)
bigdecimal (>= 3.0)
ostruct (>= 0.2)
omniauth (2.1.3)
omniauth (2.1.2)
hashie (>= 3.4.6)
logger
rack (>= 2.2.3)
rack-protection
omniauth-google-oauth2 (1.1.3)
@@ -610,9 +604,6 @@ GEM
omniauth-rails_csrf_protection (1.0.2)
actionpack (>= 4.2)
omniauth (~> 2.0)
omniauth-saml (2.2.4)
omniauth (~> 2.1)
ruby-saml (~> 1.18)
opensearch-ruby (3.4.0)
faraday (>= 1.0, < 3)
multi_json (>= 1.0)
@@ -727,8 +718,7 @@ GEM
retriable (3.1.2)
reverse_markdown (2.1.1)
nokogiri
rexml (3.4.4)
rotp (6.3.0)
rexml (3.4.1)
rspec-core (3.13.0)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.2)
@@ -783,9 +773,6 @@ GEM
faraday (>= 1)
faraday-multipart (>= 1)
ruby-progressbar (1.13.0)
ruby-saml (1.18.1)
nokogiri (>= 1.13.10)
rexml
ruby-vips (2.1.4)
ffi (~> 1.12)
ruby2_keywords (0.0.5)
@@ -1011,7 +998,6 @@ DEPENDENCIES
debug (~> 1.8)
devise (>= 4.9.4)
devise-secure_password!
devise-two-factor (>= 5.0.0)
devise_token_auth (>= 1.2.3)
dotenv-rails (>= 3.0.0)
down
@@ -1061,7 +1047,6 @@ DEPENDENCIES
omniauth-google-oauth2 (>= 1.1.3)
omniauth-oauth2
omniauth-rails_csrf_protection (~> 1.0, >= 1.0.2)
omniauth-saml
opensearch-ruby
pg
pg_search
+4 -3
View File
@@ -2,8 +2,9 @@
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative 'config/application'
# Load Enterprise Edition rake tasks if they exist
enterprise_tasks_path = Rails.root.join('enterprise/tasks_railtie.rb').to_s
require enterprise_tasks_path if File.exist?(enterprise_tasks_path)
Rails.application.load_tasks
# Load Enterprise Edition rake tasks if they exist
enterprise_tasks_path = Rails.root.join('enterprise/lib/tasks.rb').to_s
require enterprise_tasks_path if File.exist?(enterprise_tasks_path)
-2
View File
@@ -52,5 +52,3 @@ class AgentBuilder
}.compact))
end
end
AgentBuilder.prepend_mod_with('AgentBuilder')
@@ -85,8 +85,7 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
def live_chat_widget_params
permitted_params = params.permit(:inbox_id)
return {} unless permitted_params.key?(:inbox_id)
return { channel_web_widget_id: nil } if permitted_params[:inbox_id].blank?
return {} if permitted_params[:inbox_id].blank?
inbox = Inbox.find(permitted_params[:inbox_id])
return {} unless inbox.web_widget?
@@ -1,4 +1,5 @@
class Api::V1::Accounts::Whatsapp::AuthorizationsController < Api::V1::Accounts::BaseController
before_action :validate_feature_enabled!
before_action :fetch_and_validate_inbox, if: -> { params[:inbox_id].present? }
# POST /api/v1/accounts/:account_id/whatsapp/authorization
@@ -64,6 +65,15 @@ class Api::V1::Accounts::Whatsapp::AuthorizationsController < Api::V1::Accounts:
}, status: :unprocessable_entity
end
def validate_feature_enabled!
return if Current.account.feature_whatsapp_embedded_signup?
render json: {
success: false,
error: 'WhatsApp embedded signup is not enabled for this account'
}, status: :forbidden
end
def validate_embedded_signup_params!
missing_params = []
missing_params << 'code' if params[:code].blank?
@@ -1,68 +0,0 @@
class Api::V1::Profile::MfaController < Api::BaseController
before_action :check_mfa_feature_available
before_action :check_mfa_enabled, only: [:destroy, :backup_codes]
before_action :check_mfa_disabled, only: [:create, :verify]
before_action :validate_otp, only: [:verify, :backup_codes, :destroy]
before_action :validate_password, only: [:destroy]
def show; end
def create
mfa_service.enable_two_factor!
end
def verify
@backup_codes = mfa_service.verify_and_activate!
end
def destroy
mfa_service.disable_two_factor!
end
def backup_codes
@backup_codes = mfa_service.generate_backup_codes!
end
private
def mfa_service
@mfa_service ||= Mfa::ManagementService.new(user: current_user)
end
def check_mfa_enabled
render_could_not_create_error(I18n.t('errors.mfa.not_enabled')) unless current_user.mfa_enabled?
end
def check_mfa_feature_available
return if Chatwoot.mfa_enabled?
render json: {
error: I18n.t('errors.mfa.feature_unavailable')
}, status: :forbidden
end
def check_mfa_disabled
render_could_not_create_error(I18n.t('errors.mfa.already_enabled')) if current_user.mfa_enabled?
end
def validate_otp
authenticated = Mfa::AuthenticationService.new(
user: current_user,
otp_code: mfa_params[:otp_code]
).authenticate
return if authenticated
render_could_not_create_error(I18n.t('errors.mfa.invalid_code'))
end
def validate_password
return if current_user.valid_password?(mfa_params[:password])
render_could_not_create_error(I18n.t('errors.mfa.invalid_credentials'))
end
def mfa_params
params.permit(:otp_code, :password)
end
end
@@ -47,8 +47,10 @@ class DeviseOverrides::OmniauthCallbacksController < DeviseTokenAuth::OmniauthCa
end
def get_resource_from_auth_hash # rubocop:disable Naming/AccessorMethodName
email = auth_hash.dig('info', 'email')
@resource = resource_class.from_email(email)
# find the user with their email instead of UID and token
@resource = resource_class.where(
email: auth_hash['info']['email']
).first
end
def validate_signup_email_is_business_domain?
@@ -73,5 +75,3 @@ class DeviseOverrides::OmniauthCallbacksController < DeviseTokenAuth::OmniauthCa
'user'
end
end
DeviseOverrides::OmniauthCallbacksController.prepend_mod_with('DeviseOverrides::OmniauthCallbacksController')
@@ -44,5 +44,3 @@ class DeviseOverrides::PasswordsController < Devise::PasswordsController
}, status: status
end
end
DeviseOverrides::PasswordsController.prepend_mod_with('DeviseOverrides::PasswordsController')
@@ -9,11 +9,13 @@ class DeviseOverrides::SessionsController < DeviseTokenAuth::SessionsController
end
def create
return handle_mfa_verification if mfa_verification_request?
return handle_sso_authentication if sso_authentication_request?
super do |resource|
return handle_mfa_required(resource) if resource&.mfa_enabled?
# Authenticate user via the temporary sso auth token
if params[:sso_auth_token].present? && @resource.present?
authenticate_resource_with_sso_token
yield @resource if block_given?
render_create_success
else
super
end
end
@@ -23,20 +25,6 @@ class DeviseOverrides::SessionsController < DeviseTokenAuth::SessionsController
private
def mfa_verification_request?
params[:mfa_token].present?
end
def sso_authentication_request?
params[:sso_auth_token].present? && @resource.present?
end
def handle_sso_authentication
authenticate_resource_with_sso_token
yield @resource if block_given?
render_create_success
end
def login_page_url(error: nil)
frontend_url = ENV.fetch('FRONTEND_URL', nil)
@@ -58,41 +46,6 @@ class DeviseOverrides::SessionsController < DeviseTokenAuth::SessionsController
user = User.from_email(params[:email])
@resource = user if user&.valid_sso_auth_token?(params[:sso_auth_token])
end
def handle_mfa_required(resource)
render json: {
mfa_required: true,
mfa_token: Mfa::TokenService.new(user: resource).generate_token
}, status: :partial_content
end
def handle_mfa_verification
user = Mfa::TokenService.new(token: params[:mfa_token]).verify_token
return render_mfa_error('errors.mfa.invalid_token', :unauthorized) unless user
authenticated = Mfa::AuthenticationService.new(
user: user,
otp_code: params[:otp_code],
backup_code: params[:backup_code]
).authenticate
return render_mfa_error('errors.mfa.invalid_code') unless authenticated
sign_in_mfa_user(user)
end
def sign_in_mfa_user(user)
@resource = user
@token = @resource.create_token
@resource.save!
sign_in(:user, @resource, store: false, bypass: false)
render_create_success
end
def render_mfa_error(message_key, status = :bad_request)
render json: { error: I18n.t(message_key) }, status: status
end
end
DeviseOverrides::SessionsController.prepend_mod_with('DeviseOverrides::SessionsController')
+1 -6
View File
@@ -70,12 +70,7 @@ class WidgetsController < ActionController::Base
end
def allow_iframe_requests
if @web_widget.allowed_domains.blank?
response.headers.delete('X-Frame-Options')
else
domains = @web_widget.allowed_domains.split(',').map(&:strip).join(' ')
response.headers['Content-Security-Policy'] = "frame-ancestors #{domains}"
end
response.headers.delete('X-Frame-Options')
end
end
+1 -2
View File
@@ -1,5 +1,4 @@
module PortalHelper
include UrlHelper
def set_og_image_url(portal_name, title)
cdn_url = GlobalConfig.get('OG_IMAGE_CDN_URL')['OG_IMAGE_CDN_URL']
return if cdn_url.blank?
@@ -80,7 +79,7 @@ module PortalHelper
query_params = Rack::Utils.parse_query(url.query)
query_params['utm_medium'] = 'helpcenter'
query_params['utm_campaign'] = 'branding'
query_params['utm_source'] = URI.parse(referer).host if url_valid?(referer)
query_params['utm_source'] = URI.parse(referer).host if referer.present? && referer.match?(URI::DEFAULT_PARSER.make_regexp)
url.query = query_params.to_query
url.to_s
@@ -1,43 +0,0 @@
/* global axios */
import ApiClient from './ApiClient';
class AgentCapacityPolicies extends ApiClient {
constructor() {
super('agent_capacity_policies', { accountScoped: true });
}
getUsers(policyId) {
return axios.get(`${this.url}/${policyId}/users`);
}
addUser(policyId, userData) {
return axios.post(`${this.url}/${policyId}/users`, {
user_id: userData.id,
capacity: userData.capacity,
});
}
removeUser(policyId, userId) {
return axios.delete(`${this.url}/${policyId}/users/${userId}`);
}
createInboxLimit(policyId, limitData) {
return axios.post(`${this.url}/${policyId}/inbox_limits`, {
inbox_id: limitData.inboxId,
conversation_limit: limitData.conversationLimit,
});
}
updateInboxLimit(policyId, limitId, limitData) {
return axios.put(`${this.url}/${policyId}/inbox_limits/${limitId}`, {
conversation_limit: limitData.conversationLimit,
});
}
deleteInboxLimit(policyId, limitId) {
return axios.delete(`${this.url}/${policyId}/inbox_limits/${limitId}`);
}
}
export default new AgentCapacityPolicies();
@@ -1,36 +0,0 @@
/* global axios */
import ApiClient from './ApiClient';
class AssignmentPolicies extends ApiClient {
constructor() {
super('assignment_policies', { accountScoped: true });
}
getInboxes(policyId) {
return axios.get(`${this.url}/${policyId}/inboxes`);
}
setInboxPolicy(inboxId, policyId) {
return axios.post(
`/api/v1/accounts/${this.accountIdFromRoute}/inboxes/${inboxId}/assignment_policy`,
{
assignment_policy_id: policyId,
}
);
}
getInboxPolicy(inboxId) {
return axios.get(
`/api/v1/accounts/${this.accountIdFromRoute}/inboxes/${inboxId}/assignment_policy`
);
}
removeInboxPolicy(inboxId) {
return axios.delete(
`/api/v1/accounts/${this.accountIdFromRoute}/inboxes/${inboxId}/assignment_policy`
);
}
}
export default new AssignmentPolicies();
@@ -6,11 +6,11 @@ class CaptainResponses extends ApiClient {
super('captain/assistant_responses', { accountScoped: true });
}
get({ page = 1, search, assistantId, documentId, status } = {}) {
get({ page = 1, searchKey, assistantId, documentId, status } = {}) {
return axios.get(this.url, {
params: {
page,
search,
searchKey,
assistant_id: assistantId,
document_id: documentId,
status,
-28
View File
@@ -1,28 +0,0 @@
/* global axios */
import ApiClient from './ApiClient';
class MfaAPI extends ApiClient {
constructor() {
super('profile/mfa', { accountScoped: false });
}
enable() {
return axios.post(`${this.url}`);
}
verify(otpCode) {
return axios.post(`${this.url}/verify`, { otp_code: otpCode });
}
disable(password, otpCode) {
return axios.delete(this.url, {
data: { password, otp_code: otpCode },
});
}
regenerateBackupCodes(otpCode) {
return axios.post(`${this.url}/backup_codes`, { otp_code: otpCode });
}
}
export default new MfaAPI();
@@ -1,26 +0,0 @@
/* global axios */
import ApiClient from './ApiClient';
class SamlSettingsAPI extends ApiClient {
constructor() {
super('saml_settings', { accountScoped: true });
}
get() {
return axios.get(this.url);
}
create(data) {
return axios.post(this.url, { saml_settings: data });
}
update(data) {
return axios.put(this.url, { saml_settings: data });
}
delete() {
return axios.delete(this.url);
}
}
export default new SamlSettingsAPI();
@@ -1,98 +0,0 @@
import agentCapacityPolicies from '../agentCapacityPolicies';
import ApiClient from '../ApiClient';
describe('#AgentCapacityPoliciesAPI', () => {
it('creates correct instance', () => {
expect(agentCapacityPolicies).toBeInstanceOf(ApiClient);
expect(agentCapacityPolicies).toHaveProperty('get');
expect(agentCapacityPolicies).toHaveProperty('show');
expect(agentCapacityPolicies).toHaveProperty('create');
expect(agentCapacityPolicies).toHaveProperty('update');
expect(agentCapacityPolicies).toHaveProperty('delete');
expect(agentCapacityPolicies).toHaveProperty('getUsers');
expect(agentCapacityPolicies).toHaveProperty('addUser');
expect(agentCapacityPolicies).toHaveProperty('removeUser');
expect(agentCapacityPolicies).toHaveProperty('createInboxLimit');
expect(agentCapacityPolicies).toHaveProperty('updateInboxLimit');
expect(agentCapacityPolicies).toHaveProperty('deleteInboxLimit');
});
describe('API calls', () => {
const originalAxios = window.axios;
const axiosMock = {
get: vi.fn(() => Promise.resolve()),
post: vi.fn(() => Promise.resolve()),
put: vi.fn(() => Promise.resolve()),
delete: vi.fn(() => Promise.resolve()),
};
beforeEach(() => {
window.axios = axiosMock;
// Mock accountIdFromRoute
Object.defineProperty(agentCapacityPolicies, 'accountIdFromRoute', {
get: () => '1',
configurable: true,
});
});
afterEach(() => {
window.axios = originalAxios;
});
it('#getUsers', () => {
agentCapacityPolicies.getUsers(123);
expect(axiosMock.get).toHaveBeenCalledWith(
'/api/v1/accounts/1/agent_capacity_policies/123/users'
);
});
it('#addUser', () => {
const userData = { id: 456, capacity: 20 };
agentCapacityPolicies.addUser(123, userData);
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/accounts/1/agent_capacity_policies/123/users',
{
user_id: 456,
capacity: 20,
}
);
});
it('#removeUser', () => {
agentCapacityPolicies.removeUser(123, 456);
expect(axiosMock.delete).toHaveBeenCalledWith(
'/api/v1/accounts/1/agent_capacity_policies/123/users/456'
);
});
it('#createInboxLimit', () => {
const limitData = { inboxId: 1, conversationLimit: 10 };
agentCapacityPolicies.createInboxLimit(123, limitData);
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/accounts/1/agent_capacity_policies/123/inbox_limits',
{
inbox_id: 1,
conversation_limit: 10,
}
);
});
it('#updateInboxLimit', () => {
const limitData = { conversationLimit: 15 };
agentCapacityPolicies.updateInboxLimit(123, 789, limitData);
expect(axiosMock.put).toHaveBeenCalledWith(
'/api/v1/accounts/1/agent_capacity_policies/123/inbox_limits/789',
{
conversation_limit: 15,
}
);
});
it('#deleteInboxLimit', () => {
agentCapacityPolicies.deleteInboxLimit(123, 789);
expect(axiosMock.delete).toHaveBeenCalledWith(
'/api/v1/accounts/1/agent_capacity_policies/123/inbox_limits/789'
);
});
});
});
@@ -1,70 +0,0 @@
import assignmentPolicies from '../assignmentPolicies';
import ApiClient from '../ApiClient';
describe('#AssignmentPoliciesAPI', () => {
it('creates correct instance', () => {
expect(assignmentPolicies).toBeInstanceOf(ApiClient);
expect(assignmentPolicies).toHaveProperty('get');
expect(assignmentPolicies).toHaveProperty('show');
expect(assignmentPolicies).toHaveProperty('create');
expect(assignmentPolicies).toHaveProperty('update');
expect(assignmentPolicies).toHaveProperty('delete');
expect(assignmentPolicies).toHaveProperty('getInboxes');
expect(assignmentPolicies).toHaveProperty('setInboxPolicy');
expect(assignmentPolicies).toHaveProperty('getInboxPolicy');
expect(assignmentPolicies).toHaveProperty('removeInboxPolicy');
});
describe('API calls', () => {
const originalAxios = window.axios;
const axiosMock = {
get: vi.fn(() => Promise.resolve()),
post: vi.fn(() => Promise.resolve()),
delete: vi.fn(() => Promise.resolve()),
};
beforeEach(() => {
window.axios = axiosMock;
// Mock accountIdFromRoute
Object.defineProperty(assignmentPolicies, 'accountIdFromRoute', {
get: () => '1',
configurable: true,
});
});
afterEach(() => {
window.axios = originalAxios;
});
it('#getInboxes', () => {
assignmentPolicies.getInboxes(123);
expect(axiosMock.get).toHaveBeenCalledWith(
'/api/v1/accounts/1/assignment_policies/123/inboxes'
);
});
it('#setInboxPolicy', () => {
assignmentPolicies.setInboxPolicy(456, 123);
expect(axiosMock.post).toHaveBeenCalledWith(
'/api/v1/accounts/1/inboxes/456/assignment_policy',
{
assignment_policy_id: 123,
}
);
});
it('#getInboxPolicy', () => {
assignmentPolicies.getInboxPolicy(456);
expect(axiosMock.get).toHaveBeenCalledWith(
'/api/v1/accounts/1/inboxes/456/assignment_policy'
);
});
it('#removeInboxPolicy', () => {
assignmentPolicies.removeInboxPolicy(456);
expect(axiosMock.delete).toHaveBeenCalledWith(
'/api/v1/accounts/1/inboxes/456/assignment_policy'
);
});
});
});
@@ -1,116 +0,0 @@
<script setup>
import AgentCapacityPolicyCard from './AgentCapacityPolicyCard.vue';
const mockUsers = [
{
id: 1,
name: 'John Smith',
email: 'john.smith@example.com',
avatarUrl: 'https://i.pravatar.cc/150?img=1',
},
{
id: 2,
name: 'Sarah Johnson',
email: 'sarah.johnson@example.com',
avatarUrl: 'https://i.pravatar.cc/150?img=2',
},
{
id: 3,
name: 'Mike Chen',
email: 'mike.chen@example.com',
avatarUrl: 'https://i.pravatar.cc/150?img=3',
},
{
id: 4,
name: 'Emily Davis',
email: 'emily.davis@example.com',
avatarUrl: 'https://i.pravatar.cc/150?img=4',
},
{
id: 5,
name: 'Alex Rodriguez',
email: 'alex.rodriguez@example.com',
avatarUrl: 'https://i.pravatar.cc/150?img=5',
},
];
const withCount = policy => ({
...policy,
assignedAgentCount: policy.users.length,
});
const policyA = withCount({
id: 1,
name: 'High Volume Support',
description:
'Capacity-based policy for handling high conversation volumes with experienced agents',
users: [mockUsers[0], mockUsers[1], mockUsers[2]],
isFetchingUsers: false,
});
const policyB = withCount({
id: 2,
name: 'Specialized Team',
description: 'Custom capacity limits for specialized support team members',
users: [mockUsers[3], mockUsers[4]],
isFetchingUsers: false,
});
const emptyPolicy = withCount({
id: 3,
name: 'New Policy',
description: 'Recently created policy with no assigned agents yet',
users: [],
isFetchingUsers: false,
});
const loadingPolicy = withCount({
id: 4,
name: 'Loading Policy',
description: 'Policy currently loading agent information',
users: [],
isFetchingUsers: true,
});
const onEdit = id => console.log('Edit policy:', id);
const onDelete = id => console.log('Delete policy:', id);
const onFetchUsers = id => console.log('Fetch users for policy:', id);
</script>
<template>
<Story
title="Components/AgentManagementPolicy/AgentCapacityPolicyCard"
:layout="{ type: 'grid', width: '1200px' }"
>
<Variant title="Multiple Cards (Various States)">
<div class="p-4 bg-n-background">
<div class="grid grid-cols-1 gap-4">
<AgentCapacityPolicyCard
v-bind="policyA"
@edit="onEdit"
@delete="onDelete"
@fetch-users="onFetchUsers"
/>
<AgentCapacityPolicyCard
v-bind="policyB"
@edit="onEdit"
@delete="onDelete"
@fetch-users="onFetchUsers"
/>
<AgentCapacityPolicyCard
v-bind="emptyPolicy"
@edit="onEdit"
@delete="onDelete"
@fetch-users="onFetchUsers"
/>
<AgentCapacityPolicyCard
v-bind="loadingPolicy"
@edit="onEdit"
@delete="onDelete"
@fetch-users="onFetchUsers"
/>
</div>
</div>
</Variant>
</Story>
</template>
@@ -1,86 +0,0 @@
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from 'dashboard/components-next/button/Button.vue';
import CardLayout from 'dashboard/components-next/CardLayout.vue';
import CardPopover from '../components/CardPopover.vue';
const props = defineProps({
id: { type: Number, required: true },
name: { type: String, default: '' },
description: { type: String, default: '' },
assignedAgentCount: { type: Number, default: 0 },
users: { type: Array, default: () => [] },
isFetchingUsers: { type: Boolean, default: false },
});
const emit = defineEmits(['edit', 'delete', 'fetchUsers']);
const { t } = useI18n();
const users = computed(() => {
return props.users.map(user => {
return {
name: user.name,
key: user.id,
email: user.email,
avatarUrl: user.avatarUrl,
};
});
});
const handleEdit = () => {
emit('edit', props.id);
};
const handleDelete = () => {
emit('delete', props.id);
};
const handleFetchUsers = () => {
if (props.users?.length > 0) return;
emit('fetchUsers', props.id);
};
</script>
<template>
<CardLayout class="[&>div]:px-5">
<div class="flex flex-col gap-2 relative justify-between w-full">
<div class="flex items-center gap-3 justify-between w-full">
<div class="flex items-center gap-3">
<h3 class="text-base font-medium text-n-slate-12 line-clamp-1">
{{ name }}
</h3>
<CardPopover
:title="
t('ASSIGNMENT_POLICY.AGENT_CAPACITY_POLICY.INDEX.CARD.POPOVER')
"
icon="i-lucide-users-round"
:count="assignedAgentCount"
:items="users"
:is-fetching="isFetchingUsers"
@fetch="handleFetchUsers"
/>
</div>
<div class="flex items-center gap-2">
<Button
:label="
t('ASSIGNMENT_POLICY.AGENT_CAPACITY_POLICY.INDEX.CARD.EDIT')
"
sm
slate
link
class="px-2"
@click="handleEdit"
/>
<div class="w-px h-2.5 bg-n-slate-5" />
<Button icon="i-lucide-trash" sm slate ghost @click="handleDelete" />
</div>
</div>
<p class="text-n-slate-11 text-sm line-clamp-1 mb-0 py-1">
{{ description }}
</p>
</div>
</CardLayout>
</template>
@@ -1,104 +0,0 @@
<script setup>
import AssignmentPolicyCard from './AssignmentPolicyCard.vue';
const mockInboxes = [
{
id: 1,
name: 'Website Support',
channel_type: 'Channel::WebWidget',
inbox_type: 'Website',
},
{
id: 2,
name: 'Email Support',
channel_type: 'Channel::Email',
inbox_type: 'Email',
},
{
id: 3,
name: 'WhatsApp Business',
channel_type: 'Channel::Whatsapp',
inbox_type: 'WhatsApp',
},
{
id: 4,
name: 'Facebook Messenger',
channel_type: 'Channel::FacebookPage',
inbox_type: 'Messenger',
},
];
const withCount = policy => ({
...policy,
assignedInboxCount: policy.inboxes.length,
});
const policyA = withCount({
id: 1,
name: 'Website & Email',
description: 'Distributes conversations evenly among available agents',
assignmentOrder: 'round_robin',
conversationPriority: 'high',
enabled: true,
inboxes: [mockInboxes[0], mockInboxes[1]],
isFetchingInboxes: false,
});
const policyB = withCount({
id: 2,
name: 'WhatsApp & Messenger',
description: 'Assigns based on capacity and workload',
assignmentOrder: 'capacity_based',
conversationPriority: 'medium',
enabled: true,
inboxes: [mockInboxes[2], mockInboxes[3]],
isFetchingInboxes: false,
});
const emptyPolicy = withCount({
id: 3,
name: 'No Inboxes Yet',
description: 'Policy with no assigned inboxes',
assignmentOrder: 'manual',
conversationPriority: 'low',
enabled: false,
inboxes: [],
isFetchingInboxes: false,
});
const onEdit = id => console.log('Edit policy:', id);
const onDelete = id => console.log('Delete policy:', id);
const onFetch = () => console.log('Fetch inboxes');
</script>
<template>
<Story
title="Components/AgentManagementPolicy/AssignmentPolicyCard"
:layout="{ type: 'grid', width: '1200px' }"
>
<Variant title="Three Cards (Two with inboxes, One empty)">
<div class="p-4 bg-n-background">
<div class="grid grid-cols-1 gap-4">
<AssignmentPolicyCard
v-bind="policyA"
@edit="onEdit"
@delete="onDelete"
@fetch-inboxes="onFetch"
/>
<AssignmentPolicyCard
v-bind="policyB"
@edit="onEdit"
@delete="onDelete"
@fetch-inboxes="onFetch"
/>
<AssignmentPolicyCard
v-bind="emptyPolicy"
@edit="onEdit"
@delete="onDelete"
@fetch-inboxes="onFetch"
/>
</div>
</div>
</Variant>
</Story>
</template>
@@ -1,133 +0,0 @@
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { getInboxIconByType } from 'dashboard/helper/inbox';
import { formatToTitleCase } from 'dashboard/helper/commons';
import Button from 'dashboard/components-next/button/Button.vue';
import CardLayout from 'dashboard/components-next/CardLayout.vue';
import CardPopover from '../components/CardPopover.vue';
const props = defineProps({
id: { type: Number, required: true },
name: { type: String, default: '' },
description: { type: String, default: '' },
assignmentOrder: { type: String, default: '' },
conversationPriority: { type: String, default: '' },
assignedInboxCount: { type: Number, default: 0 },
enabled: { type: Boolean, default: false },
inboxes: { type: Array, default: () => [] },
isFetchingInboxes: { type: Boolean, default: false },
});
const emit = defineEmits(['edit', 'delete', 'fetchInboxes']);
const { t } = useI18n();
const inboxes = computed(() => {
return props.inboxes.map(inbox => {
return {
name: inbox.name,
id: inbox.id,
icon: getInboxIconByType(inbox.channelType, inbox.medium, 'line'),
};
});
});
const order = computed(() => {
return formatToTitleCase(props.assignmentOrder);
});
const priority = computed(() => {
return formatToTitleCase(props.conversationPriority);
});
const handleEdit = () => {
emit('edit', props.id);
};
const handleDelete = () => {
emit('delete', props.id);
};
const handleFetchInboxes = () => {
if (props.inboxes?.length > 0) return;
emit('fetchInboxes', props.id);
};
</script>
<template>
<CardLayout class="[&>div]:px-5">
<div class="flex flex-col gap-2 relative justify-between w-full">
<div class="flex items-center gap-3 justify-between w-full">
<div class="flex items-center gap-3">
<h3 class="text-base font-medium text-n-slate-12 line-clamp-1">
{{ name }}
</h3>
<div class="flex items-center gap-2">
<div class="flex items-center rounded-md bg-n-alpha-2 h-6 px-2">
<span
class="text-xs"
:class="enabled ? 'text-n-teal-11' : 'text-n-slate-12'"
>
{{
enabled
? t(
'ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.INDEX.CARD.ACTIVE'
)
: t(
'ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.INDEX.CARD.INACTIVE'
)
}}
</span>
</div>
<CardPopover
:title="
t(
'ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.INDEX.CARD.POPOVER'
)
"
icon="i-lucide-inbox"
:count="assignedInboxCount"
:items="inboxes"
:is-fetching="isFetchingInboxes"
@fetch="handleFetchInboxes"
/>
</div>
</div>
<div class="flex items-center gap-2">
<Button
:label="
t('ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.INDEX.CARD.EDIT')
"
sm
slate
link
class="px-2"
@click="handleEdit"
/>
<div v-if="order" class="w-px h-2.5 bg-n-slate-5" />
<Button icon="i-lucide-trash" sm slate ghost @click="handleDelete" />
</div>
</div>
<p class="text-n-slate-11 text-sm line-clamp-1 mb-0 py-1">
{{ description }}
</p>
<div class="flex items-center gap-3 py-1.5">
<span v-if="order" class="text-n-slate-11 text-sm">
{{
`${t('ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.INDEX.CARD.ORDER')}:`
}}
<span class="text-n-slate-12">{{ order }}</span>
</span>
<div v-if="order" class="w-px h-3 bg-n-strong" />
<span v-if="priority" class="text-n-slate-11 text-sm">
{{
`${t('ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.INDEX.CARD.PRIORITY')}:`
}}
<span class="text-n-slate-12">{{ priority }}</span>
</span>
</div>
</div>
</CardLayout>
</template>
@@ -1,169 +0,0 @@
<script setup>
import { computed, ref } from 'vue';
import { useToggle, useWindowSize, useElementBounding } from '@vueuse/core';
import { vOnClickOutside } from '@vueuse/components';
import { picoSearch } from '@scmmishra/pico-search';
import Avatar from 'next/avatar/Avatar.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import Button from 'dashboard/components-next/button/Button.vue';
import Input from 'dashboard/components-next/input/Input.vue';
const props = defineProps({
label: {
type: String,
default: '',
},
searchPlaceholder: {
type: String,
default: '',
},
items: {
type: Array,
default: () => [],
},
});
const emit = defineEmits(['add']);
const BUFFER_SPACE = 20;
const [showPopover, togglePopover] = useToggle();
const buttonRef = ref();
const dropdownRef = ref();
const searchValue = ref('');
const { width: windowWidth, height: windowHeight } = useWindowSize();
const {
top: buttonTop,
left: buttonLeft,
width: buttonWidth,
height: buttonHeight,
} = useElementBounding(buttonRef);
const { width: dropdownWidth, height: dropdownHeight } =
useElementBounding(dropdownRef);
const filteredItems = computed(() => {
if (!searchValue.value) return props.items;
const query = searchValue.value.toLowerCase();
return picoSearch(props.items, query, ['name']);
});
const handleAdd = item => {
emit('add', item);
togglePopover(false);
};
const shouldShowAbove = computed(() => {
if (!buttonRef.value || !dropdownRef.value) return false;
const spaceBelow =
windowHeight.value - (buttonTop.value + buttonHeight.value);
const spaceAbove = buttonTop.value;
return (
spaceBelow < dropdownHeight.value + BUFFER_SPACE && spaceAbove > spaceBelow
);
});
const shouldAlignRight = computed(() => {
if (!buttonRef.value || !dropdownRef.value) return false;
const spaceRight = windowWidth.value - buttonLeft.value;
const spaceLeft = buttonLeft.value + buttonWidth.value;
return (
spaceRight < dropdownWidth.value + BUFFER_SPACE && spaceLeft > spaceRight
);
});
const handleClickOutside = () => {
if (showPopover.value) {
togglePopover(false);
}
};
</script>
<template>
<div
v-on-click-outside="handleClickOutside"
class="relative flex items-center group"
>
<Button
ref="buttonRef"
slate
type="button"
icon="i-lucide-plus"
sm
:label="label"
@click="togglePopover(!showPopover)"
/>
<div
v-if="showPopover"
ref="dropdownRef"
class="z-50 flex flex-col items-start absolute bg-n-alpha-3 backdrop-blur-[50px] border-0 gap-4 outline outline-1 outline-n-weak rounded-xl max-w-96 min-w-80 max-h-[20rem] overflow-y-auto py-2"
:class="[
shouldShowAbove ? 'bottom-full mb-2' : 'top-full mt-2',
shouldAlignRight ? 'right-0' : 'left-0',
]"
>
<div class="flex flex-col divide-y divide-n-slate-4 w-full">
<Input
v-model="searchValue"
:placeholder="searchPlaceholder"
custom-input-class="bg-transparent !outline-none w-full ltr:!pl-10 rtl:!pr-10 h-10"
>
<template #prefix>
<Icon
icon="i-lucide-search"
class="absolute -translate-y-1/2 text-n-slate-11 size-4 top-1/2 ltr:left-3 rtl:right-3"
/>
</template>
</Input>
<div
v-for="item in filteredItems"
:key="item.id"
class="flex gap-3 min-w-0 w-full py-4 px-3 hover:bg-n-alpha-2 cursor-pointer"
:class="{ 'items-center': item.color, 'items-start': !item.color }"
@click="handleAdd(item)"
>
<Icon
v-if="item.icon"
:icon="item.icon"
class="size-4 text-n-slate-12 flex-shrink-0 mt-0.5"
/>
<span
v-else-if="item.color"
:style="{ backgroundColor: item.color }"
class="size-3 rounded-sm"
/>
<Avatar
v-else
:title="item.name"
:src="item.avatarUrl"
:name="item.name"
:size="20"
rounded-full
/>
<div class="flex flex-col items-start gap-2 min-w-0 flex-1">
<div class="flex items-center gap-1 min-w-0 w-full">
<span
:title="item.name || item.title"
class="text-sm text-n-slate-12 truncate min-w-0 flex-1"
>
{{ item.name || item.title }}
</span>
</div>
<span
v-if="item.email || item.phoneNumber"
:title="item.email || item.phoneNumber"
class="text-sm text-n-slate-11 truncate min-w-0 w-full block"
>
{{ item.email || item.phoneNumber }}
</span>
</div>
</div>
</div>
</div>
</div>
</template>
@@ -1,127 +0,0 @@
<script setup>
import { computed, watch } from 'vue';
import { useVuelidate } from '@vuelidate/core';
import { required, minLength } from '@vuelidate/validators';
import WithLabel from 'v3/components/Form/WithLabel.vue';
import Input from 'dashboard/components-next/input/Input.vue';
import Switch from 'dashboard/components-next/switch/Switch.vue';
defineProps({
nameLabel: {
type: String,
default: '',
},
namePlaceholder: {
type: String,
default: '',
},
descriptionLabel: {
type: String,
default: '',
},
descriptionPlaceholder: {
type: String,
default: '',
},
statusLabel: {
type: String,
default: '',
},
statusPlaceholder: {
type: String,
default: '',
},
});
const emit = defineEmits(['validationChange']);
const policyName = defineModel('policyName', {
type: String,
default: '',
});
const description = defineModel('description', {
type: String,
default: '',
});
const enabled = defineModel('enabled', {
type: Boolean,
default: true,
});
const validationRules = {
policyName: { required, minLength: minLength(1) },
description: { required, minLength: minLength(1) },
};
const v$ = useVuelidate(validationRules, { policyName, description });
const isValid = computed(() => !v$.value.$invalid);
watch(
isValid,
() => {
emit('validationChange', {
isValid: isValid.value,
section: 'baseInfo',
});
},
{ immediate: true }
);
</script>
<template>
<div class="flex flex-col gap-4 pb-4">
<!-- Policy Name Field -->
<div class="flex items-center gap-6">
<WithLabel
:label="nameLabel"
name="policyName"
class="flex items-center w-full [&>label]:min-w-[120px]"
>
<div class="flex-1">
<Input
v-model="policyName"
type="text"
:placeholder="namePlaceholder"
/>
</div>
</WithLabel>
</div>
<!-- Description Field -->
<div class="flex items-center gap-6">
<WithLabel
:label="descriptionLabel"
name="description"
class="flex items-center w-full [&>label]:min-w-[120px]"
>
<div class="flex-1">
<Input
v-model="description"
type="text"
:placeholder="descriptionPlaceholder"
/>
</div>
</WithLabel>
</div>
<!-- Status Field -->
<div v-if="statusLabel" class="flex items-center gap-6">
<WithLabel
:label="statusLabel"
name="enabled"
class="flex items-center w-full [&>label]:min-w-[120px]"
>
<div class="flex items-center gap-2">
<Switch v-model="enabled" />
<span class="text-sm text-n-slate-11">
{{ statusPlaceholder }}
</span>
</div>
</WithLabel>
</div>
</div>
</template>
@@ -1,121 +0,0 @@
<script setup>
import { useToggle } from '@vueuse/core';
import { vOnClickOutside } from '@vueuse/components';
import Avatar from 'next/avatar/Avatar.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
defineProps({
count: {
type: Number,
default: 0,
},
title: {
type: String,
default: '',
},
icon: {
type: String,
default: '',
},
items: {
type: Array,
default: () => [],
},
isFetching: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['fetch']);
const [showPopover, togglePopover] = useToggle();
const handleButtonClick = () => {
emit('fetch');
togglePopover(!showPopover.value);
};
const handleClickOutside = () => {
if (showPopover.value) {
togglePopover(false);
}
};
</script>
<template>
<div
v-on-click-outside="handleClickOutside"
class="relative flex items-center group"
>
<button
v-if="count"
class="h-6 px-2 rounded-md bg-n-alpha-2 gap-1.5 flex items-center"
@click="handleButtonClick()"
>
<Icon :icon="icon" class="size-3.5 text-n-slate-12" />
<span class="text-n-slate-12 text-sm">
{{ count }}
</span>
</button>
<div
v-if="showPopover"
class="top-full mt-1 ltr:left-0 rtl:right-0 z-50 flex flex-col items-start absolute bg-n-alpha-3 backdrop-blur-[50px] border-0 gap-4 outline outline-1 outline-n-weak p-3 rounded-xl max-w-96 min-w-80 max-h-[20rem] overflow-y-auto"
>
<div class="flex items-center gap-2.5 pb-2">
<Icon :icon="icon" class="size-3.5" />
<span class="text-sm text-n-slate-12 font-medium">{{ title }}</span>
</div>
<div
v-if="isFetching"
class="flex items-center justify-center py-3 w-full text-n-slate-11"
>
<Spinner />
</div>
<div v-else class="flex flex-col gap-4 w-full">
<div
v-for="item in items"
:key="item.id"
class="flex items-center justify-between gap-2 min-w-0 w-full"
>
<div class="flex items-center gap-2 min-w-0 w-full">
<Icon
v-if="item.icon"
:icon="item.icon"
class="size-4 text-n-slate-12 flex-shrink-0"
/>
<Avatar
v-else
:title="item.name"
:src="item.avatarUrl"
:name="item.name"
:size="20"
rounded-full
/>
<div class="flex items-center gap-1 min-w-0 flex-1">
<span
:title="item.name"
class="text-sm text-n-slate-12 truncate min-w-0"
>
{{ item.name }}
</span>
<span
v-if="item.id"
class="text-sm text-n-slate-11 flex-shrink-0"
>
{{ `#${item.id}` }}
</span>
</div>
</div>
<span v-if="item.email" class="text-sm text-n-slate-11 flex-shrink-0">
{{ item.email }}
</span>
</div>
</div>
</div>
</div>
</template>
@@ -1,90 +0,0 @@
<script setup>
import Avatar from 'next/avatar/Avatar.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import Button from 'dashboard/components-next/button/Button.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
defineProps({
items: {
type: Array,
default: () => [],
},
isFetching: {
type: Boolean,
default: false,
},
emptyStateMessage: {
type: String,
default: '',
},
});
const emit = defineEmits(['delete']);
const handleDelete = itemId => {
emit('delete', itemId);
};
</script>
<template>
<div
v-if="isFetching"
class="flex items-center justify-center py-3 w-full text-n-slate-11"
>
<Spinner />
</div>
<div
v-else-if="items.length === 0 && emptyStateMessage"
class="custom-dashed-border flex items-center justify-center py-6 w-full"
>
<span class="text-sm text-n-slate-11">
{{ emptyStateMessage }}
</span>
</div>
<div v-else class="flex flex-col divide-y divide-n-weak">
<div
v-for="item in items"
:key="item.id"
class="grid grid-cols-4 items-center gap-3 min-w-0 w-full justify-between h-[3.25rem] ltr:pr-2 rtl:pl-2"
>
<div class="flex items-center gap-2 col-span-2">
<Icon
v-if="item.icon"
:icon="item.icon"
class="size-4 text-n-slate-12 flex-shrink-0"
/>
<Avatar
v-else
:title="item.name"
:src="item.avatarUrl"
:name="item.name"
:size="20"
rounded-full
/>
<span class="text-sm text-n-slate-12 truncate min-w-0">
{{ item.name }}
</span>
</div>
<div class="flex items-start gap-2 col-span-1">
<span
:title="item.email || item.phoneNumber"
class="text-sm text-n-slate-12 truncate min-w-0"
>
{{ item.email || item.phoneNumber }}
</span>
</div>
<div class="col-span-1 justify-end flex items-center">
<Button
icon="i-lucide-trash"
slate
ghost
sm
type="button"
@click="handleDelete(item.id)"
/>
</div>
</div>
</div>
</template>
@@ -1,149 +0,0 @@
<script setup>
import { computed, ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import AddDataDropdown from 'dashboard/components-next/AssignmentPolicy/components/AddDataDropdown.vue';
import LabelItem from 'dashboard/components-next/Label/LabelItem.vue';
import DurationInput from 'dashboard/components-next/input/DurationInput.vue';
import { DURATION_UNITS } from 'dashboard/components-next/input/constants';
const props = defineProps({
tagsList: {
type: Array,
default: () => [],
},
});
const excludedLabels = defineModel('excludedLabels', {
type: Array,
default: () => [],
});
const excludeOlderThanMinutes = defineModel('excludeOlderThanMinutes', {
type: Number,
default: 10,
});
// Duration limits: 10 minutes to 999 days (in minutes)
const MIN_DURATION_MINUTES = 10;
const MAX_DURATION_MINUTES = 1438560; // 999 days * 24 hours * 60 minutes
const { t } = useI18n();
const hoveredLabel = ref(null);
const windowUnit = ref(DURATION_UNITS.MINUTES);
const addedTags = computed(() =>
props.tagsList
.filter(label => excludedLabels.value.includes(label.name))
.map(label => ({ id: label.id, title: label.name, ...label }))
);
const filteredTags = computed(() =>
props.tagsList.filter(
label => !addedTags.value.some(tag => tag.id === label.id)
)
);
const detectUnit = minutes => {
const m = Number(minutes) || 0;
if (m === 0) return DURATION_UNITS.MINUTES;
if (m % (24 * 60) === 0) return DURATION_UNITS.DAYS;
if (m % 60 === 0) return DURATION_UNITS.HOURS;
return DURATION_UNITS.MINUTES;
};
const onClickAddTag = tag => {
excludedLabels.value = [...excludedLabels.value, tag.name];
};
const onClickRemoveTag = tag => {
excludedLabels.value = excludedLabels.value.filter(
name => name !== tag.title
);
};
onMounted(() => {
windowUnit.value = detectUnit(excludeOlderThanMinutes.value);
});
</script>
<template>
<div class="py-4 flex-col flex gap-6">
<div class="flex flex-col items-start gap-1 py-1">
<label class="text-sm font-medium text-n-slate-12 py-1">
{{
t(
'ASSIGNMENT_POLICY.AGENT_CAPACITY_POLICY.FORM.EXCLUSION_RULES.LABEL'
)
}}
</label>
<p class="mb-0 text-n-slate-11 text-sm">
{{
t(
'ASSIGNMENT_POLICY.AGENT_CAPACITY_POLICY.FORM.EXCLUSION_RULES.DESCRIPTION'
)
}}
</p>
</div>
<div class="flex flex-col items-start gap-4">
<label class="text-sm font-medium text-n-slate-12 py-1">
{{
t(
'ASSIGNMENT_POLICY.AGENT_CAPACITY_POLICY.FORM.EXCLUSION_RULES.TAGS.LABEL'
)
}}
</label>
<div
class="flex items-start gap-2 flex-wrap"
@mouseleave="hoveredLabel = null"
>
<LabelItem
v-for="tag in addedTags"
:key="tag.id"
:label="tag"
:is-hovered="hoveredLabel === tag.id"
class="h-8"
@remove="onClickRemoveTag"
@hover="hoveredLabel = tag.id"
/>
<AddDataDropdown
:label="
t(
'ASSIGNMENT_POLICY.AGENT_CAPACITY_POLICY.FORM.EXCLUSION_RULES.TAGS.ADD_TAG'
)
"
:search-placeholder="
t(
'ASSIGNMENT_POLICY.AGENT_CAPACITY_POLICY.FORM.EXCLUSION_RULES.TAGS.DROPDOWN.SEARCH_PLACEHOLDER'
)
"
:items="filteredTags"
class="[&>button]:!text-n-blue-text [&>div]:min-w-64"
@add="onClickAddTag"
/>
</div>
</div>
<div class="flex flex-col items-start gap-4">
<label class="text-sm font-medium text-n-slate-12 py-1">
{{
t(
'ASSIGNMENT_POLICY.AGENT_CAPACITY_POLICY.FORM.EXCLUSION_RULES.DURATION.LABEL'
)
}}
</label>
<div
class="flex items-center gap-2 flex-1 [&>select]:!bg-n-alpha-2 [&>select]:!outline-none [&>select]:hover:brightness-110"
>
<!-- allow 10 mins to 999 days -->
<DurationInput
v-model:unit="windowUnit"
v-model:model-value="excludeOlderThanMinutes"
:min="MIN_DURATION_MINUTES"
:max="MAX_DURATION_MINUTES"
/>
</div>
</div>
</div>
</template>
@@ -1,86 +0,0 @@
<script setup>
import { ref, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import Input from 'dashboard/components-next/input/Input.vue';
import DurationInput from 'dashboard/components-next/input/DurationInput.vue';
import { DURATION_UNITS } from 'dashboard/components-next/input/constants';
const { t } = useI18n();
const fairDistributionLimit = defineModel('fairDistributionLimit', {
type: Number,
default: 100,
set(value) {
return Number(value) || 0;
},
});
const fairDistributionWindow = defineModel('fairDistributionWindow', {
type: Number,
default: 3600,
set(value) {
return Number(value) || 0;
},
});
const windowUnit = ref(DURATION_UNITS.MINUTES);
const detectUnit = minutes => {
const m = Number(minutes) || 0;
if (m === 0) return DURATION_UNITS.MINUTES;
if (m % (24 * 60) === 0) return DURATION_UNITS.DAYS;
if (m % 60 === 0) return DURATION_UNITS.HOURS;
return DURATION_UNITS.MINUTES;
};
onMounted(() => {
windowUnit.value = detectUnit(fairDistributionWindow.value);
});
</script>
<template>
<div
class="flex items-start xl:items-center flex-col md:flex-row gap-4 lg:gap-3 bg-n-solid-1 p-4 outline outline-1 outline-n-weak rounded-xl"
>
<div class="flex items-center gap-3">
<label class="text-sm font-medium text-n-slate-12">
{{
t(
'ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.FORM.FAIR_DISTRIBUTION.INPUT_MAX'
)
}}
</label>
<div class="flex-1">
<Input
v-model="fairDistributionLimit"
type="number"
placeholder="100"
max="100000"
class="w-full"
/>
</div>
</div>
<div class="flex sm:flex-row flex-col items-start sm:items-center gap-4">
<label class="text-sm font-medium text-n-slate-12">
{{
t(
'ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.FORM.FAIR_DISTRIBUTION.DURATION'
)
}}
</label>
<div
class="flex items-center gap-2 flex-1 [&>select]:!bg-n-alpha-2 [&>select]:!outline-none [&>select]:hover:brightness-110"
>
<!-- allow 10 mins to 999 days -->
<DurationInput
v-model:model-value="fairDistributionWindow"
v-model:unit="windowUnit"
:min="10"
:max="1438560"
/>
</div>
</div>
</div>
</template>
@@ -1,177 +0,0 @@
<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from 'dashboard/components-next/button/Button.vue';
import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
import AddDataDropdown from 'dashboard/components-next/AssignmentPolicy/components/AddDataDropdown.vue';
const props = defineProps({
inboxList: {
type: Array,
default: () => [],
},
isFetching: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['delete', 'add', 'update']);
const inboxCapacityLimits = defineModel('inboxCapacityLimits', {
type: Array,
default: () => [],
});
const { t } = useI18n();
const BASE_KEY = 'ASSIGNMENT_POLICY.AGENT_CAPACITY_POLICY';
const DEFAULT_CONVERSATION_LIMIT = 10;
const MIN_CONVERSATION_LIMIT = 1;
const MAX_CONVERSATION_LIMIT = 100000;
const selectedInboxIds = computed(
() => new Set(inboxCapacityLimits.value.map(limit => limit.inboxId))
);
const availableInboxes = computed(() =>
props.inboxList.filter(
inbox => inbox && !selectedInboxIds.value.has(inbox.id)
)
);
const isLimitValid = limit => {
return (
limit.conversationLimit >= MIN_CONVERSATION_LIMIT &&
limit.conversationLimit <= MAX_CONVERSATION_LIMIT
);
};
const inboxMap = computed(
() => new Map(props.inboxList.map(inbox => [inbox.id, inbox]))
);
const handleAddInbox = inbox => {
emit('add', {
inboxId: inbox.id,
conversationLimit: DEFAULT_CONVERSATION_LIMIT,
});
};
const handleRemoveLimit = limitId => {
emit('delete', limitId);
};
const handleLimitChange = limit => {
if (isLimitValid(limit)) {
emit('update', limit);
}
};
const getInboxName = inboxId => {
return inboxMap.value.get(inboxId)?.name || '';
};
</script>
<template>
<div class="py-4 flex-col flex gap-3">
<div class="flex items-center w-full gap-8 justify-between pt-1 pb-3">
<label class="text-sm font-medium text-n-slate-12">
{{ t(`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.LABEL`) }}
</label>
<AddDataDropdown
:label="t(`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.ADD_BUTTON`)"
:search-placeholder="
t(`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.FIELD.SELECT_INBOX`)
"
:items="availableInboxes"
@add="handleAddInbox"
/>
</div>
<div
v-if="isFetching"
class="flex items-center justify-center py-3 w-full text-n-slate-11"
>
<Spinner />
</div>
<div
v-else-if="!inboxCapacityLimits.length"
class="custom-dashed-border flex items-center justify-center py-6 w-full"
>
<span class="text-sm text-n-slate-11">
{{ t(`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.EMPTY_STATE`) }}
</span>
</div>
<div v-else class="flex-col flex gap-3">
<div
v-for="(limit, index) in inboxCapacityLimits"
:key="limit.id || `temp-${index}`"
class="flex flex-col xs:flex-row items-stretch gap-3"
>
<div
class="flex items-center rounded-lg outline-1 outline cursor-not-allowed text-n-slate-11 outline-n-weak py-2.5 px-3 text-sm w-full min-w-0"
:title="getInboxName(limit.inboxId)"
>
<span class="truncate min-w-0">
{{ getInboxName(limit.inboxId) }}
</span>
</div>
<div class="flex items-center gap-3 w-full xs:w-auto">
<div
class="py-2.5 px-3 rounded-lg gap-2 outline outline-1 flex-1 xs:flex-shrink-0 flex items-center min-w-0"
:class="[
!isLimitValid(limit) ? 'outline-n-ruby-8' : 'outline-n-weak',
]"
>
<label
class="text-sm text-n-slate-12 ltr:pr-2 rtl:pl-2 truncate min-w-0 flex-shrink"
:title="
t(
`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.FIELD.MAX_CONVERSATIONS`
)
"
>
{{
t(
`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.FIELD.MAX_CONVERSATIONS`
)
}}
</label>
<div class="h-5 w-px bg-n-weak" />
<input
v-model.number="limit.conversationLimit"
type="number"
:min="MIN_CONVERSATION_LIMIT"
:max="MAX_CONVERSATION_LIMIT"
class="reset-base bg-transparent focus:outline-none min-w-16 w-24 text-sm flex-shrink-0"
:class="[
!isLimitValid(limit)
? 'placeholder:text-n-ruby-9 !text-n-ruby-9'
: 'placeholder:text-n-slate-10 text-n-slate-12',
]"
:placeholder="
t(`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.FIELD.SET_LIMIT`)
"
@blur="handleLimitChange(limit)"
/>
</div>
<Button
type="button"
slate
icon="i-lucide-trash"
class="flex-shrink-0"
@click="handleRemoveLimit(limit.id)"
/>
</div>
</div>
</div>
</div>
</template>
@@ -1,60 +0,0 @@
<script setup>
const props = defineProps({
id: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
isActive: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['select']);
const handleChange = () => {
if (!props.isActive) {
emit('select', props.id);
}
};
</script>
<template>
<div
class="relative cursor-pointer rounded-xl outline outline-1 p-4 transition-all duration-200 bg-n-solid-1 py-4 ltr:pl-4 rtl:pr-4 ltr:pr-6 rtl:pl-6"
:class="[
isActive ? 'outline-n-blue-9' : 'outline-n-weak hover:outline-n-strong',
]"
@click="handleChange"
>
<div class="absolute top-4 right-4">
<input
:id="`${id}`"
:checked="isActive"
:value="id"
:name="id"
type="radio"
class="h-4 w-4 border-n-slate-6 text-n-brand focus:ring-n-brand focus:ring-offset-0"
@change="handleChange"
/>
</div>
<!-- Content -->
<div class="flex flex-col gap-3 items-start">
<h3 class="text-sm font-medium text-n-slate-12">
{{ label }}
</h3>
<p class="text-sm text-n-slate-11">
{{ description }}
</p>
</div>
</div>
</template>
@@ -1,92 +0,0 @@
<script setup>
import AddDataDropdown from '../AddDataDropdown.vue';
const mockInboxes = [
{
id: 1,
name: 'Website Support',
email: 'support@company.com',
icon: 'i-lucide-globe',
},
{
id: 2,
name: 'Email Support',
email: 'help@company.com',
icon: 'i-lucide-mail',
},
{
id: 3,
name: 'WhatsApp Business',
phoneNumber: '+1 555-0123',
icon: 'i-lucide-message-circle',
},
{
id: 4,
name: 'Facebook Messenger',
email: 'messenger@company.com',
icon: 'i-lucide-facebook',
},
{
id: 5,
name: 'Twitter DM',
email: 'twitter@company.com',
icon: 'i-lucide-twitter',
},
];
const mockTags = [
{
id: 1,
name: 'urgent',
color: '#ff4757',
},
{
id: 2,
name: 'bug',
color: '#ff6b6b',
},
{
id: 3,
name: 'feature-request',
color: '#4834d4',
},
{
id: 4,
name: 'documentation',
color: '#26de81',
},
];
const handleAdd = item => {
console.log('Add item:', item);
};
</script>
<template>
<Story
title="Components/AgentManagementPolicy/AddDataDropdown"
:layout="{ type: 'grid', width: '500px' }"
>
<Variant title="Basic Usage - Inboxes">
<div class="p-8 bg-n-background flex gap-4 h-[400px] items-start">
<AddDataDropdown
label="Add Inbox"
search-placeholder="Search inboxes..."
:items="mockInboxes"
@add="handleAdd"
/>
</div>
</Variant>
<Variant title="Basic Usage - Tags">
<div class="p-8 bg-n-background flex gap-4 h-[400px] items-start">
<AddDataDropdown
label="Add Tag"
search-placeholder="Search tags..."
:items="mockTags"
@add="handleAdd"
/>
</div>
</Variant>
</Story>
</template>
@@ -1,33 +0,0 @@
<script setup>
import { ref } from 'vue';
import BaseInfo from '../BaseInfo.vue';
const policyName = ref('Round Robin Policy');
const description = ref(
'Distributes conversations evenly among available agents'
);
const enabled = ref(true);
</script>
<template>
<Story
title="Components/AgentManagementPolicy/BaseInfo"
:layout="{ type: 'grid', width: '600px' }"
>
<Variant title="Basic Usage">
<div class="p-8 bg-n-background">
<BaseInfo
v-model:policy-name="policyName"
v-model:description="description"
v-model:enabled="enabled"
name-label="Policy Name"
name-placeholder="Enter policy name"
description-label="Description"
description-placeholder="Enter policy description"
status-label="Status"
status-placeholder="Active"
/>
</div>
</Variant>
</Story>
</template>
@@ -1,89 +0,0 @@
<script setup>
import CardPopover from '../CardPopover.vue';
const mockItems = [
{
id: 1,
name: 'Website Support',
icon: 'i-lucide-globe',
},
{
id: 2,
name: 'Email Support',
icon: 'i-lucide-mail',
},
{
id: 3,
name: 'WhatsApp Business',
icon: 'i-lucide-message-circle',
},
{
id: 4,
name: 'Facebook Messenger',
icon: 'i-lucide-facebook',
},
];
const mockUsers = [
{
id: 1,
name: 'John Smith',
email: 'john.smith@example.com',
avatarUrl: 'https://i.pravatar.cc/150?img=1',
},
{
id: 2,
name: 'Sarah Johnson',
email: 'sarah.johnson@example.com',
avatarUrl: 'https://i.pravatar.cc/150?img=2',
},
{
id: 3,
name: 'Mike Chen',
email: 'mike.chen@example.com',
avatarUrl: 'https://i.pravatar.cc/150?img=3',
},
{
id: 4,
name: 'Emily Davis',
email: 'emily.davis@example.com',
avatarUrl: 'https://i.pravatar.cc/150?img=4',
},
{
id: 5,
name: 'Alex Rodriguez',
email: 'alex.rodriguez@example.com',
avatarUrl: 'https://i.pravatar.cc/150?img=5',
},
];
</script>
<template>
<Story
title="Components/AgentManagementPolicy/CardPopover"
:layout="{ type: 'grid', width: '800px' }"
>
<Variant title="Basic Usage">
<div class="p-8 bg-n-background flex gap-4 h-96 items-start">
<CardPopover
:count="3"
title="Added Inboxes"
icon="i-lucide-inbox"
:items="mockItems.slice(0, 3)"
@fetch="() => console.log('Fetch triggered')"
/>
</div>
</Variant>
<Variant title="Basic Usage">
<div class="p-8 bg-n-background flex gap-4 h-96 items-start">
<CardPopover
:count="3"
title="Added Agents"
icon="i-lucide-users-round"
:items="mockUsers.slice(0, 3)"
@fetch="() => console.log('Fetch triggered')"
/>
</div>
</Variant>
</Story>
</template>
@@ -1,87 +0,0 @@
<script setup>
import DataTable from '../DataTable.vue';
const mockItems = [
{
id: 1,
name: 'Website Support',
email: 'support@company.com',
icon: 'i-lucide-globe',
},
{
id: 2,
name: 'Email Support',
email: 'help@company.com',
icon: 'i-lucide-mail',
},
{
id: 3,
name: 'WhatsApp Business',
phoneNumber: '+1 555-0123',
icon: 'i-lucide-message-circle',
},
];
const mockAgentList = [
{
id: 1,
name: 'John Doe',
email: 'john.doe@example.com',
avatarUrl: 'https://i.pravatar.cc/150?img=1',
},
{
id: 2,
name: 'Jane Smith',
email: 'jane.smith@example.com',
avatarUrl: 'https://i.pravatar.cc/150?img=2',
},
];
const handleDelete = itemId => {
console.log('Delete item:', itemId);
};
</script>
<template>
<Story
title="Components/AgentManagementPolicy/DataTable"
:layout="{ type: 'grid', width: '800px' }"
>
<Variant title="With Data">
<div class="p-8 bg-n-background">
<DataTable
:items="mockItems"
:is-fetching="false"
@delete="handleDelete"
/>
</div>
</Variant>
<Variant title="With Agents">
<div class="p-8 bg-n-background">
<DataTable
:items="mockAgentList"
:is-fetching="false"
@delete="handleDelete"
/>
</div>
</Variant>
<Variant title="Loading State">
<div class="p-8 bg-n-background">
<DataTable :items="[]" is-fetching @delete="handleDelete" />
</div>
</Variant>
<Variant title="Empty State">
<div class="p-8 bg-n-background">
<DataTable
:items="[]"
:is-fetching="false"
empty-state-message="No items found"
@delete="handleDelete"
/>
</div>
</Variant>
</Story>
</template>
@@ -1,67 +0,0 @@
<script setup>
import ExclusionRules from '../ExclusionRules.vue';
import { ref } from 'vue';
const mockTagsList = [
{
id: 1,
name: 'urgent',
color: '#ff4757',
},
{
id: 2,
name: 'bug',
color: '#ff6b6b',
},
{
id: 3,
name: 'feature-request',
color: '#4834d4',
},
{
id: 4,
name: 'documentation',
color: '#26de81',
},
{
id: 5,
name: 'enhancement',
color: '#2ed573',
},
{
id: 6,
name: 'question',
color: '#ffa502',
},
{
id: 7,
name: 'duplicate',
color: '#747d8c',
},
{
id: 8,
name: 'wontfix',
color: '#57606f',
},
];
const excludedLabelsBasic = ref([]);
const excludeOlderThanHoursBasic = ref(10);
</script>
<template>
<Story
title="Components/AgentManagementPolicy/ExclusionRules"
:layout="{ type: 'grid', width: '1200px' }"
>
<Variant title="Basic Usage">
<div class="p-8 bg-n-background h-[600px]">
<ExclusionRules
v-model:excluded-labels="excludedLabelsBasic"
v-model:exclude-older-than-minutes="excludeOlderThanHoursBasic"
:tags-list="mockTagsList"
/>
</div>
</Variant>
</Story>
</template>
@@ -1,25 +0,0 @@
<script setup>
import { ref } from 'vue';
import FairDistribution from '../FairDistribution.vue';
const fairDistributionLimit = ref(100);
const fairDistributionWindow = ref(3600);
const windowUnit = ref('minutes');
</script>
<template>
<Story
title="Components/AgentManagementPolicy/FairDistribution"
:layout="{ type: 'grid', width: '800px' }"
>
<Variant title="Basic Usage">
<div class="p-8 bg-n-background">
<FairDistribution
v-model:fair-distribution-limit="fairDistributionLimit"
v-model:fair-distribution-window="fairDistributionWindow"
v-model:window-unit="windowUnit"
/>
</div>
</Variant>
</Story>
</template>
@@ -1,108 +0,0 @@
<script setup>
import InboxCapacityLimits from '../InboxCapacityLimits.vue';
import { ref } from 'vue';
const mockInboxList = [
{
value: 1,
label: 'Website Support',
icon: 'i-lucide-globe',
},
{
value: 2,
label: 'Email Support',
icon: 'i-lucide-mail',
},
{
value: 3,
label: 'WhatsApp Business',
icon: 'i-lucide-message-circle',
},
{
value: 4,
label: 'Facebook Messenger',
icon: 'i-lucide-facebook',
},
{
value: 5,
label: 'Twitter DM',
icon: 'i-lucide-twitter',
},
{
value: 6,
label: 'Telegram',
icon: 'i-lucide-send',
},
];
const inboxCapacityLimitsEmpty = ref([]);
const inboxCapacityLimitsNew = ref([
{ id: 1, inboxId: 1, conversationLimit: 5 },
{ inboxId: null, conversationLimit: null },
]);
const handleDelete = id => {
console.log('Delete capacity limit:', id);
};
</script>
<template>
<Story
title="Components/AgentManagementPolicy/InboxCapacityLimits"
:layout="{ type: 'grid', width: '900px' }"
>
<Variant title="Empty State">
<div class="p-8 bg-n-background">
<InboxCapacityLimits
v-model:inbox-capacity-limits="inboxCapacityLimitsEmpty"
:inbox-list="mockInboxList"
:is-fetching="false"
:is-updating="false"
@delete="handleDelete"
/>
</div>
</Variant>
<Variant title="Loading State">
<div class="p-8 bg-n-background">
<InboxCapacityLimits
v-model:inbox-capacity-limits="inboxCapacityLimitsEmpty"
:inbox-list="mockInboxList"
is-fetching
:is-updating="false"
@delete="handleDelete"
/>
</div>
</Variant>
<Variant title="With New Row and existing data">
<div class="p-8 bg-n-background">
<InboxCapacityLimits
v-model:inbox-capacity-limits="inboxCapacityLimitsNew"
:inbox-list="mockInboxList"
:is-fetching="false"
:is-updating="false"
@delete="handleDelete"
/>
</div>
</Variant>
<Variant title="Interactive Demo">
<div class="p-8 bg-n-background">
<InboxCapacityLimits
v-model:inbox-capacity-limits="inboxCapacityLimitsEmpty"
:inbox-list="mockInboxList"
:is-fetching="false"
:is-updating="false"
@delete="handleDelete"
/>
<div class="mt-4 p-4 bg-n-alpha-2 rounded-lg">
<h4 class="text-sm font-medium mb-2">Current Limits:</h4>
<pre class="text-xs">{{
JSON.stringify(inboxCapacityLimitsEmpty, null, 2)
}}</pre>
</div>
</div>
</Variant>
</Story>
</template>
@@ -1,61 +0,0 @@
<script setup>
import { ref } from 'vue';
import RadioCard from '../RadioCard.vue';
const selectedOption = ref('round_robin');
const handleSelect = value => {
selectedOption.value = value;
console.log('Selected:', value);
};
</script>
<template>
<Story
title="Components/AgentManagementPolicy/RadioCard"
:layout="{ type: 'grid', width: '600px' }"
>
<Variant title="Basic Usage">
<div class="p-8 bg-n-background space-y-4">
<RadioCard
id="round_robin"
label="Round Robin"
description="Distributes conversations evenly among all available agents in a rotating manner"
:is-active="selectedOption === 'round_robin'"
@select="handleSelect"
/>
<RadioCard
id="balanced"
label="Balanced Assignment"
description="Assigns conversations based on agent workload to maintain balance"
:is-active="selectedOption === 'balanced'"
@select="handleSelect"
/>
</div>
</Variant>
<Variant title="Active State">
<div class="p-8 bg-n-background">
<RadioCard
id="active_option"
label="Active Option"
description="This option is currently selected and active"
is-active
@select="handleSelect"
/>
</div>
</Variant>
<Variant title="Inactive State">
<div class="p-8 bg-n-background">
<RadioCard
id="inactive_option"
label="Inactive Option"
description="This option is not selected and can be clicked to activate"
is-active
@select="handleSelect"
/>
</div>
</Variant>
</Story>
</template>
@@ -86,8 +86,8 @@ const handleLabelAction = async ({ value }) => {
}
};
const handleRemoveLabel = label => {
return handleLabelAction({ value: label.id });
const handleRemoveLabel = labelId => {
return handleLabelAction({ value: labelId });
};
watch(
@@ -51,20 +51,12 @@ const originalState = reactive({ ...state });
const liveChatWidgets = computed(() => {
const inboxes = store.getters['inboxes/getInboxes'];
const widgetOptions = inboxes
return inboxes
.filter(inbox => inbox.channel_type === 'Channel::WebWidget')
.map(inbox => ({
value: inbox.id,
label: inbox.name,
}));
return [
{
value: '',
label: t('HELP_CENTER.PORTAL_SETTINGS.FORM.LIVE_CHAT_WIDGET.NONE_OPTION'),
},
...widgetOptions,
];
});
const rules = {
@@ -116,7 +108,7 @@ watch(
widgetColor: newVal.color,
homePageLink: newVal.homepage_link,
slug: newVal.slug,
liveChatWidgetInboxId: newVal.inbox?.id || '',
liveChatWidgetInboxId: newVal.inbox?.id,
});
if (newVal.logo) {
const {
@@ -15,7 +15,7 @@ const props = defineProps({
const emit = defineEmits(['remove', 'hover']);
const handleRemoveLabel = () => {
emit('remove', props.label);
emit('remove', props.label?.id);
};
const handleMouseEnter = () => {
@@ -45,7 +45,6 @@ const handleMouseEnter = () => {
<Button
class="transition-opacity duration-200 !h-7 ltr:rounded-r-md rtl:rounded-l-md ltr:rounded-l-none rtl:rounded-r-none w-6 bg-transparent"
:class="{ 'opacity-0': !isHovered, 'opacity-100': isHovered }"
type="button"
slate
xs
faded
@@ -103,12 +103,6 @@ const validationError = computed(() => {
);
});
const inputFieldType = computed(() => {
if (inputType.value === 'date') return 'date';
if (inputType.value === 'number') return 'number';
return 'text';
});
const resetModelOnAttributeKeyChange = newAttributeKey => {
/**
* Resets the filter values and operator when the attribute key changes. This ensures that
@@ -188,7 +182,7 @@ defineExpose({ validate });
<Input
v-else
v-model="values"
:type="inputFieldType"
:type="inputType === 'date' ? 'date' : 'text'"
class="[&>input]:h-8 [&>input]:py-1.5 [&>input]:outline-offset-0"
:placeholder="t('FILTER.INPUT_PLACEHOLDER')"
/>
@@ -164,8 +164,8 @@ export function useConversationFilterContext() {
value: CONVERSATION_ATTRIBUTES.DISPLAY_ID,
attributeName: t('FILTER.ATTRIBUTES.CONVERSATION_IDENTIFIER'),
label: t('FILTER.ATTRIBUTES.CONVERSATION_IDENTIFIER'),
inputType: 'number',
dataType: 'number',
inputType: 'plainText',
datatype: 'number',
filterOperators: containmentOperators.value,
attributeModel: 'standard',
},
@@ -179,7 +179,7 @@ export function useConversationFilterContext() {
id: campaign.id,
name: campaign.title,
})),
dataType: 'number',
datatype: 'number',
filterOperators: presenceOperators.value,
attributeModel: 'standard',
},
@@ -78,8 +78,6 @@ watch(unit, () => {
<option :value="DURATION_UNITS.HOURS">
{{ t('DURATION_INPUT.HOURS') }}
</option>
<option :value="DURATION_UNITS.DAYS">
{{ t('DURATION_INPUT.DAYS') }}
</option>
<option :value="DURATION_UNITS.DAYS">{{ t('DURATION_INPUT.DAYS') }}</option>
</select>
</template>
@@ -7,11 +7,6 @@ const props = defineProps({
placeholder: { type: String, default: '' },
label: { type: String, default: '' },
id: { type: String, default: '' },
size: {
type: String,
default: 'md',
validator: value => ['sm', 'md'].includes(value),
},
message: { type: String, default: '' },
disabled: { type: Boolean, default: false },
messageType: {
@@ -20,7 +15,6 @@ const props = defineProps({
validator: value => ['info', 'error', 'success'].includes(value),
},
min: { type: String, default: '' },
max: { type: String, default: '' },
autofocus: { type: Boolean, default: false },
});
@@ -60,12 +54,7 @@ const inputOutlineClass = computed(() => {
});
const handleInput = event => {
let value = event.target.value;
// Convert to number if type is number and value is not empty
if (props.type === 'number' && value !== '') {
value = Number(value);
}
emit('update:modelValue', value);
emit('update:modelValue', event.target.value);
emit('input', event);
};
@@ -74,17 +63,6 @@ const handleFocus = event => {
isFocused.value = true;
};
const sizeClass = computed(() => {
switch (props.size) {
case 'sm':
return 'h-8 !px-3 !py-2';
case 'md':
return 'h-10 !px-3 !py-2.5';
default:
return 'h-10 !px-3 !py-2.5';
}
});
const handleBlur = event => {
emit('blur', event);
isFocused.value = false;
@@ -116,13 +94,11 @@ onMounted(() => {
<slot name="prefix" />
<input
:id="uniqueId"
v-bind="$attrs"
ref="inputRef"
:value="modelValue"
:class="[
customInputClass,
inputOutlineClass,
sizeClass,
{
error: messageType === 'error',
focus: isFocused,
@@ -132,12 +108,7 @@ onMounted(() => {
:placeholder="placeholder"
:disabled="disabled"
:min="['date', 'datetime-local', 'time'].includes(type) ? min : undefined"
:max="
['date', 'datetime-local', 'time', 'number'].includes(type)
? max
: undefined
"
class="block w-full reset-base text-sm !mb-0 outline outline-1 border-none border-0 outline-offset-[-1px] rounded-lg bg-n-alpha-black2 file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-n-slate-10 dark:placeholder:text-n-slate-10 disabled:cursor-not-allowed disabled:opacity-50 text-n-slate-12 transition-all duration-500 ease-in-out [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
class="block w-full reset-base text-sm h-10 !px-3 !py-2.5 !mb-0 outline outline-1 border-none border-0 outline-offset-[-1px] rounded-lg bg-n-alpha-black2 file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-n-slate-10 dark:placeholder:text-n-slate-10 disabled:cursor-not-allowed disabled:opacity-50 text-n-slate-12 transition-all duration-500 ease-in-out"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@@ -131,8 +131,6 @@ const props = defineProps({
sourceId: { type: String, default: '' }, // eslint-disable-line vue/no-unused-properties
});
const emit = defineEmits(['retry']);
const contextMenuPosition = ref({});
const showBackgroundHighlight = ref(false);
const showContextMenu = ref(false);
@@ -526,7 +524,6 @@ provideMessageContext({
class="[grid-area:meta]"
:class="flexOrientationClass"
:error="contentAttributes.externalError"
@retry="emit('retry')"
/>
</div>
<div v-if="shouldShowContextMenu" class="context-menu-wrap">
@@ -1,22 +1,16 @@
<script setup>
import { computed } from 'vue';
import Icon from 'next/icon/Icon.vue';
import { useI18n } from 'vue-i18n';
import { useMessageContext } from './provider.js';
import { hasOneDayPassed } from 'shared/helpers/timeHelper';
import { ORIENTATION, MESSAGE_STATUS } from './constants';
import { ORIENTATION } from './constants';
defineProps({
error: { type: String, required: true },
});
const emit = defineEmits(['retry']);
const { orientation, status, createdAt } = useMessageContext();
const { orientation } = useMessageContext();
const { t } = useI18n();
const canRetry = computed(() => !hasOneDayPassed(createdAt.value));
</script>
<template>
@@ -41,14 +35,5 @@ const canRetry = computed(() => !hasOneDayPassed(createdAt.value));
{{ error }}
</div>
</div>
<button
v-if="canRetry"
type="button"
:disabled="status !== MESSAGE_STATUS.FAILED"
class="bg-n-alpha-2 rounded-md size-5 grid place-content-center cursor-pointer"
@click="emit('retry')"
>
<Icon icon="i-lucide-refresh-ccw" class="text-n-ruby-11 size-[14px]" />
</button>
</div>
</template>
@@ -37,8 +37,6 @@ const props = defineProps({
},
});
const emit = defineEmits(['retry']);
const allMessages = computed(() => {
return useCamelCase(props.messages, { deep: true });
});
@@ -115,7 +113,6 @@ const getInReplyToMessage = parentMessage => {
:inbox-supports-reply-to="inboxSupportsReplyTo"
:current-user-id="currentUserId"
data-clarity-mask="True"
@retry="emit('retry', message)"
/>
</template>
<slot name="after" />
@@ -1,16 +1,8 @@
<script setup>
import {
computed,
onMounted,
useTemplateRef,
ref,
getCurrentInstance,
} from 'vue';
import { computed, onMounted, useTemplateRef, ref } from 'vue';
import Icon from 'next/icon/Icon.vue';
import { timeStampAppendedURL } from 'dashboard/helper/URLHelper';
import { downloadFile } from '@chatwoot/utils';
import { useEmitter } from 'dashboard/composables/emitter';
import { emitter } from 'shared/helpers/mitt';
const { attachment } = defineProps({
attachment: {
@@ -35,8 +27,6 @@ const currentTime = ref(0);
const duration = ref(0);
const playbackSpeed = ref(1);
const { uid } = getCurrentInstance();
const onLoadedMetadata = () => {
duration.value = audioPlayer.value?.duration;
};
@@ -53,18 +43,6 @@ onMounted(() => {
audioPlayer.value.playbackRate = playbackSpeed.value;
});
// Listen for global audio play events and pause if it's not this audio
useEmitter('pause_playing_audio', currentPlayingId => {
if (currentPlayingId !== uid && isPlaying.value) {
try {
audioPlayer.value.pause();
} catch {
/* ignore pause errors */
}
isPlaying.value = false;
}
});
const formatTime = time => {
if (!time || Number.isNaN(time)) return '00:00';
const minutes = Math.floor(time / 60);
@@ -92,8 +70,6 @@ const playOrPause = () => {
audioPlayer.value.pause();
isPlaying.value = false;
} else {
// Emit event to pause all other audio
emitter.emit('pause_playing_audio', uid);
audioPlayer.value.play();
isPlaying.value = true;
}
@@ -125,7 +101,6 @@ const downloadAudio = async () => {
ref="audioPlayer"
controls
class="hidden"
playsinline
@loadedmetadata="onLoadedMetadata"
@timeupdate="onTimeUpdate"
@ended="onEnd"
@@ -494,12 +494,6 @@ const menuItems = computed(() => {
icon: 'i-lucide-clock-alert',
to: accountScopedRoute('sla_list'),
},
{
name: 'Settings Security',
label: t('SIDEBAR.SECURITY'),
icon: 'i-lucide-shield',
to: accountScopedRoute('security_settings_index'),
},
{
name: 'Settings Billing',
label: t('SIDEBAR.BILLING'),
@@ -72,10 +72,6 @@ const formatType = computed(() => {
return format ? format.charAt(0) + format.slice(1).toLowerCase() : '';
});
const isDocumentTemplate = computed(() => {
return headerComponent.value?.format?.toLowerCase() === 'document';
});
const hasVariables = computed(() => {
return bodyText.value?.match(/{{([^}]+)}}/g) !== null;
});
@@ -130,11 +126,6 @@ const updateMediaUrl = value => {
processedParams.value.header.media_url = value;
};
const updateMediaName = value => {
processedParams.value.header ??= {};
processedParams.value.header.media_name = value;
};
const sendMessage = () => {
v$.value.$touch();
if (v$.value.$invalid) return;
@@ -177,12 +168,10 @@ defineExpose({
processedParams,
hasVariables,
hasMediaHeader,
isDocumentTemplate,
headerComponent,
renderedTemplate,
v$,
updateMediaUrl,
updateMediaName,
sendMessage,
resetTemplate,
goBack,
@@ -236,17 +225,6 @@ defineExpose({
@update:model-value="updateMediaUrl"
/>
</div>
<div v-if="isDocumentTemplate" class="flex items-center mb-2.5">
<Input
:model-value="processedParams.header?.media_name || ''"
type="text"
class="flex-1"
:placeholder="
t('WHATSAPP_TEMPLATES.PARSER.DOCUMENT_NAME_PLACEHOLDER')
"
@update:model-value="updateMediaName"
/>
</div>
</div>
<!-- Body Variables Section -->
@@ -1,328 +0,0 @@
<script setup>
import axios from 'axios';
import { ref, computed, nextTick } from 'vue';
import { useI18n } from 'vue-i18n';
import { handleOtpPaste } from 'shared/helpers/clipboard';
import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
import { useAccount } from 'dashboard/composables/useAccount';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import FormInput from 'v3/components/Form/Input.vue';
import NextButton from 'dashboard/components-next/button/Button.vue';
import Dialog from 'dashboard/components-next/dialog/Dialog.vue';
const props = defineProps({
mfaToken: {
type: String,
required: true,
},
});
const emit = defineEmits(['verified', 'cancel']);
const { t } = useI18n();
const { isOnChatwootCloud } = useAccount();
const OTP = 'otp';
const BACKUP = 'backup';
// State
const verificationMethod = ref(OTP);
const otpDigits = ref(['', '', '', '', '', '']);
const backupCode = ref('');
const isVerifying = ref(false);
const errorMessage = ref('');
const helpModalRef = ref(null);
const otpInputRefs = ref([]);
// Computed
const otpCode = computed(() => otpDigits.value.join(''));
const canSubmit = computed(() =>
verificationMethod.value === OTP
? otpCode.value.length === 6
: backupCode.value.length === 8
);
const contactDescKey = computed(() =>
isOnChatwootCloud.value ? 'CONTACT_DESC_CLOUD' : 'CONTACT_DESC_SELF_HOSTED'
);
const focusInput = i => otpInputRefs.value[i]?.focus();
// Verification
const handleVerification = async () => {
if (!canSubmit.value || isVerifying.value) return;
isVerifying.value = true;
errorMessage.value = '';
try {
const payload = {
mfa_token: props.mfaToken,
};
if (verificationMethod.value === OTP) {
payload.otp_code = otpCode.value;
} else {
payload.backup_code = backupCode.value;
}
const response = await axios.post('/auth/sign_in', payload);
// Set auth credentials and redirect
if (response.data && response.headers) {
// Store auth credentials in cookies
const authData = {
'access-token': response.headers['access-token'],
'token-type': response.headers['token-type'],
client: response.headers.client,
expiry: response.headers.expiry,
uid: response.headers.uid,
};
// Store in cookies for auth
document.cookie = `cw_d_session_info=${encodeURIComponent(JSON.stringify(authData))}; path=/; SameSite=Lax`;
// Redirect to dashboard
window.location.href = '/app/';
} else {
emit('verified', response.data);
}
} catch (error) {
errorMessage.value =
parseAPIErrorResponse(error) || t('MFA_VERIFICATION.VERIFICATION_FAILED');
// Clear inputs on error
if (verificationMethod.value === OTP) {
otpDigits.value.fill('');
await nextTick();
focusInput(0);
} else {
backupCode.value = '';
}
} finally {
isVerifying.value = false;
}
};
// OTP Input Handling
const handleOtpInput = async i => {
const v = otpDigits.value[i];
// Only allow numbers
if (!/^\d*$/.test(v)) {
otpDigits.value[i] = '';
return;
}
// Move to next input if value entered
if (v && i < 5) {
await nextTick();
focusInput(i + 1);
}
// Auto-submit if all digits entered
if (otpCode.value.length === 6) {
handleVerification();
}
};
const handleBackspace = (e, i) => {
if (!otpDigits.value[i] && i > 0) {
e.preventDefault();
focusInput(i - 1);
otpDigits.value[i - 1] = '';
}
};
const handleOtpCodePaste = e => {
e.preventDefault();
const code = handleOtpPaste(e, 6);
if (code) {
otpDigits.value = code.split('');
handleVerification();
}
};
// Alternative Actions
const handleTryAnotherMethod = () => {
// Toggle between methods
verificationMethod.value = verificationMethod.value === OTP ? BACKUP : OTP;
otpDigits.value.fill('');
backupCode.value = '';
errorMessage.value = '';
};
</script>
<template>
<div class="w-full max-w-md mx-auto">
<div
class="bg-white shadow sm:mx-auto sm:w-full sm:max-w-lg dark:bg-n-solid-2 p-11 sm:shadow-lg sm:rounded-lg"
>
<!-- Header -->
<div class="text-center mb-6">
<div
class="inline-flex items-center justify-center size-14 bg-n-solid-1 outline outline-n-weak rounded-full mb-4"
>
<Icon icon="i-lucide-lock-keyhole" class="size-6 text-n-slate-10" />
</div>
<h2 class="text-2xl font-semibold text-n-slate-12">
{{ $t('MFA_VERIFICATION.TITLE') }}
</h2>
<p class="text-sm text-n-slate-11 mt-2">
{{ $t('MFA_VERIFICATION.DESCRIPTION') }}
</p>
</div>
<!-- Tab Selection -->
<div class="flex rounded-lg bg-n-alpha-black2 p-1 mb-6">
<button
v-for="method in [OTP, BACKUP]"
:key="method"
class="flex-1 py-2 px-4 text-sm font-medium rounded-md transition-colors"
:class="
verificationMethod === method
? 'bg-n-solid-active text-n-slate-12 shadow-sm'
: 'text-n-slate-12'
"
@click="verificationMethod = method"
>
{{
$t(
`MFA_VERIFICATION.${method === OTP ? 'AUTHENTICATOR_APP' : 'BACKUP_CODE'}`
)
}}
</button>
</div>
<!-- Verification Form -->
<form class="space-y-4" @submit.prevent="handleVerification">
<!-- OTP Code Input -->
<div v-if="verificationMethod === OTP">
<label class="block text-sm font-medium text-n-slate-12 mb-2">
{{ $t('MFA_VERIFICATION.ENTER_OTP_CODE') }}
</label>
<div class="flex justify-between gap-2">
<input
v-for="(_, i) in otpDigits"
:key="i"
ref="otpInputRefs"
v-model="otpDigits[i]"
type="text"
maxlength="1"
pattern="[0-9]"
inputmode="numeric"
class="w-12 h-12 text-center text-lg font-semibold border-2 border-n-weak hover:border-n-strong rounded-lg focus:border-n-brand bg-n-alpha-black2 text-n-slate-12 placeholder:text-n-slate-10"
@input="handleOtpInput(i)"
@keydown.left.prevent="focusInput(i - 1)"
@keydown.right.prevent="focusInput(i + 1)"
@keydown.backspace="handleBackspace($event, i)"
@paste="handleOtpCodePaste"
/>
</div>
</div>
<!-- Backup Code Input -->
<div v-if="verificationMethod === BACKUP">
<FormInput
v-model="backupCode"
name="backup_code"
type="text"
data-testid="backup_code_input"
:tabindex="1"
required
:label="$t('MFA_VERIFICATION.ENTER_BACKUP_CODE')"
:placeholder="
$t('MFA_VERIFICATION.BACKUP_CODE_PLACEHOLDER') || '000000'
"
@keyup.enter="handleVerification"
/>
</div>
<!-- Error Message -->
<div
v-if="errorMessage"
class="p-3 bg-n-ruby-3 outline outline-n-ruby-5 outline-1 rounded-lg"
>
<p class="text-sm text-n-ruby-9">{{ errorMessage }}</p>
</div>
<!-- Submit Button -->
<NextButton
lg
type="submit"
data-testid="submit_button"
class="w-full"
:tabindex="2"
:label="$t('MFA_VERIFICATION.VERIFY_BUTTON')"
:disabled="!canSubmit || isVerifying"
:is-loading="isVerifying"
/>
<!-- Alternative Actions -->
<div class="text-center flex items-center flex-col gap-2 pt-4">
<NextButton
sm
link
type="button"
class="w-full hover:!no-underline"
:tabindex="2"
:label="$t('MFA_VERIFICATION.TRY_ANOTHER_METHOD')"
@click="handleTryAnotherMethod"
/>
<NextButton
sm
slate
link
type="button"
class="w-full hover:!no-underline"
:tabindex="3"
:label="$t('MFA_VERIFICATION.CANCEL_LOGIN')"
@click="() => emit('cancel')"
/>
</div>
</form>
</div>
<!-- Help Text -->
<div class="mt-6 text-center">
<p class="text-sm text-n-slate-11">
{{ $t('MFA_VERIFICATION.HELP_TEXT') }}
</p>
<NextButton
sm
link
type="button"
class="w-full hover:!no-underline"
:tabindex="4"
:label="$t('MFA_VERIFICATION.LEARN_MORE')"
@click="helpModalRef?.open()"
/>
</div>
<!-- Help Modal -->
<Dialog
ref="helpModalRef"
:title="$t('MFA_VERIFICATION.HELP_MODAL.TITLE')"
:show-confirm-button="false"
class="[&>dialog>div]:bg-n-alpha-3 [&>dialog>div]:rounded-lg"
@confirm="helpModalRef?.close()"
>
<div class="space-y-4 text-sm text-n-slate-11">
<div v-for="section in ['AUTHENTICATOR', 'BACKUP']" :key="section">
<h4 class="font-medium text-n-slate-12 mb-2">
{{ $t(`MFA_VERIFICATION.HELP_MODAL.${section}_TITLE`) }}
</h4>
<p>{{ $t(`MFA_VERIFICATION.HELP_MODAL.${section}_DESC`) }}</p>
</div>
<div>
<h4 class="font-medium text-n-slate-12 mb-2">
{{ $t('MFA_VERIFICATION.HELP_MODAL.CONTACT_TITLE') }}
</h4>
<p>{{ $t(`MFA_VERIFICATION.HELP_MODAL.${contactDescKey}`) }}</p>
</div>
</div>
</Dialog>
</div>
</template>
@@ -4,7 +4,6 @@ import { ref, provide } from 'vue';
import { useConfig } from 'dashboard/composables/useConfig';
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
import { useAI } from 'dashboard/composables/useAI';
import { useSnakeCase } from 'dashboard/composables/useTransformKeys';
// components
import ReplyBox from './ReplyBox.vue';
@@ -438,11 +437,6 @@ export default {
makeMessagesRead() {
this.$store.dispatch('markMessagesRead', { id: this.currentChat.id });
},
async handleMessageRetry(message) {
if (!message) return;
const payload = useSnakeCase(message);
await this.$store.dispatch('sendMessageWithData', payload);
},
},
};
</script>
@@ -471,7 +465,6 @@ export default {
:is-an-email-channel="isAnEmailChannel"
:inbox-supports-reply-to="inboxSupportsReplyTo"
:messages="getMessages"
@retry="handleMessageRetry"
>
<template #beforeAll>
<transition name="slide-up">
@@ -104,7 +104,6 @@ export default function useAutomationValues() {
contacts: contacts.value,
customAttributes: getters['attributes/getAttributes'].value,
inboxes: inboxes.value,
labels: labels.value,
statusFilterOptions: statusFilterOptions.value,
priorityOptions: priorityOptions.value,
messageTypeOptions: messageTypeOptions.value,
@@ -35,8 +35,6 @@ export default {
HELP_CENTER_DOCS_URL:
'https://www.chatwoot.com/docs/product/others/help-center',
TESTIMONIAL_URL: 'https://testimonials.cdn.chatwoot.com/content.json',
WHATSAPP_EMBEDDED_SIGNUP_DOCS_URL:
'https://developers.facebook.com/docs/whatsapp/embedded-signup/custom-flows/onboarding-business-app-users#limitations',
SMALL_SCREEN_BREAKPOINT: 768,
AVAILABILITY_STATUS_KEYS: ['online', 'busy', 'offline'],
SNOOZE_OPTIONS: {
+1 -1
View File
@@ -38,8 +38,8 @@ export const FEATURE_FLAGS = {
REPORT_V4: 'report_v4',
CHANNEL_INSTAGRAM: 'channel_instagram',
CONTACT_CHATWOOT_SUPPORT_TEAM: 'contact_chatwoot_support_team',
WHATSAPP_EMBEDDED_SIGNUP: 'whatsapp_embedded_signup',
CAPTAIN_V2: 'captain_integration_v2',
SAML: 'saml',
};
export const PREMIUM_FEATURES = [
@@ -124,7 +124,6 @@ export const getConditionOptions = ({
customAttributes,
inboxes,
languages,
labels,
statusFilterOptions,
teams,
type,
@@ -151,7 +150,6 @@ export const getConditionOptions = ({
country_code: countries,
message_type: messageTypeOptions,
priority: priorityOptions,
labels: generateConditionOptions(labels, 'title'),
};
return conditionFilterMaps[type];
@@ -96,18 +96,3 @@ export const sanitizeVariableSearchKey = (searchKey = '') => {
.replace(/,/g, '') // remove commas
.trim();
};
/**
* Convert underscore-separated string to title case.
* Eg. "round_robin" => "Round Robin"
* @param {string} str
* @returns {string}
*/
export const formatToTitleCase = str => {
return (
str
?.replace(/_/g, ' ')
.replace(/\b\w/g, l => l.toUpperCase())
.trim() || ''
);
};
@@ -19,7 +19,6 @@ const FEATURE_HELP_URLS = {
team_management: 'https://chwt.app/hc/teams',
webhook: 'https://chwt.app/hc/webhooks',
billing: 'https://chwt.app/pricing',
saml: 'https://chwt.app/hc/saml',
};
export function getHelpUrlForFeature(featureName) {
@@ -5,7 +5,6 @@ import {
convertToCategorySlug,
convertToPortalSlug,
sanitizeVariableSearchKey,
formatToTitleCase,
} from '../commons';
describe('#getTypingUsersText', () => {
@@ -143,51 +142,3 @@ describe('sanitizeVariableSearchKey', () => {
expect(sanitizeVariableSearchKey()).toBe('');
});
});
describe('formatToTitleCase', () => {
it('converts underscore-separated string to title case', () => {
expect(formatToTitleCase('round_robin')).toBe('Round Robin');
});
it('converts single word to title case', () => {
expect(formatToTitleCase('priority')).toBe('Priority');
});
it('converts multiple underscores to title case', () => {
expect(formatToTitleCase('auto_assignment_policy')).toBe(
'Auto Assignment Policy'
);
});
it('handles already capitalized words', () => {
expect(formatToTitleCase('HIGH_PRIORITY')).toBe('HIGH PRIORITY');
});
it('handles mixed case with underscores', () => {
expect(formatToTitleCase('first_Name_last')).toBe('First Name Last');
});
it('handles empty string', () => {
expect(formatToTitleCase('')).toBe('');
});
it('handles null input', () => {
expect(formatToTitleCase(null)).toBe('');
});
it('handles undefined input', () => {
expect(formatToTitleCase(undefined)).toBe('');
});
it('handles string without underscores', () => {
expect(formatToTitleCase('hello')).toBe('Hello');
});
it('handles string with numbers', () => {
expect(formatToTitleCase('priority_1_high')).toBe('Priority 1 High');
});
it('handles leading and trailing underscores', () => {
expect(formatToTitleCase('_leading_trailing_')).toBe('Leading Trailing');
});
});
@@ -218,7 +218,6 @@ describe('templateHelper', () => {
expect(result.header).toEqual({
media_url: '',
media_type: 'document',
media_name: '',
});
expect(result.body).toEqual({
1: '',
@@ -51,11 +51,6 @@ export const buildTemplateParameters = (template, hasMediaHeaderValue) => {
if (!allVariables.header) allVariables.header = {};
allVariables.header.media_url = '';
allVariables.header.media_type = headerComponent.format.toLowerCase();
// For document templates, include media_name field for filename support
if (headerComponent.format.toLowerCase() === 'document') {
allVariables.header.media_name = '';
}
}
// Process button variables
@@ -71,17 +71,6 @@
"SHOW_LABELS": "Show labels",
"HIDE_LABELS": "Hide labels"
},
"VOICE_CALL": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NO_ANSWER": "No answer",
"MISSED_CALL": "Missed call",
"CALL_ENDED": "Call ended",
"NOT_ANSWERED_YET": "Not answered yet",
"THEY_ANSWERED": "They answered",
"YOU_ANSWERED": "You answered"
},
"HEADER": {
"RESOLVE_ACTION": "Resolve",
"REOPEN_ACTION": "Reopen",
@@ -51,13 +51,6 @@
"LARGER": "Larger",
"EXTRA_LARGE": "Extra Large"
}
},
"LANGUAGE": {
"TITLE": "Preferred Language",
"NOTE": "Choose the language you want to use.",
"UPDATE_SUCCESS": "Your Language settings have been updated successfully",
"UPDATE_ERROR": "There is an error while updating the language settings, please try again",
"USE_ACCOUNT_DEFAULT": "Use account default"
}
},
"MESSAGE_SIGNATURE_SECTION": {
@@ -337,7 +330,6 @@
"REPORTS_LABEL": "Labels",
"REPORTS_INBOX": "Inbox",
"REPORTS_TEAM": "Team",
"AGENT_ASSIGNMENT": "Agent Assignment",
"SET_AVAILABILITY_TITLE": "Set yourself as",
"SET_YOUR_AVAILABILITY": "Set your availability",
"SLA": "SLA",
@@ -426,255 +418,5 @@
"SWITCH_TO_REPLY": "Switch to Reply",
"TOGGLE_SNOOZE_DROPDOWN": "Toggle snooze dropdown"
}
},
"ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent assignment",
"DESCRIPTION": "Define policies to effectively manage workload and route conversations based on the needs of inboxes and agents. Learn more here"
},
"ASSIGNMENT_POLICY": {
"TITLE": "Assignment policy",
"DESCRIPTION": "Manage how conversations get assigned in inboxes.",
"FEATURES": [
"Assign by conversations evenly or by available capacity",
"Add fair distribution rules to avoid overloading any agent",
"Add inboxes to a policy - one policy per inbox"
]
},
"AGENT_CAPACITY_POLICY": {
"TITLE": "Agent capacity policy",
"DESCRIPTION": "Manage workload for agents.",
"FEATURES": [
"Define maximum conversations per inbox",
"Create exceptions based on labels and time",
"Add agents to a policy - one policy per agent"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Assignment policy",
"CREATE_POLICY": "New policy"
},
"CARD": {
"ORDER": "Order",
"PRIORITY": "Priority",
"ACTIVE": "Active",
"INACTIVE": "Inactive",
"POPOVER": "Added inboxes",
"EDIT": "Edit"
},
"NO_RECORDS_FOUND": "No assignment policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create assignment policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
"ERROR_MESSAGE": "Failed to create assignment policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit assignment policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Add inbox",
"DESCRIPTION": "{inboxName} inbox is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Cancel"
},
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Inbox added to policy successfully",
"ERROR_MESSAGE": "Failed to add inbox to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Inbox removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove inbox from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Description:",
"PLACEHOLDER": "Enter description"
},
"STATUS": {
"LABEL": "Status:",
"PLACEHOLDER": "Select status",
"ACTIVE": "Policy is active",
"INACTIVE": "Policy is inactive"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Assignment order",
"ROUND_ROBIN": {
"LABEL": "Round robin",
"DESCRIPTION": "Assign conversations evenly among agents."
},
"BALANCED": {
"LABEL": "Balanced",
"DESCRIPTION": "Assign conversations based on available capacity."
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Assignment priority",
"EARLIEST_CREATED": {
"LABEL": "Earliest created",
"DESCRIPTION": "The conversation that was created first gets assigned first."
},
"LONGEST_WAITING": {
"LABEL": "Longest waiting",
"DESCRIPTION": "The conversation waiting the longest gets assigned first."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Fair distribution policy",
"DESCRIPTION": "Set the maximum number of conversations that can be assigned per agent within a time window to avoid overloading any one agent. This required field defaults to 100 conversations per hour.",
"INPUT_MAX": "Assign max",
"DURATION": "Conversations per agent in every"
},
"INBOXES": {
"LABEL": "Added inboxes",
"DESCRIPTION": "Add inboxes for which this policy will be applicable.",
"ADD_BUTTON": "Add inbox",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select inboxes to add",
"ADD_BUTTON": "Add"
},
"EMPTY_STATE": "No inboxes added to this policy, add an inbox to get started",
"API": {
"SUCCESS_MESSAGE": "Inbox successfully added to policy",
"ERROR_MESSAGE": "Failed to add inbox to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Assignment policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete assignment policy"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent capacity",
"CREATE_POLICY": "New policy"
},
"CARD": {
"POPOVER": "Added agents",
"EDIT": "Edit"
},
"NO_RECORDS_FOUND": "No agent capacity policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create agent capacity policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy created successfully",
"ERROR_MESSAGE": "Failed to create agent capacity policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit agent capacity policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Add agent",
"DESCRIPTION": "{agentName} is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Cancel"
},
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy updated successfully",
"ERROR_MESSAGE": "Failed to update agent capacity policy"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agent added to policy successfully",
"ERROR_MESSAGE": "Failed to add agent to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Description:",
"PLACEHOLDER": "Enter description"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Inbox capacity limits",
"ADD_BUTTON": "Add inbox",
"FIELD": {
"SELECT_INBOX": "Select inbox",
"MAX_CONVERSATIONS": "Max conversations",
"SET_LIMIT": "Set limit"
},
"EMPTY_STATE": "No inbox limit set"
},
"EXCLUSION_RULES": {
"LABEL": "Exclusion rules",
"DESCRIPTION": "Conversations that satisfy the following conditions would not count towards agent capacity",
"TAGS": {
"LABEL": "Exclude conversations tagged with specific labels",
"ADD_TAG": "add tag",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select tags to add"
},
"EMPTY_STATE": "No tags added to this policy."
},
"DURATION": {
"LABEL": "Exclude conversations older than a specified duration",
"PLACEHOLDER": "Set time"
}
},
"USERS": {
"LABEL": "Assigned agents",
"DESCRIPTION": "Add agents for which this policy will be applicable.",
"ADD_BUTTON": "Add agent",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select agents to add",
"ADD_BUTTON": "Add"
},
"EMPTY_STATE": "No agents added",
"API": {
"SUCCESS_MESSAGE": "Agent successfully added to policy",
"ERROR_MESSAGE": "Failed to add agent to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Agent capacity policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete agent capacity policy"
}
},
"DELETE_POLICY": {
"TITLE": "Delete policy",
"DESCRIPTION": "Are you sure you want to delete this policy? This action cannot be undone.",
"CONFIRM_BUTTON_LABEL": "Delete",
"CANCEL_BUTTON_LABEL": "Cancel"
}
}
}
@@ -71,17 +71,6 @@
"SHOW_LABELS": "إظهار السمات",
"HIDE_LABELS": "إخفاء السمات"
},
"VOICE_CALL": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NO_ANSWER": "No answer",
"MISSED_CALL": "Missed call",
"CALL_ENDED": "Call ended",
"NOT_ANSWERED_YET": "Not answered yet",
"THEY_ANSWERED": "They answered",
"YOU_ANSWERED": "You answered"
},
"HEADER": {
"RESOLVE_ACTION": "حل المحادثة",
"REOPEN_ACTION": "إعادة فتح",
@@ -51,13 +51,6 @@
"LARGER": "Larger",
"EXTRA_LARGE": "Extra Large"
}
},
"LANGUAGE": {
"TITLE": "Preferred Language",
"NOTE": "Choose the language you want to use.",
"UPDATE_SUCCESS": "Your Language settings have been updated successfully",
"UPDATE_ERROR": "There is an error while updating the language settings, please try again",
"USE_ACCOUNT_DEFAULT": "Use account default"
}
},
"MESSAGE_SIGNATURE_SECTION": {
@@ -337,7 +330,6 @@
"REPORTS_LABEL": "الوسوم",
"REPORTS_INBOX": "صندوق الوارد",
"REPORTS_TEAM": "الفريق",
"AGENT_ASSIGNMENT": "Agent Assignment",
"SET_AVAILABILITY_TITLE": "تعيين نفسك كـ",
"SET_YOUR_AVAILABILITY": "قم بتعيين توافرك",
"SLA": "SLA",
@@ -426,255 +418,5 @@
"SWITCH_TO_REPLY": "التبديل إلى الرد",
"TOGGLE_SNOOZE_DROPDOWN": "تبديل القائمة المنسدلة"
}
},
"ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent assignment",
"DESCRIPTION": "Define policies to effectively manage workload and route conversations based on the needs of inboxes and agents. Learn more here"
},
"ASSIGNMENT_POLICY": {
"TITLE": "Assignment policy",
"DESCRIPTION": "Manage how conversations get assigned in inboxes.",
"FEATURES": [
"Assign by conversations evenly or by available capacity",
"Add fair distribution rules to avoid overloading any agent",
"Add inboxes to a policy - one policy per inbox"
]
},
"AGENT_CAPACITY_POLICY": {
"TITLE": "Agent capacity policy",
"DESCRIPTION": "Manage workload for agents.",
"FEATURES": [
"Define maximum conversations per inbox",
"Create exceptions based on labels and time",
"Add agents to a policy - one policy per agent"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Assignment policy",
"CREATE_POLICY": "New policy"
},
"CARD": {
"ORDER": "Order",
"PRIORITY": "الأولوية",
"ACTIVE": "مفعل",
"INACTIVE": "Inactive",
"POPOVER": "Added inboxes",
"EDIT": "تعديل"
},
"NO_RECORDS_FOUND": "No assignment policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create assignment policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
"ERROR_MESSAGE": "Failed to create assignment policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit assignment policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Add inbox",
"DESCRIPTION": "{inboxName} inbox is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "إلغاء"
},
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Inbox added to policy successfully",
"ERROR_MESSAGE": "Failed to add inbox to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Inbox removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove inbox from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "الوصف:",
"PLACEHOLDER": "أدخل الوصف"
},
"STATUS": {
"LABEL": "الحالة:",
"PLACEHOLDER": "اختر الحالة",
"ACTIVE": "Policy is active",
"INACTIVE": "Policy is inactive"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Assignment order",
"ROUND_ROBIN": {
"LABEL": "Round robin",
"DESCRIPTION": "Assign conversations evenly among agents."
},
"BALANCED": {
"LABEL": "Balanced",
"DESCRIPTION": "Assign conversations based on available capacity."
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Assignment priority",
"EARLIEST_CREATED": {
"LABEL": "Earliest created",
"DESCRIPTION": "The conversation that was created first gets assigned first."
},
"LONGEST_WAITING": {
"LABEL": "Longest waiting",
"DESCRIPTION": "The conversation waiting the longest gets assigned first."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Fair distribution policy",
"DESCRIPTION": "Set the maximum number of conversations that can be assigned per agent within a time window to avoid overloading any one agent. This required field defaults to 100 conversations per hour.",
"INPUT_MAX": "Assign max",
"DURATION": "Conversations per agent in every"
},
"INBOXES": {
"LABEL": "Added inboxes",
"DESCRIPTION": "Add inboxes for which this policy will be applicable.",
"ADD_BUTTON": "Add inbox",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select inboxes to add",
"ADD_BUTTON": "إضافة"
},
"EMPTY_STATE": "No inboxes added to this policy, add an inbox to get started",
"API": {
"SUCCESS_MESSAGE": "Inbox successfully added to policy",
"ERROR_MESSAGE": "Failed to add inbox to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Assignment policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete assignment policy"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent capacity",
"CREATE_POLICY": "New policy"
},
"CARD": {
"POPOVER": "Added agents",
"EDIT": "تعديل"
},
"NO_RECORDS_FOUND": "No agent capacity policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create agent capacity policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy created successfully",
"ERROR_MESSAGE": "Failed to create agent capacity policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit agent capacity policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Add agent",
"DESCRIPTION": "{agentName} is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "إلغاء"
},
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy updated successfully",
"ERROR_MESSAGE": "Failed to update agent capacity policy"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agent added to policy successfully",
"ERROR_MESSAGE": "Failed to add agent to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "الوصف:",
"PLACEHOLDER": "أدخل الوصف"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Inbox capacity limits",
"ADD_BUTTON": "Add inbox",
"FIELD": {
"SELECT_INBOX": "Select inbox",
"MAX_CONVERSATIONS": "Max conversations",
"SET_LIMIT": "Set limit"
},
"EMPTY_STATE": "No inbox limit set"
},
"EXCLUSION_RULES": {
"LABEL": "Exclusion rules",
"DESCRIPTION": "Conversations that satisfy the following conditions would not count towards agent capacity",
"TAGS": {
"LABEL": "Exclude conversations tagged with specific labels",
"ADD_TAG": "add tag",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select tags to add"
},
"EMPTY_STATE": "No tags added to this policy."
},
"DURATION": {
"LABEL": "Exclude conversations older than a specified duration",
"PLACEHOLDER": "Set time"
}
},
"USERS": {
"LABEL": "Assigned agents",
"DESCRIPTION": "Add agents for which this policy will be applicable.",
"ADD_BUTTON": "Add agent",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select agents to add",
"ADD_BUTTON": "إضافة"
},
"EMPTY_STATE": "No agents added",
"API": {
"SUCCESS_MESSAGE": "Agent successfully added to policy",
"ERROR_MESSAGE": "Failed to add agent to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Agent capacity policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete agent capacity policy"
}
},
"DELETE_POLICY": {
"TITLE": "Delete policy",
"DESCRIPTION": "Are you sure you want to delete this policy? This action cannot be undone.",
"CONFIRM_BUTTON_LABEL": "حذف",
"CANCEL_BUTTON_LABEL": "إلغاء"
}
}
}
@@ -71,17 +71,6 @@
"SHOW_LABELS": "Show labels",
"HIDE_LABELS": "Hide labels"
},
"VOICE_CALL": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NO_ANSWER": "No answer",
"MISSED_CALL": "Missed call",
"CALL_ENDED": "Call ended",
"NOT_ANSWERED_YET": "Not answered yet",
"THEY_ANSWERED": "They answered",
"YOU_ANSWERED": "You answered"
},
"HEADER": {
"RESOLVE_ACTION": "Resolve",
"REOPEN_ACTION": "Reopen",
@@ -51,13 +51,6 @@
"LARGER": "Larger",
"EXTRA_LARGE": "Extra Large"
}
},
"LANGUAGE": {
"TITLE": "Preferred Language",
"NOTE": "Choose the language you want to use.",
"UPDATE_SUCCESS": "Your Language settings have been updated successfully",
"UPDATE_ERROR": "There is an error while updating the language settings, please try again",
"USE_ACCOUNT_DEFAULT": "Use account default"
}
},
"MESSAGE_SIGNATURE_SECTION": {
@@ -337,7 +330,6 @@
"REPORTS_LABEL": "Labels",
"REPORTS_INBOX": "Inbox",
"REPORTS_TEAM": "Team",
"AGENT_ASSIGNMENT": "Agent Assignment",
"SET_AVAILABILITY_TITLE": "Set yourself as",
"SET_YOUR_AVAILABILITY": "Set your availability",
"SLA": "SLA",
@@ -426,255 +418,5 @@
"SWITCH_TO_REPLY": "Switch to Reply",
"TOGGLE_SNOOZE_DROPDOWN": "Toggle snooze dropdown"
}
},
"ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent assignment",
"DESCRIPTION": "Define policies to effectively manage workload and route conversations based on the needs of inboxes and agents. Learn more here"
},
"ASSIGNMENT_POLICY": {
"TITLE": "Assignment policy",
"DESCRIPTION": "Manage how conversations get assigned in inboxes.",
"FEATURES": [
"Assign by conversations evenly or by available capacity",
"Add fair distribution rules to avoid overloading any agent",
"Add inboxes to a policy - one policy per inbox"
]
},
"AGENT_CAPACITY_POLICY": {
"TITLE": "Agent capacity policy",
"DESCRIPTION": "Manage workload for agents.",
"FEATURES": [
"Define maximum conversations per inbox",
"Create exceptions based on labels and time",
"Add agents to a policy - one policy per agent"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Assignment policy",
"CREATE_POLICY": "New policy"
},
"CARD": {
"ORDER": "Order",
"PRIORITY": "Priority",
"ACTIVE": "Active",
"INACTIVE": "Inactive",
"POPOVER": "Added inboxes",
"EDIT": "Edit"
},
"NO_RECORDS_FOUND": "No assignment policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create assignment policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
"ERROR_MESSAGE": "Failed to create assignment policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit assignment policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Add inbox",
"DESCRIPTION": "{inboxName} inbox is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Cancel"
},
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Inbox added to policy successfully",
"ERROR_MESSAGE": "Failed to add inbox to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Inbox removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove inbox from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Description:",
"PLACEHOLDER": "Enter description"
},
"STATUS": {
"LABEL": "Status:",
"PLACEHOLDER": "Select status",
"ACTIVE": "Policy is active",
"INACTIVE": "Policy is inactive"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Assignment order",
"ROUND_ROBIN": {
"LABEL": "Round robin",
"DESCRIPTION": "Assign conversations evenly among agents."
},
"BALANCED": {
"LABEL": "Balanced",
"DESCRIPTION": "Assign conversations based on available capacity."
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Assignment priority",
"EARLIEST_CREATED": {
"LABEL": "Earliest created",
"DESCRIPTION": "The conversation that was created first gets assigned first."
},
"LONGEST_WAITING": {
"LABEL": "Longest waiting",
"DESCRIPTION": "The conversation waiting the longest gets assigned first."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Fair distribution policy",
"DESCRIPTION": "Set the maximum number of conversations that can be assigned per agent within a time window to avoid overloading any one agent. This required field defaults to 100 conversations per hour.",
"INPUT_MAX": "Assign max",
"DURATION": "Conversations per agent in every"
},
"INBOXES": {
"LABEL": "Added inboxes",
"DESCRIPTION": "Add inboxes for which this policy will be applicable.",
"ADD_BUTTON": "Add inbox",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select inboxes to add",
"ADD_BUTTON": "Add"
},
"EMPTY_STATE": "No inboxes added to this policy, add an inbox to get started",
"API": {
"SUCCESS_MESSAGE": "Inbox successfully added to policy",
"ERROR_MESSAGE": "Failed to add inbox to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Assignment policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete assignment policy"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent capacity",
"CREATE_POLICY": "New policy"
},
"CARD": {
"POPOVER": "Added agents",
"EDIT": "Edit"
},
"NO_RECORDS_FOUND": "No agent capacity policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create agent capacity policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy created successfully",
"ERROR_MESSAGE": "Failed to create agent capacity policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit agent capacity policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Add agent",
"DESCRIPTION": "{agentName} is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Cancel"
},
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy updated successfully",
"ERROR_MESSAGE": "Failed to update agent capacity policy"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agent added to policy successfully",
"ERROR_MESSAGE": "Failed to add agent to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Description:",
"PLACEHOLDER": "Enter description"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Inbox capacity limits",
"ADD_BUTTON": "Add inbox",
"FIELD": {
"SELECT_INBOX": "Select inbox",
"MAX_CONVERSATIONS": "Max conversations",
"SET_LIMIT": "Set limit"
},
"EMPTY_STATE": "No inbox limit set"
},
"EXCLUSION_RULES": {
"LABEL": "Exclusion rules",
"DESCRIPTION": "Conversations that satisfy the following conditions would not count towards agent capacity",
"TAGS": {
"LABEL": "Exclude conversations tagged with specific labels",
"ADD_TAG": "add tag",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select tags to add"
},
"EMPTY_STATE": "No tags added to this policy."
},
"DURATION": {
"LABEL": "Exclude conversations older than a specified duration",
"PLACEHOLDER": "Set time"
}
},
"USERS": {
"LABEL": "Assigned agents",
"DESCRIPTION": "Add agents for which this policy will be applicable.",
"ADD_BUTTON": "Add agent",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select agents to add",
"ADD_BUTTON": "Add"
},
"EMPTY_STATE": "No agents added",
"API": {
"SUCCESS_MESSAGE": "Agent successfully added to policy",
"ERROR_MESSAGE": "Failed to add agent to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Agent capacity policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete agent capacity policy"
}
},
"DELETE_POLICY": {
"TITLE": "Delete policy",
"DESCRIPTION": "Are you sure you want to delete this policy? This action cannot be undone.",
"CONFIRM_BUTTON_LABEL": "Delete",
"CANCEL_BUTTON_LABEL": "Cancel"
}
}
}
@@ -71,17 +71,6 @@
"SHOW_LABELS": "Show labels",
"HIDE_LABELS": "Hide labels"
},
"VOICE_CALL": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NO_ANSWER": "No answer",
"MISSED_CALL": "Missed call",
"CALL_ENDED": "Call ended",
"NOT_ANSWERED_YET": "Not answered yet",
"THEY_ANSWERED": "They answered",
"YOU_ANSWERED": "You answered"
},
"HEADER": {
"RESOLVE_ACTION": "Resolve",
"REOPEN_ACTION": "Reopen",
@@ -51,13 +51,6 @@
"LARGER": "Larger",
"EXTRA_LARGE": "Extra Large"
}
},
"LANGUAGE": {
"TITLE": "Preferred Language",
"NOTE": "Choose the language you want to use.",
"UPDATE_SUCCESS": "Your Language settings have been updated successfully",
"UPDATE_ERROR": "There is an error while updating the language settings, please try again",
"USE_ACCOUNT_DEFAULT": "Use account default"
}
},
"MESSAGE_SIGNATURE_SECTION": {
@@ -337,7 +330,6 @@
"REPORTS_LABEL": "Labels",
"REPORTS_INBOX": "Входяща кутия",
"REPORTS_TEAM": "Team",
"AGENT_ASSIGNMENT": "Agent Assignment",
"SET_AVAILABILITY_TITLE": "Set yourself as",
"SET_YOUR_AVAILABILITY": "Set your availability",
"SLA": "SLA",
@@ -426,255 +418,5 @@
"SWITCH_TO_REPLY": "Switch to Reply",
"TOGGLE_SNOOZE_DROPDOWN": "Toggle snooze dropdown"
}
},
"ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent assignment",
"DESCRIPTION": "Define policies to effectively manage workload and route conversations based on the needs of inboxes and agents. Learn more here"
},
"ASSIGNMENT_POLICY": {
"TITLE": "Assignment policy",
"DESCRIPTION": "Manage how conversations get assigned in inboxes.",
"FEATURES": [
"Assign by conversations evenly or by available capacity",
"Add fair distribution rules to avoid overloading any agent",
"Add inboxes to a policy - one policy per inbox"
]
},
"AGENT_CAPACITY_POLICY": {
"TITLE": "Agent capacity policy",
"DESCRIPTION": "Manage workload for agents.",
"FEATURES": [
"Define maximum conversations per inbox",
"Create exceptions based on labels and time",
"Add agents to a policy - one policy per agent"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Assignment policy",
"CREATE_POLICY": "New policy"
},
"CARD": {
"ORDER": "Order",
"PRIORITY": "Priority",
"ACTIVE": "Активен",
"INACTIVE": "Inactive",
"POPOVER": "Added inboxes",
"EDIT": "Редактирай"
},
"NO_RECORDS_FOUND": "No assignment policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create assignment policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
"ERROR_MESSAGE": "Failed to create assignment policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit assignment policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Add inbox",
"DESCRIPTION": "{inboxName} inbox is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Отмени"
},
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Inbox added to policy successfully",
"ERROR_MESSAGE": "Failed to add inbox to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Inbox removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove inbox from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Описание:",
"PLACEHOLDER": "Enter description"
},
"STATUS": {
"LABEL": "Статус:",
"PLACEHOLDER": "Select status",
"ACTIVE": "Policy is active",
"INACTIVE": "Policy is inactive"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Assignment order",
"ROUND_ROBIN": {
"LABEL": "Round robin",
"DESCRIPTION": "Assign conversations evenly among agents."
},
"BALANCED": {
"LABEL": "Balanced",
"DESCRIPTION": "Assign conversations based on available capacity."
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Assignment priority",
"EARLIEST_CREATED": {
"LABEL": "Earliest created",
"DESCRIPTION": "The conversation that was created first gets assigned first."
},
"LONGEST_WAITING": {
"LABEL": "Longest waiting",
"DESCRIPTION": "The conversation waiting the longest gets assigned first."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Fair distribution policy",
"DESCRIPTION": "Set the maximum number of conversations that can be assigned per agent within a time window to avoid overloading any one agent. This required field defaults to 100 conversations per hour.",
"INPUT_MAX": "Assign max",
"DURATION": "Conversations per agent in every"
},
"INBOXES": {
"LABEL": "Added inboxes",
"DESCRIPTION": "Add inboxes for which this policy will be applicable.",
"ADD_BUTTON": "Add inbox",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select inboxes to add",
"ADD_BUTTON": "Добавяне"
},
"EMPTY_STATE": "No inboxes added to this policy, add an inbox to get started",
"API": {
"SUCCESS_MESSAGE": "Inbox successfully added to policy",
"ERROR_MESSAGE": "Failed to add inbox to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Assignment policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete assignment policy"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent capacity",
"CREATE_POLICY": "New policy"
},
"CARD": {
"POPOVER": "Added agents",
"EDIT": "Редактирай"
},
"NO_RECORDS_FOUND": "No agent capacity policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create agent capacity policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy created successfully",
"ERROR_MESSAGE": "Failed to create agent capacity policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit agent capacity policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Add agent",
"DESCRIPTION": "{agentName} is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Отмени"
},
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy updated successfully",
"ERROR_MESSAGE": "Failed to update agent capacity policy"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agent added to policy successfully",
"ERROR_MESSAGE": "Failed to add agent to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Описание:",
"PLACEHOLDER": "Enter description"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Inbox capacity limits",
"ADD_BUTTON": "Add inbox",
"FIELD": {
"SELECT_INBOX": "Select inbox",
"MAX_CONVERSATIONS": "Max conversations",
"SET_LIMIT": "Set limit"
},
"EMPTY_STATE": "No inbox limit set"
},
"EXCLUSION_RULES": {
"LABEL": "Exclusion rules",
"DESCRIPTION": "Conversations that satisfy the following conditions would not count towards agent capacity",
"TAGS": {
"LABEL": "Exclude conversations tagged with specific labels",
"ADD_TAG": "add tag",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select tags to add"
},
"EMPTY_STATE": "No tags added to this policy."
},
"DURATION": {
"LABEL": "Exclude conversations older than a specified duration",
"PLACEHOLDER": "Set time"
}
},
"USERS": {
"LABEL": "Assigned agents",
"DESCRIPTION": "Add agents for which this policy will be applicable.",
"ADD_BUTTON": "Add agent",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select agents to add",
"ADD_BUTTON": "Добавяне"
},
"EMPTY_STATE": "No agents added",
"API": {
"SUCCESS_MESSAGE": "Agent successfully added to policy",
"ERROR_MESSAGE": "Failed to add agent to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Agent capacity policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete agent capacity policy"
}
},
"DELETE_POLICY": {
"TITLE": "Delete policy",
"DESCRIPTION": "Are you sure you want to delete this policy? This action cannot be undone.",
"CONFIRM_BUTTON_LABEL": "Изтрий",
"CANCEL_BUTTON_LABEL": "Отмени"
}
}
}
@@ -71,17 +71,6 @@
"SHOW_LABELS": "Mostra etiquetes",
"HIDE_LABELS": "Amaga etiquetes"
},
"VOICE_CALL": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NO_ANSWER": "No answer",
"MISSED_CALL": "Missed call",
"CALL_ENDED": "Call ended",
"NOT_ANSWERED_YET": "Not answered yet",
"THEY_ANSWERED": "They answered",
"YOU_ANSWERED": "You answered"
},
"HEADER": {
"RESOLVE_ACTION": "Resoldre",
"REOPEN_ACTION": "Tornar a obrir",
@@ -51,13 +51,6 @@
"LARGER": "Larger",
"EXTRA_LARGE": "Extra Large"
}
},
"LANGUAGE": {
"TITLE": "Preferred Language",
"NOTE": "Choose the language you want to use.",
"UPDATE_SUCCESS": "Your Language settings have been updated successfully",
"UPDATE_ERROR": "There is an error while updating the language settings, please try again",
"USE_ACCOUNT_DEFAULT": "Use account default"
}
},
"MESSAGE_SIGNATURE_SECTION": {
@@ -337,7 +330,6 @@
"REPORTS_LABEL": "Etiquetes",
"REPORTS_INBOX": "Safata d'entrada",
"REPORTS_TEAM": "Equip",
"AGENT_ASSIGNMENT": "Agent Assignment",
"SET_AVAILABILITY_TITLE": "Posa't com",
"SET_YOUR_AVAILABILITY": "Estableix la vostra disponibilitat",
"SLA": "SLA",
@@ -426,255 +418,5 @@
"SWITCH_TO_REPLY": "Canvia a Respon",
"TOGGLE_SNOOZE_DROPDOWN": "Commuta el menú desplegable de posposar"
}
},
"ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent assignment",
"DESCRIPTION": "Define policies to effectively manage workload and route conversations based on the needs of inboxes and agents. Learn more here"
},
"ASSIGNMENT_POLICY": {
"TITLE": "Assignment policy",
"DESCRIPTION": "Manage how conversations get assigned in inboxes.",
"FEATURES": [
"Assign by conversations evenly or by available capacity",
"Add fair distribution rules to avoid overloading any agent",
"Add inboxes to a policy - one policy per inbox"
]
},
"AGENT_CAPACITY_POLICY": {
"TITLE": "Agent capacity policy",
"DESCRIPTION": "Manage workload for agents.",
"FEATURES": [
"Define maximum conversations per inbox",
"Create exceptions based on labels and time",
"Add agents to a policy - one policy per agent"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Assignment policy",
"CREATE_POLICY": "New policy"
},
"CARD": {
"ORDER": "Order",
"PRIORITY": "Prioritat",
"ACTIVE": "Actiu",
"INACTIVE": "Inactive",
"POPOVER": "Added inboxes",
"EDIT": "Edita"
},
"NO_RECORDS_FOUND": "No assignment policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create assignment policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
"ERROR_MESSAGE": "Failed to create assignment policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit assignment policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Add inbox",
"DESCRIPTION": "{inboxName} inbox is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Cancel·la"
},
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Inbox added to policy successfully",
"ERROR_MESSAGE": "Failed to add inbox to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Inbox removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove inbox from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Descripció:",
"PLACEHOLDER": "Introdueix la descripció"
},
"STATUS": {
"LABEL": "Estat:",
"PLACEHOLDER": "Selecciona l'estat",
"ACTIVE": "Policy is active",
"INACTIVE": "Policy is inactive"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Assignment order",
"ROUND_ROBIN": {
"LABEL": "Round robin",
"DESCRIPTION": "Assign conversations evenly among agents."
},
"BALANCED": {
"LABEL": "Balanced",
"DESCRIPTION": "Assign conversations based on available capacity."
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Assignment priority",
"EARLIEST_CREATED": {
"LABEL": "Earliest created",
"DESCRIPTION": "The conversation that was created first gets assigned first."
},
"LONGEST_WAITING": {
"LABEL": "Longest waiting",
"DESCRIPTION": "The conversation waiting the longest gets assigned first."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Fair distribution policy",
"DESCRIPTION": "Set the maximum number of conversations that can be assigned per agent within a time window to avoid overloading any one agent. This required field defaults to 100 conversations per hour.",
"INPUT_MAX": "Assign max",
"DURATION": "Conversations per agent in every"
},
"INBOXES": {
"LABEL": "Added inboxes",
"DESCRIPTION": "Add inboxes for which this policy will be applicable.",
"ADD_BUTTON": "Add inbox",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select inboxes to add",
"ADD_BUTTON": "Afegir"
},
"EMPTY_STATE": "No inboxes added to this policy, add an inbox to get started",
"API": {
"SUCCESS_MESSAGE": "Inbox successfully added to policy",
"ERROR_MESSAGE": "Failed to add inbox to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Assignment policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete assignment policy"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent capacity",
"CREATE_POLICY": "New policy"
},
"CARD": {
"POPOVER": "Added agents",
"EDIT": "Edita"
},
"NO_RECORDS_FOUND": "No agent capacity policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create agent capacity policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy created successfully",
"ERROR_MESSAGE": "Failed to create agent capacity policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit agent capacity policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Add agent",
"DESCRIPTION": "{agentName} is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Cancel·la"
},
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy updated successfully",
"ERROR_MESSAGE": "Failed to update agent capacity policy"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agent added to policy successfully",
"ERROR_MESSAGE": "Failed to add agent to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Descripció:",
"PLACEHOLDER": "Introdueix la descripció"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Inbox capacity limits",
"ADD_BUTTON": "Add inbox",
"FIELD": {
"SELECT_INBOX": "Select inbox",
"MAX_CONVERSATIONS": "Max conversations",
"SET_LIMIT": "Set limit"
},
"EMPTY_STATE": "No inbox limit set"
},
"EXCLUSION_RULES": {
"LABEL": "Exclusion rules",
"DESCRIPTION": "Conversations that satisfy the following conditions would not count towards agent capacity",
"TAGS": {
"LABEL": "Exclude conversations tagged with specific labels",
"ADD_TAG": "add tag",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select tags to add"
},
"EMPTY_STATE": "No tags added to this policy."
},
"DURATION": {
"LABEL": "Exclude conversations older than a specified duration",
"PLACEHOLDER": "Set time"
}
},
"USERS": {
"LABEL": "Assigned agents",
"DESCRIPTION": "Add agents for which this policy will be applicable.",
"ADD_BUTTON": "Add agent",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select agents to add",
"ADD_BUTTON": "Afegir"
},
"EMPTY_STATE": "No agents added",
"API": {
"SUCCESS_MESSAGE": "Agent successfully added to policy",
"ERROR_MESSAGE": "Failed to add agent to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Agent capacity policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete agent capacity policy"
}
},
"DELETE_POLICY": {
"TITLE": "Delete policy",
"DESCRIPTION": "Are you sure you want to delete this policy? This action cannot be undone.",
"CONFIRM_BUTTON_LABEL": "Esborrar",
"CANCEL_BUTTON_LABEL": "Cancel·la"
}
}
}
@@ -71,17 +71,6 @@
"SHOW_LABELS": "Show labels",
"HIDE_LABELS": "Hide labels"
},
"VOICE_CALL": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NO_ANSWER": "No answer",
"MISSED_CALL": "Missed call",
"CALL_ENDED": "Call ended",
"NOT_ANSWERED_YET": "Not answered yet",
"THEY_ANSWERED": "They answered",
"YOU_ANSWERED": "You answered"
},
"HEADER": {
"RESOLVE_ACTION": "Vyřešit",
"REOPEN_ACTION": "Znovu otevřít",
@@ -51,13 +51,6 @@
"LARGER": "Larger",
"EXTRA_LARGE": "Extra Large"
}
},
"LANGUAGE": {
"TITLE": "Preferred Language",
"NOTE": "Choose the language you want to use.",
"UPDATE_SUCCESS": "Your Language settings have been updated successfully",
"UPDATE_ERROR": "There is an error while updating the language settings, please try again",
"USE_ACCOUNT_DEFAULT": "Use account default"
}
},
"MESSAGE_SIGNATURE_SECTION": {
@@ -337,7 +330,6 @@
"REPORTS_LABEL": "Štítky",
"REPORTS_INBOX": "Inbox",
"REPORTS_TEAM": "Team",
"AGENT_ASSIGNMENT": "Agent Assignment",
"SET_AVAILABILITY_TITLE": "Set yourself as",
"SET_YOUR_AVAILABILITY": "Nastavte svou dostupnost",
"SLA": "SLA",
@@ -426,255 +418,5 @@
"SWITCH_TO_REPLY": "Switch to Reply",
"TOGGLE_SNOOZE_DROPDOWN": "Toggle snooze dropdown"
}
},
"ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent assignment",
"DESCRIPTION": "Define policies to effectively manage workload and route conversations based on the needs of inboxes and agents. Learn more here"
},
"ASSIGNMENT_POLICY": {
"TITLE": "Assignment policy",
"DESCRIPTION": "Manage how conversations get assigned in inboxes.",
"FEATURES": [
"Assign by conversations evenly or by available capacity",
"Add fair distribution rules to avoid overloading any agent",
"Add inboxes to a policy - one policy per inbox"
]
},
"AGENT_CAPACITY_POLICY": {
"TITLE": "Agent capacity policy",
"DESCRIPTION": "Manage workload for agents.",
"FEATURES": [
"Define maximum conversations per inbox",
"Create exceptions based on labels and time",
"Add agents to a policy - one policy per agent"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Assignment policy",
"CREATE_POLICY": "New policy"
},
"CARD": {
"ORDER": "Order",
"PRIORITY": "Priority",
"ACTIVE": "Active",
"INACTIVE": "Inactive",
"POPOVER": "Added inboxes",
"EDIT": "Upravit"
},
"NO_RECORDS_FOUND": "No assignment policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create assignment policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
"ERROR_MESSAGE": "Failed to create assignment policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit assignment policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Add inbox",
"DESCRIPTION": "{inboxName} inbox is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Zrušit"
},
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Inbox added to policy successfully",
"ERROR_MESSAGE": "Failed to add inbox to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Inbox removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove inbox from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Description:",
"PLACEHOLDER": "Enter description"
},
"STATUS": {
"LABEL": "Stav:",
"PLACEHOLDER": "Select status",
"ACTIVE": "Policy is active",
"INACTIVE": "Policy is inactive"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Assignment order",
"ROUND_ROBIN": {
"LABEL": "Round robin",
"DESCRIPTION": "Assign conversations evenly among agents."
},
"BALANCED": {
"LABEL": "Balanced",
"DESCRIPTION": "Assign conversations based on available capacity."
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Assignment priority",
"EARLIEST_CREATED": {
"LABEL": "Earliest created",
"DESCRIPTION": "The conversation that was created first gets assigned first."
},
"LONGEST_WAITING": {
"LABEL": "Longest waiting",
"DESCRIPTION": "The conversation waiting the longest gets assigned first."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Fair distribution policy",
"DESCRIPTION": "Set the maximum number of conversations that can be assigned per agent within a time window to avoid overloading any one agent. This required field defaults to 100 conversations per hour.",
"INPUT_MAX": "Assign max",
"DURATION": "Conversations per agent in every"
},
"INBOXES": {
"LABEL": "Added inboxes",
"DESCRIPTION": "Add inboxes for which this policy will be applicable.",
"ADD_BUTTON": "Add inbox",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select inboxes to add",
"ADD_BUTTON": "Přidat"
},
"EMPTY_STATE": "No inboxes added to this policy, add an inbox to get started",
"API": {
"SUCCESS_MESSAGE": "Inbox successfully added to policy",
"ERROR_MESSAGE": "Failed to add inbox to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Assignment policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete assignment policy"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent capacity",
"CREATE_POLICY": "New policy"
},
"CARD": {
"POPOVER": "Added agents",
"EDIT": "Upravit"
},
"NO_RECORDS_FOUND": "No agent capacity policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create agent capacity policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy created successfully",
"ERROR_MESSAGE": "Failed to create agent capacity policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit agent capacity policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Add agent",
"DESCRIPTION": "{agentName} is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Zrušit"
},
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy updated successfully",
"ERROR_MESSAGE": "Failed to update agent capacity policy"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agent added to policy successfully",
"ERROR_MESSAGE": "Failed to add agent to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Description:",
"PLACEHOLDER": "Enter description"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Inbox capacity limits",
"ADD_BUTTON": "Add inbox",
"FIELD": {
"SELECT_INBOX": "Select inbox",
"MAX_CONVERSATIONS": "Max conversations",
"SET_LIMIT": "Set limit"
},
"EMPTY_STATE": "No inbox limit set"
},
"EXCLUSION_RULES": {
"LABEL": "Exclusion rules",
"DESCRIPTION": "Conversations that satisfy the following conditions would not count towards agent capacity",
"TAGS": {
"LABEL": "Exclude conversations tagged with specific labels",
"ADD_TAG": "add tag",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select tags to add"
},
"EMPTY_STATE": "No tags added to this policy."
},
"DURATION": {
"LABEL": "Exclude conversations older than a specified duration",
"PLACEHOLDER": "Set time"
}
},
"USERS": {
"LABEL": "Assigned agents",
"DESCRIPTION": "Add agents for which this policy will be applicable.",
"ADD_BUTTON": "Add agent",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select agents to add",
"ADD_BUTTON": "Přidat"
},
"EMPTY_STATE": "No agents added",
"API": {
"SUCCESS_MESSAGE": "Agent successfully added to policy",
"ERROR_MESSAGE": "Failed to add agent to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Agent capacity policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete agent capacity policy"
}
},
"DELETE_POLICY": {
"TITLE": "Delete policy",
"DESCRIPTION": "Are you sure you want to delete this policy? This action cannot be undone.",
"CONFIRM_BUTTON_LABEL": "Vymazat",
"CANCEL_BUTTON_LABEL": "Zrušit"
}
}
}
@@ -71,17 +71,6 @@
"SHOW_LABELS": "Vis etiketter",
"HIDE_LABELS": "Skjul etiketter"
},
"VOICE_CALL": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NO_ANSWER": "No answer",
"MISSED_CALL": "Missed call",
"CALL_ENDED": "Call ended",
"NOT_ANSWERED_YET": "Not answered yet",
"THEY_ANSWERED": "They answered",
"YOU_ANSWERED": "You answered"
},
"HEADER": {
"RESOLVE_ACTION": "Løs",
"REOPEN_ACTION": "Genåben",
@@ -51,13 +51,6 @@
"LARGER": "Larger",
"EXTRA_LARGE": "Extra Large"
}
},
"LANGUAGE": {
"TITLE": "Preferred Language",
"NOTE": "Choose the language you want to use.",
"UPDATE_SUCCESS": "Your Language settings have been updated successfully",
"UPDATE_ERROR": "There is an error while updating the language settings, please try again",
"USE_ACCOUNT_DEFAULT": "Use account default"
}
},
"MESSAGE_SIGNATURE_SECTION": {
@@ -337,7 +330,6 @@
"REPORTS_LABEL": "Etiketter",
"REPORTS_INBOX": "Indbakke",
"REPORTS_TEAM": "Team",
"AGENT_ASSIGNMENT": "Agent Assignment",
"SET_AVAILABILITY_TITLE": "Sæt dig selv som",
"SET_YOUR_AVAILABILITY": "Indstil din tilgængelighed",
"SLA": "SLA",
@@ -426,255 +418,5 @@
"SWITCH_TO_REPLY": "Skift til svar",
"TOGGLE_SNOOZE_DROPDOWN": "Skift snooze dropdown"
}
},
"ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent assignment",
"DESCRIPTION": "Define policies to effectively manage workload and route conversations based on the needs of inboxes and agents. Learn more here"
},
"ASSIGNMENT_POLICY": {
"TITLE": "Assignment policy",
"DESCRIPTION": "Manage how conversations get assigned in inboxes.",
"FEATURES": [
"Assign by conversations evenly or by available capacity",
"Add fair distribution rules to avoid overloading any agent",
"Add inboxes to a policy - one policy per inbox"
]
},
"AGENT_CAPACITY_POLICY": {
"TITLE": "Agent capacity policy",
"DESCRIPTION": "Manage workload for agents.",
"FEATURES": [
"Define maximum conversations per inbox",
"Create exceptions based on labels and time",
"Add agents to a policy - one policy per agent"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Assignment policy",
"CREATE_POLICY": "New policy"
},
"CARD": {
"ORDER": "Order",
"PRIORITY": "Priority",
"ACTIVE": "Aktiv",
"INACTIVE": "Inactive",
"POPOVER": "Added inboxes",
"EDIT": "Rediger"
},
"NO_RECORDS_FOUND": "No assignment policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create assignment policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
"ERROR_MESSAGE": "Failed to create assignment policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit assignment policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Add inbox",
"DESCRIPTION": "{inboxName} inbox is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Annuller"
},
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Inbox added to policy successfully",
"ERROR_MESSAGE": "Failed to add inbox to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Inbox removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove inbox from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Beskrivelse:",
"PLACEHOLDER": "Enter description"
},
"STATUS": {
"LABEL": "Status:",
"PLACEHOLDER": "Select status",
"ACTIVE": "Policy is active",
"INACTIVE": "Policy is inactive"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Assignment order",
"ROUND_ROBIN": {
"LABEL": "Round robin",
"DESCRIPTION": "Assign conversations evenly among agents."
},
"BALANCED": {
"LABEL": "Balanced",
"DESCRIPTION": "Assign conversations based on available capacity."
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Assignment priority",
"EARLIEST_CREATED": {
"LABEL": "Earliest created",
"DESCRIPTION": "The conversation that was created first gets assigned first."
},
"LONGEST_WAITING": {
"LABEL": "Longest waiting",
"DESCRIPTION": "The conversation waiting the longest gets assigned first."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Fair distribution policy",
"DESCRIPTION": "Set the maximum number of conversations that can be assigned per agent within a time window to avoid overloading any one agent. This required field defaults to 100 conversations per hour.",
"INPUT_MAX": "Assign max",
"DURATION": "Conversations per agent in every"
},
"INBOXES": {
"LABEL": "Added inboxes",
"DESCRIPTION": "Add inboxes for which this policy will be applicable.",
"ADD_BUTTON": "Add inbox",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select inboxes to add",
"ADD_BUTTON": "Tilføj"
},
"EMPTY_STATE": "No inboxes added to this policy, add an inbox to get started",
"API": {
"SUCCESS_MESSAGE": "Inbox successfully added to policy",
"ERROR_MESSAGE": "Failed to add inbox to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Assignment policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete assignment policy"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent capacity",
"CREATE_POLICY": "New policy"
},
"CARD": {
"POPOVER": "Added agents",
"EDIT": "Rediger"
},
"NO_RECORDS_FOUND": "No agent capacity policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create agent capacity policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy created successfully",
"ERROR_MESSAGE": "Failed to create agent capacity policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit agent capacity policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Add agent",
"DESCRIPTION": "{agentName} is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Annuller"
},
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy updated successfully",
"ERROR_MESSAGE": "Failed to update agent capacity policy"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agent added to policy successfully",
"ERROR_MESSAGE": "Failed to add agent to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Beskrivelse:",
"PLACEHOLDER": "Enter description"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Inbox capacity limits",
"ADD_BUTTON": "Add inbox",
"FIELD": {
"SELECT_INBOX": "Select inbox",
"MAX_CONVERSATIONS": "Max conversations",
"SET_LIMIT": "Set limit"
},
"EMPTY_STATE": "No inbox limit set"
},
"EXCLUSION_RULES": {
"LABEL": "Exclusion rules",
"DESCRIPTION": "Conversations that satisfy the following conditions would not count towards agent capacity",
"TAGS": {
"LABEL": "Exclude conversations tagged with specific labels",
"ADD_TAG": "add tag",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select tags to add"
},
"EMPTY_STATE": "No tags added to this policy."
},
"DURATION": {
"LABEL": "Exclude conversations older than a specified duration",
"PLACEHOLDER": "Set time"
}
},
"USERS": {
"LABEL": "Assigned agents",
"DESCRIPTION": "Add agents for which this policy will be applicable.",
"ADD_BUTTON": "Add agent",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select agents to add",
"ADD_BUTTON": "Tilføj"
},
"EMPTY_STATE": "No agents added",
"API": {
"SUCCESS_MESSAGE": "Agent successfully added to policy",
"ERROR_MESSAGE": "Failed to add agent to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Agent capacity policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete agent capacity policy"
}
},
"DELETE_POLICY": {
"TITLE": "Delete policy",
"DESCRIPTION": "Are you sure you want to delete this policy? This action cannot be undone.",
"CONFIRM_BUTTON_LABEL": "Slet",
"CANCEL_BUTTON_LABEL": "Annuller"
}
}
}
@@ -71,17 +71,6 @@
"SHOW_LABELS": "Labels anzeigen",
"HIDE_LABELS": "Labels ausblenden"
},
"VOICE_CALL": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NO_ANSWER": "No answer",
"MISSED_CALL": "Missed call",
"CALL_ENDED": "Call ended",
"NOT_ANSWERED_YET": "Not answered yet",
"THEY_ANSWERED": "They answered",
"YOU_ANSWERED": "You answered"
},
"HEADER": {
"RESOLVE_ACTION": "Fall schließen",
"REOPEN_ACTION": "Wieder öffnen",
@@ -51,13 +51,6 @@
"LARGER": "Larger",
"EXTRA_LARGE": "Extra Large"
}
},
"LANGUAGE": {
"TITLE": "Preferred Language",
"NOTE": "Choose the language you want to use.",
"UPDATE_SUCCESS": "Your Language settings have been updated successfully",
"UPDATE_ERROR": "There is an error while updating the language settings, please try again",
"USE_ACCOUNT_DEFAULT": "Use account default"
}
},
"MESSAGE_SIGNATURE_SECTION": {
@@ -337,7 +330,6 @@
"REPORTS_LABEL": "Labels",
"REPORTS_INBOX": "Posteingang",
"REPORTS_TEAM": "Team",
"AGENT_ASSIGNMENT": "Agent Assignment",
"SET_AVAILABILITY_TITLE": "Setzen Sie sich als",
"SET_YOUR_AVAILABILITY": "Legen Sie Ihre Verfügbarkeit fest",
"SLA": "SLA",
@@ -426,255 +418,5 @@
"SWITCH_TO_REPLY": "Zur Antwort wechseln",
"TOGGLE_SNOOZE_DROPDOWN": "Schlummer-Dropdown ein-/ausblenden"
}
},
"ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent assignment",
"DESCRIPTION": "Define policies to effectively manage workload and route conversations based on the needs of inboxes and agents. Learn more here"
},
"ASSIGNMENT_POLICY": {
"TITLE": "Assignment policy",
"DESCRIPTION": "Manage how conversations get assigned in inboxes.",
"FEATURES": [
"Assign by conversations evenly or by available capacity",
"Add fair distribution rules to avoid overloading any agent",
"Add inboxes to a policy - one policy per inbox"
]
},
"AGENT_CAPACITY_POLICY": {
"TITLE": "Agent capacity policy",
"DESCRIPTION": "Manage workload for agents.",
"FEATURES": [
"Define maximum conversations per inbox",
"Create exceptions based on labels and time",
"Add agents to a policy - one policy per agent"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Assignment policy",
"CREATE_POLICY": "New policy"
},
"CARD": {
"ORDER": "Order",
"PRIORITY": "Priorität",
"ACTIVE": "Aktiv",
"INACTIVE": "Inactive",
"POPOVER": "Added inboxes",
"EDIT": "Bearbeiten"
},
"NO_RECORDS_FOUND": "No assignment policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create assignment policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
"ERROR_MESSAGE": "Failed to create assignment policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit assignment policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Add inbox",
"DESCRIPTION": "{inboxName} inbox is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Stornieren"
},
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Inbox added to policy successfully",
"ERROR_MESSAGE": "Failed to add inbox to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Inbox removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove inbox from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Beschreibung:",
"PLACEHOLDER": "Beschreibung eingeben"
},
"STATUS": {
"LABEL": "Status:",
"PLACEHOLDER": "Status auswählen",
"ACTIVE": "Policy is active",
"INACTIVE": "Policy is inactive"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Assignment order",
"ROUND_ROBIN": {
"LABEL": "Round robin",
"DESCRIPTION": "Assign conversations evenly among agents."
},
"BALANCED": {
"LABEL": "Balanced",
"DESCRIPTION": "Assign conversations based on available capacity."
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Assignment priority",
"EARLIEST_CREATED": {
"LABEL": "Earliest created",
"DESCRIPTION": "The conversation that was created first gets assigned first."
},
"LONGEST_WAITING": {
"LABEL": "Longest waiting",
"DESCRIPTION": "The conversation waiting the longest gets assigned first."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Fair distribution policy",
"DESCRIPTION": "Set the maximum number of conversations that can be assigned per agent within a time window to avoid overloading any one agent. This required field defaults to 100 conversations per hour.",
"INPUT_MAX": "Assign max",
"DURATION": "Conversations per agent in every"
},
"INBOXES": {
"LABEL": "Added inboxes",
"DESCRIPTION": "Add inboxes for which this policy will be applicable.",
"ADD_BUTTON": "Add inbox",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select inboxes to add",
"ADD_BUTTON": "Hinzufügen"
},
"EMPTY_STATE": "No inboxes added to this policy, add an inbox to get started",
"API": {
"SUCCESS_MESSAGE": "Inbox successfully added to policy",
"ERROR_MESSAGE": "Failed to add inbox to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Assignment policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete assignment policy"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent capacity",
"CREATE_POLICY": "New policy"
},
"CARD": {
"POPOVER": "Added agents",
"EDIT": "Bearbeiten"
},
"NO_RECORDS_FOUND": "No agent capacity policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create agent capacity policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy created successfully",
"ERROR_MESSAGE": "Failed to create agent capacity policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit agent capacity policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Add agent",
"DESCRIPTION": "{agentName} is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Stornieren"
},
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy updated successfully",
"ERROR_MESSAGE": "Failed to update agent capacity policy"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agent added to policy successfully",
"ERROR_MESSAGE": "Failed to add agent to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Beschreibung:",
"PLACEHOLDER": "Beschreibung eingeben"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Inbox capacity limits",
"ADD_BUTTON": "Add inbox",
"FIELD": {
"SELECT_INBOX": "Select inbox",
"MAX_CONVERSATIONS": "Max conversations",
"SET_LIMIT": "Set limit"
},
"EMPTY_STATE": "No inbox limit set"
},
"EXCLUSION_RULES": {
"LABEL": "Exclusion rules",
"DESCRIPTION": "Conversations that satisfy the following conditions would not count towards agent capacity",
"TAGS": {
"LABEL": "Exclude conversations tagged with specific labels",
"ADD_TAG": "add tag",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select tags to add"
},
"EMPTY_STATE": "No tags added to this policy."
},
"DURATION": {
"LABEL": "Exclude conversations older than a specified duration",
"PLACEHOLDER": "Set time"
}
},
"USERS": {
"LABEL": "Assigned agents",
"DESCRIPTION": "Add agents for which this policy will be applicable.",
"ADD_BUTTON": "Add agent",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select agents to add",
"ADD_BUTTON": "Hinzufügen"
},
"EMPTY_STATE": "No agents added",
"API": {
"SUCCESS_MESSAGE": "Agent successfully added to policy",
"ERROR_MESSAGE": "Failed to add agent to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Agent capacity policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete agent capacity policy"
}
},
"DELETE_POLICY": {
"TITLE": "Delete policy",
"DESCRIPTION": "Are you sure you want to delete this policy? This action cannot be undone.",
"CONFIRM_BUTTON_LABEL": "Löschen",
"CANCEL_BUTTON_LABEL": "Stornieren"
}
}
}
@@ -71,17 +71,6 @@
"SHOW_LABELS": "Προβολή ετικετών",
"HIDE_LABELS": "Απόκρυψη ετικετών"
},
"VOICE_CALL": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NO_ANSWER": "No answer",
"MISSED_CALL": "Missed call",
"CALL_ENDED": "Call ended",
"NOT_ANSWERED_YET": "Not answered yet",
"THEY_ANSWERED": "They answered",
"YOU_ANSWERED": "You answered"
},
"HEADER": {
"RESOLVE_ACTION": "Επίλυση",
"REOPEN_ACTION": "Επαναφορά",
@@ -51,13 +51,6 @@
"LARGER": "Larger",
"EXTRA_LARGE": "Extra Large"
}
},
"LANGUAGE": {
"TITLE": "Preferred Language",
"NOTE": "Choose the language you want to use.",
"UPDATE_SUCCESS": "Your Language settings have been updated successfully",
"UPDATE_ERROR": "There is an error while updating the language settings, please try again",
"USE_ACCOUNT_DEFAULT": "Use account default"
}
},
"MESSAGE_SIGNATURE_SECTION": {
@@ -337,7 +330,6 @@
"REPORTS_LABEL": "Ετικέτες",
"REPORTS_INBOX": "Εισερχόμενα",
"REPORTS_TEAM": "Ομάδα",
"AGENT_ASSIGNMENT": "Agent Assignment",
"SET_AVAILABILITY_TITLE": "Ορίστε τον εαυτό σας ως",
"SET_YOUR_AVAILABILITY": "Ορίστε τη διαθεσιμότητά σας",
"SLA": "SLA",
@@ -426,255 +418,5 @@
"SWITCH_TO_REPLY": "Εναλλαγή σε απάντηση",
"TOGGLE_SNOOZE_DROPDOWN": "Εναλλαγή αναβολής dropdown"
}
},
"ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent assignment",
"DESCRIPTION": "Define policies to effectively manage workload and route conversations based on the needs of inboxes and agents. Learn more here"
},
"ASSIGNMENT_POLICY": {
"TITLE": "Assignment policy",
"DESCRIPTION": "Manage how conversations get assigned in inboxes.",
"FEATURES": [
"Assign by conversations evenly or by available capacity",
"Add fair distribution rules to avoid overloading any agent",
"Add inboxes to a policy - one policy per inbox"
]
},
"AGENT_CAPACITY_POLICY": {
"TITLE": "Agent capacity policy",
"DESCRIPTION": "Manage workload for agents.",
"FEATURES": [
"Define maximum conversations per inbox",
"Create exceptions based on labels and time",
"Add agents to a policy - one policy per agent"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Assignment policy",
"CREATE_POLICY": "New policy"
},
"CARD": {
"ORDER": "Order",
"PRIORITY": "Priority",
"ACTIVE": "Ενεργή",
"INACTIVE": "Inactive",
"POPOVER": "Added inboxes",
"EDIT": "Επεξεργασία"
},
"NO_RECORDS_FOUND": "No assignment policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create assignment policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
"ERROR_MESSAGE": "Failed to create assignment policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit assignment policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Add inbox",
"DESCRIPTION": "{inboxName} inbox is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Άκυρο"
},
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Inbox added to policy successfully",
"ERROR_MESSAGE": "Failed to add inbox to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Inbox removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove inbox from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Περιγραφή:",
"PLACEHOLDER": "Enter description"
},
"STATUS": {
"LABEL": "Κατάσταση:",
"PLACEHOLDER": "Select status",
"ACTIVE": "Policy is active",
"INACTIVE": "Policy is inactive"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Assignment order",
"ROUND_ROBIN": {
"LABEL": "Round robin",
"DESCRIPTION": "Assign conversations evenly among agents."
},
"BALANCED": {
"LABEL": "Balanced",
"DESCRIPTION": "Assign conversations based on available capacity."
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Assignment priority",
"EARLIEST_CREATED": {
"LABEL": "Earliest created",
"DESCRIPTION": "The conversation that was created first gets assigned first."
},
"LONGEST_WAITING": {
"LABEL": "Longest waiting",
"DESCRIPTION": "The conversation waiting the longest gets assigned first."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Fair distribution policy",
"DESCRIPTION": "Set the maximum number of conversations that can be assigned per agent within a time window to avoid overloading any one agent. This required field defaults to 100 conversations per hour.",
"INPUT_MAX": "Assign max",
"DURATION": "Conversations per agent in every"
},
"INBOXES": {
"LABEL": "Added inboxes",
"DESCRIPTION": "Add inboxes for which this policy will be applicable.",
"ADD_BUTTON": "Add inbox",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select inboxes to add",
"ADD_BUTTON": "Προσθήκη"
},
"EMPTY_STATE": "No inboxes added to this policy, add an inbox to get started",
"API": {
"SUCCESS_MESSAGE": "Inbox successfully added to policy",
"ERROR_MESSAGE": "Failed to add inbox to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Assignment policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete assignment policy"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent capacity",
"CREATE_POLICY": "New policy"
},
"CARD": {
"POPOVER": "Added agents",
"EDIT": "Επεξεργασία"
},
"NO_RECORDS_FOUND": "No agent capacity policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create agent capacity policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy created successfully",
"ERROR_MESSAGE": "Failed to create agent capacity policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit agent capacity policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Add agent",
"DESCRIPTION": "{agentName} is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Άκυρο"
},
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy updated successfully",
"ERROR_MESSAGE": "Failed to update agent capacity policy"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agent added to policy successfully",
"ERROR_MESSAGE": "Failed to add agent to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Περιγραφή:",
"PLACEHOLDER": "Enter description"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Inbox capacity limits",
"ADD_BUTTON": "Add inbox",
"FIELD": {
"SELECT_INBOX": "Select inbox",
"MAX_CONVERSATIONS": "Max conversations",
"SET_LIMIT": "Set limit"
},
"EMPTY_STATE": "No inbox limit set"
},
"EXCLUSION_RULES": {
"LABEL": "Exclusion rules",
"DESCRIPTION": "Conversations that satisfy the following conditions would not count towards agent capacity",
"TAGS": {
"LABEL": "Exclude conversations tagged with specific labels",
"ADD_TAG": "add tag",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select tags to add"
},
"EMPTY_STATE": "No tags added to this policy."
},
"DURATION": {
"LABEL": "Exclude conversations older than a specified duration",
"PLACEHOLDER": "Set time"
}
},
"USERS": {
"LABEL": "Assigned agents",
"DESCRIPTION": "Add agents for which this policy will be applicable.",
"ADD_BUTTON": "Add agent",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select agents to add",
"ADD_BUTTON": "Προσθήκη"
},
"EMPTY_STATE": "No agents added",
"API": {
"SUCCESS_MESSAGE": "Agent successfully added to policy",
"ERROR_MESSAGE": "Failed to add agent to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Agent capacity policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete agent capacity policy"
}
},
"DELETE_POLICY": {
"TITLE": "Delete policy",
"DESCRIPTION": "Are you sure you want to delete this policy? This action cannot be undone.",
"CONFIRM_BUTTON_LABEL": "Διαγραφή",
"CANCEL_BUTTON_LABEL": "Άκυρο"
}
}
}
@@ -177,8 +177,7 @@
"REFERER_LINK": "Referrer Link",
"ASSIGNEE_NAME": "Assignee",
"TEAM_NAME": "Team",
"PRIORITY": "Priority",
"LABELS": "Labels"
"PRIORITY": "Priority"
}
}
}
@@ -741,8 +741,7 @@
"LIVE_CHAT_WIDGET": {
"LABEL": "Live chat widget",
"PLACEHOLDER": "Select live chat widget",
"HELP_TEXT": "Select a live chat widget that will appear on your help center",
"NONE_OPTION": "No widget"
"HELP_TEXT": "Select a live chat widget that will appear on your help center"
},
"BRAND_COLOR": {
"LABEL": "Brand color"
@@ -272,8 +272,8 @@
},
"SUBMIT_BUTTON": "Create WhatsApp Channel",
"EMBEDDED_SIGNUP": {
"TITLE": "Quick setup with Meta",
"DESC": "Use the WhatsApp Embedded Signup flow to quickly connect new numbers. You will be redirected to Meta to log into your WhatsApp Business account. Having admin access will help make the setup smooth and easy.",
"TITLE": "Quick Setup with Meta",
"DESC": "You will be redirected to Meta to log into your WhatsApp Business account. Having admin access will help make the setup smooth and easy.",
"BENEFITS": {
"TITLE": "Benefits of Embedded Signup:",
"EASY_SETUP": "No manual configuration required",
@@ -281,8 +281,9 @@
"AUTO_CONFIG": "Automatic webhook and phone number configuration"
},
"LEARN_MORE": {
"TEXT": "To learn more about integrated signup, pricing, and limitations, visit {link}.",
"LINK_TEXT": "this link"
"TEXT": "To learn more about integrated signup, pricing, and limitations, visit",
"LINK_TEXT": "this link.",
"LINK_URL": "https://developers.facebook.com/docs/whatsapp/embedded-signup/custom-flows/onboarding-business-app-users#limitations"
},
"SUBMIT_BUTTON": "Connect with WhatsApp Business",
"AUTH_PROCESSING": "Authenticating with Meta",
@@ -295,9 +296,7 @@
"INVALID_BUSINESS_DATA": "Invalid business data received from Facebook. Please try again.",
"SIGNUP_ERROR": "Signup error occurred",
"AUTH_NOT_COMPLETED": "Authentication not completed. Please restart the process.",
"SUCCESS_FALLBACK": "WhatsApp Business Account has been successfully configured",
"MANUAL_FALLBACK": "If your number is already connected to the WhatsApp Business Platform (API), or if youre a tech provider onboarding your own number, please use the {link} flow",
"MANUAL_LINK_TEXT": "manual setup flow"
"SUCCESS_FALLBACK": "WhatsApp Business Account has been successfully configured"
},
"API": {
"ERROR_MESSAGE": "We were not able to save the WhatsApp channel"
@@ -36,7 +36,6 @@ import sla from './sla.json';
import teamsSettings from './teamsSettings.json';
import whatsappTemplates from './whatsappTemplates.json';
import contentTemplates from './contentTemplates.json';
import mfa from './mfa.json';
export default {
...advancedFilters,
@@ -77,5 +76,4 @@ export default {
...teamsSettings,
...whatsappTemplates,
...contentTemplates,
...mfa,
};
@@ -759,7 +759,6 @@
"SELECTED": "{count} selected",
"SELECT_ALL": "Select all ({count})",
"UNSELECT_ALL": "Unselect all ({count})",
"SEARCH_PLACEHOLDER": "Search FAQs...",
"BULK_APPROVE_BUTTON": "Approve",
"BULK_DELETE_BUTTON": "Delete",
"BULK_APPROVE": {
@@ -1,106 +0,0 @@
{
"MFA_SETTINGS": {
"TITLE": "Two-Factor Authentication",
"SUBTITLE": "Secure your account with TOTP-based authentication",
"DESCRIPTION": "Add an extra layer of security to your account using a time-based one-time password (TOTP)",
"STATUS_TITLE": "Authentication Status",
"STATUS_DESCRIPTION": "Manage your two-factor authentication settings and backup recovery codes",
"ENABLED": "Enabled",
"DISABLED": "Disabled",
"STATUS_ENABLED": "Two-factor authentication is active",
"STATUS_ENABLED_DESC": "Your account is protected with an additional layer of security",
"ENABLE_BUTTON": "Enable Two-Factor Authentication",
"ENHANCE_SECURITY": "Enhance Your Account Security",
"ENHANCE_SECURITY_DESC": "Two-factor authentication adds an extra layer of security by requiring a verification code from your authenticator app in addition to your password.",
"SETUP": {
"STEP_NUMBER_1": "1",
"STEP_NUMBER_2": "2",
"STEP1_TITLE": "Scan QR Code with Your Authenticator App",
"STEP1_DESCRIPTION": "Use Google Authenticator, Authy, or any TOTP-compatible app",
"LOADING_QR": "Loading...",
"MANUAL_ENTRY": "Can't scan? Enter code manually",
"SECRET_KEY": "Secret Key",
"COPY": "Copy",
"ENTER_CODE": "Enter the 6-digit code from your authenticator app",
"ENTER_CODE_PLACEHOLDER": "000000",
"VERIFY_BUTTON": "Verify & Continue",
"CANCEL": "Cancel",
"ERROR_STARTING": "MFA not enabled. Please contact administrator.",
"INVALID_CODE": "Invalid verification code",
"SECRET_COPIED": "Secret key copied to clipboard",
"SUCCESS": "Two-factor authentication has been enabled successfully"
},
"BACKUP": {
"TITLE": "Save Your Backup Codes",
"DESCRIPTION": "Keep these codes safe. Each can be used once if you lose access to your authenticator",
"IMPORTANT": "Important:",
"IMPORTANT_NOTE": " Save these codes in a secure location. You won't be able to see them again.",
"DOWNLOAD": "Download",
"COPY_ALL": "Copy All",
"CONFIRM": "I have saved my backup codes in a secure location and understand that I won't be able to see them again",
"COMPLETE_SETUP": "Complete Setup",
"CODES_COPIED": "Backup codes copied to clipboard"
},
"MANAGEMENT": {
"BACKUP_CODES": "Backup Codes",
"BACKUP_CODES_DESC": "Generate new codes if you've lost or used your existing ones",
"REGENERATE": "Regenerate Backup Codes",
"DISABLE_MFA": "Disable 2FA",
"DISABLE_MFA_DESC": "Remove two-factor authentication from your account",
"DISABLE_BUTTON": "Disable Two-Factor Authentication"
},
"DISABLE": {
"TITLE": "Disable Two-Factor Authentication",
"DESCRIPTION": "You'll need to enter your password and a verification code to disable two-factor authentication.",
"PASSWORD": "Password",
"OTP_CODE": "Verification Code",
"OTP_CODE_PLACEHOLDER": "000000",
"CONFIRM": "Disable 2FA",
"CANCEL": "Cancel",
"SUCCESS": "Two-factor authentication has been disabled",
"ERROR": "Failed to disable MFA. Please check your credentials."
},
"REGENERATE": {
"TITLE": "Regenerate Backup Codes",
"DESCRIPTION": "This will invalidate your existing backup codes and generate new ones. Enter your verification code to continue.",
"OTP_CODE": "Verification Code",
"OTP_CODE_PLACEHOLDER": "000000",
"CONFIRM": "Generate New Codes",
"CANCEL": "Cancel",
"NEW_CODES_TITLE": "New Backup Codes Generated",
"NEW_CODES_DESC": "Your old backup codes have been invalidated. Save these new codes in a secure location.",
"CODES_IMPORTANT": "Important:",
"CODES_IMPORTANT_NOTE": " Each code can only be used once. Save them before closing this window.",
"DOWNLOAD_CODES": "Download Codes",
"COPY_ALL_CODES": "Copy All Codes",
"CODES_SAVED": "I've Saved My Codes",
"SUCCESS": "New backup codes have been generated",
"ERROR": "Failed to regenerate backup codes"
}
},
"MFA_VERIFICATION": {
"TITLE": "Two-Factor Authentication",
"DESCRIPTION": "Enter your verification code to continue",
"AUTHENTICATOR_APP": "Authenticator App",
"BACKUP_CODE": "Backup Code",
"ENTER_OTP_CODE": "Enter 6-digit code from your authenticator app",
"ENTER_BACKUP_CODE": "Enter one of your backup codes",
"BACKUP_CODE_PLACEHOLDER": "000000",
"VERIFY_BUTTON": "Verify",
"TRY_ANOTHER_METHOD": "Try another verification method",
"CANCEL_LOGIN": "Cancel and return to login",
"HELP_TEXT": "Having trouble signing in?",
"LEARN_MORE": "Learn more about 2FA",
"HELP_MODAL": {
"TITLE": "Two-Factor Authentication Help",
"AUTHENTICATOR_TITLE": "Using an Authenticator App",
"AUTHENTICATOR_DESC": "Open your authenticator app (Google Authenticator, Authy, etc.) and enter the 6-digit code shown for your account.",
"BACKUP_TITLE": "Using a Backup Code",
"BACKUP_DESC": "If you don't have access to your authenticator app, you can use one of the backup codes you saved when setting up 2FA. Each code can only be used once.",
"CONTACT_TITLE": "Need More Help?",
"CONTACT_DESC_CLOUD": "If you've lost access to both your authenticator app and backup codes, please reach out to Chatwoot support for assistance.",
"CONTACT_DESC_SELF_HOSTED": "If you've lost access to both your authenticator app and backup codes, please contact your administrator for assistance."
},
"VERIFICATION_FAILED": "Verification failed. Please try again."
}
}
@@ -80,11 +80,6 @@
"NOTE": "Updating your password would reset your logins in multiple devices.",
"BTN_TEXT": "Change password"
},
"SECURITY_SECTION": {
"TITLE": "Security",
"NOTE": "Manage additional security features for your account.",
"MFA_BUTTON": "Manage Two-Factor Authentication"
},
"ACCESS_TOKEN": {
"TITLE": "Access Token",
"NOTE": "This token can be used if you are building an API based integration",
@@ -363,8 +358,7 @@
"INFO_TEXT": "Let the system automatically mark you offline when you aren't using the app or dashboard.",
"INFO_SHORT": "Automatically mark offline when you aren't using the app."
},
"DOCS": "Read docs",
"SECURITY": "Security"
"DOCS": "Read docs"
},
"BILLING_SETTINGS": {
"TITLE": "Billing",
@@ -396,77 +390,6 @@
},
"NO_BILLING_USER": "Your billing account is being configured. Please refresh the page and try again."
},
"SECURITY_SETTINGS": {
"TITLE": "Security",
"DESCRIPTION": "Manage your account security settings.",
"LINK_TEXT": "Learn more about SAML SSO",
"SAML": {
"TITLE": "SAML SSO",
"NOTE": "Configure SAML single sign-on for your account. Users will authenticate through your identity provider instead of using email/password.",
"ACS_URL": {
"LABEL": "ACS URL",
"TOOLTIP": "Assertion Consumer Service URL - Configure this URL in your IdP as the destination for SAML responses"
},
"SSO_URL": {
"LABEL": "SSO URL",
"HELP": "The URL where SAML authentication requests will be sent",
"PLACEHOLDER": "https://your-idp.com/saml/sso"
},
"CERTIFICATE": {
"LABEL": "Signing certificate in PEM format",
"HELP": "The public certificate from your identity provider used to verify SAML responses",
"PLACEHOLDER": "-----BEGIN CERTIFICATE-----\nMIIC..."
},
"FINGERPRINT": {
"LABEL": "Fingerprint",
"TOOLTIP": "SHA-1 fingerprint of the certificate - Use this to verify the certificate in your IdP configuration"
},
"COPY_SUCCESS": "Copied to clipboard",
"SP_ENTITY_ID": {
"LABEL": "SP Entity ID",
"HELP": "Unique identifier for this application as a service provider (auto-generated).",
"TOOLTIP": "Unique identifier for Chatwoot as the Service Provider - Configure this in your IdP settings"
},
"IDP_ENTITY_ID": {
"LABEL": "Identity Provider Entity ID",
"HELP": "Unique identifier for your identity provider (usually found in IdP configuration)",
"PLACEHOLDER": "https://your-idp.com/saml"
},
"UPDATE_BUTTON": "Update SAML Settings",
"API": {
"SUCCESS": "SAML settings updated successfully",
"ERROR": "Failed to update SAML settings",
"ERROR_LOADING": "Failed to load SAML settings",
"DISABLED": "SAML settings disabled successfully"
},
"VALIDATION": {
"REQUIRED_FIELDS": "SSO URL, Identity Provider Entity ID, and Certificate are required fields",
"SSO_URL_ERROR": "Please enter a valid SSO URL",
"CERTIFICATE_ERROR": "Certificate is required",
"IDP_ENTITY_ID_ERROR": "Identity Provider Entity ID is required"
},
"ENTERPRISE_PAYWALL": {
"AVAILABLE_ON": "The SAML SSO feature is only available in the Enterprise plans.",
"UPGRADE_PROMPT": "Upgrade to an Enterprise plan to access SAML single sign-on and other advanced security features.",
"ASK_ADMIN": "Please reach out to your administrator for the upgrade."
},
"PAYWALL": {
"TITLE": "Upgrade to enable SAML SSO",
"AVAILABLE_ON": "The SAML SSO feature is only available in the Enterprise plans.",
"UPGRADE_PROMPT": "Upgrade your plan to get access to SAML single sign-on and other advanced features.",
"UPGRADE_NOW": "Upgrade now",
"CANCEL_ANYTIME": "You can change or cancel your plan anytime"
},
"ATTRIBUTE_MAPPING": {
"TITLE": "SAML Attribute Setup",
"DESCRIPTION": "The following attribute mappings must be configured in your identity provider"
},
"INFO_SECTION": {
"TITLE": "Service Provider Information",
"TOOLTIP": "Copy these values and configure them in your Identity Provider to establish the SAML connection"
}
}
},
"CREATE_ACCOUNT": {
"NO_ACCOUNT_WARNING": "Uh oh! We could not find any Chatwoot accounts. Please create a new account to continue.",
"NEW_ACCOUNT": "New Account",
@@ -528,230 +451,6 @@
"Add agents to a policy - one policy per agent"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Assignment policy",
"CREATE_POLICY": "New policy"
},
"CARD": {
"ORDER": "Order",
"PRIORITY": "Priority",
"ACTIVE": "Active",
"INACTIVE": "Inactive",
"POPOVER": "Added inboxes",
"EDIT": "Edit"
},
"NO_RECORDS_FOUND": "No assignment policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create assignment policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
"ERROR_MESSAGE": "Failed to create assignment policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit assignment policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Add inbox",
"DESCRIPTION": "{inboxName} inbox is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Cancel"
},
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Inbox added to policy successfully",
"ERROR_MESSAGE": "Failed to add inbox to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Inbox removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove inbox from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Description:",
"PLACEHOLDER": "Enter description"
},
"STATUS": {
"LABEL": "Status:",
"PLACEHOLDER": "Select status",
"ACTIVE": "Policy is active",
"INACTIVE": "Policy is inactive"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Assignment order",
"ROUND_ROBIN": {
"LABEL": "Round robin",
"DESCRIPTION": "Assign conversations evenly among agents."
},
"BALANCED": {
"LABEL": "Balanced",
"DESCRIPTION": "Assign conversations based on available capacity."
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Assignment priority",
"EARLIEST_CREATED": {
"LABEL": "Earliest created",
"DESCRIPTION": "The conversation that was created first gets assigned first."
},
"LONGEST_WAITING": {
"LABEL": "Longest waiting",
"DESCRIPTION": "The conversation waiting the longest gets assigned first."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Fair distribution policy",
"DESCRIPTION": "Set the maximum number of conversations that can be assigned per agent within a time window to avoid overloading any one agent. This required field defaults to 100 conversations per hour.",
"INPUT_MAX": "Assign max",
"DURATION": "Conversations per agent in every"
},
"INBOXES": {
"LABEL": "Added inboxes",
"DESCRIPTION": "Add inboxes for which this policy will be applicable.",
"ADD_BUTTON": "Add inbox",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select inboxes to add",
"ADD_BUTTON": "Add"
},
"EMPTY_STATE": "No inboxes added to this policy, add an inbox to get started",
"API": {
"SUCCESS_MESSAGE": "Inbox successfully added to policy",
"ERROR_MESSAGE": "Failed to add inbox to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Assignment policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete assignment policy"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent capacity",
"CREATE_POLICY": "New policy"
},
"CARD": {
"POPOVER": "Added agents",
"EDIT": "Edit"
},
"NO_RECORDS_FOUND": "No agent capacity policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create agent capacity policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy created successfully",
"ERROR_MESSAGE": "Failed to create agent capacity policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit agent capacity policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Add agent",
"DESCRIPTION": "{agentName} is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Cancel"
},
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy updated successfully",
"ERROR_MESSAGE": "Failed to update agent capacity policy"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agent added to policy successfully",
"ERROR_MESSAGE": "Failed to add agent to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Description:",
"PLACEHOLDER": "Enter description"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Inbox capacity limits",
"ADD_BUTTON": "Add inbox",
"FIELD": {
"SELECT_INBOX": "Select inbox",
"MAX_CONVERSATIONS": "Max conversations",
"SET_LIMIT": "Set limit"
},
"EMPTY_STATE": "No inbox limit set"
},
"EXCLUSION_RULES": {
"LABEL": "Exclusion rules",
"DESCRIPTION": "Conversations that satisfy the following conditions would not count towards agent capacity",
"TAGS": {
"LABEL": "Exclude conversations tagged with specific labels",
"ADD_TAG": "add tag",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select tags to add"
},
"EMPTY_STATE": "No tags added to this policy."
},
"DURATION": {
"LABEL": "Exclude conversations older than a specified duration",
"PLACEHOLDER": "Set time"
}
},
"USERS": {
"LABEL": "Assigned agents",
"DESCRIPTION": "Add agents for which this policy will be applicable.",
"ADD_BUTTON": "Add agent",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select agents to add",
"ADD_BUTTON": "Add"
},
"EMPTY_STATE": "No agents added",
"API": {
"SUCCESS_MESSAGE": "Agent successfully added to policy",
"ERROR_MESSAGE": "Failed to add agent to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Agent capacity policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete agent capacity policy"
}
},
"DELETE_POLICY": {
"TITLE": "Delete policy",
"DESCRIPTION": "Are you sure you want to delete this policy? This action cannot be undone.",
"CONFIRM_BUTTON_LABEL": "Delete",
"CANCEL_BUTTON_LABEL": "Cancel"
}
}
}
@@ -40,7 +40,6 @@
"BUTTON_LABEL": "Button {index}",
"COUPON_CODE": "Enter coupon code (max 15 chars)",
"MEDIA_URL_LABEL": "Enter {type} URL",
"DOCUMENT_NAME_PLACEHOLDER": "Enter document filename (e.g., Invoice_2025.pdf)",
"BUTTON_PARAMETER": "Enter button parameter"
}
}
@@ -142,7 +142,7 @@
"NEW_CAMPAIGN": "Crear campaña",
"EMPTY_STATE": {
"TITLE": "No WhatsApp campaigns are available",
"SUBTITLE": "Lanza una campaña de WhatsApp para conectar con tus clientes directamente. Envía ofertas o has anuncios fácilmente. Haz clic en 'Crear campaña' para comenzar."
"SUBTITLE": "Launch a WhatsApp campaign to reach your customers directly. Send offers or make announcements with ease. Click 'Create campaign' to get started."
},
"CARD": {
"STATUS": {
@@ -155,7 +155,7 @@
}
},
"CREATE": {
"TITLE": "Crear campaña de WhatsApp",
"TITLE": "Create WhatsApp campaign",
"CANCEL_BUTTON_TEXT": "Cancelar",
"CREATE_BUTTON_TEXT": "Crear",
"FORM": {
@@ -40,7 +40,7 @@
"SEND_MESSAGE_LABEL": "Enviar mensaje",
"FORM_ERROR_MESSAGE": "Por favor, rellene todas las variables antes de enviar",
"MEDIA_HEADER_LABEL": "{type} Header",
"MEDIA_URL_LABEL": "Ingrese la URL completa",
"MEDIA_URL_LABEL": "Enter full media URL",
"MEDIA_URL_PLACEHOLDER": "https://example.com/image.jpg"
},
"FORM": {
@@ -71,17 +71,6 @@
"SHOW_LABELS": "Mostrar etiquetas",
"HIDE_LABELS": "Ocultar etiquetas"
},
"VOICE_CALL": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NO_ANSWER": "No answer",
"MISSED_CALL": "Missed call",
"CALL_ENDED": "Call ended",
"NOT_ANSWERED_YET": "Not answered yet",
"THEY_ANSWERED": "They answered",
"YOU_ANSWERED": "You answered"
},
"HEADER": {
"RESOLVE_ACTION": "Resolver",
"REOPEN_ACTION": "Reabrir",
@@ -51,13 +51,6 @@
"LARGER": "Larger",
"EXTRA_LARGE": "Extra Large"
}
},
"LANGUAGE": {
"TITLE": "Preferred Language",
"NOTE": "Choose the language you want to use.",
"UPDATE_SUCCESS": "Your Language settings have been updated successfully",
"UPDATE_ERROR": "There is an error while updating the language settings, please try again",
"USE_ACCOUNT_DEFAULT": "Use account default"
}
},
"MESSAGE_SIGNATURE_SECTION": {
@@ -233,7 +226,7 @@
"APPEARANCE": "Cambiar apariencia",
"SUPER_ADMIN_CONSOLE": "Consola SuperAdmin",
"DOCS": "Leer la documentación",
"CHANGELOG": "Notas de versión",
"CHANGELOG": "Changelog",
"LOGOUT": "Cerrar sesión"
},
"APP_GLOBAL": {
@@ -337,7 +330,6 @@
"REPORTS_LABEL": "Etiquetas",
"REPORTS_INBOX": "Bandeja de entrada",
"REPORTS_TEAM": "Equipo",
"AGENT_ASSIGNMENT": "Agent Assignment",
"SET_AVAILABILITY_TITLE": "Ponte como",
"SET_YOUR_AVAILABILITY": "Establecer su disponibilidad",
"SLA": "SLA",
@@ -426,255 +418,5 @@
"SWITCH_TO_REPLY": "Cambiar a respuesta",
"TOGGLE_SNOOZE_DROPDOWN": "Cambiar el menú desplegable"
}
},
"ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent assignment",
"DESCRIPTION": "Define policies to effectively manage workload and route conversations based on the needs of inboxes and agents. Learn more here"
},
"ASSIGNMENT_POLICY": {
"TITLE": "Assignment policy",
"DESCRIPTION": "Manage how conversations get assigned in inboxes.",
"FEATURES": [
"Assign by conversations evenly or by available capacity",
"Add fair distribution rules to avoid overloading any agent",
"Add inboxes to a policy - one policy per inbox"
]
},
"AGENT_CAPACITY_POLICY": {
"TITLE": "Agent capacity policy",
"DESCRIPTION": "Manage workload for agents.",
"FEATURES": [
"Define maximum conversations per inbox",
"Create exceptions based on labels and time",
"Add agents to a policy - one policy per agent"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Assignment policy",
"CREATE_POLICY": "New policy"
},
"CARD": {
"ORDER": "Order",
"PRIORITY": "Prioridad",
"ACTIVE": "Activo",
"INACTIVE": "Inactive",
"POPOVER": "Added inboxes",
"EDIT": "Editar"
},
"NO_RECORDS_FOUND": "No assignment policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create assignment policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
"ERROR_MESSAGE": "Failed to create assignment policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit assignment policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Add inbox",
"DESCRIPTION": "{inboxName} inbox is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Cancelar"
},
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Inbox added to policy successfully",
"ERROR_MESSAGE": "Failed to add inbox to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Inbox removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove inbox from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Descripción:",
"PLACEHOLDER": "Introducir descripción"
},
"STATUS": {
"LABEL": "Estado:",
"PLACEHOLDER": "Seleccionar estado",
"ACTIVE": "Policy is active",
"INACTIVE": "Policy is inactive"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Assignment order",
"ROUND_ROBIN": {
"LABEL": "Round robin",
"DESCRIPTION": "Assign conversations evenly among agents."
},
"BALANCED": {
"LABEL": "Balanced",
"DESCRIPTION": "Assign conversations based on available capacity."
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Assignment priority",
"EARLIEST_CREATED": {
"LABEL": "Earliest created",
"DESCRIPTION": "The conversation that was created first gets assigned first."
},
"LONGEST_WAITING": {
"LABEL": "Longest waiting",
"DESCRIPTION": "The conversation waiting the longest gets assigned first."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Fair distribution policy",
"DESCRIPTION": "Set the maximum number of conversations that can be assigned per agent within a time window to avoid overloading any one agent. This required field defaults to 100 conversations per hour.",
"INPUT_MAX": "Assign max",
"DURATION": "Conversations per agent in every"
},
"INBOXES": {
"LABEL": "Added inboxes",
"DESCRIPTION": "Add inboxes for which this policy will be applicable.",
"ADD_BUTTON": "Add inbox",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select inboxes to add",
"ADD_BUTTON": "Añadir"
},
"EMPTY_STATE": "No inboxes added to this policy, add an inbox to get started",
"API": {
"SUCCESS_MESSAGE": "Inbox successfully added to policy",
"ERROR_MESSAGE": "Failed to add inbox to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Assignment policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete assignment policy"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent capacity",
"CREATE_POLICY": "New policy"
},
"CARD": {
"POPOVER": "Added agents",
"EDIT": "Editar"
},
"NO_RECORDS_FOUND": "No agent capacity policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create agent capacity policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy created successfully",
"ERROR_MESSAGE": "Failed to create agent capacity policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit agent capacity policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Add agent",
"DESCRIPTION": "{agentName} is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Cancelar"
},
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy updated successfully",
"ERROR_MESSAGE": "Failed to update agent capacity policy"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agent added to policy successfully",
"ERROR_MESSAGE": "Failed to add agent to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "Descripción:",
"PLACEHOLDER": "Introducir descripción"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Inbox capacity limits",
"ADD_BUTTON": "Add inbox",
"FIELD": {
"SELECT_INBOX": "Select inbox",
"MAX_CONVERSATIONS": "Max conversations",
"SET_LIMIT": "Set limit"
},
"EMPTY_STATE": "No inbox limit set"
},
"EXCLUSION_RULES": {
"LABEL": "Exclusion rules",
"DESCRIPTION": "Conversations that satisfy the following conditions would not count towards agent capacity",
"TAGS": {
"LABEL": "Exclude conversations tagged with specific labels",
"ADD_TAG": "add tag",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select tags to add"
},
"EMPTY_STATE": "No tags added to this policy."
},
"DURATION": {
"LABEL": "Exclude conversations older than a specified duration",
"PLACEHOLDER": "Set time"
}
},
"USERS": {
"LABEL": "Assigned agents",
"DESCRIPTION": "Add agents for which this policy will be applicable.",
"ADD_BUTTON": "Add agent",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select agents to add",
"ADD_BUTTON": "Añadir"
},
"EMPTY_STATE": "No agents added",
"API": {
"SUCCESS_MESSAGE": "Agent successfully added to policy",
"ERROR_MESSAGE": "Failed to add agent to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Agent capacity policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete agent capacity policy"
}
},
"DELETE_POLICY": {
"TITLE": "Delete policy",
"DESCRIPTION": "Are you sure you want to delete this policy? This action cannot be undone.",
"CONFIRM_BUTTON_LABEL": "Eliminar",
"CANCEL_BUTTON_LABEL": "Cancelar"
}
}
}
@@ -17,7 +17,7 @@
"IP_ADDRESS": "آدرس آی‌پی",
"CREATED_AT_LABEL": "ایجاد شده",
"NEW_MESSAGE": "پیام جدید",
"CALL": "تماس",
"CALL": "Call",
"CALL_UNDER_DEVELOPMENT": "Calling is under development",
"VOICE_INBOX_PICKER": {
"TITLE": "Choose a voice inbox"
@@ -71,17 +71,6 @@
"SHOW_LABELS": "مشاهده کردن برچسب‌ها",
"HIDE_LABELS": "پنهان کردن برچسب‌ها"
},
"VOICE_CALL": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NO_ANSWER": "No answer",
"MISSED_CALL": "Missed call",
"CALL_ENDED": "Call ended",
"NOT_ANSWERED_YET": "Not answered yet",
"THEY_ANSWERED": "They answered",
"YOU_ANSWERED": "You answered"
},
"HEADER": {
"RESOLVE_ACTION": "حل شد",
"REOPEN_ACTION": "دوباره باز کنید",
@@ -51,13 +51,6 @@
"LARGER": "Larger",
"EXTRA_LARGE": "Extra Large"
}
},
"LANGUAGE": {
"TITLE": "Preferred Language",
"NOTE": "Choose the language you want to use.",
"UPDATE_SUCCESS": "Your Language settings have been updated successfully",
"UPDATE_ERROR": "There is an error while updating the language settings, please try again",
"USE_ACCOUNT_DEFAULT": "Use account default"
}
},
"MESSAGE_SIGNATURE_SECTION": {
@@ -337,7 +330,6 @@
"REPORTS_LABEL": "برچسب‌ها",
"REPORTS_INBOX": "صندوق ورودی",
"REPORTS_TEAM": "تیم‌",
"AGENT_ASSIGNMENT": "Agent Assignment",
"SET_AVAILABILITY_TITLE": "خود را به عنوان",
"SET_YOUR_AVAILABILITY": "در دسترس بودن خود را تنظیم کنید",
"SLA": "SLA",
@@ -426,255 +418,5 @@
"SWITCH_TO_REPLY": "رفتن به پاسخ",
"TOGGLE_SNOOZE_DROPDOWN": "تغییر حالت بازکردن تعویق"
}
},
"ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent assignment",
"DESCRIPTION": "Define policies to effectively manage workload and route conversations based on the needs of inboxes and agents. Learn more here"
},
"ASSIGNMENT_POLICY": {
"TITLE": "Assignment policy",
"DESCRIPTION": "Manage how conversations get assigned in inboxes.",
"FEATURES": [
"Assign by conversations evenly or by available capacity",
"Add fair distribution rules to avoid overloading any agent",
"Add inboxes to a policy - one policy per inbox"
]
},
"AGENT_CAPACITY_POLICY": {
"TITLE": "Agent capacity policy",
"DESCRIPTION": "Manage workload for agents.",
"FEATURES": [
"Define maximum conversations per inbox",
"Create exceptions based on labels and time",
"Add agents to a policy - one policy per agent"
]
}
},
"AGENT_ASSIGNMENT_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Assignment policy",
"CREATE_POLICY": "New policy"
},
"CARD": {
"ORDER": "Order",
"PRIORITY": "اولویت",
"ACTIVE": "فعال",
"INACTIVE": "Inactive",
"POPOVER": "Added inboxes",
"EDIT": "ویرایش"
},
"NO_RECORDS_FOUND": "No assignment policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create assignment policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
"ERROR_MESSAGE": "Failed to create assignment policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit assignment policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_INBOX_DIALOG": {
"TITLE": "Add inbox",
"DESCRIPTION": "{inboxName} inbox is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "انصراف"
},
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
},
"INBOX_API": {
"ADD": {
"SUCCESS_MESSAGE": "Inbox added to policy successfully",
"ERROR_MESSAGE": "Failed to add inbox to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Inbox removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove inbox from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "توضیحات:",
"PLACEHOLDER": "Enter description"
},
"STATUS": {
"LABEL": "وضعیت:",
"PLACEHOLDER": "Select status",
"ACTIVE": "Policy is active",
"INACTIVE": "Policy is inactive"
},
"ASSIGNMENT_ORDER": {
"LABEL": "Assignment order",
"ROUND_ROBIN": {
"LABEL": "Round robin",
"DESCRIPTION": "Assign conversations evenly among agents."
},
"BALANCED": {
"LABEL": "Balanced",
"DESCRIPTION": "Assign conversations based on available capacity."
}
},
"ASSIGNMENT_PRIORITY": {
"LABEL": "Assignment priority",
"EARLIEST_CREATED": {
"LABEL": "Earliest created",
"DESCRIPTION": "The conversation that was created first gets assigned first."
},
"LONGEST_WAITING": {
"LABEL": "Longest waiting",
"DESCRIPTION": "The conversation waiting the longest gets assigned first."
}
},
"FAIR_DISTRIBUTION": {
"LABEL": "Fair distribution policy",
"DESCRIPTION": "Set the maximum number of conversations that can be assigned per agent within a time window to avoid overloading any one agent. This required field defaults to 100 conversations per hour.",
"INPUT_MAX": "Assign max",
"DURATION": "Conversations per agent in every"
},
"INBOXES": {
"LABEL": "Added inboxes",
"DESCRIPTION": "Add inboxes for which this policy will be applicable.",
"ADD_BUTTON": "Add inbox",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select inboxes to add",
"ADD_BUTTON": "افزودن"
},
"EMPTY_STATE": "No inboxes added to this policy, add an inbox to get started",
"API": {
"SUCCESS_MESSAGE": "Inbox successfully added to policy",
"ERROR_MESSAGE": "Failed to add inbox to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Assignment policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete assignment policy"
}
},
"AGENT_CAPACITY_POLICY": {
"INDEX": {
"HEADER": {
"TITLE": "Agent capacity",
"CREATE_POLICY": "New policy"
},
"CARD": {
"POPOVER": "Added agents",
"EDIT": "ویرایش"
},
"NO_RECORDS_FOUND": "No agent capacity policies found"
},
"CREATE": {
"HEADER": {
"TITLE": "Create agent capacity policy"
},
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy created successfully",
"ERROR_MESSAGE": "Failed to create agent capacity policy"
}
},
"EDIT": {
"HEADER": {
"TITLE": "Edit agent capacity policy"
},
"EDIT_BUTTON": "Update policy",
"CONFIRM_ADD_AGENT_DIALOG": {
"TITLE": "Add agent",
"DESCRIPTION": "{agentName} is already linked to another policy. Are you sure you want to link it to this policy? It will be unlinked from the other policy.",
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "انصراف"
},
"API": {
"SUCCESS_MESSAGE": "Agent capacity policy updated successfully",
"ERROR_MESSAGE": "Failed to update agent capacity policy"
},
"AGENT_API": {
"ADD": {
"SUCCESS_MESSAGE": "Agent added to policy successfully",
"ERROR_MESSAGE": "Failed to add agent to policy"
},
"REMOVE": {
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
}
},
"FORM": {
"NAME": {
"LABEL": "Policy name:",
"PLACEHOLDER": "Enter policy name"
},
"DESCRIPTION": {
"LABEL": "توضیحات:",
"PLACEHOLDER": "Enter description"
},
"INBOX_CAPACITY_LIMIT": {
"LABEL": "Inbox capacity limits",
"ADD_BUTTON": "Add inbox",
"FIELD": {
"SELECT_INBOX": "Select inbox",
"MAX_CONVERSATIONS": "Max conversations",
"SET_LIMIT": "Set limit"
},
"EMPTY_STATE": "No inbox limit set"
},
"EXCLUSION_RULES": {
"LABEL": "Exclusion rules",
"DESCRIPTION": "Conversations that satisfy the following conditions would not count towards agent capacity",
"TAGS": {
"LABEL": "Exclude conversations tagged with specific labels",
"ADD_TAG": "add tag",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select tags to add"
},
"EMPTY_STATE": "No tags added to this policy."
},
"DURATION": {
"LABEL": "Exclude conversations older than a specified duration",
"PLACEHOLDER": "Set time"
}
},
"USERS": {
"LABEL": "Assigned agents",
"DESCRIPTION": "Add agents for which this policy will be applicable.",
"ADD_BUTTON": "Add agent",
"DROPDOWN": {
"SEARCH_PLACEHOLDER": "Search and select agents to add",
"ADD_BUTTON": "افزودن"
},
"EMPTY_STATE": "No agents added",
"API": {
"SUCCESS_MESSAGE": "Agent successfully added to policy",
"ERROR_MESSAGE": "Failed to add agent to policy"
}
}
},
"DELETE_POLICY": {
"SUCCESS_MESSAGE": "Agent capacity policy deleted successfully",
"ERROR_MESSAGE": "Failed to delete agent capacity policy"
}
},
"DELETE_POLICY": {
"TITLE": "Delete policy",
"DESCRIPTION": "Are you sure you want to delete this policy? This action cannot be undone.",
"CONFIRM_BUTTON_LABEL": "حذف",
"CANCEL_BUTTON_LABEL": "انصراف"
}
}
}
@@ -71,17 +71,6 @@
"SHOW_LABELS": "Show labels",
"HIDE_LABELS": "Hide labels"
},
"VOICE_CALL": {
"INCOMING_CALL": "Incoming call",
"OUTGOING_CALL": "Outgoing call",
"CALL_IN_PROGRESS": "Call in progress",
"NO_ANSWER": "No answer",
"MISSED_CALL": "Missed call",
"CALL_ENDED": "Call ended",
"NOT_ANSWERED_YET": "Not answered yet",
"THEY_ANSWERED": "They answered",
"YOU_ANSWERED": "You answered"
},
"HEADER": {
"RESOLVE_ACTION": "Ratkaise",
"REOPEN_ACTION": "Uudelleenavaa",

Some files were not shown because too many files have changed in this diff Show More