Merge branch 'develop' into feat/reload-banner-chat-list
This commit is contained in:
commit
38488ae37d
122 changed files with 1582 additions and 380 deletions
|
@ -50,3 +50,6 @@ exclude_patterns:
|
|||
- 'app/javascript/dashboard/routes/dashboard/settings/automation/constants.js'
|
||||
- 'app/javascript/dashboard/components/widgets/FilterInput/FilterOperatorTypes.js'
|
||||
- 'app/javascript/dashboard/routes/dashboard/settings/reports/constants.js'
|
||||
- 'app/javascript/dashboard/i18n/index.js'
|
||||
- 'app/javascript/widget/i18n/index.js'
|
||||
- 'app/javascript/survey/i18n/index.js'
|
||||
|
|
|
@ -32,6 +32,11 @@ REDIS_SENTINELS=
|
|||
# You can find list of master using "SENTINEL masters" command
|
||||
REDIS_SENTINEL_MASTER_NAME=
|
||||
|
||||
# Redis premium breakage in heroku fix
|
||||
# enable the following configuration
|
||||
# ref: https://github.com/chatwoot/chatwoot/issues/2420
|
||||
# REDIS_OPENSSL_VERIFY_MODE=none
|
||||
|
||||
# Postgres Database config variables
|
||||
POSTGRES_HOST=postgres
|
||||
POSTGRES_USERNAME=postgres
|
||||
|
|
|
@ -29,8 +29,8 @@ module.exports = {
|
|||
'vue/html-self-closing': 'off',
|
||||
"vue/no-v-html": 'off',
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'import/extensions': ['off']
|
||||
|
||||
'import/extensions': ['off'],
|
||||
'no-console': 'error'
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -42,7 +42,7 @@ gem 'down', '~> 5.0'
|
|||
gem 'aws-sdk-s3', require: false
|
||||
gem 'azure-storage-blob', require: false
|
||||
gem 'google-cloud-storage', require: false
|
||||
gem 'image_processing'
|
||||
gem 'image_processing', '~> 1.12.2'
|
||||
|
||||
##-- gems for database --#
|
||||
gem 'groupdate'
|
||||
|
|
|
@ -688,7 +688,7 @@ DEPENDENCIES
|
|||
hairtrigger
|
||||
hashie
|
||||
html2text
|
||||
image_processing
|
||||
image_processing (~> 1.12.2)
|
||||
jbuilder
|
||||
json_refs
|
||||
json_schemer
|
||||
|
|
4
app.json
4
app.json
|
@ -32,6 +32,10 @@
|
|||
"INSTALLATION_ENV": {
|
||||
"description": "Installation method used for Chatwoot.",
|
||||
"value": "heroku"
|
||||
},
|
||||
"REDIS_OPENSSL_VERIFY_MODE":{
|
||||
"description": "OpenSSL verification mode for Redis connections. ref https://help.heroku.com/HC0F8CUS/redis-connection-issues",
|
||||
"value": "none"
|
||||
}
|
||||
},
|
||||
"formation": {
|
||||
|
|
|
@ -19,7 +19,7 @@ class V2::ReportBuilder
|
|||
# For backward compatible with old report
|
||||
def build
|
||||
timeseries.each_with_object([]) do |p, arr|
|
||||
arr << { value: p[1], timestamp: p[0].to_time.to_i }
|
||||
arr << { value: p[1], timestamp: p[0].in_time_zone(@timezone).to_i }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class Api::V1::Accounts::AgentBotsController < Api::V1::Accounts::BaseController
|
|||
end
|
||||
|
||||
def destroy
|
||||
@agent_bot.destroy
|
||||
@agent_bot.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
|
|||
end
|
||||
|
||||
def destroy
|
||||
@agent.current_account_user.destroy
|
||||
@agent.current_account_user.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont
|
|||
params.permit(
|
||||
:name, :description, :event_name, :account_id, :active,
|
||||
conditions: [:attribute_key, :filter_operator, :query_operator, { values: [] }],
|
||||
actions: [:action_name, { action_params: [] }]
|
||||
actions: [:action_name, { action_params: [{}] }]
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ class Api::V1::Accounts::CampaignsController < Api::V1::Accounts::BaseController
|
|||
end
|
||||
|
||||
def destroy
|
||||
@campaign.destroy
|
||||
@campaign.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ class Api::V1::Accounts::CannedResponsesController < Api::V1::Accounts::BaseCont
|
|||
end
|
||||
|
||||
def destroy
|
||||
@canned_response.destroy
|
||||
@canned_response.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ class Api::V1::Accounts::Contacts::NotesController < Api::V1::Accounts::Contacts
|
|||
end
|
||||
|
||||
def destroy
|
||||
@note.destroy
|
||||
@note.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class Api::V1::Accounts::CustomAttributeDefinitionsController < Api::V1::Account
|
|||
end
|
||||
|
||||
def destroy
|
||||
@custom_attribute_definition.destroy
|
||||
@custom_attribute_definition.destroy!
|
||||
head :no_content
|
||||
end
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class Api::V1::Accounts::CustomFiltersController < Api::V1::Accounts::BaseContro
|
|||
end
|
||||
|
||||
def destroy
|
||||
@custom_filter.destroy
|
||||
@custom_filter.destroy!
|
||||
head :no_content
|
||||
end
|
||||
|
||||
|
|
|
@ -48,7 +48,10 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||
# Inbox update doesn't necessarily need channel attributes
|
||||
return if permitted_params(channel_attributes)[:channel].blank?
|
||||
|
||||
validate_email_channel(channel_attributes) if @inbox.inbox_type == 'Email'
|
||||
if @inbox.inbox_type == 'Email'
|
||||
validate_email_channel(channel_attributes)
|
||||
@inbox.channel.reauthorized!
|
||||
end
|
||||
|
||||
@inbox.channel.update!(permitted_params(channel_attributes)[:channel])
|
||||
update_channel_feature_flags
|
||||
|
@ -70,7 +73,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||
end
|
||||
|
||||
def destroy
|
||||
@inbox.destroy
|
||||
@inbox.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ class Api::V1::Accounts::Integrations::HooksController < Api::V1::Accounts::Base
|
|||
end
|
||||
|
||||
def destroy
|
||||
@hook.destroy
|
||||
@hook.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ class Api::V1::Accounts::Integrations::SlackController < Api::V1::Accounts::Base
|
|||
end
|
||||
|
||||
def destroy
|
||||
@hook.destroy
|
||||
@hook.destroy!
|
||||
|
||||
head :ok
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ class Api::V1::Accounts::Kbase::CategoriesController < Api::V1::Accounts::Kbase:
|
|||
end
|
||||
|
||||
def destroy
|
||||
@category.destroy
|
||||
@category.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ class Api::V1::Accounts::Kbase::PortalsController < Api::V1::Accounts::Kbase::Ba
|
|||
end
|
||||
|
||||
def destroy
|
||||
@portal.destroy
|
||||
@portal.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class Api::V1::Accounts::LabelsController < Api::V1::Accounts::BaseController
|
|||
end
|
||||
|
||||
def destroy
|
||||
@label.destroy
|
||||
@label.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class Api::V1::Accounts::TeamsController < Api::V1::Accounts::BaseController
|
|||
end
|
||||
|
||||
def destroy
|
||||
@team.destroy
|
||||
@team.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ class Api::V1::Accounts::WebhooksController < Api::V1::Accounts::BaseController
|
|||
end
|
||||
|
||||
def destroy
|
||||
@webhook.destroy
|
||||
@webhook.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class Api::V1::NotificationSubscriptionsController < Api::BaseController
|
|||
|
||||
def destroy
|
||||
notification_subscription = NotificationSubscription.where(["subscription_attributes->>'push_token' = ?", params[:push_token]]).first
|
||||
notification_subscription.destroy
|
||||
notification_subscription.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ class Platform::Api::V1::AccountUsersController < PlatformController
|
|||
end
|
||||
|
||||
def destroy
|
||||
@resource.account_users.find_by(user_id: account_user_params[:user_id])&.destroy
|
||||
@resource.account_users.find_by(user_id: account_user_params[:user_id])&.destroy!
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -7,9 +7,9 @@ class Platform::Api::V1::UsersController < PlatformController
|
|||
|
||||
def create
|
||||
@resource = (User.find_by(email: user_params[:email]) || User.new(user_params))
|
||||
@resource.skip_confirmation!
|
||||
@resource.save!
|
||||
@resource.confirm
|
||||
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
|
||||
@platform_app.platform_app_permissibles.find_or_create_by!(permissible: @resource)
|
||||
end
|
||||
|
||||
def login
|
||||
|
|
|
@ -26,7 +26,8 @@
|
|||
|
||||
code {
|
||||
border: 0;
|
||||
font-family: 'Monaco', Verdana;
|
||||
font-family: 'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas',
|
||||
'"Liberation Mono"', '"Courier New"', 'monospace';
|
||||
font-size: $font-size-mini;
|
||||
|
||||
&.hljs {
|
||||
|
@ -55,7 +56,6 @@ code {
|
|||
padding-right: var(--space-normal);
|
||||
}
|
||||
|
||||
|
||||
.badge {
|
||||
border-radius: var(--border-radius-normal);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
:active-menu-item="activePrimaryMenu.key"
|
||||
@toggle-accounts="toggleAccountModal"
|
||||
@key-shortcut-modal="toggleKeyShortcutModal"
|
||||
@open-notification-panel="openNotificationPanel"
|
||||
/>
|
||||
<secondary-sidebar
|
||||
:account-id="accountId"
|
||||
|
@ -176,6 +177,9 @@ export default {
|
|||
showAddLabelPopup() {
|
||||
this.$emit('show-add-label-popup');
|
||||
},
|
||||
openNotificationPanel() {
|
||||
this.$emit('open-notification-panel');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
<template>
|
||||
<div class="notifications-link">
|
||||
<primary-nav-item
|
||||
name="NOTIFICATIONS"
|
||||
icon="alert"
|
||||
:to="`/app/accounts/${accountId}/notifications`"
|
||||
:count="unreadCount"
|
||||
/>
|
||||
<woot-button
|
||||
class-names="notifications-link--button"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
:class="{ 'is-active': isNotificationPanelActive }"
|
||||
@click="openNotificationPanel"
|
||||
>
|
||||
<fluent-icon icon="alert" />
|
||||
<span v-if="unreadCount" class="badge warning">{{ unreadCount }}</span>
|
||||
</woot-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import PrimaryNavItem from './PrimaryNavItem';
|
||||
|
||||
export default {
|
||||
components: { PrimaryNavItem },
|
||||
computed: {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
|
@ -28,8 +30,17 @@ export default {
|
|||
? `${this.notificationMetadata.unreadCount}`
|
||||
: '99+';
|
||||
},
|
||||
isNotificationPanelActive() {
|
||||
return this.$route.name === 'notifications_index';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openNotificationPanel() {
|
||||
if (this.$route.name !== 'notifications_index') {
|
||||
this.$emit('open-notification-panel');
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -37,4 +48,32 @@ export default {
|
|||
.notifications-link {
|
||||
margin-bottom: var(--space-small);
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
right: var(--space-minus-smaller);
|
||||
top: var(--space-minus-smaller);
|
||||
}
|
||||
.notifications-link--button {
|
||||
display: flex;
|
||||
position: relative;
|
||||
border-radius: var(--border-radius-large);
|
||||
border: 1px solid transparent;
|
||||
color: var(--s-600);
|
||||
margin: var(--space-small) 0;
|
||||
|
||||
&:hover {
|
||||
background: var(--w-50);
|
||||
color: var(--s-600);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: var(--w-500);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: var(--w-50);
|
||||
color: var(--w-500);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
/>
|
||||
</nav>
|
||||
<div class="menu vertical user-menu">
|
||||
<notification-bell />
|
||||
<notification-bell @open-notification-panel="openNotificationPanel" />
|
||||
<agent-details @toggle-menu="toggleOptions" />
|
||||
<options-menu
|
||||
:show="showOptionsMenu"
|
||||
|
@ -83,6 +83,9 @@ export default {
|
|||
toggleSupportChatWindow() {
|
||||
window.$chatwoot.toggle();
|
||||
},
|
||||
openNotificationPanel() {
|
||||
this.$emit('open-notification-panel');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -14,6 +14,10 @@ const i18nConfig = new VueI18n({
|
|||
messages: i18n,
|
||||
});
|
||||
|
||||
const $route = {
|
||||
name: 'notifications_index',
|
||||
};
|
||||
|
||||
describe('notificationBell', () => {
|
||||
const accountId = 1;
|
||||
const notificationMetadata = { unreadCount: 19 };
|
||||
|
@ -45,24 +49,40 @@ describe('notificationBell', () => {
|
|||
});
|
||||
|
||||
it('it should return unread count 19 ', () => {
|
||||
const notificationBell = shallowMount(NotificationBell, {
|
||||
store,
|
||||
const wrapper = shallowMount(NotificationBell, {
|
||||
localVue,
|
||||
i18n: i18nConfig,
|
||||
store,
|
||||
mocks: {
|
||||
$route,
|
||||
},
|
||||
});
|
||||
|
||||
const statusViewTitle = notificationBell.find('primary-nav-item-stub');
|
||||
expect(statusViewTitle.vm.count).toBe('19');
|
||||
expect(wrapper.vm.unreadCount).toBe('19');
|
||||
});
|
||||
|
||||
it('it should return unread count 99+ ', async () => {
|
||||
notificationMetadata.unreadCount = 101;
|
||||
notificationMetadata.unreadCount = 100;
|
||||
const wrapper = shallowMount(NotificationBell, {
|
||||
localVue,
|
||||
i18n: i18nConfig,
|
||||
store,
|
||||
mocks: {
|
||||
$route,
|
||||
},
|
||||
});
|
||||
expect(wrapper.vm.unreadCount).toBe('99+');
|
||||
});
|
||||
|
||||
it('isNotificationPanelActive', async () => {
|
||||
const notificationBell = shallowMount(NotificationBell, {
|
||||
store,
|
||||
localVue,
|
||||
i18n: i18nConfig,
|
||||
mocks: {
|
||||
$route,
|
||||
},
|
||||
});
|
||||
const statusViewTitle = notificationBell.find('primary-nav-item-stub');
|
||||
expect(statusViewTitle.vm.count).toBe('99+');
|
||||
|
||||
expect(notificationBell.vm.isNotificationPanelActive).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="preview-item__wrap">
|
||||
<div
|
||||
v-for="(attachment, index) in attachments"
|
||||
:key="attachment.id"
|
||||
|
@ -19,7 +19,7 @@
|
|||
</span>
|
||||
</div>
|
||||
<div class="file-size-wrap">
|
||||
<span class="item">
|
||||
<span class="item text-truncate">
|
||||
{{ formatFileSize(attachment.resource) }}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -70,15 +70,23 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.preview-item__wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
margin-top: var(--space-normal);
|
||||
max-height: 20rem;
|
||||
}
|
||||
|
||||
.preview-item {
|
||||
display: flex;
|
||||
padding: var(--space-slab) 0 0;
|
||||
background: var(--color-background-light);
|
||||
background: var(--b-50);
|
||||
border-radius: var(--border-radius-normal);
|
||||
width: fit-content;
|
||||
width: 24rem;
|
||||
padding: var(--space-smaller);
|
||||
margin-top: var(--space-normal);
|
||||
margin-bottom: var(--space-one);
|
||||
}
|
||||
|
||||
.thumb-wrap {
|
||||
|
@ -114,6 +122,7 @@ export default {
|
|||
|
||||
> .item {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-size: var(--font-size-mini);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
@ -124,7 +133,8 @@ export default {
|
|||
}
|
||||
|
||||
.file-name-wrap {
|
||||
max-width: 50%;
|
||||
max-width: 60%;
|
||||
min-width: 50%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-left: var(--space-small);
|
||||
|
|
|
@ -186,6 +186,12 @@ export default {
|
|||
blur: () => {
|
||||
this.onBlur();
|
||||
},
|
||||
paste: (view, event) => {
|
||||
const data = event.clipboardData.files;
|
||||
if (data.length > 0) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
this.focusEditorInputField();
|
||||
|
|
|
@ -453,12 +453,16 @@ export default {
|
|||
methods: {
|
||||
onPaste(e) {
|
||||
const data = e.clipboardData.files;
|
||||
if (!this.showRichContentEditor && data.length !== 0) {
|
||||
this.$refs.messageInput.$el.blur();
|
||||
}
|
||||
if (!data.length || !data[0]) {
|
||||
return;
|
||||
}
|
||||
const file = data[0];
|
||||
const { name, type, size } = file;
|
||||
this.onFileUpload({ name, type, size, file });
|
||||
data.forEach(file => {
|
||||
const { name, type, size } = file;
|
||||
this.onFileUpload({ name, type, size, file: file });
|
||||
});
|
||||
},
|
||||
toggleUserMention(currentMentionState) {
|
||||
this.hasUserMention = currentMentionState;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<div class="input-group-field">
|
||||
<woot-input
|
||||
v-model.trim="$v.ccEmailsVal.$model"
|
||||
type="email"
|
||||
type="text"
|
||||
:class="{ error: $v.ccEmailsVal.$error }"
|
||||
:placeholder="$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.CC.PLACEHOLDER')"
|
||||
@blur="onBlur"
|
||||
|
@ -35,7 +35,7 @@
|
|||
<div class="input-group-field">
|
||||
<woot-input
|
||||
v-model.trim="$v.bccEmailsVal.$model"
|
||||
type="email"
|
||||
type="text"
|
||||
:class="{ error: $v.bccEmailsVal.$error }"
|
||||
:placeholder="
|
||||
$t('CONVERSATION.REPLYBOX.EMAIL_HEAD.BCC.PLACEHOLDER')
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"is_not_present": "غير موجود",
|
||||
"is_greater_than": "هو أكبر من",
|
||||
"is_less_than": "هو أقل من",
|
||||
"days_before": "Is x days before"
|
||||
"days_before": "قبل x أيام"
|
||||
},
|
||||
"ATTRIBUTE_LABELS": {
|
||||
"TRUE": "صحيح",
|
||||
|
@ -44,7 +44,7 @@
|
|||
"CUSTOM_ATTRIBUTE_NUMBER": "العدد",
|
||||
"CUSTOM_ATTRIBUTE_LINK": "الرابط",
|
||||
"CUSTOM_ATTRIBUTE_CHECKBOX": "مربع",
|
||||
"CREATED_AT": "Created At",
|
||||
"CREATED_AT": "تم إنشاؤها في",
|
||||
"LAST_ACTIVITY": "آخر نشاط"
|
||||
},
|
||||
"GROUPS": {
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"is_not_present": "غير موجود",
|
||||
"is_greater_than": "هو أكبر من",
|
||||
"is_lesser_than": "هو أقل من",
|
||||
"days_before": "Is x days before"
|
||||
"days_before": "قبل x أيام"
|
||||
},
|
||||
"ATTRIBUTES": {
|
||||
"NAME": "الاسم",
|
||||
|
@ -37,7 +37,7 @@
|
|||
"CUSTOM_ATTRIBUTE_NUMBER": "العدد",
|
||||
"CUSTOM_ATTRIBUTE_LINK": "الرابط",
|
||||
"CUSTOM_ATTRIBUTE_CHECKBOX": "مربع",
|
||||
"CREATED_AT": "Created At",
|
||||
"CREATED_AT": "تم إنشاؤها في",
|
||||
"LAST_ACTIVITY": "آخر نشاط"
|
||||
},
|
||||
"GROUPS": {
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
}
|
||||
},
|
||||
"UPDATE_CHATWOOT": "يتوفر تحديث %{latestChatwootVersion} لـ Chatwoot. الرجاء التحديث.",
|
||||
"LEARN_MORE": "Learn more"
|
||||
"LEARN_MORE": "اعرف المزيد"
|
||||
},
|
||||
"FORMS": {
|
||||
"MULTISELECT": {
|
||||
|
|
|
@ -60,6 +60,13 @@
|
|||
"NOTIFICATIONS_PAGE": {
|
||||
"HEADER": "Notifications",
|
||||
"MARK_ALL_DONE": "Mark All Done",
|
||||
"DELETE_TITLE": "deleted",
|
||||
"UNREAD_NOTIFICATION": {
|
||||
"TITLE": "Unread Notifications",
|
||||
"ALL_NOTIFICATIONS": "View all notifications",
|
||||
"LOADING_UNREAD_MESSAGE": "Loading unread notifications...",
|
||||
"EMPTY_MESSAGE": "You have no unread notifications"
|
||||
},
|
||||
"LIST": {
|
||||
"LOADING_MESSAGE": "Loading notifications...",
|
||||
"404": "No Notifications",
|
||||
|
|
|
@ -1,41 +1,41 @@
|
|||
{
|
||||
"AUTOMATION": {
|
||||
"HEADER": "Automations",
|
||||
"HEADER": "Automazioni",
|
||||
"HEADER_BTN_TXT": "Aggiungi regola di automazione",
|
||||
"LOADING": "Fetching automation rules",
|
||||
"SIDEBAR_TXT": "<p><b>Automation Rules</b> <p>Automation can replace and automate existing processes that require manual effort. You can do many things with automation, including adding labels and assigning conversation to the best agent. So the team focuses on what they do best and spends more little time on manual tasks.</p>",
|
||||
"LOADING": "Recupero delle regole di automazione",
|
||||
"SIDEBAR_TXT": "<p><b>Regole di automazione</b> <p>L'automazione può sostituire e automatizzare i processi esistenti che richiedono uno sforzo manuale. È possibile fare molte cose con l'automazione, tra cui l'aggiunta di etichette e l'assegnazione di una conversazione al miglior agente. Così il team si concentra su quello che fanno meglio e spende meno tempo per le attività manuali.</p>",
|
||||
"ADD": {
|
||||
"TITLE": "Aggiungi regola di automazione",
|
||||
"SUBMIT": "Crea",
|
||||
"CANCEL_BUTTON_TEXT": "annulla",
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Rule Name",
|
||||
"PLACEHOLDER": "Enter rule name",
|
||||
"LABEL": "Nome regola",
|
||||
"PLACEHOLDER": "Inserisci il nome della regola",
|
||||
"ERROR": "Il nome è obbligatorio"
|
||||
},
|
||||
"DESC": {
|
||||
"LABEL": "Descrizione",
|
||||
"PLACEHOLDER": "Enter rule description",
|
||||
"PLACEHOLDER": "Inserisci la descrizione della regola",
|
||||
"ERROR": "La descrizione è obbligatoria"
|
||||
},
|
||||
"EVENT": {
|
||||
"LABEL": "Event",
|
||||
"LABEL": "Evento",
|
||||
"PLACEHOLDER": "Si prega di selezionarne uno",
|
||||
"ERROR": "Event is required"
|
||||
"ERROR": "L'evento è obbligatorio"
|
||||
},
|
||||
"CONDITIONS": {
|
||||
"LABEL": "Conditions"
|
||||
"LABEL": "Condizioni"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"LABEL": "Azioni"
|
||||
}
|
||||
},
|
||||
"CONDITION_BUTTON_LABEL": "Add Condition",
|
||||
"ACTION_BUTTON_LABEL": "Add Action",
|
||||
"CONDITION_BUTTON_LABEL": "Aggiungi condizione",
|
||||
"ACTION_BUTTON_LABEL": "Aggiungi azione",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automation rule added successfully",
|
||||
"ERROR_MESSAGE": "Could not able to create a automation rule, Please try again later"
|
||||
"SUCCESS_MESSAGE": "Regola di automazione aggiunta con successo",
|
||||
"ERROR_MESSAGE": "Impossibile creare una regola di automazione, riprova più tardi"
|
||||
}
|
||||
},
|
||||
"LIST": {
|
||||
|
@ -43,12 +43,12 @@
|
|||
"Nome",
|
||||
"Descrizione",
|
||||
"Attivo",
|
||||
"Created on"
|
||||
"Creato il"
|
||||
],
|
||||
"404": "No automation rules found"
|
||||
"404": "Nessuna regola di automazione trovata"
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE": "Delete Automation Rule",
|
||||
"TITLE": "Elimina regola di automazione",
|
||||
"SUBMIT": "Elimina",
|
||||
"CANCEL_BUTTON_TEXT": "annulla",
|
||||
"CONFIRM": {
|
||||
|
@ -58,24 +58,24 @@
|
|||
"NO": "No, Conserva "
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automation rule deleted successfully",
|
||||
"ERROR_MESSAGE": "Could not able to delete a automation rule, Please try again later"
|
||||
"SUCCESS_MESSAGE": "Regola di automazione eliminata con successo",
|
||||
"ERROR_MESSAGE": "Impossibile eliminare una regola di automazione, riprova più tardi"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Edit Automation Rule",
|
||||
"TITLE": "Modifica regola di automazione",
|
||||
"SUBMIT": "Aggiorna",
|
||||
"CANCEL_BUTTON_TEXT": "annulla",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automation rule updated successfully",
|
||||
"ERROR_MESSAGE": "Could not update automation rule, Please try again later"
|
||||
"SUCCESS_MESSAGE": "Regola di automazione aggiornata con successo",
|
||||
"ERROR_MESSAGE": "Impossibile aggiornare la regola di automazione, riprova più tardi"
|
||||
}
|
||||
},
|
||||
"CLONE": {
|
||||
"TOOLTIP": "Clone",
|
||||
"TOOLTIP": "Clona",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automation cloned successfully",
|
||||
"ERROR_MESSAGE": "Could not clone automation rule, Please try again later"
|
||||
"SUCCESS_MESSAGE": "Automazione clonata con successo",
|
||||
"ERROR_MESSAGE": "Impossibile clonare la regola di automazione, riprova più tardi"
|
||||
}
|
||||
},
|
||||
"FORM": {
|
||||
|
@ -83,24 +83,24 @@
|
|||
"CREATE": "Crea",
|
||||
"DELETE": "Elimina",
|
||||
"CANCEL": "annulla",
|
||||
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||
"RESET_MESSAGE": "Cambiare il tipo di evento resetterà le condizioni e gli eventi che hai aggiunto di seguito"
|
||||
},
|
||||
"CONDITION": {
|
||||
"DELETE_MESSAGE": "You need to have atleast one condition to save"
|
||||
"DELETE_MESSAGE": "È necessario avere almeno una condizione per salvare"
|
||||
},
|
||||
"ACTION": {
|
||||
"DELETE_MESSAGE": "You need to have atleast one action to save"
|
||||
"DELETE_MESSAGE": "È necessario avere almeno una azione da salvare"
|
||||
},
|
||||
"TOGGLE": {
|
||||
"ACTIVATION_TITLE": "Activate Automation Rule",
|
||||
"DEACTIVATION_TITLE": "Deactivate Automation Rule",
|
||||
"ACTIVATION_DESCRIPTION": "This action will activate the automation rule '{automationName}'. Are you sure you want to proceed?",
|
||||
"DEACTIVATION_DESCRIPTION": "This action will deactivate the automation rule '{automationName}'. Are you sure you want to proceed?",
|
||||
"ACTIVATION_SUCCESFUL": "Automation Rule Activated Successfully",
|
||||
"DEACTIVATION_SUCCESFUL": "Automation Rule Deactivated Successfully",
|
||||
"ACTIVATION_ERROR": "Could not Activate Automation, Please try again later",
|
||||
"DEACTIVATION_ERROR": "Could not Deactivate Automation, Please try again later",
|
||||
"CONFIRMATION_LABEL": "Yes",
|
||||
"ACTIVATION_TITLE": "Attiva regola di automazione",
|
||||
"DEACTIVATION_TITLE": "Disattiva regola di automazione",
|
||||
"ACTIVATION_DESCRIPTION": "Questa azione attiverà la regola di automazione '{automationName}'. Sei sicuro di voler procedere?",
|
||||
"DEACTIVATION_DESCRIPTION": "Questa azione disattiverà la regola di automazione '{automationName}'. Sei sicuro di voler procedere?",
|
||||
"ACTIVATION_SUCCESFUL": "Regola di automazione attivata con successo",
|
||||
"DEACTIVATION_SUCCESFUL": "Regola di automazione disattivata con successo",
|
||||
"ACTIVATION_ERROR": "Impossibile attivare l'automazione, riprova più tardi",
|
||||
"DEACTIVATION_ERROR": "Impossibile disattivare l'automazione, riprova più tardi",
|
||||
"CONFIRMATION_LABEL": "Sì",
|
||||
"CANCEL_LABEL": "No"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@
|
|||
"NO_CONTACTS": "There are no available contacts",
|
||||
"TABLE_HEADER": {
|
||||
"NAME": "Nome",
|
||||
"PHONE_NUMBER": "Phone Number",
|
||||
"PHONE_NUMBER": "Numero di telefono",
|
||||
"CONVERSATIONS": "Conversazioni",
|
||||
"LAST_ACTIVITY": "Last Activity",
|
||||
"COUNTRY": "Country",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"GENERAL_SETTINGS": {
|
||||
"TITLE": "Impostazioni account",
|
||||
"SUBMIT": "Aggiorna le impostazioni",
|
||||
"BACK": "Back",
|
||||
"BACK": "Indietro",
|
||||
"UPDATE": {
|
||||
"ERROR": "Impossibile aggiornare le impostazioni, riprova!",
|
||||
"SUCCESS": "Impostazioni account aggiornate con successo"
|
||||
|
@ -14,8 +14,8 @@
|
|||
"NOTE": ""
|
||||
},
|
||||
"ACCOUNT_ID": {
|
||||
"TITLE": "Account ID",
|
||||
"NOTE": "This ID is required if you are building an API based integration"
|
||||
"TITLE": "ID Account",
|
||||
"NOTE": "Questo ID è richiesto se si sta costruendo un'integrazione basata su API"
|
||||
},
|
||||
"NAME": {
|
||||
"LABEL": "Nome account",
|
||||
|
@ -28,8 +28,8 @@
|
|||
"ERROR": ""
|
||||
},
|
||||
"DOMAIN": {
|
||||
"LABEL": "Incoming Email Domain",
|
||||
"PLACEHOLDER": "The domain where you will receive the emails",
|
||||
"LABEL": "Dominio email in entrata",
|
||||
"PLACEHOLDER": "Il dominio in cui riceverai le email",
|
||||
"ERROR": ""
|
||||
},
|
||||
"SUPPORT_EMAIL": {
|
||||
|
@ -38,95 +38,95 @@
|
|||
"ERROR": ""
|
||||
},
|
||||
"AUTO_RESOLVE_DURATION": {
|
||||
"LABEL": "Number of days after a ticket should auto resolve if there is no activity",
|
||||
"LABEL": "Numero di giorni dopo che un ticket dovrebbe risolvere automaticamente se non c'è attività",
|
||||
"PLACEHOLDER": "30",
|
||||
"ERROR": "Please enter a valid auto resolve duration (minimum 1 day and maximum 999 days)"
|
||||
"ERROR": "Inserisci una durata di risoluzione automatica valida (minimo 1 giorno e massimo 999 giorni)"
|
||||
},
|
||||
"FEATURES": {
|
||||
"INBOUND_EMAIL_ENABLED": "Conversation continuity with emails is enabled for your account.",
|
||||
"CUSTOM_EMAIL_DOMAIN_ENABLED": "You can receive emails in your custom domain now."
|
||||
"INBOUND_EMAIL_ENABLED": "La continuità della conversazione con le email è abilitata per il tuo account.",
|
||||
"CUSTOM_EMAIL_DOMAIN_ENABLED": "Puoi ricevere email nel tuo dominio personalizzato ora."
|
||||
}
|
||||
},
|
||||
"UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance.",
|
||||
"LEARN_MORE": "Learn more"
|
||||
"UPDATE_CHATWOOT": "È disponibile un aggiornamento %{latestChatwootVersion} per Chatwoot. Aggiorna la tua istanza.",
|
||||
"LEARN_MORE": "Scopri di più"
|
||||
},
|
||||
"FORMS": {
|
||||
"MULTISELECT": {
|
||||
"ENTER_TO_SELECT": "Press enter to select",
|
||||
"ENTER_TO_REMOVE": "Press enter to remove",
|
||||
"SELECT_ONE": "Select one"
|
||||
"ENTER_TO_SELECT": "Premi Invio per selezionare",
|
||||
"ENTER_TO_REMOVE": "Premi Invio per rimuovere",
|
||||
"SELECT_ONE": "Selezionane uno"
|
||||
}
|
||||
},
|
||||
"NOTIFICATIONS_PAGE": {
|
||||
"HEADER": "Notifiche",
|
||||
"MARK_ALL_DONE": "Mark All Done",
|
||||
"MARK_ALL_DONE": "Contrassegna tutto come fatto",
|
||||
"LIST": {
|
||||
"LOADING_MESSAGE": "Loading notifications...",
|
||||
"404": "No Notifications",
|
||||
"LOADING_MESSAGE": "Caricamento notifiche...",
|
||||
"404": "Nessuna notifica",
|
||||
"TABLE_HEADER": [
|
||||
"Nome",
|
||||
"Phone Number",
|
||||
"Numero di telefono",
|
||||
"Conversazioni",
|
||||
"Last Contacted"
|
||||
"Ultimo contattato"
|
||||
]
|
||||
},
|
||||
"TYPE_LABEL": {
|
||||
"conversation_creation": "New conversation",
|
||||
"conversation_assignment": "Conversation Assigned",
|
||||
"assigned_conversation_new_message": "New Message",
|
||||
"conversation_mention": "Mention"
|
||||
"conversation_creation": "Nuova conversazione",
|
||||
"conversation_assignment": "Conversazione assegnata",
|
||||
"assigned_conversation_new_message": "Nuovo messaggio",
|
||||
"conversation_mention": "Menzione"
|
||||
}
|
||||
},
|
||||
"NETWORK": {
|
||||
"NOTIFICATION": {
|
||||
"TEXT": "Disconnected from Chatwoot"
|
||||
"TEXT": "Disconnesso da Chatwoot"
|
||||
},
|
||||
"BUTTON": {
|
||||
"REFRESH": "Refresh"
|
||||
"REFRESH": "Aggiorna"
|
||||
}
|
||||
},
|
||||
"COMMAND_BAR": {
|
||||
"SEARCH_PLACEHOLDER": "Search or jump to",
|
||||
"SEARCH_PLACEHOLDER": "Cerca o salta a",
|
||||
"SECTIONS": {
|
||||
"GENERAL": "General",
|
||||
"GENERAL": "Generale",
|
||||
"REPORTS": "Segnalazioni",
|
||||
"CONVERSATION": "Conversazioni",
|
||||
"CHANGE_ASSIGNEE": "Change Assignee",
|
||||
"CHANGE_TEAM": "Change Team",
|
||||
"ADD_LABEL": "Add label to the conversation",
|
||||
"REMOVE_LABEL": "Remove label from the conversation",
|
||||
"CHANGE_ASSIGNEE": "Cambia assegnatario",
|
||||
"CHANGE_TEAM": "Cambia Team",
|
||||
"ADD_LABEL": "Aggiungi etichetta alla conversazione",
|
||||
"REMOVE_LABEL": "Rimuovi etichetta dalla conversazione",
|
||||
"SETTINGS": "Impostazioni"
|
||||
},
|
||||
"COMMANDS": {
|
||||
"GO_TO_CONVERSATION_DASHBOARD": "Vai alla dashboard Conversazioni",
|
||||
"GO_TO_CONTACTS_DASHBOARD": "Vai alla dashboard Contatti",
|
||||
"GO_TO_REPORTS_OVERVIEW": "Go to Reports Overview",
|
||||
"GO_TO_AGENT_REPORTS": "Go to Agent Reports",
|
||||
"GO_TO_LABEL_REPORTS": "Go to Label Reports",
|
||||
"GO_TO_INBOX_REPORTS": "Go to Inbox Reports",
|
||||
"GO_TO_TEAM_REPORTS": "Go to Team Reports",
|
||||
"GO_TO_SETTINGS_AGENTS": "Go to Agent Settings",
|
||||
"GO_TO_SETTINGS_TEAMS": "Go to Team Settings",
|
||||
"GO_TO_SETTINGS_INBOXES": "Go to Inbox Settings",
|
||||
"GO_TO_SETTINGS_LABELS": "Go to Label Settings",
|
||||
"GO_TO_SETTINGS_CANNED_RESPONSES": "Go to Canned Response Settings",
|
||||
"GO_TO_SETTINGS_APPLICATIONS": "Go to Application Settings",
|
||||
"GO_TO_SETTINGS_ACCOUNT": "Go to Account Settings",
|
||||
"GO_TO_SETTINGS_PROFILE": "Go to Profile Settings",
|
||||
"GO_TO_NOTIFICATIONS": "Go to Notifications",
|
||||
"ADD_LABELS_TO_CONVERSATION": "Add label to the conversation",
|
||||
"ASSIGN_AN_AGENT": "Assign an agent",
|
||||
"ASSIGN_A_TEAM": "Assign a team",
|
||||
"MUTE_CONVERSATION": "Mute conversation",
|
||||
"UNMUTE_CONVERSATION": "Unmute conversation",
|
||||
"REMOVE_LABEL_FROM_CONVERSATION": "Remove label from the conversation",
|
||||
"GO_TO_REPORTS_OVERVIEW": "Vai alla panoramica dei report",
|
||||
"GO_TO_AGENT_REPORTS": "Vai ai report degli agenti",
|
||||
"GO_TO_LABEL_REPORTS": "Vai ai report delle etichette",
|
||||
"GO_TO_INBOX_REPORTS": "Vai ai report delle caselle",
|
||||
"GO_TO_TEAM_REPORTS": "Vai ai report dei team",
|
||||
"GO_TO_SETTINGS_AGENTS": "Vai alle impostazioni dell'agente",
|
||||
"GO_TO_SETTINGS_TEAMS": "Vai alle impostazioni del team",
|
||||
"GO_TO_SETTINGS_INBOXES": "Vai alle impostazioni delle caselle",
|
||||
"GO_TO_SETTINGS_LABELS": "Vai alle impostazioni delle etichette",
|
||||
"GO_TO_SETTINGS_CANNED_RESPONSES": "Vai alle impostazioni delle risposte predefinite",
|
||||
"GO_TO_SETTINGS_APPLICATIONS": "Vai alle impostazioni dell'applicazione",
|
||||
"GO_TO_SETTINGS_ACCOUNT": "Vai alle impostazioni dell'account",
|
||||
"GO_TO_SETTINGS_PROFILE": "Vai alle impostazioni del profilo",
|
||||
"GO_TO_NOTIFICATIONS": "Vai alle notifiche",
|
||||
"ADD_LABELS_TO_CONVERSATION": "Aggiungi etichetta alla conversazione",
|
||||
"ASSIGN_AN_AGENT": "Assegna un agente",
|
||||
"ASSIGN_A_TEAM": "Assegna un team",
|
||||
"MUTE_CONVERSATION": "Silenzia conversazione",
|
||||
"UNMUTE_CONVERSATION": "Riattiva conversazione",
|
||||
"REMOVE_LABEL_FROM_CONVERSATION": "Rimuovi etichetta dalla conversazione",
|
||||
"REOPEN_CONVERSATION": "Riapri la conversazione",
|
||||
"RESOLVE_CONVERSATION": "Risolvi la conversazione",
|
||||
"SEND_TRANSCRIPT": "Send an email transcript",
|
||||
"SNOOZE_CONVERSATION": "Snooze Conversation",
|
||||
"UNTIL_NEXT_REPLY": "Until next reply",
|
||||
"UNTIL_NEXT_WEEK": "Until next week",
|
||||
"UNTIL_TOMORROW": "Until tomorrow"
|
||||
"SEND_TRANSCRIPT": "Invia una trascrizione email",
|
||||
"SNOOZE_CONVERSATION": "Posticipa conversazione",
|
||||
"UNTIL_NEXT_REPLY": "Fino alla prossima risposta",
|
||||
"UNTIL_NEXT_WEEK": "Fino alla prossima settimana",
|
||||
"UNTIL_TOMORROW": "Fino a domani"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@
|
|||
},
|
||||
"BANDWIDTH": {
|
||||
"ACCOUNT_ID": {
|
||||
"LABEL": "Account ID",
|
||||
"LABEL": "ID Account",
|
||||
"PLACEHOLDER": "Please enter your Bandwidth Account ID",
|
||||
"ERROR": "Questo campo è obbligatorio"
|
||||
},
|
||||
|
|
|
@ -22,11 +22,11 @@
|
|||
"is_not_present": "Não está presente",
|
||||
"is_greater_than": "É maior do que",
|
||||
"is_less_than": "É menor do que",
|
||||
"days_before": "Is x days before"
|
||||
"days_before": "É x dias antes"
|
||||
},
|
||||
"ATTRIBUTE_LABELS": {
|
||||
"TRUE": "True",
|
||||
"FALSE": "False"
|
||||
"TRUE": "Verdadeiro",
|
||||
"FALSE": "Falso"
|
||||
},
|
||||
"ATTRIBUTES": {
|
||||
"STATUS": "SItuação",
|
||||
|
@ -44,7 +44,7 @@
|
|||
"CUSTOM_ATTRIBUTE_NUMBER": "Número",
|
||||
"CUSTOM_ATTRIBUTE_LINK": "Endereço",
|
||||
"CUSTOM_ATTRIBUTE_CHECKBOX": "Caixa de Seleção",
|
||||
"CREATED_AT": "Created At",
|
||||
"CREATED_AT": "Criado Em",
|
||||
"LAST_ACTIVITY": "Última atividade"
|
||||
},
|
||||
"GROUPS": {
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"PLACEHOLDER": "Por favor, selecione um tipo",
|
||||
"ERROR": "Tipo é obrigatório",
|
||||
"LIST": {
|
||||
"LABEL": "List Values",
|
||||
"LABEL": "Listar Valores",
|
||||
"PLACEHOLDER": "Por favor insira um valor e pressione Enter",
|
||||
"ERROR": "Deve possuir pelo menos um valor"
|
||||
}
|
||||
|
@ -42,7 +42,7 @@
|
|||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Custom Attribute added successfully",
|
||||
"SUCCESS_MESSAGE": "Atributo Personalizado adicionado com sucesso",
|
||||
"ERROR_MESSAGE": "Could not able to create a custom attribute, Please try again later"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
{
|
||||
"AUTOMATION": {
|
||||
"HEADER": "Automations",
|
||||
"HEADER_BTN_TXT": "Add Automation Rule",
|
||||
"HEADER": "Automatizações",
|
||||
"HEADER_BTN_TXT": "Adicionar Regra de Automação",
|
||||
"LOADING": "Fetching automation rules",
|
||||
"SIDEBAR_TXT": "<p><b>Automation Rules</b> <p>Automation can replace and automate existing processes that require manual effort. You can do many things with automation, including adding labels and assigning conversation to the best agent. So the team focuses on what they do best and spends more little time on manual tasks.</p>",
|
||||
"ADD": {
|
||||
"TITLE": "Add Automation Rule",
|
||||
"TITLE": "Adicionar Regra de Automação",
|
||||
"SUBMIT": "Criar",
|
||||
"CANCEL_BUTTON_TEXT": "cancelar",
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Rule Name",
|
||||
"PLACEHOLDER": "Enter rule name",
|
||||
"LABEL": "Nome da Regra",
|
||||
"PLACEHOLDER": "Insira nome da regra",
|
||||
"ERROR": "Nome é obrigatório"
|
||||
},
|
||||
"DESC": {
|
||||
"LABEL": "Descrição",
|
||||
"PLACEHOLDER": "Enter rule description",
|
||||
"PLACEHOLDER": "Insera descrição da regra",
|
||||
"ERROR": "Descrição é obrigatória"
|
||||
},
|
||||
"EVENT": {
|
||||
"LABEL": "Event",
|
||||
"LABEL": "Evento",
|
||||
"PLACEHOLDER": "Por favor selecione um",
|
||||
"ERROR": "Event is required"
|
||||
"ERROR": "Evento é necessário"
|
||||
},
|
||||
"CONDITIONS": {
|
||||
"LABEL": "Conditions"
|
||||
"LABEL": "Condições"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"LABEL": "Ações."
|
||||
}
|
||||
},
|
||||
"CONDITION_BUTTON_LABEL": "Add Condition",
|
||||
"ACTION_BUTTON_LABEL": "Add Action",
|
||||
"CONDITION_BUTTON_LABEL": "Adicionar Condição",
|
||||
"ACTION_BUTTON_LABEL": "Adicionar Ação",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automation rule added successfully",
|
||||
"ERROR_MESSAGE": "Could not able to create a automation rule, Please try again later"
|
||||
|
@ -43,12 +43,12 @@
|
|||
"Nome:",
|
||||
"Descrição",
|
||||
"Ativa",
|
||||
"Created on"
|
||||
"Criado em"
|
||||
],
|
||||
"404": "No automation rules found"
|
||||
"404": "Nenhuma regra de automação encontrada"
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE": "Delete Automation Rule",
|
||||
"TITLE": "Apagar Regra de Automação",
|
||||
"SUBMIT": "excluir",
|
||||
"CANCEL_BUTTON_TEXT": "cancelar",
|
||||
"CONFIRM": {
|
||||
|
@ -63,7 +63,7 @@
|
|||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Edit Automation Rule",
|
||||
"TITLE": "Editar Regra de Automação",
|
||||
"SUBMIT": "Atualização",
|
||||
"CANCEL_BUTTON_TEXT": "cancelar",
|
||||
"API": {
|
||||
|
@ -74,8 +74,8 @@
|
|||
"CLONE": {
|
||||
"TOOLTIP": "Clone",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automation cloned successfully",
|
||||
"ERROR_MESSAGE": "Could not clone automation rule, Please try again later"
|
||||
"SUCCESS_MESSAGE": "Automação clonada com sucesso",
|
||||
"ERROR_MESSAGE": "Não foi possível clonar regra de automação, por favor, tente novamente mais tarde"
|
||||
}
|
||||
},
|
||||
"FORM": {
|
||||
|
@ -86,20 +86,20 @@
|
|||
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||
},
|
||||
"CONDITION": {
|
||||
"DELETE_MESSAGE": "You need to have atleast one condition to save"
|
||||
"DELETE_MESSAGE": "É necessário ter pelo menos uma condição para salvar"
|
||||
},
|
||||
"ACTION": {
|
||||
"DELETE_MESSAGE": "You need to have atleast one action to save"
|
||||
"DELETE_MESSAGE": "É necessário ter pelo menos uma ação para salvar"
|
||||
},
|
||||
"TOGGLE": {
|
||||
"ACTIVATION_TITLE": "Activate Automation Rule",
|
||||
"DEACTIVATION_TITLE": "Deactivate Automation Rule",
|
||||
"ACTIVATION_DESCRIPTION": "This action will activate the automation rule '{automationName}'. Are you sure you want to proceed?",
|
||||
"DEACTIVATION_DESCRIPTION": "This action will deactivate the automation rule '{automationName}'. Are you sure you want to proceed?",
|
||||
"ACTIVATION_SUCCESFUL": "Automation Rule Activated Successfully",
|
||||
"DEACTIVATION_SUCCESFUL": "Automation Rule Deactivated Successfully",
|
||||
"ACTIVATION_ERROR": "Could not Activate Automation, Please try again later",
|
||||
"DEACTIVATION_ERROR": "Could not Deactivate Automation, Please try again later",
|
||||
"ACTIVATION_TITLE": "Ativar Regra de Automação",
|
||||
"DEACTIVATION_TITLE": "Desativar Regra de Automação",
|
||||
"ACTIVATION_DESCRIPTION": "Esta ação irá ativar a regra de automação '{automationName}'. Tem a certeza que deseja continuar?",
|
||||
"DEACTIVATION_DESCRIPTION": "Esta ação irá desativar a regra de automação '{automationName}'. Tem a certeza que deseja continuar?",
|
||||
"ACTIVATION_SUCCESFUL": "Regra de Automação Ativada com Sucesso",
|
||||
"DEACTIVATION_SUCCESFUL": "Regra de Automação Desativada com Sucesso",
|
||||
"ACTIVATION_ERROR": "Não foi possível Ativar a Automação, por favor, tente novamente mais tarde",
|
||||
"DEACTIVATION_ERROR": "Não foi possível Desativar a Automação, por favor, tente novamente mais tarde",
|
||||
"CONFIRMATION_LABEL": "Sim",
|
||||
"CANCEL_LABEL": "Não"
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
"ERROR": "O tempo na página é obrigatório"
|
||||
},
|
||||
"ENABLED": "Ativar a campanha",
|
||||
"TRIGGER_ONLY_BUSINESS_HOURS": "Trigger only during business hours",
|
||||
"TRIGGER_ONLY_BUSINESS_HOURS": "Ativar apenas durante o horário de trabalho",
|
||||
"SUBMIT": "Adicionar Campanha"
|
||||
},
|
||||
"API": {
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"CONTACTS_FILTER": {
|
||||
"TITLE": "Filter Contacts",
|
||||
"SUBTITLE": "Add filters below and hit 'Submit' to filter contacts.",
|
||||
"TITLE": "Filtrar Contactos",
|
||||
"SUBTITLE": "Adicionar filtros abaixo e clicar 'Submeter' para filtrar contactos.",
|
||||
"ADD_NEW_FILTER": "Adicionar Filtro",
|
||||
"CLEAR_ALL_FILTERS": "Clear All Filters",
|
||||
"CLEAR_ALL_FILTERS": "Limpar Todos os Filtros",
|
||||
"FILTER_DELETE_ERROR": "Você deve ter pelo menos um filtro para guardar",
|
||||
"SUBMIT_BUTTON_LABEL": "submeter",
|
||||
"CANCEL_BUTTON_LABEL": "cancelar",
|
||||
"CLEAR_BUTTON_LABEL": "Limpar Filtros",
|
||||
"EMPTY_VALUE_ERROR": "Valor obrigatório",
|
||||
"TOOLTIP_LABEL": "Filter contacts",
|
||||
"TOOLTIP_LABEL": "Filtrar contactos",
|
||||
"QUERY_DROPDOWN_LABELS": {
|
||||
"AND": "E",
|
||||
"OR": "OU"
|
||||
|
@ -23,13 +23,13 @@
|
|||
"is_not_present": "Não está presente",
|
||||
"is_greater_than": "É maior do que",
|
||||
"is_lesser_than": "É menor do que",
|
||||
"days_before": "Is x days before"
|
||||
"days_before": "É x dias antes"
|
||||
},
|
||||
"ATTRIBUTES": {
|
||||
"NAME": "Nome:",
|
||||
"EMAIL": "e-mail",
|
||||
"PHONE_NUMBER": "Número de telefone",
|
||||
"IDENTIFIER": "Identifier",
|
||||
"IDENTIFIER": "Identificador",
|
||||
"CITY": "Cidade",
|
||||
"COUNTRY": "País",
|
||||
"CUSTOM_ATTRIBUTE_LIST": "Lista",
|
||||
|
@ -37,7 +37,7 @@
|
|||
"CUSTOM_ATTRIBUTE_NUMBER": "Número",
|
||||
"CUSTOM_ATTRIBUTE_LINK": "Endereço",
|
||||
"CUSTOM_ATTRIBUTE_CHECKBOX": "Caixa de Seleção",
|
||||
"CREATED_AT": "Created At",
|
||||
"CREATED_AT": "Criado Em",
|
||||
"LAST_ACTIVITY": "Última atividade"
|
||||
},
|
||||
"GROUPS": {
|
||||
|
|
|
@ -57,9 +57,9 @@
|
|||
}
|
||||
},
|
||||
"FOOTER": {
|
||||
"MESSAGE_SIGN_TOOLTIP": "Message signature",
|
||||
"ENABLE_SIGN_TOOLTIP": "Enable signature",
|
||||
"DISABLE_SIGN_TOOLTIP": "Disable signature",
|
||||
"MESSAGE_SIGN_TOOLTIP": "Assinatura da mensagem",
|
||||
"ENABLE_SIGN_TOOLTIP": "Habilitar assinatura",
|
||||
"DISABLE_SIGN_TOOLTIP": "Desativar assinatura",
|
||||
"MSG_INPUT": "Shift + enter para nova linha. Comece com '/' para selecionar uma Resposta Pronta.",
|
||||
"PRIVATE_MSG_INPUT": "Shift + Enter para a nova linha. Isto será visível apenas para Agentes",
|
||||
"MESSAGE_SIGNATURE_NOT_CONFIGURED": "Message signature is not configured, please configure it in profile settings.",
|
||||
|
@ -166,7 +166,7 @@
|
|||
},
|
||||
"ACCORDION": {
|
||||
"CONTACT_DETAILS": "Detalhes do Contacto",
|
||||
"CONVERSATION_ACTIONS": "Conversation Actions",
|
||||
"CONVERSATION_ACTIONS": "Ações de Conversa",
|
||||
"CONVERSATION_LABELS": "Etiquetas da conversa",
|
||||
"CONVERSATION_INFO": "Conversation Information",
|
||||
"CONTACT_ATTRIBUTES": "Atributos do Contato",
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
}
|
||||
},
|
||||
"UPDATE_CHATWOOT": "Está disponível uma nova atualização %{latestChatwootVersion} para o ChatWoot. Por favor, atualize a sua versão.",
|
||||
"LEARN_MORE": "Learn more"
|
||||
"LEARN_MORE": "Saiba mais"
|
||||
},
|
||||
"FORMS": {
|
||||
"MULTISELECT": {
|
||||
|
@ -79,7 +79,7 @@
|
|||
},
|
||||
"NETWORK": {
|
||||
"NOTIFICATION": {
|
||||
"TEXT": "Disconnected from Chatwoot"
|
||||
"TEXT": "Desconectado do Chatwoot"
|
||||
},
|
||||
"BUTTON": {
|
||||
"REFRESH": "Atualizar"
|
||||
|
@ -116,17 +116,17 @@
|
|||
"GO_TO_NOTIFICATIONS": "Ir para Notificações",
|
||||
"ADD_LABELS_TO_CONVERSATION": "Adicionar etiqueta à conversa",
|
||||
"ASSIGN_AN_AGENT": "Atribuir um agente",
|
||||
"ASSIGN_A_TEAM": "Assign a team",
|
||||
"ASSIGN_A_TEAM": "Atribuir uma equipa",
|
||||
"MUTE_CONVERSATION": "Silenciar Conversa",
|
||||
"UNMUTE_CONVERSATION": "Unmute conversation",
|
||||
"UNMUTE_CONVERSATION": "Reativar conversa",
|
||||
"REMOVE_LABEL_FROM_CONVERSATION": "Remover etiqueta da conversa",
|
||||
"REOPEN_CONVERSATION": "Reopen conversation",
|
||||
"RESOLVE_CONVERSATION": "Resolve conversation",
|
||||
"SEND_TRANSCRIPT": "Send an email transcript",
|
||||
"REOPEN_CONVERSATION": "Reabrir conversa",
|
||||
"RESOLVE_CONVERSATION": "Resolver conversa",
|
||||
"SEND_TRANSCRIPT": "Enviar transcrição por e-mail",
|
||||
"SNOOZE_CONVERSATION": "Snooze Conversation",
|
||||
"UNTIL_NEXT_REPLY": "Until next reply",
|
||||
"UNTIL_NEXT_WEEK": "Until next week",
|
||||
"UNTIL_TOMORROW": "Until tomorrow"
|
||||
"UNTIL_NEXT_REPLY": "Até à próxima resposta",
|
||||
"UNTIL_NEXT_WEEK": "Até à próxima semana",
|
||||
"UNTIL_TOMORROW": "Até amanhã"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
},
|
||||
"CHANNEL_WEBHOOK_URL": {
|
||||
"LABEL": "URL do Webhook",
|
||||
"PLACEHOLDER": "Enter your Webhook URL",
|
||||
"PLACEHOLDER": "Introduza o seu URL Webhook",
|
||||
"ERROR": "Por favor, insira uma URL válida"
|
||||
},
|
||||
"CHANNEL_DOMAIN": {
|
||||
|
@ -118,7 +118,7 @@
|
|||
},
|
||||
"CHANNEL_NAME": {
|
||||
"LABEL": "Nome Caixa de Entrada",
|
||||
"PLACEHOLDER": "Please enter a inbox name",
|
||||
"PLACEHOLDER": "Por favor, insira um nome para a caixa de entrada",
|
||||
"ERROR": "Este campo é obrigatório"
|
||||
},
|
||||
"PHONE_NUMBER": {
|
||||
|
@ -137,14 +137,14 @@
|
|||
},
|
||||
"SMS": {
|
||||
"TITLE": "Canal SMS",
|
||||
"DESC": "Start supporting your customers via SMS.",
|
||||
"DESC": "Comece a apoiar os seus clientes via SMS.",
|
||||
"PROVIDERS": {
|
||||
"LABEL": "API Provider",
|
||||
"TWILIO": "Twilio",
|
||||
"BANDWIDTH": "Banda"
|
||||
},
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "We were not able to save the SMS channel"
|
||||
"ERROR_MESSAGE": "Não conseguimos salvar o canal de SMS"
|
||||
},
|
||||
"BANDWIDTH": {
|
||||
"ACCOUNT_ID": {
|
||||
|
@ -469,7 +469,7 @@
|
|||
"TITLE": "IMAP",
|
||||
"SUBTITLE": "Defina os seus dados IMAP",
|
||||
"UPDATE": "Atualizar configurações IMAP",
|
||||
"TOGGLE_AVAILABILITY": "Enable IMAP configuration for this inbox",
|
||||
"TOGGLE_AVAILABILITY": "Ativar a configuração IMAP para esta caixa de entrada",
|
||||
"TOGGLE_HELP": "Enabling IMAP will help the user to recieve email",
|
||||
"EDIT": {
|
||||
"SUCCESS_MESSAGE": "IMAP settings updated successfully",
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
},
|
||||
"LIST": {
|
||||
"FETCHING": "Procurando Hooks de integração",
|
||||
"INBOX": "Recebidas",
|
||||
"INBOX": "Caixa de Entrada",
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "excluir"
|
||||
}
|
||||
|
|
|
@ -240,7 +240,7 @@
|
|||
"HEADER": "Visão Geral da Caixa de Entrada",
|
||||
"LOADING_CHART": "Carregando dados da carta...",
|
||||
"NO_ENOUGH_DATA": "Não recebemos pontos de dados suficientes para gerar o relatório. Por favor, tente novamente mais tarde.",
|
||||
"DOWNLOAD_INBOX_REPORTS": "Download inbox reports",
|
||||
"DOWNLOAD_INBOX_REPORTS": "Descarregar relatórios de caixa de entrada",
|
||||
"FILTER_DROPDOWN_LABEL": "Escolher caixa de entrada",
|
||||
"METRICS": {
|
||||
"CONVERSATIONS": {
|
||||
|
@ -300,11 +300,11 @@
|
|||
}
|
||||
},
|
||||
"TEAM_REPORTS": {
|
||||
"HEADER": "Team Overview",
|
||||
"HEADER": "Resumo de Equipa",
|
||||
"LOADING_CHART": "Carregando dados da carta...",
|
||||
"NO_ENOUGH_DATA": "Não recebemos pontos de dados suficientes para gerar o relatório. Por favor, tente novamente mais tarde.",
|
||||
"DOWNLOAD_TEAM_REPORTS": "Download team reports",
|
||||
"FILTER_DROPDOWN_LABEL": "Select Team",
|
||||
"DOWNLOAD_TEAM_REPORTS": "Descarregar relatórios de equipa",
|
||||
"FILTER_DROPDOWN_LABEL": "Escolher Equipa",
|
||||
"METRICS": {
|
||||
"CONVERSATIONS": {
|
||||
"NAME": "Conversas",
|
||||
|
@ -367,7 +367,7 @@
|
|||
"NO_RECORDS": "Sem dados CSAT disponíveis para reposta.",
|
||||
"FILTERS": {
|
||||
"AGENTS": {
|
||||
"PLACEHOLDER": "Choose Agents"
|
||||
"PLACEHOLDER": "Escolher Agentes"
|
||||
}
|
||||
},
|
||||
"TABLE": {
|
||||
|
|
|
@ -101,21 +101,21 @@
|
|||
"PLACEHOLDER": "Por favor, digite a password atual"
|
||||
},
|
||||
"PASSWORD": {
|
||||
"LABEL": "New password",
|
||||
"LABEL": "Nova senha",
|
||||
"ERROR": "Por favor, digite uma senha de comprimento 6 ou mais",
|
||||
"PLACEHOLDER": "Por favor, digite uma nova senha"
|
||||
},
|
||||
"PASSWORD_CONFIRMATION": {
|
||||
"LABEL": "Confirme a nova senha",
|
||||
"ERROR": "Confirme a senha deve corresponder à senha",
|
||||
"PLACEHOLDER": "Please re-enter your new password"
|
||||
"PLACEHOLDER": "Por favor, digite novamente a sua senha nova"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SIDEBAR_ITEMS": {
|
||||
"CHANGE_AVAILABILITY_STATUS": "Trocar",
|
||||
"CHANGE_ACCOUNTS": "Trocar de conta",
|
||||
"CONTACT_SUPPORT": "Contact Support",
|
||||
"CONTACT_SUPPORT": "Contactar Suporte",
|
||||
"SELECTOR_SUBTITLE": "Escolha uma conta da lista a seguir",
|
||||
"PROFILE_SETTINGS": "Configurações do perfil",
|
||||
"KEYBOARD_SHORTCUTS": "Atalhos do teclado",
|
||||
|
@ -124,7 +124,7 @@
|
|||
"APP_GLOBAL": {
|
||||
"TRIAL_MESSAGE": "dias de teste restantes.",
|
||||
"TRAIL_BUTTON": "Comprar agora",
|
||||
"DELETED_USER": "Deleted User"
|
||||
"DELETED_USER": "Usuário Excluído"
|
||||
},
|
||||
"COMPONENTS": {
|
||||
"CODE": {
|
||||
|
@ -164,15 +164,15 @@
|
|||
"APPLICATIONS": "Aplicações",
|
||||
"LABELS": "Etiquetas",
|
||||
"CUSTOM_ATTRIBUTES": "Atributos personalizados",
|
||||
"AUTOMATION": "Automation",
|
||||
"AUTOMATION": "Automatização",
|
||||
"TEAMS": "Equipas",
|
||||
"CUSTOM_VIEWS_FOLDER": "Folders",
|
||||
"CUSTOM_VIEWS_SEGMENTS": "Segments",
|
||||
"CUSTOM_VIEWS_FOLDER": "Pastas",
|
||||
"CUSTOM_VIEWS_SEGMENTS": "Segmentos",
|
||||
"ALL_CONTACTS": "Todos os contatos",
|
||||
"TAGGED_WITH": "Etiquetada com",
|
||||
"NEW_LABEL": "New label",
|
||||
"NEW_TEAM": "New team",
|
||||
"NEW_INBOX": "New inbox",
|
||||
"NEW_LABEL": "Nova etiqueta",
|
||||
"NEW_TEAM": "Nova equipa",
|
||||
"NEW_INBOX": "Nova caixa de entrada",
|
||||
"REPORTS_OVERVIEW": "Visão geral",
|
||||
"CSAT": "CSAT",
|
||||
"CAMPAIGNS": "Campanhas",
|
||||
|
@ -181,8 +181,8 @@
|
|||
"REPORTS_AGENT": "Agentes",
|
||||
"REPORTS_LABEL": "Etiquetas",
|
||||
"REPORTS_INBOX": "Caixa de Entrada",
|
||||
"REPORTS_TEAM": "Team",
|
||||
"SET_AVAILABILITY_TITLE": "Set yourself as",
|
||||
"REPORTS_TEAM": "Equipa",
|
||||
"SET_AVAILABILITY_TITLE": "Defina-se como",
|
||||
"BETA": "Beta"
|
||||
},
|
||||
"CREATE_ACCOUNT": {
|
||||
|
@ -215,7 +215,7 @@
|
|||
"GO_TO_REPORTS_SIDEBAR": "Ir para barra lateral de Relatórios",
|
||||
"MOVE_TO_NEXT_TAB": "Mover para próximo separador da lista de conversas",
|
||||
"GO_TO_SETTINGS": "Ir para as configurações",
|
||||
"SWITCH_CONVERSATION_STATUS": "Switch to the next conversation status",
|
||||
"SWITCH_CONVERSATION_STATUS": "Mudar para o próximo estado de conversa",
|
||||
"SWITCH_TO_PRIVATE_NOTE": "Alterar para nota privada",
|
||||
"TOGGLE_RICH_CONTENT_EDITOR": "Ativar/desativar editor de conteúdo",
|
||||
"SWITCH_TO_REPLY": "Mudar para resposta",
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<sidebar
|
||||
:route="currentRoute"
|
||||
:class="sidebarClassName"
|
||||
@open-notification-panel="openNotificationPanel"
|
||||
@toggle-account-modal="toggleAccountModal"
|
||||
@open-key-shortcut-modal="toggleKeyShortcutModal"
|
||||
@close-key-shortcut-modal="closeKeyShortcutModal"
|
||||
|
@ -25,6 +26,10 @@
|
|||
@close="closeKeyShortcutModal"
|
||||
@clickaway="closeKeyShortcutModal"
|
||||
/>
|
||||
<notification-panel
|
||||
v-if="isNotificationPanel"
|
||||
@close="closeNotificationPanel"
|
||||
/>
|
||||
<woot-modal :show.sync="showAddLabelModal" :on-close="hideAddLabelPopup">
|
||||
<add-label-modal @close="hideAddLabelPopup" />
|
||||
</woot-modal>
|
||||
|
@ -40,6 +45,7 @@ import WootKeyShortcutModal from 'dashboard/components/widgets/modal/WootKeyShor
|
|||
import AddAccountModal from 'dashboard/components/layout/sidebarComponents/AddAccountModal';
|
||||
import AccountSelector from 'dashboard/components/layout/sidebarComponents/AccountSelector';
|
||||
import AddLabelModal from 'dashboard/routes/dashboard/settings/labels/AddLabel.vue';
|
||||
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -49,6 +55,7 @@ export default {
|
|||
AddAccountModal,
|
||||
AccountSelector,
|
||||
AddLabelModal,
|
||||
NotificationPanel,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -58,6 +65,7 @@ export default {
|
|||
showCreateAccountModal: false,
|
||||
showAddLabelModal: false,
|
||||
showShortcutModal: false,
|
||||
isNotificationPanel: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -126,6 +134,12 @@ export default {
|
|||
hideAddLabelPopup() {
|
||||
this.showAddLabelModal = false;
|
||||
},
|
||||
openNotificationPanel() {
|
||||
this.isNotificationPanel = true;
|
||||
},
|
||||
closeNotificationPanel() {
|
||||
this.isNotificationPanel = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -80,7 +80,11 @@ export default {
|
|||
return false;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
conversationId() {
|
||||
this.fetchConversationIfUnavailable();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('agents/get');
|
||||
this.initialize();
|
||||
|
|
|
@ -59,7 +59,29 @@
|
|||
</div>
|
||||
<div class="row">
|
||||
<div class="columns">
|
||||
<label :class="{ error: $v.message.$error }">
|
||||
<div v-if="isAnEmailInbox || isAnWebWidgetInbox">
|
||||
<label>
|
||||
{{ $t('NEW_CONVERSATION.FORM.MESSAGE.LABEL') }}
|
||||
<reply-email-head
|
||||
v-if="isAnEmailInbox"
|
||||
:cc-emails.sync="ccEmails"
|
||||
:bcc-emails.sync="bccEmails"
|
||||
/>
|
||||
<label class="editor-wrap">
|
||||
<woot-message-editor
|
||||
v-model="message"
|
||||
class="message-editor"
|
||||
:class="{ editor_warning: $v.message.$error }"
|
||||
:placeholder="$t('NEW_CONVERSATION.FORM.MESSAGE.PLACEHOLDER')"
|
||||
@blur="$v.message.$touch"
|
||||
/>
|
||||
<span v-if="$v.message.$error" class="editor-warning__message">
|
||||
{{ $t('NEW_CONVERSATION.FORM.MESSAGE.ERROR') }}
|
||||
</span>
|
||||
</label>
|
||||
</label>
|
||||
</div>
|
||||
<label v-else :class="{ error: $v.message.$error }">
|
||||
{{ $t('NEW_CONVERSATION.FORM.MESSAGE.LABEL') }}
|
||||
<textarea
|
||||
v-model="message"
|
||||
|
@ -89,6 +111,8 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
|
||||
import ReplyEmailHead from 'dashboard/components/widgets/conversation/ReplyEmailHead';
|
||||
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
|
||||
|
@ -98,6 +122,8 @@ import { required, requiredIf } from 'vuelidate/lib/validators';
|
|||
export default {
|
||||
components: {
|
||||
Thumbnail,
|
||||
WootMessageEditor,
|
||||
ReplyEmailHead,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
|
@ -116,6 +142,8 @@ export default {
|
|||
subject: '',
|
||||
message: '',
|
||||
selectedInbox: '',
|
||||
bccEmails: '',
|
||||
ccEmails: '',
|
||||
};
|
||||
},
|
||||
validations: {
|
||||
|
@ -136,7 +164,7 @@ export default {
|
|||
currentUser: 'getCurrentUser',
|
||||
}),
|
||||
getNewConversation() {
|
||||
return {
|
||||
const payload = {
|
||||
inboxId: this.targetInbox.inbox.id,
|
||||
sourceId: this.targetInbox.source_id,
|
||||
contactId: this.contact.id,
|
||||
|
@ -144,6 +172,14 @@ export default {
|
|||
mailSubject: this.subject,
|
||||
assigneeId: this.currentUser.id,
|
||||
};
|
||||
if (this.ccEmails) {
|
||||
payload.message.cc_emails = this.ccEmails;
|
||||
}
|
||||
|
||||
if (this.bccEmails) {
|
||||
payload.message.bcc_emails = this.bccEmails;
|
||||
}
|
||||
return payload;
|
||||
},
|
||||
targetInbox: {
|
||||
get() {
|
||||
|
@ -168,6 +204,12 @@ export default {
|
|||
this.selectedInbox.inbox.channel_type === INBOX_TYPES.EMAIL
|
||||
);
|
||||
},
|
||||
isAnWebWidgetInbox() {
|
||||
return (
|
||||
this.selectedInbox &&
|
||||
this.selectedInbox.inbox.channel_type === INBOX_TYPES.WEB
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onCancel() {
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
<template>
|
||||
<div class="modal-mask">
|
||||
<div
|
||||
v-on-clickaway="closeNotificationPanel"
|
||||
class="notification-wrap flex-space-between"
|
||||
>
|
||||
<div class="header-wrap w-full flex-space-between">
|
||||
<div class="header-title--wrap flex-view">
|
||||
<span class="header-title">
|
||||
{{ $t('NOTIFICATIONS_PAGE.UNREAD_NOTIFICATION.TITLE') }}
|
||||
</span>
|
||||
<span v-if="totalUnreadNotifications" class="total-count block-title">
|
||||
{{ totalUnreadNotifications }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex-view">
|
||||
<woot-button
|
||||
v-if="!noUnreadNotificationAvailable"
|
||||
color-scheme="primary"
|
||||
variant="smooth"
|
||||
size="tiny"
|
||||
class-names="action-button"
|
||||
:is-loading="uiFlags.isUpdating"
|
||||
@click="onMarkAllDoneClick"
|
||||
>
|
||||
{{ $t('NOTIFICATIONS_PAGE.MARK_ALL_DONE') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
color-scheme="secondary"
|
||||
variant="link"
|
||||
size="tiny"
|
||||
icon="dismiss"
|
||||
@click="closeNotificationPanel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<notification-panel-list
|
||||
:notifications="getUnreadNotifications"
|
||||
:is-loading="uiFlags.isFetching"
|
||||
:on-click-notification="openConversation"
|
||||
:in-last-page="inLastPage"
|
||||
/>
|
||||
<div v-if="records.length !== 0" class="footer-wrap flex-space-between">
|
||||
<div class="flex-view">
|
||||
<woot-button
|
||||
size="medium"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
class-names="page-change--button"
|
||||
:is-disabled="inFirstPage"
|
||||
@click="onClickFirstPage"
|
||||
>
|
||||
<fluent-icon icon="chevron-left" size="16" />
|
||||
<fluent-icon
|
||||
icon="chevron-left"
|
||||
size="16"
|
||||
class="margin-left-minus-slab"
|
||||
/>
|
||||
</woot-button>
|
||||
<woot-button
|
||||
color-scheme="secondary"
|
||||
variant="clear"
|
||||
size="medium"
|
||||
icon="chevron-left"
|
||||
:disabled="inFirstPage"
|
||||
@click="onClickPreviousPage"
|
||||
>
|
||||
</woot-button>
|
||||
</div>
|
||||
<span class="page-count"> {{ currentPage }} - {{ lastPage }} </span>
|
||||
<div class="flex-view">
|
||||
<woot-button
|
||||
color-scheme="secondary"
|
||||
variant="clear"
|
||||
size="medium"
|
||||
icon="chevron-right"
|
||||
:disabled="inLastPage"
|
||||
@click="onClickNextPage"
|
||||
>
|
||||
</woot-button>
|
||||
<woot-button
|
||||
size="medium"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
class-names="page-change--button"
|
||||
:disabled="inLastPage"
|
||||
@click="onClickLastPage"
|
||||
>
|
||||
<fluent-icon icon="chevron-right" size="16" />
|
||||
<fluent-icon
|
||||
icon="chevron-right"
|
||||
size="16"
|
||||
class="margin-left-minus-slab"
|
||||
/>
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
import NotificationPanelList from './NotificationPanelList';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
NotificationPanelList,
|
||||
},
|
||||
mixins: [clickaway],
|
||||
data() {
|
||||
return {
|
||||
pageSize: 15,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
meta: 'notifications/getMeta',
|
||||
records: 'notifications/getNotifications',
|
||||
uiFlags: 'notifications/getUIFlags',
|
||||
}),
|
||||
totalUnreadNotifications() {
|
||||
return this.meta.unreadCount;
|
||||
},
|
||||
noUnreadNotificationAvailable() {
|
||||
return this.meta.unreadCount === 0;
|
||||
},
|
||||
getUnreadNotifications() {
|
||||
return this.records.filter(notification => notification.read_at === null);
|
||||
},
|
||||
currentPage() {
|
||||
return Number(this.meta.currentPage);
|
||||
},
|
||||
lastPage() {
|
||||
if (this.totalUnreadNotifications > 15) {
|
||||
return Math.ceil(this.totalUnreadNotifications / this.pageSize);
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
inFirstPage() {
|
||||
const page = Number(this.meta.currentPage);
|
||||
return page === 1;
|
||||
},
|
||||
inLastPage() {
|
||||
return this.currentPage === this.lastPage;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('notifications/get', { page: 1 });
|
||||
},
|
||||
methods: {
|
||||
onPageChange(page) {
|
||||
this.$store.dispatch('notifications/get', { page });
|
||||
},
|
||||
openConversation(notification) {
|
||||
const {
|
||||
primary_actor_id: primaryActorId,
|
||||
primary_actor_type: primaryActorType,
|
||||
primary_actor: { id: conversationId },
|
||||
} = notification;
|
||||
|
||||
this.$store.dispatch('notifications/read', {
|
||||
primaryActorId,
|
||||
primaryActorType,
|
||||
unreadCount: this.meta.unreadCount,
|
||||
});
|
||||
this.$router.push({
|
||||
name: 'inbox_conversation',
|
||||
params: { conversation_id: conversationId },
|
||||
});
|
||||
this.$emit('close');
|
||||
},
|
||||
onClickNextPage() {
|
||||
if (!this.inLastPage) {
|
||||
const page = this.currentPage + 1;
|
||||
this.onPageChange(page);
|
||||
}
|
||||
},
|
||||
onClickPreviousPage() {
|
||||
if (!this.inFirstPage) {
|
||||
const page = this.currentPage - 1;
|
||||
this.onPageChange(page);
|
||||
}
|
||||
},
|
||||
onClickFirstPage() {
|
||||
if (!this.inFirstPage) {
|
||||
const page = 1;
|
||||
this.onPageChange(page);
|
||||
}
|
||||
},
|
||||
onClickLastPage() {
|
||||
if (!this.inLastPage) {
|
||||
const page = this.lastPage;
|
||||
this.onPageChange(page);
|
||||
}
|
||||
},
|
||||
onMarkAllDoneClick() {
|
||||
this.$store.dispatch('notifications/readAll');
|
||||
},
|
||||
closeNotificationPanel() {
|
||||
this.$emit('close');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.flex-view {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-space-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.notification-wrap {
|
||||
flex-direction: column;
|
||||
height: 90vh;
|
||||
width: 52rem;
|
||||
background-color: var(--white);
|
||||
border-radius: var(--border-radius-medium);
|
||||
position: absolute;
|
||||
left: var(--space-jumbo);
|
||||
margin: var(--space-small);
|
||||
}
|
||||
.header-wrap {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--s-50);
|
||||
padding: var(--space-two) var(--space-medium) var(--space-slab)
|
||||
var(--space-medium);
|
||||
|
||||
.header-title--wrap {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: var(--font-size-two);
|
||||
font-weight: var(--font-weight-black);
|
||||
}
|
||||
|
||||
.total-count {
|
||||
padding: var(--space-smaller) var(--space-small);
|
||||
background: var(--b-50);
|
||||
border-radius: var(--border-radius-rounded);
|
||||
font-size: var(--font-size-micro);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.action-button {
|
||||
padding: var(--space-micro) var(--space-small);
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
}
|
||||
|
||||
.page-count {
|
||||
font-size: var(--font-size-micro);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--s-500);
|
||||
}
|
||||
|
||||
.footer-wrap {
|
||||
align-items: center;
|
||||
padding: var(--space-smaller) var(--space-two);
|
||||
}
|
||||
|
||||
.page-change--button:hover {
|
||||
background: var(--s-50);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,219 @@
|
|||
<template>
|
||||
<div class="notification-list-item--wrap h-full flex-view ">
|
||||
<woot-button
|
||||
v-for="notificationItem in notifications"
|
||||
v-show="!isLoading"
|
||||
:key="notificationItem.id"
|
||||
size="expanded"
|
||||
color-scheme="secondary"
|
||||
variant="link"
|
||||
@click="() => onClickNotification(notificationItem)"
|
||||
>
|
||||
<div class="notification-list--wrap flex-view w-full">
|
||||
<div
|
||||
v-if="!notificationItem.read_at"
|
||||
class="notification-unread--indicator"
|
||||
></div>
|
||||
<div v-else class="empty flex-view"></div>
|
||||
<div class="notification-content--wrap w-full flex-space-between">
|
||||
<div class="flex-space-between">
|
||||
<div class="title-wrap flex-view ">
|
||||
<span class="notification-title">
|
||||
{{
|
||||
`#${
|
||||
notificationItem.primary_actor
|
||||
? notificationItem.primary_actor.id
|
||||
: $t(`NOTIFICATIONS_PAGE.DELETE_TITLE`)
|
||||
}`
|
||||
}}
|
||||
</span>
|
||||
<span class="notification-type">
|
||||
{{
|
||||
$t(
|
||||
`NOTIFICATIONS_PAGE.TYPE_LABEL.${notificationItem.notification_type}`
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<thumbnail
|
||||
v-if="notificationItem.primary_actor.meta.assignee"
|
||||
:src="notificationItem.primary_actor.meta.assignee.thumbnail"
|
||||
size="16px"
|
||||
:username="notificationItem.primary_actor.meta.assignee.name"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex-view ">
|
||||
<span class="notification-message text-truncate">
|
||||
{{ notificationItem.push_message_title }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="timestamp flex-view">
|
||||
{{ dynamicTime(notificationItem.created_at) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</woot-button>
|
||||
<empty-state
|
||||
v-if="showEmptyResult"
|
||||
:title="$t('NOTIFICATIONS_PAGE.UNREAD_NOTIFICATION.EMPTY_MESSAGE')"
|
||||
/>
|
||||
<woot-button
|
||||
v-if="!isLoading && inLastPage"
|
||||
size="medium"
|
||||
variant="clear"
|
||||
color-scheme="primary"
|
||||
class-names="action-button"
|
||||
@click="openNotificationPage"
|
||||
>
|
||||
{{ $t('NOTIFICATIONS_PAGE.UNREAD_NOTIFICATION.ALL_NOTIFICATIONS') }}
|
||||
</woot-button>
|
||||
<div v-if="isLoading" class="notifications-loader flex-view">
|
||||
<spinner />
|
||||
<span>{{
|
||||
$t('NOTIFICATIONS_PAGE.UNREAD_NOTIFICATION.LOADING_UNREAD_MESSAGE')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
||||
import timeMixin from '../../../../mixins/time';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Thumbnail,
|
||||
Spinner,
|
||||
EmptyState,
|
||||
},
|
||||
mixins: [timeMixin],
|
||||
props: {
|
||||
notifications: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
onClickNotification: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
inLastPage: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
notificationMetadata: 'notifications/getMeta',
|
||||
}),
|
||||
showEmptyResult() {
|
||||
return !this.isLoading && this.notifications.length === 0;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openNotificationPage() {
|
||||
if (this.$route.name !== 'notifications_index') {
|
||||
this.$router.push({
|
||||
name: 'notifications_index',
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.flex-view {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-space-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.notification-list-item--wrap {
|
||||
flex-direction: column;
|
||||
padding: var(--space-small) var(--space-slab);
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.empty {
|
||||
width: var(--space-small);
|
||||
}
|
||||
|
||||
.notification-list--wrap {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: var(--space-slab);
|
||||
line-height: 1.4;
|
||||
border-bottom: 1px solid var(--b-50);
|
||||
}
|
||||
|
||||
.notification-list--wrap:hover {
|
||||
background: var(--b-100);
|
||||
border-radius: var(--border-radius-normal);
|
||||
}
|
||||
|
||||
.notification-content--wrap {
|
||||
flex-direction: column;
|
||||
margin-left: var(--space-slab);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.title-wrap {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
font-weight: var(--font-weight-black);
|
||||
}
|
||||
|
||||
.notification-type {
|
||||
font-size: var(--font-size-micro);
|
||||
padding: var(--space-micro) var(--space-smaller);
|
||||
margin-left: var(--space-small);
|
||||
background: var(--s-50);
|
||||
border-radius: var(--border-radius-normal);
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
color: var(--color-body);
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
margin-top: var(--space-smaller);
|
||||
color: var(--b-500);
|
||||
font-size: var(--font-size-micro);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.notification-unread--indicator {
|
||||
width: var(--space-small);
|
||||
height: var(--space-small);
|
||||
border-radius: var(--border-radius-rounded);
|
||||
background: var(--color-woot);
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin-top: var(--space-slab);
|
||||
}
|
||||
|
||||
.notifications-loader {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: var(--space-larger) var(--space-small);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
</style>
|
|
@ -17,17 +17,17 @@
|
|||
@click="() => onClickNotification(notificationItem)"
|
||||
>
|
||||
<td>
|
||||
<div class="">
|
||||
<div class="flex-view notification-contant--wrap">
|
||||
<h5 class="notification--title">
|
||||
{{
|
||||
`#${
|
||||
notificationItem.primary_actor
|
||||
? notificationItem.primary_actor.id
|
||||
: 'deleted'
|
||||
: $t(`NOTIFICATIONS_PAGE.DELETE_TITLE`)
|
||||
}`
|
||||
}}
|
||||
</h5>
|
||||
<span class="notification--message-title">
|
||||
<span class="notification--message-title text-truncate">
|
||||
{{ notificationItem.push_message_title }}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -197,6 +197,11 @@ export default {
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
.notification-contant--wrap {
|
||||
flex-direction: column;
|
||||
max-width: 50rem;
|
||||
}
|
||||
|
||||
.notification--message-title {
|
||||
color: var(--s-700);
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ export const AUTOMATIONS = {
|
|||
// {
|
||||
// key: 'send_email_to_team',
|
||||
// name: 'Send an email to team',
|
||||
// attributeI18nKey: 'SEND_MESSAGE',
|
||||
// attributeI18nKey: 'SEND_EMAIL_TO_TEAM',
|
||||
// },
|
||||
],
|
||||
},
|
||||
|
@ -186,7 +186,7 @@ export const AUTOMATIONS = {
|
|||
// {
|
||||
// key: 'send_email_to_team',
|
||||
// name: 'Send an email to team',
|
||||
// attributeI18nKey: 'SEND_MESSAGE',
|
||||
// attributeI18nKey: 'SEND_EMAIL_TO_TEAM',
|
||||
// },
|
||||
{
|
||||
key: 'assign_agent',
|
||||
|
|
|
@ -66,9 +66,6 @@ export const actions = {
|
|||
id: contactId,
|
||||
data: response.data.payload,
|
||||
});
|
||||
commit(types.default.SET_ALL_CONVERSATION, response.data.payload, {
|
||||
root: true,
|
||||
});
|
||||
commit(types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, {
|
||||
isFetching: false,
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ import getters, { getSelectedChatConversation } from './getters';
|
|||
import actions from './actions';
|
||||
import { findPendingMessageIndex } from './helpers';
|
||||
import wootConstants from '../../../constants';
|
||||
import { BUS_EVENTS } from '../../../../shared/constants/busEvents';
|
||||
|
||||
const state = {
|
||||
allConversations: [],
|
||||
|
@ -109,7 +110,7 @@ export const mutations = {
|
|||
chat.messages.push(message);
|
||||
chat.timestamp = message.created_at;
|
||||
if (selectedChatId === conversationId) {
|
||||
window.bus.$emit('scrollToMessage');
|
||||
window.bus.$emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -131,7 +132,7 @@ export const mutations = {
|
|||
};
|
||||
Vue.set(allConversations, currentConversationIndex, currentConversation);
|
||||
if (_state.selectedChatId === conversation.id) {
|
||||
window.bus.$emit('scrollToMessage');
|
||||
window.bus.$emit(BUS_EVENTS.SCROLL_TO_MESSAGE);
|
||||
}
|
||||
} else {
|
||||
_state.allConversations.push(conversation);
|
||||
|
|
|
@ -19,7 +19,6 @@ describe('#actions', () => {
|
|||
types.default.SET_CONTACT_CONVERSATIONS,
|
||||
{ id: 1, data: conversationList },
|
||||
],
|
||||
[types.default.SET_ALL_CONVERSATION, conversationList, { root: true }],
|
||||
[
|
||||
types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG,
|
||||
{ isFetching: false },
|
||||
|
|
|
@ -120,7 +120,7 @@ describe('#mutations', () => {
|
|||
timestamp: 1602256198,
|
||||
},
|
||||
]);
|
||||
expect(global.bus.$emit).toHaveBeenCalledWith('scrollToMessage');
|
||||
expect(global.bus.$emit).toHaveBeenCalledWith('SCROLL_TO_MESSAGE');
|
||||
});
|
||||
|
||||
it('update message if it exist in the store', () => {
|
||||
|
|
|
@ -3,6 +3,7 @@ import Vuelidate from 'vuelidate';
|
|||
import VueI18n from 'vue-i18n';
|
||||
import App from '../survey/App.vue';
|
||||
import i18n from '../survey/i18n';
|
||||
import store from '../survey/store';
|
||||
|
||||
Vue.use(VueI18n);
|
||||
Vue.use(Vuelidate);
|
||||
|
@ -20,6 +21,7 @@ Vue.config.productionTip = false;
|
|||
window.onload = () => {
|
||||
window.WOOT_SURVEY = new Vue({
|
||||
i18n: i18nConfig,
|
||||
store,
|
||||
render: h => h(App),
|
||||
}).$mount('#app');
|
||||
};
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
<script>
|
||||
import globalConfigMixin from 'shared/mixins/globalConfigMixin';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
const {
|
||||
LOGO_THUMBNAIL: logoThumbnail,
|
||||
|
@ -41,13 +40,20 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ referrerHost: 'appConfig/getReferrerHost' }),
|
||||
brandRedirectURL() {
|
||||
const baseURL = `${this.globalConfig.widgetBrandURL}?utm_source=widget_branding`;
|
||||
if (this.referrerHost) {
|
||||
return `${baseURL}&utm_referrer=${this.referrerHost}`;
|
||||
try {
|
||||
const referrerHost = this.$store.getters['appConfig/getReferrerHost'];
|
||||
const baseURL = `${this.globalConfig.widgetBrandURL}?utm_source=${
|
||||
referrerHost ? 'widget_branding' : 'survey_branding'
|
||||
}`;
|
||||
if (referrerHost) {
|
||||
return `${baseURL}&utm_referrer=${referrerHost}`;
|
||||
}
|
||||
return baseURL;
|
||||
} catch (e) {
|
||||
// Suppressing the error as getter is not defined in some cases
|
||||
}
|
||||
return baseURL;
|
||||
return '';
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,5 +1,69 @@
|
|||
import { default as ar } from './locale/ar.json';
|
||||
import { default as ca } from './locale/ca.json';
|
||||
import { default as cs } from './locale/cs.json';
|
||||
import { default as da } from './locale/da.json';
|
||||
import { default as de } from './locale/de.json';
|
||||
import { default as el } from './locale/el.json';
|
||||
import { default as en } from './locale/en.json';
|
||||
import { default as es } from './locale/es.json';
|
||||
import { default as fa } from './locale/fa.json';
|
||||
import { default as fi } from './locale/fi.json';
|
||||
import { default as fr } from './locale/fr.json';
|
||||
import { default as hi } from './locale/hi.json';
|
||||
import { default as hu } from './locale/hu.json';
|
||||
import { default as id } from './locale/id.json';
|
||||
import { default as it } from './locale/it.json';
|
||||
import { default as ja } from './locale/ja.json';
|
||||
import { default as ko } from './locale/ko.json';
|
||||
import { default as ml } from './locale/ml.json';
|
||||
import { default as nl } from './locale/nl.json';
|
||||
import { default as no } from './locale/no.json';
|
||||
import { default as pl } from './locale/pl.json';
|
||||
import { default as pt } from './locale/pt.json';
|
||||
import { default as pt_BR } from './locale/pt_BR.json';
|
||||
import { default as ro } from './locale/ro.json';
|
||||
import { default as ru } from './locale/ru.json';
|
||||
import { default as sk } from './locale/sk.json';
|
||||
import { default as sv } from './locale/sv.json';
|
||||
import { default as ta } from './locale/ta.json';
|
||||
import { default as tr } from './locale/tr.json';
|
||||
import { default as uk } from './locale/uk.json';
|
||||
import { default as vi } from './locale/vi.json';
|
||||
import { default as zh_CN } from './locale/zh_CN.json';
|
||||
import { default as zh_TW } from './locale/zh_TW.json';
|
||||
|
||||
export default {
|
||||
ar,
|
||||
ca,
|
||||
cs,
|
||||
da,
|
||||
de,
|
||||
el,
|
||||
en,
|
||||
es,
|
||||
fa,
|
||||
fi,
|
||||
fr,
|
||||
hi,
|
||||
hu,
|
||||
id,
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
ml,
|
||||
nl,
|
||||
no,
|
||||
pl,
|
||||
pt,
|
||||
pt_BR,
|
||||
ro,
|
||||
ru,
|
||||
sk,
|
||||
sv,
|
||||
ta,
|
||||
tr,
|
||||
uk,
|
||||
vi,
|
||||
zh_CN,
|
||||
zh_TW,
|
||||
};
|
||||
|
|
11
app/javascript/survey/store/index.js
Normal file
11
app/javascript/survey/store/index.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import globalConfig from 'shared/store/globalConfig';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
globalConfig,
|
||||
},
|
||||
});
|
|
@ -144,6 +144,7 @@ export default {
|
|||
this.surveyDetails = result?.data?.csat_survey_response;
|
||||
this.selectedRating = this.surveyDetails?.rating;
|
||||
this.feedbackMessage = this.surveyDetails?.feedback_message || '';
|
||||
this.setLocale(result.data.locale);
|
||||
} catch (error) {
|
||||
const errorMessage = error?.response?.data?.message;
|
||||
this.errorMessage = errorMessage || this.$t('SURVEY.API.ERROR_MESSAGE');
|
||||
|
@ -179,6 +180,9 @@ export default {
|
|||
this.isUpdating = false;
|
||||
}
|
||||
},
|
||||
setLocale(locale) {
|
||||
this.$root.$i18n.locale = locale || 'en';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -51,7 +51,10 @@
|
|||
|
||||
.has-attachment {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
|
||||
:not([audio]) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.has-text {
|
||||
margin-top: $space-smaller;
|
||||
|
@ -213,11 +216,14 @@
|
|||
display: inline-block;
|
||||
font-size: $font-size-default;
|
||||
line-height: 1.5;
|
||||
max-width: 100%;
|
||||
padding: $space-slab $space-normal;
|
||||
text-align: left;
|
||||
word-break: break-word;
|
||||
|
||||
:not([audio]) {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
>a {
|
||||
color: $color-primary;
|
||||
word-break: break-all;
|
||||
|
|
|
@ -34,6 +34,9 @@
|
|||
:readable-time="readableTime"
|
||||
@error="onImageLoadError"
|
||||
/>
|
||||
<audio v-else-if="attachment.file_type === 'audio'" controls>
|
||||
<source :src="attachment.data_url" />
|
||||
</audio>
|
||||
<file-bubble v-else :url="attachment.data_url" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="flex overflow-hidden">
|
||||
<div class="flex">
|
||||
<span
|
||||
v-for="(user, index) in users"
|
||||
:key="user.id"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="px-5">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="text-black-700">
|
||||
<div class="text-black-700 max-w-xs">
|
||||
<div class="text-base leading-5 font-medium mb-1">
|
||||
{{
|
||||
isOnline
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"IN_A_DAY": "عادة نقوم بالرد خلال يوم واحد"
|
||||
},
|
||||
"START_CONVERSATION": "ابدأ المحادثة",
|
||||
"END_CONVERSATION": "End Conversation",
|
||||
"END_CONVERSATION": "إنهاء المحادثة",
|
||||
"CONTINUE_CONVERSATION": "متابعة المحادثة",
|
||||
"START_NEW_CONVERSATION": "ابدأ محادثة جديدة",
|
||||
"UNREAD_VIEW": {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"IN_A_DAY": "Normalmente respondemos num dia"
|
||||
},
|
||||
"START_CONVERSATION": "Iniciar Conversa",
|
||||
"END_CONVERSATION": "End Conversation",
|
||||
"END_CONVERSATION": "Terminar Conversa",
|
||||
"CONTINUE_CONVERSATION": "Continuar conversa",
|
||||
"START_NEW_CONVERSATION": "Iniciar uma nova conversa",
|
||||
"UNREAD_VIEW": {
|
||||
|
|
|
@ -40,12 +40,16 @@ export default {
|
|||
availableAgents: 'agent/availableAgents',
|
||||
activeCampaign: 'campaign/getActiveCampaign',
|
||||
conversationSize: 'conversation/getConversationSize',
|
||||
currentUser: 'contacts/getCurrentUser',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
startConversation() {
|
||||
const isUserEmailAvailable = !!this.currentUser.email;
|
||||
if (this.preChatFormEnabled && !this.conversationSize) {
|
||||
return this.replaceRoute('prechat-form');
|
||||
return this.replaceRoute('prechat-form', {
|
||||
disableContactFields: isUserEmailAvailable,
|
||||
});
|
||||
}
|
||||
return this.replaceRoute('messages');
|
||||
},
|
||||
|
|
|
@ -1,9 +1,28 @@
|
|||
require 'net/imap'
|
||||
|
||||
class Inboxes::FetchImapEmailsJob < ApplicationJob
|
||||
queue_as :low
|
||||
|
||||
def perform(channel)
|
||||
return unless channel.imap_enabled?
|
||||
return unless should_fetch_email?(channel)
|
||||
|
||||
process_mail_for_channel(channel)
|
||||
rescue Errno::ECONNREFUSED, Net::OpenTimeout, Net::IMAP::NoResponseError
|
||||
channel.authorization_error!
|
||||
rescue StandardError => e
|
||||
channel.authorization_error!
|
||||
Sentry.capture_exception(e)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def should_fetch_email?(channel)
|
||||
channel.imap_enabled? && !channel.reauthorization_required?
|
||||
end
|
||||
|
||||
def process_mail_for_channel(channel)
|
||||
# TODO: rather than setting this as default method for all mail objects, lets if can do new mail object
|
||||
# using Mail.retriever_method.new(params)
|
||||
Mail.defaults do
|
||||
retriever_method :imap, address: channel.imap_address,
|
||||
port: channel.imap_port,
|
||||
|
@ -21,6 +40,6 @@ class Inboxes::FetchImapEmailsJob < ApplicationJob
|
|||
end
|
||||
end
|
||||
|
||||
Channel::Email.update(channel.id, imap_inbox_synced_at: Time.now.utc) if new_mails
|
||||
channel.update(imap_inbox_synced_at: Time.now.utc) if new_mails
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,51 +1,58 @@
|
|||
class AutomationRuleListener < BaseListener
|
||||
def conversation_updated(event_obj)
|
||||
conversation = event_obj.data[:conversation]
|
||||
return unless rule_present?('conversation_updated', conversation)
|
||||
account = conversation.account
|
||||
|
||||
return unless rule_present?('conversation_updated', account)
|
||||
|
||||
@rules.each do |rule|
|
||||
conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation).perform
|
||||
AutomationRules::ActionService.new(rule, conversation).perform if conditions_match.present?
|
||||
AutomationRules::ActionService.new(rule, account, conversation).perform if conditions_match.present?
|
||||
end
|
||||
end
|
||||
|
||||
def conversation_status_changed(event_obj)
|
||||
conversation = event_obj.data[:conversation]
|
||||
return unless rule_present?('conversation_status_changed', conversation)
|
||||
account = conversation.account
|
||||
|
||||
return unless rule_present?('conversation_status_changed', account)
|
||||
|
||||
@rules.each do |rule|
|
||||
conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation).perform
|
||||
AutomationRules::ActionService.new(rule, conversation).perform if conditions_match.present?
|
||||
AutomationRules::ActionService.new(rule, account, conversation).perform if conditions_match.present?
|
||||
end
|
||||
end
|
||||
|
||||
def conversation_created(event_obj)
|
||||
conversation = event_obj.data[:conversation]
|
||||
return unless rule_present?('conversation_created', conversation)
|
||||
account = conversation.account
|
||||
|
||||
return unless rule_present?('conversation_created', account)
|
||||
|
||||
@rules.each do |rule|
|
||||
conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation).perform
|
||||
::AutomationRules::ActionService.new(rule, conversation).perform if conditions_match.present?
|
||||
::AutomationRules::ActionService.new(rule, account, conversation).perform if conditions_match.present?
|
||||
end
|
||||
end
|
||||
|
||||
def message_created(event_obj)
|
||||
message = event_obj.data[:message]
|
||||
conversation = message.conversation
|
||||
return unless rule_present?('message_created', conversation)
|
||||
account = message.try(:account)
|
||||
|
||||
return unless rule_present?('message_created', account)
|
||||
|
||||
@rules.each do |rule|
|
||||
conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation).message_conditions(message)
|
||||
::AutomationRules::ActionService.new(rule, conversation).perform if conditions_match.present?
|
||||
conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, message.conversation).message_conditions
|
||||
::AutomationRules::ActionService.new(rule, account, message.conversation).perform if conditions_match.present?
|
||||
end
|
||||
end
|
||||
|
||||
def rule_present?(event_name, conversation)
|
||||
return if conversation.blank?
|
||||
def rule_present?(event_name, account)
|
||||
return if account.blank?
|
||||
|
||||
@rules = AutomationRule.where(
|
||||
event_name: event_name,
|
||||
account_id: conversation.account_id,
|
||||
account_id: account.id,
|
||||
active: true
|
||||
)
|
||||
@rules.any?
|
||||
|
|
|
@ -5,7 +5,7 @@ module MailboxHelper
|
|||
@message = @conversation.messages.create(
|
||||
account_id: @conversation.account_id,
|
||||
sender: @conversation.contact,
|
||||
content: mail_content,
|
||||
content: mail_content&.truncate(150_000),
|
||||
inbox_id: @conversation.inbox_id,
|
||||
message_type: 'incoming',
|
||||
content_type: 'incoming_email',
|
||||
|
|
|
@ -15,6 +15,14 @@ class AdministratorNotifications::ChannelNotificationsMailer < ApplicationMailer
|
|||
send_mail_with_liquid(to: admin_emails, subject: subject) and return
|
||||
end
|
||||
|
||||
def email_disconnect(inbox)
|
||||
return unless smtp_config_set_or_development?
|
||||
|
||||
subject = 'Your email inbox has been disconnected. Please update the credentials for SMTP/IMAP'
|
||||
@action_url = "#{ENV['FRONTEND_URL']}/app/accounts/#{Current.account.id}/settings/inboxes/#{inbox.id}"
|
||||
send_mail_with_liquid(to: admin_emails, subject: subject) and return
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def admin_emails
|
||||
|
|
|
@ -40,6 +40,10 @@ module ConversationReplyMailerHelper
|
|||
end
|
||||
|
||||
def email_smtp_enabled
|
||||
@inbox.inbox_type == 'Email' && @channel.smtp_enabled
|
||||
end
|
||||
|
||||
def email_imap_enabled
|
||||
@inbox.inbox_type == 'Email' && @channel.imap_enabled
|
||||
end
|
||||
|
||||
|
@ -48,6 +52,6 @@ module ConversationReplyMailerHelper
|
|||
end
|
||||
|
||||
def email_reply_to
|
||||
email_smtp_enabled ? @channel.smtp_email : reply_email
|
||||
email_imap_enabled ? @channel.imap_email : reply_email
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,50 +4,57 @@ class TeamNotifications::AutomationNotificationMailer < ApplicationMailer
|
|||
|
||||
@agents = team.team_members
|
||||
@conversation = conversation
|
||||
@message = message
|
||||
@custom_message = message
|
||||
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
|
||||
|
||||
send_an_email_to_team
|
||||
send_an_email_to_team and return
|
||||
end
|
||||
|
||||
def conversation_updated(conversation, team)
|
||||
def conversation_updated(conversation, team, message)
|
||||
return unless smtp_config_set_or_development?
|
||||
|
||||
@agents = team.team_members
|
||||
@conversation = conversation
|
||||
@message = message
|
||||
@custom_message = message
|
||||
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
|
||||
|
||||
send_an_email_to_team
|
||||
send_an_email_to_team and return
|
||||
end
|
||||
|
||||
def message_created(message, agent)
|
||||
def message_created(conversation, team, message)
|
||||
return unless smtp_config_set_or_development?
|
||||
|
||||
@agent = agent
|
||||
@conversation = message.conversation
|
||||
@message = message
|
||||
subject = "#{@agent.available_name}, You have been mentioned in conversation [ID - #{@conversation.display_id}]"
|
||||
@agents = team.team_members
|
||||
@conversation = conversation
|
||||
@custom_message = message
|
||||
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
|
||||
send_mail_with_liquid(to: @agent.email, subject: subject)
|
||||
|
||||
send_an_email_to_team and return
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_an_email_to_team
|
||||
@agents.each do |agent|
|
||||
subject = "#{@agent.available_name}, A new conversation [ID - #{@conversation.display_id}] has been created in #{@conversation.inbox&.name}."
|
||||
subject = "#{agent.user.available_name}, This email has been sent via automation rule actions."
|
||||
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
|
||||
send_mail_with_liquid(to: agent.email, subject: subject)
|
||||
@agent = agent
|
||||
|
||||
send_mail_with_liquid(to: @agent.user.email, subject: subject)
|
||||
end
|
||||
end
|
||||
|
||||
def liquid_droppables
|
||||
super.merge({
|
||||
user: @agent,
|
||||
conversation: @conversation,
|
||||
inbox: @conversation.inbox,
|
||||
message: @message
|
||||
})
|
||||
super.merge!({
|
||||
user: @agent.user,
|
||||
conversation: @conversation,
|
||||
inbox: @conversation.inbox
|
||||
})
|
||||
end
|
||||
|
||||
def liquid_locals
|
||||
super.merge!({
|
||||
custom_message: @custom_message
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -79,6 +79,7 @@ class Account < ApplicationRecord
|
|||
|
||||
before_validation :validate_limit_keys
|
||||
after_create_commit :notify_creation
|
||||
after_destroy :remove_account_sequences
|
||||
|
||||
def agents
|
||||
users.where(account_users: { role: :agent })
|
||||
|
@ -110,7 +111,7 @@ class Account < ApplicationRecord
|
|||
end
|
||||
|
||||
def support_email
|
||||
super || GlobalConfig.get('MAILER_SUPPORT_EMAIL')['MAILER_SUPPORT_EMAIL'] || ENV.fetch('MAILER_SENDER_EMAIL', 'Chatwoot <accounts@chatwoot.com>')
|
||||
super || ENV['MAILER_SENDER_EMAIL'] || GlobalConfig.get('MAILER_SUPPORT_EMAIL')['MAILER_SUPPORT_EMAIL']
|
||||
end
|
||||
|
||||
def usage_limits
|
||||
|
@ -137,4 +138,9 @@ class Account < ApplicationRecord
|
|||
def validate_limit_keys
|
||||
# method overridden in enterprise module
|
||||
end
|
||||
|
||||
def remove_account_sequences
|
||||
ActiveRecord::Base.connection.exec_query("drop sequence IF EXISTS camp_dpid_seq_#{id}")
|
||||
ActiveRecord::Base.connection.exec_query("drop sequence IF EXISTS conv_dpid_seq_#{id}")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,9 +20,9 @@
|
|||
class AutomationRule < ApplicationRecord
|
||||
belongs_to :account
|
||||
|
||||
validates :account, presence: true
|
||||
validate :json_conditions_format
|
||||
validate :json_actions_format
|
||||
validates :account_id, presence: true
|
||||
|
||||
scope :active, -> { where(active: true) }
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
|
||||
class Channel::Email < ApplicationRecord
|
||||
include Channelable
|
||||
include Reauthorizable
|
||||
|
||||
self.table_name = 'channel_email'
|
||||
EDITABLE_ATTRS = [:email, :imap_enabled, :imap_email, :imap_password, :imap_address, :imap_port, :imap_enable_ssl, :imap_inbox_synced_at,
|
||||
|
|
|
@ -20,7 +20,7 @@ module ActivityMessageHandler
|
|||
def create_status_change_message(user_name)
|
||||
content = if user_name
|
||||
I18n.t("conversations.activity.status.#{status}", user_name: user_name)
|
||||
elsif Current.contact.present?
|
||||
elsif Current.contact.present? && resolved?
|
||||
I18n.t('conversations.activity.status.contact_resolved', contact_name: Current.contact.name.capitalize)
|
||||
elsif resolved?
|
||||
I18n.t('conversations.activity.status.auto_resolved', duration: auto_resolve_duration)
|
||||
|
|
|
@ -39,10 +39,11 @@ module Reauthorizable
|
|||
|
||||
if (is_a? Integrations::Hook) && slack?
|
||||
AdministratorNotifications::ChannelNotificationsMailer.with(account: account).slack_disconnect.deliver_later
|
||||
elsif is_a? Channel::FacebookPage
|
||||
AdministratorNotifications::ChannelNotificationsMailer.with(account: account).facebook_disconnect(inbox).deliver_later
|
||||
elsif is_a? Channel::Email
|
||||
AdministratorNotifications::ChannelNotificationsMailer.with(account: account).email_disconnect(inbox).deliver_later
|
||||
end
|
||||
return unless is_a? Channel::FacebookPage
|
||||
|
||||
AdministratorNotifications::ChannelNotificationsMailer.with(account: account).facebook_disconnect(inbox).deliver_later
|
||||
end
|
||||
|
||||
# call this after you successfully Reauthorized the object in UI
|
||||
|
|
|
@ -50,6 +50,8 @@ class Conversation < ApplicationRecord
|
|||
validates :account_id, presence: true
|
||||
validates :inbox_id, presence: true
|
||||
before_validation :validate_additional_attributes
|
||||
validates :additional_attributes, jsonb_attributes_length: true
|
||||
validates :custom_attributes, jsonb_attributes_length: true
|
||||
|
||||
enum status: { open: 0, resolved: 1, pending: 2, snoozed: 3 }
|
||||
|
||||
|
@ -210,6 +212,8 @@ class Conversation < ApplicationRecord
|
|||
end
|
||||
|
||||
def dispatcher_dispatch(event_name)
|
||||
return if Current.executed_by.present? && Current.executed_by.instance_of?(AutomationRule)
|
||||
|
||||
Rails.configuration.dispatcher.dispatch(event_name, Time.zone.now, conversation: self, notifiable_assignee_change: notifiable_assignee_change?)
|
||||
end
|
||||
|
||||
|
|
21
app/models/jsonb_attributes_length_validator.rb
Normal file
21
app/models/jsonb_attributes_length_validator.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class JsonbAttributesLengthValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
return if value.empty?
|
||||
|
||||
@attribute = attribute
|
||||
@record = record
|
||||
|
||||
value.each do |key, attribute_value|
|
||||
validate_keys(key, attribute_value)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_keys(key, attribute_value)
|
||||
case attribute_value.class.name
|
||||
when 'String'
|
||||
@record.errors.add @attribute, "#{key} length should be < 1500" if attribute_value.length > 1500
|
||||
when 'Integer'
|
||||
@record.errors.add @attribute, "#{key} value should be < 9999999999" if attribute_value > 9_999_999_999
|
||||
end
|
||||
end
|
||||
end
|
|
@ -166,6 +166,8 @@ class Message < ApplicationRecord
|
|||
end
|
||||
|
||||
def dispatch_create_events
|
||||
return if Current.executed_by.present? && Current.executed_by.instance_of?(AutomationRule)
|
||||
|
||||
Rails.configuration.dispatcher.dispatch(MESSAGE_CREATED, Time.zone.now, message: self)
|
||||
|
||||
if outgoing? && conversation.messages.outgoing.count == 1
|
||||
|
@ -174,6 +176,8 @@ class Message < ApplicationRecord
|
|||
end
|
||||
|
||||
def dispatch_update_event
|
||||
return if Current.executed_by.present? && Current.executed_by.instance_of?(AutomationRule)
|
||||
|
||||
Rails.configuration.dispatcher.dispatch(MESSAGE_UPDATED, Time.zone.now, message: self)
|
||||
end
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ class Team < ApplicationRecord
|
|||
end
|
||||
|
||||
def remove_member(user_id)
|
||||
team_members.find_by(user_id: user_id)&.destroy
|
||||
team_members.find_by(user_id: user_id)&.destroy!
|
||||
end
|
||||
|
||||
def messages
|
||||
|
|
|
@ -1,40 +1,68 @@
|
|||
class AutomationRules::ActionService
|
||||
def initialize(rule, conversation)
|
||||
def initialize(rule, account, conversation)
|
||||
@rule = rule
|
||||
@account = account
|
||||
@conversation = conversation
|
||||
@account = @conversation.account
|
||||
Current.executed_by = rule
|
||||
end
|
||||
|
||||
def perform
|
||||
@rule.actions.each do |action, _current_index|
|
||||
@rule.actions.each do |action|
|
||||
action = action.with_indifferent_access
|
||||
send(action[:action_name], action[:action_params])
|
||||
begin
|
||||
send(action[:action_name], action[:action_params])
|
||||
rescue StandardError => e
|
||||
Sentry.capture_exception(e)
|
||||
end
|
||||
end
|
||||
ensure
|
||||
Current.reset
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_email_transcript(email)
|
||||
ConversationReplyMailer.with(account: conversation.account).conversation_transcript(@conversation, email)&.deliver_later
|
||||
end
|
||||
|
||||
def mute_conversation(_params)
|
||||
@conversation.mute!
|
||||
end
|
||||
|
||||
def change_status(status)
|
||||
@conversation.update!(status: status[0])
|
||||
end
|
||||
|
||||
def send_webhook_events(webhook_url)
|
||||
payload = @conversation.webhook_data.merge(event: "automation_event: #{@rule.event_name}")
|
||||
WebhookJob.perform_later(webhook_url, payload)
|
||||
end
|
||||
|
||||
def send_message(message)
|
||||
# params = { content: message, private: false }
|
||||
# mb = Messages::MessageBuilder.new(@administrator, @conversation, params)
|
||||
# mb.perform
|
||||
return if @rule.event_name == 'message_created'
|
||||
|
||||
params = { content: message[0], private: false }
|
||||
mb = Messages::MessageBuilder.new(@administrator, @conversation, params)
|
||||
mb.perform
|
||||
end
|
||||
|
||||
def assign_team(team_ids = [])
|
||||
return unless team_belongs_to_account?(team_ids)
|
||||
|
||||
@account.teams.find_by(id: team_ids)
|
||||
@conversation.update!(team_id: team_ids[0])
|
||||
end
|
||||
|
||||
def assign_best_agents(agent_ids = [])
|
||||
def assign_best_agent(agent_ids = [])
|
||||
return unless agent_belongs_to_account?(agent_ids)
|
||||
|
||||
@agent = @account.users.find_by(id: agent_ids)
|
||||
@conversation.update_assignee(@agent)
|
||||
|
||||
@conversation.update!(assignee_id: @agent.id) if @agent.present?
|
||||
end
|
||||
|
||||
def add_label(labels = [])
|
||||
def add_label(labels)
|
||||
return if labels.empty?
|
||||
|
||||
@conversation.add_labels(labels)
|
||||
end
|
||||
|
||||
|
@ -43,11 +71,11 @@ class AutomationRules::ActionService
|
|||
|
||||
case @rule.event_name
|
||||
when 'conversation_created', 'conversation_status_changed'
|
||||
TeamNotifications::AutomationNotificationMailer.conversation_creation(@conversation, team, params[:message])
|
||||
TeamNotifications::AutomationNotificationMailer.conversation_creation(@conversation, team, params[:message])&.deliver_now
|
||||
when 'conversation_updated'
|
||||
TeamNotifications::AutomationNotificationMailer.conversation_updated(@conversation, team, params[:message])
|
||||
TeamNotifications::AutomationNotificationMailer.conversation_updated(@conversation, team, params[:message])&.deliver_now
|
||||
when 'message_created'
|
||||
TeamNotifications::AutomationNotificationMailer.message_created(@conversation, team, params[:message])
|
||||
TeamNotifications::AutomationNotificationMailer.message_created(@conversation, team, params[:message])&.deliver_now
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
require 'json'
|
||||
|
||||
class AutomationRules::ConditionsFilterService < FilterService
|
||||
def initialize(rule, conversation)
|
||||
def initialize(rule, conversation = nil)
|
||||
super([], nil)
|
||||
@rule = rule
|
||||
@conversation = conversation
|
||||
|
@ -21,14 +21,14 @@ class AutomationRules::ConditionsFilterService < FilterService
|
|||
records.any?
|
||||
end
|
||||
|
||||
def message_conditions(message)
|
||||
def message_conditions
|
||||
message_filters = @filters['messages']
|
||||
|
||||
@rule.conditions.each_with_index do |query_hash, current_index|
|
||||
current_filter = message_filters[query_hash['attribute_key']]
|
||||
@query_string += message_query_string(current_filter, query_hash.with_indifferent_access, current_index)
|
||||
end
|
||||
records = Message.where(id: message.id).where(@query_string, @filter_values.with_indifferent_access)
|
||||
records = Message.where(conversation: @conversation).where(@query_string, @filter_values.with_indifferent_access)
|
||||
records.any?
|
||||
end
|
||||
|
||||
|
@ -44,6 +44,19 @@ class AutomationRules::ConditionsFilterService < FilterService
|
|||
end
|
||||
end
|
||||
|
||||
# This will be used in future for contact automation rule
|
||||
def contact_conditions(_contact)
|
||||
conversation_filters = @filters['conversations']
|
||||
|
||||
@rule.conditions.each_with_index do |query_hash, current_index|
|
||||
current_filter = conversation_filters[query_hash['attribute_key']]
|
||||
@query_string += conversation_query_string(current_filter, query_hash.with_indifferent_access, current_index)
|
||||
end
|
||||
|
||||
records = base_relation.where(@query_string, @filter_values.with_indifferent_access)
|
||||
records.any?
|
||||
end
|
||||
|
||||
def conversation_query_string(current_filter, query_hash, current_index)
|
||||
attribute_key = query_hash['attribute_key']
|
||||
query_operator = query_hash['query_operator']
|
||||
|
@ -63,6 +76,6 @@ class AutomationRules::ConditionsFilterService < FilterService
|
|||
end
|
||||
|
||||
def base_relation
|
||||
Conversation.where(id: @conversation)
|
||||
Conversation.where(id: @conversation.id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,6 +20,8 @@ class Instagram::MessageText < Instagram::WebhooksBaseService
|
|||
# person can connect the channel and then delete the inbox
|
||||
return if @inbox.blank?
|
||||
|
||||
return unsend_message if message_is_deleted?
|
||||
|
||||
ensure_contact(contact_id)
|
||||
|
||||
create_message
|
||||
|
@ -46,6 +48,19 @@ class Instagram::MessageText < Instagram::WebhooksBaseService
|
|||
@messaging[:message][:is_echo].present?
|
||||
end
|
||||
|
||||
def message_is_deleted?
|
||||
@messaging[:message][:is_deleted].present?
|
||||
end
|
||||
|
||||
def unsend_message
|
||||
message_to_delete = @inbox.messages.find_by(
|
||||
source_id: @messaging[:message][:mid]
|
||||
)
|
||||
return if message_to_delete.blank?
|
||||
|
||||
message_to_delete.update!(content: I18n.t('conversations.messages.deleted'), deleted: true)
|
||||
end
|
||||
|
||||
def create_message
|
||||
Messages::Instagram::MessageBuilder.new(@messaging, @inbox, outgoing_echo: agent_message_via_echo?).perform
|
||||
end
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<p>Hello,</p>
|
||||
|
||||
<p>Your email inbox has been disconnected due to configuration errors. </p>
|
||||
<p>Please update it to continue receiving messages.</p>
|
||||
|
||||
<p>
|
||||
Click <a href="{{action_url}}">here</a> to re-connect.
|
||||
</p>
|
|
@ -1,7 +1,7 @@
|
|||
<p>Hello,</p>
|
||||
|
||||
<p>Your Facebook Inbox Access has expired. </p>
|
||||
<p>Please reconnect Facebook Page to continue receiving messages in Chatwoot</p>
|
||||
<p>Please reconnect Facebook Page to continue receiving messages.</p>
|
||||
|
||||
<p>
|
||||
Click <a href="{{action_url}}">here</a> to re-connect.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<p>Hi {{user.available_name}}</p>
|
||||
|
||||
|
||||
<p>Time to save the world. A new conversation has been created in {{ inbox.name }}</p>
|
||||
<p>{{ custom_message }}</p>
|
||||
|
||||
<p>
|
||||
Click <a href="{{ action_url }}">here</a> to get cracking.
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<p>Hi {{user.available_name}}</p>
|
||||
|
||||
|
||||
<p>{{ custom_message }}</p>
|
||||
|
||||
<p>
|
||||
Click <a href="{{ action_url }}">here</a> to get cracking.
|
||||
</p>
|
|
@ -0,0 +1,8 @@
|
|||
<p>Hi {{user.available_name}}</p>
|
||||
|
||||
|
||||
<p>{{ custom_message }}</p>
|
||||
|
||||
<p>
|
||||
Click <a href="{{ action_url }}">here</a> to get cracking.
|
||||
</p>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue