Add conversation participants filter

This commit is contained in:
Aswin Dev P.S 2022-03-09 18:02:03 +05:30
parent 5c9765ec31
commit 2bc1dbd76b
16 changed files with 234 additions and 9 deletions

View file

@ -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 }

View file

@ -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();

View file

@ -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;

View file

@ -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' },

View file

@ -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],

View file

@ -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>

View file

@ -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;

View file

@ -92,6 +92,9 @@ const getters = {
value => value.id === Number(conversationId)
);
},
getConversationParticipants: _state => {
return _state.conversationParticipants;
},
};
export default getters;

View file

@ -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 {

View file

@ -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',

View file

@ -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

View file

@ -33,7 +33,7 @@ class ConversationParticipant < ApplicationRecord
before_validation :ensure_account_id
private
def ensure_account_id
self.account_id = conversation&.account_id
end

View file

@ -1,3 +1,3 @@
json.array! @participants do |participant|
json.partial! 'api/v1/models/agent.json.jbuilder', resource: participant.user
end
end

View file

@ -1,3 +1,3 @@
json.array! @participants do |participant|
json.partial! 'api/v1/models/agent.json.jbuilder', resource: participant.user
end
end

View file

@ -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

View file

@ -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