Compare commits

..
Author SHA1 Message Date
Sivin VargheseandGitHub d60fe03db3 Merge branch 'develop' into feat/CW-3082 2024-05-23 11:35:42 +05:30
Sivin VargheseandGitHub be97c68721 fix: TypeError cannot read properties of undefined (reading 'status') (#9505) 2024-05-23 11:22:14 +05:30
Sivin VargheseandGitHub 4b93738462 fix: Space key in input closing dropdown (#9525) 2024-05-23 10:40:44 +05:30
PranavandGitHub 87d92f73d4 feat: Improve Report API performance (#9476)
- Re-write the methods for clarity
- Remove the dependency on the ReportHelper class.
- Remove n+1 queries in the average metric time series data.
2024-05-22 17:34:24 -07:00
Muhsin KelothandGitHub 023b3ad507 feat: Add APIs for linear integration (#9346) 2024-05-22 13:37:58 +05:30
Sivin VargheseandGitHub 0d13c11c44 fix: Right click Snooze is not working (#9498) 2024-05-22 13:03:49 +05:30
Sojan JoseandGitHub db13049e6f fix: [Snyk] Security upgrade administrate-field-active_storage from 1.0.2 to 1.0.3 (#9496)
- Security upgrade administrate-field-active_storage from 1.0.2 to 1.0.3
2024-05-21 13:36:11 -07:00
PranavandGitHub 7a1a686133 fix: Fix typo in the font for portals (#9515)
The fonts on the portal were not loaded properly before due to a typo in
the stylesheet. The font that is shown on the public portal is ui-sans
right now. This PR fixes it.
2024-05-21 13:35:35 -07:00
Sivin VargheseandGitHub 00dca9466a feat: Move the SLA filter dropdown to UI folder (#9502)
# Pull Request Template

## Description

I moved the newly created SLA filter dropdown to the UI folder to use in
adding the Linear issue modal.
2024-05-20 16:23:42 +05:30
e9831b8855 feat: add inbox reconnection banner (#9441)
![CleanShot 2024-05-09 at 12 44
07@2x](https://github.com/chatwoot/chatwoot/assets/18097732/2b2861d4-ddcb-481a-b411-f553c19ff573)

![CleanShot 2024-05-09 at 12 44
36@2x](https://github.com/chatwoot/chatwoot/assets/18097732/9cdb8213-acce-4499-a72b-1feba2b611a6)

---------

Co-authored-by: Fayaz Ahmed <fayazara@gmail.com>
2024-05-20 11:57:03 +05:30
Shivam MishraandGitHub f6650b5025 feat: move Azure config to installation_config (#9481)
This PR has the following changes

1. Add `AZURE_APP_ID` and `AZURE_APP_SECRET` to installation config
2. Add Microsoft config to `super_admin/features.yml`
3. Replace usage of `ENV.fetch` with `GlobalConfigService.load` for
fetch App ID and Secret
2024-05-20 11:52:42 +05:30
Shivam MishraandGitHub 00a460db43 fix: fetch FB_APP_ID from @global_config (#9483) 2024-05-17 15:44:50 +05:30
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
85dcb84675 chore(deps): bump rexml from 3.2.5 to 3.2.8 (#9489)
Bumps rexml from 3.2.5 to 3.2.8.

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-16 18:19:22 -07:00
Sivin VargheseandGitHub a83b609025 fix: New account button style in switch account modal (#9456) 2024-05-16 22:33:43 +05:30
Sojan 528b2dc17c Merge branch 'release/3.9.0' into develop 2024-05-15 22:32:27 -07:00
Sojan 41e373ace3 Bump version to 3.9.0 2024-05-15 22:15:52 -07:00
Chatwoot BotandGitHub b0b4668d23 chore: Update translations (#9439)
- update translations from crowdin
2024-05-15 21:52:30 -07:00
Sojan JoseandGitHub a2d0e60a88 fix: Get online status from db when not present in cache [CW-3233] (#9477)
Previously, we returned the static value 'online' when the status was
not present in the Redis cache. This PR changes it to fall back to the
DB value and updates the cache in such cases.

fixes:
https://linear.app/chatwoot/issue/CW-3233/write-a-back-up-for-online-status-in-case-if-redis-keys-are-not
2024-05-15 21:23:19 -07:00
Sojan JoseandGitHub 7b83480979 chore: Add indexes to improve reporting performance (#9478)
- Adding a new index on (account_id,created_at,message_type) based on
our performance improvement exercise. This index significantly improves
the page load speeds of messaging reports.
2024-05-15 21:21:15 -07:00
Sivin VargheseandGitHub 5657473573 fix: Dashboard phone number input country undefined in onSelectCountry (#9473)
# Pull Request Template

## Description

This PR will fix this sentry
[issue](https://chatwoot-p3.sentry.io/issues/5291039795/)

**Issue**
The root cause of this issue is the usage of
`keyboardEventListenerMixins`. The key events are always active when the
edit conversation modal is active, even if the country dropdown is not
visible. So, if we press the enter key, this error will be thrown into
the console.

**Solution**
Remove the use of `keyboardEventListenerMixins` and handle it directly
in the Vue native key events. Also, always check if the dropdown is
active.

**Other changes**
1. Remove the `mouseup` event lister and use the click away directive.
2. Use inline Tailwind css


Fixes
https://linear.app/chatwoot/issue/CW-3282/phonenumberinput-country-undefined-in-onselectcountry

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

**Steps**
1. Open a conversation.
3. And click the edit contact button
4. And click the enter key
5. Now you can see the error in the console


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
2024-05-16 09:16:02 +05:30
iamsivin 4fdbaa200c chore: Color fix 2024-04-30 21:50:35 +05:30
Sivin VargheseandGitHub bbe4508454 Merge branch 'develop' into feat/CW-3082 2024-04-30 21:44:09 +05:30
Sivin VargheseandGitHub 4f20741c95 Merge branch 'develop' into feat/CW-3082 2024-04-29 17:33:17 +05:30
Sivin VargheseandGitHub 7c7aebc0fd Merge branch 'develop' into feat/CW-3082 2024-04-24 09:27:10 +05:30
Sivin VargheseandGitHub 801d9b51da Merge branch 'develop' into feat/CW-3082 2024-04-15 09:54:44 +05:30
Sivin VargheseandGitHub 2086f52edd Merge branch 'develop' into feat/CW-3082 2024-04-12 13:44:30 +05:30
Shivam MishraandGitHub a1541a85ca Merge branch 'develop' into feat/CW-3082 2024-04-10 12:18:58 +05:30
Sivin VargheseandGitHub 0c868fecaa Merge branch 'develop' into feat/CW-3082 2024-04-08 21:42:41 +05:30
Sivin VargheseandGitHub 59fa7926b1 Merge branch 'develop' into feat/CW-3082 2024-04-04 22:53:27 +05:30
Sivin VargheseandGitHub d8108ee324 Merge branch 'develop' into feat/CW-3082 2024-04-01 12:07:35 +05:30
Sivin VargheseandGitHub f37fd877bb Merge branch 'develop' into feat/CW-3082 2024-03-27 13:21:29 +05:30
Sivin VargheseandGitHub 64ff91afbb Merge branch 'develop' into feat/CW-3082 2024-03-25 23:41:38 +05:30
Sivin VargheseandGitHub 271b7da094 Merge branch 'develop' into feat/CW-3082 2024-03-21 15:08:36 +05:30
Sivin VargheseandGitHub 1588db0d83 Merge branch 'develop' into feat/CW-3082 2024-03-18 19:45:05 +05:30
Sivin VargheseandGitHub f4f068a366 Merge branch 'develop' into feat/CW-3082 2024-03-18 17:14:14 +05:30
iamsivin 279c11f38f fix: snapshot 2024-03-18 11:25:47 +05:30
Sivin VargheseandGitHub d9dee0c9da Merge branch 'develop' into feat/CW-3082 2024-03-17 20:06:10 +05:30
iamsivin 82dda9ba22 chore: Review fix 2024-03-15 19:38:24 +05:30
Shivam Mishra 0b1238814c test: resize directive 2024-03-15 16:47:49 +05:30
Shivam Mishra 9563281b16 refactor: use a custom directive for resize 2024-03-15 16:35:01 +05:30
Shivam Mishra 8925cf1e94 chore: run tailwind format 2024-03-15 16:30:33 +05:30
Shivam Mishra af90e3a56c feat: add resize directive 2024-03-15 16:30:23 +05:30
iamsivin b581d19c93 feat: Add sla types 2024-03-15 13:35:23 +05:30
iamsivin ccca226f82 chore: Review fix 2024-03-15 12:43:00 +05:30
Sivin VargheseandGitHub dc392da069 Merge branch 'develop' into feat/CW-3082 2024-03-15 12:41:41 +05:30
Muhsin KelothandGitHub a87ebb1f98 Merge branch 'develop' into feat/CW-3082 2024-03-15 11:59:12 +05:30
Sivin VargheseandGitHub 884433ed1f Merge branch 'develop' into feat/CW-3082 2024-03-15 11:27:41 +05:30
Muhsin Keloth dd2617cf37 chore: add sla title 2024-03-15 11:02:49 +05:30
iamsivin 9848b43b57 chore: clean up 2024-03-15 09:19:45 +05:30
iamsivin eb919456f2 chore: style fix 2024-03-15 09:13:39 +05:30
Sivin VargheseandGitHub ab2e2b92e2 Merge branch 'develop' into feat/CW-3082 2024-03-14 17:30:25 +05:30
iamsivin 905cb652d5 chore: clean up 2024-03-14 17:28:42 +05:30
iamsivin 162efff711 chore: Ui fixes 2024-03-14 17:05:27 +05:30
iamsivin 3a021569d4 fix: responsive issues 2024-03-14 16:40:49 +05:30
iamsivin 8b207a9a94 fix: notification types 2024-03-14 15:53:34 +05:30
iamsivin f419dcc662 chore: Adds specs 2024-03-14 15:18:24 +05:30
iamsivin 048e1d9bc4 chore: Use resize observer 2024-03-14 15:13:40 +05:30
Shivam Mishra 3cc1147fa8 refactor: use simple estimation based logic to calculate number of labels 2024-03-14 13:46:02 +05:30
Sivin VargheseandGitHub 51e293976f Merge branch 'develop' into feat/CW-3082 2024-03-14 09:34:12 +05:30
iamsivin 28563ca129 chore: Minor fix 2024-03-13 23:38:33 +05:30
iamsivin 4d433de344 feat: Update inbox card design 2024-03-12 11:47:08 +05:30
389 changed files with 3741 additions and 1197 deletions
-7
View File
@@ -57,13 +57,6 @@ POSTGRES_PASSWORD=
RAILS_ENV=development
# Changes the Postgres query timeout limit. The default is 14 seconds. Modify only when required.
# POSTGRES_STATEMENT_TIMEOUT=14s
# read replica configuration for production
# POSTGRES_HOST_RR=postgres_rr
# create a new user with readonly permissions
# POSTGRES_USERNAME_RR=postgres_readonly
# POSTGRES_PASSWORD_RR=
RAILS_MAX_THREADS=5
# The email from which all outgoing emails are sent
+1
View File
@@ -45,6 +45,7 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.ref }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
+1 -1
View File
@@ -77,7 +77,7 @@ gem 'jwt'
gem 'pundit'
# super admin
gem 'administrate', '>= 0.20.1'
gem 'administrate-field-active_storage', '>= 1.0.2'
gem 'administrate-field-active_storage', '>= 1.0.3'
gem 'administrate-field-belongs_to_search', '>= 0.9.0'
##--- gems for pubsub service ---##
+59 -57
View File
@@ -33,70 +33,70 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.8.1)
actionpack (= 7.0.8.1)
activesupport (= 7.0.8.1)
actioncable (7.0.8.3)
actionpack (= 7.0.8.3)
activesupport (= 7.0.8.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.8.1)
actionpack (= 7.0.8.1)
activejob (= 7.0.8.1)
activerecord (= 7.0.8.1)
activestorage (= 7.0.8.1)
activesupport (= 7.0.8.1)
actionmailbox (7.0.8.3)
actionpack (= 7.0.8.3)
activejob (= 7.0.8.3)
activerecord (= 7.0.8.3)
activestorage (= 7.0.8.3)
activesupport (= 7.0.8.3)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.8.1)
actionpack (= 7.0.8.1)
actionview (= 7.0.8.1)
activejob (= 7.0.8.1)
activesupport (= 7.0.8.1)
actionmailer (7.0.8.3)
actionpack (= 7.0.8.3)
actionview (= 7.0.8.3)
activejob (= 7.0.8.3)
activesupport (= 7.0.8.3)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.8.1)
actionview (= 7.0.8.1)
activesupport (= 7.0.8.1)
actionpack (7.0.8.3)
actionview (= 7.0.8.3)
activesupport (= 7.0.8.3)
rack (~> 2.0, >= 2.2.4)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.8.1)
actionpack (= 7.0.8.1)
activerecord (= 7.0.8.1)
activestorage (= 7.0.8.1)
activesupport (= 7.0.8.1)
actiontext (7.0.8.3)
actionpack (= 7.0.8.3)
activerecord (= 7.0.8.3)
activestorage (= 7.0.8.3)
activesupport (= 7.0.8.3)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.8.1)
activesupport (= 7.0.8.1)
actionview (7.0.8.3)
activesupport (= 7.0.8.3)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
active_record_query_trace (1.8)
activejob (7.0.8.1)
activesupport (= 7.0.8.1)
activejob (7.0.8.3)
activesupport (= 7.0.8.3)
globalid (>= 0.3.6)
activemodel (7.0.8.1)
activesupport (= 7.0.8.1)
activerecord (7.0.8.1)
activemodel (= 7.0.8.1)
activesupport (= 7.0.8.1)
activemodel (7.0.8.3)
activesupport (= 7.0.8.3)
activerecord (7.0.8.3)
activemodel (= 7.0.8.3)
activesupport (= 7.0.8.3)
activerecord-import (1.4.1)
activerecord (>= 4.2)
activestorage (7.0.8.1)
actionpack (= 7.0.8.1)
activejob (= 7.0.8.1)
activerecord (= 7.0.8.1)
activesupport (= 7.0.8.1)
activestorage (7.0.8.3)
actionpack (= 7.0.8.3)
activejob (= 7.0.8.3)
activerecord (= 7.0.8.3)
activesupport (= 7.0.8.3)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.8.1)
activesupport (7.0.8.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@@ -113,7 +113,7 @@ GEM
kaminari (~> 1.2.2)
sassc-rails (~> 2.1)
selectize-rails (~> 0.6)
administrate-field-active_storage (1.0.2)
administrate-field-active_storage (1.0.3)
administrate (>= 0.2.2)
rails (>= 7.0)
administrate-field-belongs_to_search (0.9.0)
@@ -461,7 +461,7 @@ GEM
mini_magick (4.12.0)
mini_mime (1.1.5)
mini_portile2 (2.8.6)
minitest (5.22.3)
minitest (5.23.0)
mock_redis (0.36.0)
ruby2_keywords
msgpack (1.7.0)
@@ -569,20 +569,20 @@ GEM
rack-test (2.1.0)
rack (>= 1.3)
rack-timeout (0.6.3)
rails (7.0.8.1)
actioncable (= 7.0.8.1)
actionmailbox (= 7.0.8.1)
actionmailer (= 7.0.8.1)
actionpack (= 7.0.8.1)
actiontext (= 7.0.8.1)
actionview (= 7.0.8.1)
activejob (= 7.0.8.1)
activemodel (= 7.0.8.1)
activerecord (= 7.0.8.1)
activestorage (= 7.0.8.1)
activesupport (= 7.0.8.1)
rails (7.0.8.3)
actioncable (= 7.0.8.3)
actionmailbox (= 7.0.8.3)
actionmailer (= 7.0.8.3)
actionpack (= 7.0.8.3)
actiontext (= 7.0.8.3)
actionview (= 7.0.8.3)
activejob (= 7.0.8.3)
activemodel (= 7.0.8.3)
activerecord (= 7.0.8.3)
activestorage (= 7.0.8.3)
activesupport (= 7.0.8.3)
bundler (>= 1.15.0)
railties (= 7.0.8.1)
railties (= 7.0.8.3)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
@@ -590,9 +590,9 @@ GEM
rails-html-sanitizer (1.6.0)
loofah (~> 2.21)
nokogiri (~> 1.14)
railties (7.0.8.1)
actionpack (= 7.0.8.1)
activesupport (= 7.0.8.1)
railties (7.0.8.3)
actionpack (= 7.0.8.3)
activesupport (= 7.0.8.3)
method_source
rake (>= 12.2)
thor (~> 1.0)
@@ -628,7 +628,8 @@ GEM
retriable (3.1.2)
reverse_markdown (2.1.1)
nokogiri
rexml (3.2.5)
rexml (3.2.8)
strscan (>= 3.0.9)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.3)
@@ -758,6 +759,7 @@ GEM
stackprof (0.2.25)
statsd-ruby (1.5.0)
stripe (8.5.0)
strscan (3.1.0)
telephone_number (1.4.20)
test-prof (1.2.1)
thor (1.3.1)
@@ -837,7 +839,7 @@ DEPENDENCIES
activerecord-import
acts-as-taggable-on
administrate (>= 0.20.1)
administrate-field-active_storage (>= 1.0.2)
administrate-field-active_storage (>= 1.0.3)
administrate-field-belongs_to_search (>= 0.9.0)
annotate
attr_extras
@@ -0,0 +1,30 @@
class V2::Reports::Conversations::BaseReportBuilder
pattr_initialize :account, :params
private
AVG_METRICS = %w[avg_first_response_time avg_resolution_time reply_time].freeze
COUNT_METRICS = %w[
conversations_count
incoming_messages_count
outgoing_messages_count
resolutions_count
bot_resolutions_count
bot_handoffs_count
].freeze
def builder_class(metric)
case metric
when *AVG_METRICS
V2::Reports::Timeseries::AverageReportBuilder
when *COUNT_METRICS
V2::Reports::Timeseries::CountReportBuilder
end
end
def log_invalid_metric
Rails.logger.error "ReportBuilder: Invalid metric - #{params[:metric]}"
{}
end
end
@@ -0,0 +1,30 @@
class V2::Reports::Conversations::MetricBuilder < V2::Reports::Conversations::BaseReportBuilder
def summary
{
conversations_count: count('conversations_count'),
incoming_messages_count: count('incoming_messages_count'),
outgoing_messages_count: count('outgoing_messages_count'),
avg_first_response_time: count('avg_first_response_time'),
avg_resolution_time: count('avg_resolution_time'),
resolutions_count: count('resolutions_count'),
reply_time: count('reply_time')
}
end
def bot_summary
{
bot_resolutions_count: count('bot_resolutions_count'),
bot_handoffs_count: count('bot_handoffs_count')
}
end
private
def count(metric)
builder_class(metric).new(account, builder_params(metric)).aggregate_value
end
def builder_params(metric)
params.merge({ metric: metric })
end
end
@@ -0,0 +1,21 @@
class V2::Reports::Conversations::ReportBuilder < V2::Reports::Conversations::BaseReportBuilder
def timeseries
perform_action(:timeseries)
end
def aggregate_value
perform_action(:aggregate_value)
end
private
def perform_action(method_name)
return builder.new(account, params).public_send(method_name) if builder.present?
log_invalid_metric
end
def builder
builder_class(params[:metric])
end
end
@@ -0,0 +1,48 @@
class V2::Reports::Timeseries::AverageReportBuilder < V2::Reports::Timeseries::BaseTimeseriesBuilder
def timeseries
grouped_average_time = reporting_events.average(average_value_key)
grouped_event_count = reporting_events.count
grouped_average_time.each_with_object([]) do |element, arr|
event_date, average_time = element
arr << {
value: average_time,
timestamp: event_date.in_time_zone(timezone).to_i,
count: grouped_event_count[event_date]
}
end
end
def aggregate_value
object_scope.average(average_value_key)
end
private
def event_name
metric_to_event_name = {
avg_first_response_time: :first_response,
avg_resolution_time: :conversation_resolved,
reply_time: :reply_time
}
metric_to_event_name[params[:metric].to_sym]
end
def object_scope
scope.reporting_events.where(name: event_name, created_at: range)
end
def reporting_events
@grouped_values = object_scope.group_by_period(
group_by,
:created_at,
default_value: 0,
range: range,
permit: %w[day week month year hour],
time_zone: timezone
)
end
def average_value_key
@average_value_key ||= params[:business_hours].present? ? :value_in_business_hours : :value
end
end
@@ -0,0 +1,46 @@
class V2::Reports::Timeseries::BaseTimeseriesBuilder
include TimezoneHelper
include DateRangeHelper
DEFAULT_GROUP_BY = 'day'.freeze
pattr_initialize :account, :params
def scope
case params[:type].to_sym
when :account
account
when :inbox
inbox
when :agent
user
when :label
label
when :team
team
end
end
def inbox
@inbox ||= account.inboxes.find(params[:id])
end
def user
@user ||= account.users.find(params[:id])
end
def label
@label ||= account.labels.find(params[:id])
end
def team
@team ||= account.teams.find(params[:id])
end
def group_by
@group_by ||= %w[day week month year hour].include?(params[:group_by]) ? params[:group_by] : DEFAULT_GROUP_BY
end
def timezone
@timezone ||= timezone_name_from_offset(params[:timezone_offset])
end
end
@@ -0,0 +1,71 @@
class V2::Reports::Timeseries::CountReportBuilder < V2::Reports::Timeseries::BaseTimeseriesBuilder
def timeseries
grouped_count.each_with_object([]) do |element, arr|
event_date, event_count = element
# The `event_date` is in Date format (without time), such as "Wed, 15 May 2024".
# We need a timestamp for the start of the day. However, we can't use `event_date.to_time.to_i`
# because it converts the date to 12:00 AM server timezone.
# The desired output should be 12:00 AM in the specified timezone.
arr << { value: event_count, timestamp: event_date.in_time_zone(timezone).to_i }
end
end
def aggregate_value
object_scope.count
end
private
def metric
@metric ||= params[:metric]
end
def object_scope
send("scope_for_#{metric}")
end
def scope_for_conversations_count
scope.conversations.where(account_id: account.id, created_at: range)
end
def scope_for_incoming_messages_count
scope.messages.where(account_id: account.id, created_at: range).incoming.unscope(:order)
end
def scope_for_outgoing_messages_count
scope.messages.where(account_id: account.id, created_at: range).outgoing.unscope(:order)
end
def scope_for_resolutions_count
scope.reporting_events.joins(:conversation).select(:conversation_id).where(
name: :conversation_resolved,
conversations: { status: :resolved }, created_at: range
).distinct
end
def scope_for_bot_resolutions_count
scope.reporting_events.joins(:conversation).select(:conversation_id).where(
name: :conversation_bot_resolved,
conversations: { status: :resolved }, created_at: range
).distinct
end
def scope_for_bot_handoffs_count
scope.reporting_events.joins(:conversation).select(:conversation_id).where(
name: :conversation_bot_handoff,
created_at: range
).distinct
end
def grouped_count
@grouped_values = object_scope.group_by_period(
group_by,
:created_at,
default_value: 0,
range: range,
permit: %w[day week month year hour],
time_zone: timezone
).count
end
end
@@ -0,0 +1,93 @@
class Api::V1::Accounts::Integrations::LinearController < Api::V1::Accounts::BaseController
before_action :fetch_conversation, only: [:link_issue, :linked_issues]
def teams
teams = linear_processor_service.teams
if teams[:error]
render json: { error: teams[:error] }, status: :unprocessable_entity
else
render json: teams[:data], status: :ok
end
end
def team_entities
team_id = permitted_params[:team_id]
team_entities = linear_processor_service.team_entities(team_id)
if team_entities[:error]
render json: { error: team_entities[:error] }, status: :unprocessable_entity
else
render json: team_entities[:data], status: :ok
end
end
def create_issue
issue = linear_processor_service.create_issue(permitted_params)
if issue[:error]
render json: { error: issue[:error] }, status: :unprocessable_entity
else
render json: issue[:data], status: :ok
end
end
def link_issue
issue_id = permitted_params[:issue_id]
title = permitted_params[:title]
issue = linear_processor_service.link_issue(conversation_link, issue_id, title)
if issue[:error]
render json: { error: issue[:error] }, status: :unprocessable_entity
else
render json: issue[:data], status: :ok
end
end
def unlink_issue
link_id = permitted_params[:link_id]
issue = linear_processor_service.unlink_issue(link_id)
if issue[:error]
render json: { error: issue[:error] }, status: :unprocessable_entity
else
render json: issue[:data], status: :ok
end
end
def linked_issues
issues = linear_processor_service.linked_issues(conversation_link)
if issues[:error]
render json: { error: issues[:error] }, status: :unprocessable_entity
else
render json: issues[:data], status: :ok
end
end
def search_issue
render json: { error: 'Specify search string with parameter q' }, status: :unprocessable_entity if params[:q].blank? && return
term = params[:q]
issues = linear_processor_service.search_issue(term)
if issues[:error]
render json: { error: issues[:error] }, status: :unprocessable_entity
else
render json: issues[:data], status: :ok
end
end
private
def conversation_link
"#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{Current.account.id}/conversations/#{@conversation.display_id}"
end
def fetch_conversation
@conversation = Current.account.conversations.find_by!(display_id: permitted_params[:conversation_id])
end
def linear_processor_service
Integrations::Linear::ProcessorService.new(account: Current.account)
end
def permitted_params
params.permit(:team_id, :conversation_id, :issue_id, :link_id, :title, :description, :assignee_id, :priority, label_ids: [])
end
end
@@ -5,19 +5,17 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
before_action :check_authorization
def index
builder = V2::ReportBuilder.new(Current.account, report_params)
data = builder.build
builder = V2::Reports::Conversations::ReportBuilder.new(Current.account, report_params)
data = builder.timeseries
render json: data
end
def summary
render json: summary_metrics
render json: build_summary(:summary)
end
def bot_summary
summary = V2::ReportBuilder.new(Current.account, current_summary_params).bot_summary
summary[:previous] = V2::ReportBuilder.new(Current.account, previous_summary_params).bot_summary
render json: summary
render json: build_summary(:bot_summary)
end
def agents
@@ -126,10 +124,11 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
}
end
def summary_metrics
summary = V2::ReportBuilder.new(Current.account, current_summary_params).summary
summary[:previous] = V2::ReportBuilder.new(Current.account, previous_summary_params).summary
summary
def build_summary(method)
builder = V2::Reports::Conversations::MetricBuilder
current_summary = builder.new(Current.account, current_summary_params).send(method)
previous_summary = builder.new(Current.account, previous_summary_params).send(method)
current_summary.merge(previous: previous_summary)
end
def conversation_metrics
@@ -2,7 +2,10 @@ module MicrosoftConcern
extend ActiveSupport::Concern
def microsoft_client
::OAuth2::Client.new(ENV.fetch('AZURE_APP_ID', nil), ENV.fetch('AZURE_APP_SECRET', nil),
app_id = GlobalConfigService.load('AZURE_APP_ID', nil)
app_secret = GlobalConfigService.load('AZURE_APP_SECRET', nil)
::OAuth2::Client.new(app_id, app_secret,
{
site: 'https://login.microsoftonline.com',
authorize_url: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
+1 -1
View File
@@ -58,7 +58,7 @@ class DashboardController < ActionController::Base
FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''),
FACEBOOK_API_VERSION: GlobalConfigService.load('FACEBOOK_API_VERSION', 'v17.0'),
IS_ENTERPRISE: ChatwootApp.enterprise?,
AZURE_APP_ID: ENV.fetch('AZURE_APP_ID', ''),
AZURE_APP_ID: GlobalConfigService.load('AZURE_APP_ID', ''),
GIT_SHA: GIT_HASH
}
end
+1 -1
View File
@@ -12,6 +12,6 @@ class MicrosoftController < ApplicationController
end
def microsoft_indentity
@identity_json = ENV.fetch('AZURE_APP_ID', nil)
@identity_json = GlobalConfigService.load('AZURE_APP_ID', nil)
end
end
@@ -35,6 +35,8 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
@allowed_configs = case @config
when 'facebook'
%w[FB_APP_ID FB_VERIFY_TOKEN FB_APP_SECRET IG_VERIFY_TOKEN FACEBOOK_API_VERSION ENABLE_MESSENGER_CHANNEL_HUMAN_AGENT]
when 'microsoft'
%w[AZURE_APP_ID AZURE_APP_SECRET]
when 'email'
['MAILER_INBOUND_EMAIL_DOMAIN']
else
+2 -4
View File
@@ -47,10 +47,8 @@ class WidgetsController < ActionController::Base
def build_contact
return if @contact.present?
ActiveRecord::Base.connected_to(role: :writing) do
@contact_inbox, @token = build_contact_inbox_with_token(@web_widget, additional_attributes)
@contact = @contact_inbox.contact
end
@contact_inbox, @token = build_contact_inbox_with_token(@web_widget, additional_attributes)
@contact = @contact_inbox.contact
end
def ensure_account_is_active
+19
View File
@@ -0,0 +1,19 @@
module TimezoneHelper
# ActiveSupport TimeZone is not aware of the current time, so ActiveSupport::Timezone[offset]
# would return the timezone without considering day light savings. To get the correct timezone,
# this method uses zone.now.utc_offset for comparison as referenced in the issues below
#
# https://github.com/rails/rails/pull/22243
# https://github.com/rails/rails/issues/21501
# https://github.com/rails/rails/issues/7297
def timezone_name_from_offset(offset)
return 'UTC' if offset.blank?
offset_in_seconds = offset.to_f * 3600
matching_zone = ActiveSupport::TimeZone.all.find do |zone|
zone.now.utc_offset == offset_in_seconds
end
return matching_zone.name if matching_zone
end
end
@@ -7,79 +7,17 @@
]"
>
<slot />
<div
class="flex items-center justify-between px-4 py-0"
:class="{
'pb-3 border-b border-slate-75 dark:border-slate-700':
hasAppliedFiltersOrActiveFolders,
}"
>
<div class="flex max-w-[85%] justify-center items-center">
<h1
class="text-xl font-medium break-words truncate text-black-900 dark:text-slate-100"
:title="pageTitle"
>
{{ pageTitle }}
</h1>
<span
v-if="!hasAppliedFiltersOrActiveFolders"
class="p-1 my-0.5 mx-1 rounded-md capitalize bg-slate-50 dark:bg-slate-800 text-xxs text-slate-600 dark:text-slate-300"
>
{{ $t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`) }}
</span>
</div>
<div class="flex items-center gap-1">
<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.EDIT.EDIT_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="edit"
@click="onToggleAdvanceFiltersModal"
/>
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="delete"
@click="onClickOpenDeleteFoldersModal"
/>
</div>
<woot-button
v-else
v-tooltip.right="$t('FILTER.TOOLTIP_LABEL')"
variant="smooth"
color-scheme="secondary"
icon="filter"
size="tiny"
@click="onToggleAdvanceFiltersModal"
/>
<conversation-basic-filter
v-if="!hasAppliedFiltersOrActiveFolders"
@changeFilter="onBasicFilterChange"
/>
</div>
</div>
<chat-list-header
:page-title="pageTitle"
:has-applied-filters="hasAppliedFilters"
:has-active-folders="hasActiveFolders"
:active-status="activeStatus"
@add-folders="onClickOpenAddFoldersModal"
@delete-folders="onClickOpenDeleteFoldersModal"
@filters-modal="onToggleAdvanceFiltersModal"
@reset-filters="resetAndFetchData"
@basic-filter-change="onBasicFilterChange"
/>
<add-custom-views
v-if="showAddFoldersModal"
@@ -173,6 +111,15 @@
@updateFolder="onUpdateSavedFilter"
/>
</woot-modal>
<woot-modal
:show.sync="showCustomSnoozeModal"
:on-close="hideCustomSnoozeModal"
>
<custom-snooze-modal
@close="hideCustomSnoozeModal"
@choose-time="chooseSnoozeTime"
/>
</woot-modal>
</div>
</template>
@@ -180,8 +127,8 @@
import { mapGetters } from 'vuex';
import VirtualList from 'vue-virtual-scroll-list';
import ChatListHeader from './ChatListHeader.vue';
import ConversationAdvancedFilter from './widgets/conversation/ConversationAdvancedFilter.vue';
import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter.vue';
import ChatTypeTabs from './widgets/ChatTypeTabs.vue';
import ConversationItem from './ConversationItem.vue';
import timeMixin from '../mixins/time';
@@ -205,10 +152,15 @@ import {
isOnUnattendedView,
} from '../store/modules/conversations/helpers/actionHelpers';
import { CONVERSATION_EVENTS } from '../helper/AnalyticsHelper/events';
import { CMD_SNOOZE_CONVERSATION } from 'dashboard/routes/dashboard/commands/commandBarBusEvents';
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
import { getUnixTime } from 'date-fns';
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
import IntersectionObserver from './IntersectionObserver.vue';
export default {
components: {
ChatListHeader,
AddCustomViews,
ChatTypeTabs,
// eslint-disable-next-line vue/no-unused-components
@@ -216,9 +168,9 @@ export default {
ConversationAdvancedFilter,
DeleteCustomViews,
ConversationBulkActions,
ConversationBasicFilter,
IntersectionObserver,
VirtualList,
CustomSnoozeModal,
},
mixins: [
timeMixin,
@@ -295,6 +247,7 @@ export default {
root: this.$refs.conversationList,
rootMargin: '100px 0px 100px 0px',
},
showCustomSnoozeModal: false,
itemComponent: ConversationItem,
// virtualListExtraProps is to pass the props to the conversationItem component.
@@ -329,12 +282,13 @@ export default {
campaigns: 'campaigns/getAllCampaigns',
labels: 'labels/getLabels',
selectedConversations: 'bulkActions/getSelectedConversationIds',
contextMenuChatId: 'getContextMenuChatId',
}),
hasAppliedFilters() {
return this.appliedFilters.length !== 0;
},
hasActiveFolders() {
return this.activeFolder && this.foldersId !== 0;
return Boolean(this.activeFolder && this.foldersId !== 0);
},
hasAppliedFiltersOrActiveFolders() {
return this.hasAppliedFilters || this.hasActiveFolders;
@@ -558,6 +512,11 @@ export default {
bus.$on('fetch_conversation_stats', () => {
this.$store.dispatch('conversationStats/get', this.conversationFilters);
});
bus.$on(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
},
beforeDestroy() {
bus.$off(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
},
methods: {
updateVirtualListProps(key, value) {
@@ -1028,12 +987,49 @@ export default {
allSelectedConversationsStatus(status) {
if (!this.selectedConversations.length) return false;
return this.selectedConversations.every(item => {
return this.$store.getters.getConversationById(item).status === status;
return this.$store.getters.getConversationById(item)?.status === status;
});
},
onContextMenuToggle(state) {
this.isContextMenuOpen = state;
},
onCmdSnoozeConversation(snoozeType) {
if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
this.showCustomSnoozeModal = true;
} else {
this.toggleStatus(
wootConstants.STATUS_TYPE.SNOOZED,
findSnoozeTime(snoozeType) || null
);
}
},
chooseSnoozeTime(customSnoozeTime) {
this.showCustomSnoozeModal = false;
if (customSnoozeTime) {
this.toggleStatus(
wootConstants.STATUS_TYPE.SNOOZED,
getUnixTime(customSnoozeTime)
);
}
},
toggleStatus(status, snoozedUntil) {
this.$store
.dispatch('toggleStatus', {
conversationId: this.currentChat?.id || this.contextMenuChatId,
status,
snoozedUntil,
})
.then(() => {
this.$store.dispatch('setContextMenuChatId', null);
this.showAlert(this.$t('CONVERSATION.CHANGE_STATUS'));
});
},
hideCustomSnoozeModal() {
// if we select custom snooze and then the custom snooze modal is open
// Then if the custom snooze modal is closed and set the context menu chat id to null
this.$store.dispatch('setContextMenuChatId', null);
this.showCustomSnoozeModal = false;
},
},
};
</script>
@@ -0,0 +1,115 @@
<script setup>
import { computed } from 'vue';
import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter.vue';
const props = defineProps({
pageTitle: {
type: String,
required: true,
},
hasAppliedFilters: {
type: Boolean,
required: true,
},
hasActiveFolders: {
type: Boolean,
required: true,
},
activeStatus: {
type: String,
required: true,
},
});
const emits = defineEmits([
'add-folders',
'delete-folders',
'reset-filters',
'basic-filter-change',
'filters-modal',
]);
const onBasicFilterChange = (value, type) => {
emits('basic-filter-change', value, type);
};
const hasAppliedFiltersOrActiveFolders = computed(() => {
return props.hasAppliedFilters || props.hasActiveFolders;
});
</script>
<template>
<div
class="flex items-center justify-between px-4 py-0"
:class="{
'pb-3 border-b border-slate-75 dark:border-slate-700':
hasAppliedFiltersOrActiveFolders,
}"
>
<div class="flex max-w-[85%] justify-center items-center">
<h1
class="text-xl font-medium break-words truncate text-black-900 dark:text-slate-100"
:title="pageTitle"
>
{{ pageTitle }}
</h1>
<span
v-if="!hasAppliedFiltersOrActiveFolders"
class="p-1 my-0.5 mx-1 rounded-md capitalize bg-slate-50 dark:bg-slate-800 text-xxs text-slate-600 dark:text-slate-300"
>
{{ $t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`) }}
</span>
</div>
<div class="flex items-center gap-1">
<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="emits('add-folders')"
/>
<woot-button
v-tooltip.top-end="$t('FILTER.CLEAR_BUTTON_LABEL')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="dismiss-circle"
@click="emits('reset-filters')"
/>
</div>
<div v-if="hasActiveFolders">
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.EDIT.EDIT_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="secondary"
icon="edit"
@click="emits('filters-modal')"
/>
<woot-button
v-tooltip.top-end="$t('FILTER.CUSTOM_VIEWS.DELETE.DELETE_BUTTON')"
size="tiny"
variant="smooth"
color-scheme="alert"
icon="delete"
@click="emits('delete-folders')"
/>
</div>
<woot-button
v-else
v-tooltip.right="$t('FILTER.TOOLTIP_LABEL')"
variant="smooth"
color-scheme="secondary"
icon="filter"
size="tiny"
@click="emits('filters-modal')"
/>
<conversation-basic-filter
v-if="!hasAppliedFiltersOrActiveFolders"
@changeFilter="onBasicFilterChange"
/>
</div>
</div>
</template>
@@ -73,25 +73,13 @@
</woot-dropdown-item>
</woot-dropdown-menu>
</div>
<woot-modal
:show.sync="showCustomSnoozeModal"
:on-close="hideCustomSnoozeModal"
>
<custom-snooze-modal
@close="hideCustomSnoozeModal"
@choose-time="chooseSnoozeTime"
/>
</woot-modal>
</div>
</template>
<script>
import { getUnixTime } from 'date-fns';
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
import { findSnoozeTime } from 'dashboard/helper/snoozeHelpers';
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
@@ -99,14 +87,12 @@ import wootConstants from 'dashboard/constants/globals';
import {
CMD_REOPEN_CONVERSATION,
CMD_RESOLVE_CONVERSATION,
CMD_SNOOZE_CONVERSATION,
} from '../../routes/dashboard/commands/commandBarBusEvents';
export default {
components: {
WootDropdownItem,
WootDropdownMenu,
CustomSnoozeModal,
},
mixins: [alertMixin, keyboardEventListenerMixins],
props: { conversationId: { type: [String, Number], required: true } },
@@ -115,7 +101,6 @@ export default {
isLoading: false,
showActionsDropdown: false,
STATUS_TYPE: wootConstants.STATUS_TYPE,
showCustomSnoozeModal: false,
};
},
computed: {
@@ -143,12 +128,10 @@ export default {
},
},
mounted() {
bus.$on(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
bus.$on(CMD_REOPEN_CONVERSATION, this.onCmdOpenConversation);
bus.$on(CMD_RESOLVE_CONVERSATION, this.onCmdResolveConversation);
},
destroyed() {
bus.$off(CMD_SNOOZE_CONVERSATION, this.onCmdSnoozeConversation);
bus.$off(CMD_REOPEN_CONVERSATION, this.onCmdOpenConversation);
bus.$off(CMD_RESOLVE_CONVERSATION, this.onCmdResolveConversation);
},
@@ -201,28 +184,6 @@ export default {
// error
}
},
onCmdSnoozeConversation(snoozeType) {
if (snoozeType === wootConstants.SNOOZE_OPTIONS.UNTIL_CUSTOM_TIME) {
this.showCustomSnoozeModal = true;
} else {
this.toggleStatus(
this.STATUS_TYPE.SNOOZED,
findSnoozeTime(snoozeType) || null
);
}
},
chooseSnoozeTime(customSnoozeTime) {
this.showCustomSnoozeModal = false;
if (customSnoozeTime) {
this.toggleStatus(
this.STATUS_TYPE.SNOOZED,
getUnixTime(customSnoozeTime)
);
}
},
hideCustomSnoozeModal() {
this.showCustomSnoozeModal = false;
},
onCmdOpenConversation() {
this.toggleStatus(this.STATUS_TYPE.OPEN);
},
@@ -7,7 +7,7 @@
:header-title="$t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS')"
:header-content="$t('SIDEBAR_ITEMS.SELECTOR_SUBTITLE')"
/>
<div class="px-8 pt-4 pb-8">
<div class="px-8 py-4">
<div
v-for="account in currentUser.accounts"
:id="`account-${account.id}`"
@@ -45,10 +45,10 @@
<div
v-if="globalConfig.createNewAccountFromDashboard"
class="flex justify-end items-center p-8 gap-2"
class="flex justify-end items-center px-8 pb-8 pt-4 gap-2"
>
<button
class="button success large expanded nice"
class="button success large expanded nice w-full"
@click="$emit('show-create-account-modal')"
>
{{ $t('CREATE_ACCOUNT.NEW_ACCOUNT') }}
@@ -1,9 +1,9 @@
<script setup>
import { ref, computed } from 'vue';
import { picoSearch } from '@scmmishra/pico-search';
import FilterListItemButton from './FilterListItemButton.vue';
import FilterDropdownSearch from './FilterDropdownSearch.vue';
import FilterDropdownEmptyState from './FilterDropdownEmptyState.vue';
import ListItemButton from './DropdownListItemButton.vue';
import DropdownSearch from './DropdownSearch.vue';
import DropdownEmptyState from './DropdownEmptyState.vue';
const props = defineProps({
listItems: {
@@ -54,7 +54,7 @@ const isFilterActive = id => {
@click.stop
>
<slot name="search">
<filter-dropdown-search
<dropdown-search
v-if="enableSearch && listItems.length"
:input-value="searchTerm"
:input-placeholder="inputPlaceholder"
@@ -64,11 +64,11 @@ const isFilterActive = id => {
/>
</slot>
<slot name="listItem">
<filter-dropdown-empty-state
<dropdown-empty-state
v-if="isDropdownListEmpty"
:message="$t('REPORT.FILTER_ACTIONS.EMPTY_LIST')"
/>
<filter-list-item-button
<list-item-button
v-for="item in filteredListItems"
:key="item.id"
:is-active="isFilterActive(item.id)"
@@ -18,11 +18,11 @@ defineProps({
<div
class="flex items-center justify-between h-10 min-h-[40px] sticky top-0 bg-white z-10 dark:bg-slate-800 gap-2 px-3 border-b rounded-t-xl border-slate-50 dark:border-slate-700"
>
<div class="flex items-center w-full gap-2">
<div class="flex items-center w-full gap-2" @keyup.space.prevent>
<fluent-icon
icon="search"
size="18"
class="text-slate-400 dark:text-slate-400"
size="16"
class="text-slate-400 dark:text-slate-400 flex-shrink-0"
/>
<input
type="text"
@@ -103,6 +103,7 @@
:status="chat.status"
:inbox-id="inbox.id"
:priority="chat.priority"
:chat-id="chat.id"
:has-unread-messages="hasUnread"
@update-conversation="onUpdateConversation"
@assign-agent="onAssignAgent"
@@ -16,7 +16,7 @@
/>
</template>
<menu-item
v-if="show(snoozeOption.key)"
v-if="showSnooze"
:option="snoozeOption"
variant="icon"
@click="snoozeConversation()"
@@ -86,6 +86,10 @@ export default {
},
mixins: [agentMixin],
props: {
chatId: {
type: Number,
default: null,
},
status: {
type: String,
default: '',
@@ -205,6 +209,10 @@ export default {
...this.filteredAgentOnAvailability,
];
},
showSnooze() {
// Don't show snooze if the conversation is already snoozed/resolved/pending
return this.status === wootConstants.STATUS_TYPE.OPEN;
},
},
mounted() {
this.$store.dispatch('inboxAssignableAgents/fetch', [this.inboxId]);
@@ -213,7 +221,8 @@ export default {
toggleStatus(status, snoozedUntil) {
this.$emit('update-conversation', status, snoozedUntil);
},
snoozeConversation() {
async snoozeConversation() {
await this.$store.dispatch('setContextMenuChatId', this.chatId);
const ninja = document.querySelector('ninja-keys');
ninja.open({ parent: 'snooze_conversation' });
},
@@ -1,9 +1,16 @@
<template>
<div class="phone-input--wrap relative">
<div class="phone-input" :class="{ 'has-error': error }">
<div
class="flex items-center dark:bg-slate-900 justify-start rounded-md border border-solid"
:class="
error
? 'border border-solid border-red-400 dark:border-red-400 mb-1'
: 'mb-4 border-slate-200 dark:border-slate-600'
"
>
<div
class="cursor-pointer py-2 pr-1.5 pl-2 rounded-tl-md rounded-bl-md flex items-center justify-center gap-1.5 bg-slate-25 dark:bg-slate-700 h-10 w-14"
@click="toggleCountryDropdown"
@click.prevent="toggleCountryDropdown"
>
<h5 v-if="activeCountry" class="mb-0">
{{ activeCountry.emoji }}
@@ -13,14 +20,15 @@
</div>
<span
v-if="activeDialCode"
class="flex bg-white dark:bg-slate-900 font-medium text-slate-800 dark:text-slate-100 font-normal text-base leading-normal py-2 pl-2 pr-0"
class="flex bg-white dark:bg-slate-900 text-slate-800 dark:text-slate-100 font-normal text-base leading-normal py-2 pl-2 pr-0"
>
{{ activeDialCode }}
</span>
<input
ref="phoneNumberInput"
:value="phoneNumber"
type="tel"
class="phone-input--field"
class="!mb-0 !rounded-tl-none !rounded-bl-none !border-0 font-normal !w-full dark:!bg-slate-900 text-base !px-1.5 placeholder:font-normal"
:placeholder="placeholder"
:readonly="readonly"
:style="styles"
@@ -28,24 +36,36 @@
@blur="onBlur"
/>
</div>
<div v-if="showDropdown" ref="dropdown" class="country-dropdown">
<div class="dropdown-search--wrap">
<div
v-if="showDropdown"
ref="dropdown"
v-on-clickaway="onOutsideClick"
tabindex="0"
class="z-10 absolute h-60 w-[12.5rem] shadow-md overflow-y-auto top-10 rounded px-0 pt-0 pb-1 bg-white dark:bg-slate-900"
@keydown.prevent.up="moveUp"
@keydown.prevent.down="moveDown"
@keydown.prevent.enter="
onSelectCountry(filteredCountriesBySearch[selectedIndex])
"
>
<div class="top-0 sticky bg-white dark:bg-slate-900 p-1">
<input
ref="searchbar"
v-model="searchCountry"
type="text"
placeholder="Search"
class="dropdown-search"
class="!h-8 !mb-0 !text-sm !border !border-solid !border-slate-200 dark:!border-slate-600"
@input="onSearchCountry"
/>
</div>
<div
v-for="(country, index) in filteredCountriesBySearch"
ref="dropdownItem"
:key="index"
class="country-dropdown--item"
class="flex items-center h-7 py-0 px-1 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-700"
:class="{
active: country.id === activeCountryCode,
focus: index === selectedIndex,
'bg-slate-50 dark:bg-slate-700': country.id === activeCountryCode,
'bg-slate-25 dark:bg-slate-800': index === selectedIndex,
}"
@click="onSelectCountry(country)"
>
@@ -74,10 +94,8 @@
<script>
import countries from 'shared/constants/countries.js';
import parsePhoneNumber from 'libphonenumber-js';
import keyboardEventListenerMixins from 'shared/mixins/keyboardEventListenerMixins';
export default {
mixins: [keyboardEventListenerMixins],
props: {
value: {
type: [String, Number],
@@ -102,15 +120,6 @@ export default {
},
data() {
return {
countries: [
{
name: 'Select Country',
dial_code: '',
emoji: '',
id: '',
},
...countries,
],
selectedIndex: -1,
showDropdown: false,
searchCountry: '',
@@ -120,6 +129,20 @@ export default {
};
},
computed: {
countries() {
return [
{
name: this.dropdownFirstItemName,
dial_code: '',
emoji: '',
id: '',
},
...countries,
];
},
dropdownFirstItemName() {
return this.activeCountryCode ? 'Clear selection' : 'Select Country';
},
filteredCountriesBySearch() {
return this.countries.filter(country => {
const { name, dial_code, id } = country;
@@ -154,12 +177,8 @@ export default {
},
},
mounted() {
window.addEventListener('mouseup', this.onOutsideClick);
this.setActiveCountry();
},
beforeDestroy() {
window.removeEventListener('mouseup', this.onOutsideClick);
},
methods: {
onOutsideClick(e) {
if (
@@ -177,28 +196,14 @@ export default {
onBlur(e) {
this.$emit('blur', e.target.value);
},
dropdownItem() {
if (!this.showDropdown) return [];
return Array.from(
this.$refs.dropdown.querySelectorAll(
'div.country-dropdown div.country-dropdown--item'
)
);
},
focusedItem() {
if (!this.showDropdown) return [];
return Array.from(
this.$refs.dropdown.querySelectorAll('div.country-dropdown div.focus')
);
},
focusedItemIndex() {
if (!this.showDropdown) return -1;
return Array.from(this.dropdownItem()).indexOf(this.focusedItem()[0]);
onSearchCountry() {
// Reset selected index to 0
this.selectedIndex = 0;
},
moveUp() {
if (!this.showDropdown) return;
this.selectedIndex = Math.max(this.selectedIndex - 1, 0);
this.$refs.dropdown.scrollTop = this.focusedItemIndex() * 28 - 56;
this.scrollToSelected();
},
moveDown() {
if (!this.showDropdown) return;
@@ -206,14 +211,27 @@ export default {
this.selectedIndex + 1,
this.filteredCountriesBySearch.length - 1
);
this.$refs.dropdown.scrollTop = this.focusedItemIndex() * 28 - 56;
this.scrollToSelected();
},
scrollToSelected() {
this.$nextTick(() => {
const dropdown = this.$refs.dropdown;
const selectedItem = this.$refs.dropdownItem[this.selectedIndex];
const dropdownSearchbarHeight = 40;
if (selectedItem) {
const selectedItemTop = selectedItem.offsetTop;
dropdown.scrollTop = selectedItemTop - dropdownSearchbarHeight;
}
});
},
onSelectCountry(country) {
if (!country || !this.showDropdown) return;
this.activeCountryCode = country.id;
this.searchCountry = '';
this.activeDialCode = country.dial_code;
this.$emit('setCode', country.dial_code);
this.closeDropdown();
this.$refs.phoneNumberInput.focus();
},
setActiveCountry() {
const { phoneNumber } = this;
@@ -224,33 +242,6 @@ export default {
this.activeDialCode = number.countryCallingCode;
}
},
getKeyboardEvents() {
return {
ArrowUp: {
action: e => {
e.preventDefault();
this.moveUp();
},
allowOnFocusedInput: true,
},
ArrowDown: {
action: e => {
e.preventDefault();
this.moveDown();
},
allowOnFocusedInput: true,
},
Enter: {
action: e => {
e.preventDefault();
this.onSelectCountry(
this.filteredCountriesBySearch[this.selectedIndex]
);
},
allowOnFocusedInput: true,
},
};
},
toggleCountryDropdown() {
this.showDropdown = !this.showDropdown;
this.selectedIndex = -1;
@@ -267,46 +258,3 @@ export default {
},
};
</script>
<style scoped lang="scss">
.phone-input--wrap {
.phone-input {
@apply flex items-center dark:bg-slate-900 justify-start mb-4 rounded-md border border-solid border-slate-200 dark:border-slate-600;
&.has-error {
@apply border border-solid border-red-400 dark:border-red-400;
}
}
.phone-input--field {
@apply mb-0 rounded-tl-none rounded-bl-none border-0 w-full dark:bg-slate-900 text-base px-1.5;
&::placeholder {
@apply font-normal;
}
}
.country-dropdown {
@apply z-10 absolute h-60 w-[12.5rem] shadow-md overflow-y-auto top-10 rounded px-0 pt-0 pb-1 bg-white dark:bg-slate-900;
.dropdown-search--wrap {
@apply top-0 sticky bg-white dark:bg-slate-900 p-1;
.dropdown-search {
@apply h-8 mb-0 text-sm border border-solid border-slate-200 dark:border-slate-600;
}
}
.country-dropdown--item {
@apply flex items-center h-7 py-0 px-1 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-700;
&.active {
@apply bg-slate-50 dark:bg-slate-700;
}
&.focus {
@apply bg-slate-25 dark:bg-slate-800;
}
}
}
}
</style>
@@ -52,8 +52,22 @@ export const validateLoggedInRoutes = (to, user, roleWiseRoutes) => {
return null;
};
export const isAConversationRoute = routeName =>
[
export const isAConversationRoute = (
routeName,
includeBase = false,
includeExtended = true
) => {
const baseRoutes = [
'home',
'conversation_mentions',
'conversation_unattended',
'inbox_dashboard',
'label_conversations',
'team_conversations',
'folder_conversations',
'conversation_participating',
];
const extendedRoutes = [
'inbox_conversation',
'conversation_through_mentions',
'conversation_through_unattended',
@@ -62,7 +76,15 @@ export const isAConversationRoute = routeName =>
'conversations_through_team',
'conversations_through_folders',
'conversation_through_participating',
].includes(routeName);
];
const routes = [
...(includeBase ? baseRoutes : []),
...(includeExtended ? extendedRoutes : []),
];
return routes.includes(routeName);
};
export const getConversationDashboardRoute = routeName => {
switch (routeName) {
@@ -66,3 +66,29 @@ export const snoozedReopenTime = snoozedUntil => {
}
return snoozedUntil ? format(date, 'd MMM, h.mmaaa') : null;
};
export const snoozedReopenTimeToTimestamp = snoozedUntil => {
return snoozedUntil ? getUnixTime(new Date(snoozedUntil)) : null;
};
export const shortenSnoozeTime = snoozedUntil => {
if (!snoozedUntil) {
return null;
}
const unitMap = {
minutes: 'm',
minute: 'm',
hours: 'h',
hour: 'h',
days: 'd',
day: 'd',
months: 'mo',
month: 'mo',
years: 'y',
year: 'y',
};
const shortenTime = snoozedUntil.replace(
/\s(minute|hour|day|month|year)s?\b/gi,
(match, unit) => unitMap[unit.toLowerCase()] || match
);
return shortenTime;
};
@@ -106,6 +106,51 @@ describe('isAConversationRoute', () => {
expect(isAConversationRoute('conversations_through_team')).toBe(true);
expect(isAConversationRoute('dashboard')).toBe(false);
});
it('returns true if base conversation route name is provided and includeBase is true', () => {
expect(isAConversationRoute('home', true)).toBe(true);
expect(isAConversationRoute('conversation_mentions', true)).toBe(true);
expect(isAConversationRoute('conversation_unattended', true)).toBe(true);
expect(isAConversationRoute('inbox_dashboard', true)).toBe(true);
expect(isAConversationRoute('label_conversations', true)).toBe(true);
expect(isAConversationRoute('team_conversations', true)).toBe(true);
expect(isAConversationRoute('folder_conversations', true)).toBe(true);
expect(isAConversationRoute('conversation_participating', true)).toBe(true);
});
it('returns false if base conversation route name is provided and includeBase is false', () => {
expect(isAConversationRoute('home', false)).toBe(false);
expect(isAConversationRoute('conversation_mentions', false)).toBe(false);
expect(isAConversationRoute('conversation_unattended', false)).toBe(false);
expect(isAConversationRoute('inbox_dashboard', false)).toBe(false);
expect(isAConversationRoute('label_conversations', false)).toBe(false);
expect(isAConversationRoute('team_conversations', false)).toBe(false);
expect(isAConversationRoute('folder_conversations', false)).toBe(false);
expect(isAConversationRoute('conversation_participating', false)).toBe(
false
);
});
it('returns true if base conversation route name is provided and includeBase and includeExtended is true', () => {
expect(isAConversationRoute('home', true, true)).toBe(true);
expect(isAConversationRoute('conversation_mentions', true, true)).toBe(
true
);
expect(isAConversationRoute('conversation_unattended', true, true)).toBe(
true
);
expect(isAConversationRoute('inbox_dashboard', true, true)).toBe(true);
expect(isAConversationRoute('label_conversations', true, true)).toBe(true);
expect(isAConversationRoute('team_conversations', true, true)).toBe(true);
expect(isAConversationRoute('folder_conversations', true, true)).toBe(true);
expect(isAConversationRoute('conversation_participating', true, true)).toBe(
true
);
});
it('returns false if base conversation route name is not provided', () => {
expect(isAConversationRoute('')).toBe(false);
});
});
describe('getConversationDashboardRoute', () => {
@@ -5,6 +5,8 @@ import {
findStartOfNextMonth,
findNextDay,
setHoursToNine,
snoozedReopenTimeToTimestamp,
shortenSnoozeTime,
} from '../snoozeHelpers';
describe('#Snooze Helpers', () => {
@@ -107,4 +109,41 @@ describe('#Snooze Helpers', () => {
expect(findNextDay(today)).toEqual(nextDay);
});
});
describe('snoozedReopenTimeToTimestamp', () => {
it('should return timestamp if snoozedUntil is not nil', () => {
expect(snoozedReopenTimeToTimestamp('2023-06-07T09:00:00.000Z')).toEqual(
1686128400
);
});
it('should return nil if snoozedUntil is nil', () => {
expect(snoozedReopenTimeToTimestamp(null)).toEqual(null);
});
});
describe('shortenSnoozeTime', () => {
it('should return shortened time if snoozedUntil is not nil and day is passed', () => {
expect(shortenSnoozeTime('1 day')).toEqual('1d');
});
it('should return shortened time if snoozedUntil is not nil and month is passed', () => {
expect(shortenSnoozeTime('1 month')).toEqual('1mo');
});
it('should return shortened time if snoozedUntil is not nil and year is passed', () => {
expect(shortenSnoozeTime('1 year')).toEqual('1y');
});
it('should return shortened time if snoozedUntil is not nil and hour is passed', () => {
expect(shortenSnoozeTime('1 hour')).toEqual('1h');
});
it('should return shortened time if snoozedUntil is not nil and minutes is passed', () => {
expect(shortenSnoozeTime('1 minutes')).toEqual('1m');
});
it('should return nil if snoozedUntil is nil', () => {
expect(shortenSnoozeTime(null)).toEqual(null);
});
});
});
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Loading agents",
"UPDATE": {
"CHANGE_STATUS": "Change status",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
"SNOOZE_UNTIL": "Snooze",
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,6 +84,7 @@
"CONFIRM": {
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
@@ -107,6 +107,7 @@
"GENERAL": "General",
"REPORTS": "Reports",
"CONVERSATION": "Conversation",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Change Assignee",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Change Team",
@@ -109,7 +109,8 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
}
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
},
"API": {
"UPDATE_SUCCESS": "Your notification preferences are updated successfully",
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "جاري تحميل الوكلاء",
"UPDATE": {
"CHANGE_STATUS": "تغيير الحالة",
"SNOOZE_UNTIL_NEXT_REPLY": "غفوة حتى الرد القادم.",
"SNOOZE_UNTIL": "غفوة",
"UPDATE_SUCCESFUL": "تم تحديث حالة المحادثة بنجاح.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,6 +84,7 @@
"CONFIRM": {
"TITLE": "تصدير جهات الاتصال",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "نعم, قم بالتصدير",
"NO": "نعم, إلغاء"
}
@@ -107,6 +107,7 @@
"GENERAL": "عام",
"REPORTS": "التقارير",
"CONVERSATION": "المحادثات",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "تغيير المحال إليه",
"CHANGE_PRIORITY": "تغيير الأولوية",
"CHANGE_TEAM": "تغيير الفريق",
@@ -109,7 +109,8 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
}
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
},
"API": {
"UPDATE_SUCCESS": "يتم تحديث إعدادات الإشعارات بنجاح",
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Loading agents",
"UPDATE": {
"CHANGE_STATUS": "Change status",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
"SNOOZE_UNTIL": "Snooze",
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,6 +84,7 @@
"CONFIRM": {
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
@@ -107,6 +107,7 @@
"GENERAL": "General",
"REPORTS": "Reports",
"CONVERSATION": "Разговор",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Change Assignee",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Change Team",
@@ -109,7 +109,8 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
}
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
},
"API": {
"UPDATE_SUCCESS": "Your notification preferences are updated successfully",
@@ -1,17 +1,17 @@
{
"FILTER": {
"TITLE": "Filtre de converses",
"SUBTITLE": "Add your filters below and hit 'Apply filters' to cut through the chat clutter.",
"EDIT_CUSTOM_FILTER": "Edit Folder",
"CUSTOM_VIEWS_SUBTITLE": "Add or remove filters and update your folder.",
"ADD_NEW_FILTER": "Add filter",
"FILTER_DELETE_ERROR": "Oops, looks like we can't save nothing! Please add at least one filter to save it.",
"SUBTITLE": "Afegiu els vostres filtres a continuació i premeu 'Aplicar filtres' per eliminar el desordre del xat.",
"EDIT_CUSTOM_FILTER": "Edita la carpeta",
"CUSTOM_VIEWS_SUBTITLE": "Afegeix o elimina filtres i actualitza la teva carpeta.",
"ADD_NEW_FILTER": "Afegeix un filtre",
"FILTER_DELETE_ERROR": "Vaja, sembla que no podem desar res! Afegiu almenys un filtre per desar-lo.",
"SUBMIT_BUTTON_LABEL": "Aplicar filtres",
"UPDATE_BUTTON_LABEL": "Update folder",
"UPDATE_BUTTON_LABEL": "Actualitza la carpeta",
"CANCEL_BUTTON_LABEL": "Cancel·la",
"CLEAR_BUTTON_LABEL": "Clear filters",
"FOLDER_LABEL": "Folder Name",
"FOLDER_QUERY_LABEL": "Folder Query",
"CLEAR_BUTTON_LABEL": "Esborra els filtres",
"FOLDER_LABEL": "Nom de la carpeta",
"FOLDER_QUERY_LABEL": "Consulta de carpeta",
"EMPTY_VALUE_ERROR": "El valor és necessari.",
"TOOLTIP_LABEL": "Filtre de converses",
"QUERY_DROPDOWN_LABELS": {
@@ -27,8 +27,8 @@
"is_not_present": "No és present",
"is_greater_than": "És més gran que",
"is_less_than": "És més petit que",
"days_before": "Is x days before",
"starts_with": "Starts with"
"days_before": "És x dies abans",
"starts_with": "Comença amb"
},
"ATTRIBUTE_LABELS": {
"TRUE": "Cert",
@@ -36,66 +36,66 @@
},
"ATTRIBUTES": {
"STATUS": "Estat",
"ASSIGNEE_NAME": "Assignee name",
"INBOX_NAME": "Inbox name",
"TEAM_NAME": "Team name",
"CONVERSATION_IDENTIFIER": "Conversation identifier",
"CAMPAIGN_NAME": "Campaign name",
"ASSIGNEE_NAME": "Nom assignat",
"INBOX_NAME": "Nom de la safata d'entrada",
"TEAM_NAME": "Nom de l'equip",
"CONVERSATION_IDENTIFIER": "Identificador de conversa",
"CAMPAIGN_NAME": "Nom de la campanya",
"LABELS": "Etiquetes",
"BROWSER_LANGUAGE": "Browser language",
"PRIORITY": "Priority",
"COUNTRY_NAME": "Country name",
"REFERER_LINK": "Referer link",
"CUSTOM_ATTRIBUTE_LIST": "List",
"BROWSER_LANGUAGE": "Idioma del navegador",
"PRIORITY": "Prioritat",
"COUNTRY_NAME": "Nom del país",
"REFERER_LINK": "Enllaç de referència",
"CUSTOM_ATTRIBUTE_LIST": "Llista",
"CUSTOM_ATTRIBUTE_TEXT": "Llista",
"CUSTOM_ATTRIBUTE_NUMBER": "Número",
"CUSTOM_ATTRIBUTE_LINK": "Enllaç",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
"CREATED_AT": "Created at",
"LAST_ACTIVITY": "Last activity"
"CUSTOM_ATTRIBUTE_CHECKBOX": "Casella de selecció",
"CREATED_AT": "Creat a",
"LAST_ACTIVITY": "Última activitat"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard filters",
"ADDITIONAL_FILTERS": "Additional filters",
"CUSTOM_ATTRIBUTES": "Custom attributes"
"STANDARD_FILTERS": "Filtres estàndard",
"ADDITIONAL_FILTERS": "Filtres addicionals",
"CUSTOM_ATTRIBUTES": "Atributs personalitzats"
},
"CUSTOM_VIEWS": {
"ADD": {
"TITLE": "Do you want to save this filter?",
"LABEL": "Name this filter",
"PLACEHOLDER": "Name your filter to refer it later.",
"ERROR_MESSAGE": "Name is required.",
"SAVE_BUTTON": "Save filter",
"TITLE": "Vols desar aquest filtre?",
"LABEL": "Anomena aquest filtre",
"PLACEHOLDER": "Anomena el teu filtre per referir-lo més endavant.",
"ERROR_MESSAGE": "El nom és obligatori.",
"SAVE_BUTTON": "Desa el filtre",
"CANCEL_BUTTON": "Cancel·la",
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder created successfully.",
"ERROR_MESSAGE": "Error while creating folder."
"SUCCESS_MESSAGE": "Carpeta creada correctament.",
"ERROR_MESSAGE": "S'ha produït un error en crear la carpeta."
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment created successfully.",
"ERROR_MESSAGE": "Error while creating segment."
"SUCCESS_MESSAGE": "Segment creat correctament.",
"ERROR_MESSAGE": "S'ha produït un error en crear el segment."
}
},
"EDIT": {
"EDIT_BUTTON": "Edit folder"
"EDIT_BUTTON": "Editar la carpeta"
},
"DELETE": {
"DELETE_BUTTON": "Delete filter",
"DELETE_BUTTON": "Suprimir el filtre",
"MODAL": {
"CONFIRM": {
"TITLE": "Confirm deletion",
"MESSAGE": "Are you sure to delete the filter ",
"YES": "Yes, delete",
"NO": "No, keep it"
"TITLE": "Confirmar la supressió",
"MESSAGE": "Esteu segur que suprimiu el filtre ",
"YES": "Sí, esborra",
"NO": "No, guarda-ho"
}
},
"API_FOLDERS": {
"SUCCESS_MESSAGE": "Folder deleted successfully.",
"ERROR_MESSAGE": "Error while deleting folder."
"SUCCESS_MESSAGE": "La carpeta s'ha suprimit correctament.",
"ERROR_MESSAGE": "S'ha produït un error en suprimir la carpeta."
},
"API_SEGMENTS": {
"SUCCESS_MESSAGE": "Segment deleted successfully.",
"ERROR_MESSAGE": "Error while deleting segment."
"SUCCESS_MESSAGE": "El segment s'ha suprimit correctament.",
"ERROR_MESSAGE": "S'ha produït un error en suprimir el segment."
}
}
}
@@ -1,7 +1,7 @@
{
"AGENT_BOTS": {
"HEADER": "Bots",
"LOADING_EDITOR": "Loading editor...",
"LOADING_EDITOR": "S'està carregant l'editor...",
"HEADER_BTN_TXT": "Add bot configuration",
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent Bots are like the most fabulous members of your team. They can handle the small stuff, so you can focus on the stuff that matters. Give them a try.</p> <p> You can manage your bots from this page or create new ones using the 'Add bot configuraton' button.</p> <p> Open the <a href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677497472-how-to-use-agent-bots\" target=\"blank\">Agent bots handbook</a> in another tab for a helping hand.</p>",
"CSML_BOT_EDITOR": {
@@ -3,7 +3,7 @@
"HEADER": "Agents",
"HEADER_BTN_TXT": "Afegir Agent",
"LOADING": "S'està recollint la llista d'agents",
"SIDEBAR_TXT": "<p><b>Agents</b></p> <p> Un <b>Agent</b> és membre del vostre equip datenció al client. </p><p> Els agents podran veure i respondre missatges dels teus usuaris. La llista mostra tots els agents que hi ha actualment al teu compte.</p><p> Fer clic a <b>Afegeix Agent</b> per afegir un agent nou. Lagent que afegeixes rebrà un correu electrònic amb un enllaç de confirmació per activar el seu compte, després del qual podrà accedir a Chatwoot i respondre als missatges. </p><p> Laccés a les funcions de Chatwoot es basa en els següents rols.</p><p> <b>Agent</b> - Els agents amb aquesta funció només poden accedir a les bústies dentrada, als informes i a les converses. Poden assignar converses a altres agents o a ells mateixos i resoldre converses.</p><p> <b>Administrador</b> - L'administrador tindrà accés a totes les funcions de Chatwoot habilitades per al teu compte, inclosa la configuració, juntament amb tots els privilegis dels agents normals.</p>",
"SIDEBAR_TXT": "<p><b>Agents</b></p> <p> Un <b>Agent</b> és membre del vostre equip datenció al client. </p><p> Els agents podran veure i respondre missatges dels teus usuaris. La llista mostra tots els agents que hi ha actualment al teu compte.</p><p> Fer clic a <b>Afegeix Agent</b> per afegir un agent nou. L´agent que afegeixes rebrà un correu electrònic amb un enllaç de confirmació per activar el seu compte, després del qual podrà accedir a Chatwoot i respondre als missatges. </p><p> L´accés a les funcions de Chatwoot es basa en els següents rols.</p><p> <b>Agent</b> - Els agents amb aquesta funció només poden accedir a les bústies d´entrada, als informes i a les converses. Poden assignar converses a altres agents o a ells mateixos i resoldre converses.</p><p> <b>Administrador</b> - L´administrador tindrà accés a totes les funcions de Chatwoot habilitades per al teu compte, inclosa la configuració, juntament amb tots els privilegis dels agents normals.</p>",
"AGENT_TYPES": {
"ADMINISTRATOR": "Administrador/a",
"AGENT": "Agent"
@@ -31,7 +31,7 @@
"AGENT_TYPE": {
"LABEL": "Tipus d'Agent",
"PLACEHOLDER": "Selecciona un tipus",
"ERROR": "El tipus d'Agent és necessari"
"ERROR": "El rol és necessari"
},
"EMAIL": {
"LABEL": "Adreça de correu electrònic",
@@ -76,8 +76,8 @@
},
"AGENT_AVAILABILITY": {
"LABEL": "Disponibilitat",
"PLACEHOLDER": "Please select an availability status",
"ERROR": "Availability is required"
"PLACEHOLDER": "Seleccioneu un estat de disponibilitat",
"ERROR": "Es requereix disponibilitat"
},
"SUBMIT": "Editar l'agent"
},
@@ -111,7 +111,7 @@
"PLACEHOLDER": {
"AGENT": "Cerca agents",
"TEAM": "Cerca equips",
"INPUT": "Search for agents"
"INPUT": "Cerca agents"
}
}
}
@@ -11,7 +11,7 @@
"DESC": "Audit Logs are trails for events and actions in a Chatwoot System.",
"TABLE_HEADER": [
"User",
"Action",
"Temps",
"Adreça IP"
]
},
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Loading agents",
"UPDATE": {
"CHANGE_STATUS": "Change status",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
"SNOOZE_UNTIL": "Snooze",
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -98,7 +98,7 @@
"CREATED_AT": "Created at"
},
"BUTTONS": {
"ADD": "Add",
"ADD": "Afegir",
"EDIT": "Edita",
"DELETE": "Esborrar"
},
@@ -1,75 +1,75 @@
{
"CANNED_MGMT": {
"HEADER": "Respostes predeterminades",
"HEADER_BTN_TXT": "Add canned response",
"LOADING": "Fetching canned responses...",
"HEADER_BTN_TXT": "Afegir resposta predeterminada",
"LOADING": "S'estan recollint les respostes predeterminades...",
"SEARCH_404": "No hi ha cap resposta que coincideixi amb aquesta consulta.",
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are pre-written reply templates that help you quickly respond to a conversation. To insert a canned response during a chat, agents can type a short code preceded by a '/' character. </p><p> You can manage your canned responses from this page or create new ones using the \"Add canned response\" button.</p><p>Open the <a target=\"_blank\" href=\"https://www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677501325-how-to-create-saved-reply-templates-with-canned-responses\">Canned Responses handbook</a> in another tab for a helping hand.</p><p>Also, check out the all-new <a href=\"https://www.chatwoot.com/tools/canned-responses-library\" target=\"_blank\">Canned Responses Library</a>.</p>",
"SIDEBAR_TXT": "<p><b>Respostes predeterminades</b> </p><p> Les respostes predeterminades són plantilles de respostes escrites prèviament que us ajuden a respondre ràpidament a una conversa. Per inserir una resposta predefinida durant un xat, els agents poden escriure un codi curt precedit d'un caràcter '/'. </p><p> Pots gestionar les teves respostes predefinides des d'aquesta pàgina o crear-ne de noves mitjançant el botó \"Afegeix una resposta predefinida\".</p><p>Obre el <a target=\"_blank\" href=\"https: //www.chatwoot.com/hc/chatwoot-user-guide-cloud-version/articles/1677501325-how-to-create-saved-reply-templates-with-canned-responses\">Manual de respostes llaunes</a> en una altra pestanya per ajudar-te.</p><p>També, fes una ullada al nou <a href=\"https://www.chatwoot.com/tools/canned-responses-library\" target=\"_blank \">Biblioteca de respostes predeterminades</a>.</p>",
"LIST": {
"404": "No hi ha respostes predeterminades disponibles en aquest compte.",
"TITLE": "Gestiona les respostes predeterminades",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to conversations.",
"DESC": "Les respostes predeterminades són plantilles de resposta predefinides que es poden utilitzar per enviar ràpidament respostes a les converses.",
"TABLE_HEADER": [
"Short code",
"Codi curt",
"Contingut",
"Accions"
]
},
"ADD": {
"TITLE": "Add canned response",
"DESC": "Canned Responses are predefined reply templates which can be used to quickly send out replies to conversations.",
"TITLE": "Afegir resposta predeterminada",
"DESC": "Les respostes predeterminades són plantilles de resposta predefinides que es poden utilitzar per enviar ràpidament respostes a les converses.",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"FORM": {
"SHORT_CODE": {
"LABEL": "Short code",
"PLACEHOLDER": "Please enter a short code.",
"ERROR": "Short Code is required."
"LABEL": "Codi curt",
"PLACEHOLDER": "Introduïu un codi curt.",
"ERROR": "És necessari el codi curt."
},
"CONTENT": {
"LABEL": "Missatge",
"PLACEHOLDER": "Please write the message you want to save as a template to use later.",
"ERROR": "Message is required."
"PLACEHOLDER": "Escriviu el missatge que voleu desar com a plantilla per utilitzar-lo més tard.",
"ERROR": "El missatge és obligatori."
},
"SUBMIT": "Envia"
},
"API": {
"SUCCESS_MESSAGE": "Canned response added successfully.",
"SUCCESS_MESSAGE": "Resposta predeterminada afegida correctament.",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
}
},
"EDIT": {
"TITLE": "Edit canned response",
"TITLE": "Edita la resposta predeterminada",
"CANCEL_BUTTON_TEXT": "Cancel·la",
"FORM": {
"SHORT_CODE": {
"LABEL": "Short code",
"PLACEHOLDER": "Please enter a shortcode.",
"ERROR": "Short code is required."
"LABEL": "Codi curt",
"PLACEHOLDER": "Introduïu un codi curt.",
"ERROR": "És necessari el codi curt."
},
"CONTENT": {
"LABEL": "Missatge",
"PLACEHOLDER": "Please write the message you want to save as a template to use later.",
"ERROR": "Message is required."
"PLACEHOLDER": "Escriviu el missatge que voleu desar com a plantilla per utilitzar-lo més tard.",
"ERROR": "El missatge és obligatori."
},
"SUBMIT": "Envia"
},
"BUTTON_TEXT": "Edita",
"API": {
"SUCCESS_MESSAGE": "Canned response is updated successfully.",
"SUCCESS_MESSAGE": "Resposta predeterminada actualitzada correctament.",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
}
},
"DELETE": {
"BUTTON_TEXT": "Esborra",
"API": {
"SUCCESS_MESSAGE": "Canned response deleted successfully.",
"SUCCESS_MESSAGE": "Resposta predeterminada eliminada correctament.",
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
},
"CONFIRM": {
"TITLE": "Confirm deletion",
"TITLE": "Confirma l'esborrat",
"MESSAGE": "N'estas segur ",
"YES": "Yes, delete ",
"NO": "No, keep "
"YES": "Sí, esborra ",
"NO": "No, mantén-la "
}
}
}
@@ -8,7 +8,7 @@
},
"TAB_HEADING": "Converses",
"MENTION_HEADING": "Mencions",
"UNATTENDED_HEADING": "Unattended",
"UNATTENDED_HEADING": "Sense assistència",
"SEARCH": {
"INPUT": "Cerca persones, xats, respostes desades .."
},
@@ -36,27 +36,27 @@
}
},
"VIEW_FILTER": "Veure",
"SORT_TOOLTIP_LABEL": "Sort conversations",
"SORT_TOOLTIP_LABEL": "Ordena les converses",
"CHAT_SORT": {
"STATUS": "Estat",
"ORDER_BY": "Order by"
"ORDER_BY": "Ordena per"
},
"CHAT_TIME_STAMP": {
"CREATED": {
"LATEST": "Created",
"OLDEST": "Created at:"
"LATEST": "Creat",
"OLDEST": "Creat per:"
},
"LAST_ACTIVITY": {
"NOT_ACTIVE": "Last activity:",
"ACTIVE": "Last activity"
"NOT_ACTIVE": "Darrera activitat:",
"ACTIVE": "Darrera activitat"
}
},
"SORT_ORDER_ITEMS": {
"last_activity_at_asc": {
"TEXT": "Last activity: Oldest first"
"TEXT": "Última activitat: Més antic primer"
},
"last_activity_at_desc": {
"TEXT": "Last activity: Newest first"
"TEXT": "Última activitat: La més nova primer"
},
"created_at_desc": {
"TEXT": "Created at: Newest first"
@@ -99,15 +99,15 @@
},
"CHAT_SORT_BY_FILTER": {
"TITLE": "Sort conversation",
"DROPDOWN_TITLE": "Sort by",
"DROPDOWN_TITLE": "Ordenat per",
"ITEMS": {
"LATEST": {
"NAME": "Last activity at",
"LABEL": "Last activity"
},
"CREATED_AT": {
"NAME": "Created at",
"LABEL": "Created at"
"NAME": "Creat per",
"LABEL": "Creat per"
},
"LAST_USER_MESSAGE_AT": {
"NAME": "Last user message at",
@@ -15,7 +15,7 @@
"INITIATED_FROM": "Iniciada des de",
"INITIATED_AT": "Iniciada a les",
"IP_ADDRESS": "Adreça IP",
"CREATED_AT_LABEL": "Created",
"CREATED_AT_LABEL": "Creat",
"NEW_MESSAGE": "Nou missatge",
"CONVERSATIONS": {
"NO_RECORDS_FOUND": "No hi han converses prèvies associades a aquest contacte.",
@@ -49,7 +49,7 @@
"CUSTOM_ATTRIBUTES": "Atributs personalitzats",
"CONTACT_LABELS": "Etiquetes de contactes",
"PREVIOUS_CONVERSATIONS": "Converses prèvies",
"NO_RECORDS_FOUND": "No attributes found"
"NO_RECORDS_FOUND": "No s'han trobat atributs"
}
},
"EDIT_CONTACT": {
@@ -76,16 +76,17 @@
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
},
"EXPORT_CONTACTS": {
"BUTTON_LABEL": "Export",
"TITLE": "Export Contacts",
"DESC": "Export contacts to a CSV file.",
"BUTTON_LABEL": "Exporta",
"TITLE": "Exporta contactes",
"DESC": "Exporta contactes a través d'un fitxer CSV.",
"SUCCESS_MESSAGE": "Export is in progress, You will be notified via email when export file is ready to dowanlod.",
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar",
"CONFIRM": {
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
"TITLE": "Exporta contactes",
"MESSAGE": "Esteu segur que voleu exportar tots els contactes?",
"FILTERED_MESSAGE": "Esteu segur que voleu exportar tots els contactes filtrats?",
"YES": "Sí, Exporta",
"NO": "No, cancel·la"
}
},
"DELETE_NOTE": {
@@ -151,13 +152,13 @@
"COUNTRY": {
"PLACEHOLDER": "Enter the country name",
"LABEL": "Nom del país",
"SELECT_PLACEHOLDER": "Select",
"SELECT_PLACEHOLDER": "Selecciona",
"REMOVE": "Suprimeix",
"SELECT_COUNTRY": "Select Country"
"SELECT_COUNTRY": "Selecciona país"
},
"CITY": {
"PLACEHOLDER": "Enter the city name",
"LABEL": "City Name"
"PLACEHOLDER": "Introdueix el nom de la ciutat",
"LABEL": "Nom de la ciutat"
},
"SOCIAL_PROFILES": {
"FACEBOOK": {
@@ -180,8 +181,8 @@
},
"DELETE_AVATAR": {
"API": {
"SUCCESS_MESSAGE": "Contact avatar deleted successfully",
"ERROR_MESSAGE": "Could not delete the contact avatar. Please try again later."
"SUCCESS_MESSAGE": "L'avatar del contacte s'ha suprimit correctament",
"ERROR_MESSAGE": "No s'ha pogut esborrar l'avatar de contacte. Torneu-ho a provar."
}
},
"SUCCESS_MESSAGE": "Contacte guardat correctament",
@@ -190,30 +191,30 @@
"NEW_CONVERSATION": {
"BUTTON_LABEL": "Inicia la conversa",
"TITLE": "Nova conversació",
"DESC": "Start a new conversation by sending a new message.",
"DESC": "Inicia una conversa nova enviant un missatge nou.",
"NO_INBOX": "Couldn't find an inbox to initiate a new conversation with this contact.",
"FORM": {
"TO": {
"LABEL": "To"
"LABEL": "Per"
},
"INBOX": {
"LABEL": "Inbox",
"PLACEHOLDER": "Choose source inbox",
"PLACEHOLDER": "Trieu la safata d'entrada d'origen",
"ERROR": "Select an inbox"
},
"SUBJECT": {
"LABEL": "Subject",
"PLACEHOLDER": "Subject",
"LABEL": "Assumpte",
"PLACEHOLDER": "Assumpte",
"ERROR": "Subject can't be empty"
},
"MESSAGE": {
"LABEL": "Missatge",
"PLACEHOLDER": "Write your message here",
"ERROR": "Message can't be empty"
"ERROR": "El missatge no pot estar buit"
},
"ATTACHMENTS": {
"SELECT": "Choose files",
"HELP_TEXT": "Drag and drop files here or choose files to attach"
"SELECT": "Trieu fitxers",
"HELP_TEXT": "Arrossegueu i deixeu anar fitxers aquí o trieu fitxers per adjuntar"
},
"SUBMIT": "Send message",
"CANCEL": "Cancel·la",
@@ -227,10 +228,10 @@
"FIELDS": "Contact fields",
"SEARCH_BUTTON": "Cercar",
"SEARCH_INPUT_PLACEHOLDER": "Cerca de contactes",
"FILTER_CONTACTS": "Filter",
"FILTER_CONTACTS_SAVE": "Save filter",
"FILTER_CONTACTS_DELETE": "Delete filter",
"FILTER_CONTACTS_EDIT": "Edit segment",
"FILTER_CONTACTS": "Filtre",
"FILTER_CONTACTS_SAVE": "Desa el filtre",
"FILTER_CONTACTS_DELETE": "Suprimir el filtre",
"FILTER_CONTACTS_EDIT": "Edita segment",
"LIST": {
"LOADING_MESSAGE": "Carregant contactes...",
"404": "No hi ha cap contacte que coincideixi amb la vostra cerca 🔍",
@@ -241,8 +242,8 @@
"CONVERSATIONS": "Converses",
"LAST_ACTIVITY": "Last Activity",
"CREATED_AT": "Created At",
"COUNTRY": "Country",
"CITY": "City",
"COUNTRY": "País",
"CITY": "Ciutat",
"SOCIAL_PROFILES": "Social Profiles",
"COMPANY": "Companyia",
"EMAIL_ADDRESS": "Adreça de correu electrònic"
@@ -256,93 +257,93 @@
},
"REMINDER": {
"ADD_BUTTON": {
"BUTTON": "Add",
"TITLE": "Shift + Enter to create a task"
"BUTTON": "Afegir",
"TITLE": "Shift + Enter per crear una tasca"
},
"FOOTER": {
"DUE_DATE": "Due date",
"LABEL_TITLE": "Set type"
"DUE_DATE": "Data de venciment",
"LABEL_TITLE": "Establir tipus"
}
},
"NOTES": {
"FETCHING_NOTES": "Fetching notes...",
"FETCHING_NOTES": "S'estan obtenint notes...",
"NOT_AVAILABLE": "There are no notes created for this contact",
"HEADER": {
"TITLE": "Notes"
},
"LIST": {
"LABEL": "added a note"
"LABEL": "afegeix una nota"
},
"ADD": {
"BUTTON": "Add",
"PLACEHOLDER": "Add a note",
"TITLE": "Shift + Enter to create a note"
"BUTTON": "Afegir",
"PLACEHOLDER": "Afegeix una nota",
"TITLE": "Shift + Enter per crear una nota"
},
"CONTENT_HEADER": {
"DELETE": "Delete note"
"DELETE": "Suprimeix la nota"
}
},
"EVENTS": {
"HEADER": {
"TITLE": "Activities"
"TITLE": "Activitats"
},
"BUTTON": {
"PILL_BUTTON_NOTES": "notes",
"PILL_BUTTON_EVENTS": "events",
"PILL_BUTTON_EVENTS": "esdeveniment",
"PILL_BUTTON_CONVO": "converses"
}
},
"CUSTOM_ATTRIBUTES": {
"ADD_BUTTON_TEXT": "Add attributes",
"BUTTON": "Add custom attribute",
"NOT_AVAILABLE": "There are no custom attributes available for this contact.",
"ADD_BUTTON_TEXT": "Afegir atributs",
"BUTTON": "Afegir atributs personalitzats",
"NOT_AVAILABLE": "No hi ha atributs personalitzats disponibles per a aquest contacte.",
"COPY_SUCCESSFUL": "S'ha copiat al porta-retalls amb èxit",
"SHOW_MORE": "Show all attributes",
"SHOW_LESS": "Show less attributes",
"SHOW_MORE": "Mostra tots els atributs",
"SHOW_LESS": "Mostra menys atributs",
"ACTIONS": {
"COPY": "Copy attribute",
"DELETE": "Delete attribute",
"EDIT": "Edit attribute"
"COPY": "Copia l'atribut",
"DELETE": "Suprimeix l'atribut",
"EDIT": "Edita l'atribut"
},
"ADD": {
"TITLE": "Create custom attribute",
"DESC": "Add custom information to this contact."
"TITLE": "Crear atribut personalitzat",
"DESC": "Afegeix informació personalitzada a aquest contacte."
},
"FORM": {
"CREATE": "Add attribute",
"CREATE": "Afegir atribut",
"CANCEL": "Cancel·la",
"NAME": {
"LABEL": "Custom attribute name",
"PLACEHOLDER": "Eg: shopify id",
"ERROR": "Invalid custom attribute name"
"LABEL": "Personalitza el nom de l'atribut",
"PLACEHOLDER": "P. ex.: identificador de Shopify",
"ERROR": "El nom de l'atribut personalitzat no és vàlid"
},
"VALUE": {
"LABEL": "Attribute value",
"PLACEHOLDER": "Eg: 11901 "
},
"ADD": {
"TITLE": "Create new attribute ",
"SUCCESS": "Attribute added successfully",
"ERROR": "Unable to add attribute. Please try again later"
"TITLE": "Crear nou atribut ",
"SUCCESS": "Atribut afegit correctament",
"ERROR": "No es pot afegir l'atribut. Si us plau, intenta-ho més tard"
},
"UPDATE": {
"SUCCESS": "Attribute updated successfully",
"ERROR": "Unable to update attribute. Please try again later"
"SUCCESS": "Atribut actualitzat correctament",
"ERROR": "No es pot actualitzar l'atribut. Si us plau, intenta-ho més tard"
},
"DELETE": {
"SUCCESS": "Attribute deleted successfully",
"ERROR": "Unable to delete attribute. Please try again later"
"SUCCESS": "Atribut esborrat correctament",
"ERROR": "No es pot esborrar l'atribut. Si us plau, intenta-ho més tard"
},
"ATTRIBUTE_SELECT": {
"TITLE": "Add attributes",
"PLACEHOLDER": "Search attributes",
"NO_RESULT": "No attributes found"
"TITLE": "Afegir atributs",
"PLACEHOLDER": "Cerca atributs",
"NO_RESULT": "No s'han trobat atributs"
},
"ATTRIBUTE_TYPE": {
"LIST": {
"PLACEHOLDER": "Select value",
"SEARCH_INPUT_PLACEHOLDER": "Search value",
"NO_RESULT": "No result found"
"PLACEHOLDER": "Selecciona valor",
"SEARCH_INPUT_PLACEHOLDER": "Cerca valor",
"NO_RESULT": "No s'ha trobat cap resultat"
}
}
},
@@ -365,7 +366,7 @@
"HELP_LABEL": "To be kept"
},
"SUMMARY": {
"TITLE": "Summary",
"TITLE": "Resum",
"DELETE_WARNING": "Contact of <strong>%{primaryContactName}</strong> will be deleted.",
"ATTRIBUTE_WARNING": "Contact details of <strong>%{primaryContactName}</strong> will be copied to <strong>%{parentContactName}</strong>."
},
@@ -373,13 +374,13 @@
"ERROR": "ERROR_MESSAGE"
},
"FORM": {
"SUBMIT": " Merge contacts",
"SUBMIT": " Fusiona contactes",
"CANCEL": "Cancel·la",
"CHILD_CONTACT": {
"ERROR": "Select a child contact to merge"
"ERROR": "Seleccioneu un contacte fill per fusionar"
},
"SUCCESS_MESSAGE": "Contact merged successfully",
"ERROR_MESSAGE": "Could not merge contacts, try again!"
"SUCCESS_MESSAGE": "Contacte fusionat correctament",
"ERROR_MESSAGE": "No s'ha pogut fusionar els contactes, torna-ho a provar!"
}
}
}
@@ -28,24 +28,24 @@
"is_not_present": "No és present",
"is_greater_than": "És més gran que",
"is_lesser_than": "Is lesser than",
"days_before": "Is x days before"
"days_before": "És x dies abans"
},
"ATTRIBUTES": {
"NAME": "Nom",
"EMAIL": "Correu electrònic",
"PHONE_NUMBER": "Número de telèfon",
"IDENTIFIER": "Identifier",
"CITY": "City",
"COUNTRY": "Country",
"CUSTOM_ATTRIBUTE_LIST": "List",
"CITY": "Ciutat",
"COUNTRY": "País",
"CUSTOM_ATTRIBUTE_LIST": "Llista",
"CUSTOM_ATTRIBUTE_TEXT": "Llista",
"CUSTOM_ATTRIBUTE_NUMBER": "Número",
"CUSTOM_ATTRIBUTE_LINK": "Enllaç",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Checkbox",
"CUSTOM_ATTRIBUTE_CHECKBOX": "Casella de selecció",
"CREATED_AT": "Created At",
"LAST_ACTIVITY": "Last Activity",
"REFERER_LINK": "Referrer link",
"BLOCKED": "Blocked"
"BLOCKED": "Blocat"
},
"GROUPS": {
"STANDARD_FILTERS": "Standard Filters",
@@ -1,11 +1,11 @@
{
"CONVERSATION": {
"SELECT_A_CONVERSATION": "Si us plau, selecciona una conversa al panell de lesquerra",
"CSAT_REPLY_MESSAGE": "Please rate the conversation",
"404": "Sorry, we cannot find the conversation. Please try again",
"SWITCH_VIEW_LAYOUT": "Switch the layout",
"DASHBOARD_APP_TAB_MESSAGES": "Messages",
"UNVERIFIED_SESSION": "The identity of this user is not verified",
"CSAT_REPLY_MESSAGE": "Si us plau, valoreu la conversa",
"404": "Ho sentim, no podem trobar la conversa. Torneu-ho a provar",
"SWITCH_VIEW_LAYOUT": "Canvia el disseny",
"DASHBOARD_APP_TAB_MESSAGES": "Missatges",
"UNVERIFIED_SESSION": "La identitat d'aquest usuari no està verificada",
"NO_MESSAGE_1": "Uh oh! Sembla que no hi ha missatges de clients a la safata d'entrada.",
"NO_MESSAGE_2": " per enviar un missatge a la vostra pàgina!",
"NO_INBOX_1": "Hola! Sembla que encara no heu afegit cap safata d'entrada.",
@@ -13,15 +13,15 @@
"NO_INBOX_AGENT": "Uh Oh! Sembla que no ets a cap safata d'entrada. Si us plau, poseu-vos en contacte amb l'administrador",
"SEARCH_MESSAGES": "Cerca missatges a les converses",
"EMPTY_STATE": {
"CMD_BAR": "to open command menu",
"KEYBOARD_SHORTCUTS": "to view keyboard shortcuts"
"CMD_BAR": "per obrir el menú d'ordres",
"KEYBOARD_SHORTCUTS": "per veure les dreceres del teclat"
},
"SEARCH": {
"TITLE": "Cerca missatges",
"RESULT_TITLE": "Search Results",
"RESULT_TITLE": "Resultats de la cerca",
"LOADING_MESSAGE": "S'estan restringint les dades...",
"PLACEHOLDER": "Escriu qualsevol text per cercar missatges",
"NO_MATCHING_RESULTS": "No results found."
"NO_MATCHING_RESULTS": "No s'han trobat resultats."
},
"UNREAD_MESSAGES": "Missatges no Llegits",
"UNREAD_MESSAGE": "Missatge no Llegit",
@@ -30,15 +30,15 @@
"LOADING_CONVERSATIONS": "S'estan carregant les converses",
"CANNOT_REPLY": "No pots respondre degut a",
"24_HOURS_WINDOW": "Restricció de finestra de missatges de 24 hores",
"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",
"NOT_ASSIGNED_TO_YOU": "Aquesta conversa no està assignada a tu. Vols assignar-te-la?",
"ASSIGN_TO_ME": "Assigna'm",
"TWILIO_WHATSAPP_CAN_REPLY": "Només pots respondre a aquesta conversa mitjançant una plantilla de missatge a causa de",
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "Restricció de finestra de missatges de 24 hores",
"REPLYING_TO": "Estas responent a:",
"REMOVE_SELECTION": "Elimina la selecció",
"DOWNLOAD": "Descarrega",
"UNKNOWN_FILE_TYPE": "Unknown File",
"SAVE_CONTACT": "Save",
"UNKNOWN_FILE_TYPE": "Fitxer desconegut",
"SAVE_CONTACT": "Desar",
"UPLOADING_ATTACHMENTS": "Pujant fitxers adjunts...",
"REPLIED_TO_STORY": "Replied to your story",
"UNSUPPORTED_MESSAGE": "This message is unsupported.",
@@ -89,7 +89,7 @@
"CANCEL": "Cancel·la"
},
"PRIORITY": {
"TITLE": "Priority",
"TITLE": "Prioritat",
"OPTIONS": {
"NONE": "Ningú",
"URGENT": "Urgent",
@@ -280,29 +280,29 @@
},
"CONVERSATION_CUSTOM_ATTRIBUTES": {
"ADD_BUTTON_TEXT": "Create attribute",
"NO_RECORDS_FOUND": "No attributes found",
"NO_RECORDS_FOUND": "No s'han trobat atributs",
"UPDATE": {
"SUCCESS": "Attribute updated successfully",
"ERROR": "Unable to update attribute. Please try again later"
"SUCCESS": "Atribut actualitzat correctament",
"ERROR": "No es pot actualitzar l'atribut. Si us plau, intenta-ho més tard"
},
"ADD": {
"TITLE": "Add",
"SUCCESS": "Attribute added successfully",
"ERROR": "Unable to add attribute. Please try again later"
"TITLE": "Afegir",
"SUCCESS": "Atribut afegit correctament",
"ERROR": "No es pot afegir l'atribut. Si us plau, intenta-ho més tard"
},
"DELETE": {
"SUCCESS": "Attribute deleted successfully",
"ERROR": "Unable to delete attribute. Please try again later"
"SUCCESS": "Atribut esborrat correctament",
"ERROR": "No es pot esborrar l'atribut. Si us plau, intenta-ho més tard"
},
"ATTRIBUTE_SELECT": {
"TITLE": "Add attributes",
"PLACEHOLDER": "Search attributes",
"NO_RESULT": "No attributes found"
"TITLE": "Afegir atributs",
"PLACEHOLDER": "Cerca atributs",
"NO_RESULT": "No s'han trobat atributs"
}
},
"EMAIL_HEADER": {
"FROM": "From",
"TO": "To",
"TO": "Per",
"BCC": "Bcc",
"CC": "Cc",
"SUBJECT": "Subject"
@@ -1,7 +1,7 @@
{
"EMOJI": {
"PLACEHOLDER": "Search emojis",
"NOT_FOUND": "No emoji match your search",
"PLACEHOLDER": "Cerca emojis",
"NOT_FOUND": "Cap emoji coincideix amb la teva cerca",
"REMOVE": "Suprimeix"
}
}
@@ -1,5 +1,5 @@
{
"GENERAL": {
"SHOWING_RESULTS": "Showing {firstIndex}-{lastIndex} of {totalCount} items"
"SHOWING_RESULTS": "Es mostren {firstIndex}-{lastIndex} de {totalCount} elements"
}
}
@@ -3,7 +3,7 @@
"TITLE": "Configuració del compte",
"SUBMIT": "Actualització de la configuració",
"BACK": "Enrere",
"DISMISS": "Dismiss",
"DISMISS": "Descartar",
"UPDATE": {
"ERROR": "No s'ha pogut actualitzar la configuració, torna-ho a provar!",
"SUCCESS": "La configuració del compte s'ha actualitzat correctament"
@@ -15,8 +15,8 @@
"NOTE": ""
},
"ACCOUNT_ID": {
"TITLE": "Account ID",
"NOTE": "This ID is required if you are building an API based integration"
"TITLE": "ID del compte",
"NOTE": "Aquest identificador és necessari si esteu creant una integració basada en API"
},
"NAME": {
"LABEL": "Nom del compte",
@@ -24,7 +24,7 @@
"ERROR": "Introduïu un nom de compte vàlid"
},
"LANGUAGE": {
"LABEL": "Site language",
"LABEL": "Idioma del lloc",
"PLACEHOLDER": "El nom del vostre compte",
"ERROR": ""
},
@@ -41,7 +41,7 @@
"AUTO_RESOLVE_DURATION": {
"LABEL": "El nombre de dies després que un ticket es resolgui automàticament si no hi ha activitat",
"PLACEHOLDER": "30",
"ERROR": "Please enter a valid auto resolve duration (minimum 1 day and maximum 999 days)"
"ERROR": "Introduïu una durada de resolució automàtica vàlida (mínim 1 dia i màxim 999 dies)"
},
"FEATURES": {
"INBOUND_EMAIL_ENABLED": "La continuïtat de converses amb correus electrònics està habilitada per al vostre compte.",
@@ -49,8 +49,8 @@
}
},
"UPDATE_CHATWOOT": "L'actualització %{latestChatwootVersion} per Chatwoot està disponible. Si us plau, actualitza l'instancia.",
"LEARN_MORE": "Learn more",
"PAYMENT_PENDING": "Your payment is pending. Please update your payment information to continue using Chatwoot",
"LEARN_MORE": "Aprèn més",
"PAYMENT_PENDING": "El teu pagament està pendent. Actualitzeu la vostra informació de pagament per continuar utilitzant Chatwoot",
"LIMITS_UPGRADE": "Your account has exceeded the usage limits, please upgrade your plan to continue using Chatwoot",
"OPEN_BILLING": "Open billing"
},
@@ -107,6 +107,7 @@
"GENERAL": "General",
"REPORTS": "Informes",
"CONVERSATION": "Conversation",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Change Assignee",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Change Team",
@@ -460,7 +460,7 @@
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
"BUTTON": {
"LEARN_MORE": "Learn more",
"LEARN_MORE": "Aprèn més",
"UPGRADE": "Upgrade"
},
"FEATURES": {
@@ -42,7 +42,7 @@
"SORT_OPTIONS": {
"NEWEST": "Newest",
"OLDEST": "Oldest",
"PRIORITY": "Priority"
"PRIORITY": "Prioritat"
},
"DISPLAY_OPTIONS": {
"SNOOZED": "Posposat",
@@ -83,7 +83,7 @@
},
"CHANNEL_GREETING_TOGGLE": {
"LABEL": "Activa la salutació del canal",
"HELP_TEXT": "Automatically send a greeting message when a new conversation is created.",
"HELP_TEXT": "Envia automàticament missatges de salutació quan els clients inicien una conversa i envien el seu primer missatge.",
"ENABLED": "Habilita",
"DISABLED": "Inhabilita"
},
@@ -169,7 +169,7 @@
},
"BANDWIDTH": {
"ACCOUNT_ID": {
"LABEL": "Account ID",
"LABEL": "ID del compte",
"PLACEHOLDER": "Please enter your Bandwidth Account ID",
"ERROR": "Aquest camp és obligatori"
},
@@ -340,7 +340,7 @@
},
"AUTH": {
"TITLE": "Choose a channel",
"DESC": "Chatwoot supports live-chat widgets, Facebook Messenger, Twitter profiles, WhatsApp, Emails, etc., as channels. If you want to build a custom channel, you can create it using the API channel. To get started, choose one of the channels below."
"DESC": "Chatwoot admet widgets de xat en directe, Facebook Messenger, WhatsApp, correus electrònics, etc., com a canals. Si voleu crear un canal personalitzat, podeu crear-lo mitjançant el canal API. Per començar, tria un dels canals següents."
},
"AGENTS": {
"TITLE": "Agents",
@@ -420,7 +420,7 @@
"BUSINESS_NAME": {
"BUTTON_TEXT": "+ Configure your business name",
"PLACEHOLDER": "Enter your business name",
"SAVE_BUTTON_TEXT": "Save"
"SAVE_BUTTON_TEXT": "Desar"
}
},
"ALLOW_MESSAGES_AFTER_RESOLVED": {
@@ -68,7 +68,7 @@
"TITLE": "Confirma l'esborrat",
"MESSAGE": "Are you sure to delete the webhook? (%{webhookURL})",
"YES": "Si, esborra ",
"NO": "No, manten-la"
"NO": "No, mantén-la"
}
}
},
@@ -79,8 +79,8 @@
"MESSAGE": "Are you sure you want to delete the integration? Doing so will result in the loss of access to conversations on your Slack workspace."
},
"HELP_TEXT": {
"TITLE": "Using Slack Integration",
"BODY": "<br/><p>Chatwoot will now sync all the incoming conversations into the <b><i>customer-conversations</i></b> channel inside your slack workplace.</p><p>Replying to a conversation thread in <b><i>customer-conversations</i></b> slack channel will create a response back to the customer through chatwoot.</p><p>Start the replies with <b><i>note:</i></b> to create private notes instead of replies.</p><p>If the replier on slack has an agent profile in chatwoot under the same email, the replies will be associated accordingly.</p><p>When the replier doesn't have an associated agent profile, the replies will be made from the bot profile.</p>",
"TITLE": "Com utilitzar la integració de Slack?",
"BODY": "Amb aquesta integració, totes les teves converses entrants se sincronitzaran amb el canal ***%{selectedChannelName}*** del teu espai de treball de Slack. Podeu gestionar totes les converses dels vostres clients directament des del canal i no perdreu cap missatge.\n\nAquestes són les principals característiques de la integració:\n\n**Respondre a converses des de Slack:** Per respondre a una conversa al ***%{selectedChannelName}*** canal de Slack, només has d'escriure el teu missatge i enviar-lo com a fil. Això crearà una resposta al client mitjançant Chatwoot. Així de senzill!\n\n **Crear notes privades:** si vols crear notes privades en lloc de respostes, comença el teu missatge amb ***`nota:`***. Això garanteix que el vostre missatge es mantingui privat i no serà visible per al client.\n\n**Associar un perfil d'agent:** si la persona que va respondre a Slack té un perfil d'agent a Chatwoot amb el mateix correu electrònic, les respostes s'associaran amb aquest perfil d'agent automàticament. Això vol dir que podeu fer un seguiment fàcilment qui va dir què i quan. D'altra banda, quan el contestador no té un perfil d'agent associat, les respostes apareixeran des del perfil del bot al client.",
"SELECTED": "selected"
},
"SELECT_CHANNEL": {
@@ -131,7 +131,7 @@
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
"BUTTONS": {
"NEED_HELP": "Need help?",
"DISMISS": "Dismiss",
"DISMISS": "Descartar",
"FINISH": "Finish Setup"
},
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
@@ -46,7 +46,7 @@
"DISMISS": "Dismiss suggestion"
},
"POWERED_BY": "Chatwoot AI",
"DISMISS": "Dismiss",
"DISMISS": "Descartar",
"ADD_SELECTED_LABELS": "Add selected labels",
"ADD_SELECTED_LABEL": "Add selected label",
"ADD_ALL_LABELS": "Add all labels"
@@ -3,7 +3,7 @@
"TITLE": "Entra a Chatwoot",
"EMAIL": {
"LABEL": "Correu electrònic",
"PLACEHOLDER": "Correu electrònic p.e.: someone@exemple.com",
"PLACEHOLDER": "exemple@nomdelacompanyia.com",
"ERROR": "Introduïu una adreça de correu electrònic vàlida"
},
"PASSWORD": {
@@ -16,9 +16,9 @@
"UNAUTH": "Nom d'usuari / contrasenya incorrecte. Torna-ho a provar-ho"
},
"OAUTH": {
"GOOGLE_LOGIN": "Login with Google",
"BUSINESS_ACCOUNTS_ONLY": "Please use your company email address to login",
"NO_ACCOUNT_FOUND": "We couldn't find an account for your email address."
"GOOGLE_LOGIN": "Inicieu sessió amb Google",
"BUSINESS_ACCOUNTS_ONLY": "Si us plau, utilitzeu l'adreça de correu electrònic de la vostra empresa per iniciar sessió",
"NO_ACCOUNT_FOUND": "No hem trobat cap compte per a la vostra adreça de correu electrònic."
},
"FORGOT_PASSWORD": "Has oblidat la contrasenya?",
"CREATE_NEW_ACCOUNT": "Crear un nou compte",
@@ -92,7 +92,7 @@
"DAY": "Day",
"WEEK": "Week",
"MONTH": "Month",
"YEAR": "Month"
"YEAR": "Any"
},
"GROUP_BY_DAY_OPTIONS": [
{
@@ -140,7 +140,7 @@
],
"BUSINESS_HOURS": "Business Hours",
"FILTER_ACTIONS": {
"CLEAR_FILTER": "Clear filter",
"CLEAR_FILTER": "Esborra els filtres",
"EMPTY_LIST": "No s'ha trobat agents"
}
},
@@ -232,7 +232,7 @@
},
"FIRST_RESPONSE_TIME": {
"NAME": "First Response Time",
"DESC": "( Promig )",
"DESC": "(Mitjana)",
"INFO_TEXT": "Total number of conversations used for computation:",
"TOOLTIP_TEXT": "First Response Time is %{metricValue} (based on %{conversationCount} conversations)"
},
@@ -511,52 +511,52 @@
"SATURDAY": "Saturday"
},
"SLA_REPORTS": {
"HEADER": "SLA Reports",
"NO_RECORDS": "SLA applied conversations are not available.",
"LOADING": "Loading SLA data...",
"DOWNLOAD_SLA_REPORTS": "Download SLA reports",
"DOWNLOAD_FAILED": "Failed to download SLA Reports",
"HEADER": "Informes SLA",
"NO_RECORDS": "Les converses aplicades per l'SLA no estan disponibles.",
"LOADING": "S'estan carregant dades del SLA...",
"DOWNLOAD_SLA_REPORTS": "Descarregar informes SLA",
"DOWNLOAD_FAILED": "No s'han pogut baixar els informes SLA",
"DROPDOWN": {
"ADD_FIlTER": "Add filter",
"CLEAR_ALL": "Clear all",
"CLEAR_FILTER": "Clear filter",
"ADD_FIlTER": "Afegeix un filtre",
"CLEAR_ALL": "Esborrar tot",
"CLEAR_FILTER": "Esborra els filtres",
"EMPTY_LIST": "No s'ha trobat agents",
"NO_FILTER": "No filters available",
"SEARCH": "Search filter",
"NO_FILTER": "No hi ha filtres disponibles",
"SEARCH": "Cerca filtre",
"INPUT_PLACEHOLDER": {
"SLA": "SLA name",
"AGENTS": "Agent name",
"INBOXES": "Inbox name",
"SLA": "Nom del SLA",
"AGENTS": "Nom de l'Agent",
"INBOXES": "Nom de la safata d'entrada",
"LABELS": "Nom de l'etiqueta",
"TEAMS": "Team name"
"TEAMS": "Nom de l'equip"
},
"SLA": "SLA Policy",
"INBOXES": "Inbox",
"SLA": "Política SLA",
"INBOXES": "Safata d'entrada",
"AGENTS": "Agent",
"LABELS": "Label",
"TEAMS": "Team"
"LABELS": "Etiqueta",
"TEAMS": "Equip"
},
"METRICS": {
"HIT_RATE": {
"LABEL": "Hit Rate",
"TOOLTIP": "Percentage of SLAs created were completed successfully"
"LABEL": "Proporció d'encerts",
"TOOLTIP": "El percentatge de SLA creats s'ha completat correctament"
},
"NO_OF_MISSES": {
"LABEL": "Number of Misses",
"TOOLTIP": "Total SLA misses in a certain period"
"LABEL": "Nombre de faltes",
"TOOLTIP": "SLA total faltes en un període determinat"
},
"NO_OF_CONVERSATIONS": {
"LABEL": "Number of Conversations",
"TOOLTIP": "Total number of conversations with SLA"
"LABEL": "Número de Converses",
"TOOLTIP": "Nombre total de converses amb SLA"
}
},
"TABLE": {
"HEADER": {
"POLICY": "Policy",
"CONVERSATION": "Conversation",
"POLICY": "Política",
"CONVERSATION": "Converses",
"AGENT": "Agent"
},
"VIEW_DETAILS": "View Details"
"VIEW_DETAILS": "Veure Detalls"
}
}
}
@@ -4,12 +4,12 @@
"ALL": "Totes",
"CONTACTS": "Contactes",
"CONVERSATIONS": "Converses",
"MESSAGES": "Messages"
"MESSAGES": "Missatges"
},
"SECTION": {
"CONTACTS": "Contactes",
"CONVERSATIONS": "Converses",
"MESSAGES": "Messages"
"MESSAGES": "Missatges"
},
"EMPTY_STATE": "No %{item} found for query '%{query}'",
"EMPTY_STATE_FULL": "No results found for query '%{query}'",
@@ -91,9 +91,9 @@
"CONVERSATION_MENTION": "Enviar notificacions per mail quan siguis esmentat en una conversació",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Envia notificacions per correu electrònic quan es creï un missatge nou en una conversa assignada",
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in a participating conversation",
"SLA_MISSED_FIRST_RESPONSE": "Send email notifications when a conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "Send email notifications when a conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "Send email notifications when a conversation misses resolution SLA"
"SLA_MISSED_FIRST_RESPONSE": "Envia notificacions per correu electrònic quan una conversa no compleixi un SLA de primera resposta",
"SLA_MISSED_NEXT_RESPONSE": "Envia notificacions per correu electrònic quan una conversa es perdi la següent resposta SLA",
"SLA_MISSED_RESOLUTION": "Envia notificacions per correu electrònic quan una conversa no resol un SLA"
},
"NOTIFICATIONS": {
"TITLE": "Notification preferences",
@@ -109,7 +109,8 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
}
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
},
"API": {
"UPDATE_SUCCESS": "Les teves preferències de notificació shan actualitzat correctament",
@@ -125,9 +126,9 @@
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in a participating conversation",
"HAS_ENABLED_PUSH": "Heu activat les notificacions per a aquest navegador.",
"REQUEST_PUSH": "Activa les notificacions",
"SLA_MISSED_FIRST_RESPONSE": "Send push notifications when a conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "Send push notifications when a conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "Send push notifications when a conversation misses resolution SLA"
"SLA_MISSED_FIRST_RESPONSE": "Envia notificacions automàtiques quan una conversa no tingui un SLA de primera resposta",
"SLA_MISSED_NEXT_RESPONSE": "Envia notificacions automàtiques quan una conversa no s'aconsegueixi la següent resposta SLA",
"SLA_MISSED_RESOLUTION": "Envia notificacions quan una conversa no resol el SLA"
},
"PROFILE_IMAGE": {
"LABEL": "Imatge del Perfil"
@@ -11,7 +11,7 @@
"COMPANY_NAME": {
"LABEL": "Company name",
"PLACEHOLDER": "Enter your company name. eg: Wayne Enterprises",
"ERROR": "Company name is too short"
"ERROR": "El nom de l'empresa és massa curt."
},
"FULL_NAME": {
"LABEL": "Nom complet",
@@ -20,7 +20,7 @@
},
"EMAIL": {
"LABEL": "Email de treball",
"PLACEHOLDER": "Introdueix la teva adreça email de treball. ex: bruce@wayne.enterprises",
"PLACEHOLDER": "Introdueix la teva adreça de correu electrònica de la feina. P. ex.: bruce@wayne.enterprises",
"ERROR": "Please enter a valid work email address"
},
"PASSWORD": {
@@ -32,7 +32,7 @@
"CONFIRM_PASSWORD": {
"LABEL": "Confirma la contrasenya",
"PLACEHOLDER": "Confirma la contrasenya",
"ERROR": "Les contrasenyes no coincideixen"
"ERROR": "La contrasenya no coincideix."
},
"API": {
"SUCCESS_MESSAGE": "Registrat correctament",
@@ -1,10 +1,10 @@
{
"SLA": {
"HEADER": "SLA",
"ADD_ACTION": "Add SLA",
"ADD_ACTION_LONG": "Create a new SLA Policy",
"DESCRIPTION": "Service Level Agreements (SLAs) are contracts that define clear expectations between your team and customers. They establish standards for response and resolution times, creating a framework for accountability and ensures a consistent, high-quality experience.",
"LEARN_MORE": "Learn more about SLA",
"ADD_ACTION": "Afegeix SLA",
"ADD_ACTION_LONG": "Crea una nova política de SLA",
"DESCRIPTION": "Els acords de nivell de servei (SLA) són contractes que defineixen expectatives clares entre el vostre equip i els clients. Estableixen estàndards de temps de resposta i resolució, creen un marc de responsabilitat i garanteixen una experiència coherent i d'alta qualitat.",
"LEARN_MORE": "Més informació sobre SLA",
"LOADING": "Fetching SLAs",
"PAYWALL": {
"TITLE": "Upgrade to create SLAs",
@@ -21,10 +21,10 @@
"LIST": {
"404": "There are no SLAs available in this account.",
"EMPTY": {
"TITLE_1": "Enterprise P0",
"DESC_1": "Issues raised by enterprise customers, that require immediate attention.",
"TITLE_2": "Enterprise P1",
"DESC_2": "Issues raised by enterprise customers, that needs to be acknowledged quickly."
"TITLE_1": "Empresa P0",
"DESC_1": "Problemes plantejats pels clients empresarials, que requereixen atenció immediata.",
"TITLE_2": "Empresa P1",
"DESC_2": "Problemes plantejats pels clients empresarials, que cal reconèixer ràpidament."
},
"BUSINESS_HOURS_ON": "Business hours on",
"BUSINESS_HOURS_OFF": "Business hours off",
@@ -77,7 +77,7 @@
},
"ADD": {
"TITLE": "Add SLA",
"DESC": "Friendly promises for great service!",
"DESC": "Promeses amistoses per a un gran servei!",
"API": {
"SUCCESS_MESSAGE": "SLA added successfully",
"ERROR_MESSAGE": "S'ha produït un error; tornau-ho a provar"
@@ -97,12 +97,12 @@
}
},
"EVENTS": {
"TITLE": "SLA Misses",
"TITLE": "Faltes SLA",
"FRT": "Primer temps de resposta",
"NRT": "Next response time",
"RT": "Resolution time",
"SHOW_MORE": "{count} more",
"HIDE": "Hide {count} rows"
"NRT": "Pròxim temps de resposta",
"RT": "Temps de resolució",
"SHOW_MORE": "{count} més",
"HIDE": "Amaga {count} files"
}
}
}
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Načítání agentů",
"UPDATE": {
"CHANGE_STATUS": "Change status",
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply.",
"SNOOZE_UNTIL": "Odložit",
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,6 +84,7 @@
"CONFIRM": {
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
@@ -107,6 +107,7 @@
"GENERAL": "General",
"REPORTS": "Zprávy",
"CONVERSATION": "Conversation",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Change Assignee",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Change Team",
@@ -109,7 +109,8 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
}
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
},
"API": {
"UPDATE_SUCCESS": "Vaše předvolby oznámení byly úspěšně aktualizovány",
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Indlæser agenter",
"UPDATE": {
"CHANGE_STATUS": "Skift status",
"SNOOZE_UNTIL_NEXT_REPLY": "Udsæt til næste svar.",
"SNOOZE_UNTIL": "Udsæt",
"UPDATE_SUCCESFUL": "Samtalens status blev opdateret.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,6 +84,7 @@
"CONFIRM": {
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
@@ -107,6 +107,7 @@
"GENERAL": "Generelt",
"REPORTS": "Rapporter",
"CONVERSATION": "Samtale",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Skift Modtager",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Skift Team",
@@ -109,7 +109,8 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
}
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
},
"API": {
"UPDATE_SUCCESS": "Dine notifikationsindstillinger er opdateret",
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Agenten werden geladen",
"UPDATE": {
"CHANGE_STATUS": "Status ändern",
"SNOOZE_UNTIL_NEXT_REPLY": "Bis zur nächsten Antwort zurückstellen.",
"SNOOZE_UNTIL": "Erinnern",
"UPDATE_SUCCESFUL": "Der Konversationsstatus wurde erfolgreich aktualisiert.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,6 +84,7 @@
"CONFIRM": {
"TITLE": "Kontakte exportieren",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
@@ -1,7 +1,7 @@
{
"CONVERSATION": {
"SELECT_A_CONVERSATION": "Bitte wählen Sie eine Konversation aus dem linken Bereich aus",
"CSAT_REPLY_MESSAGE": "Bitte bewerten Sie diese Unterhaltung",
"CSAT_REPLY_MESSAGE": "Bitte bewerten Sie diese Konversation",
"404": "Wir können die Konversation leider nicht finden. Bitte versuche es erneut",
"SWITCH_VIEW_LAYOUT": "Layout wechseln",
"DASHBOARD_APP_TAB_MESSAGES": "Nachrichten",
@@ -49,7 +49,7 @@
"NO_RESPONSE": "Keine Antwort",
"RATING_TITLE": "Bewertung",
"FEEDBACK_TITLE": "Feedback",
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
"REPLY_MESSAGE_NOT_FOUND": "Nachricht nicht verfügbar",
"CARD": {
"SHOW_LABELS": "Labels anzeigen",
"HIDE_LABELS": "Labels ausblenden"
@@ -150,8 +150,8 @@
"SEND": "Senden",
"CREATE": "Notiz hinzufügen",
"INSERT_READ_MORE": "Mehr erfahren",
"DISMISS_REPLY": "Dismiss reply",
"REPLYING_TO": "Replying to:",
"DISMISS_REPLY": "Antwort verwerfen",
"REPLYING_TO": "Antwort auf:",
"TIP_FORMAT_ICON": "Rich Text Editor anzeigen",
"TIP_EMOJI_ICON": "Emoji-Auswahl anzeigen",
"TIP_ATTACH_ICON": "Dateien anhängen",
@@ -206,7 +206,7 @@
},
"CONTEXT_MENU": {
"COPY": "Kopieren",
"REPLY_TO": "Reply to this message",
"REPLY_TO": "Auf diese Nachricht antworten",
"DELETE": "Löschen",
"CREATE_A_CANNED_RESPONSE": "Zu vorgefertigten Antworten hinzufügen",
"TRANSLATE": "Übersetzen",
@@ -107,6 +107,7 @@
"GENERAL": "Generell",
"REPORTS": "Berichte",
"CONVERSATION": "Konversation",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Beauftragten ändern",
"CHANGE_PRIORITY": "Priorität ändern",
"CHANGE_TEAM": "Team wechseln",
@@ -717,7 +717,7 @@
"IN_A_DAY": "Wir antworten üblicherweise innerhalb eines Tages"
},
"FOOTER": {
"START_CONVERSATION_BUTTON_TEXT": "Unterhaltung beginnen",
"START_CONVERSATION_BUTTON_TEXT": "Konversation beginnen",
"CHAT_INPUT_PLACEHOLDER": "Schreiben Sie Ihre Nachricht"
},
"BODY": {
@@ -1,7 +1,7 @@
{
"RESET_PASSWORD": {
"TITLE": "Passwort zurücksetzen",
"DESCRIPTION": "Enter the email address you use to log in to Chatwoot to get the password reset instructions.",
"DESCRIPTION": "Geben Sie die E-Mail-Adresse ein, mit der Sie sich bei Chatwoot anmelden, um die Anweisungen zum Zurücksetzen des Passworts zu erhalten.",
"GO_BACK_TO_LOGIN": "If you want to go back to the login page,",
"EMAIL": {
"LABEL": "E-Mail",
@@ -109,7 +109,8 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
}
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
},
"API": {
"UPDATE_SUCCESS": "Ihre Benachrichtigungseinstellungen wurden erfolgreich aktualisiert",
@@ -189,9 +190,9 @@
"TRIAL_MESSAGE": "Tage der Testversion verbleibend.",
"TRAIL_BUTTON": "Jetzt kaufen",
"DELETED_USER": "Gelöschter Benutzer",
"EMAIL_VERIFICATION_PENDING": "It seems that you haven't verified your email address yet. Please check your inbox for the verification email.",
"RESEND_VERIFICATION_MAIL": "Resend verification email",
"EMAIL_VERIFICATION_SENT": "Verification email has been sent. Please check your inbox.",
"EMAIL_VERIFICATION_PENDING": "Es scheint, dass Sie Ihre E-Mail-Adresse noch nicht verifiziert haben. Bitte überprüfen Sie Ihren Posteingang auf die Verifizierungs-E-Mail.",
"RESEND_VERIFICATION_MAIL": "Verifizierungs-E-Mail erneut senden",
"EMAIL_VERIFICATION_SENT": "Verifizierungs-E-Mail wurde versendet. Bitte überprüfen Sie Ihren Posteingang.",
"ACCOUNT_SUSPENDED": {
"TITLE": "Konto gesperrt",
"MESSAGE": "Ihr Account wurde gesperrt. Wenden Sie sich für weitere Informationen bitte an das Support-Team."
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Φόρτωση πρακτόρων",
"UPDATE": {
"CHANGE_STATUS": "Αλλαγή κατάστασης",
"SNOOZE_UNTIL_NEXT_REPLY": "Αναβολή έως την επόμενη απάντηση.",
"SNOOZE_UNTIL": "Αναβολή",
"UPDATE_SUCCESFUL": "Η κατάσταση συνομιλίας ενημερώθηκε με επιτυχία.",
"UPDATE_FAILED": "Failed to update conversations. Please try again."
},
@@ -84,6 +84,7 @@
"CONFIRM": {
"TITLE": "Export Contacts",
"MESSAGE": "Are you sure you want to export all contacts?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Yes, Export",
"NO": "No, Cancel"
}
@@ -107,6 +107,7 @@
"GENERAL": "Γενικά",
"REPORTS": "Αναφορές",
"CONVERSATION": "Συνομιλία",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Αλλαγή Αναδόχου",
"CHANGE_PRIORITY": "Change Priority",
"CHANGE_TEAM": "Αλλαγή Ομάδας",
@@ -109,7 +109,8 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
}
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
},
"API": {
"UPDATE_SUCCESS": "Οι προτιμήσεις σας για τις ειδοποιήσεις ενημερώθηκαν",
@@ -154,7 +154,7 @@
"UNTIL_TOMORROW": "Until tomorrow",
"UNTIL_NEXT_MONTH": "Until next month",
"AN_HOUR_FROM_NOW": "Until an hour from now",
"CUSTOM": "Custom...",
"UNTIL_CUSTOM_TIME": "Custom...",
"CHANGE_APPEARANCE": "Change Appearance",
"LIGHT_MODE": "Light",
"DARK_MODE": "Dark",
@@ -8,9 +8,8 @@
"NO_NOTIFICATIONS": "No notifications",
"NOTE": "Notifications from all subscribed inboxes",
"NO_MESSAGES_AVAILABLE": "Oops! Not able to fetch messages",
"SNOOZED_UNTIL": "Snoozed until",
"SNOOZED_UNTIL_TOMORROW": "Snoozed until tomorrow",
"SNOOZED_UNTIL_NEXT_WEEK": "Snoozed until next week"
"SNOOZED_UNTIL": "Well remind you after {time}",
"SNOOZED_ENDS": "We wont notify you again"
},
"ACTION_HEADER": {
"SNOOZE": "Snooze notification",
@@ -18,14 +17,11 @@
"BACK": "Back"
},
"TYPES": {
"CONVERSATION_MENTION": "You have been mentioned in a conversation",
"CONVERSATION_CREATION": "New conversation created",
"CONVERSATION_ASSIGNMENT": "A conversation has been assigned to you",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "New message in an assigned conversation",
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "New message in a conversation you are participating in",
"SLA_MISSED_FIRST_RESPONSE": "SLA target first response missed for conversation",
"SLA_MISSED_NEXT_RESPONSE": "SLA target next response missed for conversation",
"SLA_MISSED_RESOLUTION": "SLA target resolution missed for conversation"
"CONVERSATION_MENTION": "Mention",
"CONVERSATION_ASSIGNMENT": "Assigned to you",
"SLA_MISSED_FIRST_RESPONSE": "SLA breach",
"SLA_MISSED_NEXT_RESPONSE": "SLA breach",
"SLA_MISSED_RESOLUTION": "SLA breach"
},
"MENU_ITEM": {
"MARK_AS_READ": "Mark as read",
@@ -2,6 +2,7 @@
"INBOX_MGMT": {
"HEADER": "Inboxes",
"SIDEBAR_TXT": "<p><b>Inbox</b></p> <p> When you connect a website or a facebook Page to Chatwoot, it is called an <b>Inbox</b>. You can have unlimited inboxes in your Chatwoot account. </p><p> Click on <b>Add Inbox</b> to connect a website or a Facebook Page. </p><p> In the Dashboard, you can see all the conversations from all your inboxes in a single place and respond to them under the `Conversations` tab. </p><p> You can also see conversations specific to an inbox by clicking on the inbox name on the left pane of the dashboard. </p>",
"RECONNECTION_REQUIRED": "Your inbox is disconnected, you will not receive any new messages. <a class=\"underline\" href=\"%{actionUrl}\">Click here</a> to reconnect.",
"LIST": {
"404": "There are no inboxes attached to this account."
},
@@ -736,4 +737,4 @@
"OTHER_PROVIDERS": "Other Providers"
}
}
}
}
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "Cargando agentes",
"UPDATE": {
"CHANGE_STATUS": "Cambiar estado",
"SNOOZE_UNTIL_NEXT_REPLY": "Posponer hasta la próxima respuesta.",
"SNOOZE_UNTIL": "Posponer",
"UPDATE_SUCCESFUL": "El estado de la conversación se actualizó con éxito.",
"UPDATE_FAILED": "No se han podido actualizar las conversaciones. Inténtalo de nuevo."
},
@@ -84,6 +84,7 @@
"CONFIRM": {
"TITLE": "Exportar contactos",
"MESSAGE": "¿Está seguro que desea exportar todos los contactos?",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "Sí, exportar",
"NO": "No, cancelar"
}
@@ -107,6 +107,7 @@
"GENERAL": "General",
"REPORTS": "Informes",
"CONVERSATION": "Conversación",
"BULK_ACTIONS": "Bulk Actions",
"CHANGE_ASSIGNEE": "Cambiar Asignado",
"CHANGE_PRIORITY": "Cambiar prioridad",
"CHANGE_TEAM": "Cambiar equipo",
@@ -109,7 +109,8 @@
"SLA_MISSED_FIRST_RESPONSE": "A conversation misses first response SLA",
"SLA_MISSED_NEXT_RESPONSE": "A conversation misses next response SLA",
"SLA_MISSED_RESOLUTION": "A conversation misses resolution SLA"
}
},
"BROWSER_PERMISSION": "Enable push notifications for your browser so youre able to receive them"
},
"API": {
"UPDATE_SUCCESS": "Sus preferencias de notificación se actualizaron correctamente",
@@ -17,7 +17,7 @@
"AGENT_LIST_LOADING": "بارگذاری ایجنت ها",
"UPDATE": {
"CHANGE_STATUS": "تغییر وضعیت",
"SNOOZE_UNTIL_NEXT_REPLY": "تا پاسخ بعدی به تعویق بی‌افتد.",
"SNOOZE_UNTIL": "به تعویق انداختن",
"UPDATE_SUCCESFUL": "وضعیت گفتگو با موفقیت به روز شد.",
"UPDATE_FAILED": "گفتگوها به روز نشدند، لطفا دوباره امتحان کنید."
},
@@ -84,6 +84,7 @@
"CONFIRM": {
"TITLE": "خروجی گرفتن مخاطب ها",
"MESSAGE": "آیا مطمئن هستید که می خواهید از همه مخاطبین خروجی بگیرید؟",
"FILTERED_MESSAGE": "Are you sure you want to export all the filtered contacts?",
"YES": "بله، خروجی بگیر",
"NO": "نه، انصراف"
}
@@ -1,13 +1,13 @@
{
"DATE_PICKER": {
"APPLY_BUTTON": "درخواست دادن",
"CLEAR_BUTTON": "Clear",
"CLEAR_BUTTON": "پاک کردن",
"DATE_RANGE_INPUT": {
"START": "Start Date",
"END": "End Date"
"START": "تاریخ شروع",
"END": "تاریخ پایان"
},
"DATE_RANGE_OPTIONS": {
"TITLE": "DATE RANGE",
"TITLE": "محدوده زمانی",
"LAST_7_DAYS": "در ۷ روز گذشته",
"LAST_30_DAYS": "در ۳۰ روز گذشته",
"LAST_3_MONTHS": "۳ ماه گذشته",

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