Add conversation participants filter
This commit is contained in:
parent
5c9765ec31
commit
2bc1dbd76b
16 changed files with 234 additions and 9 deletions
|
@ -9,6 +9,15 @@ class Api::V1::Accounts::Conversations::ParticipantsController < Api::V1::Accoun
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
participants_to_be_added_ids.each { |user_id| @conversation.conversation_participants.find_or_create_by(user_id: user_id) }
|
||||||
|
participants_to_be_removed_ids.each { |user_id| @conversation.conversation_participants.find_by(user_id: user_id)&.destroy }
|
||||||
|
end
|
||||||
|
@participants = @conversation.conversation_participants
|
||||||
|
render action: 'show'
|
||||||
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
ActiveRecord::Base.transaction do
|
ActiveRecord::Base.transaction do
|
||||||
params[:user_ids].map { |user_id| @conversation.conversation_participants.find_by(user_id: user_id)&.destroy }
|
params[:user_ids].map { |user_id| @conversation.conversation_participants.find_by(user_id: user_id)&.destroy }
|
||||||
|
|
|
@ -105,6 +105,22 @@ class ConversationApi extends ApiClient {
|
||||||
custom_attributes: customAttributes,
|
custom_attributes: customAttributes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fecthParticipants(id) {
|
||||||
|
return axios.get(`${this.url}/${id}/participants`);
|
||||||
|
}
|
||||||
|
|
||||||
|
createParticipants(object) {
|
||||||
|
return axios.post(`${this.url}/${object.conversationId}/participants`, {
|
||||||
|
user_ids: object.user_ids,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateParticipants(object) {
|
||||||
|
return axios.patch(`${this.url}/${object.conversationId}/participants`, {
|
||||||
|
user_ids: object.user_ids,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ConversationApi();
|
export default new ConversationApi();
|
||||||
|
|
|
@ -37,8 +37,7 @@ export const conversationUrl = ({
|
||||||
url = `accounts/${accountId}/custom_view/${foldersId}/conversations/${id}`;
|
url = `accounts/${accountId}/custom_view/${foldersId}/conversations/${id}`;
|
||||||
} else if (conversationType === 'mention') {
|
} else if (conversationType === 'mention') {
|
||||||
url = `accounts/${accountId}/mentions/conversations/${id}`;
|
url = `accounts/${accountId}/mentions/conversations/${id}`;
|
||||||
}
|
} else if (conversationType === 'participating') {
|
||||||
else if (conversationType === 'participating') {
|
|
||||||
url = `accounts/${accountId}/participating/conversations/${id}`;
|
url = `accounts/${accountId}/participating/conversations/${id}`;
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
export const DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER = [
|
export const DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER = [
|
||||||
{ name: 'conversation_actions' },
|
{ name: 'conversation_actions' },
|
||||||
|
{ name: 'conversation_participants' },
|
||||||
{ name: 'conversation_info' },
|
{ name: 'conversation_info' },
|
||||||
{ name: 'contact_attributes' },
|
{ name: 'contact_attributes' },
|
||||||
{ name: 'previous_conversation' },
|
{ name: 'previous_conversation' },
|
||||||
|
|
|
@ -38,6 +38,24 @@
|
||||||
/>
|
/>
|
||||||
</accordion-item>
|
</accordion-item>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="element.name === 'conversation_participants'"
|
||||||
|
class="conversation--actions"
|
||||||
|
>
|
||||||
|
<accordion-item
|
||||||
|
title="Conversation Participants"
|
||||||
|
:is-open="isContactSidebarItemOpen('is_conv_participants_open')"
|
||||||
|
@click="
|
||||||
|
value =>
|
||||||
|
toggleSidebarUIState('is_conv_participants_open', value)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<conversation-participant
|
||||||
|
:conversation-id="conversationId"
|
||||||
|
:inbox-id="inboxId"
|
||||||
|
/>
|
||||||
|
</accordion-item>
|
||||||
|
</div>
|
||||||
<div v-else-if="element.name === 'conversation_info'">
|
<div v-else-if="element.name === 'conversation_info'">
|
||||||
<accordion-item
|
<accordion-item
|
||||||
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONVERSATION_INFO')"
|
:title="$t('CONVERSATION_SIDEBAR.ACCORDION.CONVERSATION_INFO')"
|
||||||
|
@ -105,6 +123,7 @@ import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import AccordionItem from 'dashboard/components/Accordion/AccordionItem';
|
import AccordionItem from 'dashboard/components/Accordion/AccordionItem';
|
||||||
import ContactConversations from './ContactConversations.vue';
|
import ContactConversations from './ContactConversations.vue';
|
||||||
import ConversationAction from './ConversationAction.vue';
|
import ConversationAction from './ConversationAction.vue';
|
||||||
|
import ConversationParticipant from './ConversationParticipant.vue';
|
||||||
|
|
||||||
import ContactInfo from './contact/ContactInfo';
|
import ContactInfo from './contact/ContactInfo';
|
||||||
import ConversationInfo from './ConversationInfo';
|
import ConversationInfo from './ConversationInfo';
|
||||||
|
@ -122,6 +141,7 @@ export default {
|
||||||
CustomAttributes,
|
CustomAttributes,
|
||||||
CustomAttributeSelector,
|
CustomAttributeSelector,
|
||||||
ConversationAction,
|
ConversationAction,
|
||||||
|
ConversationParticipant,
|
||||||
draggable,
|
draggable,
|
||||||
},
|
},
|
||||||
mixins: [alertMixin, uiSettingsMixin],
|
mixins: [alertMixin, uiSettingsMixin],
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<multiselect
|
||||||
|
v-model="participantList"
|
||||||
|
:options="agentList"
|
||||||
|
track-by="id"
|
||||||
|
label="name"
|
||||||
|
:multiple="true"
|
||||||
|
:close-on-select="false"
|
||||||
|
:clear-on-select="false"
|
||||||
|
:hide-selected="true"
|
||||||
|
placeholder="Choose Participants"
|
||||||
|
selected-label
|
||||||
|
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
|
||||||
|
:deselect-label="$t('FORMS.MULTISELECT.ENTER_TO_REMOVE')"
|
||||||
|
>
|
||||||
|
<template slot="tag" slot-scope="props">
|
||||||
|
<div class="watcher-wrap">
|
||||||
|
<thumbnail
|
||||||
|
:src="props.option.thumbnail"
|
||||||
|
:username="props.option.name"
|
||||||
|
size="18px"
|
||||||
|
class="margin-right-small"
|
||||||
|
/>
|
||||||
|
<p style="font-size: 12px">{{ props.option.name }}</p>
|
||||||
|
<woot-button
|
||||||
|
class-names="thumbnail-remove"
|
||||||
|
variant="clear"
|
||||||
|
size="tiny"
|
||||||
|
icon="dismiss"
|
||||||
|
color-scheme="secondary"
|
||||||
|
@click="props.remove(props.option)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</multiselect>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Thumbnail,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
conversationId: {
|
||||||
|
type: [Number, String],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
inboxId: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedParticipant: [],
|
||||||
|
user_ids: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
participants: 'getConversationParticipants',
|
||||||
|
agentList: 'agents/getAgents',
|
||||||
|
}),
|
||||||
|
participantList: {
|
||||||
|
get() {
|
||||||
|
return this.participants;
|
||||||
|
},
|
||||||
|
set(participants) {
|
||||||
|
this.user_ids = participants.map(el => el.id);
|
||||||
|
this.updateParticipant();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
conversationId() {
|
||||||
|
this.$store.dispatch('clearConversationParticipants');
|
||||||
|
this.fetchParticipants();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchParticipants();
|
||||||
|
this.$store.dispatch('agents/get');
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchParticipants() {
|
||||||
|
this.$store.dispatch(
|
||||||
|
'fetchConversationParticipants',
|
||||||
|
this.conversationId
|
||||||
|
);
|
||||||
|
},
|
||||||
|
updateParticipant() {
|
||||||
|
this.$store.dispatch('updateConversationParticipants', {
|
||||||
|
conversationId: this.conversationId,
|
||||||
|
user_ids: this.user_ids,
|
||||||
|
});
|
||||||
|
this.fetchParticipants();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
::v-deep .multiselect__tags-wrap {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
margin-top: var(--zero) !important;
|
||||||
|
}
|
||||||
|
.watcher-wrap {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
width: auto;
|
||||||
|
padding: 1px;
|
||||||
|
padding-left: 5px;
|
||||||
|
border-radius: var(--space-one);
|
||||||
|
background: var(--s-50);
|
||||||
|
margin: 4px 5px 5px 0px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: var(--zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-remove {
|
||||||
|
margin-left: var(--space-small);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -330,6 +330,43 @@ const actions = {
|
||||||
clearConversationFilters({ commit }) {
|
clearConversationFilters({ commit }) {
|
||||||
commit(types.CLEAR_CONVERSATION_FILTERS);
|
commit(types.CLEAR_CONVERSATION_FILTERS);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fetchConversationParticipants: async ({ commit }, id) => {
|
||||||
|
try {
|
||||||
|
const response = await ConversationApi.fecthParticipants(id);
|
||||||
|
commit(types.SET_CONVERSATION_PARTICIPANTS, response.data);
|
||||||
|
} catch (error) {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createConversationParticipants: async (
|
||||||
|
{ commit },
|
||||||
|
conversationId,
|
||||||
|
user_ids
|
||||||
|
) => {
|
||||||
|
const response = await ConversationApi.createParticipants(
|
||||||
|
conversationId,
|
||||||
|
user_ids
|
||||||
|
);
|
||||||
|
commit(types.SET_CONVERSATION_PARTICIPANTS, response.data);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateConversationParticipants: async (
|
||||||
|
{ commit },
|
||||||
|
conversationId,
|
||||||
|
user_ids
|
||||||
|
) => {
|
||||||
|
const response = await ConversationApi.updateParticipants(
|
||||||
|
conversationId,
|
||||||
|
user_ids
|
||||||
|
);
|
||||||
|
commit(types.SET_CONVERSATION_PARTICIPANTS, response.data);
|
||||||
|
},
|
||||||
|
|
||||||
|
clearConversationParticipants({ commit }) {
|
||||||
|
commit(types.CLEAR_CONVERSATION_PARTICIPANTS);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default actions;
|
export default actions;
|
||||||
|
|
|
@ -92,6 +92,9 @@ const getters = {
|
||||||
value => value.id === Number(conversationId)
|
value => value.id === Number(conversationId)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
getConversationParticipants: _state => {
|
||||||
|
return _state.conversationParticipants;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default getters;
|
export default getters;
|
||||||
|
|
|
@ -12,6 +12,7 @@ const state = {
|
||||||
currentInbox: null,
|
currentInbox: null,
|
||||||
selectedChatId: null,
|
selectedChatId: null,
|
||||||
appliedFilters: [],
|
appliedFilters: [],
|
||||||
|
conversationParticipants: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// mutations
|
// mutations
|
||||||
|
@ -194,6 +195,14 @@ export const mutations = {
|
||||||
[types.CLEAR_CONVERSATION_FILTERS](_state) {
|
[types.CLEAR_CONVERSATION_FILTERS](_state) {
|
||||||
_state.appliedFilters = [];
|
_state.appliedFilters = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[types.SET_CONVERSATION_PARTICIPANTS](_state, data) {
|
||||||
|
_state.conversationParticipants = data;
|
||||||
|
},
|
||||||
|
|
||||||
|
[types.CLEAR_CONVERSATION_PARTICIPANTS](_state) {
|
||||||
|
_state.conversationParticipants = [];
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -21,6 +21,8 @@ export default {
|
||||||
CLEAR_CONTACT_CONVERSATIONS: 'CLEAR_CONTACT_CONVERSATIONS',
|
CLEAR_CONTACT_CONVERSATIONS: 'CLEAR_CONTACT_CONVERSATIONS',
|
||||||
SET_CONVERSATION_FILTERS: 'SET_CONVERSATION_FILTERS',
|
SET_CONVERSATION_FILTERS: 'SET_CONVERSATION_FILTERS',
|
||||||
CLEAR_CONVERSATION_FILTERS: 'CLEAR_CONVERSATION_FILTERS',
|
CLEAR_CONVERSATION_FILTERS: 'CLEAR_CONVERSATION_FILTERS',
|
||||||
|
SET_CONVERSATION_PARTICIPANTS: 'SET_CONVERSATION_PARTICIPANTS',
|
||||||
|
CLEAR_CONVERSATION_PARTICIPANTS: 'CLEAR_CONVERSATION_PARTICIPANTS',
|
||||||
|
|
||||||
SET_CURRENT_CHAT_WINDOW: 'SET_CURRENT_CHAT_WINDOW',
|
SET_CURRENT_CHAT_WINDOW: 'SET_CURRENT_CHAT_WINDOW',
|
||||||
CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW',
|
CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW',
|
||||||
|
|
|
@ -77,7 +77,6 @@ class Conversation < ApplicationRecord
|
||||||
has_many :conversation_participants, dependent: :destroy_async
|
has_many :conversation_participants, dependent: :destroy_async
|
||||||
has_many :notifications, as: :primary_actor, dependent: :destroy
|
has_many :notifications, as: :primary_actor, dependent: :destroy
|
||||||
|
|
||||||
|
|
||||||
before_save :ensure_snooze_until_reset
|
before_save :ensure_snooze_until_reset
|
||||||
before_create :mark_conversation_pending_if_bot
|
before_create :mark_conversation_pending_if_bot
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ Rails.application.routes.draw do
|
||||||
resources :messages, only: [:index, :create, :destroy]
|
resources :messages, only: [:index, :create, :destroy]
|
||||||
resources :assignments, only: [:create]
|
resources :assignments, only: [:create]
|
||||||
resources :labels, only: [:create, :index]
|
resources :labels, only: [:create, :index]
|
||||||
resource :participants, only: [:show, :create, :destroy]
|
resource :participants, only: [:show, :create, :update, :destroy]
|
||||||
end
|
end
|
||||||
member do
|
member do
|
||||||
post :mute
|
post :mute
|
||||||
|
|
|
@ -4,8 +4,8 @@ class AddConversationParticipants < ActiveRecord::Migration[6.1]
|
||||||
t.references :account, null: false, foreign_key: { on_delete: :cascade }
|
t.references :account, null: false, foreign_key: { on_delete: :cascade }
|
||||||
t.references :user, null: false, foreign_key: { on_delete: :cascade }
|
t.references :user, null: false, foreign_key: { on_delete: :cascade }
|
||||||
t.references :conversation, null: false, foreign_key: { on_delete: :cascade }
|
t.references :conversation, null: false, foreign_key: { on_delete: :cascade }
|
||||||
t.datetime "created_at", precision: 6, null: false
|
t.datetime 'created_at', precision: 6, null: false
|
||||||
t.datetime "updated_at", precision: 6, null: false
|
t.datetime 'updated_at', precision: 6, null: false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue