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
|
||||
|
||||
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
|
||||
ActiveRecord::Base.transaction do
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
|
@ -37,8 +37,7 @@ export const conversationUrl = ({
|
|||
url = `accounts/${accountId}/custom_view/${foldersId}/conversations/${id}`;
|
||||
} else if (conversationType === 'mention') {
|
||||
url = `accounts/${accountId}/mentions/conversations/${id}`;
|
||||
}
|
||||
else if (conversationType === 'participating') {
|
||||
} else if (conversationType === 'participating') {
|
||||
url = `accounts/${accountId}/participating/conversations/${id}`;
|
||||
}
|
||||
return url;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { mapGetters } from 'vuex';
|
||||
export const DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER = [
|
||||
{ name: 'conversation_actions' },
|
||||
{ name: 'conversation_participants' },
|
||||
{ name: 'conversation_info' },
|
||||
{ name: 'contact_attributes' },
|
||||
{ name: 'previous_conversation' },
|
||||
|
|
|
@ -38,6 +38,24 @@
|
|||
/>
|
||||
</accordion-item>
|
||||
</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'">
|
||||
<accordion-item
|
||||
: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 ContactConversations from './ContactConversations.vue';
|
||||
import ConversationAction from './ConversationAction.vue';
|
||||
import ConversationParticipant from './ConversationParticipant.vue';
|
||||
|
||||
import ContactInfo from './contact/ContactInfo';
|
||||
import ConversationInfo from './ConversationInfo';
|
||||
|
@ -122,6 +141,7 @@ export default {
|
|||
CustomAttributes,
|
||||
CustomAttributeSelector,
|
||||
ConversationAction,
|
||||
ConversationParticipant,
|
||||
draggable,
|
||||
},
|
||||
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 }) {
|
||||
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;
|
||||
|
|
|
@ -92,6 +92,9 @@ const getters = {
|
|||
value => value.id === Number(conversationId)
|
||||
);
|
||||
},
|
||||
getConversationParticipants: _state => {
|
||||
return _state.conversationParticipants;
|
||||
},
|
||||
};
|
||||
|
||||
export default getters;
|
||||
|
|
|
@ -12,6 +12,7 @@ const state = {
|
|||
currentInbox: null,
|
||||
selectedChatId: null,
|
||||
appliedFilters: [],
|
||||
conversationParticipants: [],
|
||||
};
|
||||
|
||||
// mutations
|
||||
|
@ -194,6 +195,14 @@ export const mutations = {
|
|||
[types.CLEAR_CONVERSATION_FILTERS](_state) {
|
||||
_state.appliedFilters = [];
|
||||
},
|
||||
|
||||
[types.SET_CONVERSATION_PARTICIPANTS](_state, data) {
|
||||
_state.conversationParticipants = data;
|
||||
},
|
||||
|
||||
[types.CLEAR_CONVERSATION_PARTICIPANTS](_state) {
|
||||
_state.conversationParticipants = [];
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -21,6 +21,8 @@ export default {
|
|||
CLEAR_CONTACT_CONVERSATIONS: 'CLEAR_CONTACT_CONVERSATIONS',
|
||||
SET_CONVERSATION_FILTERS: 'SET_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',
|
||||
CLEAR_CURRENT_CHAT_WINDOW: 'CLEAR_CURRENT_CHAT_WINDOW',
|
||||
|
|
|
@ -77,7 +77,6 @@ class Conversation < ApplicationRecord
|
|||
has_many :conversation_participants, dependent: :destroy_async
|
||||
has_many :notifications, as: :primary_actor, dependent: :destroy
|
||||
|
||||
|
||||
before_save :ensure_snooze_until_reset
|
||||
before_create :mark_conversation_pending_if_bot
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ class ConversationParticipant < ApplicationRecord
|
|||
before_validation :ensure_account_id
|
||||
|
||||
private
|
||||
|
||||
|
||||
def ensure_account_id
|
||||
self.account_id = conversation&.account_id
|
||||
end
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
json.array! @participants do |participant|
|
||||
json.partial! 'api/v1/models/agent.json.jbuilder', resource: participant.user
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
json.array! @participants do |participant|
|
||||
json.partial! 'api/v1/models/agent.json.jbuilder', resource: participant.user
|
||||
end
|
||||
end
|
||||
|
|
|
@ -71,7 +71,7 @@ Rails.application.routes.draw do
|
|||
resources :messages, only: [:index, :create, :destroy]
|
||||
resources :assignments, only: [:create]
|
||||
resources :labels, only: [:create, :index]
|
||||
resource :participants, only: [:show, :create, :destroy]
|
||||
resource :participants, only: [:show, :create, :update, :destroy]
|
||||
end
|
||||
member do
|
||||
post :mute
|
||||
|
|
|
@ -4,8 +4,8 @@ class AddConversationParticipants < ActiveRecord::Migration[6.1]
|
|||
t.references :account, 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.datetime "created_at", precision: 6, null: false
|
||||
t.datetime "updated_at", precision: 6, null: false
|
||||
t.datetime 'created_at', precision: 6, null: false
|
||||
t.datetime 'updated_at', precision: 6, null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue