diff --git a/app/controllers/api/v1/accounts/agents_controller.rb b/app/controllers/api/v1/accounts/agents_controller.rb
index ed9d3ebc8..83f1ad186 100644
--- a/app/controllers/api/v1/accounts/agents_controller.rb
+++ b/app/controllers/api/v1/accounts/agents_controller.rb
@@ -64,6 +64,6 @@ class Api::V1::Accounts::AgentsController < Api::V1::Accounts::BaseController
end
def agents
- @agents ||= Current.account.users.order_by_full_name
+ @agents ||= Current.account.users.order_by_full_name.includes({ avatar_attachment: [:blob] })
end
end
diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb
index 55428b24a..c1e90d957 100644
--- a/app/controllers/api/v1/accounts/contacts_controller.rb
+++ b/app/controllers/api/v1/accounts/contacts_controller.rb
@@ -1,17 +1,32 @@
class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
+ RESULTS_PER_PAGE = 15
protect_from_forgery with: :null_session
before_action :check_authorization
+ before_action :set_current_page, only: [:index, :active, :search]
before_action :fetch_contact, only: [:show, :update]
def index
- @contacts = Current.account.contacts
+ contacts = Current.account.contacts.where.not(email: [nil, '']).or(Current.account.contacts.where.not(phone_number: [nil, '']))
+ @contacts_count = contacts.count
+ @contacts = fetch_contact_last_seen_at(contacts)
+ end
+
+ def search
+ render json: { error: 'Specify search string with parameter q' }, status: :unprocessable_entity if params[:q].blank? && return
+
+ contacts = Current.account.contacts.where.not(email: [nil, '']).or(Current.account.contacts.where.not(phone_number: [nil, '']))
+ .where('name LIKE :search OR email LIKE :search', search: "%#{params[:q]}%")
+ @contacts_count = contacts.count
+ @contacts = fetch_contact_last_seen_at(contacts)
end
# returns online contacts
def active
- @contacts = Current.account.contacts.where(id: ::OnlineStatusTracker
+ contacts = Current.account.contacts.where(id: ::OnlineStatusTracker
.get_available_contact_ids(Current.account.id))
+ @contacts_count = contacts.count
+ @contacts = contacts.page(@current_page)
end
def show; end
@@ -36,13 +51,19 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
}, status: :unprocessable_entity
end
- def search
- render json: { error: 'Specify search string with parameter q' }, status: :unprocessable_entity if params[:q].blank? && return
+ private
- @contacts = Current.account.contacts.where('name LIKE :search OR email LIKE :search', search: "%#{params[:q]}%")
+ def set_current_page
+ @current_page = params[:page] || 1
end
- private
+ def fetch_contact_last_seen_at(contacts)
+ contacts.left_outer_joins(:conversations)
+ .select('contacts.*, COUNT(conversations.id) as conversations_count, MAX(conversations.contact_last_seen_at) as last_seen_at')
+ .group('contacts.id')
+ .includes([{ avatar_attachment: [:blob] }, { contact_inboxes: [:inbox] }])
+ .page(@current_page).per(RESULTS_PER_PAGE)
+ end
def build_contact_inbox
return if params[:inbox_id].blank?
diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb
index a2a879376..0f573bbb9 100644
--- a/app/controllers/api/v1/accounts/inboxes_controller.rb
+++ b/app/controllers/api/v1/accounts/inboxes_controller.rb
@@ -4,7 +4,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
before_action :check_authorization
def index
- @inboxes = policy_scope(Current.account.inboxes.order_by_name.includes(:channel, :avatar_attachment))
+ @inboxes = policy_scope(Current.account.inboxes.order_by_name.includes(:channel, { avatar_attachment: [:blob] }))
end
def create
diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb
index 051b25679..2aa7cb321 100644
--- a/app/finders/conversation_finder.rb
+++ b/app/finders/conversation_finder.rb
@@ -62,9 +62,7 @@ class ConversationFinder
end
def find_all_conversations
- @conversations = current_account.conversations.includes(
- :assignee, :inbox, :taggings, contact: [:avatar_attachment]
- ).where(inbox_id: @inbox_ids)
+ @conversations = current_account.conversations.where(inbox_id: @inbox_ids)
end
def filter_by_assignee_type
@@ -106,6 +104,9 @@ class ConversationFinder
end
def conversations
+ @conversations = @conversations.includes(
+ :taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } }
+ )
current_page ? @conversations.latest.page(current_page) : @conversations.latest
end
end
diff --git a/app/finders/message_finder.rb b/app/finders/message_finder.rb
index 5295dce74..63c72bd8e 100644
--- a/app/finders/message_finder.rb
+++ b/app/finders/message_finder.rb
@@ -11,7 +11,7 @@ class MessageFinder
private
def conversation_messages
- @conversation.messages.includes(:attachments, :sender)
+ @conversation.messages.includes(:attachments, :sender, sender: { avatar_attachment: [:blob] })
end
def messages
diff --git a/app/javascript/dashboard/api/contacts.js b/app/javascript/dashboard/api/contacts.js
index 0988141d3..6429c53d2 100644
--- a/app/javascript/dashboard/api/contacts.js
+++ b/app/javascript/dashboard/api/contacts.js
@@ -6,9 +6,17 @@ class ContactAPI extends ApiClient {
super('contacts', { accountScoped: true });
}
+ get(page) {
+ return axios.get(`${this.url}?page=${page}`);
+ }
+
getConversations(contactId) {
return axios.get(`${this.url}/${contactId}/conversations`);
}
+
+ search(search = '', page = 1) {
+ return axios.get(`${this.url}/search?q=${search}&page=${page}`);
+ }
}
export default new ContactAPI();
diff --git a/app/javascript/dashboard/assets/scss/_woot.scss b/app/javascript/dashboard/assets/scss/_woot.scss
index 7f5ef39c9..7f9041b93 100644
--- a/app/javascript/dashboard/assets/scss/_woot.scss
+++ b/app/javascript/dashboard/assets/scss/_woot.scss
@@ -26,3 +26,4 @@
@import 'views/signup';
@import 'plugins/multiselect';
+@import 'plugins/dropdown';
diff --git a/app/javascript/dashboard/assets/scss/app.scss b/app/javascript/dashboard/assets/scss/app.scss
index 92edc8e42..3220f0326 100644
--- a/app/javascript/dashboard/assets/scss/app.scss
+++ b/app/javascript/dashboard/assets/scss/app.scss
@@ -2,6 +2,7 @@
@import 'shared/assets/stylesheets/colors';
@import 'shared/assets/stylesheets/spacing';
@import 'shared/assets/stylesheets/font-size';
+@import 'shared/assets/stylesheets/font-weights';
@import 'variables';
@import '~spinkit/scss/spinners/7-three-bounce';
diff --git a/app/javascript/dashboard/assets/scss/plugins/_dropdown.scss b/app/javascript/dashboard/assets/scss/plugins/_dropdown.scss
new file mode 100644
index 000000000..b4dada351
--- /dev/null
+++ b/app/javascript/dashboard/assets/scss/plugins/_dropdown.scss
@@ -0,0 +1,27 @@
+.dropdown-pane.sleek {
+ @include elegant-card;
+ @include border-light;
+ padding-left: 0;
+ padding-right: 0;
+ right: -12px;
+ top: 48px;
+ width: auto;
+
+ &::before {
+ @include arrow(top, var(--color-border-light), 14px);
+ position: absolute;
+ right: 6px;
+ top: -14px;
+ }
+
+ &::after {
+ @include arrow(top, $color-white, var(--space-slab));
+ position: absolute;
+ right: var(--space-small);
+ top: -12px;
+ }
+
+ .dropdown>li>a:hover {
+ background: var(--color-background);
+ }
+}
diff --git a/app/javascript/dashboard/components/layout/Sidebar.vue b/app/javascript/dashboard/components/layout/Sidebar.vue
index 43f4640e0..77b12d9f8 100644
--- a/app/javascript/dashboard/components/layout/Sidebar.vue
+++ b/app/javascript/dashboard/components/layout/Sidebar.vue
@@ -30,7 +30,7 @@