Merge branch 'release/2.11.0'
This commit is contained in:
commit
8a0d6f6f50
740 changed files with 17226 additions and 2665 deletions
|
@ -54,3 +54,5 @@ exclude_patterns:
|
||||||
- 'app/javascript/widget/i18n/index.js'
|
- 'app/javascript/widget/i18n/index.js'
|
||||||
- 'app/javascript/survey/i18n/index.js'
|
- 'app/javascript/survey/i18n/index.js'
|
||||||
- 'app/javascript/shared/constants/locales.js'
|
- 'app/javascript/shared/constants/locales.js'
|
||||||
|
- 'app/javascript/dashboard/helper/specs/macrosFixtures.js'
|
||||||
|
- 'app/javascript/dashboard/routes/dashboard/settings/macros/constants.js'
|
||||||
|
|
|
@ -34,6 +34,11 @@ REDIS_SENTINELS=
|
||||||
# You can find list of master using "SENTINEL masters" command
|
# You can find list of master using "SENTINEL masters" command
|
||||||
REDIS_SENTINEL_MASTER_NAME=
|
REDIS_SENTINEL_MASTER_NAME=
|
||||||
|
|
||||||
|
# By default Chatwoot will pass REDIS_PASSWORD as the password value for sentinels
|
||||||
|
# Use the following environment variable to customize passwords for sentinels.
|
||||||
|
# Use empty string if sentinels are configured with out passwords
|
||||||
|
# REDIS_SENTINEL_PASSWORD=
|
||||||
|
|
||||||
# Redis premium breakage in heroku fix
|
# Redis premium breakage in heroku fix
|
||||||
# enable the following configuration
|
# enable the following configuration
|
||||||
# ref: https://github.com/chatwoot/chatwoot/issues/2420
|
# ref: https://github.com/chatwoot/chatwoot/issues/2420
|
||||||
|
@ -51,7 +56,7 @@ RAILS_MAX_THREADS=5
|
||||||
|
|
||||||
# The email from which all outgoing emails are sent
|
# The email from which all outgoing emails are sent
|
||||||
# could user either `email@yourdomain.com` or `BrandName <email@yourdomain.com>`
|
# could user either `email@yourdomain.com` or `BrandName <email@yourdomain.com>`
|
||||||
MAILER_SENDER_EMAIL="Chatwoot <accounts@chatwoot.com>"
|
MAILER_SENDER_EMAIL=Chatwoot <accounts@chatwoot.com>
|
||||||
|
|
||||||
#SMTP domain key is set up for HELO checking
|
#SMTP domain key is set up for HELO checking
|
||||||
SMTP_DOMAIN=chatwoot.com
|
SMTP_DOMAIN=chatwoot.com
|
||||||
|
|
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -6,6 +6,7 @@ labels: 'Bug'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
|
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
@ -16,11 +17,11 @@ Steps to reproduce the behavior:
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. See error
|
4. See the error
|
||||||
|
|
||||||
**Expected behavior**
|
**Expected behavior**
|
||||||
|
|
||||||
A clear and concise description of what you expected to happen.
|
Share a clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
|
|
||||||
|
@ -28,27 +29,50 @@ If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
**Browser logs**
|
**Browser logs**
|
||||||
|
|
||||||
Share the browser logs to debug the issue further
|
Share the browser logs to debug the issue further.
|
||||||
|
|
||||||
**Server logs**
|
**Server logs**
|
||||||
|
|
||||||
Share the server logs to debug the issue further
|
Share the server logs to debug the issue further.
|
||||||
|
|
||||||
**Environment**
|
**Environment**
|
||||||
|
|
||||||
Describe whether you are using Chatwoot Cloud (app.chatwoot.com) or a self hosted installation of Chatwoot. If you are using a self hosted installation of Chatwoot describe the type of deployment (Docker/Linux VM installation/Heroku)
|
Describe whether you are using Chatwoot Cloud (app.chatwoot.com) or a self-hosted installation of Chatwoot. If you are using a self-hosted installation of Chatwoot, describe the type of deployment (Docker/Linux VM installation/Heroku/Kubernetes/Other).
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
- [ ] app.chatwoot.com (Chatwoot Cloud)
|
||||||
- OS: [e.g. iOS]
|
- [ ] Self-hosted
|
||||||
- Browser [e.g. chrome, safari]
|
- - [ ] Linux VM
|
||||||
|
- - [ ] Docker
|
||||||
|
- - [ ] Kubernetes
|
||||||
|
- - [ ] Heroku
|
||||||
|
- - [ ] Other (Please specify)
|
||||||
|
|
||||||
|
|
||||||
|
**Desktop (please complete the following information)** (If applicable)
|
||||||
|
- OS: [e.g. Linux, Windows, MacOS]
|
||||||
|
- Browser [e.g. chrome, firefox, safari]
|
||||||
- Version [e.g. 22]
|
- Version [e.g. 22]
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
**Smartphone (please complete the following information)** (If applicable)
|
||||||
- Device: [e.g. iPhone6]
|
- Device: [e.g. iPhone6, Pixel7]
|
||||||
- OS: [e.g. iOS8.1]
|
- OS: [e.g. iOS8.1]
|
||||||
- Browser [e.g. stock browser, safari]
|
- Browser [e.g. stock browser, firefox, safari]
|
||||||
- Version [e.g. 22]
|
- Version [e.g. 22]
|
||||||
|
|
||||||
|
**Docker** (If applicable)
|
||||||
|
|
||||||
|
Please share the output of the following.
|
||||||
|
- `docker version`
|
||||||
|
- `docker info`
|
||||||
|
- `docker-compose version`
|
||||||
|
|
||||||
|
**Cloud Provider** (If applicable)
|
||||||
|
- [ ] AWS
|
||||||
|
- [ ] GCP
|
||||||
|
- [ ] Azure
|
||||||
|
- [ ] DigitalOcean
|
||||||
|
- [ ] Others
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
|
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
|
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
|
Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires.
|
||||||
|
|
||||||
Fixes # (issue)
|
Fixes # (issue)
|
||||||
|
|
||||||
## Type of change
|
## Type of change
|
||||||
|
@ -12,18 +11,18 @@ Please delete options that are not relevant.
|
||||||
|
|
||||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||||
- [ ] New feature (non-breaking change which adds functionality)
|
- [ ] New feature (non-breaking change which adds functionality)
|
||||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
- [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected)
|
||||||
- [ ] This change requires a documentation update
|
- [ ] This change requires a documentation update
|
||||||
|
|
||||||
## How Has This Been Tested?
|
## How Has This Been Tested?
|
||||||
|
|
||||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration
|
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.
|
||||||
|
|
||||||
|
|
||||||
## Checklist:
|
## Checklist:
|
||||||
|
|
||||||
- [ ] My code follows the style guidelines of this project
|
- [ ] My code follows the style guidelines of this project
|
||||||
- [ ] I have performed a self-review of my own code
|
- [ ] I have performed a self-review of my code
|
||||||
- [ ] I have commented on my code, particularly in hard-to-understand areas
|
- [ ] I have commented on my code, particularly in hard-to-understand areas
|
||||||
- [ ] I have made corresponding changes to the documentation
|
- [ ] I have made corresponding changes to the documentation
|
||||||
- [ ] My changes generate no new warnings
|
- [ ] My changes generate no new warnings
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -60,3 +60,5 @@ test/cypress/videos/*
|
||||||
|
|
||||||
/config/master.key
|
/config/master.key
|
||||||
/config/*.enc
|
/config/*.enc
|
||||||
|
|
||||||
|
.vscode/settings.json
|
||||||
|
|
1
.vscode/settings.json
vendored
Normal file
1
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
10
Gemfile.lock
10
Gemfile.lock
|
@ -427,14 +427,14 @@ GEM
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
newrelic_rpm (8.9.0)
|
newrelic_rpm (8.9.0)
|
||||||
nio4r (2.5.8)
|
nio4r (2.5.8)
|
||||||
nokogiri (1.13.7)
|
nokogiri (1.13.9)
|
||||||
mini_portile2 (~> 2.8.0)
|
mini_portile2 (~> 2.8.0)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.13.7-arm64-darwin)
|
nokogiri (1.13.9-arm64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.13.7-x86_64-darwin)
|
nokogiri (1.13.9-x86_64-darwin)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.13.7-x86_64-linux)
|
nokogiri (1.13.9-x86_64-linux)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oauth (0.5.10)
|
oauth (0.5.10)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
|
@ -808,4 +808,4 @@ RUBY VERSION
|
||||||
ruby 3.0.4p208
|
ruby 3.0.4p208
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.3.18
|
2.3.16
|
||||||
|
|
|
@ -72,6 +72,7 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
|
||||||
|
|
||||||
def build_message
|
def build_message
|
||||||
return if @outgoing_echo && already_sent_from_chatwoot?
|
return if @outgoing_echo && already_sent_from_chatwoot?
|
||||||
|
return if message_content.blank? && all_unsupported_files?
|
||||||
|
|
||||||
@message = conversation.messages.create!(message_params)
|
@message = conversation.messages.create!(message_params)
|
||||||
|
|
||||||
|
@ -117,6 +118,13 @@ class Messages::Instagram::MessageBuilder < Messages::Messenger::MessageBuilder
|
||||||
cw_message.present?
|
cw_message.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def all_unsupported_files?
|
||||||
|
return if attachments.empty?
|
||||||
|
|
||||||
|
attachments_type = attachments.pluck(:type).uniq.first
|
||||||
|
unsupported_file_type?(attachments_type)
|
||||||
|
end
|
||||||
|
|
||||||
### Sample response
|
### Sample response
|
||||||
# {
|
# {
|
||||||
# "object": "instagram",
|
# "object": "instagram",
|
||||||
|
|
|
@ -35,7 +35,13 @@ class Messages::MessageBuilder
|
||||||
file: uploaded_attachment
|
file: uploaded_attachment
|
||||||
)
|
)
|
||||||
|
|
||||||
attachment.file_type = file_type(uploaded_attachment&.content_type) if uploaded_attachment.is_a?(ActionDispatch::Http::UploadedFile)
|
attachment.file_type = if uploaded_attachment.is_a?(String)
|
||||||
|
file_type_by_signed_id(
|
||||||
|
uploaded_attachment
|
||||||
|
)
|
||||||
|
else
|
||||||
|
file_type(uploaded_attachment&.content_type)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,8 @@ class Messages::Messenger::MessageBuilder
|
||||||
include ::FileTypeHelper
|
include ::FileTypeHelper
|
||||||
|
|
||||||
def process_attachment(attachment)
|
def process_attachment(attachment)
|
||||||
return if attachment['type'].to_sym == :template
|
# This check handles very rare case if there are multiple files to attach with only one usupported file
|
||||||
|
return if unsupported_file_type?(attachment['type'])
|
||||||
|
|
||||||
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
||||||
attachment_obj.save!
|
attachment_obj.save!
|
||||||
|
@ -80,4 +81,10 @@ class Messages::Messenger::MessageBuilder
|
||||||
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
|
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
|
||||||
{}
|
{}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def unsupported_file_type?(attachment_type)
|
||||||
|
[:template, :unsupported_type].include? attachment_type.to_sym
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,9 +5,10 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
||||||
before_action :set_current_page, only: [:index]
|
before_action :set_current_page, only: [:index]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@articles_count = @portal.articles.count
|
@portal_articles = @portal.articles
|
||||||
@articles = @portal.articles
|
@all_articles = @portal_articles.search(list_params)
|
||||||
@articles = @articles.search(list_params) if list_params.present?
|
@articles_count = @all_articles.count
|
||||||
|
@articles = @all_articles.page(@current_page)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -37,7 +38,7 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def portal
|
def portal
|
||||||
@portal ||= Current.account.portals.find_by(slug: params[:portal_id])
|
@portal ||= Current.account.portals.find_by!(slug: params[:portal_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def article_params
|
def article_params
|
||||||
|
|
|
@ -5,6 +5,7 @@ class Api::V1::Accounts::CategoriesController < Api::V1::Accounts::BaseControlle
|
||||||
before_action :set_current_page, only: [:index]
|
before_action :set_current_page, only: [:index]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
@current_locale = params[:locale]
|
||||||
@categories = @portal.categories.search(params)
|
@categories = @portal.categories.search(params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||||
before_action :check_authorization
|
|
||||||
before_action :fetch_macro, only: [:show, :update, :destroy, :execute]
|
before_action :fetch_macro, only: [:show, :update, :destroy, :execute]
|
||||||
|
before_action :check_authorization, only: [:show, :update, :destroy, :execute]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@macros = Macro.with_visibility(current_user, params)
|
@macros = Macro.with_visibility(current_user, params)
|
||||||
|
@ -14,6 +14,8 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||||
render json: { error: @macro.errors.messages }, status: :unprocessable_entity and return unless @macro.valid?
|
render json: { error: @macro.errors.messages }, status: :unprocessable_entity and return unless @macro.valid?
|
||||||
|
|
||||||
@macro.save!
|
@macro.save!
|
||||||
|
process_attachments
|
||||||
|
@macro
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@ -25,10 +27,21 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def attach_file
|
||||||
|
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||||
|
key: nil,
|
||||||
|
io: params[:attachment].tempfile,
|
||||||
|
filename: params[:attachment].original_filename,
|
||||||
|
content_type: params[:attachment].content_type
|
||||||
|
)
|
||||||
|
render json: { blob_key: file_blob.key, blob_id: file_blob.id }
|
||||||
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
@macro.update!(macros_with_user)
|
@macro.update!(macros_with_user)
|
||||||
@macro.set_visibility(current_user, permitted_params)
|
@macro.set_visibility(current_user, permitted_params)
|
||||||
|
process_attachments
|
||||||
@macro.save!
|
@macro.save!
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Rails.logger.error e
|
Rails.logger.error e
|
||||||
|
@ -42,6 +55,19 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def process_attachments
|
||||||
|
actions = @macro.actions.filter_map { |k, _v| k if k['action_name'] == 'send_attachment' }
|
||||||
|
return if actions.blank?
|
||||||
|
|
||||||
|
actions.each do |action|
|
||||||
|
blob_id = action['action_params']
|
||||||
|
blob = ActiveStorage::Blob.find_by(id: blob_id)
|
||||||
|
@macro.files.attach(blob)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def permitted_params
|
def permitted_params
|
||||||
params.permit(
|
params.permit(
|
||||||
:name, :account_id, :visibility,
|
:name, :account_id, :visibility,
|
||||||
|
@ -56,4 +82,8 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||||
def fetch_macro
|
def fetch_macro
|
||||||
@macro = Current.account.macros.find_by(id: params[:id])
|
@macro = Current.account.macros.find_by(id: params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_authorization
|
||||||
|
authorize(@macro) if @macro.present?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,10 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
|
||||||
@portal.members << agents
|
@portal.members << agents
|
||||||
end
|
end
|
||||||
|
|
||||||
def show; end
|
def show
|
||||||
|
@all_articles = @portal.articles
|
||||||
|
@articles = @all_articles.search(locale: params[:locale])
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@portal = Current.account.portals.build(portal_params)
|
@portal = Current.account.portals.build(portal_params)
|
||||||
|
|
|
@ -50,7 +50,9 @@ class Api::V1::Widget::BaseController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def contact_name
|
def contact_name
|
||||||
params[:contact][:name] || contact_email.split('@')[0] if contact_email.present?
|
return if @contact.email.present? || @contact.phone_number.present? || @contact.identifier.present?
|
||||||
|
|
||||||
|
permitted_params.dig(:contact, :name) || (contact_email.split('@')[0] if contact_email.present?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def contact_phone_number
|
def contact_phone_number
|
||||||
|
|
|
@ -17,7 +17,8 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||||
@contact = ContactIdentifyAction.new(
|
@contact = ContactIdentifyAction.new(
|
||||||
contact: @contact,
|
contact: @contact,
|
||||||
params: { email: contact_email, phone_number: contact_phone_number, name: contact_name },
|
params: { email: contact_email, phone_number: contact_phone_number, name: contact_name },
|
||||||
retain_original_contact_name: true
|
retain_original_contact_name: true,
|
||||||
|
discard_invalid_attrs: true
|
||||||
).perform
|
).perform
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
class Platform::Api::V1::AccountsController < PlatformController
|
class Platform::Api::V1::AccountsController < PlatformController
|
||||||
def create
|
def create
|
||||||
@resource = Account.new(account_params)
|
@resource = Account.new(account_params)
|
||||||
|
update_resource_features
|
||||||
@resource.save!
|
@resource.save!
|
||||||
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
|
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
|
||||||
render json: @resource
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show; end
|
||||||
render json: @resource
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@resource.update!(account_params)
|
@resource.assign_attributes(account_params)
|
||||||
render json: @resource
|
update_resource_features
|
||||||
|
@resource.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
@ -27,6 +26,18 @@ class Platform::Api::V1::AccountsController < PlatformController
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
params.permit(:name, :locale)
|
permitted_params.except(:features)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_resource_features
|
||||||
|
return if permitted_params[:features].blank?
|
||||||
|
|
||||||
|
permitted_params[:features].each do |key, value|
|
||||||
|
value.present? ? @resource.enable_features(key) : @resource.disable_features(key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def permitted_params
|
||||||
|
params.permit(:name, :locale, :domain, :support_email, :status, features: {}, limits: {}, custom_attributes: {})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Public::Api::V1::Inboxes::ContactsController < Public::Api::V1::InboxesCon
|
||||||
@contact_inbox = ::ContactInboxWithContactBuilder.new(
|
@contact_inbox = ::ContactInboxWithContactBuilder.new(
|
||||||
source_id: source_id,
|
source_id: source_id,
|
||||||
inbox: @inbox_channel.inbox,
|
inbox: @inbox_channel.inbox,
|
||||||
contact_attributes: permitted_params.except(:identifier, :identifier_hash)
|
contact_attributes: permitted_params.except(:identifier_hash)
|
||||||
).perform
|
).perform
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,15 @@ class Public::Api::V1::InboxesController < PublicController
|
||||||
before_action :set_contact_inbox
|
before_action :set_contact_inbox
|
||||||
before_action :set_conversation
|
before_action :set_conversation
|
||||||
|
|
||||||
|
def show
|
||||||
|
@inbox_channel = ::Channel::Api.find_by!(identifier: params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_inbox_channel
|
def set_inbox_channel
|
||||||
|
return if params[:inbox_id].blank?
|
||||||
|
|
||||||
@inbox_channel = ::Channel::Api.find_by!(identifier: params[:inbox_id])
|
@inbox_channel = ::Channel::Api.find_by!(identifier: params[:inbox_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
class Public::Api::V1::Portals::ArticlesController < PublicController
|
class Public::Api::V1::Portals::ArticlesController < PublicController
|
||||||
before_action :ensure_custom_domain_request, only: [:show, :index]
|
before_action :ensure_custom_domain_request, only: [:show, :index]
|
||||||
before_action :portal
|
before_action :portal
|
||||||
before_action :set_category
|
before_action :set_category, except: [:index]
|
||||||
before_action :set_article, only: [:show]
|
before_action :set_article, only: [:show]
|
||||||
layout 'portal'
|
layout 'portal'
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ class Public::Api::V1::Portals::ArticlesController < PublicController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_category
|
def set_category
|
||||||
@category = @portal.categories.find_by!(slug: params[:category_slug])
|
@category = @portal.categories.find_by!(slug: params[:category_slug]) if params[:category_slug].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def portal
|
def portal
|
||||||
|
|
|
@ -8,6 +8,12 @@ module FileTypeHelper
|
||||||
:file
|
:file
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Used in case of DIRECT_UPLOADS_ENABLED=true
|
||||||
|
def file_type_by_signed_id(signed_id)
|
||||||
|
blob = ActiveStorage::Blob.find_signed(signed_id)
|
||||||
|
file_type(blob&.content_type)
|
||||||
|
end
|
||||||
|
|
||||||
def image_file?(content_type)
|
def image_file?(content_type)
|
||||||
[
|
[
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
|
|
|
@ -7,8 +7,8 @@ class CategoriesAPI extends PortalsAPI {
|
||||||
super('categories', { accountScoped: true });
|
super('categories', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
get({ portalSlug }) {
|
get({ portalSlug, locale }) {
|
||||||
return axios.get(`${this.url}/${portalSlug}/categories`);
|
return axios.get(`${this.url}/${portalSlug}/categories?locale=${locale}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
create({ portalSlug, categoryObj }) {
|
create({ portalSlug, categoryObj }) {
|
||||||
|
|
|
@ -6,6 +6,10 @@ class PortalsAPI extends ApiClient {
|
||||||
super('portals', { accountScoped: true });
|
super('portals', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPortal({ portalSlug, locale }) {
|
||||||
|
return axios.get(`${this.url}/${portalSlug}?locale=${locale}`);
|
||||||
|
}
|
||||||
|
|
||||||
updatePortal({ portalSlug, portalObj }) {
|
updatePortal({ portalSlug, portalObj }) {
|
||||||
return axios.patch(`${this.url}/${portalSlug}`, portalObj);
|
return axios.patch(`${this.url}/${portalSlug}`, portalObj);
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,16 @@ class ConversationApi extends ApiClient {
|
||||||
custom_attributes: customAttributes,
|
custom_attributes: customAttributes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fetchParticipants(conversationId) {
|
||||||
|
return axios.get(`${this.url}/${conversationId}/participants`);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateParticipants({ conversationId, userIds }) {
|
||||||
|
return axios.patch(`${this.url}/${conversationId}/participants`, {
|
||||||
|
user_ids: userIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ConversationApi();
|
export default new ConversationApi();
|
||||||
|
|
|
@ -5,9 +5,12 @@ import Button from './ui/WootButton';
|
||||||
import Code from './Code';
|
import Code from './Code';
|
||||||
import ColorPicker from './widgets/ColorPicker';
|
import ColorPicker from './widgets/ColorPicker';
|
||||||
import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
|
import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
|
||||||
|
import ConfirmModal from './widgets/modal/ConfirmationModal.vue';
|
||||||
|
import ContextMenu from './ui/ContextMenu.vue';
|
||||||
import DeleteModal from './widgets/modal/DeleteModal.vue';
|
import DeleteModal from './widgets/modal/DeleteModal.vue';
|
||||||
import DropdownItem from 'shared/components/ui/dropdown/DropdownItem';
|
import DropdownItem from 'shared/components/ui/dropdown/DropdownItem';
|
||||||
import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
|
import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
|
||||||
|
import FeatureToggle from './widgets/FeatureToggle';
|
||||||
import HorizontalBar from './widgets/chart/HorizontalBarChart';
|
import HorizontalBar from './widgets/chart/HorizontalBarChart';
|
||||||
import Input from './widgets/forms/Input.vue';
|
import Input from './widgets/forms/Input.vue';
|
||||||
import Label from './ui/Label';
|
import Label from './ui/Label';
|
||||||
|
@ -21,8 +24,6 @@ import SubmitButton from './buttons/FormSubmitButton';
|
||||||
import Tabs from './ui/Tabs/Tabs';
|
import Tabs from './ui/Tabs/Tabs';
|
||||||
import TabsItem from './ui/Tabs/TabsItem';
|
import TabsItem from './ui/Tabs/TabsItem';
|
||||||
import Thumbnail from './widgets/Thumbnail.vue';
|
import Thumbnail from './widgets/Thumbnail.vue';
|
||||||
import ConfirmModal from './widgets/modal/ConfirmationModal.vue';
|
|
||||||
import ContextMenu from './ui/ContextMenu.vue';
|
|
||||||
|
|
||||||
const WootUIKit = {
|
const WootUIKit = {
|
||||||
AvatarUploader,
|
AvatarUploader,
|
||||||
|
@ -31,9 +32,12 @@ const WootUIKit = {
|
||||||
Code,
|
Code,
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
ConfirmDeleteModal,
|
ConfirmDeleteModal,
|
||||||
|
ConfirmModal,
|
||||||
|
ContextMenu,
|
||||||
DeleteModal,
|
DeleteModal,
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
FeatureToggle,
|
||||||
HorizontalBar,
|
HorizontalBar,
|
||||||
Input,
|
Input,
|
||||||
Label,
|
Label,
|
||||||
|
@ -47,8 +51,6 @@ const WootUIKit = {
|
||||||
Tabs,
|
Tabs,
|
||||||
TabsItem,
|
TabsItem,
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
ConfirmModal,
|
|
||||||
ContextMenu,
|
|
||||||
install(Vue) {
|
install(Vue) {
|
||||||
const keys = Object.keys(this);
|
const keys = Object.keys(this);
|
||||||
keys.pop(); // remove 'install' from keys
|
keys.pop(); // remove 'install' from keys
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
:class="{ 'text-truncate': shouldTruncate }"
|
:class="{ 'text-truncate': shouldTruncate }"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
<span v-if="isHelpCenterSidebar && childItemCount" class="count-view">
|
<span v-if="showChildCount" class="count-view">
|
||||||
{{ childItemCount }}
|
{{ childItemCount }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -76,7 +76,7 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
isHelpCenterSidebar: {
|
showChildCount: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
@ -127,11 +127,16 @@ $label-badge-size: var(--space-slab);
|
||||||
color: var(--w-500);
|
color: var(--w-500);
|
||||||
border-color: var(--w-25);
|
border-color: var(--w-25);
|
||||||
}
|
}
|
||||||
|
&.is-active .count-view {
|
||||||
|
background: var(--w-75);
|
||||||
|
color: var(--w-500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-label {
|
.menu-label {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
line-height: var(--space-two);
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inbox-icon {
|
.inbox-icon {
|
||||||
|
@ -175,10 +180,6 @@ $label-badge-size: var(--space-slab);
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
margin-left: var(--space-smaller);
|
margin-left: var(--space-smaller);
|
||||||
padding: var(--space-zero) var(--space-smaller);
|
padding: var(--space-zero) var(--space-smaller);
|
||||||
|
line-height: var(--font-size-small);
|
||||||
&.is-active {
|
|
||||||
background: var(--w-50);
|
|
||||||
color: var(--w-500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,16 +4,15 @@
|
||||||
<span class="secondary-menu--header fs-small">
|
<span class="secondary-menu--header fs-small">
|
||||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||||
</span>
|
</span>
|
||||||
<div v-if="isHelpCenterSidebar" class="submenu-icons">
|
<div v-if="menuItem.showNewButton" class="submenu-icons">
|
||||||
<woot-button
|
<woot-button
|
||||||
size="tiny"
|
size="tiny"
|
||||||
variant="clear"
|
variant="clear"
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
|
icon="add"
|
||||||
class="submenu-icon"
|
class="submenu-icon"
|
||||||
@click="onClickOpen"
|
@click="onClickOpen"
|
||||||
>
|
/>
|
||||||
<fluent-icon icon="add" size="16" />
|
|
||||||
</woot-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<router-link
|
<router-link
|
||||||
|
@ -28,11 +27,7 @@
|
||||||
size="14"
|
size="14"
|
||||||
/>
|
/>
|
||||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||||
<span
|
<span v-if="showChildCount(menuItem.count)" class="count-view">
|
||||||
v-if="isHelpCenterSidebar"
|
|
||||||
class="count-view"
|
|
||||||
:class="computedClass"
|
|
||||||
>
|
|
||||||
{{ `${menuItem.count}` }}
|
{{ `${menuItem.count}` }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
@ -55,7 +50,7 @@
|
||||||
:should-truncate="child.truncateLabel"
|
:should-truncate="child.truncateLabel"
|
||||||
:icon="computedInboxClass(child)"
|
:icon="computedInboxClass(child)"
|
||||||
:warning-icon="computedInboxErrorClass(child)"
|
:warning-icon="computedInboxErrorClass(child)"
|
||||||
:is-help-center-sidebar="isHelpCenterSidebar"
|
:show-child-count="showChildCount(child.count)"
|
||||||
:child-item-count="child.count"
|
:child-item-count="child.count"
|
||||||
/>
|
/>
|
||||||
<router-link
|
<router-link
|
||||||
|
@ -64,10 +59,10 @@
|
||||||
:to="menuItem.toState"
|
:to="menuItem.toState"
|
||||||
custom
|
custom
|
||||||
>
|
>
|
||||||
<li>
|
<li class="menu-item--new">
|
||||||
<a
|
<a
|
||||||
:href="href"
|
:href="href"
|
||||||
class="button small clear menu-item--new secondary"
|
class="button small link clear secondary"
|
||||||
:class="{ 'is-active': isActive }"
|
:class="{ 'is-active': isActive }"
|
||||||
@click="e => newLinkClick(e, navigate)"
|
@click="e => newLinkClick(e, navigate)"
|
||||||
>
|
>
|
||||||
|
@ -78,9 +73,6 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</router-link>
|
</router-link>
|
||||||
<p v-if="isHelpCenterSidebar && isCategoryEmpty" class="empty-text">
|
|
||||||
{{ $t('SIDEBAR.HELP_CENTER.CATEGORY_EMPTY_MESSAGE') }}
|
|
||||||
</p>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
@ -104,14 +96,6 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
isHelpCenterSidebar: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
isCategoryEmpty: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
|
@ -161,8 +145,8 @@ export default {
|
||||||
this.menuItem.toStateName === 'settings_applications'
|
this.menuItem.toStateName === 'settings_applications'
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isArticlesView() {
|
isCurrentRoute() {
|
||||||
return this.$store.state.route.name === this.menuItem.toStateName;
|
return this.$store.state.route.name.includes(this.menuItem.toStateName);
|
||||||
},
|
},
|
||||||
|
|
||||||
computedClass() {
|
computedClass() {
|
||||||
|
@ -181,12 +165,11 @@ export default {
|
||||||
}
|
}
|
||||||
return ' ';
|
return ' ';
|
||||||
}
|
}
|
||||||
if (this.isHelpCenterSidebar) {
|
|
||||||
if (this.isArticlesView) {
|
if (this.isCurrentRoute) {
|
||||||
return 'is-active';
|
return 'is-active';
|
||||||
}
|
}
|
||||||
return ' ';
|
|
||||||
}
|
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -222,6 +205,9 @@ export default {
|
||||||
onClickOpen() {
|
onClickOpen() {
|
||||||
this.$emit('open');
|
this.$emit('open');
|
||||||
},
|
},
|
||||||
|
showChildCount(count) {
|
||||||
|
return Number.isInteger(count);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -277,6 +263,11 @@ export default {
|
||||||
color: var(--w-500);
|
color: var(--w-500);
|
||||||
border-color: var(--w-25);
|
border-color: var(--w-25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.is-active .count-view {
|
||||||
|
background: var(--w-75);
|
||||||
|
color: var(--w-600);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondary-menu--icon {
|
.secondary-menu--icon {
|
||||||
|
@ -306,15 +297,12 @@ export default {
|
||||||
top: -1px;
|
top: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-item .button.menu-item--new {
|
.sidebar-item .menu-item--new {
|
||||||
display: inline-flex;
|
padding: var(--space-small) 0;
|
||||||
height: var(--space-medium);
|
|
||||||
margin: var(--space-smaller) 0;
|
|
||||||
padding: var(--space-smaller);
|
|
||||||
color: var(--s-500);
|
|
||||||
|
|
||||||
&:hover {
|
.button {
|
||||||
color: var(--w-500);
|
display: inline-flex;
|
||||||
|
color: var(--s-500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,11 +328,6 @@ export default {
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
margin-left: var(--space-smaller);
|
margin-left: var(--space-smaller);
|
||||||
padding: var(--space-zero) var(--space-smaller);
|
padding: var(--space-zero) var(--space-smaller);
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
background: var(--w-50);
|
|
||||||
color: var(--w-500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.submenu-icons {
|
.submenu-icons {
|
||||||
|
@ -356,10 +339,4 @@ export default {
|
||||||
margin-left: var(--space-small);
|
margin-left: var(--space-small);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
color: var(--s-500);
|
|
||||||
font-size: var(--font-size-small);
|
|
||||||
margin: var(--space-smaller);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,13 +5,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const ZERO = 0;
|
|
||||||
const MINUTE_IN_MILLI_SECONDS = 60000;
|
const MINUTE_IN_MILLI_SECONDS = 60000;
|
||||||
const HOUR_IN_MILLI_SECONDS = MINUTE_IN_MILLI_SECONDS * 60;
|
const HOUR_IN_MILLI_SECONDS = MINUTE_IN_MILLI_SECONDS * 60;
|
||||||
const DAY_IN_MILLI_SECONDS = HOUR_IN_MILLI_SECONDS * 24;
|
const DAY_IN_MILLI_SECONDS = HOUR_IN_MILLI_SECONDS * 24;
|
||||||
|
|
||||||
import timeMixin from 'dashboard/mixins/time';
|
import timeMixin from 'dashboard/mixins/time';
|
||||||
import { differenceInMilliseconds } from 'date-fns';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TimeAgo',
|
name: 'TimeAgo',
|
||||||
|
@ -28,51 +26,40 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
timeAgo: '',
|
timeAgo: this.dynamicTime(this.timestamp),
|
||||||
timer: null,
|
timer: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
watch: {
|
||||||
|
timestamp() {
|
||||||
|
this.timeAgo = this.dynamicTime(this.timestamp);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.isAutoRefreshEnabled) {
|
||||||
|
this.createTimer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
createTimer() {
|
||||||
|
this.timer = setTimeout(() => {
|
||||||
|
this.timeAgo = this.dynamicTime(this.timestamp);
|
||||||
|
this.createTimer();
|
||||||
|
}, this.refreshTime());
|
||||||
|
},
|
||||||
refreshTime() {
|
refreshTime() {
|
||||||
const timeDiff = differenceInMilliseconds(
|
const timeDiff = Date.now() - this.timestamp * 1000;
|
||||||
new Date(),
|
|
||||||
new Date(this.timestamp * 1000)
|
|
||||||
);
|
|
||||||
if (timeDiff > DAY_IN_MILLI_SECONDS) {
|
if (timeDiff > DAY_IN_MILLI_SECONDS) {
|
||||||
return DAY_IN_MILLI_SECONDS;
|
return DAY_IN_MILLI_SECONDS;
|
||||||
}
|
}
|
||||||
if (timeDiff > HOUR_IN_MILLI_SECONDS) {
|
if (timeDiff > HOUR_IN_MILLI_SECONDS) {
|
||||||
return HOUR_IN_MILLI_SECONDS;
|
return HOUR_IN_MILLI_SECONDS;
|
||||||
}
|
}
|
||||||
if (timeDiff > MINUTE_IN_MILLI_SECONDS) {
|
|
||||||
return MINUTE_IN_MILLI_SECONDS;
|
return MINUTE_IN_MILLI_SECONDS;
|
||||||
}
|
|
||||||
return ZERO;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.timeAgo = this.dynamicTime(this.timestamp);
|
|
||||||
if (this.isAutoRefreshEnabled) {
|
|
||||||
this.createTimer();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.clearTimer();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
createTimer() {
|
|
||||||
const refreshTime = this.refreshTime;
|
|
||||||
if (refreshTime > ZERO) {
|
|
||||||
this.timer = setTimeout(() => {
|
|
||||||
this.timeAgo = this.dynamicTime(this.timestamp);
|
|
||||||
this.createTimer();
|
|
||||||
}, refreshTime);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearTimer() {
|
|
||||||
if (this.timer) {
|
|
||||||
clearTimeout(this.timer);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="filter" :class="actionInputStyles">
|
||||||
class="filter"
|
|
||||||
:class="{ error: v.action_params.$dirty && v.action_params.$error }"
|
|
||||||
>
|
|
||||||
<div class="filter-inputs">
|
<div class="filter-inputs">
|
||||||
<select
|
<select
|
||||||
v-model="action_name"
|
v-model="action_name"
|
||||||
|
@ -21,14 +18,32 @@
|
||||||
<div v-if="showActionInput" class="filter__answer--wrap">
|
<div v-if="showActionInput" class="filter__answer--wrap">
|
||||||
<div v-if="inputType">
|
<div v-if="inputType">
|
||||||
<div
|
<div
|
||||||
v-if="inputType === 'multi_select'"
|
v-if="inputType === 'search_select'"
|
||||||
class="multiselect-wrap--small"
|
class="multiselect-wrap--small"
|
||||||
>
|
>
|
||||||
<multiselect
|
<multiselect
|
||||||
v-model="action_params"
|
v-model="action_params"
|
||||||
track-by="id"
|
track-by="id"
|
||||||
label="name"
|
label="name"
|
||||||
:placeholder="'Select'"
|
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
|
||||||
|
selected-label
|
||||||
|
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||||
|
deselect-label=""
|
||||||
|
:max-height="160"
|
||||||
|
:options="dropdownValues"
|
||||||
|
:allow-empty="false"
|
||||||
|
:option-height="104"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="inputType === 'multi_select'"
|
||||||
|
class="multiselect-wrap--small"
|
||||||
|
>
|
||||||
|
<multiselect
|
||||||
|
v-model="action_params"
|
||||||
|
track-by="id"
|
||||||
|
label="name"
|
||||||
|
:placeholder="$t('FORMS.MULTISELECT.SELECT')"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
selected-label
|
selected-label
|
||||||
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||||
|
@ -36,6 +51,7 @@
|
||||||
:max-height="160"
|
:max-height="160"
|
||||||
:options="dropdownValues"
|
:options="dropdownValues"
|
||||||
:allow-empty="false"
|
:allow-empty="false"
|
||||||
|
:option-height="104"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
@ -60,6 +76,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<woot-button
|
<woot-button
|
||||||
|
v-if="!isMacro"
|
||||||
icon="dismiss"
|
icon="dismiss"
|
||||||
variant="clear"
|
variant="clear"
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
|
@ -120,6 +137,10 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
isMacro: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
action_name: {
|
action_name: {
|
||||||
|
@ -146,6 +167,12 @@ export default {
|
||||||
return this.actionTypes.find(action => action.key === this.action_name)
|
return this.actionTypes.find(action => action.key === this.action_name)
|
||||||
.inputType;
|
.inputType;
|
||||||
},
|
},
|
||||||
|
actionInputStyles() {
|
||||||
|
return {
|
||||||
|
'has-error': this.v.action_params.$dirty && this.v.action_params.$error,
|
||||||
|
'is-a-macro': this.isMacro,
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
removeAction() {
|
removeAction() {
|
||||||
|
@ -165,9 +192,21 @@ export default {
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: var(--border-radius-medium);
|
border-radius: var(--border-radius-medium);
|
||||||
margin-bottom: var(--space-small);
|
margin-bottom: var(--space-small);
|
||||||
|
|
||||||
|
&.is-a-macro {
|
||||||
|
margin-bottom: 0;
|
||||||
|
background: var(--white);
|
||||||
|
padding: var(--space-zero);
|
||||||
|
border: unset;
|
||||||
|
border-radius: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter.error {
|
.no-margin-bottom {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter.has-error {
|
||||||
background: var(--r-50);
|
background: var(--r-50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +279,6 @@ export default {
|
||||||
margin-bottom: var(--space-zero);
|
margin-bottom: var(--space-zero);
|
||||||
}
|
}
|
||||||
.action-message {
|
.action-message {
|
||||||
margin: var(--space-small) 0 0;
|
margin: var(--space-small) var(--space-zero) var(--space-zero);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -113,5 +113,6 @@ input[type='file'] {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="avatar-container" :style="style" aria-hidden="true">
|
||||||
class="avatar-container"
|
{{ userInitial }}
|
||||||
:style="[style, customStyle]"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<span>{{ userInitial }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -16,69 +12,26 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
backgroundColor: {
|
|
||||||
type: String,
|
|
||||||
default: '#c2e1ff',
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: '#1976cc',
|
|
||||||
},
|
|
||||||
customStyle: {
|
|
||||||
type: Object,
|
|
||||||
default: undefined,
|
|
||||||
},
|
|
||||||
size: {
|
size: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 40,
|
default: 40,
|
||||||
},
|
},
|
||||||
src: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
rounded: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
variant: {
|
|
||||||
type: String,
|
|
||||||
default: 'circle',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
style() {
|
style() {
|
||||||
let style = {
|
return {
|
||||||
width: `${this.size}px`,
|
|
||||||
height: `${this.size}px`,
|
|
||||||
borderRadius:
|
|
||||||
this.variant === 'square' ? 'var(--border-radius-large)' : '50%',
|
|
||||||
lineHeight: `${this.size + Math.floor(this.size / 20)}px`,
|
|
||||||
fontSize: `${Math.floor(this.size / 2.5)}px`,
|
fontSize: `${Math.floor(this.size / 2.5)}px`,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.backgroundColor) {
|
|
||||||
style = { ...style, backgroundColor: this.backgroundColor };
|
|
||||||
}
|
|
||||||
if (this.color) {
|
|
||||||
style = { ...style, color: this.color };
|
|
||||||
}
|
|
||||||
return style;
|
|
||||||
},
|
},
|
||||||
userInitial() {
|
userInitial() {
|
||||||
return this.initials || this.initial(this.username);
|
const parts = this.username.split(/[ -]/);
|
||||||
},
|
let initials = parts.reduce((acc, curr) => acc + curr.charAt(0), '');
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initial(username) {
|
|
||||||
const parts = username ? username.split(/[ -]/) : [];
|
|
||||||
let initials = '';
|
|
||||||
for (let i = 0; i < parts.length; i += 1) {
|
|
||||||
initials += parts[i].charAt(0);
|
|
||||||
}
|
|
||||||
if (initials.length > 2 && initials.search(/[A-Z]/) !== -1) {
|
if (initials.length > 2 && initials.search(/[A-Z]/) !== -1) {
|
||||||
initials = initials.replace(/[a-z]+/g, '');
|
initials = initials.replace(/[a-z]+/g, '');
|
||||||
}
|
}
|
||||||
initials = initials.substring(0, 2).toUpperCase();
|
initials = initials.substring(0, 2).toUpperCase();
|
||||||
|
|
||||||
return initials;
|
return initials;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -88,11 +41,13 @@ export default {
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.avatar-container {
|
.avatar-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
line-height: 100%;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-image: linear-gradient(to top, var(--w-100) 0%, var(--w-75) 100%);
|
background-image: linear-gradient(to top, var(--w-100) 0%, var(--w-75) 100%);
|
||||||
color: var(--w-600);
|
color: var(--w-600);
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,6 +5,11 @@
|
||||||
:key="index"
|
:key="index"
|
||||||
class="dashboard-app--list"
|
class="dashboard-app--list"
|
||||||
>
|
>
|
||||||
|
<loading-state
|
||||||
|
v-if="iframeLoading"
|
||||||
|
:message="$t('DASHBOARD_APPS.LOADING_MESSAGE')"
|
||||||
|
class="dashboard-app_loading-container"
|
||||||
|
/>
|
||||||
<iframe
|
<iframe
|
||||||
v-if="configItem.type === 'frame' && configItem.url"
|
v-if="configItem.type === 'frame' && configItem.url"
|
||||||
:id="`dashboard-app--frame-${index}`"
|
:id="`dashboard-app--frame-${index}`"
|
||||||
|
@ -16,7 +21,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import LoadingState from 'dashboard/components/widgets/LoadingState';
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
LoadingState,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
config: {
|
config: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
@ -27,6 +36,11 @@ export default {
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
iframeLoading: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
dashboardAppContext() {
|
dashboardAppContext() {
|
||||||
return {
|
return {
|
||||||
|
@ -57,6 +71,7 @@ export default {
|
||||||
);
|
);
|
||||||
const eventData = { event: 'appContext', data: this.dashboardAppContext };
|
const eventData = { event: 'appContext', data: this.dashboardAppContext };
|
||||||
frameElement.contentWindow.postMessage(JSON.stringify(eventData), '*');
|
frameElement.contentWindow.postMessage(JSON.stringify(eventData), '*');
|
||||||
|
this.iframeLoading = false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -73,4 +88,11 @@ export default {
|
||||||
.dashboard-app--list iframe {
|
.dashboard-app--list iframe {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
.dashboard-app_loading-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="isFeatureEnabled">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
featureKey: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
||||||
|
accountId: 'getCurrentAccountId',
|
||||||
|
}),
|
||||||
|
isFeatureEnabled() {
|
||||||
|
return this.isFeatureEnabledonAccount(this.accountId, this.featureKey);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -32,6 +32,7 @@
|
||||||
v-for="attribute in filterAttributes"
|
v-for="attribute in filterAttributes"
|
||||||
:key="attribute.key"
|
:key="attribute.key"
|
||||||
:value="attribute.key"
|
:value="attribute.key"
|
||||||
|
:disabled="attribute.disabled"
|
||||||
>
|
>
|
||||||
{{ attribute.name }}
|
{{ attribute.name }}
|
||||||
</option>
|
</option>
|
||||||
|
@ -173,6 +174,10 @@ export default {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
customAttributeType: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
attributeKey: {
|
attributeKey: {
|
||||||
|
|
|
@ -83,75 +83,71 @@ export default {
|
||||||
},
|
},
|
||||||
pageSize: {
|
pageSize: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 15,
|
default: 25,
|
||||||
},
|
},
|
||||||
totalCount: {
|
totalCount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
onPageChange: {
|
|
||||||
type: Function,
|
|
||||||
default: () => {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isFooterVisible() {
|
isFooterVisible() {
|
||||||
return this.totalCount && !(this.firstIndex > this.totalCount);
|
return this.totalCount && !(this.firstIndex > this.totalCount);
|
||||||
},
|
},
|
||||||
firstIndex() {
|
firstIndex() {
|
||||||
const firstIndex = this.pageSize * (this.currentPage - 1) + 1;
|
return this.pageSize * (this.currentPage - 1) + 1;
|
||||||
return firstIndex;
|
|
||||||
},
|
},
|
||||||
lastIndex() {
|
lastIndex() {
|
||||||
const index = Math.min(this.totalCount, this.pageSize * this.currentPage);
|
return Math.min(this.totalCount, this.pageSize * this.currentPage);
|
||||||
return index;
|
|
||||||
},
|
},
|
||||||
searchButtonClass() {
|
searchButtonClass() {
|
||||||
return this.searchQuery !== '' ? 'show' : '';
|
return this.searchQuery !== '' ? 'show' : '';
|
||||||
},
|
},
|
||||||
hasLastPage() {
|
hasLastPage() {
|
||||||
const isDisabled =
|
return !!Math.ceil(this.totalCount / this.pageSize);
|
||||||
this.currentPage === Math.ceil(this.totalCount / this.pageSize);
|
|
||||||
return isDisabled;
|
|
||||||
},
|
},
|
||||||
hasFirstPage() {
|
hasFirstPage() {
|
||||||
const isDisabled = this.currentPage === 1;
|
return this.currentPage === 1;
|
||||||
return isDisabled;
|
|
||||||
},
|
},
|
||||||
hasNextPage() {
|
hasNextPage() {
|
||||||
const isDisabled =
|
return this.currentPage === Math.ceil(this.totalCount / this.pageSize);
|
||||||
this.currentPage === Math.ceil(this.totalCount / this.pageSize);
|
|
||||||
return isDisabled;
|
|
||||||
},
|
},
|
||||||
hasPrevPage() {
|
hasPrevPage() {
|
||||||
const isDisabled = this.currentPage === 1;
|
return this.currentPage === 1;
|
||||||
return isDisabled;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onNextPage() {
|
onNextPage() {
|
||||||
if (this.hasNextPage) return;
|
if (this.hasNextPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newPage = this.currentPage + 1;
|
const newPage = this.currentPage + 1;
|
||||||
this.onPageChange(newPage);
|
this.onPageChange(newPage);
|
||||||
},
|
},
|
||||||
onPrevPage() {
|
onPrevPage() {
|
||||||
if (this.hasPrevPage) return;
|
if (this.hasPrevPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newPage = this.currentPage - 1;
|
const newPage = this.currentPage - 1;
|
||||||
this.onPageChange(newPage);
|
this.onPageChange(newPage);
|
||||||
},
|
},
|
||||||
onFirstPage() {
|
onFirstPage() {
|
||||||
if (this.hasFirstPage) return;
|
if (this.hasFirstPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newPage = 1;
|
const newPage = 1;
|
||||||
this.onPageChange(newPage);
|
this.onPageChange(newPage);
|
||||||
},
|
},
|
||||||
onLastPage() {
|
onLastPage() {
|
||||||
if (this.hasLastPage) return;
|
if (this.hasLastPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const newPage = Math.ceil(this.totalCount / this.pageSize);
|
const newPage = Math.ceil(this.totalCount / this.pageSize);
|
||||||
this.onPageChange(newPage);
|
this.onPageChange(newPage);
|
||||||
},
|
},
|
||||||
|
onPageChange(page) {
|
||||||
|
this.$emit('page-change', page);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,49 +2,47 @@ import { mount } from '@vue/test-utils';
|
||||||
import Avatar from './Avatar.vue';
|
import Avatar from './Avatar.vue';
|
||||||
import Thumbnail from './Thumbnail.vue';
|
import Thumbnail from './Thumbnail.vue';
|
||||||
|
|
||||||
describe(`when there are NO errors loading the thumbnail`, () => {
|
describe('Thumbnail.vue', () => {
|
||||||
it(`should render the agent thumbnail`, () => {
|
it('should render the agent thumbnail if valid image is passed', () => {
|
||||||
const wrapper = mount(Thumbnail, {
|
const wrapper = mount(Thumbnail, {
|
||||||
propsData: {
|
propsData: {
|
||||||
src: 'https://some_valid_url.com',
|
src: 'https://some_valid_url.com',
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
hasImageLoaded: true,
|
||||||
imgError: false,
|
imgError: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.find('#image').exists()).toBe(true);
|
expect(wrapper.find('.user-thumbnail').exists()).toBe(true);
|
||||||
const avatarComponent = wrapper.findComponent(Avatar);
|
const avatarComponent = wrapper.findComponent(Avatar);
|
||||||
expect(avatarComponent.exists()).toBe(false);
|
expect(avatarComponent.isVisible()).toBe(false);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(`when there ARE errors loading the thumbnail`, () => {
|
it('should render the avatar component if invalid image is passed', () => {
|
||||||
it(`should render the agent avatar`, () => {
|
|
||||||
const wrapper = mount(Thumbnail, {
|
const wrapper = mount(Thumbnail, {
|
||||||
propsData: {
|
propsData: {
|
||||||
src: 'https://some_invalid_url.com',
|
src: 'https://some_invalid_url.com',
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
hasImageLoaded: true,
|
||||||
imgError: true,
|
imgError: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.find('#image').exists()).toBe(false);
|
expect(wrapper.find('#image').exists()).toBe(false);
|
||||||
const avatarComponent = wrapper.findComponent(Avatar);
|
const avatarComponent = wrapper.findComponent(Avatar);
|
||||||
expect(avatarComponent.exists()).toBe(true);
|
expect(avatarComponent.isVisible()).toBe(true);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(`when Avatar shows`, () => {
|
it('should the initial of the name if no image is passed', () => {
|
||||||
it(`initials shold correspond to username`, () => {
|
|
||||||
const wrapper = mount(Avatar, {
|
const wrapper = mount(Avatar, {
|
||||||
propsData: {
|
propsData: {
|
||||||
username: 'Angie Rojas',
|
username: 'Angie Rojas',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(wrapper.find('span').text()).toBe('AR');
|
expect(wrapper.find('div').text()).toBe('AR');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,74 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="user-thumbnail-box" :style="{ height: size, width: size }">
|
<div
|
||||||
|
:class="thumbnailBoxClass"
|
||||||
|
:style="{ height: size, width: size }"
|
||||||
|
:title="title"
|
||||||
|
>
|
||||||
|
<!-- Using v-show instead of v-if to avoid flickering as v-if removes dom elements. -->
|
||||||
<img
|
<img
|
||||||
v-if="!imgError && Boolean(src)"
|
v-show="shouldShowImage"
|
||||||
id="image"
|
|
||||||
:src="src"
|
:src="src"
|
||||||
:class="thumbnailClass"
|
:class="thumbnailClass"
|
||||||
@error="onImgError()"
|
@load="onImgLoad"
|
||||||
|
@error="onImgError"
|
||||||
/>
|
/>
|
||||||
<Avatar
|
<Avatar
|
||||||
v-else
|
v-show="!shouldShowImage"
|
||||||
:username="userNameWithoutEmoji"
|
:username="userNameWithoutEmoji"
|
||||||
:class="thumbnailClass"
|
:class="thumbnailClass"
|
||||||
:size="avatarSize"
|
:size="avatarSize"
|
||||||
:variant="variant"
|
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
v-if="badge === 'instagram_direct_message'"
|
v-if="badgeSrc"
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
class="source-badge"
|
||||||
:style="badgeStyle"
|
:style="badgeStyle"
|
||||||
src="/integrations/channels/badges/instagram-dm.png"
|
:src="`/integrations/channels/badges/${badgeSrc}.png`"
|
||||||
/>
|
alt="Badge"
|
||||||
<img
|
|
||||||
v-else-if="badge === 'facebook'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/messenger.png"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else-if="badge === 'twitter-tweet'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/twitter-tweet.png"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else-if="badge === 'twitter-dm'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/twitter-dm.png"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else-if="badge === 'whatsapp'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/whatsapp.png"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else-if="badge === 'sms'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/sms.png"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else-if="badge === 'Channel::Line'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/line.png"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
v-else-if="badge === 'Channel::Telegram'"
|
|
||||||
id="badge"
|
|
||||||
class="source-badge"
|
|
||||||
:style="badgeStyle"
|
|
||||||
src="/integrations/channels/badges/telegram.png"
|
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="showStatusIndicator"
|
v-if="showStatusIndicator"
|
||||||
|
@ -103,7 +58,7 @@ export default {
|
||||||
},
|
},
|
||||||
badge: {
|
badge: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'fb',
|
default: '',
|
||||||
},
|
},
|
||||||
username: {
|
username: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -121,6 +76,10 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
variant: {
|
variant: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'circle',
|
default: 'circle',
|
||||||
|
@ -128,6 +87,7 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
hasImageLoaded: false,
|
||||||
imgError: false,
|
imgError: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -142,6 +102,19 @@ export default {
|
||||||
avatarSize() {
|
avatarSize() {
|
||||||
return Number(this.size.replace(/\D+/g, ''));
|
return Number(this.size.replace(/\D+/g, ''));
|
||||||
},
|
},
|
||||||
|
badgeSrc() {
|
||||||
|
return {
|
||||||
|
instagram_direct_message: 'instagram-dm',
|
||||||
|
facebook: 'messenger',
|
||||||
|
'twitter-tweet': 'twitter-tweet',
|
||||||
|
'twitter-dm': 'twitter-dm',
|
||||||
|
whatsapp: 'whatsapp',
|
||||||
|
sms: 'sms',
|
||||||
|
'Channel::Line': 'line',
|
||||||
|
'Channel::Telegram': 'telegram',
|
||||||
|
'Channel::WebWidget': '',
|
||||||
|
}[this.badge];
|
||||||
|
},
|
||||||
badgeStyle() {
|
badgeStyle() {
|
||||||
const size = Math.floor(this.avatarSize / 3);
|
const size = Math.floor(this.avatarSize / 3);
|
||||||
const badgeSize = `${size + 2}px`;
|
const badgeSize = `${size + 2}px`;
|
||||||
|
@ -158,20 +131,34 @@ export default {
|
||||||
this.variant === 'circle' ? 'thumbnail-rounded' : 'thumbnail-square';
|
this.variant === 'circle' ? 'thumbnail-rounded' : 'thumbnail-square';
|
||||||
return `user-thumbnail ${classname} ${variant}`;
|
return `user-thumbnail ${classname} ${variant}`;
|
||||||
},
|
},
|
||||||
|
thumbnailBoxClass() {
|
||||||
|
const boxClass = this.variant === 'circle' ? 'is-rounded' : '';
|
||||||
|
return `user-thumbnail-box ${boxClass}`;
|
||||||
|
},
|
||||||
|
shouldShowImage() {
|
||||||
|
if (!this.src) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.hasImageLoaded) {
|
||||||
|
return !this.imgError;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
src: {
|
src(value, oldValue) {
|
||||||
handler(value, oldValue) {
|
|
||||||
if (value !== oldValue && this.imgError) {
|
if (value !== oldValue && this.imgError) {
|
||||||
this.imgError = false;
|
this.imgError = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
onImgError() {
|
onImgError() {
|
||||||
this.imgError = true;
|
this.imgError = true;
|
||||||
},
|
},
|
||||||
|
onImgLoad() {
|
||||||
|
this.hasImageLoaded = true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -182,6 +169,10 @@ export default {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
&.is-rounded {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.user-thumbnail {
|
.user-thumbnail {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
&.thumbnail-square {
|
&.thumbnail-square {
|
||||||
|
@ -191,6 +182,7 @@ export default {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
vertical-align: initial;
|
||||||
|
|
||||||
&.border {
|
&.border {
|
||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
|
@ -229,9 +221,5 @@ export default {
|
||||||
.user-online-status--offline {
|
.user-online-status--offline {
|
||||||
background: var(--s-500);
|
background: var(--s-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-online-status--offline {
|
|
||||||
background: var(--s-500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
<template>
|
||||||
|
<div class="overlapping-thumbnails">
|
||||||
|
<thumbnail
|
||||||
|
v-for="user in usersList"
|
||||||
|
:key="user.id"
|
||||||
|
v-tooltip="user.name"
|
||||||
|
:title="user.name"
|
||||||
|
:src="user.thumbnail"
|
||||||
|
:username="user.name"
|
||||||
|
:has-border="true"
|
||||||
|
:size="size"
|
||||||
|
:class="`overlapping-thumbnail gap-${gap}`"
|
||||||
|
/>
|
||||||
|
<span v-if="showMoreThumbnailsCount" class="thumbnail-more-text">
|
||||||
|
{{ moreThumbnailsText }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Thumbnail from './Thumbnail';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Thumbnail,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
usersList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: '24px',
|
||||||
|
},
|
||||||
|
showMoreThumbnailsCount: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
moreThumbnailsText: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
gap: {
|
||||||
|
type: String,
|
||||||
|
default: 'normal',
|
||||||
|
validator(value) {
|
||||||
|
// The value must match one of these strings
|
||||||
|
return ['normal', '', 'tight'].includes(value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.overlapping-thumbnails {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlapping-thumbnail {
|
||||||
|
position: relative;
|
||||||
|
box-shadow: var(--shadow-small);
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-left: var(--space-minus-smaller);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-tight {
|
||||||
|
margin-left: var(--space-minus-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-more-text {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
margin-left: var(--space-minus-small);
|
||||||
|
padding: 0 var(--space-small);
|
||||||
|
box-shadow: var(--shadow-small);
|
||||||
|
background: var(--color-background);
|
||||||
|
border-radius: var(--space-giga);
|
||||||
|
border: 1px solid var(--white);
|
||||||
|
|
||||||
|
color: var(--s-600);
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -42,7 +42,7 @@
|
||||||
</div>
|
</div>
|
||||||
<dashboard-app-frame
|
<dashboard-app-frame
|
||||||
v-else
|
v-else
|
||||||
:key="currentChat.id"
|
:key="currentChat.id + '-' + activeIndex"
|
||||||
:config="dashboardApps[activeIndex - 1].content"
|
:config="dashboardApps[activeIndex - 1].content"
|
||||||
:current-chat="currentChat"
|
:current-chat="currentChat"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -40,6 +40,12 @@
|
||||||
:url="attachment.data_url"
|
:url="attachment.data_url"
|
||||||
:readable-time="readableTime"
|
:readable-time="readableTime"
|
||||||
/>
|
/>
|
||||||
|
<bubble-location
|
||||||
|
v-else-if="attachment.file_type === 'location'"
|
||||||
|
:latitude="attachment.coordinates_lat"
|
||||||
|
:longitude="attachment.coordinates_long"
|
||||||
|
:name="attachment.fallback_title"
|
||||||
|
/>
|
||||||
<bubble-file
|
<bubble-file
|
||||||
v-else
|
v-else
|
||||||
:url="attachment.data_url"
|
:url="attachment.data_url"
|
||||||
|
@ -119,6 +125,7 @@ import BubbleImage from './bubble/Image';
|
||||||
import BubbleFile from './bubble/File';
|
import BubbleFile from './bubble/File';
|
||||||
import BubbleVideo from './bubble/Video.vue';
|
import BubbleVideo from './bubble/Video.vue';
|
||||||
import BubbleActions from './bubble/Actions';
|
import BubbleActions from './bubble/Actions';
|
||||||
|
import BubbleLocation from './bubble/Location';
|
||||||
|
|
||||||
import Spinner from 'shared/components/Spinner';
|
import Spinner from 'shared/components/Spinner';
|
||||||
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu';
|
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu';
|
||||||
|
@ -136,6 +143,7 @@ export default {
|
||||||
BubbleFile,
|
BubbleFile,
|
||||||
BubbleVideo,
|
BubbleVideo,
|
||||||
BubbleMailHead,
|
BubbleMailHead,
|
||||||
|
BubbleLocation,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
Spinner,
|
Spinner,
|
||||||
},
|
},
|
||||||
|
|
|
@ -139,7 +139,6 @@ export default {
|
||||||
listLoadingStatus: 'getAllMessagesLoaded',
|
listLoadingStatus: 'getAllMessagesLoaded',
|
||||||
getUnreadCount: 'getUnreadCount',
|
getUnreadCount: 'getUnreadCount',
|
||||||
loadingChatList: 'getChatListLoadingStatus',
|
loadingChatList: 'getChatListLoadingStatus',
|
||||||
conversationLastSeen: 'getConversationLastSeen',
|
|
||||||
}),
|
}),
|
||||||
inboxId() {
|
inboxId() {
|
||||||
return this.currentChat.inbox_id;
|
return this.currentChat.inbox_id;
|
||||||
|
@ -234,7 +233,6 @@ export default {
|
||||||
return 'arrow-chevron-left';
|
return 'arrow-chevron-left';
|
||||||
},
|
},
|
||||||
getLastSeenAt() {
|
getLastSeenAt() {
|
||||||
if (this.conversationLastSeen) return this.conversationLastSeen;
|
|
||||||
const { contact_last_seen_at: contactLastSeenAt } = this.currentChat;
|
const { contact_last_seen_at: contactLastSeenAt } = this.currentChat;
|
||||||
return contactLastSeenAt;
|
return contactLastSeenAt;
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<template>
|
||||||
|
<div class="location message-text__wrap">
|
||||||
|
<div class="icon-wrap">
|
||||||
|
<fluent-icon icon="location" class="file--icon" size="32" />
|
||||||
|
</div>
|
||||||
|
<div class="meta">
|
||||||
|
<h5 class="text-block-title text-truncate">
|
||||||
|
{{ name }}
|
||||||
|
</h5>
|
||||||
|
<div class="link-wrap">
|
||||||
|
<a
|
||||||
|
class="download clear link button small"
|
||||||
|
rel="noreferrer noopener nofollow"
|
||||||
|
target="_blank"
|
||||||
|
:href="mapUrl"
|
||||||
|
>
|
||||||
|
{{ $t('COMPONENTS.LOCATION_BUBBLE.SEE_ON_MAP') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
latitude: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
longitude: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
mapUrl() {
|
||||||
|
return `https://maps.google.com/?q=${this.latitude},${this.longitude}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.location {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: var(--space-smaller) 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.icon-wrap {
|
||||||
|
color: var(--s-600);
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0 var(--space-smaller);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-block-title {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--s-800);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding-right: var(--space-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-wrap {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -17,7 +17,10 @@
|
||||||
@click="snoozeConversation(option.snoozedUntil)"
|
@click="snoozeConversation(option.snoozedUntil)"
|
||||||
/>
|
/>
|
||||||
</menu-item-with-submenu>
|
</menu-item-with-submenu>
|
||||||
<menu-item-with-submenu :option="labelMenuConfig">
|
<menu-item-with-submenu
|
||||||
|
:option="labelMenuConfig"
|
||||||
|
:sub-menu-available="!!labels.length"
|
||||||
|
>
|
||||||
<template>
|
<template>
|
||||||
<menu-item
|
<menu-item
|
||||||
v-for="label in labels"
|
v-for="label in labels"
|
||||||
|
@ -28,7 +31,10 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</menu-item-with-submenu>
|
</menu-item-with-submenu>
|
||||||
<menu-item-with-submenu :option="agentMenuConfig">
|
<menu-item-with-submenu
|
||||||
|
:option="agentMenuConfig"
|
||||||
|
:sub-menu-available="!!assignableAgents.length"
|
||||||
|
>
|
||||||
<agent-loading-placeholder v-if="assignableAgentsUiFlags.isFetching" />
|
<agent-loading-placeholder v-if="assignableAgentsUiFlags.isFetching" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<menu-item
|
<menu-item
|
||||||
|
@ -40,7 +46,10 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</menu-item-with-submenu>
|
</menu-item-with-submenu>
|
||||||
<menu-item-with-submenu :option="teamMenuConfig">
|
<menu-item-with-submenu
|
||||||
|
:option="teamMenuConfig"
|
||||||
|
:sub-menu-available="!!teams.length"
|
||||||
|
>
|
||||||
<menu-item
|
<menu-item
|
||||||
v-for="team in teams"
|
v-for="team in teams"
|
||||||
:key="team.id"
|
:key="team.id"
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
size="20px"
|
size="20px"
|
||||||
class="agent-thumbnail"
|
class="agent-thumbnail"
|
||||||
/>
|
/>
|
||||||
<p class="menu-label truncate-text">{{ option.label }}</p>
|
<p class="menu-label text-truncate">{{ option.label }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -50,7 +50,6 @@ export default {
|
||||||
padding: var(--space-smaller);
|
padding: var(--space-smaller);
|
||||||
border-radius: var(--border-radius-small);
|
border-radius: var(--border-radius-small);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.menu-label {
|
.menu-label {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: var(--font-size-mini);
|
font-size: var(--font-size-mini);
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="menu-with-submenu flex-between">
|
<div
|
||||||
|
class="menu-with-submenu flex-between"
|
||||||
|
:class="{ disabled: !subMenuAvailable }"
|
||||||
|
>
|
||||||
<div class="menu-left">
|
<div class="menu-left">
|
||||||
<fluent-icon :icon="option.icon" size="14" class="menu-icon" />
|
<fluent-icon :icon="option.icon" size="14" class="menu-icon" />
|
||||||
<p class="menu-label">{{ option.label }}</p>
|
<p class="menu-label">{{ option.label }}</p>
|
||||||
</div>
|
</div>
|
||||||
<fluent-icon icon="chevron-right" size="12" />
|
<fluent-icon icon="chevron-right" size="12" />
|
||||||
<div class="submenu">
|
<div v-if="subMenuAvailable" class="submenu">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,6 +21,10 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
|
subMenuAvailable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -55,6 +62,11 @@ export default {
|
||||||
left: 100%;
|
left: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
display: none;
|
display: none;
|
||||||
|
min-height: min-content;
|
||||||
|
max-height: var(--space-giga);
|
||||||
|
overflow-y: auto;
|
||||||
|
// Need this because Firefox adds a horizontal scrollbar, if a text is truncated inside.
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -73,5 +85,10 @@ export default {
|
||||||
clip-path: polygon(100% 0, 0% 0%, 100% 100%);
|
clip-path: polygon(100% 0, 0% 0%, 100% 100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 50%;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import LocationBubble from '../bubble/Location.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Help Center',
|
||||||
|
component: LocationBubble,
|
||||||
|
argTypes: {
|
||||||
|
latitude: {
|
||||||
|
defaultValue: 1,
|
||||||
|
control: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
longitude: {
|
||||||
|
defaultValue: 1,
|
||||||
|
control: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
defaultValue: '420, Dope street',
|
||||||
|
control: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template = (args, { argTypes }) => ({
|
||||||
|
props: Object.keys(argTypes),
|
||||||
|
components: { LocationBubble },
|
||||||
|
template: '<location-bubble v-bind="$props" />',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LocationBubbleView = Template.bind({});
|
|
@ -0,0 +1,69 @@
|
||||||
|
import ThumbnailGroup from '../ThumbnailGroup.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/ThumbnailGroup',
|
||||||
|
component: ThumbnailGroup,
|
||||||
|
argTypes: {
|
||||||
|
usersList: {
|
||||||
|
defaultValue: [
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
id: 1,
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
id: 2,
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
id: 3,
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
id: 4,
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
id: 5,
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'John',
|
||||||
|
id: 6,
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
control: {
|
||||||
|
type: 'object',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: {
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
moreThumbnailsText: {
|
||||||
|
control: {
|
||||||
|
type: 'text',
|
||||||
|
default: '2 more',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
showMoreThumbnailsCount: {
|
||||||
|
control: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template = (args, { argTypes }) => ({
|
||||||
|
props: Object.keys(argTypes),
|
||||||
|
components: { ThumbnailGroup },
|
||||||
|
template: '<ThumbnailGroup v-bind="$props"/>',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Primary = Template.bind({});
|
|
@ -25,6 +25,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
'notification.created': this.onNotificationCreated,
|
'notification.created': this.onNotificationCreated,
|
||||||
'first.reply.created': this.onFirstReplyCreated,
|
'first.reply.created': this.onFirstReplyCreated,
|
||||||
'conversation.read': this.onConversationRead,
|
'conversation.read': this.onConversationRead,
|
||||||
|
'conversation.updated': this.onConversationUpdated,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,8 +68,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
};
|
};
|
||||||
|
|
||||||
onConversationRead = data => {
|
onConversationRead = data => {
|
||||||
const { contact_last_seen_at: lastSeen } = data;
|
this.app.$store.dispatch('updateConversation', data);
|
||||||
this.app.$store.dispatch('updateConversationRead', lastSeen);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onLogout = () => AuthAPI.logout();
|
onLogout = () => AuthAPI.logout();
|
||||||
|
@ -85,6 +85,11 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||||
this.fetchConversationStats();
|
this.fetchConversationStats();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onConversationUpdated = data => {
|
||||||
|
this.app.$store.dispatch('updateConversation', data);
|
||||||
|
this.fetchConversationStats();
|
||||||
|
};
|
||||||
|
|
||||||
onTypingOn = ({ conversation, user }) => {
|
onTypingOn = ({ conversation, user }) => {
|
||||||
const conversationId = conversation.id;
|
const conversationId = conversation.id;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ const generatePayload = data => {
|
||||||
let payload = actions.map(item => {
|
let payload = actions.map(item => {
|
||||||
if (Array.isArray(item.action_params)) {
|
if (Array.isArray(item.action_params)) {
|
||||||
item.action_params = formatArray(item.action_params);
|
item.action_params = formatArray(item.action_params);
|
||||||
} else if (typeof item.values === 'object') {
|
} else if (typeof item.action_params === 'object') {
|
||||||
item.action_params = [item.action_params.id];
|
item.action_params = [item.action_params.id];
|
||||||
} else if (!item.action_params) {
|
} else if (!item.action_params) {
|
||||||
item.action_params = [];
|
item.action_params = [];
|
||||||
|
|
242
app/javascript/dashboard/helper/automationHelper.js
Normal file
242
app/javascript/dashboard/helper/automationHelper.js
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
import {
|
||||||
|
OPERATOR_TYPES_1,
|
||||||
|
OPERATOR_TYPES_3,
|
||||||
|
OPERATOR_TYPES_4,
|
||||||
|
} from 'dashboard/routes/dashboard/settings/automation/operators';
|
||||||
|
import filterQueryGenerator from './filterQueryGenerator';
|
||||||
|
import actionQueryGenerator from './actionQueryGenerator';
|
||||||
|
const MESSAGE_CONDITION_VALUES = [
|
||||||
|
{
|
||||||
|
id: 'incoming',
|
||||||
|
name: 'Incoming Message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'outgoing',
|
||||||
|
name: 'Outgoing Message',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const getCustomAttributeInputType = key => {
|
||||||
|
const customAttributeMap = {
|
||||||
|
date: 'date',
|
||||||
|
text: 'plain_text',
|
||||||
|
list: 'search_select',
|
||||||
|
checkbox: 'search_select',
|
||||||
|
};
|
||||||
|
|
||||||
|
return customAttributeMap[key] || 'plain_text';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isACustomAttribute = (customAttributes, key) => {
|
||||||
|
return customAttributes.find(attr => {
|
||||||
|
return attr.attribute_key === key;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCustomAttributeListDropdownValues = (
|
||||||
|
customAttributes,
|
||||||
|
type
|
||||||
|
) => {
|
||||||
|
return customAttributes
|
||||||
|
.find(attr => attr.attribute_key === type)
|
||||||
|
.attribute_values.map(item => {
|
||||||
|
return {
|
||||||
|
id: item,
|
||||||
|
name: item,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isCustomAttributeCheckbox = (customAttributes, key) => {
|
||||||
|
return customAttributes.find(attr => {
|
||||||
|
return (
|
||||||
|
attr.attribute_key === key && attr.attribute_display_type === 'checkbox'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isCustomAttributeList = (customAttributes, type) => {
|
||||||
|
return customAttributes.find(attr => {
|
||||||
|
return (
|
||||||
|
attr.attribute_key === type && attr.attribute_display_type === 'list'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getOperatorTypes = key => {
|
||||||
|
const operatorMap = {
|
||||||
|
list: OPERATOR_TYPES_1,
|
||||||
|
text: OPERATOR_TYPES_3,
|
||||||
|
number: OPERATOR_TYPES_1,
|
||||||
|
link: OPERATOR_TYPES_1,
|
||||||
|
date: OPERATOR_TYPES_4,
|
||||||
|
checkbox: OPERATOR_TYPES_1,
|
||||||
|
};
|
||||||
|
|
||||||
|
return operatorMap[key] || OPERATOR_TYPES_1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateCustomAttributeTypes = (customAttributes, type) => {
|
||||||
|
return customAttributes.map(attr => {
|
||||||
|
return {
|
||||||
|
key: attr.attribute_key,
|
||||||
|
name: attr.attribute_display_name,
|
||||||
|
inputType: getCustomAttributeInputType(attr.attribute_display_type),
|
||||||
|
filterOperators: getOperatorTypes(attr.attribute_display_type),
|
||||||
|
customAttributeType: type,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateConditionOptions = (options, key = 'id') => {
|
||||||
|
return options.map(i => {
|
||||||
|
return {
|
||||||
|
id: i[key],
|
||||||
|
name: i.title,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getActionOptions = ({ teams, labels, type }) => {
|
||||||
|
const actionsMap = {
|
||||||
|
assign_team: teams,
|
||||||
|
send_email_to_team: teams,
|
||||||
|
add_label: generateConditionOptions(labels, 'title'),
|
||||||
|
};
|
||||||
|
return actionsMap[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getConditionOptions = ({
|
||||||
|
agents,
|
||||||
|
booleanFilterOptions,
|
||||||
|
campaigns,
|
||||||
|
contacts,
|
||||||
|
countries,
|
||||||
|
customAttributes,
|
||||||
|
inboxes,
|
||||||
|
languages,
|
||||||
|
statusFilterOptions,
|
||||||
|
teams,
|
||||||
|
type,
|
||||||
|
}) => {
|
||||||
|
if (isCustomAttributeCheckbox(customAttributes, type)) {
|
||||||
|
return booleanFilterOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCustomAttributeList(customAttributes, type)) {
|
||||||
|
return getCustomAttributeListDropdownValues(customAttributes, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionFilterMaps = {
|
||||||
|
status: statusFilterOptions,
|
||||||
|
assignee_id: agents,
|
||||||
|
contact: contacts,
|
||||||
|
inbox_id: inboxes,
|
||||||
|
team_id: teams,
|
||||||
|
campaigns: generateConditionOptions(campaigns),
|
||||||
|
browser_language: languages,
|
||||||
|
country_code: countries,
|
||||||
|
message_type: MESSAGE_CONDITION_VALUES,
|
||||||
|
};
|
||||||
|
|
||||||
|
return conditionFilterMaps[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFileName = (action, files = []) => {
|
||||||
|
const blobId = action.action_params[0];
|
||||||
|
if (!blobId) return '';
|
||||||
|
if (action.action_name === 'send_attachment') {
|
||||||
|
const file = files.find(item => item.blob_id === blobId);
|
||||||
|
if (file) return file.filename.toString();
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDefaultConditions = eventName => {
|
||||||
|
if (eventName === 'message_created') {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
attribute_key: 'message_type',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
custom_attribute_type: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: '',
|
||||||
|
query_operator: 'and',
|
||||||
|
custom_attribute_type: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDefaultActions = () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filterCustomAttributes = customAttributes => {
|
||||||
|
return customAttributes.map(attr => {
|
||||||
|
return {
|
||||||
|
key: attr.attribute_key,
|
||||||
|
name: attr.attribute_display_name,
|
||||||
|
type: attr.attribute_display_type,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStandardAttributeInputType = (automationTypes, event, key) => {
|
||||||
|
return automationTypes[event].conditions.find(item => item.key === key)
|
||||||
|
.inputType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateAutomationPayload = payload => {
|
||||||
|
const automation = JSON.parse(JSON.stringify(payload));
|
||||||
|
automation.conditions[automation.conditions.length - 1].query_operator = null;
|
||||||
|
automation.conditions = filterQueryGenerator(automation.conditions).payload;
|
||||||
|
automation.actions = actionQueryGenerator(automation.actions);
|
||||||
|
return automation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isCustomAttribute = (attrs, key) => {
|
||||||
|
return attrs.find(attr => attr.key === key);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateCustomAttributes = (
|
||||||
|
conversationAttributes = [],
|
||||||
|
contactAttribtues = [],
|
||||||
|
conversationlabel,
|
||||||
|
contactlabel
|
||||||
|
) => {
|
||||||
|
const customAttributes = [];
|
||||||
|
if (conversationAttributes.length) {
|
||||||
|
customAttributes.push(
|
||||||
|
{
|
||||||
|
key: `conversation_custom_attribute`,
|
||||||
|
name: conversationlabel,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
...conversationAttributes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (contactAttribtues.length) {
|
||||||
|
customAttributes.push(
|
||||||
|
{
|
||||||
|
key: `contact_custom_attribute`,
|
||||||
|
name: contactlabel,
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
...contactAttribtues
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return customAttributes;
|
||||||
|
};
|
|
@ -1,15 +1,3 @@
|
||||||
const lowerCaseValues = (operator, values) => {
|
|
||||||
if (operator === 'equal_to' || operator === 'not_equal_to') {
|
|
||||||
values = values.map(val => {
|
|
||||||
if (typeof val === 'string') {
|
|
||||||
return val.toLowerCase();
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generatePayload = data => {
|
const generatePayload = data => {
|
||||||
// Make a copy of data to avoid vue data reactivity issues
|
// Make a copy of data to avoid vue data reactivity issues
|
||||||
const filters = JSON.parse(JSON.stringify(data));
|
const filters = JSON.parse(JSON.stringify(data));
|
||||||
|
@ -23,8 +11,6 @@ const generatePayload = data => {
|
||||||
} else {
|
} else {
|
||||||
item.values = [item.values];
|
item.values = [item.values];
|
||||||
}
|
}
|
||||||
// Convert all values to lowerCase if operator_type is 'equal_to' or 'not_equal_to'
|
|
||||||
item.values = lowerCaseValues(item.filter_operator, item.values);
|
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
// For every query added, the query_operator is set default to and so the
|
// For every query added, the query_operator is set default to and so the
|
||||||
|
|
|
@ -5,7 +5,7 @@ const testData = [
|
||||||
attribute_key: 'status',
|
attribute_key: 'status',
|
||||||
filter_operator: 'equal_to',
|
filter_operator: 'equal_to',
|
||||||
values: [
|
values: [
|
||||||
{ id: 'PENDING', name: 'Pending' },
|
{ id: 'pending', name: 'Pending' },
|
||||||
{ id: 'resolved', name: 'Resolved' },
|
{ id: 'resolved', name: 'Resolved' },
|
||||||
],
|
],
|
||||||
query_operator: 'and',
|
query_operator: 'and',
|
||||||
|
@ -18,7 +18,7 @@ const testData = [
|
||||||
account_id: 1,
|
account_id: 1,
|
||||||
auto_offline: true,
|
auto_offline: true,
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
email: 'fayazara@gmail.com',
|
email: 'fayaz@test.com',
|
||||||
available_name: 'Fayaz',
|
available_name: 'Fayaz',
|
||||||
name: 'Fayaz',
|
name: 'Fayaz',
|
||||||
role: 'agent',
|
role: 'agent',
|
||||||
|
@ -52,7 +52,7 @@ const finalResult = {
|
||||||
{
|
{
|
||||||
attribute_key: 'id',
|
attribute_key: 'id',
|
||||||
filter_operator: 'equal_to',
|
filter_operator: 'equal_to',
|
||||||
values: ['this is a test'],
|
values: ['This is a test'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
122
app/javascript/dashboard/helper/specs/macrosFixtures.js
Normal file
122
app/javascript/dashboard/helper/specs/macrosFixtures.js
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
export const teams = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: '⚙️ sales team',
|
||||||
|
description: 'This is our internal sales team',
|
||||||
|
allow_auto_assign: true,
|
||||||
|
account_id: 1,
|
||||||
|
is_member: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '🤷♂️ fayaz',
|
||||||
|
description: 'Test',
|
||||||
|
allow_auto_assign: true,
|
||||||
|
account_id: 1,
|
||||||
|
is_member: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: '🇮🇳 apac sales',
|
||||||
|
description: 'Sales team for France Territory',
|
||||||
|
allow_auto_assign: true,
|
||||||
|
account_id: 1,
|
||||||
|
is_member: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const labels = [
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
title: 'sales',
|
||||||
|
description: 'sales team',
|
||||||
|
color: '#8EA20F',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: 'billing',
|
||||||
|
description: 'billing',
|
||||||
|
color: '#4077DA',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: 'snoozed',
|
||||||
|
description: 'Items marked for later',
|
||||||
|
color: '#D12F42',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
title: 'mobile-app',
|
||||||
|
description: 'tech team',
|
||||||
|
color: '#2DB1CC',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 14,
|
||||||
|
title: 'human-resources-department-with-long-title',
|
||||||
|
description: 'Test',
|
||||||
|
color: '#FF6E09',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 22,
|
||||||
|
title: 'priority',
|
||||||
|
description: 'For important sales leads',
|
||||||
|
color: '#7E7CED',
|
||||||
|
show_on_sidebar: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const agents = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
account_id: 1,
|
||||||
|
availability_status: 'offline',
|
||||||
|
auto_offline: true,
|
||||||
|
confirmed: true,
|
||||||
|
email: 'john@doe.com',
|
||||||
|
available_name: 'John Doe',
|
||||||
|
name: 'John Doe',
|
||||||
|
role: 'agent',
|
||||||
|
thumbnail:
|
||||||
|
'http://localhost:3000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBUZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--746506837470c1a3dd063e90211ba2386963d52f/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJY0c1bkJqb0dSVlE2QzNKbGMybDZaVWtpRERJMU1IZ3lOVEFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e0e35266e8ed66e90c51be02408be8a022aca545/batman_90804.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
account_id: 1,
|
||||||
|
availability_status: 'offline',
|
||||||
|
auto_offline: true,
|
||||||
|
confirmed: true,
|
||||||
|
email: 'clark@kent.com',
|
||||||
|
available_name: 'Clark Kent',
|
||||||
|
name: 'Clark Kent',
|
||||||
|
role: 'agent',
|
||||||
|
thumbnail: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const files = [
|
||||||
|
{
|
||||||
|
id: 76,
|
||||||
|
macro_id: 77,
|
||||||
|
file_type: 'image/jpeg',
|
||||||
|
account_id: 1,
|
||||||
|
file_url:
|
||||||
|
'http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBYUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--aa41b5a779a83c1d86b28475a5cf0bd17f41f0ff/fayaz_cropped.jpeg',
|
||||||
|
blob_id: 88,
|
||||||
|
filename: 'fayaz_cropped.jpeg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 82,
|
||||||
|
macro_id: 77,
|
||||||
|
file_type: 'image/png',
|
||||||
|
account_id: 1,
|
||||||
|
file_url:
|
||||||
|
'http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBZdz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--260fda80b77409ffaaac10b96681fba447600545/screenshot.png',
|
||||||
|
blob_id: 94,
|
||||||
|
filename: 'screenshot.png',
|
||||||
|
},
|
||||||
|
];
|
75
app/javascript/dashboard/helper/specs/macrosHelper.spec.js
Normal file
75
app/javascript/dashboard/helper/specs/macrosHelper.spec.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import {
|
||||||
|
emptyMacro,
|
||||||
|
resolveActionName,
|
||||||
|
resolveLabels,
|
||||||
|
resolveTeamIds,
|
||||||
|
getFileName,
|
||||||
|
resolveAgents,
|
||||||
|
} from '../../routes/dashboard/settings/macros/macroHelper';
|
||||||
|
import { MACRO_ACTION_TYPES } from '../../routes/dashboard/settings/macros/constants';
|
||||||
|
import { teams, labels, files, agents } from './macrosFixtures';
|
||||||
|
|
||||||
|
describe('#emptyMacro', () => {
|
||||||
|
const defaultMacro = {
|
||||||
|
name: '',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
visibility: 'global',
|
||||||
|
};
|
||||||
|
it('returns the default macro', () => {
|
||||||
|
expect(emptyMacro).toEqual(defaultMacro);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#resolveActionName', () => {
|
||||||
|
it('resolve action name from key and return the correct label', () => {
|
||||||
|
expect(resolveActionName(MACRO_ACTION_TYPES[0].key)).toEqual(
|
||||||
|
MACRO_ACTION_TYPES[0].label
|
||||||
|
);
|
||||||
|
expect(resolveActionName(MACRO_ACTION_TYPES[1].key)).toEqual(
|
||||||
|
MACRO_ACTION_TYPES[1].label
|
||||||
|
);
|
||||||
|
expect(resolveActionName(MACRO_ACTION_TYPES[1].key)).not.toEqual(
|
||||||
|
MACRO_ACTION_TYPES[0].label
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#resolveTeamIds', () => {
|
||||||
|
it('resolves team names from ids, and returns a joined string', () => {
|
||||||
|
const resolvedTeams = '⚙️ sales team, 🤷♂️ fayaz';
|
||||||
|
expect(resolveTeamIds(teams, [1, 2])).toEqual(resolvedTeams);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#resolveLabels', () => {
|
||||||
|
it('resolves labels names from ids and returns a joined string', () => {
|
||||||
|
const resolvedLabels = 'sales, billing';
|
||||||
|
expect(resolveLabels(labels, ['sales', 'billing'])).toEqual(resolvedLabels);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#resolveAgents', () => {
|
||||||
|
it('resolves agents names from ids and returns a joined string', () => {
|
||||||
|
const resolvedAgents = 'John Doe';
|
||||||
|
expect(resolveAgents(agents, [1])).toEqual(resolvedAgents);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#getFileName', () => {
|
||||||
|
it('returns the correct file name from the list of files', () => {
|
||||||
|
expect(getFileName(files[0].blob_id, 'send_attachment', files)).toEqual(
|
||||||
|
files[0].filename
|
||||||
|
);
|
||||||
|
expect(getFileName(files[1].blob_id, 'send_attachment', files)).toEqual(
|
||||||
|
files[1].filename
|
||||||
|
);
|
||||||
|
expect(getFileName(files[0].blob_id, 'wrong_action', files)).toEqual('');
|
||||||
|
expect(getFileName(null, 'send_attachment', files)).toEqual('');
|
||||||
|
expect(getFileName(files[0].blob_id, 'send_attachment', [])).toEqual('');
|
||||||
|
});
|
||||||
|
});
|
5
app/javascript/dashboard/i18n/locale/ar/agentBots.json
Normal file
5
app/javascript/dashboard/i18n/locale/ar/agentBots.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"AGENT_BOTS": {
|
||||||
|
"HEADER": "Bots"
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,7 +86,9 @@
|
||||||
"RESET_MESSAGE": "تغيير نوع الحدث سوف يعيد تعيين الشروط والأحداث التي أضفتها أدناه"
|
"RESET_MESSAGE": "تغيير نوع الحدث سوف يعيد تعيين الشروط والأحداث التي أضفتها أدناه"
|
||||||
},
|
},
|
||||||
"CONDITION": {
|
"CONDITION": {
|
||||||
"DELETE_MESSAGE": "يجب أن يكون لديك على الأقل شرط واحد للحفظ"
|
"DELETE_MESSAGE": "يجب أن يكون لديك على الأقل شرط واحد للحفظ",
|
||||||
|
"CONTACT_CUSTOM_ATTR_LABEL": "Contact Custom Attributes",
|
||||||
|
"CONVERSATION_CUSTOM_ATTR_LABEL": "Conversation Custom Attributes"
|
||||||
},
|
},
|
||||||
"ACTION": {
|
"ACTION": {
|
||||||
"DELETE_MESSAGE": "يجب أن يكون لديك على الأقل شرط واحد للحفظ",
|
"DELETE_MESSAGE": "يجب أن يكون لديك على الأقل شرط واحد للحفظ",
|
||||||
|
@ -109,7 +111,7 @@
|
||||||
"UPLOAD_ERROR": "تعذر تحميل المرفق، الرجاء المحاولة مرة أخرى",
|
"UPLOAD_ERROR": "تعذر تحميل المرفق، الرجاء المحاولة مرة أخرى",
|
||||||
"LABEL_IDLE": "ارفع المرفق",
|
"LABEL_IDLE": "ارفع المرفق",
|
||||||
"LABEL_UPLOADING": "جاري الرفع...",
|
"LABEL_UPLOADING": "جاري الرفع...",
|
||||||
"LABEL_UPLOADED": "تم الرفع بنجاح",
|
"LABEL_UPLOADED": "Successfully Uploaded",
|
||||||
"LABEL_UPLOAD_FAILED": "فشل الرفع"
|
"LABEL_UPLOAD_FAILED": "فشل الرفع"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
"BULK_ACTION": {
|
"BULK_ACTION": {
|
||||||
"CONVERSATIONS_SELECTED": "%{conversationCount} المحادثات المحددة",
|
"CONVERSATIONS_SELECTED": "%{conversationCount} المحادثات المحددة",
|
||||||
"AGENT_SELECT_LABEL": "اختر وكيل",
|
"AGENT_SELECT_LABEL": "اختر وكيل",
|
||||||
"ASSIGN_CONFIRMATION_LABEL": "هل أنت متأكد من أنك تريد تعيين %{conversationCount} %{conversationLabel} إلى",
|
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
|
||||||
|
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
|
||||||
"GO_BACK_LABEL": "العودة للخلف",
|
"GO_BACK_LABEL": "العودة للخلف",
|
||||||
"ASSIGN_LABEL": "تكليف",
|
"ASSIGN_LABEL": "تكليف",
|
||||||
|
"YES": "نعم",
|
||||||
"ASSIGN_AGENT_TOOLTIP": "إسناد وكيل",
|
"ASSIGN_AGENT_TOOLTIP": "إسناد وكيل",
|
||||||
"ASSIGN_SUCCESFUL": "تم تعيين المحادثات بنجاح",
|
"ASSIGN_SUCCESFUL": "تم تعيين المحادثات بنجاح",
|
||||||
"ASSIGN_FAILED": "فشل في تعيين المحادثات، الرجاء المحاولة مرة أخرى",
|
"ASSIGN_FAILED": "فشل في تعيين المحادثات، الرجاء المحاولة مرة أخرى",
|
||||||
|
|
|
@ -208,7 +208,8 @@
|
||||||
"CONVERSATION_LABELS": "وسوم المحادثة",
|
"CONVERSATION_LABELS": "وسوم المحادثة",
|
||||||
"CONVERSATION_INFO": "معلومات المحادثة",
|
"CONVERSATION_INFO": "معلومات المحادثة",
|
||||||
"CONTACT_ATTRIBUTES": "سمات جهة الاتصال",
|
"CONTACT_ATTRIBUTES": "سمات جهة الاتصال",
|
||||||
"PREVIOUS_CONVERSATION": "المحادثات السابقة"
|
"PREVIOUS_CONVERSATION": "المحادثات السابقة",
|
||||||
|
"MACROS": "Macros"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
||||||
|
|
|
@ -54,7 +54,8 @@
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
"ENTER_TO_SELECT": "اضغط على زر الإدخال للاختيار",
|
"ENTER_TO_SELECT": "اضغط على زر الإدخال للاختيار",
|
||||||
"ENTER_TO_REMOVE": "اضغط على زر الإدخال للحذف",
|
"ENTER_TO_REMOVE": "اضغط على زر الإدخال للحذف",
|
||||||
"SELECT_ONE": "اختر واحدا"
|
"SELECT_ONE": "اختر واحدا",
|
||||||
|
"SELECT": "Select"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"NOTIFICATIONS_PAGE": {
|
"NOTIFICATIONS_PAGE": {
|
||||||
|
@ -136,5 +137,8 @@
|
||||||
"UNTIL_NEXT_WEEK": "حتى الأسبوع القادم",
|
"UNTIL_NEXT_WEEK": "حتى الأسبوع القادم",
|
||||||
"UNTIL_TOMORROW": "حتى الغد"
|
"UNTIL_TOMORROW": "حتى الغد"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"DASHBOARD_APPS": {
|
||||||
|
"LOADING_MESSAGE": "Loading Dashboard App..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,14 +217,14 @@
|
||||||
"DOMAIN": {
|
"DOMAIN": {
|
||||||
"LABEL": "نطاق مخصص",
|
"LABEL": "نطاق مخصص",
|
||||||
"PLACEHOLDER": "نطاق البوابة المخصص",
|
"PLACEHOLDER": "نطاق البوابة المخصص",
|
||||||
"HELP_TEXT": "أضف فقط إذا كنت ترغب في استخدام نطاق مخصص للبوابات الخاصة بك.",
|
"HELP_TEXT": "Add only If you want to use a custom domain for your portals. Eg: https://example.com",
|
||||||
"ERROR": "النطاق المخصص مطلوب"
|
"ERROR": "Enter a valid domain URL"
|
||||||
},
|
},
|
||||||
"HOME_PAGE_LINK": {
|
"HOME_PAGE_LINK": {
|
||||||
"LABEL": "رابط الصفحة الرئيسية",
|
"LABEL": "رابط الصفحة الرئيسية",
|
||||||
"PLACEHOLDER": "رابط الصفحة الرئيسية للبوابة",
|
"PLACEHOLDER": "رابط الصفحة الرئيسية للبوابة",
|
||||||
"HELP_TEXT": "الرابط المستخدم للعودة من البوابة إلى الصفحة الرئيسية.",
|
"HELP_TEXT": "The link used to return from the portal to the home page. Eg: https://example.com",
|
||||||
"ERROR": "رابط الصفحة الرئيسية مطلوب"
|
"ERROR": "Enter a valid home page URL"
|
||||||
},
|
},
|
||||||
"THEME_COLOR": {
|
"THEME_COLOR": {
|
||||||
"LABEL": "لون قالب البوابة",
|
"LABEL": "لون قالب البوابة",
|
||||||
|
@ -306,7 +306,7 @@
|
||||||
"PUBLISH_ARTICLE": {
|
"PUBLISH_ARTICLE": {
|
||||||
"API": {
|
"API": {
|
||||||
"ERROR": "حدث خطأ أثناء نشر المقالة",
|
"ERROR": "حدث خطأ أثناء نشر المقالة",
|
||||||
"SUCCESS": "تم نشر المقالة بنجاح"
|
"SUCCESS": "Article published successfully"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ARCHIVE_ARTICLE": {
|
"ARCHIVE_ARTICLE": {
|
||||||
|
|
|
@ -239,7 +239,9 @@
|
||||||
},
|
},
|
||||||
"API_CALLBACK": {
|
"API_CALLBACK": {
|
||||||
"TITLE": "عنوان Callback URL",
|
"TITLE": "عنوان Callback URL",
|
||||||
"SUBTITLE": "يجب عليك تكوين URL webhook في بوابة مطور فيسبوك مع عنوان URL المذكور هنا."
|
"SUBTITLE": "You have to configure the webhook URL and the verification token in the Facebook Developer portal with the values shown below.",
|
||||||
|
"WEBHOOK_URL": "رابط Webhook",
|
||||||
|
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
|
||||||
},
|
},
|
||||||
"SUBMIT_BUTTON": "إنشاء قناة واتساب",
|
"SUBMIT_BUTTON": "إنشاء قناة واتساب",
|
||||||
"API": {
|
"API": {
|
||||||
|
|
78
app/javascript/dashboard/i18n/locale/ar/macros.json
Normal file
78
app/javascript/dashboard/i18n/locale/ar/macros.json
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"MACROS": {
|
||||||
|
"HEADER": "Macros",
|
||||||
|
"HEADER_BTN_TXT": "Add a new macro",
|
||||||
|
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||||
|
"LOADING": "Fetching macros",
|
||||||
|
"SIDEBAR_TXT": "<p><b>Macros</b><p>A macro is a set of saved actions that help customer service agents easily complete tasks. The agents can define a set of actions like tagging a conversation with a label, sending an email transcript, updating a custom attribute, etc., and they can run these actions in a single click. When the agents run the macro, the actions would be performed sequentially in the order they are defined. Macros improve productivity and increase consistency in actions. </p><p>A macro can be helpful in 2 ways. </p><p><b>As an agent assist:</b> If an agent performs a set of actions multiple times, they can save it as a macro and execute all the actions together using a single click.</p><p><b>As an option to onboard a team member:</b> Every agent has to perform many different checks/actions during each conversation. Onboarding a new support team member will be easy if pre-defined macros are available on the account. Instead of describing each step in detail, the manager/team lead can point to the macros used in different scenarios.</p>",
|
||||||
|
"ERROR": "Something went wrong. Please try again",
|
||||||
|
"ORDER_INFO": "Macros will run in the order you add your actions. You can rearrange them by dragging them by the handle beside each node.",
|
||||||
|
"ADD": {
|
||||||
|
"FORM": {
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "Macro name",
|
||||||
|
"PLACEHOLDER": "Enter a name for your macro",
|
||||||
|
"ERROR": "Name is required for creating a macro"
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"LABEL": "الإجراءات"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro added successfully",
|
||||||
|
"ERROR_MESSAGE": "Unable to create macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LIST": {
|
||||||
|
"TABLE_HEADER": [
|
||||||
|
"الاسم",
|
||||||
|
"Created by",
|
||||||
|
"Last updated by",
|
||||||
|
"Visibility"
|
||||||
|
],
|
||||||
|
"404": "No macros found"
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"TOOLTIP": "Delete macro",
|
||||||
|
"CONFIRM": {
|
||||||
|
"MESSAGE": "هل أنت متأكد من الحذف ",
|
||||||
|
"YES": "نعم، احذف",
|
||||||
|
"NO": "لا"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "There was an error deleting the macro. Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TOOLTIP": "Edit macro",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not update Macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDITOR": {
|
||||||
|
"START_FLOW": "Start Flow",
|
||||||
|
"END_FLOW": "End Flow",
|
||||||
|
"LOADING": "Fetching macro",
|
||||||
|
"ADD_BTN_TOOLTIP": "Add new action",
|
||||||
|
"DELETE_BTN_TOOLTIP": "Delete Action",
|
||||||
|
"VISIBILITY": {
|
||||||
|
"LABEL": "Macro Visibility",
|
||||||
|
"GLOBAL": {
|
||||||
|
"LABEL": "Public",
|
||||||
|
"DESCRIPTION": "This macro is available publicly for all agents in this account."
|
||||||
|
},
|
||||||
|
"PERSONAL": {
|
||||||
|
"LABEL": "Private",
|
||||||
|
"DESCRIPTION": "This macro will be private to you and not be available to others."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EXECUTE": {
|
||||||
|
"BUTTON_TOOLTIP": "Execute",
|
||||||
|
"PREVIEW": "Preview Macro",
|
||||||
|
"EXECUTED_SUCCESSFULLY": "Macro executed successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -158,6 +158,9 @@
|
||||||
"DOWNLOAD": "تنزيل",
|
"DOWNLOAD": "تنزيل",
|
||||||
"UPLOADING": "جاري الرفع..."
|
"UPLOADING": "جاري الرفع..."
|
||||||
},
|
},
|
||||||
|
"LOCATION_BUBBLE": {
|
||||||
|
"SEE_ON_MAP": "See on map"
|
||||||
|
},
|
||||||
"FORM_BUBBLE": {
|
"FORM_BUBBLE": {
|
||||||
"SUBMIT": "إرسال"
|
"SUBMIT": "إرسال"
|
||||||
}
|
}
|
||||||
|
@ -179,6 +182,7 @@
|
||||||
"CONTACTS": "جهات الاتصال",
|
"CONTACTS": "جهات الاتصال",
|
||||||
"HOME": "الرئيسية",
|
"HOME": "الرئيسية",
|
||||||
"AGENTS": "موظف الدعم",
|
"AGENTS": "موظف الدعم",
|
||||||
|
"AGENT_BOTS": "Bots",
|
||||||
"INBOXES": "قنوات التواصل",
|
"INBOXES": "قنوات التواصل",
|
||||||
"NOTIFICATIONS": "الإشعارات",
|
"NOTIFICATIONS": "الإشعارات",
|
||||||
"CANNED_RESPONSES": "الردود السريعة",
|
"CANNED_RESPONSES": "الردود السريعة",
|
||||||
|
@ -189,6 +193,7 @@
|
||||||
"LABELS": "الوسوم",
|
"LABELS": "الوسوم",
|
||||||
"CUSTOM_ATTRIBUTES": "سمات مخصصة",
|
"CUSTOM_ATTRIBUTES": "سمات مخصصة",
|
||||||
"AUTOMATION": "الأتمتة",
|
"AUTOMATION": "الأتمتة",
|
||||||
|
"MACROS": "Macros",
|
||||||
"TEAMS": "الفرق",
|
"TEAMS": "الفرق",
|
||||||
"BILLING": "الفواتير",
|
"BILLING": "الفواتير",
|
||||||
"CUSTOM_VIEWS_FOLDER": "المجلدات",
|
"CUSTOM_VIEWS_FOLDER": "المجلدات",
|
||||||
|
|
5
app/javascript/dashboard/i18n/locale/bg/agentBots.json
Normal file
5
app/javascript/dashboard/i18n/locale/bg/agentBots.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"AGENT_BOTS": {
|
||||||
|
"HEADER": "Bots"
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,7 +86,9 @@
|
||||||
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||||
},
|
},
|
||||||
"CONDITION": {
|
"CONDITION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one condition to save"
|
"DELETE_MESSAGE": "You need to have atleast one condition to save",
|
||||||
|
"CONTACT_CUSTOM_ATTR_LABEL": "Contact Custom Attributes",
|
||||||
|
"CONVERSATION_CUSTOM_ATTR_LABEL": "Conversation Custom Attributes"
|
||||||
},
|
},
|
||||||
"ACTION": {
|
"ACTION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
||||||
|
@ -109,7 +111,7 @@
|
||||||
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
||||||
"LABEL_IDLE": "Upload Attachment",
|
"LABEL_IDLE": "Upload Attachment",
|
||||||
"LABEL_UPLOADING": "Качване...",
|
"LABEL_UPLOADING": "Качване...",
|
||||||
"LABEL_UPLOADED": "Succesfully Uploaded",
|
"LABEL_UPLOADED": "Successfully Uploaded",
|
||||||
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
"BULK_ACTION": {
|
"BULK_ACTION": {
|
||||||
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
||||||
"AGENT_SELECT_LABEL": "Select Agent",
|
"AGENT_SELECT_LABEL": "Select Agent",
|
||||||
"ASSIGN_CONFIRMATION_LABEL": "Are you sure you want to assign %{conversationCount} %{conversationLabel} to",
|
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
|
||||||
|
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
|
||||||
"GO_BACK_LABEL": "Go back",
|
"GO_BACK_LABEL": "Go back",
|
||||||
"ASSIGN_LABEL": "Assign",
|
"ASSIGN_LABEL": "Assign",
|
||||||
|
"YES": "Yes",
|
||||||
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
||||||
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
||||||
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
||||||
|
|
|
@ -208,7 +208,8 @@
|
||||||
"CONVERSATION_LABELS": "Етикети на разговора",
|
"CONVERSATION_LABELS": "Етикети на разговора",
|
||||||
"CONVERSATION_INFO": "Conversation Information",
|
"CONVERSATION_INFO": "Conversation Information",
|
||||||
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
||||||
"PREVIOUS_CONVERSATION": "Предишни разговори"
|
"PREVIOUS_CONVERSATION": "Предишни разговори",
|
||||||
|
"MACROS": "Macros"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
||||||
|
|
|
@ -54,7 +54,8 @@
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
"ENTER_TO_SELECT": "Press enter to select",
|
"ENTER_TO_SELECT": "Press enter to select",
|
||||||
"ENTER_TO_REMOVE": "Press enter to remove",
|
"ENTER_TO_REMOVE": "Press enter to remove",
|
||||||
"SELECT_ONE": "Select one"
|
"SELECT_ONE": "Select one",
|
||||||
|
"SELECT": "Select"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"NOTIFICATIONS_PAGE": {
|
"NOTIFICATIONS_PAGE": {
|
||||||
|
@ -136,5 +137,8 @@
|
||||||
"UNTIL_NEXT_WEEK": "Until next week",
|
"UNTIL_NEXT_WEEK": "Until next week",
|
||||||
"UNTIL_TOMORROW": "Until tomorrow"
|
"UNTIL_TOMORROW": "Until tomorrow"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"DASHBOARD_APPS": {
|
||||||
|
"LOADING_MESSAGE": "Loading Dashboard App..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,14 +217,14 @@
|
||||||
"DOMAIN": {
|
"DOMAIN": {
|
||||||
"LABEL": "Custom Domain",
|
"LABEL": "Custom Domain",
|
||||||
"PLACEHOLDER": "Portal custom domain",
|
"PLACEHOLDER": "Portal custom domain",
|
||||||
"HELP_TEXT": "Add only If you want to use a custom domain for your portals.",
|
"HELP_TEXT": "Add only If you want to use a custom domain for your portals. Eg: https://example.com",
|
||||||
"ERROR": "Custom Domain is required"
|
"ERROR": "Enter a valid domain URL"
|
||||||
},
|
},
|
||||||
"HOME_PAGE_LINK": {
|
"HOME_PAGE_LINK": {
|
||||||
"LABEL": "Home Page Link",
|
"LABEL": "Home Page Link",
|
||||||
"PLACEHOLDER": "Portal home page link",
|
"PLACEHOLDER": "Portal home page link",
|
||||||
"HELP_TEXT": "The link used to return from the portal to the home page.",
|
"HELP_TEXT": "The link used to return from the portal to the home page. Eg: https://example.com",
|
||||||
"ERROR": "Home Page Link is required"
|
"ERROR": "Enter a valid home page URL"
|
||||||
},
|
},
|
||||||
"THEME_COLOR": {
|
"THEME_COLOR": {
|
||||||
"LABEL": "Portal theme color",
|
"LABEL": "Portal theme color",
|
||||||
|
@ -306,7 +306,7 @@
|
||||||
"PUBLISH_ARTICLE": {
|
"PUBLISH_ARTICLE": {
|
||||||
"API": {
|
"API": {
|
||||||
"ERROR": "Error while publishing article",
|
"ERROR": "Error while publishing article",
|
||||||
"SUCCESS": "Article publishied successfully"
|
"SUCCESS": "Article published successfully"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ARCHIVE_ARTICLE": {
|
"ARCHIVE_ARTICLE": {
|
||||||
|
|
|
@ -239,7 +239,9 @@
|
||||||
},
|
},
|
||||||
"API_CALLBACK": {
|
"API_CALLBACK": {
|
||||||
"TITLE": "Callback URL",
|
"TITLE": "Callback URL",
|
||||||
"SUBTITLE": "You have to configure the webhook URL in facebook developer portal with the URL mentioned here."
|
"SUBTITLE": "You have to configure the webhook URL and the verification token in the Facebook Developer portal with the values shown below.",
|
||||||
|
"WEBHOOK_URL": "Webhook URL",
|
||||||
|
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
|
||||||
},
|
},
|
||||||
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
||||||
"API": {
|
"API": {
|
||||||
|
|
78
app/javascript/dashboard/i18n/locale/bg/macros.json
Normal file
78
app/javascript/dashboard/i18n/locale/bg/macros.json
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"MACROS": {
|
||||||
|
"HEADER": "Macros",
|
||||||
|
"HEADER_BTN_TXT": "Add a new macro",
|
||||||
|
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||||
|
"LOADING": "Fetching macros",
|
||||||
|
"SIDEBAR_TXT": "<p><b>Macros</b><p>A macro is a set of saved actions that help customer service agents easily complete tasks. The agents can define a set of actions like tagging a conversation with a label, sending an email transcript, updating a custom attribute, etc., and they can run these actions in a single click. When the agents run the macro, the actions would be performed sequentially in the order they are defined. Macros improve productivity and increase consistency in actions. </p><p>A macro can be helpful in 2 ways. </p><p><b>As an agent assist:</b> If an agent performs a set of actions multiple times, they can save it as a macro and execute all the actions together using a single click.</p><p><b>As an option to onboard a team member:</b> Every agent has to perform many different checks/actions during each conversation. Onboarding a new support team member will be easy if pre-defined macros are available on the account. Instead of describing each step in detail, the manager/team lead can point to the macros used in different scenarios.</p>",
|
||||||
|
"ERROR": "Something went wrong. Please try again",
|
||||||
|
"ORDER_INFO": "Macros will run in the order you add your actions. You can rearrange them by dragging them by the handle beside each node.",
|
||||||
|
"ADD": {
|
||||||
|
"FORM": {
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "Macro name",
|
||||||
|
"PLACEHOLDER": "Enter a name for your macro",
|
||||||
|
"ERROR": "Name is required for creating a macro"
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"LABEL": "Действия"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro added successfully",
|
||||||
|
"ERROR_MESSAGE": "Unable to create macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LIST": {
|
||||||
|
"TABLE_HEADER": [
|
||||||
|
"Име",
|
||||||
|
"Created by",
|
||||||
|
"Last updated by",
|
||||||
|
"Visibility"
|
||||||
|
],
|
||||||
|
"404": "No macros found"
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"TOOLTIP": "Delete macro",
|
||||||
|
"CONFIRM": {
|
||||||
|
"MESSAGE": "Сигурни ли сте за изтриването ",
|
||||||
|
"YES": "Да, изтрий",
|
||||||
|
"NO": "No"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "There was an error deleting the macro. Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TOOLTIP": "Edit macro",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not update Macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDITOR": {
|
||||||
|
"START_FLOW": "Start Flow",
|
||||||
|
"END_FLOW": "End Flow",
|
||||||
|
"LOADING": "Fetching macro",
|
||||||
|
"ADD_BTN_TOOLTIP": "Add new action",
|
||||||
|
"DELETE_BTN_TOOLTIP": "Delete Action",
|
||||||
|
"VISIBILITY": {
|
||||||
|
"LABEL": "Macro Visibility",
|
||||||
|
"GLOBAL": {
|
||||||
|
"LABEL": "Public",
|
||||||
|
"DESCRIPTION": "This macro is available publicly for all agents in this account."
|
||||||
|
},
|
||||||
|
"PERSONAL": {
|
||||||
|
"LABEL": "Private",
|
||||||
|
"DESCRIPTION": "This macro will be private to you and not be available to others."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EXECUTE": {
|
||||||
|
"BUTTON_TOOLTIP": "Execute",
|
||||||
|
"PREVIEW": "Preview Macro",
|
||||||
|
"EXECUTED_SUCCESSFULLY": "Macro executed successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -158,6 +158,9 @@
|
||||||
"DOWNLOAD": "Download",
|
"DOWNLOAD": "Download",
|
||||||
"UPLOADING": "Качване..."
|
"UPLOADING": "Качване..."
|
||||||
},
|
},
|
||||||
|
"LOCATION_BUBBLE": {
|
||||||
|
"SEE_ON_MAP": "See on map"
|
||||||
|
},
|
||||||
"FORM_BUBBLE": {
|
"FORM_BUBBLE": {
|
||||||
"SUBMIT": "Изпращане"
|
"SUBMIT": "Изпращане"
|
||||||
}
|
}
|
||||||
|
@ -179,6 +182,7 @@
|
||||||
"CONTACTS": "Контакти",
|
"CONTACTS": "Контакти",
|
||||||
"HOME": "Home",
|
"HOME": "Home",
|
||||||
"AGENTS": "Агенти",
|
"AGENTS": "Агенти",
|
||||||
|
"AGENT_BOTS": "Bots",
|
||||||
"INBOXES": "Inboxes",
|
"INBOXES": "Inboxes",
|
||||||
"NOTIFICATIONS": "Notifications",
|
"NOTIFICATIONS": "Notifications",
|
||||||
"CANNED_RESPONSES": "Готови отговори",
|
"CANNED_RESPONSES": "Готови отговори",
|
||||||
|
@ -189,6 +193,7 @@
|
||||||
"LABELS": "Labels",
|
"LABELS": "Labels",
|
||||||
"CUSTOM_ATTRIBUTES": "Персонализирани атрибути",
|
"CUSTOM_ATTRIBUTES": "Персонализирани атрибути",
|
||||||
"AUTOMATION": "Автоматизация",
|
"AUTOMATION": "Автоматизация",
|
||||||
|
"MACROS": "Macros",
|
||||||
"TEAMS": "Teams",
|
"TEAMS": "Teams",
|
||||||
"BILLING": "Billing",
|
"BILLING": "Billing",
|
||||||
"CUSTOM_VIEWS_FOLDER": "Folders",
|
"CUSTOM_VIEWS_FOLDER": "Folders",
|
||||||
|
|
|
@ -1,48 +1,48 @@
|
||||||
{
|
{
|
||||||
"FILTER": {
|
"FILTER": {
|
||||||
"TITLE": "Filter Conversations",
|
"TITLE": "Filtre de converses",
|
||||||
"SUBTITLE": "Add filters below and hit 'Apply filters' to filter conversations.",
|
"SUBTITLE": "Add filters below and hit 'Apply filters' to filter conversations.",
|
||||||
"ADD_NEW_FILTER": "Add Filter",
|
"ADD_NEW_FILTER": "Afegeix filtre",
|
||||||
"FILTER_DELETE_ERROR": "You should have atleast one filter to save",
|
"FILTER_DELETE_ERROR": "You should have atleast one filter to save",
|
||||||
"SUBMIT_BUTTON_LABEL": "Apply filters",
|
"SUBMIT_BUTTON_LABEL": "Aplicar filtres",
|
||||||
"CANCEL_BUTTON_LABEL": "Cancel·la",
|
"CANCEL_BUTTON_LABEL": "Cancel·la",
|
||||||
"CLEAR_BUTTON_LABEL": "Clear Filters",
|
"CLEAR_BUTTON_LABEL": "Clear Filters",
|
||||||
"EMPTY_VALUE_ERROR": "Value is required",
|
"EMPTY_VALUE_ERROR": "El valor és necessari",
|
||||||
"TOOLTIP_LABEL": "Filter conversations",
|
"TOOLTIP_LABEL": "Filtre de converses",
|
||||||
"QUERY_DROPDOWN_LABELS": {
|
"QUERY_DROPDOWN_LABELS": {
|
||||||
"AND": "AND",
|
"AND": "I",
|
||||||
"OR": "OR"
|
"OR": "O"
|
||||||
},
|
},
|
||||||
"OPERATOR_LABELS": {
|
"OPERATOR_LABELS": {
|
||||||
"equal_to": "Equal to",
|
"equal_to": "Igual a",
|
||||||
"not_equal_to": "Not equal to",
|
"not_equal_to": "No és igual a",
|
||||||
"contains": "Contains",
|
"contains": "Conté",
|
||||||
"does_not_contain": "Does not contain",
|
"does_not_contain": "No conté",
|
||||||
"is_present": "Is present",
|
"is_present": "És present",
|
||||||
"is_not_present": "Is not present",
|
"is_not_present": "No és present",
|
||||||
"is_greater_than": "Is greater than",
|
"is_greater_than": "És més gran que",
|
||||||
"is_less_than": "Is lesser than",
|
"is_less_than": "És més petit que",
|
||||||
"days_before": "Is x days before"
|
"days_before": "Is x days before"
|
||||||
},
|
},
|
||||||
"ATTRIBUTE_LABELS": {
|
"ATTRIBUTE_LABELS": {
|
||||||
"TRUE": "True",
|
"TRUE": "Cert",
|
||||||
"FALSE": "False"
|
"FALSE": "Fals"
|
||||||
},
|
},
|
||||||
"ATTRIBUTES": {
|
"ATTRIBUTES": {
|
||||||
"STATUS": "Estat",
|
"STATUS": "Estat",
|
||||||
"ASSIGNEE_NAME": "Assignee Name",
|
"ASSIGNEE_NAME": "Assignee Name",
|
||||||
"INBOX_NAME": "Nom de la safata d'entrada",
|
"INBOX_NAME": "Nom de la safata d'entrada",
|
||||||
"TEAM_NAME": "Team Name",
|
"TEAM_NAME": "Nom de l'equip",
|
||||||
"CONVERSATION_IDENTIFIER": "Conversation Identifier",
|
"CONVERSATION_IDENTIFIER": "Identificador de la conversa",
|
||||||
"CAMPAIGN_NAME": "Campaign Name",
|
"CAMPAIGN_NAME": "Campaign Name",
|
||||||
"LABELS": "Etiquetes",
|
"LABELS": "Etiquetes",
|
||||||
"BROWSER_LANGUAGE": "Browser Language",
|
"BROWSER_LANGUAGE": "Browser Language",
|
||||||
"COUNTRY_NAME": "Country Name",
|
"COUNTRY_NAME": "Nom del país",
|
||||||
"REFERER_LINK": "Referer link",
|
"REFERER_LINK": "Referer link",
|
||||||
"CUSTOM_ATTRIBUTE_LIST": "List",
|
"CUSTOM_ATTRIBUTE_LIST": "List",
|
||||||
"CUSTOM_ATTRIBUTE_TEXT": "Text",
|
"CUSTOM_ATTRIBUTE_TEXT": "Llista",
|
||||||
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
|
"CUSTOM_ATTRIBUTE_NUMBER": "Número",
|
||||||
"CUSTOM_ATTRIBUTE_LINK": "Link",
|
"CUSTOM_ATTRIBUTE_LINK": "Enllaç",
|
||||||
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
|
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
|
||||||
"CREATED_AT": "Created At",
|
"CREATED_AT": "Created At",
|
||||||
"LAST_ACTIVITY": "Last Activity"
|
"LAST_ACTIVITY": "Last Activity"
|
||||||
|
|
5
app/javascript/dashboard/i18n/locale/ca/agentBots.json
Normal file
5
app/javascript/dashboard/i18n/locale/ca/agentBots.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"AGENT_BOTS": {
|
||||||
|
"HEADER": "Bots"
|
||||||
|
}
|
||||||
|
}
|
|
@ -96,16 +96,16 @@
|
||||||
"PLACEHOLDER": "Ningú",
|
"PLACEHOLDER": "Ningú",
|
||||||
"TITLE": {
|
"TITLE": {
|
||||||
"AGENT": "Seleccionar Agent",
|
"AGENT": "Seleccionar Agent",
|
||||||
"TEAM": "Select team"
|
"TEAM": "Selecciona equip"
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"NO_RESULTS": {
|
"NO_RESULTS": {
|
||||||
"AGENT": "No s'han trobat agents",
|
"AGENT": "No s'han trobat agents",
|
||||||
"TEAM": "No teams found"
|
"TEAM": "No s'han trobat equips"
|
||||||
},
|
},
|
||||||
"PLACEHOLDER": {
|
"PLACEHOLDER": {
|
||||||
"AGENT": "Search agents",
|
"AGENT": "Cerca agents",
|
||||||
"TEAM": "Search teams"
|
"TEAM": "Cerca equips"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
"ERROR": "Description is required"
|
"ERROR": "Description is required"
|
||||||
},
|
},
|
||||||
"EVENT": {
|
"EVENT": {
|
||||||
"LABEL": "Event",
|
"LABEL": "Esdeveniment",
|
||||||
"PLACEHOLDER": "Please select one",
|
"PLACEHOLDER": "Please select one",
|
||||||
"ERROR": "Event is required"
|
"ERROR": "Event is required"
|
||||||
},
|
},
|
||||||
|
@ -86,7 +86,9 @@
|
||||||
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||||
},
|
},
|
||||||
"CONDITION": {
|
"CONDITION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one condition to save"
|
"DELETE_MESSAGE": "You need to have atleast one condition to save",
|
||||||
|
"CONTACT_CUSTOM_ATTR_LABEL": "Contact Custom Attributes",
|
||||||
|
"CONVERSATION_CUSTOM_ATTR_LABEL": "Conversation Custom Attributes"
|
||||||
},
|
},
|
||||||
"ACTION": {
|
"ACTION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
||||||
|
@ -109,7 +111,7 @@
|
||||||
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
||||||
"LABEL_IDLE": "Upload Attachment",
|
"LABEL_IDLE": "Upload Attachment",
|
||||||
"LABEL_UPLOADING": "S'està carregant...",
|
"LABEL_UPLOADING": "S'està carregant...",
|
||||||
"LABEL_UPLOADED": "Succesfully Uploaded",
|
"LABEL_UPLOADED": "Successfully Uploaded",
|
||||||
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
"BULK_ACTION": {
|
"BULK_ACTION": {
|
||||||
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
||||||
"AGENT_SELECT_LABEL": "Seleccionar Agent",
|
"AGENT_SELECT_LABEL": "Seleccionar Agent",
|
||||||
"ASSIGN_CONFIRMATION_LABEL": "Are you sure you want to assign %{conversationCount} %{conversationLabel} to",
|
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
|
||||||
|
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
|
||||||
"GO_BACK_LABEL": "Go back",
|
"GO_BACK_LABEL": "Go back",
|
||||||
"ASSIGN_LABEL": "Assignar",
|
"ASSIGN_LABEL": "Assignar",
|
||||||
|
"YES": "Si",
|
||||||
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
||||||
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
||||||
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
"FORM": {
|
"FORM": {
|
||||||
"SHORT_CODE": {
|
"SHORT_CODE": {
|
||||||
"LABEL": "Codi curt",
|
"LABEL": "Codi curt",
|
||||||
"PLACEHOLDER": "Please enter a short code",
|
"PLACEHOLDER": "Introduïu un codi curt",
|
||||||
"ERROR": "És necessari el codi curt"
|
"ERROR": "És necessari el codi curt"
|
||||||
},
|
},
|
||||||
"CONTENT": {
|
"CONTENT": {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"404": "No hi ha converses actives en aquest grup."
|
"404": "No hi ha converses actives en aquest grup."
|
||||||
},
|
},
|
||||||
"TAB_HEADING": "Converses",
|
"TAB_HEADING": "Converses",
|
||||||
"MENTION_HEADING": "Mentions",
|
"MENTION_HEADING": "Mencions",
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"INPUT": "Cerca persones, xats, respostes desades .."
|
"INPUT": "Cerca persones, xats, respostes desades .."
|
||||||
},
|
},
|
||||||
|
@ -25,10 +25,10 @@
|
||||||
"TEXT": "Resoltes"
|
"TEXT": "Resoltes"
|
||||||
},
|
},
|
||||||
"pending": {
|
"pending": {
|
||||||
"TEXT": "Pending"
|
"TEXT": "Pendent"
|
||||||
},
|
},
|
||||||
"snoozed": {
|
"snoozed": {
|
||||||
"TEXT": "Snoozed"
|
"TEXT": "Posposat"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
"ATTACHMENTS": {
|
||||||
|
@ -54,12 +54,12 @@
|
||||||
"RECEIVED_VIA_EMAIL": "Rebut per correu electrònic",
|
"RECEIVED_VIA_EMAIL": "Rebut per correu electrònic",
|
||||||
"VIEW_TWEET_IN_TWITTER": "Veure el tuit a Twitter",
|
"VIEW_TWEET_IN_TWITTER": "Veure el tuit a Twitter",
|
||||||
"REPLY_TO_TWEET": "Respon a aquest tuit",
|
"REPLY_TO_TWEET": "Respon a aquest tuit",
|
||||||
"LINK_TO_STORY": "Go to instagram story",
|
"LINK_TO_STORY": "Ves a la història d'instagram",
|
||||||
"SENT": "Sent successfully",
|
"SENT": "Enviat correctament",
|
||||||
"NO_MESSAGES": "Cap Missatge",
|
"NO_MESSAGES": "Cap Missatge",
|
||||||
"NO_CONTENT": "No content available",
|
"NO_CONTENT": "No hi ha contingut disponible",
|
||||||
"HIDE_QUOTED_TEXT": "Hide Quoted Text",
|
"HIDE_QUOTED_TEXT": "Amaga text entre cometes",
|
||||||
"SHOW_QUOTED_TEXT": "Show Quoted Text",
|
"SHOW_QUOTED_TEXT": "Mostra text entre cometes",
|
||||||
"MESSAGE_READ": "Read"
|
"MESSAGE_READ": "Llegir"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,40 +3,40 @@
|
||||||
"NOT_AVAILABLE": "No disponible",
|
"NOT_AVAILABLE": "No disponible",
|
||||||
"EMAIL_ADDRESS": "Adreça de correu electrònic",
|
"EMAIL_ADDRESS": "Adreça de correu electrònic",
|
||||||
"PHONE_NUMBER": "Número de telèfon",
|
"PHONE_NUMBER": "Número de telèfon",
|
||||||
"IDENTIFIER": "Identifier",
|
"IDENTIFIER": "Identificador",
|
||||||
"COPY_SUCCESSFUL": "S'ha copiat al porta-retalls amb èxit",
|
"COPY_SUCCESSFUL": "S'ha copiat al porta-retalls amb èxit",
|
||||||
"COMPANY": "Companyia",
|
"COMPANY": "Companyia",
|
||||||
"LOCATION": "Ubicació",
|
"LOCATION": "Ubicació",
|
||||||
"BROWSER_LANGUAGE": "Browser Language",
|
"BROWSER_LANGUAGE": "Idioma del navegador",
|
||||||
"CONVERSATION_TITLE": "Detalls de les converses",
|
"CONVERSATION_TITLE": "Detalls de les converses",
|
||||||
"VIEW_PROFILE": "View Profile",
|
"VIEW_PROFILE": "Veure perfil",
|
||||||
"BROWSER": "Navegador",
|
"BROWSER": "Navegador",
|
||||||
"OS": "Sistema operatiu",
|
"OS": "Sistema operatiu",
|
||||||
"INITIATED_FROM": "Iniciada des de",
|
"INITIATED_FROM": "Iniciada des de",
|
||||||
"INITIATED_AT": "Iniciada a les",
|
"INITIATED_AT": "Iniciada a les",
|
||||||
"IP_ADDRESS": "Adreça IP",
|
"IP_ADDRESS": "Adreça IP",
|
||||||
"NEW_MESSAGE": "New message",
|
"NEW_MESSAGE": "Nou missatge",
|
||||||
"CONVERSATIONS": {
|
"CONVERSATIONS": {
|
||||||
"NO_RECORDS_FOUND": "No hi han converses prèvies associades a aquest contacte.",
|
"NO_RECORDS_FOUND": "No hi han converses prèvies associades a aquest contacte.",
|
||||||
"TITLE": "Converses prèvies"
|
"TITLE": "Converses prèvies"
|
||||||
},
|
},
|
||||||
"LABELS": {
|
"LABELS": {
|
||||||
"CONTACT": {
|
"CONTACT": {
|
||||||
"TITLE": "Contact Labels",
|
"TITLE": "Etiquetes de contactes",
|
||||||
"ERROR": "Couldn't update labels"
|
"ERROR": "No s'han pogut actualitzar les etiquetes"
|
||||||
},
|
},
|
||||||
"CONVERSATION": {
|
"CONVERSATION": {
|
||||||
"TITLE": "Etiquetes de converses",
|
"TITLE": "Etiquetes de converses",
|
||||||
"ADD_BUTTON": "Add Labels"
|
"ADD_BUTTON": "Afegir etiquetes"
|
||||||
},
|
},
|
||||||
"LABEL_SELECT": {
|
"LABEL_SELECT": {
|
||||||
"TITLE": "Add Labels",
|
"TITLE": "Afegir etiquetes",
|
||||||
"PLACEHOLDER": "Search labels",
|
"PLACEHOLDER": "Cerca etiquetes",
|
||||||
"NO_RESULT": "No labels found"
|
"NO_RESULT": "No s'han trobat etiquetes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"MERGE_CONTACT": "Merge contact",
|
"MERGE_CONTACT": "Reagrupa contacte",
|
||||||
"CONTACT_ACTIONS": "Contact actions",
|
"CONTACT_ACTIONS": "Accions de contacte",
|
||||||
"MUTE_CONTACT": "Silencia la conversa",
|
"MUTE_CONTACT": "Silencia la conversa",
|
||||||
"UNMUTE_CONTACT": "Desactiva el silenci de la conversa",
|
"UNMUTE_CONTACT": "Desactiva el silenci de la conversa",
|
||||||
"MUTED_SUCCESS": "Aquesta conversa s'ha silenciat durant 6 hores",
|
"MUTED_SUCCESS": "Aquesta conversa s'ha silenciat durant 6 hores",
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
"EDIT_LABEL": "Edita",
|
"EDIT_LABEL": "Edita",
|
||||||
"SIDEBAR_SECTIONS": {
|
"SIDEBAR_SECTIONS": {
|
||||||
"CUSTOM_ATTRIBUTES": "Atributs personalitzats",
|
"CUSTOM_ATTRIBUTES": "Atributs personalitzats",
|
||||||
"CONTACT_LABELS": "Contact Labels",
|
"CONTACT_LABELS": "Etiquetes de contactes",
|
||||||
"PREVIOUS_CONVERSATIONS": "Converses prèvies"
|
"PREVIOUS_CONVERSATIONS": "Converses prèvies"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -60,30 +60,30 @@
|
||||||
"DESC": "Afegir informació bàsica sobre el contacte."
|
"DESC": "Afegir informació bàsica sobre el contacte."
|
||||||
},
|
},
|
||||||
"IMPORT_CONTACTS": {
|
"IMPORT_CONTACTS": {
|
||||||
"BUTTON_LABEL": "Import",
|
"BUTTON_LABEL": "Importa",
|
||||||
"TITLE": "Import Contacts",
|
"TITLE": "Importa contactes",
|
||||||
"DESC": "Import contacts through a CSV file.",
|
"DESC": "Importa contactes a través d'un fitxer CSV.",
|
||||||
"DOWNLOAD_LABEL": "Download a sample csv.",
|
"DOWNLOAD_LABEL": "Descarrega un csv d'exemple.",
|
||||||
"FORM": {
|
"FORM": {
|
||||||
"LABEL": "CSV File",
|
"LABEL": "Fitxer CSV",
|
||||||
"SUBMIT": "Import",
|
"SUBMIT": "Importa",
|
||||||
"CANCEL": "Cancel·la"
|
"CANCEL": "Cancel·la"
|
||||||
},
|
},
|
||||||
"SUCCESS_MESSAGE": "Contacts saved successfully",
|
"SUCCESS_MESSAGE": "Contactes desat correctament",
|
||||||
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
|
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
|
||||||
},
|
},
|
||||||
"DELETE_NOTE": {
|
"DELETE_NOTE": {
|
||||||
"CONFIRM": {
|
"CONFIRM": {
|
||||||
"TITLE": "Confirma l'esborrat",
|
"TITLE": "Confirma l'esborrat",
|
||||||
"MESSAGE": "Are you want sure to delete this note?",
|
"MESSAGE": "Vols suprimir aquesta nota amb seguretat?",
|
||||||
"YES": "Yes, Delete it",
|
"YES": "Si, esborra'l",
|
||||||
"NO": "No, manten-la"
|
"NO": "No, manten-la"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"DELETE_CONTACT": {
|
"DELETE_CONTACT": {
|
||||||
"BUTTON_LABEL": "Delete Contact",
|
"BUTTON_LABEL": "Contacte esborrat",
|
||||||
"TITLE": "Delete contact",
|
"TITLE": "Contacte esborrat",
|
||||||
"DESC": "Delete contact details",
|
"DESC": "Detalls del contacte esborrat",
|
||||||
"CONFIRM": {
|
"CONFIRM": {
|
||||||
"TITLE": "Confirma l'esborrat",
|
"TITLE": "Confirma l'esborrat",
|
||||||
"MESSAGE": "N'estas segur? ",
|
"MESSAGE": "N'estas segur? ",
|
||||||
|
@ -91,8 +91,8 @@
|
||||||
"NO": "No, segueix"
|
"NO": "No, segueix"
|
||||||
},
|
},
|
||||||
"API": {
|
"API": {
|
||||||
"SUCCESS_MESSAGE": "Contact deleted successfully",
|
"SUCCESS_MESSAGE": "Contacte esborrat correctament",
|
||||||
"ERROR_MESSAGE": "Could not delete contact. Please try again later."
|
"ERROR_MESSAGE": "No s'ha pogut esborrar el contacte. Torneu-ho a provar."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONTACT_FORM": {
|
"CONTACT_FORM": {
|
||||||
|
@ -160,7 +160,7 @@
|
||||||
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
|
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
|
||||||
},
|
},
|
||||||
"NEW_CONVERSATION": {
|
"NEW_CONVERSATION": {
|
||||||
"BUTTON_LABEL": "Start conversation",
|
"BUTTON_LABEL": "Inicia la conversa",
|
||||||
"TITLE": "Nova conversació",
|
"TITLE": "Nova conversació",
|
||||||
"DESC": "Start a new conversation by sending a new message.",
|
"DESC": "Start a new conversation by sending a new message.",
|
||||||
"NO_INBOX": "Couldn't find an inbox to initiate a new conversation with this contact.",
|
"NO_INBOX": "Couldn't find an inbox to initiate a new conversation with this contact.",
|
||||||
|
|
|
@ -2,26 +2,26 @@
|
||||||
"CONTACTS_FILTER": {
|
"CONTACTS_FILTER": {
|
||||||
"TITLE": "Filter Contacts",
|
"TITLE": "Filter Contacts",
|
||||||
"SUBTITLE": "Add filters below and hit 'Submit' to filter contacts.",
|
"SUBTITLE": "Add filters below and hit 'Submit' to filter contacts.",
|
||||||
"ADD_NEW_FILTER": "Add Filter",
|
"ADD_NEW_FILTER": "Afegeix filtre",
|
||||||
"CLEAR_ALL_FILTERS": "Clear All Filters",
|
"CLEAR_ALL_FILTERS": "Clear All Filters",
|
||||||
"FILTER_DELETE_ERROR": "You should have atleast one filter to save",
|
"FILTER_DELETE_ERROR": "You should have atleast one filter to save",
|
||||||
"SUBMIT_BUTTON_LABEL": "Envia",
|
"SUBMIT_BUTTON_LABEL": "Envia",
|
||||||
"CANCEL_BUTTON_LABEL": "Cancel·la",
|
"CANCEL_BUTTON_LABEL": "Cancel·la",
|
||||||
"CLEAR_BUTTON_LABEL": "Clear Filters",
|
"CLEAR_BUTTON_LABEL": "Clear Filters",
|
||||||
"EMPTY_VALUE_ERROR": "Value is required",
|
"EMPTY_VALUE_ERROR": "El valor és necessari",
|
||||||
"TOOLTIP_LABEL": "Filter contacts",
|
"TOOLTIP_LABEL": "Filter contacts",
|
||||||
"QUERY_DROPDOWN_LABELS": {
|
"QUERY_DROPDOWN_LABELS": {
|
||||||
"AND": "AND",
|
"AND": "I",
|
||||||
"OR": "OR"
|
"OR": "O"
|
||||||
},
|
},
|
||||||
"OPERATOR_LABELS": {
|
"OPERATOR_LABELS": {
|
||||||
"equal_to": "Equal to",
|
"equal_to": "Igual a",
|
||||||
"not_equal_to": "Not equal to",
|
"not_equal_to": "No és igual a",
|
||||||
"contains": "Contains",
|
"contains": "Conté",
|
||||||
"does_not_contain": "Does not contain",
|
"does_not_contain": "No conté",
|
||||||
"is_present": "Is present",
|
"is_present": "És present",
|
||||||
"is_not_present": "Is not present",
|
"is_not_present": "No és present",
|
||||||
"is_greater_than": "Is greater than",
|
"is_greater_than": "És més gran que",
|
||||||
"is_lesser_than": "Is lesser than",
|
"is_lesser_than": "Is lesser than",
|
||||||
"days_before": "Is x days before"
|
"days_before": "Is x days before"
|
||||||
},
|
},
|
||||||
|
@ -33,9 +33,9 @@
|
||||||
"CITY": "City",
|
"CITY": "City",
|
||||||
"COUNTRY": "Country",
|
"COUNTRY": "Country",
|
||||||
"CUSTOM_ATTRIBUTE_LIST": "List",
|
"CUSTOM_ATTRIBUTE_LIST": "List",
|
||||||
"CUSTOM_ATTRIBUTE_TEXT": "Text",
|
"CUSTOM_ATTRIBUTE_TEXT": "Llista",
|
||||||
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
|
"CUSTOM_ATTRIBUTE_NUMBER": "Número",
|
||||||
"CUSTOM_ATTRIBUTE_LINK": "Link",
|
"CUSTOM_ATTRIBUTE_LINK": "Enllaç",
|
||||||
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
|
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
|
||||||
"CREATED_AT": "Created At",
|
"CREATED_AT": "Created At",
|
||||||
"LAST_ACTIVITY": "Last Activity",
|
"LAST_ACTIVITY": "Last Activity",
|
||||||
|
|
|
@ -208,7 +208,8 @@
|
||||||
"CONVERSATION_LABELS": "Etiquetes de converses",
|
"CONVERSATION_LABELS": "Etiquetes de converses",
|
||||||
"CONVERSATION_INFO": "Conversation Information",
|
"CONVERSATION_INFO": "Conversation Information",
|
||||||
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
||||||
"PREVIOUS_CONVERSATION": "Converses prèvies"
|
"PREVIOUS_CONVERSATION": "Converses prèvies",
|
||||||
|
"MACROS": "Macros"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
||||||
|
|
|
@ -54,7 +54,8 @@
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
"ENTER_TO_SELECT": "Presiona retorn (tecla enter) per seleccionar",
|
"ENTER_TO_SELECT": "Presiona retorn (tecla enter) per seleccionar",
|
||||||
"ENTER_TO_REMOVE": "Presiona retorn (tecla enter) per eliminar",
|
"ENTER_TO_REMOVE": "Presiona retorn (tecla enter) per eliminar",
|
||||||
"SELECT_ONE": "Selecciona un"
|
"SELECT_ONE": "Selecciona un",
|
||||||
|
"SELECT": "Select"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"NOTIFICATIONS_PAGE": {
|
"NOTIFICATIONS_PAGE": {
|
||||||
|
@ -136,5 +137,8 @@
|
||||||
"UNTIL_NEXT_WEEK": "Until next week",
|
"UNTIL_NEXT_WEEK": "Until next week",
|
||||||
"UNTIL_TOMORROW": "Until tomorrow"
|
"UNTIL_TOMORROW": "Until tomorrow"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"DASHBOARD_APPS": {
|
||||||
|
"LOADING_MESSAGE": "Loading Dashboard App..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,14 +217,14 @@
|
||||||
"DOMAIN": {
|
"DOMAIN": {
|
||||||
"LABEL": "Custom Domain",
|
"LABEL": "Custom Domain",
|
||||||
"PLACEHOLDER": "Portal custom domain",
|
"PLACEHOLDER": "Portal custom domain",
|
||||||
"HELP_TEXT": "Add only If you want to use a custom domain for your portals.",
|
"HELP_TEXT": "Add only If you want to use a custom domain for your portals. Eg: https://example.com",
|
||||||
"ERROR": "Custom Domain is required"
|
"ERROR": "Enter a valid domain URL"
|
||||||
},
|
},
|
||||||
"HOME_PAGE_LINK": {
|
"HOME_PAGE_LINK": {
|
||||||
"LABEL": "Home Page Link",
|
"LABEL": "Home Page Link",
|
||||||
"PLACEHOLDER": "Portal home page link",
|
"PLACEHOLDER": "Portal home page link",
|
||||||
"HELP_TEXT": "The link used to return from the portal to the home page.",
|
"HELP_TEXT": "The link used to return from the portal to the home page. Eg: https://example.com",
|
||||||
"ERROR": "Home Page Link is required"
|
"ERROR": "Enter a valid home page URL"
|
||||||
},
|
},
|
||||||
"THEME_COLOR": {
|
"THEME_COLOR": {
|
||||||
"LABEL": "Portal theme color",
|
"LABEL": "Portal theme color",
|
||||||
|
@ -306,7 +306,7 @@
|
||||||
"PUBLISH_ARTICLE": {
|
"PUBLISH_ARTICLE": {
|
||||||
"API": {
|
"API": {
|
||||||
"ERROR": "Error while publishing article",
|
"ERROR": "Error while publishing article",
|
||||||
"SUCCESS": "Article publishied successfully"
|
"SUCCESS": "Article published successfully"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ARCHIVE_ARTICLE": {
|
"ARCHIVE_ARTICLE": {
|
||||||
|
|
|
@ -239,7 +239,9 @@
|
||||||
},
|
},
|
||||||
"API_CALLBACK": {
|
"API_CALLBACK": {
|
||||||
"TITLE": "Callback URL",
|
"TITLE": "Callback URL",
|
||||||
"SUBTITLE": "You have to configure the webhook URL in facebook developer portal with the URL mentioned here."
|
"SUBTITLE": "You have to configure the webhook URL and the verification token in the Facebook Developer portal with the values shown below.",
|
||||||
|
"WEBHOOK_URL": "URL del webhook",
|
||||||
|
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
|
||||||
},
|
},
|
||||||
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
||||||
"API": {
|
"API": {
|
||||||
|
|
78
app/javascript/dashboard/i18n/locale/ca/macros.json
Normal file
78
app/javascript/dashboard/i18n/locale/ca/macros.json
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"MACROS": {
|
||||||
|
"HEADER": "Macros",
|
||||||
|
"HEADER_BTN_TXT": "Add a new macro",
|
||||||
|
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||||
|
"LOADING": "Fetching macros",
|
||||||
|
"SIDEBAR_TXT": "<p><b>Macros</b><p>A macro is a set of saved actions that help customer service agents easily complete tasks. The agents can define a set of actions like tagging a conversation with a label, sending an email transcript, updating a custom attribute, etc., and they can run these actions in a single click. When the agents run the macro, the actions would be performed sequentially in the order they are defined. Macros improve productivity and increase consistency in actions. </p><p>A macro can be helpful in 2 ways. </p><p><b>As an agent assist:</b> If an agent performs a set of actions multiple times, they can save it as a macro and execute all the actions together using a single click.</p><p><b>As an option to onboard a team member:</b> Every agent has to perform many different checks/actions during each conversation. Onboarding a new support team member will be easy if pre-defined macros are available on the account. Instead of describing each step in detail, the manager/team lead can point to the macros used in different scenarios.</p>",
|
||||||
|
"ERROR": "Something went wrong. Please try again",
|
||||||
|
"ORDER_INFO": "Macros will run in the order you add your actions. You can rearrange them by dragging them by the handle beside each node.",
|
||||||
|
"ADD": {
|
||||||
|
"FORM": {
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "Macro name",
|
||||||
|
"PLACEHOLDER": "Enter a name for your macro",
|
||||||
|
"ERROR": "Name is required for creating a macro"
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"LABEL": "Accions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro added successfully",
|
||||||
|
"ERROR_MESSAGE": "Unable to create macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LIST": {
|
||||||
|
"TABLE_HEADER": [
|
||||||
|
"Nom",
|
||||||
|
"Created by",
|
||||||
|
"Last updated by",
|
||||||
|
"Visibility"
|
||||||
|
],
|
||||||
|
"404": "No macros found"
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"TOOLTIP": "Delete macro",
|
||||||
|
"CONFIRM": {
|
||||||
|
"MESSAGE": "N'estas segur? ",
|
||||||
|
"YES": "Si, esborra",
|
||||||
|
"NO": "No"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "There was an error deleting the macro. Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TOOLTIP": "Edit macro",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not update Macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDITOR": {
|
||||||
|
"START_FLOW": "Start Flow",
|
||||||
|
"END_FLOW": "End Flow",
|
||||||
|
"LOADING": "Fetching macro",
|
||||||
|
"ADD_BTN_TOOLTIP": "Add new action",
|
||||||
|
"DELETE_BTN_TOOLTIP": "Delete Action",
|
||||||
|
"VISIBILITY": {
|
||||||
|
"LABEL": "Macro Visibility",
|
||||||
|
"GLOBAL": {
|
||||||
|
"LABEL": "Public",
|
||||||
|
"DESCRIPTION": "This macro is available publicly for all agents in this account."
|
||||||
|
},
|
||||||
|
"PERSONAL": {
|
||||||
|
"LABEL": "Private",
|
||||||
|
"DESCRIPTION": "This macro will be private to you and not be available to others."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EXECUTE": {
|
||||||
|
"BUTTON_TOOLTIP": "Execute",
|
||||||
|
"PREVIEW": "Preview Macro",
|
||||||
|
"EXECUTED_SUCCESSFULLY": "Macro executed successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -158,6 +158,9 @@
|
||||||
"DOWNLOAD": "Descarrega",
|
"DOWNLOAD": "Descarrega",
|
||||||
"UPLOADING": "S'està carregant..."
|
"UPLOADING": "S'està carregant..."
|
||||||
},
|
},
|
||||||
|
"LOCATION_BUBBLE": {
|
||||||
|
"SEE_ON_MAP": "See on map"
|
||||||
|
},
|
||||||
"FORM_BUBBLE": {
|
"FORM_BUBBLE": {
|
||||||
"SUBMIT": "Envia"
|
"SUBMIT": "Envia"
|
||||||
}
|
}
|
||||||
|
@ -173,12 +176,13 @@
|
||||||
"SWITCH": "Switch",
|
"SWITCH": "Switch",
|
||||||
"CONVERSATIONS": "Converses",
|
"CONVERSATIONS": "Converses",
|
||||||
"ALL_CONVERSATIONS": "All Conversations",
|
"ALL_CONVERSATIONS": "All Conversations",
|
||||||
"MENTIONED_CONVERSATIONS": "Mentions",
|
"MENTIONED_CONVERSATIONS": "Mencions",
|
||||||
"REPORTS": "Informes",
|
"REPORTS": "Informes",
|
||||||
"SETTINGS": "Configuracions",
|
"SETTINGS": "Configuracions",
|
||||||
"CONTACTS": "Contactes",
|
"CONTACTS": "Contactes",
|
||||||
"HOME": "Inici",
|
"HOME": "Inici",
|
||||||
"AGENTS": "Agents",
|
"AGENTS": "Agents",
|
||||||
|
"AGENT_BOTS": "Bots",
|
||||||
"INBOXES": "Safates d'entrada",
|
"INBOXES": "Safates d'entrada",
|
||||||
"NOTIFICATIONS": "Notificacions",
|
"NOTIFICATIONS": "Notificacions",
|
||||||
"CANNED_RESPONSES": "Respostes predeterminades",
|
"CANNED_RESPONSES": "Respostes predeterminades",
|
||||||
|
@ -189,14 +193,15 @@
|
||||||
"LABELS": "Etiquetes",
|
"LABELS": "Etiquetes",
|
||||||
"CUSTOM_ATTRIBUTES": "Atributs personalitzats",
|
"CUSTOM_ATTRIBUTES": "Atributs personalitzats",
|
||||||
"AUTOMATION": "Automation",
|
"AUTOMATION": "Automation",
|
||||||
|
"MACROS": "Macros",
|
||||||
"TEAMS": "Equips",
|
"TEAMS": "Equips",
|
||||||
"BILLING": "Billing",
|
"BILLING": "Billing",
|
||||||
"CUSTOM_VIEWS_FOLDER": "Folders",
|
"CUSTOM_VIEWS_FOLDER": "Folders",
|
||||||
"CUSTOM_VIEWS_SEGMENTS": "Segments",
|
"CUSTOM_VIEWS_SEGMENTS": "Segments",
|
||||||
"ALL_CONTACTS": "All Contacts",
|
"ALL_CONTACTS": "All Contacts",
|
||||||
"TAGGED_WITH": "Tagged with",
|
"TAGGED_WITH": "Tagged with",
|
||||||
"NEW_LABEL": "New label",
|
"NEW_LABEL": "Nova etiqueta",
|
||||||
"NEW_TEAM": "New team",
|
"NEW_TEAM": "Nou equip",
|
||||||
"NEW_INBOX": "New inbox",
|
"NEW_INBOX": "New inbox",
|
||||||
"REPORTS_CONVERSATION": "Converses",
|
"REPORTS_CONVERSATION": "Converses",
|
||||||
"CSAT": "CSAT",
|
"CSAT": "CSAT",
|
||||||
|
|
5
app/javascript/dashboard/i18n/locale/cs/agentBots.json
Normal file
5
app/javascript/dashboard/i18n/locale/cs/agentBots.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"AGENT_BOTS": {
|
||||||
|
"HEADER": "Bots"
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,7 +86,9 @@
|
||||||
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||||
},
|
},
|
||||||
"CONDITION": {
|
"CONDITION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one condition to save"
|
"DELETE_MESSAGE": "You need to have atleast one condition to save",
|
||||||
|
"CONTACT_CUSTOM_ATTR_LABEL": "Contact Custom Attributes",
|
||||||
|
"CONVERSATION_CUSTOM_ATTR_LABEL": "Conversation Custom Attributes"
|
||||||
},
|
},
|
||||||
"ACTION": {
|
"ACTION": {
|
||||||
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
||||||
|
@ -109,7 +111,7 @@
|
||||||
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
||||||
"LABEL_IDLE": "Upload Attachment",
|
"LABEL_IDLE": "Upload Attachment",
|
||||||
"LABEL_UPLOADING": "Nahrávání...",
|
"LABEL_UPLOADING": "Nahrávání...",
|
||||||
"LABEL_UPLOADED": "Succesfully Uploaded",
|
"LABEL_UPLOADED": "Successfully Uploaded",
|
||||||
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
"BULK_ACTION": {
|
"BULK_ACTION": {
|
||||||
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
||||||
"AGENT_SELECT_LABEL": "Vybrat agenta",
|
"AGENT_SELECT_LABEL": "Vybrat agenta",
|
||||||
"ASSIGN_CONFIRMATION_LABEL": "Are you sure you want to assign %{conversationCount} %{conversationLabel} to",
|
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
|
||||||
|
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
|
||||||
"GO_BACK_LABEL": "Go back",
|
"GO_BACK_LABEL": "Go back",
|
||||||
"ASSIGN_LABEL": "Přiřadit",
|
"ASSIGN_LABEL": "Přiřadit",
|
||||||
|
"YES": "Ano",
|
||||||
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
||||||
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
||||||
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
||||||
|
|
|
@ -208,7 +208,8 @@
|
||||||
"CONVERSATION_LABELS": "Štítky konverzace",
|
"CONVERSATION_LABELS": "Štítky konverzace",
|
||||||
"CONVERSATION_INFO": "Informace o konverzaci",
|
"CONVERSATION_INFO": "Informace o konverzaci",
|
||||||
"CONTACT_ATTRIBUTES": "Atributy kontaktu",
|
"CONTACT_ATTRIBUTES": "Atributy kontaktu",
|
||||||
"PREVIOUS_CONVERSATION": "Předchozí konverzace"
|
"PREVIOUS_CONVERSATION": "Předchozí konverzace",
|
||||||
|
"MACROS": "Macros"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
||||||
|
|
|
@ -54,7 +54,8 @@
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
"ENTER_TO_SELECT": "Stiskněte Enter pro vybrání",
|
"ENTER_TO_SELECT": "Stiskněte Enter pro vybrání",
|
||||||
"ENTER_TO_REMOVE": "Stiskněte Enter pro odebrání",
|
"ENTER_TO_REMOVE": "Stiskněte Enter pro odebrání",
|
||||||
"SELECT_ONE": "Vyberte jeden"
|
"SELECT_ONE": "Vyberte jeden",
|
||||||
|
"SELECT": "Select"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"NOTIFICATIONS_PAGE": {
|
"NOTIFICATIONS_PAGE": {
|
||||||
|
@ -136,5 +137,8 @@
|
||||||
"UNTIL_NEXT_WEEK": "Until next week",
|
"UNTIL_NEXT_WEEK": "Until next week",
|
||||||
"UNTIL_TOMORROW": "Until tomorrow"
|
"UNTIL_TOMORROW": "Until tomorrow"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"DASHBOARD_APPS": {
|
||||||
|
"LOADING_MESSAGE": "Loading Dashboard App..."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -217,14 +217,14 @@
|
||||||
"DOMAIN": {
|
"DOMAIN": {
|
||||||
"LABEL": "Custom Domain",
|
"LABEL": "Custom Domain",
|
||||||
"PLACEHOLDER": "Portal custom domain",
|
"PLACEHOLDER": "Portal custom domain",
|
||||||
"HELP_TEXT": "Add only If you want to use a custom domain for your portals.",
|
"HELP_TEXT": "Add only If you want to use a custom domain for your portals. Eg: https://example.com",
|
||||||
"ERROR": "Custom Domain is required"
|
"ERROR": "Enter a valid domain URL"
|
||||||
},
|
},
|
||||||
"HOME_PAGE_LINK": {
|
"HOME_PAGE_LINK": {
|
||||||
"LABEL": "Home Page Link",
|
"LABEL": "Home Page Link",
|
||||||
"PLACEHOLDER": "Portal home page link",
|
"PLACEHOLDER": "Portal home page link",
|
||||||
"HELP_TEXT": "The link used to return from the portal to the home page.",
|
"HELP_TEXT": "The link used to return from the portal to the home page. Eg: https://example.com",
|
||||||
"ERROR": "Home Page Link is required"
|
"ERROR": "Enter a valid home page URL"
|
||||||
},
|
},
|
||||||
"THEME_COLOR": {
|
"THEME_COLOR": {
|
||||||
"LABEL": "Portal theme color",
|
"LABEL": "Portal theme color",
|
||||||
|
@ -306,7 +306,7 @@
|
||||||
"PUBLISH_ARTICLE": {
|
"PUBLISH_ARTICLE": {
|
||||||
"API": {
|
"API": {
|
||||||
"ERROR": "Error while publishing article",
|
"ERROR": "Error while publishing article",
|
||||||
"SUCCESS": "Article publishied successfully"
|
"SUCCESS": "Article published successfully"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ARCHIVE_ARTICLE": {
|
"ARCHIVE_ARTICLE": {
|
||||||
|
|
|
@ -239,7 +239,9 @@
|
||||||
},
|
},
|
||||||
"API_CALLBACK": {
|
"API_CALLBACK": {
|
||||||
"TITLE": "Callback URL",
|
"TITLE": "Callback URL",
|
||||||
"SUBTITLE": "You have to configure the webhook URL in facebook developer portal with the URL mentioned here."
|
"SUBTITLE": "You have to configure the webhook URL and the verification token in the Facebook Developer portal with the values shown below.",
|
||||||
|
"WEBHOOK_URL": "URL webového háčku",
|
||||||
|
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
|
||||||
},
|
},
|
||||||
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
"SUBMIT_BUTTON": "Create WhatsApp Channel",
|
||||||
"API": {
|
"API": {
|
||||||
|
@ -357,7 +359,7 @@
|
||||||
},
|
},
|
||||||
"FINISH": {
|
"FINISH": {
|
||||||
"TITLE": "Vaše doručená pošta je připravena!",
|
"TITLE": "Vaše doručená pošta je připravena!",
|
||||||
"MESSAGE": "Nyní se můžete spojit se svými zákazníky prostřednictvím nového kanálu. Šťastná podpora ",
|
"MESSAGE": "You can now engage with your customers through your new Channel. Happy supporting",
|
||||||
"BUTTON_TEXT": "Vezmi mě tam",
|
"BUTTON_TEXT": "Vezmi mě tam",
|
||||||
"MORE_SETTINGS": "More settings",
|
"MORE_SETTINGS": "More settings",
|
||||||
"WEBSITE_SUCCESS": "Úspěšně jste dokončili vytvoření webového kanálu. Zkopírujte kód zobrazený níže a vložte jej na vaše webové stránky. Když zákazník příště použije živý chat, konverzace se automaticky objeví ve vaší doručené poště."
|
"WEBSITE_SUCCESS": "Úspěšně jste dokončili vytvoření webového kanálu. Zkopírujte kód zobrazený níže a vložte jej na vaše webové stránky. Když zákazník příště použije živý chat, konverzace se automaticky objeví ve vaší doručené poště."
|
||||||
|
|
78
app/javascript/dashboard/i18n/locale/cs/macros.json
Normal file
78
app/javascript/dashboard/i18n/locale/cs/macros.json
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"MACROS": {
|
||||||
|
"HEADER": "Macros",
|
||||||
|
"HEADER_BTN_TXT": "Add a new macro",
|
||||||
|
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||||
|
"LOADING": "Fetching macros",
|
||||||
|
"SIDEBAR_TXT": "<p><b>Macros</b><p>A macro is a set of saved actions that help customer service agents easily complete tasks. The agents can define a set of actions like tagging a conversation with a label, sending an email transcript, updating a custom attribute, etc., and they can run these actions in a single click. When the agents run the macro, the actions would be performed sequentially in the order they are defined. Macros improve productivity and increase consistency in actions. </p><p>A macro can be helpful in 2 ways. </p><p><b>As an agent assist:</b> If an agent performs a set of actions multiple times, they can save it as a macro and execute all the actions together using a single click.</p><p><b>As an option to onboard a team member:</b> Every agent has to perform many different checks/actions during each conversation. Onboarding a new support team member will be easy if pre-defined macros are available on the account. Instead of describing each step in detail, the manager/team lead can point to the macros used in different scenarios.</p>",
|
||||||
|
"ERROR": "Something went wrong. Please try again",
|
||||||
|
"ORDER_INFO": "Macros will run in the order you add your actions. You can rearrange them by dragging them by the handle beside each node.",
|
||||||
|
"ADD": {
|
||||||
|
"FORM": {
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "Macro name",
|
||||||
|
"PLACEHOLDER": "Enter a name for your macro",
|
||||||
|
"ERROR": "Name is required for creating a macro"
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"LABEL": "Akce"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro added successfully",
|
||||||
|
"ERROR_MESSAGE": "Unable to create macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LIST": {
|
||||||
|
"TABLE_HEADER": [
|
||||||
|
"Název",
|
||||||
|
"Created by",
|
||||||
|
"Last updated by",
|
||||||
|
"Visibility"
|
||||||
|
],
|
||||||
|
"404": "No macros found"
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"TOOLTIP": "Delete macro",
|
||||||
|
"CONFIRM": {
|
||||||
|
"MESSAGE": "Opravdu chcete odstranit ",
|
||||||
|
"YES": "Ano, odstranit",
|
||||||
|
"NO": "Ne"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "There was an error deleting the macro. Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TOOLTIP": "Edit macro",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Macro updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not update Macro, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDITOR": {
|
||||||
|
"START_FLOW": "Start Flow",
|
||||||
|
"END_FLOW": "End Flow",
|
||||||
|
"LOADING": "Fetching macro",
|
||||||
|
"ADD_BTN_TOOLTIP": "Add new action",
|
||||||
|
"DELETE_BTN_TOOLTIP": "Delete Action",
|
||||||
|
"VISIBILITY": {
|
||||||
|
"LABEL": "Macro Visibility",
|
||||||
|
"GLOBAL": {
|
||||||
|
"LABEL": "Public",
|
||||||
|
"DESCRIPTION": "This macro is available publicly for all agents in this account."
|
||||||
|
},
|
||||||
|
"PERSONAL": {
|
||||||
|
"LABEL": "Private",
|
||||||
|
"DESCRIPTION": "This macro will be private to you and not be available to others."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EXECUTE": {
|
||||||
|
"BUTTON_TOOLTIP": "Execute",
|
||||||
|
"PREVIEW": "Preview Macro",
|
||||||
|
"EXECUTED_SUCCESSFULLY": "Macro executed successfully"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -158,6 +158,9 @@
|
||||||
"DOWNLOAD": "Stáhnout",
|
"DOWNLOAD": "Stáhnout",
|
||||||
"UPLOADING": "Nahrávání..."
|
"UPLOADING": "Nahrávání..."
|
||||||
},
|
},
|
||||||
|
"LOCATION_BUBBLE": {
|
||||||
|
"SEE_ON_MAP": "See on map"
|
||||||
|
},
|
||||||
"FORM_BUBBLE": {
|
"FORM_BUBBLE": {
|
||||||
"SUBMIT": "Odeslat"
|
"SUBMIT": "Odeslat"
|
||||||
}
|
}
|
||||||
|
@ -179,6 +182,7 @@
|
||||||
"CONTACTS": "Kontakty",
|
"CONTACTS": "Kontakty",
|
||||||
"HOME": "Domů",
|
"HOME": "Domů",
|
||||||
"AGENTS": "Agenti",
|
"AGENTS": "Agenti",
|
||||||
|
"AGENT_BOTS": "Bots",
|
||||||
"INBOXES": "Schránky",
|
"INBOXES": "Schránky",
|
||||||
"NOTIFICATIONS": "Oznámení",
|
"NOTIFICATIONS": "Oznámení",
|
||||||
"CANNED_RESPONSES": "Konzervované odpovědi",
|
"CANNED_RESPONSES": "Konzervované odpovědi",
|
||||||
|
@ -189,6 +193,7 @@
|
||||||
"LABELS": "Štítky",
|
"LABELS": "Štítky",
|
||||||
"CUSTOM_ATTRIBUTES": "Vlastní atributy",
|
"CUSTOM_ATTRIBUTES": "Vlastní atributy",
|
||||||
"AUTOMATION": "Automation",
|
"AUTOMATION": "Automation",
|
||||||
|
"MACROS": "Macros",
|
||||||
"TEAMS": "Týmy",
|
"TEAMS": "Týmy",
|
||||||
"BILLING": "Billing",
|
"BILLING": "Billing",
|
||||||
"CUSTOM_VIEWS_FOLDER": "Folders",
|
"CUSTOM_VIEWS_FOLDER": "Folders",
|
||||||
|
|
5
app/javascript/dashboard/i18n/locale/da/agentBots.json
Normal file
5
app/javascript/dashboard/i18n/locale/da/agentBots.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"AGENT_BOTS": {
|
||||||
|
"HEADER": "Bots"
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,7 +86,9 @@
|
||||||
"RESET_MESSAGE": "Ændring af begivenhedstype vil nulstille de betingelser og begivenheder, du har tilføjet nedenfor"
|
"RESET_MESSAGE": "Ændring af begivenhedstype vil nulstille de betingelser og begivenheder, du har tilføjet nedenfor"
|
||||||
},
|
},
|
||||||
"CONDITION": {
|
"CONDITION": {
|
||||||
"DELETE_MESSAGE": "Du skal have mindst én betingelse for at gemme"
|
"DELETE_MESSAGE": "Du skal have mindst én betingelse for at gemme",
|
||||||
|
"CONTACT_CUSTOM_ATTR_LABEL": "Contact Custom Attributes",
|
||||||
|
"CONVERSATION_CUSTOM_ATTR_LABEL": "Conversation Custom Attributes"
|
||||||
},
|
},
|
||||||
"ACTION": {
|
"ACTION": {
|
||||||
"DELETE_MESSAGE": "Du skal have mindst én handling for at gemme",
|
"DELETE_MESSAGE": "Du skal have mindst én handling for at gemme",
|
||||||
|
@ -109,7 +111,7 @@
|
||||||
"UPLOAD_ERROR": "Kunne ikke uploade vedhæftning, Prøv venligst igen",
|
"UPLOAD_ERROR": "Kunne ikke uploade vedhæftning, Prøv venligst igen",
|
||||||
"LABEL_IDLE": "Upload Vedhæftning",
|
"LABEL_IDLE": "Upload Vedhæftning",
|
||||||
"LABEL_UPLOADING": "Uploader...",
|
"LABEL_UPLOADING": "Uploader...",
|
||||||
"LABEL_UPLOADED": "Succesfuldt Uploadet",
|
"LABEL_UPLOADED": "Successfully Uploaded",
|
||||||
"LABEL_UPLOAD_FAILED": "Upload Mislykkedes"
|
"LABEL_UPLOAD_FAILED": "Upload Mislykkedes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
"BULK_ACTION": {
|
"BULK_ACTION": {
|
||||||
"CONVERSATIONS_SELECTED": "%{conversationCount} samtaler valgt",
|
"CONVERSATIONS_SELECTED": "%{conversationCount} samtaler valgt",
|
||||||
"AGENT_SELECT_LABEL": "Vælg Agent",
|
"AGENT_SELECT_LABEL": "Vælg Agent",
|
||||||
"ASSIGN_CONFIRMATION_LABEL": "Er du sikker på, at du vil tildele %{conversationCount} %{conversationLabel} til",
|
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
|
||||||
|
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
|
||||||
"GO_BACK_LABEL": "Gå tilbage",
|
"GO_BACK_LABEL": "Gå tilbage",
|
||||||
"ASSIGN_LABEL": "Tildel",
|
"ASSIGN_LABEL": "Tildel",
|
||||||
|
"YES": "Ja",
|
||||||
"ASSIGN_AGENT_TOOLTIP": "Tildel Agent",
|
"ASSIGN_AGENT_TOOLTIP": "Tildel Agent",
|
||||||
"ASSIGN_SUCCESFUL": "Samtaler tildelt",
|
"ASSIGN_SUCCESFUL": "Samtaler tildelt",
|
||||||
"ASSIGN_FAILED": "Mislykkedes at tildele samtaler, prøv igen",
|
"ASSIGN_FAILED": "Mislykkedes at tildele samtaler, prøv igen",
|
||||||
|
|
|
@ -208,7 +208,8 @@
|
||||||
"CONVERSATION_LABELS": "Samtale Etiketter",
|
"CONVERSATION_LABELS": "Samtale Etiketter",
|
||||||
"CONVERSATION_INFO": "Samtale Information",
|
"CONVERSATION_INFO": "Samtale Information",
|
||||||
"CONTACT_ATTRIBUTES": "Kontakt Attributter",
|
"CONTACT_ATTRIBUTES": "Kontakt Attributter",
|
||||||
"PREVIOUS_CONVERSATION": "Tidligere Samtaler"
|
"PREVIOUS_CONVERSATION": "Tidligere Samtaler",
|
||||||
|
"MACROS": "Macros"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue