Merge branch 'release/2.2.0'
This commit is contained in:
commit
b6c3d61163
@ -22,6 +22,9 @@ checks:
|
|||||||
enabled: true
|
enabled: true
|
||||||
config:
|
config:
|
||||||
threshold: 300
|
threshold: 300
|
||||||
|
method-lines:
|
||||||
|
config:
|
||||||
|
threshold: 50
|
||||||
exclude_patterns:
|
exclude_patterns:
|
||||||
- 'spec/'
|
- 'spec/'
|
||||||
- '**/specs/'
|
- '**/specs/'
|
||||||
@ -44,3 +47,6 @@ exclude_patterns:
|
|||||||
- 'app/javascript/shared/constants/countries.js'
|
- 'app/javascript/shared/constants/countries.js'
|
||||||
- 'app/javascript/dashboard/components/widgets/conversation/advancedFilterItems/languages.js'
|
- 'app/javascript/dashboard/components/widgets/conversation/advancedFilterItems/languages.js'
|
||||||
- 'app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js'
|
- 'app/javascript/dashboard/routes/dashboard/contacts/contactFilterItems/index.js'
|
||||||
|
- 'app/javascript/dashboard/routes/dashboard/settings/automation/constants.js'
|
||||||
|
- 'app/javascript/dashboard/components/widgets/FilterInput/FilterOperatorTypes.js'
|
||||||
|
- 'app/javascript/dashboard/routes/dashboard/settings/reports/constants.js'
|
||||||
|
|||||||
2
Gemfile
2
Gemfile
@ -102,7 +102,7 @@ gem 'sentry-ruby'
|
|||||||
gem 'sentry-sidekiq'
|
gem 'sentry-sidekiq'
|
||||||
|
|
||||||
##-- background job processing --##
|
##-- background job processing --##
|
||||||
gem 'sidekiq'
|
gem 'sidekiq', '~> 6.4.0'
|
||||||
# We want cron jobs
|
# We want cron jobs
|
||||||
gem 'sidekiq-cron'
|
gem 'sidekiq-cron'
|
||||||
|
|
||||||
|
|||||||
122
Gemfile.lock
122
Gemfile.lock
@ -9,63 +9,63 @@ GIT
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (6.1.4.3)
|
actioncable (6.1.4.6)
|
||||||
actionpack (= 6.1.4.3)
|
actionpack (= 6.1.4.6)
|
||||||
activesupport (= 6.1.4.3)
|
activesupport (= 6.1.4.6)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (6.1.4.3)
|
actionmailbox (6.1.4.6)
|
||||||
actionpack (= 6.1.4.3)
|
actionpack (= 6.1.4.6)
|
||||||
activejob (= 6.1.4.3)
|
activejob (= 6.1.4.6)
|
||||||
activerecord (= 6.1.4.3)
|
activerecord (= 6.1.4.6)
|
||||||
activestorage (= 6.1.4.3)
|
activestorage (= 6.1.4.6)
|
||||||
activesupport (= 6.1.4.3)
|
activesupport (= 6.1.4.6)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
actionmailer (6.1.4.3)
|
actionmailer (6.1.4.6)
|
||||||
actionpack (= 6.1.4.3)
|
actionpack (= 6.1.4.6)
|
||||||
actionview (= 6.1.4.3)
|
actionview (= 6.1.4.6)
|
||||||
activejob (= 6.1.4.3)
|
activejob (= 6.1.4.6)
|
||||||
activesupport (= 6.1.4.3)
|
activesupport (= 6.1.4.6)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (6.1.4.3)
|
actionpack (6.1.4.6)
|
||||||
actionview (= 6.1.4.3)
|
actionview (= 6.1.4.6)
|
||||||
activesupport (= 6.1.4.3)
|
activesupport (= 6.1.4.6)
|
||||||
rack (~> 2.0, >= 2.0.9)
|
rack (~> 2.0, >= 2.0.9)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (6.1.4.3)
|
actiontext (6.1.4.6)
|
||||||
actionpack (= 6.1.4.3)
|
actionpack (= 6.1.4.6)
|
||||||
activerecord (= 6.1.4.3)
|
activerecord (= 6.1.4.6)
|
||||||
activestorage (= 6.1.4.3)
|
activestorage (= 6.1.4.6)
|
||||||
activesupport (= 6.1.4.3)
|
activesupport (= 6.1.4.6)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (6.1.4.3)
|
actionview (6.1.4.6)
|
||||||
activesupport (= 6.1.4.3)
|
activesupport (= 6.1.4.6)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||||
active_record_query_trace (1.8)
|
active_record_query_trace (1.8)
|
||||||
activejob (6.1.4.3)
|
activejob (6.1.4.6)
|
||||||
activesupport (= 6.1.4.3)
|
activesupport (= 6.1.4.6)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (6.1.4.3)
|
activemodel (6.1.4.6)
|
||||||
activesupport (= 6.1.4.3)
|
activesupport (= 6.1.4.6)
|
||||||
activerecord (6.1.4.3)
|
activerecord (6.1.4.6)
|
||||||
activemodel (= 6.1.4.3)
|
activemodel (= 6.1.4.6)
|
||||||
activesupport (= 6.1.4.3)
|
activesupport (= 6.1.4.6)
|
||||||
activerecord-import (1.2.0)
|
activerecord-import (1.2.0)
|
||||||
activerecord (>= 3.2)
|
activerecord (>= 3.2)
|
||||||
activestorage (6.1.4.3)
|
activestorage (6.1.4.6)
|
||||||
actionpack (= 6.1.4.3)
|
actionpack (= 6.1.4.6)
|
||||||
activejob (= 6.1.4.3)
|
activejob (= 6.1.4.6)
|
||||||
activerecord (= 6.1.4.3)
|
activerecord (= 6.1.4.6)
|
||||||
activesupport (= 6.1.4.3)
|
activesupport (= 6.1.4.6)
|
||||||
marcel (~> 1.0.0)
|
marcel (~> 1.0.0)
|
||||||
mini_mime (>= 1.1.0)
|
mini_mime (>= 1.1.0)
|
||||||
activesupport (6.1.4.3)
|
activesupport (6.1.4.6)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
@ -300,7 +300,7 @@ GEM
|
|||||||
mime-types (~> 3.0)
|
mime-types (~> 3.0)
|
||||||
multi_xml (>= 0.5.2)
|
multi_xml (>= 0.5.2)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
i18n (1.8.11)
|
i18n (1.9.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
image_processing (1.12.1)
|
image_processing (1.12.1)
|
||||||
mini_magick (>= 4.9.5, < 5)
|
mini_magick (>= 4.9.5, < 5)
|
||||||
@ -346,7 +346,7 @@ GEM
|
|||||||
listen (3.7.0)
|
listen (3.7.0)
|
||||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
rb-inotify (~> 0.9, >= 0.9.10)
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
loofah (2.13.0)
|
loofah (2.14.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.7.1)
|
mail (2.7.1)
|
||||||
@ -400,7 +400,7 @@ GEM
|
|||||||
pry-rails (0.3.9)
|
pry-rails (0.3.9)
|
||||||
pry (>= 0.10.4)
|
pry (>= 0.10.4)
|
||||||
public_suffix (4.0.6)
|
public_suffix (4.0.6)
|
||||||
puma (5.5.2)
|
puma (5.6.2)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.1.1)
|
pundit (2.1.1)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
@ -416,29 +416,29 @@ GEM
|
|||||||
rack-test (1.1.0)
|
rack-test (1.1.0)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-timeout (0.6.0)
|
rack-timeout (0.6.0)
|
||||||
rails (6.1.4.3)
|
rails (6.1.4.6)
|
||||||
actioncable (= 6.1.4.3)
|
actioncable (= 6.1.4.6)
|
||||||
actionmailbox (= 6.1.4.3)
|
actionmailbox (= 6.1.4.6)
|
||||||
actionmailer (= 6.1.4.3)
|
actionmailer (= 6.1.4.6)
|
||||||
actionpack (= 6.1.4.3)
|
actionpack (= 6.1.4.6)
|
||||||
actiontext (= 6.1.4.3)
|
actiontext (= 6.1.4.6)
|
||||||
actionview (= 6.1.4.3)
|
actionview (= 6.1.4.6)
|
||||||
activejob (= 6.1.4.3)
|
activejob (= 6.1.4.6)
|
||||||
activemodel (= 6.1.4.3)
|
activemodel (= 6.1.4.6)
|
||||||
activerecord (= 6.1.4.3)
|
activerecord (= 6.1.4.6)
|
||||||
activestorage (= 6.1.4.3)
|
activestorage (= 6.1.4.6)
|
||||||
activesupport (= 6.1.4.3)
|
activesupport (= 6.1.4.6)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 6.1.4.3)
|
railties (= 6.1.4.6)
|
||||||
sprockets-rails (>= 2.0.0)
|
sprockets-rails (>= 2.0.0)
|
||||||
rails-dom-testing (2.0.3)
|
rails-dom-testing (2.0.3)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.4.2)
|
rails-html-sanitizer (1.4.2)
|
||||||
loofah (~> 2.3)
|
loofah (~> 2.3)
|
||||||
railties (6.1.4.3)
|
railties (6.1.4.6)
|
||||||
actionpack (= 6.1.4.3)
|
actionpack (= 6.1.4.6)
|
||||||
activesupport (= 6.1.4.3)
|
activesupport (= 6.1.4.6)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 0.13)
|
rake (>= 0.13)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
@ -447,7 +447,7 @@ GEM
|
|||||||
rb-fsevent (0.11.0)
|
rb-fsevent (0.11.0)
|
||||||
rb-inotify (0.10.1)
|
rb-inotify (0.10.1)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
redis (4.4.0)
|
redis (4.5.1)
|
||||||
redis-namespace (1.8.1)
|
redis-namespace (1.8.1)
|
||||||
redis (>= 3.0.4)
|
redis (>= 3.0.4)
|
||||||
regexp_parser (2.1.1)
|
regexp_parser (2.1.1)
|
||||||
@ -546,7 +546,7 @@ GEM
|
|||||||
sexp_processor (4.15.3)
|
sexp_processor (4.15.3)
|
||||||
shoulda-matchers (5.0.0)
|
shoulda-matchers (5.0.0)
|
||||||
activesupport (>= 5.2.0)
|
activesupport (>= 5.2.0)
|
||||||
sidekiq (6.2.2)
|
sidekiq (6.4.0)
|
||||||
connection_pool (>= 2.2.2)
|
connection_pool (>= 2.2.2)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
redis (>= 4.2.0)
|
redis (>= 4.2.0)
|
||||||
@ -583,7 +583,7 @@ GEM
|
|||||||
squasher (0.6.2)
|
squasher (0.6.2)
|
||||||
statsd-ruby (1.5.0)
|
statsd-ruby (1.5.0)
|
||||||
telephone_number (1.4.12)
|
telephone_number (1.4.12)
|
||||||
thor (1.1.0)
|
thor (1.2.1)
|
||||||
tilt (2.0.10)
|
tilt (2.0.10)
|
||||||
time_diff (0.3.0)
|
time_diff (0.3.0)
|
||||||
activesupport
|
activesupport
|
||||||
@ -635,7 +635,7 @@ GEM
|
|||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
wisper (2.0.0)
|
wisper (2.0.0)
|
||||||
zeitwerk (2.5.1)
|
zeitwerk (2.5.4)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
arm64-darwin-20
|
arm64-darwin-20
|
||||||
@ -726,7 +726,7 @@ DEPENDENCIES
|
|||||||
sentry-ruby
|
sentry-ruby
|
||||||
sentry-sidekiq
|
sentry-sidekiq
|
||||||
shoulda-matchers
|
shoulda-matchers
|
||||||
sidekiq
|
sidekiq (~> 6.4.0)
|
||||||
sidekiq-cron
|
sidekiq-cron
|
||||||
simplecov (= 0.17.1)
|
simplecov (= 0.17.1)
|
||||||
slack-ruby-client
|
slack-ruby-client
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class AccountBuilder
|
class AccountBuilder
|
||||||
include CustomExceptions::Account
|
include CustomExceptions::Account
|
||||||
pattr_initialize [:account_name!, :email!, :confirmed, :user, :user_full_name, :user_password]
|
pattr_initialize [:account_name!, :email!, :confirmed, :user, :user_full_name, :user_password, :super_admin]
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
if @user.nil?
|
if @user.nil?
|
||||||
@ -65,6 +65,7 @@ class AccountBuilder
|
|||||||
password: user_password,
|
password: user_password,
|
||||||
password_confirmation: user_password,
|
password_confirmation: user_password,
|
||||||
name: @user_full_name)
|
name: @user_full_name)
|
||||||
|
@user.type = 'SuperAdmin' if @super_admin
|
||||||
@user.confirm if @confirmed
|
@user.confirm if @confirmed
|
||||||
@user.save!
|
@user.save!
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,7 +4,7 @@ class ContactInboxBuilder
|
|||||||
def perform
|
def perform
|
||||||
@contact = Contact.find(contact_id)
|
@contact = Contact.find(contact_id)
|
||||||
@inbox = @contact.account.inboxes.find(inbox_id)
|
@inbox = @contact.account.inboxes.find(inbox_id)
|
||||||
return unless ['Channel::TwilioSms', 'Channel::Email', 'Channel::Api', 'Channel::Whatsapp'].include? @inbox.channel_type
|
return unless ['Channel::TwilioSms', 'Channel::Sms', 'Channel::Email', 'Channel::Api', 'Channel::Whatsapp'].include? @inbox.channel_type
|
||||||
|
|
||||||
source_id = @source_id || generate_source_id
|
source_id = @source_id || generate_source_id
|
||||||
create_contact_inbox(source_id) if source_id.present?
|
create_contact_inbox(source_id) if source_id.present?
|
||||||
@ -13,12 +13,18 @@ class ContactInboxBuilder
|
|||||||
private
|
private
|
||||||
|
|
||||||
def generate_source_id
|
def generate_source_id
|
||||||
return twilio_source_id if @inbox.channel_type == 'Channel::TwilioSms'
|
case @inbox.channel_type
|
||||||
return wa_source_id if @inbox.channel_type == 'Channel::Whatsapp'
|
when 'Channel::TwilioSms'
|
||||||
return @contact.email if @inbox.channel_type == 'Channel::Email'
|
twilio_source_id
|
||||||
return SecureRandom.uuid if @inbox.channel_type == 'Channel::Api'
|
when 'Channel::Whatsapp'
|
||||||
|
wa_source_id
|
||||||
nil
|
when 'Channel::Email'
|
||||||
|
@contact.email
|
||||||
|
when 'Channel::Sms'
|
||||||
|
@contact.phone_number
|
||||||
|
when 'Channel::Api'
|
||||||
|
SecureRandom.uuid
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def wa_source_id
|
def wa_source_id
|
||||||
|
|||||||
@ -29,11 +29,12 @@ class Messages::MessageBuilder
|
|||||||
return if @attachments.blank?
|
return if @attachments.blank?
|
||||||
|
|
||||||
@attachments.each do |uploaded_attachment|
|
@attachments.each do |uploaded_attachment|
|
||||||
@message.attachments.build(
|
attachment = @message.attachments.build(
|
||||||
account_id: @message.account_id,
|
account_id: @message.account_id,
|
||||||
file_type: file_type(uploaded_attachment&.content_type),
|
|
||||||
file: uploaded_attachment
|
file: uploaded_attachment
|
||||||
)
|
)
|
||||||
|
|
||||||
|
attachment.file_type = file_type(uploaded_attachment&.content_type) if uploaded_attachment.is_a?(ActionDispatch::Http::UploadedFile)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@ class V2::ReportBuilder
|
|||||||
include DateRangeHelper
|
include DateRangeHelper
|
||||||
attr_reader :account, :params
|
attr_reader :account, :params
|
||||||
|
|
||||||
|
DEFAULT_GROUP_BY = 'day'.freeze
|
||||||
|
|
||||||
def initialize(account, params)
|
def initialize(account, params)
|
||||||
@account = account
|
@account = account
|
||||||
@params = params
|
@params = params
|
||||||
@ -64,26 +66,30 @@ class V2::ReportBuilder
|
|||||||
|
|
||||||
def conversations_count
|
def conversations_count
|
||||||
scope.conversations
|
scope.conversations
|
||||||
.group_by_day(:created_at, range: range, default_value: 0)
|
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
|
||||||
|
:created_at, range: range, default_value: 0, permit: %w[day week month year])
|
||||||
.count
|
.count
|
||||||
end
|
end
|
||||||
|
|
||||||
def incoming_messages_count
|
def incoming_messages_count
|
||||||
scope.messages.incoming.unscope(:order)
|
scope.messages.incoming.unscope(:order)
|
||||||
.group_by_day(:created_at, range: range, default_value: 0)
|
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
|
||||||
|
:created_at, range: range, default_value: 0, permit: %w[day week month year])
|
||||||
.count
|
.count
|
||||||
end
|
end
|
||||||
|
|
||||||
def outgoing_messages_count
|
def outgoing_messages_count
|
||||||
scope.messages.outgoing.unscope(:order)
|
scope.messages.outgoing.unscope(:order)
|
||||||
.group_by_day(:created_at, range: range, default_value: 0)
|
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
|
||||||
|
:created_at, range: range, default_value: 0, permit: %w[day week month year])
|
||||||
.count
|
.count
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolutions_count
|
def resolutions_count
|
||||||
scope.conversations
|
scope.conversations
|
||||||
.resolved
|
.resolved
|
||||||
.group_by_day(:created_at, range: range, default_value: 0)
|
.group_by_period(params[:group_by] || DEFAULT_GROUP_BY,
|
||||||
|
:created_at, range: range, default_value: 0, permit: %w[day week month year])
|
||||||
.count
|
.count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +1,44 @@
|
|||||||
class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseController
|
class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseController
|
||||||
before_action :check_authorization
|
before_action :check_authorization
|
||||||
|
before_action :fetch_automation_rule, only: [:show, :update, :destroy, :clone]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@automation_rules = Current.account.automation_rules
|
@automation_rules = Current.account.automation_rules.active
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@automation_rule = Current.account.automation_rules.create(automation_rules_permit)
|
@automation_rule = Current.account.automation_rules.create(automation_rules_permit)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@automation_rule.update(automation_rules_permit)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@automation_rule.destroy!
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def clone
|
||||||
|
automation_rule = Current.account.automation_rules.find_by(id: params[:automation_rule_id])
|
||||||
|
new_rule = automation_rule.dup
|
||||||
|
new_rule.save
|
||||||
|
@automation_rule = new_rule
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def automation_rules_permit
|
def automation_rules_permit
|
||||||
params.permit(
|
params.permit(
|
||||||
:name, :description, :event_name, :account_id,
|
:name, :description, :event_name, :account_id,
|
||||||
conditions: [:attribute_key, :filter_operator, :query_operator, { values: [] }],
|
conditions: [:attribute_key, :filter_operator, :query_operator, { values: [] }],
|
||||||
actions: [:action_name, { action_params: [:intiated_at] }]
|
actions: [:action_name, { action_params: [] }]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fetch_automation_rule
|
||||||
|
@automation_rule = Current.account.automation_rules.find_by(id: params[:id])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -18,7 +18,7 @@ class Api::V1::Accounts::CampaignsController < Api::V1::Accounts::BaseController
|
|||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@campaign.update(campaign_params)
|
@campaign.update!(campaign_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@ -61,6 +61,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
|||||||
else
|
else
|
||||||
@status = @conversation.toggle_status
|
@status = @conversation.toggle_status
|
||||||
end
|
end
|
||||||
|
assign_conversation if @conversation.status == 'open' && Current.user.is_a?(User) && Current.user&.agent?
|
||||||
end
|
end
|
||||||
|
|
||||||
def toggle_typing_status
|
def toggle_typing_status
|
||||||
@ -93,6 +94,11 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
|||||||
@conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until]
|
@conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def assign_conversation
|
||||||
|
@agent = Current.account.users.find(current_user.id)
|
||||||
|
@conversation.update_assignee(@agent)
|
||||||
|
end
|
||||||
|
|
||||||
def trigger_typing_event(event, is_private)
|
def trigger_typing_event(event, is_private)
|
||||||
user = current_user.presence || @resource
|
user = current_user.presence || @resource
|
||||||
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: user, is_private: is_private)
|
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: user, is_private: is_private)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||||||
include Api::V1::InboxesHelper
|
include Api::V1::InboxesHelper
|
||||||
before_action :fetch_inbox, except: [:index, :create]
|
before_action :fetch_inbox, except: [:index, :create]
|
||||||
before_action :fetch_agent_bot, only: [:set_agent_bot]
|
before_action :fetch_agent_bot, only: [:set_agent_bot]
|
||||||
|
before_action :validate_limit, only: [:create]
|
||||||
# we are already handling the authorization in fetch inbox
|
# we are already handling the authorization in fetch inbox
|
||||||
before_action :check_authorization, except: [:show]
|
before_action :check_authorization, except: [:show]
|
||||||
|
|
||||||
@ -91,20 +92,9 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create_channel
|
def create_channel
|
||||||
case permitted_params[:channel][:type]
|
return unless %w[web_widget api email line telegram whatsapp sms].include?(permitted_params[:channel][:type])
|
||||||
when 'web_widget'
|
|
||||||
Current.account.web_widgets.create!(permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].except(:type))
|
account_channels_method.create!(permitted_params(channel_type_from_params::EDITABLE_ATTRS)[:channel].except(:type))
|
||||||
when 'api'
|
|
||||||
Current.account.api_channels.create!(permitted_params(Channel::Api::EDITABLE_ATTRS)[:channel].except(:type))
|
|
||||||
when 'email'
|
|
||||||
Current.account.email_channels.create!(permitted_params(Channel::Email::EDITABLE_ATTRS)[:channel].except(:type))
|
|
||||||
when 'line'
|
|
||||||
Current.account.line_channels.create!(permitted_params(Channel::Line::EDITABLE_ATTRS)[:channel].except(:type))
|
|
||||||
when 'telegram'
|
|
||||||
Current.account.telegram_channels.create!(permitted_params(Channel::Telegram::EDITABLE_ATTRS)[:channel].except(:type))
|
|
||||||
when 'whatsapp'
|
|
||||||
Current.account.whatsapp_channels.create!(permitted_params(Channel::Whatsapp::EDITABLE_ATTRS)[:channel].except(:type))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_channel_feature_flags
|
def update_channel_feature_flags
|
||||||
@ -123,6 +113,30 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def channel_type_from_params
|
||||||
|
{
|
||||||
|
'web_widget' => Channel::WebWidget,
|
||||||
|
'api' => Channel::Api,
|
||||||
|
'email' => Channel::Email,
|
||||||
|
'line' => Channel::Line,
|
||||||
|
'telegram' => Channel::Telegram,
|
||||||
|
'whatsapp' => Channel::Whatsapp,
|
||||||
|
'sms' => Channel::Sms
|
||||||
|
}[permitted_params[:channel][:type]]
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_channels_method
|
||||||
|
{
|
||||||
|
'web_widget' => Current.account.web_widgets,
|
||||||
|
'api' => Current.account.api_channels,
|
||||||
|
'email' => Current.account.email_channels,
|
||||||
|
'line' => Current.account.line_channels,
|
||||||
|
'telegram' => Current.account.telegram_channels,
|
||||||
|
'whatsapp' => Current.account.whatsapp_channels,
|
||||||
|
'sms' => Current.account.sms_channels
|
||||||
|
}[permitted_params[:channel][:type]]
|
||||||
|
end
|
||||||
|
|
||||||
def get_channel_attributes(channel_type)
|
def get_channel_attributes(channel_type)
|
||||||
if channel_type.constantize.const_defined?('EDITABLE_ATTRS')
|
if channel_type.constantize.const_defined?('EDITABLE_ATTRS')
|
||||||
channel_type.constantize::EDITABLE_ATTRS.presence
|
channel_type.constantize::EDITABLE_ATTRS.presence
|
||||||
@ -130,4 +144,10 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_limit
|
||||||
|
return unless Current.account.inboxes.count >= Current.account.usage_limits[:inboxes]
|
||||||
|
|
||||||
|
render_payment_required('Account limit exceeded. Upgrade to a higher plan')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -38,6 +38,7 @@ class Api::V1::ProfilesController < Api::BaseController
|
|||||||
:name,
|
:name,
|
||||||
:display_name,
|
:display_name,
|
||||||
:avatar,
|
:avatar,
|
||||||
|
:message_signature,
|
||||||
ui_settings: {}
|
ui_settings: {}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -29,11 +29,12 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
|||||||
return if params[:message][:attachments].blank?
|
return if params[:message][:attachments].blank?
|
||||||
|
|
||||||
params[:message][:attachments].each do |uploaded_attachment|
|
params[:message][:attachments].each do |uploaded_attachment|
|
||||||
@message.attachments.new(
|
attachment = @message.attachments.new(
|
||||||
account_id: @message.account_id,
|
account_id: @message.account_id,
|
||||||
file_type: helpers.file_type(uploaded_attachment&.content_type),
|
|
||||||
file: uploaded_attachment
|
file: uploaded_attachment
|
||||||
)
|
)
|
||||||
|
|
||||||
|
attachment.file_type = helpers.file_type(uploaded_attachment&.content_type) if uploaded_attachment.is_a?(ActionDispatch::Http::UploadedFile)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,8 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||||||
type: params[:type].to_sym,
|
type: params[:type].to_sym,
|
||||||
since: params[:since],
|
since: params[:since],
|
||||||
until: params[:until],
|
until: params[:until],
|
||||||
id: params[:id]
|
id: params[:id],
|
||||||
|
group_by: params[:group_by]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -56,7 +57,8 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||||||
type: params[:type].to_sym,
|
type: params[:type].to_sym,
|
||||||
since: params[:since],
|
since: params[:since],
|
||||||
until: params[:until],
|
until: params[:until],
|
||||||
id: params[:id]
|
id: params[:id],
|
||||||
|
group_by: params[:group_by]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,8 @@ class DashboardController < ActionController::Base
|
|||||||
'API_CHANNEL_NAME',
|
'API_CHANNEL_NAME',
|
||||||
'API_CHANNEL_THUMBNAIL',
|
'API_CHANNEL_THUMBNAIL',
|
||||||
'ANALYTICS_TOKEN',
|
'ANALYTICS_TOKEN',
|
||||||
'ANALYTICS_HOST'
|
'ANALYTICS_HOST',
|
||||||
|
'DIRECT_UPLOADS_ENABLED'
|
||||||
).merge(app_config)
|
).merge(app_config)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ class Installation::OnboardingController < ApplicationController
|
|||||||
user_full_name: onboarding_params.dig(:user, :name),
|
user_full_name: onboarding_params.dig(:user, :name),
|
||||||
email: onboarding_params.dig(:user, :email),
|
email: onboarding_params.dig(:user, :email),
|
||||||
user_password: params.dig(:user, :password),
|
user_password: params.dig(:user, :password),
|
||||||
|
super_admin: true,
|
||||||
confirmed: true
|
confirmed: true
|
||||||
).perform
|
).perform
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
|
|||||||
@ -13,7 +13,8 @@ class Platform::Api::V1::UsersController < PlatformController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def login
|
def login
|
||||||
render json: { url: "#{ENV['FRONTEND_URL']}/app/login?email=#{@resource.email}&sso_auth_token=#{@resource.generate_sso_auth_token}" }
|
encoded_email = ERB::Util.url_encode(@resource.email)
|
||||||
|
render json: { url: "#{ENV['FRONTEND_URL']}/app/login?email=#{encoded_email}&sso_auth_token=#{@resource.generate_sso_auth_token}" }
|
||||||
end
|
end
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|||||||
@ -33,11 +33,11 @@ class SuperAdmin::AccountsController < SuperAdmin::ApplicationController
|
|||||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||||
# and `dashboard`:
|
# and `dashboard`:
|
||||||
#
|
#
|
||||||
# def resource_params
|
def resource_params
|
||||||
# params.require(resource_class.model_name.param_key).
|
permitted_params = super
|
||||||
# permit(dashboard.permitted_attributes).
|
permitted_params[:limits] = permitted_params[:limits].to_h.compact
|
||||||
# transform_values { |value| value == "" ? nil : value }
|
permitted_params
|
||||||
# end
|
end
|
||||||
|
|
||||||
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||||
# for more information
|
# for more information
|
||||||
|
|||||||
@ -8,7 +8,7 @@ class SuperAdmin::Devise::SessionsController < Devise::SessionsController
|
|||||||
def create
|
def create
|
||||||
redirect_to(super_admin_session_path, flash: { error: @error_message }) && return unless valid_credentials?
|
redirect_to(super_admin_session_path, flash: { error: @error_message }) && return unless valid_credentials?
|
||||||
|
|
||||||
sign_in(@super_admin, scope: :super_admin)
|
sign_in(:super_admin, @super_admin)
|
||||||
flash.discard
|
flash.discard
|
||||||
redirect_to super_admin_users_path
|
redirect_to super_admin_users_path
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,44 +0,0 @@
|
|||||||
class SuperAdmin::SuperAdminsController < SuperAdmin::ApplicationController
|
|
||||||
# Overwrite any of the RESTful controller actions to implement custom behavior
|
|
||||||
# For example, you may want to send an email after a foo is updated.
|
|
||||||
#
|
|
||||||
# def update
|
|
||||||
# super
|
|
||||||
# send_foo_updated_email(requested_resource)
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Override this method to specify custom lookup behavior.
|
|
||||||
# This will be used to set the resource for the `show`, `edit`, and `update`
|
|
||||||
# actions.
|
|
||||||
#
|
|
||||||
# def find_resource(param)
|
|
||||||
# Foo.find_by!(slug: param)
|
|
||||||
# end
|
|
||||||
|
|
||||||
# The result of this lookup will be available as `requested_resource`
|
|
||||||
|
|
||||||
# Override this if you have certain roles that require a subset
|
|
||||||
# this will be used to set the records shown on the `index` action.
|
|
||||||
#
|
|
||||||
# def scoped_resource
|
|
||||||
# if current_user.super_admin?
|
|
||||||
# resource_class
|
|
||||||
# else
|
|
||||||
# resource_class.with_less_stuff
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Override `resource_params` if you want to transform the submitted
|
|
||||||
# data before it's persisted. For example, the following would turn all
|
|
||||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
|
||||||
# and `dashboard`:
|
|
||||||
#
|
|
||||||
# def resource_params
|
|
||||||
# params.require(resource_class.model_name.param_key).
|
|
||||||
# permit(dashboard.permitted_attributes).
|
|
||||||
# transform_values { |value| value == "" ? nil : value }
|
|
||||||
# end
|
|
||||||
|
|
||||||
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
|
||||||
# for more information
|
|
||||||
end
|
|
||||||
@ -33,12 +33,15 @@ class SuperAdmin::UsersController < SuperAdmin::ApplicationController
|
|||||||
# empty values into nil values. It uses other APIs such as `resource_class`
|
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||||
# and `dashboard`:
|
# and `dashboard`:
|
||||||
#
|
#
|
||||||
# def resource_params
|
def resource_params
|
||||||
# params.require(resource_class.model_name.param_key).
|
permitted_params = super
|
||||||
# permit(dashboard.permitted_attributes).
|
permitted_params.delete(:password) if permitted_params[:password].blank?
|
||||||
# transform_values { |value| value == "" ? nil : value }
|
permitted_params
|
||||||
# end
|
end
|
||||||
|
|
||||||
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||||
# for more information
|
# for more information
|
||||||
|
def find_resource(param)
|
||||||
|
super.becomes(User)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
6
app/controllers/webhooks/sms_controller.rb
Normal file
6
app/controllers/webhooks/sms_controller.rb
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
class Webhooks::SmsController < ActionController::API
|
||||||
|
def process_payload
|
||||||
|
Webhooks::SmsEventsJob.perform_later(params['_json']&.first&.to_unsafe_hash)
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -10,7 +10,7 @@ class WidgetsController < ActionController::Base
|
|||||||
private
|
private
|
||||||
|
|
||||||
def set_global_config
|
def set_global_config
|
||||||
@global_config = GlobalConfig.get('LOGO_THUMBNAIL', 'BRAND_NAME', 'WIDGET_BRAND_URL')
|
@global_config = GlobalConfig.get('LOGO_THUMBNAIL', 'BRAND_NAME', 'WIDGET_BRAND_URL', 'DIRECT_UPLOADS_ENABLED')
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_web_widget
|
def set_web_widget
|
||||||
|
|||||||
@ -7,6 +7,8 @@ class AccountDashboard < Administrate::BaseDashboard
|
|||||||
# Each different type represents an Administrate::Field object,
|
# Each different type represents an Administrate::Field object,
|
||||||
# which determines how the attribute is displayed
|
# which determines how the attribute is displayed
|
||||||
# on pages throughout the dashboard.
|
# on pages throughout the dashboard.
|
||||||
|
|
||||||
|
enterprise_attribute_types = ChatwootApp.enterprise? ? { limits: Enterprise::AccountLimitsField } : {}
|
||||||
ATTRIBUTE_TYPES = {
|
ATTRIBUTE_TYPES = {
|
||||||
id: Field::Number,
|
id: Field::Number,
|
||||||
name: Field::String,
|
name: Field::String,
|
||||||
@ -16,7 +18,7 @@ class AccountDashboard < Administrate::BaseDashboard
|
|||||||
conversations: CountField,
|
conversations: CountField,
|
||||||
locale: Field::Select.with_options(collection: LANGUAGES_CONFIG.map { |_x, y| y[:iso_639_1_code] }),
|
locale: Field::Select.with_options(collection: LANGUAGES_CONFIG.map { |_x, y| y[:iso_639_1_code] }),
|
||||||
account_users: Field::HasMany
|
account_users: Field::HasMany
|
||||||
}.freeze
|
}.merge(enterprise_attribute_types).freeze
|
||||||
|
|
||||||
# COLLECTION_ATTRIBUTES
|
# COLLECTION_ATTRIBUTES
|
||||||
# an array of attributes that will be displayed on the model's index page.
|
# an array of attributes that will be displayed on the model's index page.
|
||||||
@ -33,7 +35,8 @@ class AccountDashboard < Administrate::BaseDashboard
|
|||||||
|
|
||||||
# SHOW_PAGE_ATTRIBUTES
|
# SHOW_PAGE_ATTRIBUTES
|
||||||
# an array of attributes that will be displayed on the model's show page.
|
# an array of attributes that will be displayed on the model's show page.
|
||||||
SHOW_PAGE_ATTRIBUTES = %i[
|
enterprise_show_page_attributes = ChatwootApp.enterprise? ? %i[limits] : []
|
||||||
|
SHOW_PAGE_ATTRIBUTES = (%i[
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
created_at
|
created_at
|
||||||
@ -41,15 +44,16 @@ class AccountDashboard < Administrate::BaseDashboard
|
|||||||
locale
|
locale
|
||||||
conversations
|
conversations
|
||||||
account_users
|
account_users
|
||||||
].freeze
|
] + enterprise_show_page_attributes).freeze
|
||||||
|
|
||||||
# FORM_ATTRIBUTES
|
# FORM_ATTRIBUTES
|
||||||
# an array of attributes that will be displayed
|
# an array of attributes that will be displayed
|
||||||
# on the model's form (`new` and `edit`) pages.
|
# on the model's form (`new` and `edit`) pages.
|
||||||
FORM_ATTRIBUTES = %i[
|
enterprise_form_attributes = ChatwootApp.enterprise? ? %i[limits] : []
|
||||||
|
FORM_ATTRIBUTES = (%i[
|
||||||
name
|
name
|
||||||
locale
|
locale
|
||||||
].freeze
|
] + enterprise_form_attributes).freeze
|
||||||
|
|
||||||
# COLLECTION_FILTERS
|
# COLLECTION_FILTERS
|
||||||
# a hash that defines filters that can be used while searching via the search
|
# a hash that defines filters that can be used while searching via the search
|
||||||
@ -69,4 +73,8 @@ class AccountDashboard < Administrate::BaseDashboard
|
|||||||
def display_resource(account)
|
def display_resource(account)
|
||||||
"##{account.id} #{account.name}"
|
"##{account.id} #{account.name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def permitted_attributes
|
||||||
|
super + [limits: {}]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,75 +0,0 @@
|
|||||||
require 'administrate/base_dashboard'
|
|
||||||
|
|
||||||
class SuperAdminDashboard < Administrate::BaseDashboard
|
|
||||||
# ATTRIBUTE_TYPES
|
|
||||||
# a hash that describes the type of each of the model's fields.
|
|
||||||
#
|
|
||||||
# Each different type represents an Administrate::Field object,
|
|
||||||
# which determines how the attribute is displayed
|
|
||||||
# on pages throughout the dashboard.
|
|
||||||
ATTRIBUTE_TYPES = {
|
|
||||||
id: Field::Number,
|
|
||||||
email: Field::String,
|
|
||||||
password: Field::Password,
|
|
||||||
remember_created_at: Field::DateTime,
|
|
||||||
sign_in_count: Field::Number,
|
|
||||||
current_sign_in_at: Field::DateTime,
|
|
||||||
last_sign_in_at: Field::DateTime,
|
|
||||||
current_sign_in_ip: Field::String.with_options(searchable: false),
|
|
||||||
last_sign_in_ip: Field::String.with_options(searchable: false),
|
|
||||||
created_at: Field::DateTime,
|
|
||||||
updated_at: Field::DateTime
|
|
||||||
}.freeze
|
|
||||||
|
|
||||||
# COLLECTION_ATTRIBUTES
|
|
||||||
# an array of attributes that will be displayed on the model's index page.
|
|
||||||
#
|
|
||||||
# By default, it's limited to four items to reduce clutter on index pages.
|
|
||||||
# Feel free to add, remove, or rearrange items.
|
|
||||||
COLLECTION_ATTRIBUTES = %i[
|
|
||||||
id
|
|
||||||
email
|
|
||||||
].freeze
|
|
||||||
|
|
||||||
# SHOW_PAGE_ATTRIBUTES
|
|
||||||
# an array of attributes that will be displayed on the model's show page.
|
|
||||||
SHOW_PAGE_ATTRIBUTES = %i[
|
|
||||||
id
|
|
||||||
email
|
|
||||||
remember_created_at
|
|
||||||
sign_in_count
|
|
||||||
current_sign_in_at
|
|
||||||
last_sign_in_at
|
|
||||||
current_sign_in_ip
|
|
||||||
last_sign_in_ip
|
|
||||||
created_at
|
|
||||||
updated_at
|
|
||||||
].freeze
|
|
||||||
|
|
||||||
# FORM_ATTRIBUTES
|
|
||||||
# an array of attributes that will be displayed
|
|
||||||
# on the model's form (`new` and `edit`) pages.
|
|
||||||
FORM_ATTRIBUTES = %i[
|
|
||||||
email
|
|
||||||
password
|
|
||||||
].freeze
|
|
||||||
|
|
||||||
# COLLECTION_FILTERS
|
|
||||||
# a hash that defines filters that can be used while searching via the search
|
|
||||||
# field of the dashboard.
|
|
||||||
#
|
|
||||||
# For example to add an option to search for open resources by typing "open:"
|
|
||||||
# in the search field:
|
|
||||||
#
|
|
||||||
# COLLECTION_FILTERS = {
|
|
||||||
# open: ->(resources) { resources.where(open: true) }
|
|
||||||
# }.freeze
|
|
||||||
COLLECTION_FILTERS = {}.freeze
|
|
||||||
|
|
||||||
# Overwrite this method to customize how super admins are displayed
|
|
||||||
# across all pages of the admin dashboard.
|
|
||||||
#
|
|
||||||
# def display_resource(super_admin)
|
|
||||||
# "SuperAdmin ##{super_admin.id}"
|
|
||||||
# end
|
|
||||||
end
|
|
||||||
@ -30,6 +30,7 @@ class UserDashboard < Administrate::BaseDashboard
|
|||||||
created_at: Field::DateTime,
|
created_at: Field::DateTime,
|
||||||
updated_at: Field::DateTime,
|
updated_at: Field::DateTime,
|
||||||
pubsub_token: Field::String,
|
pubsub_token: Field::String,
|
||||||
|
type: Field::Select.with_options(collection: [nil, 'SuperAdmin']),
|
||||||
accounts: CountField
|
accounts: CountField
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ class UserDashboard < Administrate::BaseDashboard
|
|||||||
name
|
name
|
||||||
email
|
email
|
||||||
accounts
|
accounts
|
||||||
|
type
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
# SHOW_PAGE_ATTRIBUTES
|
# SHOW_PAGE_ATTRIBUTES
|
||||||
@ -53,10 +55,12 @@ class UserDashboard < Administrate::BaseDashboard
|
|||||||
avatar_url
|
avatar_url
|
||||||
unconfirmed_email
|
unconfirmed_email
|
||||||
name
|
name
|
||||||
|
type
|
||||||
display_name
|
display_name
|
||||||
email
|
email
|
||||||
created_at
|
created_at
|
||||||
updated_at
|
updated_at
|
||||||
|
confirmed_at
|
||||||
account_users
|
account_users
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
@ -68,6 +72,8 @@ class UserDashboard < Administrate::BaseDashboard
|
|||||||
display_name
|
display_name
|
||||||
email
|
email
|
||||||
password
|
password
|
||||||
|
confirmed_at
|
||||||
|
type
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
# COLLECTION_FILTERS
|
# COLLECTION_FILTERS
|
||||||
|
|||||||
7
app/fields/enterprise/account_limits_field.rb
Normal file
7
app/fields/enterprise/account_limits_field.rb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
require 'administrate/field/base'
|
||||||
|
|
||||||
|
class Enterprise::AccountLimitsField < Administrate::Field::Base
|
||||||
|
def to_s
|
||||||
|
data.present? ? data.to_json : { agents: nil, inboxes: nil }.to_json
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -55,7 +55,7 @@ class ConversationFinder
|
|||||||
|
|
||||||
def set_inboxes
|
def set_inboxes
|
||||||
@inbox_ids = if params[:inbox_id]
|
@inbox_ids = if params[:inbox_id]
|
||||||
current_account.inboxes.where(id: params[:inbox_id])
|
@current_user.assigned_inboxes.where(id: params[:inbox_id])
|
||||||
else
|
else
|
||||||
@current_user.assigned_inboxes.pluck(:id)
|
@current_user.assigned_inboxes.pluck(:id)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -26,8 +26,48 @@ module Api::V1::InboxesHelper
|
|||||||
def validate_smtp(channel_data)
|
def validate_smtp(channel_data)
|
||||||
return unless channel_data.key?('smtp_enabled') && channel_data[:smtp_enabled]
|
return unless channel_data.key?('smtp_enabled') && channel_data[:smtp_enabled]
|
||||||
|
|
||||||
smtp = Net::SMTP.start(channel_data[:smtp_address], channel_data[:smtp_port], channel_data[:smtp_domain], channel_data[:smtp_email],
|
smtp = Net::SMTP.new(channel_data[:smtp_address], channel_data[:smtp_port])
|
||||||
channel_data[:smtp_password], :login)
|
|
||||||
|
set_smtp_encryption(channel_data, smtp)
|
||||||
|
|
||||||
|
smtp.start(channel_data[:smtp_domain], channel_data[:smtp_email], channel_data[:smtp_password], :login)
|
||||||
smtp.finish unless smtp&.nil?
|
smtp.finish unless smtp&.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_smtp_encryption(channel_data, smtp)
|
||||||
|
if channel_data[:smtp_enable_ssl_tls]
|
||||||
|
set_enable_tls(channel_data, smtp)
|
||||||
|
elsif channel_data[:smtp_enable_starttls_auto]
|
||||||
|
set_enable_starttls_auto(channel_data, smtp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_enable_starttls_auto(channel_data, smtp)
|
||||||
|
return unless smtp.respond_to?(:enable_starttls_auto)
|
||||||
|
|
||||||
|
if channel_data[:smtp_openssl_verify_mode]
|
||||||
|
context = enable_openssl_mode(channel_data[:smtp_openssl_verify_mode])
|
||||||
|
smtp.enable_starttls_auto(context)
|
||||||
|
else
|
||||||
|
smtp.enable_starttls_auto
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_enable_tls(channel_data, smtp)
|
||||||
|
return unless smtp.respond_to?(:enable_tls)
|
||||||
|
|
||||||
|
if channel_data[:smtp_openssl_verify_mode]
|
||||||
|
context = enable_openssl_mode(channel_data[:smtp_openssl_verify_mode])
|
||||||
|
smtp.enable_tls(context)
|
||||||
|
else
|
||||||
|
smtp.enable_tls
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def enable_openssl_mode(smtp_openssl_verify_mode)
|
||||||
|
openssl_verify_mode = "OpenSSL::SSL::VERIFY_#{smtp_openssl_verify_mode.upcase}".constantize if smtp_openssl_verify_mode.is_a?(String)
|
||||||
|
context = Net::SMTP.default_ssl_context
|
||||||
|
context.verify_mode = openssl_verify_mode
|
||||||
|
context
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -138,13 +138,14 @@ export default {
|
|||||||
password,
|
password,
|
||||||
password_confirmation,
|
password_confirmation,
|
||||||
displayName,
|
displayName,
|
||||||
|
avatar,
|
||||||
...profileAttributes
|
...profileAttributes
|
||||||
}) {
|
}) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
Object.keys(profileAttributes).forEach(key => {
|
Object.keys(profileAttributes).forEach(key => {
|
||||||
const value = profileAttributes[key];
|
const hasValue = profileAttributes[key] === undefined;
|
||||||
if (value) {
|
if (!hasValue) {
|
||||||
formData.append(`profile[${key}]`, value);
|
formData.append(`profile[${key}]`, profileAttributes[key]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
formData.append('profile[display_name]', displayName || '');
|
formData.append('profile[display_name]', displayName || '');
|
||||||
@ -152,6 +153,9 @@ export default {
|
|||||||
formData.append('profile[password]', password);
|
formData.append('profile[password]', password);
|
||||||
formData.append('profile[password_confirmation]', password_confirmation);
|
formData.append('profile[password_confirmation]', password_confirmation);
|
||||||
}
|
}
|
||||||
|
if (avatar) {
|
||||||
|
formData.append('profile[avatar]', avatar);
|
||||||
|
}
|
||||||
return axios.put(endPoints('profileUpdate').url, formData);
|
return axios.put(endPoints('profileUpdate').url, formData);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
14
app/javascript/dashboard/api/automation.js
Normal file
14
app/javascript/dashboard/api/automation.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* global axios */
|
||||||
|
import ApiClient from './ApiClient';
|
||||||
|
|
||||||
|
class AutomationsAPI extends ApiClient {
|
||||||
|
constructor() {
|
||||||
|
super('automation_rules', { accountScoped: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
clone(automationId) {
|
||||||
|
return axios.post(`${this.url}/${automationId}/clone`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new AutomationsAPI();
|
||||||
18
app/javascript/dashboard/api/customViews.js
Normal file
18
app/javascript/dashboard/api/customViews.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/* global axios */
|
||||||
|
import ApiClient from './ApiClient';
|
||||||
|
|
||||||
|
class CustomViewsAPI extends ApiClient {
|
||||||
|
constructor() {
|
||||||
|
super('custom_filters', { accountScoped: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomViewsByFilterType(type) {
|
||||||
|
return axios.get(`${this.url}?filter_type=${type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCustomViews(id, type) {
|
||||||
|
return axios.delete(`${this.url}/${id}?filter_type=${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new CustomViewsAPI();
|
||||||
@ -7,17 +7,19 @@ export const buildCreatePayload = ({
|
|||||||
isPrivate,
|
isPrivate,
|
||||||
contentAttributes,
|
contentAttributes,
|
||||||
echoId,
|
echoId,
|
||||||
file,
|
files,
|
||||||
ccEmails = '',
|
ccEmails = '',
|
||||||
bccEmails = '',
|
bccEmails = '',
|
||||||
}) => {
|
}) => {
|
||||||
let payload;
|
let payload;
|
||||||
if (file) {
|
if (files && files.length !== 0) {
|
||||||
payload = new FormData();
|
payload = new FormData();
|
||||||
payload.append('attachments[]', file, file.name);
|
|
||||||
if (message) {
|
if (message) {
|
||||||
payload.append('content', message);
|
payload.append('content', message);
|
||||||
}
|
}
|
||||||
|
files.forEach(file => {
|
||||||
|
payload.append('attachments[]', file);
|
||||||
|
});
|
||||||
payload.append('private', isPrivate);
|
payload.append('private', isPrivate);
|
||||||
payload.append('echo_id', echoId);
|
payload.append('echo_id', echoId);
|
||||||
payload.append('cc_emails', ccEmails);
|
payload.append('cc_emails', ccEmails);
|
||||||
@ -46,7 +48,7 @@ class MessageApi extends ApiClient {
|
|||||||
private: isPrivate,
|
private: isPrivate,
|
||||||
contentAttributes,
|
contentAttributes,
|
||||||
echo_id: echoId,
|
echo_id: echoId,
|
||||||
file,
|
files,
|
||||||
ccEmails = '',
|
ccEmails = '',
|
||||||
bccEmails = '',
|
bccEmails = '',
|
||||||
}) {
|
}) {
|
||||||
@ -58,7 +60,7 @@ class MessageApi extends ApiClient {
|
|||||||
isPrivate,
|
isPrivate,
|
||||||
contentAttributes,
|
contentAttributes,
|
||||||
echoId,
|
echoId,
|
||||||
file,
|
files,
|
||||||
ccEmails,
|
ccEmails,
|
||||||
bccEmails,
|
bccEmails,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -6,15 +6,15 @@ class ReportsAPI extends ApiClient {
|
|||||||
super('reports', { accountScoped: true, apiVersion: 'v2' });
|
super('reports', { accountScoped: true, apiVersion: 'v2' });
|
||||||
}
|
}
|
||||||
|
|
||||||
getReports(metric, since, until, type = 'account', id) {
|
getReports(metric, since, until, type = 'account', id, group_by) {
|
||||||
return axios.get(`${this.url}`, {
|
return axios.get(`${this.url}`, {
|
||||||
params: { metric, since, until, type, id },
|
params: { metric, since, until, type, id, group_by },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getSummary(since, until, type = 'account', id) {
|
getSummary(since, until, type = 'account', id, group_by) {
|
||||||
return axios.get(`${this.url}/summary`, {
|
return axios.get(`${this.url}/summary`, {
|
||||||
params: { since, until, type, id },
|
params: { since, until, type, id, group_by },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
app/javascript/dashboard/api/specs/automation.spec.js
Normal file
15
app/javascript/dashboard/api/specs/automation.spec.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import automations from '../automation';
|
||||||
|
import ApiClient from '../ApiClient';
|
||||||
|
|
||||||
|
describe('#AutomationsAPI', () => {
|
||||||
|
it('creates correct instance', () => {
|
||||||
|
expect(automations).toBeInstanceOf(ApiClient);
|
||||||
|
expect(automations).toHaveProperty('get');
|
||||||
|
expect(automations).toHaveProperty('show');
|
||||||
|
expect(automations).toHaveProperty('create');
|
||||||
|
expect(automations).toHaveProperty('update');
|
||||||
|
expect(automations).toHaveProperty('delete');
|
||||||
|
expect(automations).toHaveProperty('clone');
|
||||||
|
expect(automations.url).toBe('/api/v1/automation_rules');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -36,7 +36,7 @@ describe('#ConversationAPI', () => {
|
|||||||
echoId: 12,
|
echoId: 12,
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
|
|
||||||
file: new Blob(['test-content'], { type: 'application/pdf' }),
|
files: [new Blob(['test-content'], { type: 'application/pdf' })],
|
||||||
});
|
});
|
||||||
expect(formPayload).toBeInstanceOf(FormData);
|
expect(formPayload).toBeInstanceOf(FormData);
|
||||||
expect(formPayload.get('content')).toEqual('test content');
|
expect(formPayload.get('content')).toEqual('test content');
|
||||||
|
|||||||
@ -228,8 +228,14 @@
|
|||||||
@include flex-align(right, null);
|
@include flex-align(right, null);
|
||||||
|
|
||||||
.wrap {
|
.wrap {
|
||||||
|
align-items: flex-end;
|
||||||
|
display: flex;
|
||||||
margin-right: $space-normal;
|
margin-right: $space-normal;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
||||||
|
.sender--info {
|
||||||
|
padding: var(--space-small) 0 var(--space-smaller) var(--space-small);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bubble {
|
.bubble {
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
@include shadow;
|
@include shadow;
|
||||||
background-color: $woot-snackbar-bg;
|
background-color: $woot-snackbar-bg;
|
||||||
border-radius: $space-smaller;
|
border-radius: $space-smaller;
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
margin-bottom: $space-small;
|
margin-bottom: $space-small;
|
||||||
max-width: 40rem;
|
max-width: 40rem;
|
||||||
min-height: 3rem;
|
min-height: 3rem;
|
||||||
|
|||||||
@ -1,26 +1,51 @@
|
|||||||
l<template>
|
<template>
|
||||||
<div class="conversations-list-wrap">
|
<div class="conversations-list-wrap">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<div class="chat-list__top" :class="{ filter__applied: hasAppliedFilters }">
|
<div
|
||||||
|
class="chat-list__top"
|
||||||
|
:class="{ filter__applied: hasAppliedFiltersOrActiveFolders }"
|
||||||
|
>
|
||||||
<h1 class="page-title text-truncate" :title="pageTitle">
|
<h1 class="page-title text-truncate" :title="pageTitle">
|
||||||
{{ pageTitle }}
|
{{ pageTitle }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="filter--actions">
|
<div class="filter--actions">
|
||||||
<chat-filter
|
<chat-filter
|
||||||
v-if="!hasAppliedFilters"
|
v-if="!hasAppliedFiltersOrActiveFolders"
|
||||||
@statusFilterChange="updateStatusType"
|
@statusFilterChange="updateStatusType"
|
||||||
/>
|
/>
|
||||||
|
<div v-if="hasAppliedFilters && !hasActiveFolders">
|
||||||
|
<woot-button
|
||||||
|
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.ADD.SAVE_BUTTON')"
|
||||||
|
size="tiny"
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="secondary"
|
||||||
|
icon="save"
|
||||||
|
@click="onClickOpenAddFoldersModal"
|
||||||
|
/>
|
||||||
|
<woot-button
|
||||||
|
v-tooltip.top-end="$t('FILTER.CLEAR_BUTTON_LABEL')"
|
||||||
|
size="tiny"
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="alert"
|
||||||
|
icon="dismiss-circle"
|
||||||
|
@click="resetAndFetchData"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="hasActiveFolders">
|
||||||
|
<woot-button
|
||||||
|
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
|
||||||
|
size="tiny"
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="alert"
|
||||||
|
icon="delete"
|
||||||
|
class="delete-custom-view__button"
|
||||||
|
@click="onClickOpenDeleteFoldersModal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<woot-button
|
<woot-button
|
||||||
v-else
|
v-else
|
||||||
size="small"
|
|
||||||
variant="clear"
|
|
||||||
color-scheme="alert"
|
|
||||||
@click="resetAndFetchData"
|
|
||||||
>
|
|
||||||
{{ $t('FILTER.CLEAR_BUTTON_LABEL') }}
|
|
||||||
</woot-button>
|
|
||||||
<woot-button
|
|
||||||
v-tooltip.top-end="$t('FILTER.TOOLTIP_LABEL')"
|
v-tooltip.top-end="$t('FILTER.TOOLTIP_LABEL')"
|
||||||
variant="clear"
|
variant="clear"
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
@ -33,8 +58,24 @@ l<template>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<add-custom-views
|
||||||
|
v-if="showAddFoldersModal"
|
||||||
|
:custom-views-query="foldersQuery"
|
||||||
|
:open-last-saved-item="openLastSavedItemInFolder"
|
||||||
|
@close="onCloseAddFoldersModal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<delete-custom-views
|
||||||
|
v-if="showDeleteFoldersModal"
|
||||||
|
:show-delete-popup.sync="showDeleteFoldersModal"
|
||||||
|
:active-custom-view="activeFolder"
|
||||||
|
:custom-views-id="foldersId"
|
||||||
|
:open-last-item-after-delete="openLastItemAfterDeleteInFolder"
|
||||||
|
@close="onCloseDeleteFoldersModal"
|
||||||
|
/>
|
||||||
|
|
||||||
<chat-type-tabs
|
<chat-type-tabs
|
||||||
v-if="!hasAppliedFilters"
|
v-if="!hasAppliedFiltersOrActiveFolders"
|
||||||
:items="assigneeTabItems"
|
:items="assigneeTabItems"
|
||||||
:active-tab="activeAssigneeTab"
|
:active-tab="activeAssigneeTab"
|
||||||
class="tab--chat-type"
|
class="tab--chat-type"
|
||||||
@ -51,6 +92,7 @@ l<template>
|
|||||||
:key="chat.id"
|
:key="chat.id"
|
||||||
:active-label="label"
|
:active-label="label"
|
||||||
:team-id="teamId"
|
:team-id="teamId"
|
||||||
|
:folders-id="foldersId"
|
||||||
:chat="chat"
|
:chat="chat"
|
||||||
:conversation-type="conversationType"
|
:conversation-type="conversationType"
|
||||||
:show-assignee="showAssigneeInConversationCard"
|
:show-assignee="showAssigneeInConversationCard"
|
||||||
@ -87,7 +129,7 @@ l<template>
|
|||||||
>
|
>
|
||||||
<conversation-advanced-filter
|
<conversation-advanced-filter
|
||||||
v-if="showAdvancedFilters"
|
v-if="showAdvancedFilters"
|
||||||
:filter-types="advancedFilterTypes"
|
:initial-filter-types="advancedFilterTypes"
|
||||||
:on-close="onToggleAdvanceFiltersModal"
|
:on-close="onToggleAdvanceFiltersModal"
|
||||||
@applyFilter="onApplyFilter"
|
@applyFilter="onApplyFilter"
|
||||||
/>
|
/>
|
||||||
@ -108,6 +150,8 @@ import conversationMixin from '../mixins/conversations';
|
|||||||
import wootConstants from '../constants';
|
import wootConstants from '../constants';
|
||||||
import advancedFilterTypes from './widgets/conversation/advancedFilterItems';
|
import advancedFilterTypes from './widgets/conversation/advancedFilterItems';
|
||||||
import filterQueryGenerator from '../helper/filterQueryGenerator.js';
|
import filterQueryGenerator from '../helper/filterQueryGenerator.js';
|
||||||
|
import AddCustomViews from 'dashboard/routes/dashboard/customviews/AddCustomViews';
|
||||||
|
import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCustomViews.vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
hasPressedAltAndJKey,
|
hasPressedAltAndJKey,
|
||||||
@ -116,10 +160,12 @@ import {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
AddCustomViews,
|
||||||
ChatTypeTabs,
|
ChatTypeTabs,
|
||||||
ConversationCard,
|
ConversationCard,
|
||||||
ChatFilter,
|
ChatFilter,
|
||||||
ConversationAdvancedFilter,
|
ConversationAdvancedFilter,
|
||||||
|
DeleteCustomViews,
|
||||||
},
|
},
|
||||||
mixins: [timeMixin, conversationMixin, eventListenerMixins],
|
mixins: [timeMixin, conversationMixin, eventListenerMixins],
|
||||||
props: {
|
props: {
|
||||||
@ -139,6 +185,10 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
foldersId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -149,6 +199,9 @@ export default {
|
|||||||
...filter,
|
...filter,
|
||||||
attributeName: this.$t(`FILTER.ATTRIBUTES.${filter.attributeI18nKey}`),
|
attributeName: this.$t(`FILTER.ATTRIBUTES.${filter.attributeI18nKey}`),
|
||||||
})),
|
})),
|
||||||
|
foldersQuery: {},
|
||||||
|
showAddFoldersModal: false,
|
||||||
|
showDeleteFoldersModal: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -163,10 +216,24 @@ export default {
|
|||||||
activeInbox: 'getSelectedInbox',
|
activeInbox: 'getSelectedInbox',
|
||||||
conversationStats: 'conversationStats/getStats',
|
conversationStats: 'conversationStats/getStats',
|
||||||
appliedFilters: 'getAppliedConversationFilters',
|
appliedFilters: 'getAppliedConversationFilters',
|
||||||
|
folders: 'customViews/getCustomViews',
|
||||||
}),
|
}),
|
||||||
hasAppliedFilters() {
|
hasAppliedFilters() {
|
||||||
return this.appliedFilters.length;
|
return this.appliedFilters.length;
|
||||||
},
|
},
|
||||||
|
hasActiveFolders() {
|
||||||
|
return this.activeFolder && this.foldersId !== 0;
|
||||||
|
},
|
||||||
|
hasAppliedFiltersOrActiveFolders() {
|
||||||
|
return this.hasAppliedFilters || this.hasActiveFolders;
|
||||||
|
},
|
||||||
|
savedFoldersValue() {
|
||||||
|
if (this.hasActiveFolders) {
|
||||||
|
const payload = this.activeFolder.query;
|
||||||
|
this.fetchSavedFilteredConversations(payload);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
assigneeTabItems() {
|
assigneeTabItems() {
|
||||||
return this.$t('CHAT_LIST.ASSIGNEE_TYPE_TABS').map(item => {
|
return this.$t('CHAT_LIST.ASSIGNEE_TYPE_TABS').map(item => {
|
||||||
const count = this.conversationStats[item.COUNT_KEY] || 0;
|
const count = this.conversationStats[item.COUNT_KEY] || 0;
|
||||||
@ -178,7 +245,10 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
showAssigneeInConversationCard() {
|
showAssigneeInConversationCard() {
|
||||||
return this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE.ALL;
|
return (
|
||||||
|
this.hasAppliedFiltersOrActiveFolders ||
|
||||||
|
this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE.ALL
|
||||||
|
);
|
||||||
},
|
},
|
||||||
inbox() {
|
inbox() {
|
||||||
return this.$store.getters['inboxes/getInbox'](this.activeInbox);
|
return this.$store.getters['inboxes/getInbox'](this.activeInbox);
|
||||||
@ -189,7 +259,9 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
currentPageFilterKey() {
|
currentPageFilterKey() {
|
||||||
return this.hasAppliedFilters ? 'appliedFilters' : this.activeAssigneeTab;
|
return this.hasAppliedFiltersOrActiveFolders
|
||||||
|
? 'appliedFilters'
|
||||||
|
: this.activeAssigneeTab;
|
||||||
},
|
},
|
||||||
currentFiltersPage() {
|
currentFiltersPage() {
|
||||||
return this.$store.getters['conversationPage/getCurrentPageFilter'](
|
return this.$store.getters['conversationPage/getCurrentPageFilter'](
|
||||||
@ -212,6 +284,7 @@ export default {
|
|||||||
conversationType: this.conversationType
|
conversationType: this.conversationType
|
||||||
? this.conversationType
|
? this.conversationType
|
||||||
: undefined,
|
: undefined,
|
||||||
|
folders: this.hasActiveFolders ? this.savedFoldersValue : undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
pageTitle() {
|
pageTitle() {
|
||||||
@ -227,11 +300,14 @@ export default {
|
|||||||
if (this.conversationType === 'mention') {
|
if (this.conversationType === 'mention') {
|
||||||
return this.$t('CHAT_LIST.MENTION_HEADING');
|
return this.$t('CHAT_LIST.MENTION_HEADING');
|
||||||
}
|
}
|
||||||
|
if (this.hasActiveFolders) {
|
||||||
|
return this.activeFolder.name;
|
||||||
|
}
|
||||||
return this.$t('CHAT_LIST.TAB_HEADING');
|
return this.$t('CHAT_LIST.TAB_HEADING');
|
||||||
},
|
},
|
||||||
conversationList() {
|
conversationList() {
|
||||||
let conversationList = [];
|
let conversationList = [];
|
||||||
if (!this.hasAppliedFilters) {
|
if (!this.hasAppliedFiltersOrActiveFolders) {
|
||||||
const filters = this.conversationFilters;
|
const filters = this.conversationFilters;
|
||||||
if (this.activeAssigneeTab === 'me') {
|
if (this.activeAssigneeTab === 'me') {
|
||||||
conversationList = [...this.mineChatsList(filters)];
|
conversationList = [...this.mineChatsList(filters)];
|
||||||
@ -246,6 +322,16 @@ export default {
|
|||||||
|
|
||||||
return conversationList;
|
return conversationList;
|
||||||
},
|
},
|
||||||
|
activeFolder() {
|
||||||
|
if (this.foldersId) {
|
||||||
|
const activeView = this.folders.filter(
|
||||||
|
view => view.id === Number(this.foldersId)
|
||||||
|
);
|
||||||
|
const [firstValue] = activeView;
|
||||||
|
return firstValue;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
activeTeam() {
|
activeTeam() {
|
||||||
if (this.teamId) {
|
if (this.teamId) {
|
||||||
return this.$store.getters['teams/getTeam'](this.teamId);
|
return this.$store.getters['teams/getTeam'](this.teamId);
|
||||||
@ -266,6 +352,11 @@ export default {
|
|||||||
conversationType() {
|
conversationType() {
|
||||||
this.resetAndFetchData();
|
this.resetAndFetchData();
|
||||||
},
|
},
|
||||||
|
activeFolder() {
|
||||||
|
if (!this.hasAppliedFilters) {
|
||||||
|
this.resetAndFetchData();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.dispatch('setChatFilter', this.activeStatus);
|
this.$store.dispatch('setChatFilter', this.activeStatus);
|
||||||
@ -280,10 +371,23 @@ export default {
|
|||||||
if (this.$route.name !== 'home') {
|
if (this.$route.name !== 'home') {
|
||||||
this.$router.push({ name: 'home' });
|
this.$router.push({ name: 'home' });
|
||||||
}
|
}
|
||||||
|
this.foldersQuery = filterQueryGenerator(payload);
|
||||||
this.$store.dispatch('conversationPage/reset');
|
this.$store.dispatch('conversationPage/reset');
|
||||||
this.$store.dispatch('emptyAllConversations');
|
this.$store.dispatch('emptyAllConversations');
|
||||||
this.fetchFilteredConversations(payload);
|
this.fetchFilteredConversations(payload);
|
||||||
},
|
},
|
||||||
|
onClickOpenAddFoldersModal() {
|
||||||
|
this.showAddFoldersModal = true;
|
||||||
|
},
|
||||||
|
onCloseAddFoldersModal() {
|
||||||
|
this.showAddFoldersModal = false;
|
||||||
|
},
|
||||||
|
onClickOpenDeleteFoldersModal() {
|
||||||
|
this.showDeleteFoldersModal = true;
|
||||||
|
},
|
||||||
|
onCloseDeleteFoldersModal() {
|
||||||
|
this.showDeleteFoldersModal = false;
|
||||||
|
},
|
||||||
onToggleAdvanceFiltersModal() {
|
onToggleAdvanceFiltersModal() {
|
||||||
this.showAdvancedFilters = !this.showAdvancedFilters;
|
this.showAdvancedFilters = !this.showAdvancedFilters;
|
||||||
},
|
},
|
||||||
@ -335,6 +439,13 @@ export default {
|
|||||||
this.$store.dispatch('conversationPage/reset');
|
this.$store.dispatch('conversationPage/reset');
|
||||||
this.$store.dispatch('emptyAllConversations');
|
this.$store.dispatch('emptyAllConversations');
|
||||||
this.$store.dispatch('clearConversationFilters');
|
this.$store.dispatch('clearConversationFilters');
|
||||||
|
if (this.hasActiveFolders) {
|
||||||
|
const payload = this.activeFolder.query;
|
||||||
|
this.fetchSavedFilteredConversations(payload);
|
||||||
|
}
|
||||||
|
if (this.foldersId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.fetchConversations();
|
this.fetchConversations();
|
||||||
},
|
},
|
||||||
fetchConversations() {
|
fetchConversations() {
|
||||||
@ -343,8 +454,12 @@ export default {
|
|||||||
.then(() => this.$emit('conversation-load'));
|
.then(() => this.$emit('conversation-load'));
|
||||||
},
|
},
|
||||||
loadMoreConversations() {
|
loadMoreConversations() {
|
||||||
if (!this.hasAppliedFilters) {
|
if (!this.hasAppliedFiltersOrActiveFolders) {
|
||||||
this.fetchConversations();
|
this.fetchConversations();
|
||||||
|
}
|
||||||
|
if (this.hasActiveFolders) {
|
||||||
|
const payload = this.activeFolder.query;
|
||||||
|
this.fetchSavedFilteredConversations(payload);
|
||||||
} else {
|
} else {
|
||||||
this.fetchFilteredConversations(this.appliedFilters);
|
this.fetchFilteredConversations(this.appliedFilters);
|
||||||
}
|
}
|
||||||
@ -359,6 +474,15 @@ export default {
|
|||||||
.then(() => this.$emit('conversation-load'));
|
.then(() => this.$emit('conversation-load'));
|
||||||
this.showAdvancedFilters = false;
|
this.showAdvancedFilters = false;
|
||||||
},
|
},
|
||||||
|
fetchSavedFilteredConversations(payload) {
|
||||||
|
let page = this.currentFiltersPage + 1;
|
||||||
|
this.$store
|
||||||
|
.dispatch('fetchFilteredConversations', {
|
||||||
|
queryData: payload,
|
||||||
|
page,
|
||||||
|
})
|
||||||
|
.then(() => this.$emit('conversation-load'));
|
||||||
|
},
|
||||||
updateAssigneeTab(selectedTab) {
|
updateAssigneeTab(selectedTab) {
|
||||||
if (this.activeAssigneeTab !== selectedTab) {
|
if (this.activeAssigneeTab !== selectedTab) {
|
||||||
bus.$emit('clearSearchInput');
|
bus.$emit('clearSearchInput');
|
||||||
@ -374,6 +498,22 @@ export default {
|
|||||||
this.resetAndFetchData();
|
this.resetAndFetchData();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
openLastSavedItemInFolder() {
|
||||||
|
const lastItemOfFolder = this.folders[this.folders.length - 1];
|
||||||
|
const lastItemId = lastItemOfFolder.id;
|
||||||
|
this.$router.push({
|
||||||
|
name: 'folder_conversations',
|
||||||
|
params: { id: lastItemId },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
openLastItemAfterDeleteInFolder() {
|
||||||
|
if (this.folders.length > 0) {
|
||||||
|
this.openLastSavedItemInFolder();
|
||||||
|
} else {
|
||||||
|
this.$router.push({ name: 'home' });
|
||||||
|
this.fetchConversations();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -413,7 +553,11 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filter__applied {
|
.filter__applied {
|
||||||
padding: var(--space-slab) 0 !important;
|
padding: 0 0 var(--space-slab) 0 !important;
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delete-custom-view__button {
|
||||||
|
margin-right: var(--space-normal);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -63,7 +63,7 @@
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="value"
|
class="value"
|
||||||
>
|
>
|
||||||
{{ value || '---' }}
|
{{ urlValue }}
|
||||||
</a>
|
</a>
|
||||||
<p v-else class="value">
|
<p v-else class="value">
|
||||||
{{ displayValue || '---' }}
|
{{ displayValue || '---' }}
|
||||||
@ -119,7 +119,7 @@ import format from 'date-fns/format';
|
|||||||
import { required, url } from 'vuelidate/lib/validators';
|
import { required, url } from 'vuelidate/lib/validators';
|
||||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||||
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
|
import MultiselectDropdown from 'shared/components/ui/MultiselectDropdown.vue';
|
||||||
|
import { isValidURL } from '../helper/URLHelper';
|
||||||
const DATE_FORMAT = 'yyyy-MM-dd';
|
const DATE_FORMAT = 'yyyy-MM-dd';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -141,16 +141,6 @@ export default {
|
|||||||
editedValue: null,
|
editedValue: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
validations() {
|
|
||||||
if (this.isAttributeTypeLink) {
|
|
||||||
return {
|
|
||||||
editedValue: { required, url },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
editedValue: { required },
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
formattedValue() {
|
formattedValue() {
|
||||||
@ -184,6 +174,9 @@ export default {
|
|||||||
isAttributeTypeDate() {
|
isAttributeTypeDate() {
|
||||||
return this.attributeType === 'date';
|
return this.attributeType === 'date';
|
||||||
},
|
},
|
||||||
|
urlValue() {
|
||||||
|
return isValidURL(this.value) ? this.value : '---';
|
||||||
|
},
|
||||||
notAttributeTypeCheckboxAndList() {
|
notAttributeTypeCheckboxAndList() {
|
||||||
return !this.isAttributeTypeCheckbox && !this.isAttributeTypeList;
|
return !this.isAttributeTypeCheckbox && !this.isAttributeTypeList;
|
||||||
},
|
},
|
||||||
@ -206,6 +199,23 @@ export default {
|
|||||||
return this.editedValue;
|
return this.editedValue;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
value() {
|
||||||
|
this.isEditing = false;
|
||||||
|
this.editedValue = this.value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
validations() {
|
||||||
|
if (this.isAttributeTypeLink) {
|
||||||
|
return {
|
||||||
|
editedValue: { required, url },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
editedValue: { required },
|
||||||
|
};
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.editedValue = this.formattedValue;
|
this.editedValue = this.formattedValue;
|
||||||
bus.$on(BUS_EVENTS.FOCUS_CUSTOM_ATTRIBUTE, focusAttributeKey => {
|
bus.$on(BUS_EVENTS.FOCUS_CUSTOM_ATTRIBUTE, focusAttributeKey => {
|
||||||
|
|||||||
@ -4,7 +4,12 @@
|
|||||||
<div class="ui-notification">
|
<div class="ui-notification">
|
||||||
<fluent-icon icon="wifi-off" />
|
<fluent-icon icon="wifi-off" />
|
||||||
<p class="ui-notification-text">
|
<p class="ui-notification-text">
|
||||||
{{ $t('NETWORK.NOTIFICATION.TEXT') }}
|
{{
|
||||||
|
useInstallationName(
|
||||||
|
$t('NETWORK.NOTIFICATION.TEXT'),
|
||||||
|
globalConfig.installationName
|
||||||
|
)
|
||||||
|
}}
|
||||||
</p>
|
</p>
|
||||||
<woot-button variant="clear" size="small" @click="refreshPage">
|
<woot-button variant="clear" size="small" @click="refreshPage">
|
||||||
{{ $t('NETWORK.BUTTON.REFRESH') }}
|
{{ $t('NETWORK.BUTTON.REFRESH') }}
|
||||||
@ -23,13 +28,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [globalConfigMixin],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showNotification: !navigator.onLine,
|
showNotification: !navigator.onLine,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters({ globalConfig: 'globalConfig/get' }),
|
||||||
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
window.addEventListener('offline', this.updateOnlineStatus);
|
window.addEventListener('offline', this.updateOnlineStatus);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,6 +4,11 @@
|
|||||||
<div class="ui-snackbar-text">
|
<div class="ui-snackbar-text">
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="action" class="ui-snackbar-action">
|
||||||
|
<router-link v-if="action.type == 'link'" :to="action.to">
|
||||||
|
{{ action.message }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -12,6 +17,10 @@
|
|||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
message: { type: String, default: '' },
|
message: { type: String, default: '' },
|
||||||
|
action: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
showButton: Boolean,
|
showButton: Boolean,
|
||||||
duration: {
|
duration: {
|
||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
v-for="snackMessage in snackMessages"
|
v-for="snackMessage in snackMessages"
|
||||||
:key="snackMessage.key"
|
:key="snackMessage.key"
|
||||||
:message="snackMessage.message"
|
:message="snackMessage.message"
|
||||||
|
:action="snackMessage.action"
|
||||||
/>
|
/>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</template>
|
</template>
|
||||||
@ -35,8 +36,12 @@ export default {
|
|||||||
bus.$off('newToastMessage', this.onNewToastMessage);
|
bus.$off('newToastMessage', this.onNewToastMessage);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onNewToastMessage(message) {
|
onNewToastMessage(message, action) {
|
||||||
this.snackMessages.push({ key: new Date().getTime(), message });
|
this.snackMessages.push({
|
||||||
|
key: new Date().getTime(),
|
||||||
|
message,
|
||||||
|
action,
|
||||||
|
});
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
this.snackMessages.splice(0, 1);
|
this.snackMessages.splice(0, 1);
|
||||||
}, this.duration);
|
}, this.duration);
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
:inboxes="inboxes"
|
:inboxes="inboxes"
|
||||||
:labels="labels"
|
:labels="labels"
|
||||||
:teams="teams"
|
:teams="teams"
|
||||||
|
:custom-views="customViews"
|
||||||
:menu-config="activeSecondaryMenu"
|
:menu-config="activeSecondaryMenu"
|
||||||
:current-role="currentRole"
|
:current-role="currentRole"
|
||||||
@add-label="showAddLabelPopup"
|
@add-label="showAddLabelPopup"
|
||||||
@ -92,6 +93,26 @@ export default {
|
|||||||
labels: 'labels/getLabelsOnSidebar',
|
labels: 'labels/getLabelsOnSidebar',
|
||||||
teams: 'teams/getMyTeams',
|
teams: 'teams/getMyTeams',
|
||||||
}),
|
}),
|
||||||
|
activeCustomView() {
|
||||||
|
if (this.activePrimaryMenu.key === 'contacts') {
|
||||||
|
return 'contact';
|
||||||
|
}
|
||||||
|
if (this.activePrimaryMenu.key === 'conversations') {
|
||||||
|
return 'conversation';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
customViews() {
|
||||||
|
return this.$store.getters['customViews/getCustomViewsByFilterType'](
|
||||||
|
this.activeCustomView
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isConversationOrContactActive() {
|
||||||
|
return (
|
||||||
|
this.activePrimaryMenu.key === 'contacts' ||
|
||||||
|
this.activePrimaryMenu.key === 'conversations'
|
||||||
|
);
|
||||||
|
},
|
||||||
sideMenuConfig() {
|
sideMenuConfig() {
|
||||||
return getSidebarItems(this.accountId);
|
return getSidebarItems(this.accountId);
|
||||||
},
|
},
|
||||||
@ -119,15 +140,27 @@ export default {
|
|||||||
return activePrimaryMenu;
|
return activePrimaryMenu;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
activeCustomView() {
|
||||||
|
this.fetchCustomViews();
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.dispatch('labels/get');
|
this.$store.dispatch('labels/get');
|
||||||
this.$store.dispatch('inboxes/get');
|
this.$store.dispatch('inboxes/get');
|
||||||
this.$store.dispatch('notifications/unReadCount');
|
this.$store.dispatch('notifications/unReadCount');
|
||||||
this.$store.dispatch('teams/get');
|
this.$store.dispatch('teams/get');
|
||||||
this.$store.dispatch('attributes/get');
|
this.$store.dispatch('attributes/get');
|
||||||
|
this.fetchCustomViews();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
fetchCustomViews() {
|
||||||
|
if (this.isConversationOrContactActive) {
|
||||||
|
this.$store.dispatch('customViews/get', this.activeCustomView);
|
||||||
|
}
|
||||||
|
},
|
||||||
toggleKeyShortcutModal() {
|
toggleKeyShortcutModal() {
|
||||||
this.showShortcutModal = true;
|
this.showShortcutModal = true;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const contacts = accountId => ({
|
|||||||
routes: [
|
routes: [
|
||||||
'contacts_dashboard',
|
'contacts_dashboard',
|
||||||
'contact_profile_dashboard',
|
'contact_profile_dashboard',
|
||||||
|
'contacts_segments_dashboard',
|
||||||
'contacts_labels_dashboard',
|
'contacts_labels_dashboard',
|
||||||
],
|
],
|
||||||
menuItems: [
|
menuItems: [
|
||||||
|
|||||||
@ -14,6 +14,8 @@ const conversations = accountId => ({
|
|||||||
'conversations_through_team',
|
'conversations_through_team',
|
||||||
'conversation_mentions',
|
'conversation_mentions',
|
||||||
'conversation_through_mentions',
|
'conversation_through_mentions',
|
||||||
|
'folder_conversations',
|
||||||
|
'conversations_through_folders',
|
||||||
],
|
],
|
||||||
menuItems: [
|
menuItems: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -70,7 +70,7 @@ const settings = accountId => ({
|
|||||||
toStateName: 'attributes_list',
|
toStateName: 'attributes_list',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'autocorrect',
|
icon: 'automation',
|
||||||
label: 'AUTOMATION',
|
label: 'AUTOMATION',
|
||||||
hasSubMenu: false,
|
hasSubMenu: false,
|
||||||
toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
|
toState: frontendURL(`accounts/${accountId}/settings/automation/list`),
|
||||||
|
|||||||
@ -40,6 +40,10 @@ export default {
|
|||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
|
customViews: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
menuConfig: {
|
menuConfig: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
@ -53,6 +57,9 @@ export default {
|
|||||||
hasSecondaryMenu() {
|
hasSecondaryMenu() {
|
||||||
return this.menuConfig.menuItems && this.menuConfig.menuItems.length;
|
return this.menuConfig.menuItems && this.menuConfig.menuItems.length;
|
||||||
},
|
},
|
||||||
|
contactCustomViews() {
|
||||||
|
return this.customViews.filter(view => view.filter_type === 'contact');
|
||||||
|
},
|
||||||
accessibleMenuItems() {
|
accessibleMenuItems() {
|
||||||
if (!this.currentRole) {
|
if (!this.currentRole) {
|
||||||
return [];
|
return [];
|
||||||
@ -150,14 +157,57 @@ export default {
|
|||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
foldersSection() {
|
||||||
|
return {
|
||||||
|
icon: 'folder',
|
||||||
|
label: 'CUSTOM_VIEWS_FOLDER',
|
||||||
|
hasSubMenu: true,
|
||||||
|
key: 'custom_view',
|
||||||
|
children: this.customViews
|
||||||
|
.filter(view => view.filter_type === 'conversation')
|
||||||
|
.map(view => ({
|
||||||
|
id: view.id,
|
||||||
|
label: view.name,
|
||||||
|
truncateLabel: true,
|
||||||
|
toState: frontendURL(
|
||||||
|
`accounts/${this.accountId}/custom_view/${view.id}`
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
contactSegmentsSection() {
|
||||||
|
return {
|
||||||
|
icon: 'folder',
|
||||||
|
label: 'CUSTOM_VIEWS_SEGMENTS',
|
||||||
|
hasSubMenu: true,
|
||||||
|
key: 'custom_view',
|
||||||
|
children: this.customViews
|
||||||
|
.filter(view => view.filter_type === 'contact')
|
||||||
|
.map(view => ({
|
||||||
|
id: view.id,
|
||||||
|
label: view.name,
|
||||||
|
truncateLabel: true,
|
||||||
|
toState: frontendURL(
|
||||||
|
`accounts/${this.accountId}/contacts/custom_view/${view.id}`
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
},
|
||||||
additionalSecondaryMenuItems() {
|
additionalSecondaryMenuItems() {
|
||||||
let conversationMenuItems = [this.inboxSection, this.labelSection];
|
let conversationMenuItems = [this.inboxSection, this.labelSection];
|
||||||
|
let contactMenuItems = [this.contactLabelSection];
|
||||||
if (this.teams.length) {
|
if (this.teams.length) {
|
||||||
conversationMenuItems = [this.teamSection, ...conversationMenuItems];
|
conversationMenuItems = [this.teamSection, ...conversationMenuItems];
|
||||||
}
|
}
|
||||||
|
if (this.customViews.length) {
|
||||||
|
conversationMenuItems = [this.foldersSection, ...conversationMenuItems];
|
||||||
|
}
|
||||||
|
if (this.contactCustomViews.length) {
|
||||||
|
contactMenuItems = [this.contactSegmentsSection, ...contactMenuItems];
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
conversations: conversationMenuItems,
|
conversations: conversationMenuItems,
|
||||||
contacts: [this.contactLabelSection],
|
contacts: contactMenuItems,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
v-else
|
v-else
|
||||||
class="secondary-menu--title secondary-menu--link fs-small"
|
class="secondary-menu--title secondary-menu--link fs-small"
|
||||||
:class="computedClass"
|
:class="computedClass"
|
||||||
:to="menuItem.toState"
|
:to="menuItem && menuItem.toState"
|
||||||
>
|
>
|
||||||
<fluent-icon
|
<fluent-icon
|
||||||
:icon="menuItem.icon"
|
:icon="menuItem.icon"
|
||||||
@ -15,6 +15,13 @@
|
|||||||
size="14"
|
size="14"
|
||||||
/>
|
/>
|
||||||
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
{{ $t(`SIDEBAR.${menuItem.label}`) }}
|
||||||
|
<span
|
||||||
|
v-if="menuItem.label === 'AUTOMATION'"
|
||||||
|
data-view-component="true"
|
||||||
|
label="Beta"
|
||||||
|
class="beta"
|
||||||
|
>Beta
|
||||||
|
</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<ul v-if="hasSubMenu" class="nested vertical menu">
|
<ul v-if="hasSubMenu" class="nested vertical menu">
|
||||||
@ -73,14 +80,48 @@ export default {
|
|||||||
hasSubMenu() {
|
hasSubMenu() {
|
||||||
return !!this.menuItem.children;
|
return !!this.menuItem.children;
|
||||||
},
|
},
|
||||||
|
isInboxConversation() {
|
||||||
|
return (
|
||||||
|
this.$store.state.route.name === 'inbox_conversation' &&
|
||||||
|
this.menuItem.toStateName === 'home'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isTeamsSettings() {
|
||||||
|
return (
|
||||||
|
this.$store.state.route.name === 'settings_teams_edit' &&
|
||||||
|
this.menuItem.toStateName === 'settings_teams_list'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isInboxsSettings() {
|
||||||
|
return (
|
||||||
|
this.$store.state.route.name === 'settings_inbox_show' &&
|
||||||
|
this.menuItem.toStateName === 'settings_inbox_list'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isIntegrationsSettings() {
|
||||||
|
return (
|
||||||
|
this.$store.state.route.name === 'settings_integrations_webhook' &&
|
||||||
|
this.menuItem.toStateName === 'settings_integrations'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isApplicationsSettings() {
|
||||||
|
return (
|
||||||
|
this.$store.state.route.name === 'settings_applications_integration' &&
|
||||||
|
this.menuItem.toStateName === 'settings_applications'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
computedClass() {
|
computedClass() {
|
||||||
// If active Inbox is present
|
// If active Inbox is present
|
||||||
// donot highlight conversations
|
// donot highlight conversations
|
||||||
if (this.activeInbox) return ' ';
|
if (this.activeInbox) return ' ';
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.$store.state.route.name === 'inbox_conversation' &&
|
this.isInboxConversation ||
|
||||||
this.menuItem.toStateName === 'home'
|
this.isTeamsSettings ||
|
||||||
|
this.isInboxsSettings ||
|
||||||
|
this.isIntegrationsSettings ||
|
||||||
|
this.isApplicationsSettings
|
||||||
) {
|
) {
|
||||||
return 'is-active';
|
return 'is-active';
|
||||||
}
|
}
|
||||||
@ -187,4 +228,17 @@ export default {
|
|||||||
color: var(--w-500);
|
color: var(--w-500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.beta {
|
||||||
|
padding-right: var(--space-smaller) !important;
|
||||||
|
padding-left: var(--space-smaller) !important;
|
||||||
|
margin-left: var(--space-half) !important;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
line-height: 18px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 2em;
|
||||||
|
color: var(--g-800);
|
||||||
|
border-color: var(--g-700);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
129
app/javascript/dashboard/components/ui/Banner.vue
Normal file
129
app/javascript/dashboard/components/ui/Banner.vue
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<template>
|
||||||
|
<div class="banner" :class="bannerClasses">
|
||||||
|
<span>
|
||||||
|
{{ bannerMessage }}
|
||||||
|
<a
|
||||||
|
v-if="hrefLink"
|
||||||
|
:href="hrefLink"
|
||||||
|
rel="noopener noreferrer nofollow"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{{ hrefLinkText }}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<woot-button
|
||||||
|
v-if="hasActionButton"
|
||||||
|
size="small"
|
||||||
|
variant="link"
|
||||||
|
icon="arrow-right"
|
||||||
|
color-scheme="primary"
|
||||||
|
class-names="banner-action__button"
|
||||||
|
@click="onClick"
|
||||||
|
>
|
||||||
|
{{ actionButtonLabel }}
|
||||||
|
</woot-button>
|
||||||
|
<woot-button
|
||||||
|
v-if="hasCloseButton"
|
||||||
|
size="small"
|
||||||
|
variant="link"
|
||||||
|
color-scheme="warning"
|
||||||
|
icon="dismiss-circle"
|
||||||
|
class-names="banner-action__button"
|
||||||
|
@click="onClickClose"
|
||||||
|
>
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
bannerMessage: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hrefLink: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hrefLinkText: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hasActionButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
actionButtonLabel: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
colorScheme: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hasCloseButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
bannerClasses() {
|
||||||
|
return [this.colorScheme];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onClick(e) {
|
||||||
|
this.$emit('click', e);
|
||||||
|
},
|
||||||
|
onClickClose(e) {
|
||||||
|
this.$emit('close', e);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.banner {
|
||||||
|
display: flex;
|
||||||
|
color: var(--white);
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
padding: var(--space-slab) var(--space-normal);
|
||||||
|
justify-content: center;
|
||||||
|
position: sticky;
|
||||||
|
|
||||||
|
&.secondary {
|
||||||
|
background: var(--s-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.alert {
|
||||||
|
background: var(--r-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warning {
|
||||||
|
background: var(--y-800);
|
||||||
|
color: var(--s-600);
|
||||||
|
a {
|
||||||
|
color: var(--s-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.gray {
|
||||||
|
background: var(--b-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: var(--white);
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-action__button {
|
||||||
|
margin: 0 var(--space-smaller);
|
||||||
|
|
||||||
|
::v-deep .button__content {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -30,7 +30,7 @@ export default {
|
|||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
type: Date,
|
type: Date,
|
||||||
default: () => [],
|
default: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
>
|
>
|
||||||
<div class="thumb-wrap">
|
<div class="thumb-wrap">
|
||||||
<img
|
<img
|
||||||
v-if="isTypeImage(attachment.resource.type)"
|
v-if="isTypeImage(attachment.resource)"
|
||||||
class="image-thumb"
|
class="image-thumb"
|
||||||
:src="attachment.thumb"
|
:src="attachment.thumb"
|
||||||
/>
|
/>
|
||||||
@ -15,12 +15,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="file-name-wrap">
|
<div class="file-name-wrap">
|
||||||
<span class="item">
|
<span class="item">
|
||||||
{{ attachment.resource.name }}
|
{{ fileName(attachment.resource) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="file-size-wrap">
|
<div class="file-size-wrap">
|
||||||
<span class="item">
|
<span class="item">
|
||||||
{{ formatFileSize(attachment.resource.size) }}
|
{{ formatFileSize(attachment.resource) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="remove-file-wrap">
|
<div class="remove-file-wrap">
|
||||||
@ -50,12 +50,17 @@ export default {
|
|||||||
onRemoveAttachment(index) {
|
onRemoveAttachment(index) {
|
||||||
this.removeAttachment(index);
|
this.removeAttachment(index);
|
||||||
},
|
},
|
||||||
formatFileSize(size) {
|
formatFileSize(file) {
|
||||||
|
const size = file.byte_size || file.size;
|
||||||
return formatBytes(size, 0);
|
return formatBytes(size, 0);
|
||||||
},
|
},
|
||||||
isTypeImage(type) {
|
isTypeImage(file) {
|
||||||
|
const type = file.content_type || file.type;
|
||||||
return type.includes('image');
|
return type.includes('image');
|
||||||
},
|
},
|
||||||
|
fileName(file) {
|
||||||
|
return file.filename || file.name;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -0,0 +1,182 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="filter"
|
||||||
|
:class="{ error: v.action_params.$dirty && v.action_params.$error }"
|
||||||
|
>
|
||||||
|
<div class="filter-inputs">
|
||||||
|
<select
|
||||||
|
v-model="action_name"
|
||||||
|
class="action__question"
|
||||||
|
@change="resetFilter()"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="attribute in actionTypes"
|
||||||
|
:key="attribute.key"
|
||||||
|
:value="attribute.key"
|
||||||
|
>
|
||||||
|
{{ attribute.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="filter__answer--wrap">
|
||||||
|
<div class="multiselect-wrap--small">
|
||||||
|
<multiselect
|
||||||
|
v-model="action_params"
|
||||||
|
track-by="id"
|
||||||
|
label="name"
|
||||||
|
:placeholder="'Select'"
|
||||||
|
:multiple="true"
|
||||||
|
selected-label
|
||||||
|
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||||
|
deselect-label=""
|
||||||
|
:max-height="160"
|
||||||
|
:options="dropdownValues"
|
||||||
|
:allow-empty="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<woot-button
|
||||||
|
icon="dismiss"
|
||||||
|
variant="clear"
|
||||||
|
color-scheme="secondary"
|
||||||
|
@click="removeAction"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
v-if="v.action_params.$dirty && v.action_params.$error"
|
||||||
|
class="filter-error"
|
||||||
|
>
|
||||||
|
{{ $t('FILTER.EMPTY_VALUE_ERROR') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
actionTypes: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
dropdownValues: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
v: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
action_name: {
|
||||||
|
get() {
|
||||||
|
if (!this.value) return null;
|
||||||
|
return this.value.action_name;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
const payload = this.value || {};
|
||||||
|
this.$emit('input', { ...payload, action_name: value });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
action_params: {
|
||||||
|
get() {
|
||||||
|
if (!this.value) return null;
|
||||||
|
return this.value.action_params;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
const payload = this.value || {};
|
||||||
|
this.$emit('input', { ...payload, action_params: value });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
removeAction() {
|
||||||
|
this.$emit('removeAction');
|
||||||
|
},
|
||||||
|
resetFilter() {
|
||||||
|
this.$emit('resetFilter');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.filter {
|
||||||
|
background: var(--color-background);
|
||||||
|
padding: var(--space-small);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius-medium);
|
||||||
|
margin-bottom: var(--space-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter.error {
|
||||||
|
background: var(--r-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-inputs {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-error {
|
||||||
|
color: var(--r-500);
|
||||||
|
display: block;
|
||||||
|
margin: var(--space-smaller) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action__question,
|
||||||
|
.filter__operator {
|
||||||
|
margin-bottom: var(--space-zero);
|
||||||
|
margin-right: var(--space-smaller);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action__question {
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter__answer--wrap {
|
||||||
|
margin-right: var(--space-smaller);
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.filter__answer {
|
||||||
|
&.answer--text-input {
|
||||||
|
margin-bottom: var(--space-zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter__join-operator-wrap {
|
||||||
|
position: relative;
|
||||||
|
z-index: var(--z-index-twenty);
|
||||||
|
margin: var(--space-zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter__join-operator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
margin: var(--space-one) var(--space-zero);
|
||||||
|
|
||||||
|
.operator__line {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator__select {
|
||||||
|
position: relative;
|
||||||
|
width: auto;
|
||||||
|
margin-bottom: var(--space-zero) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect {
|
||||||
|
margin-bottom: var(--space-zero);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -18,11 +18,11 @@ export default {
|
|||||||
},
|
},
|
||||||
backgroundColor: {
|
backgroundColor: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'white',
|
default: '#c2e1ff',
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '#1976cc',
|
||||||
},
|
},
|
||||||
customStyle: {
|
customStyle: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@ -0,0 +1,75 @@
|
|||||||
|
export const OPERATOR_TYPES_1 = [
|
||||||
|
{
|
||||||
|
value: 'equal_to',
|
||||||
|
label: 'Equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'not_equal_to',
|
||||||
|
label: 'Not equal to',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OPERATOR_TYPES_2 = [
|
||||||
|
{
|
||||||
|
value: 'equal_to',
|
||||||
|
label: 'Equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'not_equal_to',
|
||||||
|
label: 'Not equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_present',
|
||||||
|
label: 'Is present',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_not_present',
|
||||||
|
label: 'Is not present',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OPERATOR_TYPES_3 = [
|
||||||
|
{
|
||||||
|
value: 'equal_to',
|
||||||
|
label: 'Equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'not_equal_to',
|
||||||
|
label: 'Not equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'contains',
|
||||||
|
label: 'Contains',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'does_not_contain',
|
||||||
|
label: 'Does not contain',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const OPERATOR_TYPES_4 = [
|
||||||
|
{
|
||||||
|
value: 'equal_to',
|
||||||
|
label: 'Equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'not_equal_to',
|
||||||
|
label: 'Not equal to',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_present',
|
||||||
|
label: 'Is present',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_not_present',
|
||||||
|
label: 'Is not present',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_greater_than',
|
||||||
|
label: 'Is greater than',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'is_lesser_than',
|
||||||
|
label: 'Is lesser than',
|
||||||
|
},
|
||||||
|
];
|
||||||
@ -1,8 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="filters">
|
<div class="filters">
|
||||||
<div class="filter">
|
<div class="filter" :class="{ error: v.values.$dirty && v.values.$error }">
|
||||||
<div class="filter-inputs">
|
<div class="filter-inputs">
|
||||||
<select
|
<select
|
||||||
|
v-if="groupedFilters"
|
||||||
|
v-model="attributeKey"
|
||||||
|
class="filter__question"
|
||||||
|
@change="resetFilter()"
|
||||||
|
>
|
||||||
|
<optgroup
|
||||||
|
v-for="(group, i) in filterGroups"
|
||||||
|
:key="i"
|
||||||
|
:label="group.name"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="attribute in group.attributes"
|
||||||
|
:key="attribute.key"
|
||||||
|
:value="attribute.key"
|
||||||
|
>
|
||||||
|
{{ attribute.name }}
|
||||||
|
</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
v-else
|
||||||
v-model="attributeKey"
|
v-model="attributeKey"
|
||||||
class="filter__question"
|
class="filter__question"
|
||||||
@change="resetFilter()"
|
@change="resetFilter()"
|
||||||
@ -63,6 +84,14 @@
|
|||||||
:option-height="104"
|
:option-height="104"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="inputType === 'date'" class="multiselect-wrap--small">
|
||||||
|
<input
|
||||||
|
v-model="values"
|
||||||
|
type="date"
|
||||||
|
:editable="false"
|
||||||
|
class="answer--text-input datepicker"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
v-else
|
v-else
|
||||||
v-model="values"
|
v-model="values"
|
||||||
@ -132,6 +161,14 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
groupedFilters: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
filterGroups: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
attributeKey: {
|
attributeKey: {
|
||||||
@ -193,6 +230,10 @@ export default {
|
|||||||
border-radius: var(--border-radius-medium);
|
border-radius: var(--border-radius-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter.error {
|
||||||
|
background: var(--r-50);
|
||||||
|
}
|
||||||
|
|
||||||
.filter-inputs {
|
.filter-inputs {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
<div class="bottom-box" :class="wrapClass">
|
<div class="bottom-box" :class="wrapClass">
|
||||||
<div class="left-wrap">
|
<div class="left-wrap">
|
||||||
<woot-button
|
<woot-button
|
||||||
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
||||||
:title="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
:title="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
||||||
icon="emoji"
|
icon="emoji"
|
||||||
emoji="😊"
|
emoji="😊"
|
||||||
@ -14,10 +15,16 @@
|
|||||||
<!-- ensure the same validations for attachment types are implemented in backend models as well -->
|
<!-- ensure the same validations for attachment types are implemented in backend models as well -->
|
||||||
<file-upload
|
<file-upload
|
||||||
ref="upload"
|
ref="upload"
|
||||||
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
|
||||||
:size="4096 * 4096"
|
:size="4096 * 4096"
|
||||||
:accept="allowedFileTypes"
|
:accept="allowedFileTypes"
|
||||||
|
:multiple="enableMultipleFileUpload"
|
||||||
:drop="true"
|
:drop="true"
|
||||||
:drop-directory="false"
|
:drop-directory="false"
|
||||||
|
:data="{
|
||||||
|
direct_upload_url: '/rails/active_storage/direct_uploads',
|
||||||
|
direct_upload: true,
|
||||||
|
}"
|
||||||
@input-file="onFileUpload"
|
@input-file="onFileUpload"
|
||||||
>
|
>
|
||||||
<woot-button
|
<woot-button
|
||||||
@ -33,6 +40,7 @@
|
|||||||
</file-upload>
|
</file-upload>
|
||||||
<woot-button
|
<woot-button
|
||||||
v-if="enableRichEditor && !isOnPrivateNote"
|
v-if="enableRichEditor && !isOnPrivateNote"
|
||||||
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||||
icon="quote"
|
icon="quote"
|
||||||
emoji="🖊️"
|
emoji="🖊️"
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
@ -41,6 +49,16 @@
|
|||||||
:title="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
:title="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||||
@click="toggleFormatMode"
|
@click="toggleFormatMode"
|
||||||
/>
|
/>
|
||||||
|
<woot-button
|
||||||
|
v-if="showMessageSignatureButton"
|
||||||
|
v-tooltip.top-end="signatureToggleTooltip"
|
||||||
|
icon="signature"
|
||||||
|
color-scheme="secondary"
|
||||||
|
variant="smooth"
|
||||||
|
size="small"
|
||||||
|
:title="signatureToggleTooltip"
|
||||||
|
@click="toggleMessageSignature"
|
||||||
|
/>
|
||||||
<transition name="modal-fade">
|
<transition name="modal-fade">
|
||||||
<div
|
<div
|
||||||
v-show="$refs.upload && $refs.upload.dropActive"
|
v-show="$refs.upload && $refs.upload.dropActive"
|
||||||
@ -79,18 +97,22 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import FileUpload from 'vue-upload-component';
|
import FileUpload from 'vue-upload-component';
|
||||||
|
import * as ActiveStorage from 'activestorage';
|
||||||
import {
|
import {
|
||||||
hasPressedAltAndWKey,
|
hasPressedAltAndWKey,
|
||||||
hasPressedAltAndAKey,
|
hasPressedAltAndAKey,
|
||||||
} from 'shared/helpers/KeyboardHelpers';
|
} from 'shared/helpers/KeyboardHelpers';
|
||||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||||
|
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||||
|
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||||
|
|
||||||
import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
|
import { ALLOWED_FILE_TYPES } from 'shared/constants/messages';
|
||||||
|
|
||||||
import { REPLY_EDITOR_MODES } from './constants';
|
import { REPLY_EDITOR_MODES } from './constants';
|
||||||
export default {
|
export default {
|
||||||
name: 'ReplyTopPanel',
|
name: 'ReplyTopPanel',
|
||||||
components: { FileUpload },
|
components: { FileUpload },
|
||||||
mixins: [eventListenerMixins],
|
mixins: [eventListenerMixins, uiSettingsMixin, inboxMixin],
|
||||||
props: {
|
props: {
|
||||||
mode: {
|
mode: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -104,6 +126,10 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
inbox: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
showFileUpload: {
|
showFileUpload: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@ -144,6 +170,10 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
enableMultipleFileUpload: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isNote() {
|
isNote() {
|
||||||
@ -165,6 +195,21 @@ export default {
|
|||||||
allowedFileTypes() {
|
allowedFileTypes() {
|
||||||
return ALLOWED_FILE_TYPES;
|
return ALLOWED_FILE_TYPES;
|
||||||
},
|
},
|
||||||
|
showMessageSignatureButton() {
|
||||||
|
return !this.isPrivate && this.isAnEmailChannel;
|
||||||
|
},
|
||||||
|
sendWithSignature() {
|
||||||
|
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||||
|
return isEnabled;
|
||||||
|
},
|
||||||
|
signatureToggleTooltip() {
|
||||||
|
return this.sendWithSignature
|
||||||
|
? this.$t('CONVERSATION.FOOTER.DISABLE_SIGN_TOOLTIP')
|
||||||
|
: this.$t('CONVERSATION.FOOTER.ENABLE_SIGN_TOOLTIP');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
ActiveStorage.start();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleKeyEvents(e) {
|
handleKeyEvents(e) {
|
||||||
@ -181,6 +226,11 @@ export default {
|
|||||||
toggleEnterToSend() {
|
toggleEnterToSend() {
|
||||||
this.$emit('toggleEnterToSend', !this.enterToSendEnabled);
|
this.$emit('toggleEnterToSend', !this.enterToSendEnabled);
|
||||||
},
|
},
|
||||||
|
toggleMessageSignature() {
|
||||||
|
this.updateUISettings({
|
||||||
|
send_with_signature: !this.sendWithSignature,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -9,12 +9,13 @@
|
|||||||
v-for="(filter, i) in appliedFilters"
|
v-for="(filter, i) in appliedFilters"
|
||||||
:key="i"
|
:key="i"
|
||||||
v-model="appliedFilters[i]"
|
v-model="appliedFilters[i]"
|
||||||
:filter-attributes="filterAttributes"
|
:filter-groups="filterGroups"
|
||||||
:input-type="getInputType(appliedFilters[i].attribute_key)"
|
:input-type="getInputType(appliedFilters[i].attribute_key)"
|
||||||
:operators="getOperators(appliedFilters[i].attribute_key)"
|
:operators="getOperators(appliedFilters[i].attribute_key)"
|
||||||
:dropdown-values="getDropdownValues(appliedFilters[i].attribute_key)"
|
:dropdown-values="getDropdownValues(appliedFilters[i].attribute_key)"
|
||||||
:show-query-operator="i !== appliedFilters.length - 1"
|
:show-query-operator="i !== appliedFilters.length - 1"
|
||||||
:show-user-input="showUserInput(appliedFilters[i].filter_operator)"
|
:show-user-input="showUserInput(appliedFilters[i].filter_operator)"
|
||||||
|
:grouped-filters="true"
|
||||||
:v="$v.appliedFilters.$each[i]"
|
:v="$v.appliedFilters.$each[i]"
|
||||||
@resetFilter="resetFilter(i, appliedFilters[i])"
|
@resetFilter="resetFilter(i, appliedFilters[i])"
|
||||||
@removeFilter="removeFilter(i)"
|
@removeFilter="removeFilter(i)"
|
||||||
@ -48,22 +49,24 @@
|
|||||||
<script>
|
<script>
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import { required, requiredIf } from 'vuelidate/lib/validators';
|
import { required, requiredIf } from 'vuelidate/lib/validators';
|
||||||
import FilterInputBox from '../FilterInput.vue';
|
import FilterInputBox from '../FilterInput/Index.vue';
|
||||||
import languages from './advancedFilterItems/languages';
|
import languages from './advancedFilterItems/languages';
|
||||||
import countries from '/app/javascript/shared/constants/countries.js';
|
import countries from 'shared/constants/countries.js';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
import { filterAttributeGroups } from './advancedFilterItems';
|
||||||
|
import filterMixin from 'shared/mixins/filterMixin';
|
||||||
|
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
FilterInputBox,
|
FilterInputBox,
|
||||||
},
|
},
|
||||||
mixins: [alertMixin],
|
mixins: [alertMixin, filterMixin],
|
||||||
props: {
|
props: {
|
||||||
onClose: {
|
onClose: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
filterTypes: {
|
initialFilterTypes: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
@ -87,22 +90,21 @@ export default {
|
|||||||
return {
|
return {
|
||||||
show: true,
|
show: true,
|
||||||
appliedFilters: [],
|
appliedFilters: [],
|
||||||
|
filterTypes: this.initialFilterTypes,
|
||||||
|
filterAttributeGroups,
|
||||||
|
filterGroups: [],
|
||||||
|
allCustomAttributes: [],
|
||||||
|
attributeModel: 'conversation_attribute',
|
||||||
|
filtersFori18n: 'FILTER',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
filterAttributes() {
|
|
||||||
return this.filterTypes.map(type => {
|
|
||||||
return {
|
|
||||||
key: type.attributeKey,
|
|
||||||
name: this.$t(`FILTER.ATTRIBUTES.${type.attributeI18nKey}`),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
getAppliedConversationFilters: 'getAppliedConversationFilters',
|
getAppliedConversationFilters: 'getAppliedConversationFilters',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.setFilterAttributes();
|
||||||
this.$store.dispatch('campaigns/get');
|
this.$store.dispatch('campaigns/get');
|
||||||
if (this.getAppliedConversationFilters.length) {
|
if (this.getAppliedConversationFilters.length) {
|
||||||
this.appliedFilters = [...this.getAppliedConversationFilters];
|
this.appliedFilters = [...this.getAppliedConversationFilters];
|
||||||
@ -112,10 +114,41 @@ export default {
|
|||||||
filter_operator: 'equal_to',
|
filter_operator: 'equal_to',
|
||||||
values: '',
|
values: '',
|
||||||
query_operator: 'and',
|
query_operator: 'and',
|
||||||
|
attribute_model: 'standard',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getOperatorTypes(key) {
|
||||||
|
switch (key) {
|
||||||
|
case 'list':
|
||||||
|
return OPERATORS.OPERATOR_TYPES_1;
|
||||||
|
case 'text':
|
||||||
|
return OPERATORS.OPERATOR_TYPES_3;
|
||||||
|
case 'number':
|
||||||
|
return OPERATORS.OPERATOR_TYPES_1;
|
||||||
|
case 'link':
|
||||||
|
return OPERATORS.OPERATOR_TYPES_1;
|
||||||
|
case 'date':
|
||||||
|
return OPERATORS.OPERATOR_TYPES_4;
|
||||||
|
case 'checkbox':
|
||||||
|
return OPERATORS.OPERATOR_TYPES_1;
|
||||||
|
default:
|
||||||
|
return OPERATORS.OPERATOR_TYPES_1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customAttributeInputType(key) {
|
||||||
|
switch (key) {
|
||||||
|
case 'date':
|
||||||
|
return 'date';
|
||||||
|
default:
|
||||||
|
return 'plain_text';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getAttributeModel(key) {
|
||||||
|
const type = this.filterTypes.find(filter => filter.attributeKey === key);
|
||||||
|
return type.attributeModel;
|
||||||
|
},
|
||||||
getInputType(key) {
|
getInputType(key) {
|
||||||
const type = this.filterTypes.find(filter => filter.attributeKey === key);
|
const type = this.filterTypes.find(filter => filter.attributeKey === key);
|
||||||
return type.inputType;
|
return type.inputType;
|
||||||
@ -192,7 +225,6 @@ export default {
|
|||||||
'setConversationFilters',
|
'setConversationFilters',
|
||||||
JSON.parse(JSON.stringify(this.appliedFilters))
|
JSON.parse(JSON.stringify(this.appliedFilters))
|
||||||
);
|
);
|
||||||
this.appliedFilters[this.appliedFilters.length - 1].query_operator = null;
|
|
||||||
this.$emit('applyFilter', this.appliedFilters);
|
this.$emit('applyFilter', this.appliedFilters);
|
||||||
},
|
},
|
||||||
resetFilter(index, currentFilter) {
|
resetFilter(index, currentFilter) {
|
||||||
|
|||||||
@ -130,6 +130,10 @@ export default {
|
|||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
foldersId: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
showAssignee: {
|
showAssignee: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@ -248,8 +252,12 @@ export default {
|
|||||||
id: chat.id,
|
id: chat.id,
|
||||||
label: this.activeLabel,
|
label: this.activeLabel,
|
||||||
teamId: this.teamId,
|
teamId: this.teamId,
|
||||||
|
foldersId: this.foldersId,
|
||||||
conversationType: this.conversationType,
|
conversationType: this.conversationType,
|
||||||
});
|
});
|
||||||
|
if (this.isActiveChat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
router.push({ path: frontendURL(path) });
|
router.push({ path: frontendURL(path) });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -57,22 +57,26 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<spinner v-if="isPending" size="tiny" />
|
<spinner v-if="isPending" size="tiny" />
|
||||||
<a
|
<div
|
||||||
v-if="isATweet && isIncoming && sender"
|
v-if="showAvatar"
|
||||||
|
v-tooltip.top="tooltipForSender"
|
||||||
class="sender--info"
|
class="sender--info"
|
||||||
:href="twitterProfileLink"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer nofollow"
|
|
||||||
>
|
>
|
||||||
<woot-thumbnail
|
<woot-thumbnail
|
||||||
:src="sender.thumbnail"
|
:src="sender.thumbnail"
|
||||||
:username="sender.name"
|
:username="senderNameForAvatar"
|
||||||
size="16px"
|
size="16px"
|
||||||
/>
|
/>
|
||||||
<div class="sender--available-name">
|
<a
|
||||||
|
v-if="isATweet && isIncoming"
|
||||||
|
class="sender--available-name"
|
||||||
|
:href="twitterProfileLink"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer nofollow"
|
||||||
|
>
|
||||||
{{ sender.name }}
|
{{ sender.name }}
|
||||||
</div>
|
</a>
|
||||||
</a>
|
</div>
|
||||||
<div v-if="isFailed" class="message-failed--alert">
|
<div v-if="isFailed" class="message-failed--alert">
|
||||||
<woot-button
|
<woot-button
|
||||||
v-tooltip.top-end="$t('CONVERSATION.TRY_AGAIN')"
|
v-tooltip.top-end="$t('CONVERSATION.TRY_AGAIN')"
|
||||||
@ -113,7 +117,6 @@ import BubbleActions from './bubble/Actions';
|
|||||||
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';
|
||||||
|
|
||||||
import { isEmptyObject } from 'dashboard/helper/commons';
|
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import contentTypeMixin from 'shared/mixins/contentTypeMixin';
|
import contentTypeMixin from 'shared/mixins/contentTypeMixin';
|
||||||
import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages';
|
import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages';
|
||||||
@ -242,6 +245,12 @@ export default {
|
|||||||
isIncoming() {
|
isIncoming() {
|
||||||
return this.data.message_type === MESSAGE_TYPE.INCOMING;
|
return this.data.message_type === MESSAGE_TYPE.INCOMING;
|
||||||
},
|
},
|
||||||
|
isOutgoing() {
|
||||||
|
return this.data.message_type === MESSAGE_TYPE.OUTGOING;
|
||||||
|
},
|
||||||
|
isTemplate() {
|
||||||
|
return this.data.message_type === MESSAGE_TYPE.TEMPLATE;
|
||||||
|
},
|
||||||
emailHeadAttributes() {
|
emailHeadAttributes() {
|
||||||
return {
|
return {
|
||||||
email: this.contentAttributes.email,
|
email: this.contentAttributes.email,
|
||||||
@ -258,6 +267,19 @@ export default {
|
|||||||
hasText() {
|
hasText() {
|
||||||
return !!this.data.content;
|
return !!this.data.content;
|
||||||
},
|
},
|
||||||
|
tooltipForSender() {
|
||||||
|
const name = this.senderNameForAvatar;
|
||||||
|
const { message_type: messageType } = this.data;
|
||||||
|
const showTooltip =
|
||||||
|
messageType === MESSAGE_TYPE.OUTGOING ||
|
||||||
|
messageType === MESSAGE_TYPE.TEMPLATE;
|
||||||
|
return showTooltip
|
||||||
|
? {
|
||||||
|
content: `${this.$t('CONVERSATION.SENT_BY')} ${name}`,
|
||||||
|
classes: 'top',
|
||||||
|
}
|
||||||
|
: false;
|
||||||
|
},
|
||||||
messageToolTip() {
|
messageToolTip() {
|
||||||
if (this.isMessageDeleted) {
|
if (this.isMessageDeleted) {
|
||||||
return false;
|
return false;
|
||||||
@ -265,13 +287,7 @@ export default {
|
|||||||
if (this.isFailed) {
|
if (this.isFailed) {
|
||||||
return this.$t(`CONVERSATION.SEND_FAILED`);
|
return this.$t(`CONVERSATION.SEND_FAILED`);
|
||||||
}
|
}
|
||||||
const { sender } = this;
|
return false;
|
||||||
return this.data.message_type === 1 && !isEmptyObject(sender)
|
|
||||||
? {
|
|
||||||
content: `${this.$t('CONVERSATION.SENT_BY')} ${sender.name}`,
|
|
||||||
classes: 'top',
|
|
||||||
}
|
|
||||||
: false;
|
|
||||||
},
|
},
|
||||||
wrapClass() {
|
wrapClass() {
|
||||||
return {
|
return {
|
||||||
@ -313,6 +329,19 @@ export default {
|
|||||||
const { meta } = this.data;
|
const { meta } = this.data;
|
||||||
return meta ? meta.error : '';
|
return meta ? meta.error : '';
|
||||||
},
|
},
|
||||||
|
showAvatar() {
|
||||||
|
if (this.isOutgoing || this.isTemplate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.isATweet && this.isIncoming && this.sender;
|
||||||
|
},
|
||||||
|
senderNameForAvatar() {
|
||||||
|
if (this.isOutgoing || this.isTemplate) {
|
||||||
|
const { name = this.$t('CONVERSATION.BOT') } = this.sender || {};
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
data() {
|
data() {
|
||||||
@ -423,6 +452,9 @@ export default {
|
|||||||
.message-text--metadata .time {
|
.message-text--metadata .time {
|
||||||
color: var(--v-50);
|
color: var(--v-50);
|
||||||
}
|
}
|
||||||
|
&.is-private .message-text--metadata .time {
|
||||||
|
color: var(--s-400);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-failed {
|
&.is-failed {
|
||||||
|
|||||||
@ -1,56 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="view-box fill-height">
|
<div class="view-box fill-height">
|
||||||
<div
|
<banner
|
||||||
v-if="!currentChat.can_reply && !isAWhatsappChannel"
|
v-if="!currentChat.can_reply && !isAWhatsappChannel"
|
||||||
class="banner messenger-policy--banner"
|
color-scheme="alert"
|
||||||
>
|
:banner-message="$t('CONVERSATION.CANNOT_REPLY')"
|
||||||
<span>
|
:href-link="facebookReplyPolicy"
|
||||||
{{ $t('CONVERSATION.CANNOT_REPLY') }}
|
:href-link-text="$t('CONVERSATION.24_HOURS_WINDOW')"
|
||||||
<a
|
/>
|
||||||
:href="facebookReplyPolicy"
|
|
||||||
rel="noopener noreferrer nofollow"
|
<banner
|
||||||
target="_blank"
|
v-if="!currentChat.can_reply && isAWhatsappChannel"
|
||||||
>
|
color-scheme="alert"
|
||||||
{{ $t('CONVERSATION.24_HOURS_WINDOW') }}
|
:banner-message="$t('CONVERSATION.TWILIO_WHATSAPP_CAN_REPLY')"
|
||||||
</a>
|
:href-link="twilioWhatsAppReplyPolicy"
|
||||||
</span>
|
:href-link-text="$t('CONVERSATION.TWILIO_WHATSAPP_24_HOURS_WINDOW')"
|
||||||
</div>
|
/>
|
||||||
<div
|
|
||||||
v-if="!currentChat.can_reply && isAWhatsappChannel"
|
<banner
|
||||||
class="banner messenger-policy--banner"
|
v-if="isATweet"
|
||||||
>
|
color-scheme="gray"
|
||||||
<span>
|
:banner-message="tweetBannerText"
|
||||||
{{ $t('CONVERSATION.TWILIO_WHATSAPP_CAN_REPLY') }}
|
:has-close-button="hasSelectedTweetId"
|
||||||
<a
|
@close="removeTweetSelection"
|
||||||
:href="twilioWhatsAppReplyPolicy"
|
/>
|
||||||
rel="noopener noreferrer nofollow"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
{{ $t('CONVERSATION.TWILIO_WHATSAPP_24_HOURS_WINDOW') }}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="isATweet" class="banner">
|
|
||||||
<span v-if="!selectedTweetId">
|
|
||||||
{{ $t('CONVERSATION.SELECT_A_TWEET_TO_REPLY') }}
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
{{ $t('CONVERSATION.REPLYING_TO') }}
|
|
||||||
{{ selectedTweet.content || '' }}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
v-if="selectedTweetId"
|
|
||||||
class="banner-close-button"
|
|
||||||
@click="removeTweetSelection"
|
|
||||||
>
|
|
||||||
<fluent-icon
|
|
||||||
v-tooltip="$t('CONVERSATION.REMOVE_SELECTION')"
|
|
||||||
size="16"
|
|
||||||
icon="dismiss"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="sidebar-toggle__wrap">
|
<div class="sidebar-toggle__wrap">
|
||||||
<woot-button
|
<woot-button
|
||||||
variant="smooth"
|
variant="smooth"
|
||||||
@ -126,6 +99,7 @@ import { mapGetters } from 'vuex';
|
|||||||
import ReplyBox from './ReplyBox';
|
import ReplyBox from './ReplyBox';
|
||||||
import Message from './Message';
|
import Message from './Message';
|
||||||
import conversationMixin from '../../../mixins/conversations';
|
import conversationMixin from '../../../mixins/conversations';
|
||||||
|
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||||
import { getTypingUsersText } from '../../../helper/commons';
|
import { getTypingUsersText } from '../../../helper/commons';
|
||||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||||
import { REPLY_POLICY } from 'shared/constants/links';
|
import { REPLY_POLICY } from 'shared/constants/links';
|
||||||
@ -139,6 +113,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
Message,
|
Message,
|
||||||
ReplyBox,
|
ReplyBox,
|
||||||
|
Banner,
|
||||||
},
|
},
|
||||||
mixins: [conversationMixin, inboxMixin, eventListenerMixins, clickaway],
|
mixins: [conversationMixin, inboxMixin, eventListenerMixins, clickaway],
|
||||||
props: {
|
props: {
|
||||||
@ -173,7 +148,17 @@ export default {
|
|||||||
inbox() {
|
inbox() {
|
||||||
return this.$store.getters['inboxes/getInbox'](this.inboxId);
|
return this.$store.getters['inboxes/getInbox'](this.inboxId);
|
||||||
},
|
},
|
||||||
|
hasSelectedTweetId() {
|
||||||
|
return !!this.selectedTweetId;
|
||||||
|
},
|
||||||
|
|
||||||
|
tweetBannerText() {
|
||||||
|
return !this.selectedTweetId
|
||||||
|
? this.$t('CONVERSATION.SELECT_A_TWEET_TO_REPLY')
|
||||||
|
: `
|
||||||
|
${this.$t('CONVERSATION.REPLYING_TO')}
|
||||||
|
${this.selectedTweet.content}` || '';
|
||||||
|
},
|
||||||
typingUsersList() {
|
typingUsersList() {
|
||||||
const userList = this.$store.getters[
|
const userList = this.$store.getters[
|
||||||
'conversationTypingStatus/getUserList'
|
'conversationTypingStatus/getUserList'
|
||||||
@ -375,31 +360,6 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.banner {
|
|
||||||
background: var(--b-500);
|
|
||||||
color: var(--white);
|
|
||||||
font-size: var(--font-size-mini);
|
|
||||||
padding: var(--space-slab) var(--space-normal);
|
|
||||||
text-align: center;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: underline;
|
|
||||||
color: var(--white);
|
|
||||||
font-size: var(--font-size-mini);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.messenger-policy--banner {
|
|
||||||
background: var(--r-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-close-button {
|
|
||||||
cursor: pointer;
|
|
||||||
margin-left: var(--space--two);
|
|
||||||
color: var(--white);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner--container {
|
.spinner--container {
|
||||||
min-height: var(--space-jumbo);
|
min-height: var(--space-jumbo);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="reply-box" :class="replyBoxClass">
|
<div class="reply-box" :class="replyBoxClass">
|
||||||
|
<banner
|
||||||
|
v-if="showSelfAssignBanner"
|
||||||
|
color-scheme="secondary"
|
||||||
|
:banner-message="$t('CONVERSATION.NOT_ASSIGNED_TO_YOU')"
|
||||||
|
:has-action-button="true"
|
||||||
|
:action-button-label="$t('CONVERSATION.ASSIGN_TO_ME')"
|
||||||
|
@click="onClickSelfAssign"
|
||||||
|
/>
|
||||||
<reply-top-panel
|
<reply-top-panel
|
||||||
:mode="replyType"
|
:mode="replyType"
|
||||||
:set-reply-mode="setReplyMode"
|
:set-reply-mode="setReplyMode"
|
||||||
@ -58,8 +66,16 @@
|
|||||||
:remove-attachment="removeAttachment"
|
:remove-attachment="removeAttachment"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="showMessageSignature"
|
||||||
|
v-tooltip="$t('CONVERSATION.FOOTER.MESSAGE_SIGN_TOOLTIP')"
|
||||||
|
class="message-signature-wrap"
|
||||||
|
>
|
||||||
|
<p class="message-signature" v-html="formatMessage(messageSignature)" />
|
||||||
|
</div>
|
||||||
<reply-bottom-panel
|
<reply-bottom-panel
|
||||||
:mode="replyType"
|
:mode="replyType"
|
||||||
|
:inbox="inbox"
|
||||||
:send-button-text="replyButtonLabel"
|
:send-button-text="replyButtonLabel"
|
||||||
:on-file-upload="onFileUpload"
|
:on-file-upload="onFileUpload"
|
||||||
:show-file-upload="showFileUpload"
|
:show-file-upload="showFileUpload"
|
||||||
@ -72,6 +88,7 @@
|
|||||||
:is-format-mode="showRichContentEditor"
|
:is-format-mode="showRichContentEditor"
|
||||||
:enable-rich-editor="isRichEditorEnabled"
|
:enable-rich-editor="isRichEditorEnabled"
|
||||||
:enter-to-send-enabled="enterToSendEnabled"
|
:enter-to-send-enabled="enterToSendEnabled"
|
||||||
|
:enable-multiple-file-upload="enableMultipleFileUpload"
|
||||||
@toggleEnterToSend="toggleEnterToSend"
|
@toggleEnterToSend="toggleEnterToSend"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -89,8 +106,10 @@ import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview';
|
|||||||
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel';
|
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel';
|
||||||
import ReplyEmailHead from './ReplyEmailHead';
|
import ReplyEmailHead from './ReplyEmailHead';
|
||||||
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel';
|
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel';
|
||||||
|
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||||
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
|
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
|
||||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
|
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
|
||||||
|
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||||
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
|
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
|
||||||
import { MAXIMUM_FILE_UPLOAD_SIZE } from 'shared/constants/messages';
|
import { MAXIMUM_FILE_UPLOAD_SIZE } from 'shared/constants/messages';
|
||||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||||
@ -99,10 +118,12 @@ import {
|
|||||||
isEscape,
|
isEscape,
|
||||||
isEnter,
|
isEnter,
|
||||||
hasPressedShift,
|
hasPressedShift,
|
||||||
|
hasPressedCommandPlusKKey,
|
||||||
} from 'shared/helpers/KeyboardHelpers';
|
} from 'shared/helpers/KeyboardHelpers';
|
||||||
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
|
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
|
||||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||||
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||||
|
import { DirectUpload } from 'activestorage';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -114,8 +135,15 @@ export default {
|
|||||||
ReplyEmailHead,
|
ReplyEmailHead,
|
||||||
ReplyBottomPanel,
|
ReplyBottomPanel,
|
||||||
WootMessageEditor,
|
WootMessageEditor,
|
||||||
|
Banner,
|
||||||
},
|
},
|
||||||
mixins: [clickaway, inboxMixin, uiSettingsMixin, alertMixin],
|
mixins: [
|
||||||
|
clickaway,
|
||||||
|
inboxMixin,
|
||||||
|
uiSettingsMixin,
|
||||||
|
alertMixin,
|
||||||
|
messageFormatterMixin,
|
||||||
|
],
|
||||||
props: {
|
props: {
|
||||||
selectedTweet: {
|
selectedTweet: {
|
||||||
type: [Object, String],
|
type: [Object, String],
|
||||||
@ -147,6 +175,13 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
currentChat: 'getSelectedChat',
|
||||||
|
messageSignature: 'getMessageSignature',
|
||||||
|
currentUser: 'getCurrentUser',
|
||||||
|
globalConfig: 'globalConfig/get',
|
||||||
|
}),
|
||||||
|
|
||||||
showRichContentEditor() {
|
showRichContentEditor() {
|
||||||
if (this.isOnPrivateNote) {
|
if (this.isOnPrivateNote) {
|
||||||
return true;
|
return true;
|
||||||
@ -161,7 +196,36 @@ export default {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
...mapGetters({ currentChat: 'getSelectedChat' }),
|
assignedAgent: {
|
||||||
|
get() {
|
||||||
|
return this.currentChat.meta.assignee;
|
||||||
|
},
|
||||||
|
set(agent) {
|
||||||
|
const agentId = agent ? agent.id : 0;
|
||||||
|
this.$store.dispatch('setCurrentChatAssignee', agent);
|
||||||
|
this.$store
|
||||||
|
.dispatch('assignAgent', {
|
||||||
|
conversationId: this.currentChat.id,
|
||||||
|
agentId,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.showAlert(this.$t('CONVERSATION.CHANGE_AGENT'));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
showSelfAssignBanner() {
|
||||||
|
if (this.message !== '' && !this.isOnPrivateNote) {
|
||||||
|
if (!this.assignedAgent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.assignedAgent.id !== this.currentUser.id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
enterToSendEnabled() {
|
enterToSendEnabled() {
|
||||||
return !!this.uiSettings.enter_to_send_enabled;
|
return !!this.uiSettings.enter_to_send_enabled;
|
||||||
},
|
},
|
||||||
@ -189,7 +253,7 @@ export default {
|
|||||||
return this.maxLength - this.message.length;
|
return this.maxLength - this.message.length;
|
||||||
},
|
},
|
||||||
isReplyButtonDisabled() {
|
isReplyButtonDisabled() {
|
||||||
if (this.isATweet && !this.inReplyTo) {
|
if (this.isATweet && !this.inReplyTo && !this.isOnPrivateNote) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,6 +347,16 @@ export default {
|
|||||||
showReplyHead() {
|
showReplyHead() {
|
||||||
return !this.isOnPrivateNote && this.isAnEmailChannel;
|
return !this.isOnPrivateNote && this.isAnEmailChannel;
|
||||||
},
|
},
|
||||||
|
enableMultipleFileUpload() {
|
||||||
|
return this.isAnEmailChannel || this.isAWebWidgetInbox || this.isAPIInbox;
|
||||||
|
},
|
||||||
|
showMessageSignature() {
|
||||||
|
return !this.isPrivate && this.isAnEmailChannel && this.sendWithSignature;
|
||||||
|
},
|
||||||
|
sendWithSignature() {
|
||||||
|
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||||
|
return isEnabled;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
currentChat(conversation) {
|
currentChat(conversation) {
|
||||||
@ -355,17 +429,49 @@ export default {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.sendMessage();
|
this.sendMessage();
|
||||||
}
|
}
|
||||||
|
} else if (hasPressedCommandPlusKKey(e)) {
|
||||||
|
this.openCommandBar();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
openCommandBar() {
|
||||||
|
const ninja = document.querySelector('ninja-keys');
|
||||||
|
ninja.open();
|
||||||
|
},
|
||||||
toggleEnterToSend(enterToSendEnabled) {
|
toggleEnterToSend(enterToSendEnabled) {
|
||||||
this.updateUISettings({ enter_to_send_enabled: enterToSendEnabled });
|
this.updateUISettings({ enter_to_send_enabled: enterToSendEnabled });
|
||||||
},
|
},
|
||||||
|
onClickSelfAssign() {
|
||||||
|
const {
|
||||||
|
account_id,
|
||||||
|
availability_status,
|
||||||
|
available_name,
|
||||||
|
email,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
role,
|
||||||
|
avatar_url,
|
||||||
|
} = this.currentUser;
|
||||||
|
const selfAssign = {
|
||||||
|
account_id,
|
||||||
|
availability_status,
|
||||||
|
available_name,
|
||||||
|
email,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
role,
|
||||||
|
thumbnail: avatar_url,
|
||||||
|
};
|
||||||
|
this.assignedAgent = selfAssign;
|
||||||
|
},
|
||||||
async sendMessage() {
|
async sendMessage() {
|
||||||
if (this.isReplyButtonDisabled) {
|
if (this.isReplyButtonDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.showMentions) {
|
if (!this.showMentions) {
|
||||||
const newMessage = this.message;
|
let newMessage = this.message;
|
||||||
|
if (this.sendWithSignature && this.messageSignature) {
|
||||||
|
newMessage += '\n\n' + this.messageSignature;
|
||||||
|
}
|
||||||
const messagePayload = this.getMessagePayload(newMessage);
|
const messagePayload = this.getMessagePayload(newMessage);
|
||||||
this.clearMessage();
|
this.clearMessage();
|
||||||
try {
|
try {
|
||||||
@ -440,21 +546,30 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onFileUpload(file) {
|
onFileUpload(file) {
|
||||||
|
if (this.globalConfig.directUploadsEnabled) {
|
||||||
|
this.onDirectFileUpload(file);
|
||||||
|
} else {
|
||||||
|
this.onIndirectFileUpload(file);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDirectFileUpload(file) {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) {
|
if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) {
|
||||||
this.attachedFiles = [];
|
const upload = new DirectUpload(
|
||||||
const reader = new FileReader();
|
file.file,
|
||||||
reader.readAsDataURL(file.file);
|
'/rails/active_storage/direct_uploads',
|
||||||
reader.onloadend = () => {
|
null,
|
||||||
this.attachedFiles.push({
|
file.file.name
|
||||||
currentChatId: this.currentChat.id,
|
);
|
||||||
resource: file,
|
upload.create((error, blob) => {
|
||||||
isPrivate: this.isPrivate,
|
if (error) {
|
||||||
thumb: reader.result,
|
this.showAlert(error);
|
||||||
});
|
} else {
|
||||||
};
|
this.attachFile({ file, blob });
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.showAlert(
|
this.showAlert(
|
||||||
this.$t('CONVERSATION.FILE_SIZE_LIMIT', {
|
this.$t('CONVERSATION.FILE_SIZE_LIMIT', {
|
||||||
@ -463,13 +578,39 @@ export default {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onIndirectFileUpload(file) {
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) {
|
||||||
|
this.attachFile({ file });
|
||||||
|
} else {
|
||||||
|
this.showAlert(
|
||||||
|
this.$t('CONVERSATION.FILE_SIZE_LIMIT', {
|
||||||
|
MAXIMUM_FILE_UPLOAD_SIZE,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
attachFile({ blob, file }) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file.file);
|
||||||
|
reader.onloadend = () => {
|
||||||
|
this.attachedFiles.push({
|
||||||
|
currentChatId: this.currentChat.id,
|
||||||
|
resource: blob || file,
|
||||||
|
isPrivate: this.isPrivate,
|
||||||
|
thumb: reader.result,
|
||||||
|
blobSignedId: blob ? blob.signed_id : undefined,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
removeAttachment(itemIndex) {
|
removeAttachment(itemIndex) {
|
||||||
this.attachedFiles = this.attachedFiles.filter(
|
this.attachedFiles = this.attachedFiles.filter(
|
||||||
(item, index) => itemIndex !== index
|
(item, index) => itemIndex !== index
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
getMessagePayload(message) {
|
getMessagePayload(message) {
|
||||||
const [attachment] = this.attachedFiles;
|
|
||||||
const messagePayload = {
|
const messagePayload = {
|
||||||
conversationId: this.currentChat.id,
|
conversationId: this.currentChat.id,
|
||||||
message,
|
message,
|
||||||
@ -480,8 +621,15 @@ export default {
|
|||||||
messagePayload.contentAttributes = { in_reply_to: this.inReplyTo };
|
messagePayload.contentAttributes = { in_reply_to: this.inReplyTo };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attachment) {
|
if (this.attachedFiles && this.attachedFiles.length) {
|
||||||
messagePayload.file = attachment.resource.file;
|
messagePayload.files = [];
|
||||||
|
this.attachedFiles.forEach(attachment => {
|
||||||
|
if (this.globalConfig.directUploadsEnabled) {
|
||||||
|
messagePayload.files.push(attachment.blobSignedId);
|
||||||
|
} else {
|
||||||
|
messagePayload.files.push(attachment.resource.file);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.ccEmails) {
|
if (this.ccEmails) {
|
||||||
@ -510,6 +658,25 @@ export default {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-signature-wrap {
|
||||||
|
margin: 0 var(--space-normal);
|
||||||
|
padding: var(--space-small);
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
border: 1px dashed var(--s-100);
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--s-25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-signature {
|
||||||
|
width: fit-content;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.attachment-preview-box {
|
.attachment-preview-box {
|
||||||
padding: 0 var(--space-normal);
|
padding: 0 var(--space-normal);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|||||||
@ -1,229 +1,144 @@
|
|||||||
|
import {
|
||||||
|
OPERATOR_TYPES_1,
|
||||||
|
OPERATOR_TYPES_2,
|
||||||
|
OPERATOR_TYPES_3,
|
||||||
|
} from '../../FilterInput/FilterOperatorTypes';
|
||||||
|
|
||||||
const filterTypes = [
|
const filterTypes = [
|
||||||
{
|
{
|
||||||
attributeKey: 'status',
|
attributeKey: 'status',
|
||||||
attributeI18nKey: 'STATUS',
|
attributeI18nKey: 'STATUS',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
filterOperators: [
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
{
|
attributeModel: 'standard',
|
||||||
value: 'equal_to',
|
|
||||||
label: 'Equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'not_equal_to',
|
|
||||||
label: 'Not equal to',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
attribute_type: 'standard',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributeKey: 'assignee_id',
|
attributeKey: 'assignee_id',
|
||||||
attributeI18nKey: 'ASSIGNEE_NAME',
|
attributeI18nKey: 'ASSIGNEE_NAME',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
filterOperators: [
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
{
|
attributeModel: 'standard',
|
||||||
value: 'equal_to',
|
|
||||||
label: 'Equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'not_equal_to',
|
|
||||||
label: 'Not equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'is_present',
|
|
||||||
label: 'Is present',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'is_not_present',
|
|
||||||
label: 'Is not present',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
attribute_type: 'standard',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributeKey: 'inbox_id',
|
attributeKey: 'inbox_id',
|
||||||
attributeI18nKey: 'INBOX_NAME',
|
attributeI18nKey: 'INBOX_NAME',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
filterOperators: [
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
{
|
attributeModel: 'standard',
|
||||||
value: 'equal_to',
|
|
||||||
label: 'Equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'not_equal_to',
|
|
||||||
label: 'Not equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'is_present',
|
|
||||||
label: 'Is present',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'is_not_present',
|
|
||||||
label: 'Is not present',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
attribute_type: 'standard',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributeKey: 'team_id',
|
attributeKey: 'team_id',
|
||||||
attributeI18nKey: 'TEAM_NAME',
|
attributeI18nKey: 'TEAM_NAME',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
dataType: 'number',
|
dataType: 'number',
|
||||||
filterOperators: [
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
{
|
attributeModel: 'standard',
|
||||||
value: 'equal_to',
|
|
||||||
label: 'Equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'not_equal_to',
|
|
||||||
label: 'Not equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'is_present',
|
|
||||||
label: 'Is present',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'is_not_present',
|
|
||||||
label: 'Is not present',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
attribute_type: 'standard',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributeKey: 'display_id',
|
attributeKey: 'display_id',
|
||||||
attributeI18nKey: 'CONVERSATION_IDENTIFIER',
|
attributeI18nKey: 'CONVERSATION_IDENTIFIER',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
dataType: 'Number',
|
dataType: 'Number',
|
||||||
filterOperators: [
|
filterOperators: OPERATOR_TYPES_3,
|
||||||
{
|
attributeModel: 'standard',
|
||||||
value: 'equal_to',
|
|
||||||
label: 'Equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'not_equal_to',
|
|
||||||
label: 'Not equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'contains',
|
|
||||||
label: 'Contains',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'does_not_contain',
|
|
||||||
label: 'Does not contain',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
attribute_type: 'standard',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributeKey: 'campaign_id',
|
attributeKey: 'campaign_id',
|
||||||
attributeI18nKey: 'CAMPAIGN_NAME',
|
attributeI18nKey: 'CAMPAIGN_NAME',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
dataType: 'Number',
|
dataType: 'Number',
|
||||||
filterOperators: [
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
{
|
attributeModel: 'standard',
|
||||||
value: 'equal_to',
|
|
||||||
label: 'Equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'not_equal_to',
|
|
||||||
label: 'Not equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'is_present',
|
|
||||||
label: 'Is present',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'is_not_present',
|
|
||||||
label: 'Is not present',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
attribute_type: 'standard',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributeKey: 'labels',
|
attributeKey: 'labels',
|
||||||
attributeI18nKey: 'LABELS',
|
attributeI18nKey: 'LABELS',
|
||||||
inputType: 'multi_select',
|
inputType: 'multi_select',
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
filterOperators: [
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
{
|
attributeModel: 'standard',
|
||||||
value: 'equal_to',
|
|
||||||
label: 'Equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'not_equal_to',
|
|
||||||
label: 'Not equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'is_present',
|
|
||||||
label: 'Is present',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'is_not_present',
|
|
||||||
label: 'Is not present',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
attribute_type: 'standard',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributeKey: 'browser_language',
|
attributeKey: 'browser_language',
|
||||||
attributeI18nKey: 'BROWSER_LANGUAGE',
|
attributeI18nKey: 'BROWSER_LANGUAGE',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
filterOperators: [
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
{
|
attributeModel: 'additional',
|
||||||
value: 'equal_to',
|
|
||||||
label: 'Equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'not_equal_to',
|
|
||||||
label: 'Not equal to',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
attribute_type: 'additional_attributes',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributeKey: 'country_code',
|
attributeKey: 'country_code',
|
||||||
attributeI18nKey: 'COUNTRY_NAME',
|
attributeI18nKey: 'COUNTRY_NAME',
|
||||||
inputType: 'search_select',
|
inputType: 'search_select',
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
filterOperators: [
|
filterOperators: OPERATOR_TYPES_1,
|
||||||
{
|
attributeModel: 'additional',
|
||||||
value: 'equal_to',
|
|
||||||
label: 'Equal to',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'not_equal_to',
|
|
||||||
label: 'Not equal to',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
attribute_type: 'additional_attributes',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
attributeKey: 'referer',
|
attributeKey: 'referer',
|
||||||
attributeI18nKey: 'REFERER_LINK',
|
attributeI18nKey: 'REFERER_LINK',
|
||||||
inputType: 'plain_text',
|
inputType: 'plain_text',
|
||||||
dataType: 'text',
|
dataType: 'text',
|
||||||
filterOperators: [
|
filterOperators: OPERATOR_TYPES_3,
|
||||||
|
attributeModel: 'additional',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const filterAttributeGroups = [
|
||||||
|
{
|
||||||
|
name: 'Standard Filters',
|
||||||
|
i18nGroup: 'STANDARD_FILTERS',
|
||||||
|
attributes: [
|
||||||
{
|
{
|
||||||
value: 'equal_to',
|
key: 'status',
|
||||||
label: 'Equal to',
|
i18nKey: 'STATUS',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'not_equal_to',
|
key: 'assignee_id',
|
||||||
label: 'Not equal to',
|
i18nKey: 'ASSIGNEE_NAME',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'contains',
|
key: 'inbox_id',
|
||||||
label: 'Contains',
|
i18nKey: 'INBOX_NAME',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'does_not_contain',
|
key: 'team_id',
|
||||||
label: 'Does not contain',
|
i18nKey: 'TEAM_NAME',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'display_id',
|
||||||
|
i18nKey: 'CONVERSATION_IDENTIFIER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'campaign_id',
|
||||||
|
i18nKey: 'CAMPAIGN_NAME',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'labels',
|
||||||
|
i18nKey: 'LABELS',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Additional Filters',
|
||||||
|
i18nGroup: 'ADDITIONAL_FILTERS',
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
key: 'browser_language',
|
||||||
|
i18nKey: 'BROWSER_LANGUAGE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'country_code',
|
||||||
|
i18nKey: 'COUNTRY_NAME',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'referer',
|
||||||
|
i18nKey: 'REFERER_LINK',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
attribute_type: 'additional_attributes',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -137,10 +137,10 @@ export default {
|
|||||||
.time {
|
.time {
|
||||||
color: var(--w-100);
|
color: var(--w-100);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.action--icon {
|
.action--icon {
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,10 @@
|
|||||||
class="message__mail-head"
|
class="message__mail-head"
|
||||||
:class="{ 'is-incoming': isIncoming }"
|
:class="{ 'is-incoming': isIncoming }"
|
||||||
>
|
>
|
||||||
|
<div v-if="fromMail" class="meta-wrap">
|
||||||
|
<span class="message__content--type">{{ $t('EMAIL_HEADER.FROM') }}:</span>
|
||||||
|
<span>{{ fromMail }}</span>
|
||||||
|
</div>
|
||||||
<div v-if="toMails" class="meta-wrap">
|
<div v-if="toMails" class="meta-wrap">
|
||||||
<span class="message__content--type">{{ $t('EMAIL_HEADER.TO') }}:</span>
|
<span class="message__content--type">{{ $t('EMAIL_HEADER.TO') }}:</span>
|
||||||
<span>{{ toMails }}</span>
|
<span>{{ toMails }}</span>
|
||||||
@ -46,6 +50,10 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
fromMail() {
|
||||||
|
const from = this.emailAttributes.from || [];
|
||||||
|
return from.join(', ');
|
||||||
|
},
|
||||||
toMails() {
|
toMails() {
|
||||||
const to = this.emailAttributes.to || [];
|
const to = this.emailAttributes.to || [];
|
||||||
return to.join(', ');
|
return to.join(', ');
|
||||||
|
|||||||
@ -84,7 +84,8 @@ describe('MoveActions', () => {
|
|||||||
|
|
||||||
expect(window.bus.$emit).toBeCalledWith(
|
expect(window.bus.$emit).toBeCalledWith(
|
||||||
'newToastMessage',
|
'newToastMessage',
|
||||||
'This conversation is muted for 6 hours'
|
'This conversation is muted for 6 hours',
|
||||||
|
undefined
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -109,7 +110,8 @@ describe('MoveActions', () => {
|
|||||||
|
|
||||||
expect(window.bus.$emit).toBeCalledWith(
|
expect(window.bus.$emit).toBeCalledWith(
|
||||||
'newToastMessage',
|
'newToastMessage',
|
||||||
'This conversation is unmuted'
|
'This conversation is unmuted',
|
||||||
|
undefined
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export const conversationUrl = ({
|
|||||||
label,
|
label,
|
||||||
teamId,
|
teamId,
|
||||||
conversationType = '',
|
conversationType = '',
|
||||||
|
foldersId,
|
||||||
}) => {
|
}) => {
|
||||||
let url = `accounts/${accountId}/conversations/${id}`;
|
let url = `accounts/${accountId}/conversations/${id}`;
|
||||||
if (activeInbox) {
|
if (activeInbox) {
|
||||||
@ -20,6 +21,8 @@ export const conversationUrl = ({
|
|||||||
url = `accounts/${accountId}/label/${label}/conversations/${id}`;
|
url = `accounts/${accountId}/label/${label}/conversations/${id}`;
|
||||||
} else if (teamId) {
|
} else if (teamId) {
|
||||||
url = `accounts/${accountId}/team/${teamId}/conversations/${id}`;
|
url = `accounts/${accountId}/team/${teamId}/conversations/${id}`;
|
||||||
|
} else if (foldersId && foldersId !== 0) {
|
||||||
|
url = `accounts/${accountId}/custom_view/${foldersId}/conversations/${id}`;
|
||||||
} else if (conversationType === 'mention') {
|
} else if (conversationType === 'mention') {
|
||||||
url = `accounts/${accountId}/mentions/conversations/${id}`;
|
url = `accounts/${accountId}/mentions/conversations/${id}`;
|
||||||
}
|
}
|
||||||
@ -34,3 +37,9 @@ export const accountIdFromPathname = pathname => {
|
|||||||
const accountId = isScoped ? Number(urlParam) : '';
|
const accountId = isScoped ? Number(urlParam) : '';
|
||||||
return accountId;
|
return accountId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isValidURL = value => {
|
||||||
|
/* eslint-disable no-useless-escape */
|
||||||
|
const URL_REGEX = /^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/gm;
|
||||||
|
return URL_REGEX.test(value);
|
||||||
|
};
|
||||||
|
|||||||
18
app/javascript/dashboard/helper/actionQueryGenerator.js
Normal file
18
app/javascript/dashboard/helper/actionQueryGenerator.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const generatePayload = data => {
|
||||||
|
const actions = JSON.parse(JSON.stringify(data));
|
||||||
|
let payload = actions.map(item => {
|
||||||
|
if (Array.isArray(item.action_params)) {
|
||||||
|
item.action_params = item.action_params.map(val => val.id);
|
||||||
|
} else if (typeof item.values === 'object') {
|
||||||
|
item.action_params = [item.action_params.id];
|
||||||
|
} else if (!item.action_params) {
|
||||||
|
item.action_params = [];
|
||||||
|
} else {
|
||||||
|
item.action_params = [item.action_params];
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
return payload;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default generatePayload;
|
||||||
@ -1,5 +1,19 @@
|
|||||||
|
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 => {
|
||||||
let payload = data.map(item => {
|
// Make a copy of data to avoid vue data reactivity issues
|
||||||
|
const filters = JSON.parse(JSON.stringify(data));
|
||||||
|
let payload = filters.map(item => {
|
||||||
if (Array.isArray(item.values)) {
|
if (Array.isArray(item.values)) {
|
||||||
item.values = item.values.map(val => val.id);
|
item.values = item.values.map(val => val.id);
|
||||||
} else if (typeof item.values === 'object') {
|
} else if (typeof item.values === 'object') {
|
||||||
@ -9,9 +23,14 @@ 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
|
||||||
|
// last query will have an extra query_operator, this would break the api.
|
||||||
|
// Setting this to null for all query payload
|
||||||
|
payload[payload.length - 1].query_operator = undefined;
|
||||||
return { payload };
|
return { payload };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import {
|
|||||||
frontendURL,
|
frontendURL,
|
||||||
conversationUrl,
|
conversationUrl,
|
||||||
accountIdFromPathname,
|
accountIdFromPathname,
|
||||||
|
isValidURL,
|
||||||
} from '../URLHelper';
|
} from '../URLHelper';
|
||||||
|
|
||||||
describe('#URL Helpers', () => {
|
describe('#URL Helpers', () => {
|
||||||
@ -48,4 +49,13 @@ describe('#URL Helpers', () => {
|
|||||||
expect(accountIdFromPathname('')).toBe('');
|
expect(accountIdFromPathname('')).toBe('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isValidURL', () => {
|
||||||
|
it('should return true if valid url is passed', () => {
|
||||||
|
expect(isValidURL('https://chatwoot.com')).toBe(true);
|
||||||
|
});
|
||||||
|
it('should return false if invalid url is passed', () => {
|
||||||
|
expect(isValidURL('alert.window')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
import actionQueryGenerator from '../actionQueryGenerator';
|
||||||
|
|
||||||
|
const testData = [
|
||||||
|
{
|
||||||
|
action_name: 'add_label',
|
||||||
|
action_params: [{ id: 'testlabel', name: 'testlabel' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'sales team',
|
||||||
|
description: 'This is our internal sales team',
|
||||||
|
allow_auto_assign: true,
|
||||||
|
account_id: 1,
|
||||||
|
is_member: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const finalResult = [
|
||||||
|
{
|
||||||
|
action_name: 'add_label',
|
||||||
|
action_params: ['testlabel'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action_name: 'assign_team',
|
||||||
|
action_params: [1],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('#actionQueryGenerator', () => {
|
||||||
|
it('returns the correct format of filter query', () => {
|
||||||
|
expect(actionQueryGenerator(testData)).toEqual(finalResult);
|
||||||
|
expect(
|
||||||
|
actionQueryGenerator(testData).every(i => Array.isArray(i.action_params))
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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',
|
||||||
@ -31,7 +31,7 @@ const testData = [
|
|||||||
attribute_key: 'id',
|
attribute_key: 'id',
|
||||||
filter_operator: 'equal_to',
|
filter_operator: 'equal_to',
|
||||||
values: 'This is a test',
|
values: 'This is a test',
|
||||||
query_operator: null,
|
query_operator: 'or',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -52,8 +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'],
|
||||||
query_operator: null,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"FILTER": {
|
"FILTER": {
|
||||||
"TITLE": "تصفية المحادثة",
|
"TITLE": "تصفية المحادثات",
|
||||||
"SUBTITLE": "إضافة فلاتر أدناه واضغط على 'إرسال' لتصفية المحادثات.",
|
"SUBTITLE": "إضافة فلاتر أدناه واضغط على 'إرسال' لتصفية المحادثات.",
|
||||||
"ADD_NEW_FILTER": "إضافة فلتر",
|
"ADD_NEW_FILTER": "إضافة فلتر",
|
||||||
"FILTER_DELETE_ERROR": "يجب ان يكون لديك فلتر واحد على الاقل",
|
"FILTER_DELETE_ERROR": "يجب ان يكون لديك فلتر واحد على الاقل",
|
||||||
@ -19,7 +19,9 @@
|
|||||||
"contains": "يحتوي",
|
"contains": "يحتوي",
|
||||||
"does_not_contain": "لا يحتوي",
|
"does_not_contain": "لا يحتوي",
|
||||||
"is_present": "موجود",
|
"is_present": "موجود",
|
||||||
"is_not_present": "غير موجود"
|
"is_not_present": "غير موجود",
|
||||||
|
"is_greater_than": "هو أكبر من",
|
||||||
|
"is_lesser_than": "هو أقل من"
|
||||||
},
|
},
|
||||||
"ATTRIBUTES": {
|
"ATTRIBUTES": {
|
||||||
"STATUS": "الحالة",
|
"STATUS": "الحالة",
|
||||||
@ -31,7 +33,54 @@
|
|||||||
"LABELS": "الوسوم",
|
"LABELS": "الوسوم",
|
||||||
"BROWSER_LANGUAGE": "لغة المتصفح",
|
"BROWSER_LANGUAGE": "لغة المتصفح",
|
||||||
"COUNTRY_NAME": "اسم الدولة",
|
"COUNTRY_NAME": "اسم الدولة",
|
||||||
"REFERER_LINK": "رابط المرجع"
|
"REFERER_LINK": "رابط المرجع",
|
||||||
|
"CUSTOM_ATTRIBUTE_LIST": "القائمة",
|
||||||
|
"CUSTOM_ATTRIBUTE_TEXT": "النص",
|
||||||
|
"CUSTOM_ATTRIBUTE_NUMBER": "العدد",
|
||||||
|
"CUSTOM_ATTRIBUTE_LINK": "الرابط",
|
||||||
|
"CUSTOM_ATTRIBUTE_CHECKBOX": "مربع"
|
||||||
|
},
|
||||||
|
"GROUPS": {
|
||||||
|
"STANDARD_FILTERS": "الفلاتر القياسية",
|
||||||
|
"ADDITIONAL_FILTERS": "فلاتر إضافية",
|
||||||
|
"CUSTOM_ATTRIBUTES": "سمات مخصصة"
|
||||||
|
},
|
||||||
|
"CUSTOM_VIEWS": {
|
||||||
|
"ADD": {
|
||||||
|
"TITLE": "هل تريد حفظ هذا الفلتر؟",
|
||||||
|
"LABEL": "تسمية هذا الفلتر",
|
||||||
|
"PLACEHOLDER": "أدخل اسم لهذا الفلتر",
|
||||||
|
"ERROR_MESSAGE": "الاسم مطلوب",
|
||||||
|
"SAVE_BUTTON": "حفظ الفلتر",
|
||||||
|
"CANCEL_BUTTON": "إلغاء",
|
||||||
|
"API_FOLDERS": {
|
||||||
|
"SUCCESS_MESSAGE": "تم إنشاء طريقة عرض مخصصة بنجاح",
|
||||||
|
"ERROR_MESSAGE": "خطأ أثناء إنشاء طريقة عرض مخصصة"
|
||||||
|
},
|
||||||
|
"API_SEGMENTS": {
|
||||||
|
"SUCCESS_MESSAGE": "تم إنشاء طريقة عرض مخصصة بنجاح",
|
||||||
|
"ERROR_MESSAGE": "خطأ أثناء إنشاء طريقة عرض مخصصة"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"DELETE_BUTTON": "حذف الفلتر",
|
||||||
|
"MODAL": {
|
||||||
|
"CONFIRM": {
|
||||||
|
"TITLE": "تأكيد الحذف",
|
||||||
|
"MESSAGE": "هل أنت متأكد من حذف الفلتر ",
|
||||||
|
"YES": "نعم، احذف",
|
||||||
|
"NO": "لا، احتفظ به"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"API_FOLDERS": {
|
||||||
|
"SUCCESS_MESSAGE": "تم حذف طريقة عرض مخصصة بنجاح",
|
||||||
|
"ERROR_MESSAGE": "حدث خطأ أثناء حذف المجلد"
|
||||||
|
},
|
||||||
|
"API_SEGMENTS": {
|
||||||
|
"SUCCESS_MESSAGE": "تم حذف العرض المخصص بنجاح",
|
||||||
|
"ERROR_MESSAGE": "حدث خطأ أثناء حذف طريقة عرض مخصصة"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,89 @@
|
|||||||
{
|
{
|
||||||
"AUTOMATION": {
|
"AUTOMATION": {
|
||||||
"HEADER": "الأتمتة",
|
"HEADER": "الأتمتة",
|
||||||
"HEADER_BTN_TXT": "إضافة قاعدة أتمتة"
|
"HEADER_BTN_TXT": "إضافة قاعدة أتمتة",
|
||||||
|
"LOADING": "جلب قواعد الأتمتة",
|
||||||
|
"SIDEBAR_TXT": "<p><b>قواعد الأتمتة الآليه</b> <p>يمكن للأتمتة استبدال وأتمتة العمليات القائمة التي تتطلب جهداً يدوياً. يمكنك القيام بالعديد من الأشياء مع التشغيل الآلي، بما في ذلك إضافة تسميات وتعيين المحادثة لأفضل وكيل. لذا يركز الفريق على ما يفعلونه على أفضل وجه ويقضي وقتاً قليلاً على المهام اليدوية.</p>",
|
||||||
|
"ADD": {
|
||||||
|
"TITLE": "إضافة قاعدة أتمتة",
|
||||||
|
"SUBMIT": "إنشاء",
|
||||||
|
"CANCEL_BUTTON_TEXT": "إلغاء",
|
||||||
|
"FORM": {
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "اسم القاعدة",
|
||||||
|
"PLACEHOLDER": "أدخل اسم القاعدة",
|
||||||
|
"ERROR": "الاسم مطلوب"
|
||||||
|
},
|
||||||
|
"DESC": {
|
||||||
|
"LABEL": "الوصف",
|
||||||
|
"PLACEHOLDER": "ادخل وصف القاعدة",
|
||||||
|
"ERROR": "الوصف مطلوب"
|
||||||
|
},
|
||||||
|
"EVENT": {
|
||||||
|
"LABEL": "الحدث",
|
||||||
|
"PLACEHOLDER": "الرجاء اختيار واحد",
|
||||||
|
"ERROR": "الحدث مطلوب"
|
||||||
|
},
|
||||||
|
"CONDITIONS": {
|
||||||
|
"LABEL": "الشروط"
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"LABEL": "الإجراءات"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CONDITION_BUTTON_LABEL": "إضافة شرط",
|
||||||
|
"ACTION_BUTTON_LABEL": "إضافة إجراء",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "تمت إضافة قاعدة الأتمتة بنجاح",
|
||||||
|
"ERROR_MESSAGE": "تعذر إنشاء قاعدة أتمتة ، يرجى المحاولة مرة أخرى لاحقاً"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LIST": {
|
||||||
|
"TABLE_HEADER": [
|
||||||
|
"الاسم",
|
||||||
|
"الوصف",
|
||||||
|
"مفعل",
|
||||||
|
"تم إنشاؤها في"
|
||||||
|
],
|
||||||
|
"404": "لم يتم العثور على قواعد أتمتة"
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"TITLE": "حذف قاعدة الأتمتة",
|
||||||
|
"SUBMIT": "حذف",
|
||||||
|
"CANCEL_BUTTON_TEXT": "إلغاء",
|
||||||
|
"CONFIRM": {
|
||||||
|
"TITLE": "تأكيد الحذف",
|
||||||
|
"MESSAGE": "هل أنت متأكد من الحذف ",
|
||||||
|
"YES": "نعم، احذف ",
|
||||||
|
"NO": "لا، احتفظ "
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "تم حذف قاعدة الأتمتة بنجاح",
|
||||||
|
"ERROR_MESSAGE": "تعذر حذف قاعدة الأتمتة، يرجى المحاولة مرة أخرى لاحقاً"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TITLE": "تعديل قاعدة الأتمتة",
|
||||||
|
"SUBMIT": "تعديل",
|
||||||
|
"CANCEL_BUTTON_TEXT": "إلغاء",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "تم تحديث قاعدة الأتمتة بنجاح",
|
||||||
|
"ERROR_MESSAGE": "تعذر تحديث قاعدة الأتمتة، الرجاء المحاولة مرة أخرى في وقت لاحق"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CLONE": {
|
||||||
|
"TOOLTIP": "نسخ",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "تم نسخ الأتمتة بنجاح",
|
||||||
|
"ERROR_MESSAGE": "تعذر استنساخ قاعدة الأتمتة، الرجاء المحاولة مرة أخرى لاحقاً"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"FORM": {
|
||||||
|
"EDIT": "تعديل",
|
||||||
|
"CREATE": "إنشاء",
|
||||||
|
"DELETE": "حذف",
|
||||||
|
"CANCEL": "إلغاء",
|
||||||
|
"RESET_MESSAGE": "تغيير نوع الحدث سوف يعيد تعيين الشروط والأحداث التي أضفتها أدناه"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
"ADD": {
|
"ADD": {
|
||||||
"TITLE": "إضافة رد جاهز",
|
"TITLE": "إضافة رد جاهز",
|
||||||
"DESC": "الردود الجاهزة هي قوالب رسائل معدة مسبقاً يمكن استخدامها لتسريع كتابة الردود في المحادثات .",
|
"DESC": "الردود الجاهزة هي قوالب رسائل معدة مسبقاً يمكن استخدامها لتسريع كتابة الردود في المحادثات.",
|
||||||
"CANCEL_BUTTON_TEXT": "إلغاء",
|
"CANCEL_BUTTON_TEXT": "إلغاء",
|
||||||
"FORM": {
|
"FORM": {
|
||||||
"SHORT_CODE": {
|
"SHORT_CODE": {
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
"404": "لا توجد محادثات نشطة في هذه المجموعة."
|
"404": "لا توجد محادثات نشطة في هذه المجموعة."
|
||||||
},
|
},
|
||||||
"TAB_HEADING": "المحادثات",
|
"TAB_HEADING": "المحادثات",
|
||||||
"MENTION_HEADING": "Mentions",
|
"MENTION_HEADING": "الإشارات",
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"INPUT": "البحث عن جهات الاتصال، المحادثات، قوالب الردود الجاهزة .."
|
"INPUT": "البحث عن جهات الاتصال، المحادثات، قوالب الردود الجاهزة .."
|
||||||
},
|
},
|
||||||
|
|||||||
@ -111,7 +111,7 @@
|
|||||||
"LABEL": "رقم الهاتف",
|
"LABEL": "رقم الهاتف",
|
||||||
"HELP": "يجب ان يحتوى رقم الهاتف على كود دولتك تسبقها علامة +\nمثال: +20101243567",
|
"HELP": "يجب ان يحتوى رقم الهاتف على كود دولتك تسبقها علامة +\nمثال: +20101243567",
|
||||||
"ERROR": "يجب ان تكون خانة رقم الهاتف إما فارغة او مكتملة مع رمز الدولة",
|
"ERROR": "يجب ان تكون خانة رقم الهاتف إما فارغة او مكتملة مع رمز الدولة",
|
||||||
"DUPLICATE": "This phone number is in use for another contact."
|
"DUPLICATE": "رقم الهاتف هذا مستخدم لجهة اتصال أخرى."
|
||||||
},
|
},
|
||||||
"LOCATION": {
|
"LOCATION": {
|
||||||
"PLACEHOLDER": "أدخل موقع جهة الاتصال",
|
"PLACEHOLDER": "أدخل موقع جهة الاتصال",
|
||||||
@ -169,6 +169,7 @@
|
|||||||
"SUBMIT": "إرسال الرسالة",
|
"SUBMIT": "إرسال الرسالة",
|
||||||
"CANCEL": "إلغاء",
|
"CANCEL": "إلغاء",
|
||||||
"SUCCESS_MESSAGE": "تم إرسال الرسالة!",
|
"SUCCESS_MESSAGE": "تم إرسال الرسالة!",
|
||||||
|
"GO_TO_CONVERSATION": "عرض",
|
||||||
"ERROR_MESSAGE": "تعذر الإرسال! حاول مرة أخرى"
|
"ERROR_MESSAGE": "تعذر الإرسال! حاول مرة أخرى"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -177,7 +178,9 @@
|
|||||||
"FIELDS": "تصنفيات جهات الاتصال",
|
"FIELDS": "تصنفيات جهات الاتصال",
|
||||||
"SEARCH_BUTTON": "بحث",
|
"SEARCH_BUTTON": "بحث",
|
||||||
"SEARCH_INPUT_PLACEHOLDER": "بحث عن جهات الاتصال",
|
"SEARCH_INPUT_PLACEHOLDER": "بحث عن جهات الاتصال",
|
||||||
"FILTER_CONTACTS": "Filter",
|
"FILTER_CONTACTS": "فلترة",
|
||||||
|
"FILTER_CONTACTS_SAVE": "حفظ الفلتر",
|
||||||
|
"FILTER_CONTACTS_DELETE": "حذف الفلتر",
|
||||||
"LIST": {
|
"LIST": {
|
||||||
"LOADING_MESSAGE": "جاري تحميل جهات الاتصال...",
|
"LOADING_MESSAGE": "جاري تحميل جهات الاتصال...",
|
||||||
"404": "لا توجد جهات اتصال تطابق بحثك 🔍",
|
"404": "لا توجد جهات اتصال تطابق بحثك 🔍",
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"CONTACTS_FILTER": {
|
"CONTACTS_FILTER": {
|
||||||
"TITLE": "Filter Contacts",
|
"TITLE": "تصفية جهات الاتصال",
|
||||||
"SUBTITLE": "Add filters below and hit 'Submit' to filter contacts.",
|
"SUBTITLE": "إضافة فلاتر أدناه واضغط على 'إرسال' لتصفية جهات الاتصال.",
|
||||||
"ADD_NEW_FILTER": "إضافة فلتر",
|
"ADD_NEW_FILTER": "إضافة فلتر",
|
||||||
"CLEAR_ALL_FILTERS": "Clear All Filters",
|
"CLEAR_ALL_FILTERS": "مسح جميع الفلاتر",
|
||||||
"FILTER_DELETE_ERROR": "يجب ان يكون لديك فلتر واحد على الاقل",
|
"FILTER_DELETE_ERROR": "يجب ان يكون لديك فلتر واحد على الاقل",
|
||||||
"SUBMIT_BUTTON_LABEL": "إرسال",
|
"SUBMIT_BUTTON_LABEL": "إرسال",
|
||||||
"CANCEL_BUTTON_LABEL": "إلغاء",
|
"CANCEL_BUTTON_LABEL": "إلغاء",
|
||||||
"CLEAR_BUTTON_LABEL": "مسح الفلاتر",
|
"CLEAR_BUTTON_LABEL": "مسح الفلاتر",
|
||||||
"EMPTY_VALUE_ERROR": "القيمة مطلوبة",
|
"EMPTY_VALUE_ERROR": "القيمة مطلوبة",
|
||||||
"TOOLTIP_LABEL": "Filter contacts",
|
"TOOLTIP_LABEL": "تصفية جهات الاتصال",
|
||||||
"QUERY_DROPDOWN_LABELS": {
|
"QUERY_DROPDOWN_LABELS": {
|
||||||
"AND": "و",
|
"AND": "و",
|
||||||
"OR": "أو"
|
"OR": "أو"
|
||||||
@ -20,15 +20,27 @@
|
|||||||
"contains": "يحتوي",
|
"contains": "يحتوي",
|
||||||
"does_not_contain": "لا يحتوي",
|
"does_not_contain": "لا يحتوي",
|
||||||
"is_present": "موجود",
|
"is_present": "موجود",
|
||||||
"is_not_present": "غير موجود"
|
"is_not_present": "غير موجود",
|
||||||
|
"is_greater_than": "هو أكبر من",
|
||||||
|
"is_lesser_than": "هو أقل من"
|
||||||
},
|
},
|
||||||
"ATTRIBUTES": {
|
"ATTRIBUTES": {
|
||||||
"NAME": "الاسم",
|
"NAME": "الاسم",
|
||||||
"EMAIL": "البريد الإلكتروني",
|
"EMAIL": "البريد الإلكتروني",
|
||||||
"PHONE_NUMBER": "رقم الهاتف",
|
"PHONE_NUMBER": "رقم الهاتف",
|
||||||
"IDENTIFIER": "Identifier",
|
"IDENTIFIER": "المعرف",
|
||||||
"CITY": "المدينة",
|
"CITY": "المدينة",
|
||||||
"COUNTRY": "الدولة"
|
"COUNTRY": "الدولة",
|
||||||
|
"CUSTOM_ATTRIBUTE_LIST": "القائمة",
|
||||||
|
"CUSTOM_ATTRIBUTE_TEXT": "النص",
|
||||||
|
"CUSTOM_ATTRIBUTE_NUMBER": "العدد",
|
||||||
|
"CUSTOM_ATTRIBUTE_LINK": "الرابط",
|
||||||
|
"CUSTOM_ATTRIBUTE_CHECKBOX": "مربع"
|
||||||
|
},
|
||||||
|
"GROUPS": {
|
||||||
|
"STANDARD_FILTERS": "الفلاتر القياسية",
|
||||||
|
"ADDITIONAL_FILTERS": "فلاتر إضافية",
|
||||||
|
"CUSTOM_ATTRIBUTES": "سمات مخصصة"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,8 @@
|
|||||||
"LOADING_CONVERSATIONS": "جاري تحميل المحادثات",
|
"LOADING_CONVERSATIONS": "جاري تحميل المحادثات",
|
||||||
"CANNOT_REPLY": "لا يمكنك الرد بسبب",
|
"CANNOT_REPLY": "لا يمكنك الرد بسبب",
|
||||||
"24_HOURS_WINDOW": "قيد نافذة الـ 24 ساعة",
|
"24_HOURS_WINDOW": "قيد نافذة الـ 24 ساعة",
|
||||||
|
"NOT_ASSIGNED_TO_YOU": "لم يتم تعيين هذه المحادثة لك. هل ترغب في تعيين هذه المحادثة لنفسك؟",
|
||||||
|
"ASSIGN_TO_ME": "إسناد لي",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "يمكنك فقط الرد على هذه المحادثة باستخدام رسالة قالب بسبب",
|
"TWILIO_WHATSAPP_CAN_REPLY": "يمكنك فقط الرد على هذه المحادثة باستخدام رسالة قالب بسبب",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "قيد نافذة الـ 24 ساعة",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "قيد نافذة الـ 24 ساعة",
|
||||||
"SELECT_A_TWEET_TO_REPLY": "الرجاء تحديد تغريدة للرد عليها.",
|
"SELECT_A_TWEET_TO_REPLY": "الرجاء تحديد تغريدة للرد عليها.",
|
||||||
@ -55,6 +57,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"FOOTER": {
|
"FOOTER": {
|
||||||
|
"MESSAGE_SIGN_TOOLTIP": "Message signature",
|
||||||
|
"ENABLE_SIGN_TOOLTIP": "Enable signature",
|
||||||
|
"DISABLE_SIGN_TOOLTIP": "Disable signature",
|
||||||
"MSG_INPUT": "زر Shift + Enter لإضافة سطر جديد. ابدأ بزر / للاختيار من الردود السريعة.",
|
"MSG_INPUT": "زر Shift + Enter لإضافة سطر جديد. ابدأ بزر / للاختيار من الردود السريعة.",
|
||||||
"PRIVATE_MSG_INPUT": "زر Shift + Enter لإضافة سطر جديد. سيكون هذا مرئياً للموظفين فقط"
|
"PRIVATE_MSG_INPUT": "زر Shift + Enter لإضافة سطر جديد. سيكون هذا مرئياً للموظفين فقط"
|
||||||
},
|
},
|
||||||
@ -90,6 +95,9 @@
|
|||||||
"FILE_SIZE_LIMIT": "حجم الملف يتجاوز حد الاقصى وهو {MAXIMUM_FILE_UPLOAD_SIZE}",
|
"FILE_SIZE_LIMIT": "حجم الملف يتجاوز حد الاقصى وهو {MAXIMUM_FILE_UPLOAD_SIZE}",
|
||||||
"MESSAGE_ERROR": "غير قادر على إرسال هذه الرسالة، الرجاء المحاولة مرة أخرى لاحقاً",
|
"MESSAGE_ERROR": "غير قادر على إرسال هذه الرسالة، الرجاء المحاولة مرة أخرى لاحقاً",
|
||||||
"SENT_BY": "أرسلت بواسطة:",
|
"SENT_BY": "أرسلت بواسطة:",
|
||||||
|
"BOT": "رد آلي",
|
||||||
|
"SEND_FAILED": "تعذر إرسال الرسالة! حاول مرة أخرى",
|
||||||
|
"TRY_AGAIN": "إعادة المحاولة",
|
||||||
"ASSIGNMENT": {
|
"ASSIGNMENT": {
|
||||||
"SELECT_AGENT": "اختر وكيل",
|
"SELECT_AGENT": "اختر وكيل",
|
||||||
"REMOVE": "حذف",
|
"REMOVE": "حذف",
|
||||||
@ -179,6 +187,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"EMAIL_HEADER": {
|
"EMAIL_HEADER": {
|
||||||
|
"FROM": "من",
|
||||||
"TO": "إلى",
|
"TO": "إلى",
|
||||||
"BCC": "Bcc",
|
"BCC": "Bcc",
|
||||||
"CC": "Cc",
|
"CC": "Cc",
|
||||||
|
|||||||
@ -14,8 +14,8 @@
|
|||||||
"NOTE": ""
|
"NOTE": ""
|
||||||
},
|
},
|
||||||
"ACCOUNT_ID": {
|
"ACCOUNT_ID": {
|
||||||
"TITLE": "Account ID",
|
"TITLE": "معرف الحساب",
|
||||||
"NOTE": "This ID is required if you are building an API based integration"
|
"NOTE": "هذا المعرف مطلوب إذا كنت بصدد بناء تكامل على API"
|
||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"LABEL": "اسم الحساب",
|
"LABEL": "اسم الحساب",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"AUTO_RESOLVE_DURATION": {
|
"AUTO_RESOLVE_DURATION": {
|
||||||
"LABEL": "عدد الأيام بعد التذكرة التي يجب أن يحل تلقائياً إذا لم يكن هناك أي نشاط",
|
"LABEL": "عدد الأيام بعد التذكرة التي يجب أن يحل تلقائياً إذا لم يكن هناك أي نشاط",
|
||||||
"PLACEHOLDER": "30",
|
"PLACEHOLDER": "30",
|
||||||
"ERROR": "الرجاء إدخال مدة الحل التلقائي صحيحة (يوم واحد على الأقل)"
|
"ERROR": "الرجاء إدخال مدة حل تلقائي صالحة (حد أدنى 1 يوم والحد الأقصى 999 يوما)"
|
||||||
},
|
},
|
||||||
"FEATURES": {
|
"FEATURES": {
|
||||||
"INBOUND_EMAIL_ENABLED": "الاستمرار في المحادثة عبر رسائل البريد الإلكتروني مفعّل لحسابك.",
|
"INBOUND_EMAIL_ENABLED": "الاستمرار في المحادثة عبر رسائل البريد الإلكتروني مفعّل لحسابك.",
|
||||||
|
|||||||
@ -49,7 +49,7 @@
|
|||||||
"HELP": "لإضافة حساب تويتر الخاص بك كقناة تواصل، تحتاج إلى مصادقة حسابك على تويتر بك بالنقر على زر \"تسجيل الدخول باستخدام تويتر\" ",
|
"HELP": "لإضافة حساب تويتر الخاص بك كقناة تواصل، تحتاج إلى مصادقة حسابك على تويتر بك بالنقر على زر \"تسجيل الدخول باستخدام تويتر\" ",
|
||||||
"ERROR_MESSAGE": "حدث خطأ أثناء الاتصال بـ Twitter، الرجاء المحاولة مرة أخرى",
|
"ERROR_MESSAGE": "حدث خطأ أثناء الاتصال بـ Twitter، الرجاء المحاولة مرة أخرى",
|
||||||
"TWEETS": {
|
"TWEETS": {
|
||||||
"ENABLE": "Create conversations from mentioned Tweets"
|
"ENABLE": "إنشاء محادثات من التغريدات المشار إليها"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"WEBSITE_CHANNEL": {
|
"WEBSITE_CHANNEL": {
|
||||||
@ -136,8 +136,56 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SMS": {
|
"SMS": {
|
||||||
"TITLE": "قناة SMS عبر Twilio",
|
"TITLE": "قناة SMS",
|
||||||
"DESC": "ابدأ في دعم عملائك عبر الرسائل القصيرة بإستخدام Twilio."
|
"DESC": "ابدأ في دعم عملائك عبر الرسائل القصيرة.",
|
||||||
|
"PROVIDERS": {
|
||||||
|
"LABEL": "API Provider",
|
||||||
|
"TWILIO": "تويليو",
|
||||||
|
"BANDWIDTH": "سعة الإنترنت"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"ERROR_MESSAGE": "لم نتمكن من حفظ قناة الرسائل القصيرة"
|
||||||
|
},
|
||||||
|
"BANDWIDTH": {
|
||||||
|
"ACCOUNT_ID": {
|
||||||
|
"LABEL": "معرف الحساب",
|
||||||
|
"PLACEHOLDER": "الرجاء إدخال معرف حساب النطاق الترددي الخاص بك",
|
||||||
|
"ERROR": "هذا الحقل مطلوب"
|
||||||
|
},
|
||||||
|
"API_KEY": {
|
||||||
|
"LABEL": "مفتاح API",
|
||||||
|
"PLACEHOLDER": "الرجاء إدخال مفتاح API الخاص بك",
|
||||||
|
"ERROR": "هذا الحقل مطلوب"
|
||||||
|
},
|
||||||
|
"API_SECRET": {
|
||||||
|
"LABEL": "سرية API",
|
||||||
|
"PLACEHOLDER": "الرجاء إدخال مفتاح API الخاص بك",
|
||||||
|
"ERROR": "هذا الحقل مطلوب"
|
||||||
|
},
|
||||||
|
"APPLICATION_ID": {
|
||||||
|
"LABEL": "معرف التطبيق",
|
||||||
|
"PLACEHOLDER": "الرجاء إدخال معرف تطبيق النطاق الترددي الخاص بك",
|
||||||
|
"ERROR": "هذا الحقل مطلوب"
|
||||||
|
},
|
||||||
|
"INBOX_NAME": {
|
||||||
|
"LABEL": "اسم صندوق الوارد لقناة التواصل",
|
||||||
|
"PLACEHOLDER": "الرجاء إدخال اسم القناة",
|
||||||
|
"ERROR": "هذا الحقل مطلوب"
|
||||||
|
},
|
||||||
|
"PHONE_NUMBER": {
|
||||||
|
"LABEL": "رقم الهاتف",
|
||||||
|
"PLACEHOLDER": "الرجاء إدخال رقم الهاتف الذي سيتم إرسال الرسائل منه.",
|
||||||
|
"ERROR": "الرجاء إدخال قيمة صحيحة. يجب أن يبدأ رقم الهاتف بعلامة `+`."
|
||||||
|
},
|
||||||
|
"SUBMIT_BUTTON": "إنشاء قناة عرض التردد",
|
||||||
|
"API": {
|
||||||
|
"ERROR_MESSAGE": "تعذر تكوين المصادقة بواسطة بيانات الاعتماد الخاصة بحسابك على Twilio، يرجى المحاولة مرة أخرى"
|
||||||
|
},
|
||||||
|
"API_CALLBACK": {
|
||||||
|
"TITLE": "عنوان Callback URL",
|
||||||
|
"SUBTITLE": "يتوجب تهيئة عنوان callback URL في إعدادات Twilio بإدخال القيمة أدناه."
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"WHATSAPP": {
|
"WHATSAPP": {
|
||||||
"TITLE": "قناة واتساب",
|
"TITLE": "قناة واتساب",
|
||||||
@ -305,6 +353,14 @@
|
|||||||
"ENABLED": "مفعل",
|
"ENABLED": "مفعل",
|
||||||
"DISABLED": "معطّل"
|
"DISABLED": "معطّل"
|
||||||
},
|
},
|
||||||
|
"ALLOW_MESSAGES_AFTER_RESOLVED": {
|
||||||
|
"ENABLED": "مفعل",
|
||||||
|
"DISABLED": "معطّل"
|
||||||
|
},
|
||||||
|
"ENABLE_CONTINUITY_VIA_EMAIL": {
|
||||||
|
"ENABLED": "مفعل",
|
||||||
|
"DISABLED": "معطّل"
|
||||||
|
},
|
||||||
"ENABLE_HMAC": {
|
"ENABLE_HMAC": {
|
||||||
"LABEL": "تمكين"
|
"LABEL": "تمكين"
|
||||||
}
|
}
|
||||||
@ -351,6 +407,8 @@
|
|||||||
"AUTO_ASSIGNMENT": "تفعيل الإسناد التلقائي",
|
"AUTO_ASSIGNMENT": "تفعيل الإسناد التلقائي",
|
||||||
"ENABLE_CSAT": "تمكين تقييم خدمة العملاء",
|
"ENABLE_CSAT": "تمكين تقييم خدمة العملاء",
|
||||||
"ENABLE_CSAT_SUB_TEXT": "تمكين/تعطيل تقييم خدمة العملاء بعد إنتهاء المحادثة",
|
"ENABLE_CSAT_SUB_TEXT": "تمكين/تعطيل تقييم خدمة العملاء بعد إنتهاء المحادثة",
|
||||||
|
"ENABLE_CONTINUITY_VIA_EMAIL": "تمكين استمرارية المحادثة عبر البريد الإلكتروني",
|
||||||
|
"ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "المحادثات ستستمر عبر البريد الإلكتروني إذا كان عنوان البريد الإلكتروني لجهة الاتصال متاحاً.",
|
||||||
"INBOX_UPDATE_TITLE": "إعدادات قناة التواصل",
|
"INBOX_UPDATE_TITLE": "إعدادات قناة التواصل",
|
||||||
"INBOX_UPDATE_SUB_TEXT": "تحديث إعدادات قناة التواصل",
|
"INBOX_UPDATE_SUB_TEXT": "تحديث إعدادات قناة التواصل",
|
||||||
"AUTO_ASSIGNMENT_SUB_TEXT": "تمكين أو تعطيل الإسناد التلقائي للمحادثات الجديدة إلى الموظفين المضافين إلى قناة التواصل هذه.",
|
"AUTO_ASSIGNMENT_SUB_TEXT": "تمكين أو تعطيل الإسناد التلقائي للمحادثات الجديدة إلى الموظفين المضافين إلى قناة التواصل هذه.",
|
||||||
@ -361,7 +419,9 @@
|
|||||||
"INBOX_IDENTIFIER": "معرف صندوق الوارد",
|
"INBOX_IDENTIFIER": "معرف صندوق الوارد",
|
||||||
"INBOX_IDENTIFIER_SUB_TEXT": "استخدم رمز 'inbox_identifier' المعروض هنا للمصادقة على عملاء API الخاص بك.",
|
"INBOX_IDENTIFIER_SUB_TEXT": "استخدم رمز 'inbox_identifier' المعروض هنا للمصادقة على عملاء API الخاص بك.",
|
||||||
"FORWARD_EMAIL_TITLE": "إعادة التوجيه إلى البريد الإلكتروني",
|
"FORWARD_EMAIL_TITLE": "إعادة التوجيه إلى البريد الإلكتروني",
|
||||||
"FORWARD_EMAIL_SUB_TEXT": "بدء إعادة توجيه رسائل البريد الإلكتروني الخاصة بك إلى عنوان البريد الإلكتروني التالي."
|
"FORWARD_EMAIL_SUB_TEXT": "بدء إعادة توجيه رسائل البريد الإلكتروني الخاصة بك إلى عنوان البريد الإلكتروني التالي.",
|
||||||
|
"ALLOW_MESSAGES_AFTER_RESOLVED": "السماح بالرسائل بعد حل المحادثة",
|
||||||
|
"ALLOW_MESSAGES_AFTER_RESOLVED_SUB_TEXT": "السماح للمستخدمين النهائيين بإرسال رسائل حتى بعد تسوية المحادثة."
|
||||||
},
|
},
|
||||||
"FACEBOOK_REAUTHORIZE": {
|
"FACEBOOK_REAUTHORIZE": {
|
||||||
"TITLE": "إعادة التصريح",
|
"TITLE": "إعادة التصريح",
|
||||||
@ -461,7 +521,11 @@
|
|||||||
"DOMAIN": {
|
"DOMAIN": {
|
||||||
"LABEL": "الدومين",
|
"LABEL": "الدومين",
|
||||||
"PLACE_HOLDER": "الدومين"
|
"PLACE_HOLDER": "الدومين"
|
||||||
}
|
},
|
||||||
|
"ENCRYPTION": "التشفير",
|
||||||
|
"SSL_TLS": "SSL/TLS",
|
||||||
|
"START_TLS": "STARTTLS",
|
||||||
|
"OPEN_SSL_VERIFY_MODE": "فتح وضع التحقق من SSL"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
|
import { default as _advancedFilters } from './advancedFilters.json';
|
||||||
import { default as _agentMgmt } from './agentMgmt.json';
|
import { default as _agentMgmt } from './agentMgmt.json';
|
||||||
import { default as _attributesMgmt } from './attributesMgmt.json';
|
import { default as _attributesMgmt } from './attributesMgmt.json';
|
||||||
|
import { default as _automation } from './automation.json';
|
||||||
import { default as _campaign } from './campaign.json';
|
import { default as _campaign } from './campaign.json';
|
||||||
import { default as _cannedMgmt } from './cannedMgmt.json';
|
import { default as _cannedMgmt } from './cannedMgmt.json';
|
||||||
import { default as _chatlist } from './chatlist.json';
|
import { default as _chatlist } from './chatlist.json';
|
||||||
import { default as _contact } from './contact.json';
|
import { default as _contact } from './contact.json';
|
||||||
|
import { default as _contactFilters } from './contactFilters.json';
|
||||||
import { default as _conversation } from './conversation.json';
|
import { default as _conversation } from './conversation.json';
|
||||||
import { default as _csatMgmtMgmt } from './csatMgmt.json';
|
import { default as _csatMgmtMgmt } from './csatMgmt.json';
|
||||||
import { default as _generalSettings } from './generalSettings.json';
|
import { default as _generalSettings } from './generalSettings.json';
|
||||||
@ -20,12 +23,15 @@ import { default as _signup } from './signup.json';
|
|||||||
import { default as _teamsSettings } from './teamsSettings.json';
|
import { default as _teamsSettings } from './teamsSettings.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
..._advancedFilters,
|
||||||
..._agentMgmt,
|
..._agentMgmt,
|
||||||
..._attributesMgmt,
|
..._attributesMgmt,
|
||||||
|
..._automation,
|
||||||
..._campaign,
|
..._campaign,
|
||||||
..._cannedMgmt,
|
..._cannedMgmt,
|
||||||
..._chatlist,
|
..._chatlist,
|
||||||
..._contact,
|
..._contact,
|
||||||
|
..._contactFilters,
|
||||||
..._conversation,
|
..._conversation,
|
||||||
..._csatMgmtMgmt,
|
..._csatMgmtMgmt,
|
||||||
..._generalSettings,
|
..._generalSettings,
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
"TITLE": "إعدادات الملف الشخصي",
|
"TITLE": "إعدادات الملف الشخصي",
|
||||||
"BTN_TEXT": "تعديل الملف الشخصي",
|
"BTN_TEXT": "تعديل الملف الشخصي",
|
||||||
"DELETE_AVATAR": "حذف الصورة الرمزية",
|
"DELETE_AVATAR": "حذف الصورة الرمزية",
|
||||||
"AVATAR_DELETE_SUCCESS": "Avatar has been deleted successfully",
|
"AVATAR_DELETE_SUCCESS": "تم حذف الصورة الرمزية بنجاح",
|
||||||
"AVATAR_DELETE_FAILED": "There is an error while deleting avatar, please try again",
|
"AVATAR_DELETE_FAILED": "حدث خطأ أثناء حذف الصورة الرمزية، الرجاء المحاولة مرة أخرى",
|
||||||
"UPDATE_SUCCESS": "تم تحديث حسابك بنجاح",
|
"UPDATE_SUCCESS": "تم تحديث حسابك بنجاح",
|
||||||
"PASSWORD_UPDATE_SUCCESS": "تم تغيير كلمة المرور بنجاح",
|
"PASSWORD_UPDATE_SUCCESS": "تم تغيير كلمة المرور بنجاح",
|
||||||
"AFTER_EMAIL_CHANGED": "تم تحديث ملفك الشخصي بنجاح، الرجاء تسجيل الدخول مرة أخرى حيث أنه قد تم تغيير بيانات تسجيل الدخول الخاصة بك",
|
"AFTER_EMAIL_CHANGED": "تم تحديث ملفك الشخصي بنجاح، الرجاء تسجيل الدخول مرة أخرى حيث أنه قد تم تغيير بيانات تسجيل الدخول الخاصة بك",
|
||||||
@ -19,6 +19,18 @@
|
|||||||
"TITLE": "الملف الشخصي",
|
"TITLE": "الملف الشخصي",
|
||||||
"NOTE": "عنوان بريدك الإلكتروني هو المعرف الخاص بك الذي ستستخدمه لتسجيل الدخول."
|
"NOTE": "عنوان بريدك الإلكتروني هو المعرف الخاص بك الذي ستستخدمه لتسجيل الدخول."
|
||||||
},
|
},
|
||||||
|
"MESSAGE_SIGNATURE_SECTION": {
|
||||||
|
"TITLE": "Personal message signature",
|
||||||
|
"NOTE": "Create a personal message signature that would be added to all the messages you send from the platform. Use the rich content editor to create a highly personalised signature.",
|
||||||
|
"BTN_TEXT": "Save message signature",
|
||||||
|
"API_ERROR": "Couldn't save signature! Try again",
|
||||||
|
"API_SUCCESS": "Signature saved successfully"
|
||||||
|
},
|
||||||
|
"MESSAGE_SIGNATURE": {
|
||||||
|
"LABEL": "Message Signature",
|
||||||
|
"ERROR": "Message Signature cannot be empty",
|
||||||
|
"PLACEHOLDER": "Insert your personal message signature here."
|
||||||
|
},
|
||||||
"PASSWORD_SECTION": {
|
"PASSWORD_SECTION": {
|
||||||
"TITLE": "كلمة المرور",
|
"TITLE": "كلمة المرور",
|
||||||
"NOTE": "تعديل كلمة المرور الخاصة بك سيعيد ضبط تسجيلات الدخول الخاصة بك في الأجهزة الأخرى.",
|
"NOTE": "تعديل كلمة المرور الخاصة بك سيعيد ضبط تسجيلات الدخول الخاصة بك في الأجهزة الأخرى.",
|
||||||
@ -89,7 +101,7 @@
|
|||||||
"PLACEHOLDER": "الرجاء إدخال كلمة مرور جديدة"
|
"PLACEHOLDER": "الرجاء إدخال كلمة مرور جديدة"
|
||||||
},
|
},
|
||||||
"PASSWORD": {
|
"PASSWORD": {
|
||||||
"LABEL": "كلمة المرور",
|
"LABEL": "كلمة مرور جديدة",
|
||||||
"ERROR": "الرجاء إدخال كلمة مرور بطول 6 أحرف أو أكثر",
|
"ERROR": "الرجاء إدخال كلمة مرور بطول 6 أحرف أو أكثر",
|
||||||
"PLACEHOLDER": "الرجاء إدخال كلمة مرور جديدة"
|
"PLACEHOLDER": "الرجاء إدخال كلمة مرور جديدة"
|
||||||
},
|
},
|
||||||
@ -103,7 +115,7 @@
|
|||||||
"SIDEBAR_ITEMS": {
|
"SIDEBAR_ITEMS": {
|
||||||
"CHANGE_AVAILABILITY_STATUS": "يتغيرون",
|
"CHANGE_AVAILABILITY_STATUS": "يتغيرون",
|
||||||
"CHANGE_ACCOUNTS": "تبديل الحساب",
|
"CHANGE_ACCOUNTS": "تبديل الحساب",
|
||||||
"CONTACT_SUPPORT": "Contact Support",
|
"CONTACT_SUPPORT": "تواصل مع الدعم",
|
||||||
"SELECTOR_SUBTITLE": "اختر حساباً من القائمة التالية",
|
"SELECTOR_SUBTITLE": "اختر حساباً من القائمة التالية",
|
||||||
"PROFILE_SETTINGS": "إعدادات الملف الشخصي",
|
"PROFILE_SETTINGS": "إعدادات الملف الشخصي",
|
||||||
"KEYBOARD_SHORTCUTS": "اختصارات لوحة المفاتيح",
|
"KEYBOARD_SHORTCUTS": "اختصارات لوحة المفاتيح",
|
||||||
@ -136,7 +148,7 @@
|
|||||||
"SIDEBAR": {
|
"SIDEBAR": {
|
||||||
"CONVERSATIONS": "المحادثات",
|
"CONVERSATIONS": "المحادثات",
|
||||||
"ALL_CONVERSATIONS": "كل المحادثات",
|
"ALL_CONVERSATIONS": "كل المحادثات",
|
||||||
"MENTIONED_CONVERSATIONS": "Mentions",
|
"MENTIONED_CONVERSATIONS": "الإشارات",
|
||||||
"REPORTS": "التقارير",
|
"REPORTS": "التقارير",
|
||||||
"SETTINGS": "الإعدادات",
|
"SETTINGS": "الإعدادات",
|
||||||
"CONTACTS": "جهات الاتصال",
|
"CONTACTS": "جهات الاتصال",
|
||||||
@ -153,11 +165,13 @@
|
|||||||
"CUSTOM_ATTRIBUTES": "سمات مخصصة",
|
"CUSTOM_ATTRIBUTES": "سمات مخصصة",
|
||||||
"AUTOMATION": "الأتمتة",
|
"AUTOMATION": "الأتمتة",
|
||||||
"TEAMS": "الفرق",
|
"TEAMS": "الفرق",
|
||||||
|
"CUSTOM_VIEWS_FOLDER": "المجلدات",
|
||||||
|
"CUSTOM_VIEWS_SEGMENTS": "الأجزاء",
|
||||||
"ALL_CONTACTS": "جميع جهات الاتصال",
|
"ALL_CONTACTS": "جميع جهات الاتصال",
|
||||||
"TAGGED_WITH": "مشار إليه بواسطة",
|
"TAGGED_WITH": "مشار إليه بواسطة",
|
||||||
"NEW_LABEL": "New label",
|
"NEW_LABEL": "علامة جديدة",
|
||||||
"NEW_TEAM": "New team",
|
"NEW_TEAM": "فريق جديد",
|
||||||
"NEW_INBOX": "New inbox",
|
"NEW_INBOX": "صندوق الوارد الجديد",
|
||||||
"REPORTS_OVERVIEW": "نظرة عامة",
|
"REPORTS_OVERVIEW": "نظرة عامة",
|
||||||
"CSAT": "CSAT",
|
"CSAT": "CSAT",
|
||||||
"CAMPAIGNS": "الحملات",
|
"CAMPAIGNS": "الحملات",
|
||||||
@ -167,7 +181,7 @@
|
|||||||
"REPORTS_LABEL": "الوسوم",
|
"REPORTS_LABEL": "الوسوم",
|
||||||
"REPORTS_INBOX": "صندوق الوارد",
|
"REPORTS_INBOX": "صندوق الوارد",
|
||||||
"REPORTS_TEAM": "الفريق",
|
"REPORTS_TEAM": "الفريق",
|
||||||
"SET_AVAILABILITY_TITLE": "Set yourself as"
|
"SET_AVAILABILITY_TITLE": "تعيين نفسك كـ"
|
||||||
},
|
},
|
||||||
"CREATE_ACCOUNT": {
|
"CREATE_ACCOUNT": {
|
||||||
"NO_ACCOUNT_WARNING": "أوه! لم نتمكن من العثور على الحساب. الرجاء إنشاء حساب جديد للمتابعة.",
|
"NO_ACCOUNT_WARNING": "أوه! لم نتمكن من العثور على الحساب. الرجاء إنشاء حساب جديد للمتابعة.",
|
||||||
|
|||||||
@ -19,7 +19,9 @@
|
|||||||
"contains": "Съдържа",
|
"contains": "Съдържа",
|
||||||
"does_not_contain": "Не съдържа",
|
"does_not_contain": "Не съдържа",
|
||||||
"is_present": "Присъства",
|
"is_present": "Присъства",
|
||||||
"is_not_present": "Не присъства"
|
"is_not_present": "Не присъства",
|
||||||
|
"is_greater_than": "Is greater than",
|
||||||
|
"is_lesser_than": "Is lesser than"
|
||||||
},
|
},
|
||||||
"ATTRIBUTES": {
|
"ATTRIBUTES": {
|
||||||
"STATUS": "Статус",
|
"STATUS": "Статус",
|
||||||
@ -31,7 +33,54 @@
|
|||||||
"LABELS": "Етикети",
|
"LABELS": "Етикети",
|
||||||
"BROWSER_LANGUAGE": "Език на браузъра",
|
"BROWSER_LANGUAGE": "Език на браузъра",
|
||||||
"COUNTRY_NAME": "Име на държавата",
|
"COUNTRY_NAME": "Име на държавата",
|
||||||
"REFERER_LINK": "Референтна връзка"
|
"REFERER_LINK": "Референтна връзка",
|
||||||
|
"CUSTOM_ATTRIBUTE_LIST": "List",
|
||||||
|
"CUSTOM_ATTRIBUTE_TEXT": "Text",
|
||||||
|
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
|
||||||
|
"CUSTOM_ATTRIBUTE_LINK": "Link",
|
||||||
|
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox"
|
||||||
|
},
|
||||||
|
"GROUPS": {
|
||||||
|
"STANDARD_FILTERS": "Standard Filters",
|
||||||
|
"ADDITIONAL_FILTERS": "Additional Filters",
|
||||||
|
"CUSTOM_ATTRIBUTES": "Персонализирани атрибути"
|
||||||
|
},
|
||||||
|
"CUSTOM_VIEWS": {
|
||||||
|
"ADD": {
|
||||||
|
"TITLE": "Do you want to save this filter?",
|
||||||
|
"LABEL": "Name this filter",
|
||||||
|
"PLACEHOLDER": "Enter a name for this filter",
|
||||||
|
"ERROR_MESSAGE": "Name is required",
|
||||||
|
"SAVE_BUTTON": "Save filter",
|
||||||
|
"CANCEL_BUTTON": "Отмени",
|
||||||
|
"API_FOLDERS": {
|
||||||
|
"SUCCESS_MESSAGE": "Folder created successfully",
|
||||||
|
"ERROR_MESSAGE": "Error while creating folder"
|
||||||
|
},
|
||||||
|
"API_SEGMENTS": {
|
||||||
|
"SUCCESS_MESSAGE": "Segment created successfully",
|
||||||
|
"ERROR_MESSAGE": "Error while creating segment"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"DELETE_BUTTON": "Delete filter",
|
||||||
|
"MODAL": {
|
||||||
|
"CONFIRM": {
|
||||||
|
"TITLE": "Потвърди изтриването",
|
||||||
|
"MESSAGE": "Are you sure to delete the filter ",
|
||||||
|
"YES": "Да, изтрий",
|
||||||
|
"NO": "No, Keep it"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"API_FOLDERS": {
|
||||||
|
"SUCCESS_MESSAGE": "Folder deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "Error while deleting folder"
|
||||||
|
},
|
||||||
|
"API_SEGMENTS": {
|
||||||
|
"SUCCESS_MESSAGE": "Segment deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "Error while deleting segment"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,89 @@
|
|||||||
{
|
{
|
||||||
"AUTOMATION": {
|
"AUTOMATION": {
|
||||||
"HEADER": "Автоматизация",
|
"HEADER": "Автоматизация",
|
||||||
"HEADER_BTN_TXT": "Добавяне правило за автоматизация"
|
"HEADER_BTN_TXT": "Добавяне правило за автоматизация",
|
||||||
|
"LOADING": "Fetching automation rules",
|
||||||
|
"SIDEBAR_TXT": "<p><b>Automation Rules</b> <p>Automation can replace and automate existing processes that require manual effort. You can do many things with automation, including adding labels and assigning conversation to the best agent. So the team focuses on what they do best and spends more little time on manual tasks.</p>",
|
||||||
|
"ADD": {
|
||||||
|
"TITLE": "Добавяне правило за автоматизация",
|
||||||
|
"SUBMIT": "Създаване",
|
||||||
|
"CANCEL_BUTTON_TEXT": "Отмени",
|
||||||
|
"FORM": {
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "Rule Name",
|
||||||
|
"PLACEHOLDER": "Enter rule name",
|
||||||
|
"ERROR": "Name is required"
|
||||||
|
},
|
||||||
|
"DESC": {
|
||||||
|
"LABEL": "Описание",
|
||||||
|
"PLACEHOLDER": "Enter rule description",
|
||||||
|
"ERROR": "Description is required"
|
||||||
|
},
|
||||||
|
"EVENT": {
|
||||||
|
"LABEL": "Event",
|
||||||
|
"PLACEHOLDER": "Please select one",
|
||||||
|
"ERROR": "Event is required"
|
||||||
|
},
|
||||||
|
"CONDITIONS": {
|
||||||
|
"LABEL": "Conditions"
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"LABEL": "Действия"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CONDITION_BUTTON_LABEL": "Add Condition",
|
||||||
|
"ACTION_BUTTON_LABEL": "Add Action",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Automation rule added successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not able to create a automation rule, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LIST": {
|
||||||
|
"TABLE_HEADER": [
|
||||||
|
"Име",
|
||||||
|
"Описание",
|
||||||
|
"Активен",
|
||||||
|
"Created on"
|
||||||
|
],
|
||||||
|
"404": "No automation rules found"
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"TITLE": "Delete Automation Rule",
|
||||||
|
"SUBMIT": "Изтрий",
|
||||||
|
"CANCEL_BUTTON_TEXT": "Отмени",
|
||||||
|
"CONFIRM": {
|
||||||
|
"TITLE": "Потвърди изтриването",
|
||||||
|
"MESSAGE": "Сигурни ли сте за изтриването ",
|
||||||
|
"YES": "Да, изтрий ",
|
||||||
|
"NO": "Не, запази "
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Automation rule deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not able to delete a automation rule, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TITLE": "Edit Automation Rule",
|
||||||
|
"SUBMIT": "Редактирай",
|
||||||
|
"CANCEL_BUTTON_TEXT": "Отмени",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Automation rule updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not update automation rule, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CLONE": {
|
||||||
|
"TOOLTIP": "Clone",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Automation cloned successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not clone automation rule, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"FORM": {
|
||||||
|
"EDIT": "Редактирай",
|
||||||
|
"CREATE": "Създаване",
|
||||||
|
"DELETE": "Изтрий",
|
||||||
|
"CANCEL": "Отмени",
|
||||||
|
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"HEADER_BTN_TXT": "Добавяне на готов отговор",
|
"HEADER_BTN_TXT": "Добавяне на готов отговор",
|
||||||
"LOADING": "Извличане на готови отговори",
|
"LOADING": "Извличане на готови отговори",
|
||||||
"SEARCH_404": "Няма резултати отговарящи на тази заявка",
|
"SEARCH_404": "Няма резултати отговарящи на тази заявка",
|
||||||
"SIDEBAR_TXT": "<p><b>Готови отговори</b> </p><p> Готовите отговори са запазени шаблони за отговори, които могат да се използват за бързо изпращане на отговор в разговора. </p><p> За да създадете готов отговор, просто щракнете върху <b>Добавяне на готов отговор</b>. Можете също да редактирате или изтриете съществуващ готов отговор, като щракнете върху бутона Редактиране или Изтриване </p><p> Готовите отговори се използват с помощта на <b>Кратки кодове</b>. Агентите имат достъп до готовите отговори, докато са в чат, като напишат <b>'/'</b>, последвано от краткия код. </p>",
|
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are saved reply templates which can be used to quickly send out a reply to a conversation. </p><p> For creating a Canned Response, just click on the <b>Add Canned Response</b>. You can also edit or delete an existing Canned Response by clicking on the Edit or Delete button </p><p> Canned responses are used with the help of <b>Short Codes</b>. Agents can access canned responses while on a chat by typing <b>'/'</b> followed by the short code. </p>",
|
||||||
"LIST": {
|
"LIST": {
|
||||||
"404": "Няма налични готови отговори в този акаунт.",
|
"404": "Няма налични готови отговори в този акаунт.",
|
||||||
"TITLE": "Управлявайте готовите отговори",
|
"TITLE": "Управлявайте готовите отговори",
|
||||||
@ -17,12 +17,12 @@
|
|||||||
},
|
},
|
||||||
"ADD": {
|
"ADD": {
|
||||||
"TITLE": "Добавяне на готов отговор",
|
"TITLE": "Добавяне на готов отговор",
|
||||||
"DESC": "Готовите отговори са предварително дефинирани шаблони за отговор, които могат да се изпращат бързо в чата.",
|
"DESC": "Canned Responses are saved reply templates which can be used to quickly send out reply to conversation.",
|
||||||
"CANCEL_BUTTON_TEXT": "Отмени",
|
"CANCEL_BUTTON_TEXT": "Отмени",
|
||||||
"FORM": {
|
"FORM": {
|
||||||
"SHORT_CODE": {
|
"SHORT_CODE": {
|
||||||
"LABEL": "Кратък код",
|
"LABEL": "Кратък код",
|
||||||
"PLACEHOLDER": "Моля, въведете кратък код",
|
"PLACEHOLDER": "Please enter a short code",
|
||||||
"ERROR": "Краткия код е задължителен"
|
"ERROR": "Краткия код е задължителен"
|
||||||
},
|
},
|
||||||
"CONTENT": {
|
"CONTENT": {
|
||||||
|
|||||||
@ -169,6 +169,7 @@
|
|||||||
"SUBMIT": "Изпрати съобщение",
|
"SUBMIT": "Изпрати съобщение",
|
||||||
"CANCEL": "Отмени",
|
"CANCEL": "Отмени",
|
||||||
"SUCCESS_MESSAGE": "Съобщението е изпратено!",
|
"SUCCESS_MESSAGE": "Съобщението е изпратено!",
|
||||||
|
"GO_TO_CONVERSATION": "View",
|
||||||
"ERROR_MESSAGE": "Не може да се изпрати! Опитайте пак"
|
"ERROR_MESSAGE": "Не може да се изпрати! Опитайте пак"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -178,6 +179,8 @@
|
|||||||
"SEARCH_BUTTON": "Търсене",
|
"SEARCH_BUTTON": "Търсене",
|
||||||
"SEARCH_INPUT_PLACEHOLDER": "Търсене на контакти",
|
"SEARCH_INPUT_PLACEHOLDER": "Търсене на контакти",
|
||||||
"FILTER_CONTACTS": "Филтър",
|
"FILTER_CONTACTS": "Филтър",
|
||||||
|
"FILTER_CONTACTS_SAVE": "Save filter",
|
||||||
|
"FILTER_CONTACTS_DELETE": "Delete filter",
|
||||||
"LIST": {
|
"LIST": {
|
||||||
"LOADING_MESSAGE": "Зареждане на контактите...",
|
"LOADING_MESSAGE": "Зареждане на контактите...",
|
||||||
"404": "Няма контакти отговарящи на търсенети ви 🔍",
|
"404": "Няма контакти отговарящи на търсенети ви 🔍",
|
||||||
|
|||||||
@ -20,7 +20,9 @@
|
|||||||
"contains": "Съдържа",
|
"contains": "Съдържа",
|
||||||
"does_not_contain": "Не съдържа",
|
"does_not_contain": "Не съдържа",
|
||||||
"is_present": "Присъства",
|
"is_present": "Присъства",
|
||||||
"is_not_present": "Не присъства"
|
"is_not_present": "Не присъства",
|
||||||
|
"is_greater_than": "Is greater than",
|
||||||
|
"is_lesser_than": "Is lesser than"
|
||||||
},
|
},
|
||||||
"ATTRIBUTES": {
|
"ATTRIBUTES": {
|
||||||
"NAME": "Име",
|
"NAME": "Име",
|
||||||
@ -28,7 +30,17 @@
|
|||||||
"PHONE_NUMBER": "Телефон",
|
"PHONE_NUMBER": "Телефон",
|
||||||
"IDENTIFIER": "Идентификатор",
|
"IDENTIFIER": "Идентификатор",
|
||||||
"CITY": "Град",
|
"CITY": "Град",
|
||||||
"COUNTRY": "Държава"
|
"COUNTRY": "Държава",
|
||||||
|
"CUSTOM_ATTRIBUTE_LIST": "List",
|
||||||
|
"CUSTOM_ATTRIBUTE_TEXT": "Text",
|
||||||
|
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
|
||||||
|
"CUSTOM_ATTRIBUTE_LINK": "Link",
|
||||||
|
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox"
|
||||||
|
},
|
||||||
|
"GROUPS": {
|
||||||
|
"STANDARD_FILTERS": "Standard Filters",
|
||||||
|
"ADDITIONAL_FILTERS": "Additional Filters",
|
||||||
|
"CUSTOM_ATTRIBUTES": "Персонализирани атрибути"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,8 @@
|
|||||||
"LOADING_CONVERSATIONS": "Loading Conversations",
|
"LOADING_CONVERSATIONS": "Loading Conversations",
|
||||||
"CANNOT_REPLY": "You cannot reply due to",
|
"CANNOT_REPLY": "You cannot reply due to",
|
||||||
"24_HOURS_WINDOW": "24 hour message window restriction",
|
"24_HOURS_WINDOW": "24 hour message window restriction",
|
||||||
|
"NOT_ASSIGNED_TO_YOU": "This conversation is not assigned to you. Would you like to assign this conversation to yourself?",
|
||||||
|
"ASSIGN_TO_ME": "Assign to me",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
|
||||||
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
|
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
|
||||||
@ -55,6 +57,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"FOOTER": {
|
"FOOTER": {
|
||||||
|
"MESSAGE_SIGN_TOOLTIP": "Message signature",
|
||||||
|
"ENABLE_SIGN_TOOLTIP": "Enable signature",
|
||||||
|
"DISABLE_SIGN_TOOLTIP": "Disable signature",
|
||||||
"MSG_INPUT": "Shift + enter for new line. Start with '/' to select a Canned Response.",
|
"MSG_INPUT": "Shift + enter for new line. Start with '/' to select a Canned Response.",
|
||||||
"PRIVATE_MSG_INPUT": "Shift + enter for new line. This will be visible only to Agents"
|
"PRIVATE_MSG_INPUT": "Shift + enter for new line. This will be visible only to Agents"
|
||||||
},
|
},
|
||||||
@ -90,6 +95,9 @@
|
|||||||
"FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_FILE_UPLOAD_SIZE} attachment limit",
|
"FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_FILE_UPLOAD_SIZE} attachment limit",
|
||||||
"MESSAGE_ERROR": "Unable to send this message, please try again later",
|
"MESSAGE_ERROR": "Unable to send this message, please try again later",
|
||||||
"SENT_BY": "Sent by:",
|
"SENT_BY": "Sent by:",
|
||||||
|
"BOT": "Бот",
|
||||||
|
"SEND_FAILED": "Couldn't send message! Try again",
|
||||||
|
"TRY_AGAIN": "retry",
|
||||||
"ASSIGNMENT": {
|
"ASSIGNMENT": {
|
||||||
"SELECT_AGENT": "Select Agent",
|
"SELECT_AGENT": "Select Agent",
|
||||||
"REMOVE": "Remove",
|
"REMOVE": "Remove",
|
||||||
@ -127,7 +135,7 @@
|
|||||||
},
|
},
|
||||||
"TEAM_MEMBERS": {
|
"TEAM_MEMBERS": {
|
||||||
"TITLE": "Invite your team members",
|
"TITLE": "Invite your team members",
|
||||||
"DESCRIPTION": "Since you are getting ready to talk to your customer, bring in your teammates to assist you. You can invite your teammates by adding their email address to the agent list.",
|
"DESCRIPTION": "Since you are getting ready to talk to your customer, bring in your teammates to assist you. You can invite your teammates by adding their email addresses to the agent list.",
|
||||||
"NEW_LINK": "Click here to invite a team member"
|
"NEW_LINK": "Click here to invite a team member"
|
||||||
},
|
},
|
||||||
"INBOXES": {
|
"INBOXES": {
|
||||||
@ -179,6 +187,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"EMAIL_HEADER": {
|
"EMAIL_HEADER": {
|
||||||
|
"FROM": "From",
|
||||||
"TO": "До",
|
"TO": "До",
|
||||||
"BCC": "Bcc",
|
"BCC": "Bcc",
|
||||||
"CC": "Cc",
|
"CC": "Cc",
|
||||||
|
|||||||
@ -40,7 +40,7 @@
|
|||||||
"AUTO_RESOLVE_DURATION": {
|
"AUTO_RESOLVE_DURATION": {
|
||||||
"LABEL": "Number of days after a ticket should auto resolve if there is no activity",
|
"LABEL": "Number of days after a ticket should auto resolve if there is no activity",
|
||||||
"PLACEHOLDER": "30",
|
"PLACEHOLDER": "30",
|
||||||
"ERROR": "Please enter a valid auto resolve duration (minimum 1 day)"
|
"ERROR": "Please enter a valid auto resolve duration (minimum 1 day and maximum 999 days)"
|
||||||
},
|
},
|
||||||
"FEATURES": {
|
"FEATURES": {
|
||||||
"INBOUND_EMAIL_ENABLED": "Conversation continuity with emails is enabled for your account.",
|
"INBOUND_EMAIL_ENABLED": "Conversation continuity with emails is enabled for your account.",
|
||||||
|
|||||||
@ -82,7 +82,7 @@
|
|||||||
},
|
},
|
||||||
"CHANNEL_GREETING_TOGGLE": {
|
"CHANNEL_GREETING_TOGGLE": {
|
||||||
"LABEL": "Enable channel greeting",
|
"LABEL": "Enable channel greeting",
|
||||||
"HELP_TEXT": "Send a greeting message to the user when he starts the conversation.",
|
"HELP_TEXT": "Send a greeting message to the users when they starts the conversation.",
|
||||||
"ENABLED": "Enabled",
|
"ENABLED": "Enabled",
|
||||||
"DISABLED": "Disabled"
|
"DISABLED": "Disabled"
|
||||||
},
|
},
|
||||||
@ -136,8 +136,56 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SMS": {
|
"SMS": {
|
||||||
"TITLE": "SMS Channel via Twilio",
|
"TITLE": "SMS Channel",
|
||||||
"DESC": "Start supporting your customers via SMS with Twilio integration."
|
"DESC": "Start supporting your customers via SMS.",
|
||||||
|
"PROVIDERS": {
|
||||||
|
"LABEL": "API Provider",
|
||||||
|
"TWILIO": "Twilio",
|
||||||
|
"BANDWIDTH": "Bandwidth"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"ERROR_MESSAGE": "We were not able to save the SMS channel"
|
||||||
|
},
|
||||||
|
"BANDWIDTH": {
|
||||||
|
"ACCOUNT_ID": {
|
||||||
|
"LABEL": "Account ID",
|
||||||
|
"PLACEHOLDER": "Please enter your Bandwidth Account ID",
|
||||||
|
"ERROR": "This field is required"
|
||||||
|
},
|
||||||
|
"API_KEY": {
|
||||||
|
"LABEL": "API Key",
|
||||||
|
"PLACEHOLDER": "Please enter your Bandwith API Key",
|
||||||
|
"ERROR": "This field is required"
|
||||||
|
},
|
||||||
|
"API_SECRET": {
|
||||||
|
"LABEL": "API Secret",
|
||||||
|
"PLACEHOLDER": "Please enter your Bandwith API Secret",
|
||||||
|
"ERROR": "This field is required"
|
||||||
|
},
|
||||||
|
"APPLICATION_ID": {
|
||||||
|
"LABEL": "Application ID",
|
||||||
|
"PLACEHOLDER": "Please enter your Bandwidth Application ID",
|
||||||
|
"ERROR": "This field is required"
|
||||||
|
},
|
||||||
|
"INBOX_NAME": {
|
||||||
|
"LABEL": "Име на входящата кутия",
|
||||||
|
"PLACEHOLDER": "Please enter a inbox name",
|
||||||
|
"ERROR": "This field is required"
|
||||||
|
},
|
||||||
|
"PHONE_NUMBER": {
|
||||||
|
"LABEL": "Телефон",
|
||||||
|
"PLACEHOLDER": "Please enter the phone number from which message will be sent.",
|
||||||
|
"ERROR": "Please enter a valid value. Phone number should start with `+` sign."
|
||||||
|
},
|
||||||
|
"SUBMIT_BUTTON": "Create Bandwidth Channel",
|
||||||
|
"API": {
|
||||||
|
"ERROR_MESSAGE": "We were not able to authenticate Bandwidth credentials, please try again"
|
||||||
|
},
|
||||||
|
"API_CALLBACK": {
|
||||||
|
"TITLE": "Callback URL",
|
||||||
|
"SUBTITLE": "You have to configure the message callback URL in Bandwidth with the URL mentioned here."
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"WHATSAPP": {
|
"WHATSAPP": {
|
||||||
"TITLE": "WhatsApp Channel",
|
"TITLE": "WhatsApp Channel",
|
||||||
@ -305,6 +353,14 @@
|
|||||||
"ENABLED": "Enabled",
|
"ENABLED": "Enabled",
|
||||||
"DISABLED": "Disabled"
|
"DISABLED": "Disabled"
|
||||||
},
|
},
|
||||||
|
"ALLOW_MESSAGES_AFTER_RESOLVED": {
|
||||||
|
"ENABLED": "Включен",
|
||||||
|
"DISABLED": "Изключен"
|
||||||
|
},
|
||||||
|
"ENABLE_CONTINUITY_VIA_EMAIL": {
|
||||||
|
"ENABLED": "Включен",
|
||||||
|
"DISABLED": "Изключен"
|
||||||
|
},
|
||||||
"ENABLE_HMAC": {
|
"ENABLE_HMAC": {
|
||||||
"LABEL": "Enable"
|
"LABEL": "Enable"
|
||||||
}
|
}
|
||||||
@ -351,6 +407,8 @@
|
|||||||
"AUTO_ASSIGNMENT": "Enable auto assignment",
|
"AUTO_ASSIGNMENT": "Enable auto assignment",
|
||||||
"ENABLE_CSAT": "Enable CSAT",
|
"ENABLE_CSAT": "Enable CSAT",
|
||||||
"ENABLE_CSAT_SUB_TEXT": "Enable/Disable CSAT(Customer satisfaction) survey after resolving a conversation",
|
"ENABLE_CSAT_SUB_TEXT": "Enable/Disable CSAT(Customer satisfaction) survey after resolving a conversation",
|
||||||
|
"ENABLE_CONTINUITY_VIA_EMAIL": "Enable conversation continuity via email",
|
||||||
|
"ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "Conversations will continue over email if the contact email address is available.",
|
||||||
"INBOX_UPDATE_TITLE": "Inbox Settings",
|
"INBOX_UPDATE_TITLE": "Inbox Settings",
|
||||||
"INBOX_UPDATE_SUB_TEXT": "Update your inbox settings",
|
"INBOX_UPDATE_SUB_TEXT": "Update your inbox settings",
|
||||||
"AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.",
|
"AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.",
|
||||||
@ -361,7 +419,9 @@
|
|||||||
"INBOX_IDENTIFIER": "Inbox Identifier",
|
"INBOX_IDENTIFIER": "Inbox Identifier",
|
||||||
"INBOX_IDENTIFIER_SUB_TEXT": "Use the `inbox_identifier` token shown here to authentication your API clients.",
|
"INBOX_IDENTIFIER_SUB_TEXT": "Use the `inbox_identifier` token shown here to authentication your API clients.",
|
||||||
"FORWARD_EMAIL_TITLE": "Forward to Email",
|
"FORWARD_EMAIL_TITLE": "Forward to Email",
|
||||||
"FORWARD_EMAIL_SUB_TEXT": "Start forwarding your emails to the following email address."
|
"FORWARD_EMAIL_SUB_TEXT": "Start forwarding your emails to the following email address.",
|
||||||
|
"ALLOW_MESSAGES_AFTER_RESOLVED": "Allow messages after conversation resolved",
|
||||||
|
"ALLOW_MESSAGES_AFTER_RESOLVED_SUB_TEXT": "Allow the end-users to send messages even after the conversation is resolved."
|
||||||
},
|
},
|
||||||
"FACEBOOK_REAUTHORIZE": {
|
"FACEBOOK_REAUTHORIZE": {
|
||||||
"TITLE": "Reauthorize",
|
"TITLE": "Reauthorize",
|
||||||
@ -461,7 +521,11 @@
|
|||||||
"DOMAIN": {
|
"DOMAIN": {
|
||||||
"LABEL": "Domain",
|
"LABEL": "Domain",
|
||||||
"PLACE_HOLDER": "Domain"
|
"PLACE_HOLDER": "Domain"
|
||||||
}
|
},
|
||||||
|
"ENCRYPTION": "Encryption",
|
||||||
|
"SSL_TLS": "SSL/TLS",
|
||||||
|
"START_TLS": "STARTTLS",
|
||||||
|
"OPEN_SSL_VERIFY_MODE": "Open SSL Verify Mode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
49
app/javascript/dashboard/i18n/locale/bg/index.js
Normal file
49
app/javascript/dashboard/i18n/locale/bg/index.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { default as _advancedFilters } from './advancedFilters.json';
|
||||||
|
import { default as _agentMgmt } from './agentMgmt.json';
|
||||||
|
import { default as _attributesMgmt } from './attributesMgmt.json';
|
||||||
|
import { default as _automation } from './automation.json';
|
||||||
|
import { default as _campaign } from './campaign.json';
|
||||||
|
import { default as _cannedMgmt } from './cannedMgmt.json';
|
||||||
|
import { default as _chatlist } from './chatlist.json';
|
||||||
|
import { default as _contact } from './contact.json';
|
||||||
|
import { default as _contactFilters } from './contactFilters.json';
|
||||||
|
import { default as _conversation } from './conversation.json';
|
||||||
|
import { default as _csatMgmtMgmt } from './csatMgmt.json';
|
||||||
|
import { default as _generalSettings } from './generalSettings.json';
|
||||||
|
import { default as _inboxMgmt } from './inboxMgmt.json';
|
||||||
|
import { default as _integrationApps } from './integrationApps.json';
|
||||||
|
import { default as _integrations } from './integrations.json';
|
||||||
|
import { default as _labelsMgmt } from './labelsMgmt.json';
|
||||||
|
import { default as _login } from './login.json';
|
||||||
|
import { default as _report } from './report.json';
|
||||||
|
import { default as _resetPassword } from './resetPassword.json';
|
||||||
|
import { default as _setNewPassword } from './setNewPassword.json';
|
||||||
|
import { default as _settings } from './settings.json';
|
||||||
|
import { default as _signup } from './signup.json';
|
||||||
|
import { default as _teamsSettings } from './teamsSettings.json';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
..._advancedFilters,
|
||||||
|
..._agentMgmt,
|
||||||
|
..._attributesMgmt,
|
||||||
|
..._automation,
|
||||||
|
..._campaign,
|
||||||
|
..._cannedMgmt,
|
||||||
|
..._chatlist,
|
||||||
|
..._contact,
|
||||||
|
..._contactFilters,
|
||||||
|
..._conversation,
|
||||||
|
..._csatMgmtMgmt,
|
||||||
|
..._generalSettings,
|
||||||
|
..._inboxMgmt,
|
||||||
|
..._integrationApps,
|
||||||
|
..._integrations,
|
||||||
|
..._labelsMgmt,
|
||||||
|
..._login,
|
||||||
|
..._report,
|
||||||
|
..._resetPassword,
|
||||||
|
..._setNewPassword,
|
||||||
|
..._settings,
|
||||||
|
..._signup,
|
||||||
|
..._teamsSettings,
|
||||||
|
};
|
||||||
@ -19,6 +19,18 @@
|
|||||||
"TITLE": "Profile",
|
"TITLE": "Profile",
|
||||||
"NOTE": "Your email address is your identity and is used to log in."
|
"NOTE": "Your email address is your identity and is used to log in."
|
||||||
},
|
},
|
||||||
|
"MESSAGE_SIGNATURE_SECTION": {
|
||||||
|
"TITLE": "Personal message signature",
|
||||||
|
"NOTE": "Create a personal message signature that would be added to all the messages you send from the platform. Use the rich content editor to create a highly personalised signature.",
|
||||||
|
"BTN_TEXT": "Save message signature",
|
||||||
|
"API_ERROR": "Couldn't save signature! Try again",
|
||||||
|
"API_SUCCESS": "Signature saved successfully"
|
||||||
|
},
|
||||||
|
"MESSAGE_SIGNATURE": {
|
||||||
|
"LABEL": "Message Signature",
|
||||||
|
"ERROR": "Message Signature cannot be empty",
|
||||||
|
"PLACEHOLDER": "Insert your personal message signature here."
|
||||||
|
},
|
||||||
"PASSWORD_SECTION": {
|
"PASSWORD_SECTION": {
|
||||||
"TITLE": "Password",
|
"TITLE": "Password",
|
||||||
"NOTE": "Updating your password would reset your logins in multiple devices.",
|
"NOTE": "Updating your password would reset your logins in multiple devices.",
|
||||||
@ -89,14 +101,14 @@
|
|||||||
"PLACEHOLDER": "Please enter the current password"
|
"PLACEHOLDER": "Please enter the current password"
|
||||||
},
|
},
|
||||||
"PASSWORD": {
|
"PASSWORD": {
|
||||||
"LABEL": "Password",
|
"LABEL": "New password",
|
||||||
"ERROR": "Please enter a password of length 6 or more",
|
"ERROR": "Please enter a password of length 6 or more",
|
||||||
"PLACEHOLDER": "Please enter a new password"
|
"PLACEHOLDER": "Please enter a new password"
|
||||||
},
|
},
|
||||||
"PASSWORD_CONFIRMATION": {
|
"PASSWORD_CONFIRMATION": {
|
||||||
"LABEL": "Confirm new password",
|
"LABEL": "Confirm new password",
|
||||||
"ERROR": "Confirm password should match the password",
|
"ERROR": "Confirm password should match the password",
|
||||||
"PLACEHOLDER": "Please re-enter your password"
|
"PLACEHOLDER": "Please re-enter your new password"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -153,6 +165,8 @@
|
|||||||
"CUSTOM_ATTRIBUTES": "Персонализирани атрибути",
|
"CUSTOM_ATTRIBUTES": "Персонализирани атрибути",
|
||||||
"AUTOMATION": "Автоматизация",
|
"AUTOMATION": "Автоматизация",
|
||||||
"TEAMS": "Teams",
|
"TEAMS": "Teams",
|
||||||
|
"CUSTOM_VIEWS_FOLDER": "Folders",
|
||||||
|
"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": "New label",
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"TEAMS_SETTINGS": {
|
"TEAMS_SETTINGS": {
|
||||||
"NEW_TEAM": "Create new team",
|
"NEW_TEAM": "Create new team",
|
||||||
"HEADER": "Teams",
|
"HEADER": "Teams",
|
||||||
"SIDEBAR_TXT": "<p><b>Teams</b></p> <p>Teams let you organize your agents into groups based on their responsibilities. <br /> A user can be part of multiple teams. You can assign conversations to a team when you are working collaboratively. </p>",
|
"SIDEBAR_TXT": "<p><b>Teams</b></p> <p>Teams let you organize your agents into groups based on their responsibilities. <br /> An agent can be part of multiple teams. You can assign conversations to a team when you are working collaboratively. </p>",
|
||||||
"LIST": {
|
"LIST": {
|
||||||
"404": "There are no teams created on this account.",
|
"404": "There are no teams created on this account.",
|
||||||
"EDIT_TEAM": "Edit team"
|
"EDIT_TEAM": "Edit team"
|
||||||
|
|||||||
@ -19,7 +19,9 @@
|
|||||||
"contains": "Contains",
|
"contains": "Contains",
|
||||||
"does_not_contain": "Does not contain",
|
"does_not_contain": "Does not contain",
|
||||||
"is_present": "Is present",
|
"is_present": "Is present",
|
||||||
"is_not_present": "Is not present"
|
"is_not_present": "Is not present",
|
||||||
|
"is_greater_than": "Is greater than",
|
||||||
|
"is_lesser_than": "Is lesser than"
|
||||||
},
|
},
|
||||||
"ATTRIBUTES": {
|
"ATTRIBUTES": {
|
||||||
"STATUS": "Estat",
|
"STATUS": "Estat",
|
||||||
@ -31,7 +33,54 @@
|
|||||||
"LABELS": "Etiquetes",
|
"LABELS": "Etiquetes",
|
||||||
"BROWSER_LANGUAGE": "Browser Language",
|
"BROWSER_LANGUAGE": "Browser Language",
|
||||||
"COUNTRY_NAME": "Country Name",
|
"COUNTRY_NAME": "Country Name",
|
||||||
"REFERER_LINK": "Referer link"
|
"REFERER_LINK": "Referer link",
|
||||||
|
"CUSTOM_ATTRIBUTE_LIST": "List",
|
||||||
|
"CUSTOM_ATTRIBUTE_TEXT": "Text",
|
||||||
|
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
|
||||||
|
"CUSTOM_ATTRIBUTE_LINK": "Link",
|
||||||
|
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox"
|
||||||
|
},
|
||||||
|
"GROUPS": {
|
||||||
|
"STANDARD_FILTERS": "Standard Filters",
|
||||||
|
"ADDITIONAL_FILTERS": "Additional Filters",
|
||||||
|
"CUSTOM_ATTRIBUTES": "Atributs personalitzats"
|
||||||
|
},
|
||||||
|
"CUSTOM_VIEWS": {
|
||||||
|
"ADD": {
|
||||||
|
"TITLE": "Do you want to save this filter?",
|
||||||
|
"LABEL": "Name this filter",
|
||||||
|
"PLACEHOLDER": "Enter a name for this filter",
|
||||||
|
"ERROR_MESSAGE": "Name is required",
|
||||||
|
"SAVE_BUTTON": "Save filter",
|
||||||
|
"CANCEL_BUTTON": "Cancel·la",
|
||||||
|
"API_FOLDERS": {
|
||||||
|
"SUCCESS_MESSAGE": "Folder created successfully",
|
||||||
|
"ERROR_MESSAGE": "Error while creating folder"
|
||||||
|
},
|
||||||
|
"API_SEGMENTS": {
|
||||||
|
"SUCCESS_MESSAGE": "Segment created successfully",
|
||||||
|
"ERROR_MESSAGE": "Error while creating segment"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"DELETE_BUTTON": "Delete filter",
|
||||||
|
"MODAL": {
|
||||||
|
"CONFIRM": {
|
||||||
|
"TITLE": "Confirma l'esborrat",
|
||||||
|
"MESSAGE": "Are you sure to delete the filter ",
|
||||||
|
"YES": "Si, esborra",
|
||||||
|
"NO": "No, manten-la"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"API_FOLDERS": {
|
||||||
|
"SUCCESS_MESSAGE": "Folder deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "Error while deleting folder"
|
||||||
|
},
|
||||||
|
"API_SEGMENTS": {
|
||||||
|
"SUCCESS_MESSAGE": "Segment deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "Error while deleting segment"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,12 +90,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"NO_RESULTS": "No results found."
|
"NO_RESULTS": "No s'ha trobat agents."
|
||||||
},
|
},
|
||||||
"MULTI_SELECTOR": {
|
"MULTI_SELECTOR": {
|
||||||
"PLACEHOLDER": "None",
|
"PLACEHOLDER": "Ningú",
|
||||||
"TITLE": {
|
"TITLE": {
|
||||||
"AGENT": "Select agent",
|
"AGENT": "Seleccionar Agent",
|
||||||
"TEAM": "Select team"
|
"TEAM": "Select team"
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
|
|||||||
@ -1,6 +1,89 @@
|
|||||||
{
|
{
|
||||||
"AUTOMATION": {
|
"AUTOMATION": {
|
||||||
"HEADER": "Automation",
|
"HEADER": "Automation",
|
||||||
"HEADER_BTN_TXT": "Add Automation Rule"
|
"HEADER_BTN_TXT": "Add Automation Rule",
|
||||||
|
"LOADING": "Fetching automation rules",
|
||||||
|
"SIDEBAR_TXT": "<p><b>Automation Rules</b> <p>Automation can replace and automate existing processes that require manual effort. You can do many things with automation, including adding labels and assigning conversation to the best agent. So the team focuses on what they do best and spends more little time on manual tasks.</p>",
|
||||||
|
"ADD": {
|
||||||
|
"TITLE": "Add Automation Rule",
|
||||||
|
"SUBMIT": "Crear",
|
||||||
|
"CANCEL_BUTTON_TEXT": "Cancel·la",
|
||||||
|
"FORM": {
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "Rule Name",
|
||||||
|
"PLACEHOLDER": "Enter rule name",
|
||||||
|
"ERROR": "Name is required"
|
||||||
|
},
|
||||||
|
"DESC": {
|
||||||
|
"LABEL": "Descripció",
|
||||||
|
"PLACEHOLDER": "Enter rule description",
|
||||||
|
"ERROR": "Description is required"
|
||||||
|
},
|
||||||
|
"EVENT": {
|
||||||
|
"LABEL": "Event",
|
||||||
|
"PLACEHOLDER": "Please select one",
|
||||||
|
"ERROR": "Event is required"
|
||||||
|
},
|
||||||
|
"CONDITIONS": {
|
||||||
|
"LABEL": "Conditions"
|
||||||
|
},
|
||||||
|
"ACTIONS": {
|
||||||
|
"LABEL": "Accions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CONDITION_BUTTON_LABEL": "Add Condition",
|
||||||
|
"ACTION_BUTTON_LABEL": "Add Action",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Automation rule added successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not able to create a automation rule, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"LIST": {
|
||||||
|
"TABLE_HEADER": [
|
||||||
|
"Nom",
|
||||||
|
"Descripció",
|
||||||
|
"Active",
|
||||||
|
"Created on"
|
||||||
|
],
|
||||||
|
"404": "No automation rules found"
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"TITLE": "Delete Automation Rule",
|
||||||
|
"SUBMIT": "Esborrar",
|
||||||
|
"CANCEL_BUTTON_TEXT": "Cancel·la",
|
||||||
|
"CONFIRM": {
|
||||||
|
"TITLE": "Confirma l'esborrat",
|
||||||
|
"MESSAGE": "N'estas segur? ",
|
||||||
|
"YES": "Si, esborra ",
|
||||||
|
"NO": "No, segueix "
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Automation rule deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not able to delete a automation rule, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TITLE": "Edit Automation Rule",
|
||||||
|
"SUBMIT": "Edita",
|
||||||
|
"CANCEL_BUTTON_TEXT": "Cancel·la",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Automation rule updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not update automation rule, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"CLONE": {
|
||||||
|
"TOOLTIP": "Clone",
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Automation cloned successfully",
|
||||||
|
"ERROR_MESSAGE": "Could not clone automation rule, Please try again later"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"FORM": {
|
||||||
|
"EDIT": "Edita",
|
||||||
|
"CREATE": "Crear",
|
||||||
|
"DELETE": "Esborrar",
|
||||||
|
"CANCEL": "Cancel·la",
|
||||||
|
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"HEADER_BTN_TXT": "Afegeix una resposta predeterminada",
|
"HEADER_BTN_TXT": "Afegeix una resposta predeterminada",
|
||||||
"LOADING": "S'estan recollint les respostes predeterminades",
|
"LOADING": "S'estan recollint les respostes predeterminades",
|
||||||
"SEARCH_404": "No hi ha cap resposta que coincideixi amb aquesta consulta",
|
"SEARCH_404": "No hi ha cap resposta que coincideixi amb aquesta consulta",
|
||||||
"SIDEBAR_TXT": "<p><b>Respostes predeterminades</b> </p><p> Les respostes predeterminades són plantilles de resposta que es poden utilitzar per enviar ràpidament una resposta a una conversa .</p><p> Per crear una Resposta Predeterminada, clica en <b>Afegir Resposta Predeterminada</b>. També pots editar o suprimir una resposta predeterminada fent clic al botó Edita o Suprimeix </p><p> Les respostes predeterminades s'utilitzen amb l'ajuda dels <b>Codi curt</b>. Els agents poden accedir a les respostes predeterminades en un xat escrivint <b>'/'</b> seguit del codi curt. </p>",
|
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are saved reply templates which can be used to quickly send out a reply to a conversation. </p><p> For creating a Canned Response, just click on the <b>Add Canned Response</b>. You can also edit or delete an existing Canned Response by clicking on the Edit or Delete button </p><p> Canned responses are used with the help of <b>Short Codes</b>. Agents can access canned responses while on a chat by typing <b>'/'</b> followed by the short code. </p>",
|
||||||
"LIST": {
|
"LIST": {
|
||||||
"404": "No hi ha respostes predeterminades disponibles en aquest compte.",
|
"404": "No hi ha respostes predeterminades disponibles en aquest compte.",
|
||||||
"TITLE": "Gestiona les respostes predeterminades",
|
"TITLE": "Gestiona les respostes predeterminades",
|
||||||
@ -17,12 +17,12 @@
|
|||||||
},
|
},
|
||||||
"ADD": {
|
"ADD": {
|
||||||
"TITLE": "Afegeix Resposta Predeterminada",
|
"TITLE": "Afegeix Resposta Predeterminada",
|
||||||
"DESC": "Les respostes predeterminades són plantilles de resposta que es poden utilitzar per enviar ràpidament les respostes a les converses.",
|
"DESC": "Canned Responses are saved reply templates which can be used to quickly send out reply to conversation.",
|
||||||
"CANCEL_BUTTON_TEXT": "Cancel·la",
|
"CANCEL_BUTTON_TEXT": "Cancel·la",
|
||||||
"FORM": {
|
"FORM": {
|
||||||
"SHORT_CODE": {
|
"SHORT_CODE": {
|
||||||
"LABEL": "Codi curt",
|
"LABEL": "Codi curt",
|
||||||
"PLACEHOLDER": "Introduïu un codi curt",
|
"PLACEHOLDER": "Please enter a short code",
|
||||||
"ERROR": "És necessari el codi curt"
|
"ERROR": "És necessari el codi curt"
|
||||||
},
|
},
|
||||||
"CONTENT": {
|
"CONTENT": {
|
||||||
|
|||||||
@ -169,6 +169,7 @@
|
|||||||
"SUBMIT": "Send message",
|
"SUBMIT": "Send message",
|
||||||
"CANCEL": "Cancel·la",
|
"CANCEL": "Cancel·la",
|
||||||
"SUCCESS_MESSAGE": "Message sent!",
|
"SUCCESS_MESSAGE": "Message sent!",
|
||||||
|
"GO_TO_CONVERSATION": "Veure",
|
||||||
"ERROR_MESSAGE": "Couldn't send! try again"
|
"ERROR_MESSAGE": "Couldn't send! try again"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -178,6 +179,8 @@
|
|||||||
"SEARCH_BUTTON": "Cercar",
|
"SEARCH_BUTTON": "Cercar",
|
||||||
"SEARCH_INPUT_PLACEHOLDER": "Cerca de contactes",
|
"SEARCH_INPUT_PLACEHOLDER": "Cerca de contactes",
|
||||||
"FILTER_CONTACTS": "Filter",
|
"FILTER_CONTACTS": "Filter",
|
||||||
|
"FILTER_CONTACTS_SAVE": "Save filter",
|
||||||
|
"FILTER_CONTACTS_DELETE": "Delete filter",
|
||||||
"LIST": {
|
"LIST": {
|
||||||
"LOADING_MESSAGE": "Carregant contactes...",
|
"LOADING_MESSAGE": "Carregant contactes...",
|
||||||
"404": "No hi ha cap contacte que coincideixi amb la vostra cerca 🔍",
|
"404": "No hi ha cap contacte que coincideixi amb la vostra cerca 🔍",
|
||||||
|
|||||||
@ -20,7 +20,9 @@
|
|||||||
"contains": "Contains",
|
"contains": "Contains",
|
||||||
"does_not_contain": "Does not contain",
|
"does_not_contain": "Does not contain",
|
||||||
"is_present": "Is present",
|
"is_present": "Is present",
|
||||||
"is_not_present": "Is not present"
|
"is_not_present": "Is not present",
|
||||||
|
"is_greater_than": "Is greater than",
|
||||||
|
"is_lesser_than": "Is lesser than"
|
||||||
},
|
},
|
||||||
"ATTRIBUTES": {
|
"ATTRIBUTES": {
|
||||||
"NAME": "Nom",
|
"NAME": "Nom",
|
||||||
@ -28,7 +30,17 @@
|
|||||||
"PHONE_NUMBER": "Número de telèfon",
|
"PHONE_NUMBER": "Número de telèfon",
|
||||||
"IDENTIFIER": "Identifier",
|
"IDENTIFIER": "Identifier",
|
||||||
"CITY": "City",
|
"CITY": "City",
|
||||||
"COUNTRY": "Country"
|
"COUNTRY": "Country",
|
||||||
|
"CUSTOM_ATTRIBUTE_LIST": "List",
|
||||||
|
"CUSTOM_ATTRIBUTE_TEXT": "Text",
|
||||||
|
"CUSTOM_ATTRIBUTE_NUMBER": "Number",
|
||||||
|
"CUSTOM_ATTRIBUTE_LINK": "Link",
|
||||||
|
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox"
|
||||||
|
},
|
||||||
|
"GROUPS": {
|
||||||
|
"STANDARD_FILTERS": "Standard Filters",
|
||||||
|
"ADDITIONAL_FILTERS": "Additional Filters",
|
||||||
|
"CUSTOM_ATTRIBUTES": "Atributs personalitzats"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user