diff --git a/app/controllers/api/v1/accounts/contacts/notes_controller.rb b/app/controllers/api/v1/accounts/contacts/notes_controller.rb index d49f3b838..fb9f3c5c3 100644 --- a/app/controllers/api/v1/accounts/contacts/notes_controller.rb +++ b/app/controllers/api/v1/accounts/contacts/notes_controller.rb @@ -2,7 +2,7 @@ class Api::V1::Accounts::Contacts::NotesController < Api::V1::Accounts::Contacts before_action :note, except: [:index, :create] def index - @notes = @contact.notes.includes(:user) + @notes = @contact.notes.latest.includes(:user) end def create diff --git a/app/javascript/dashboard/api/contactNotes.js b/app/javascript/dashboard/api/contactNotes.js index 9508ea9dc..5be33784a 100644 --- a/app/javascript/dashboard/api/contactNotes.js +++ b/app/javascript/dashboard/api/contactNotes.js @@ -2,7 +2,27 @@ import ApiClient from './ApiClient'; class ContactNotes extends ApiClient { constructor() { - super('contact_notes', { accountScoped: true }); + super('notes', { accountScoped: true }); + this.contactId = null; + } + + get url() { + return `${this.baseUrl()}/contacts/${this.contactId}/notes`; + } + + get(contactId) { + this.contactId = contactId; + return super.get(); + } + + create(contactId, content) { + this.contactId = contactId; + return super.create({ content }); + } + + delete(contactId, id) { + this.contactId = contactId; + return super.delete(id); } } diff --git a/app/javascript/dashboard/assets/scss/_foundation-custom.scss b/app/javascript/dashboard/assets/scss/_foundation-custom.scss index 0e2329b66..50b78bf50 100644 --- a/app/javascript/dashboard/assets/scss/_foundation-custom.scss +++ b/app/javascript/dashboard/assets/scss/_foundation-custom.scss @@ -9,7 +9,7 @@ .card { margin-bottom: var(--space-small); - padding: var(--space-small); + padding: var(--space-normal); } .button-wrapper .button.link.grey-btn { diff --git a/app/javascript/dashboard/assets/scss/_helper-classes.scss b/app/javascript/dashboard/assets/scss/_helper-classes.scss index 416ec0185..a8a5a0b93 100644 --- a/app/javascript/dashboard/assets/scss/_helper-classes.scss +++ b/app/javascript/dashboard/assets/scss/_helper-classes.scss @@ -55,6 +55,10 @@ justify-content: space-between; } -.w-100 { +.w-full { width: 100%; } + +.h-full { + height: 100%; +} diff --git a/app/javascript/dashboard/assets/scss/_utility-helpers.scss b/app/javascript/dashboard/assets/scss/_utility-helpers.scss index 60fecb994..8785f1313 100644 --- a/app/javascript/dashboard/assets/scss/_utility-helpers.scss +++ b/app/javascript/dashboard/assets/scss/_utility-helpers.scss @@ -1,3 +1,44 @@ .margin-right-small { margin-right: var(--space-small); } + +.fs-small { + font-size: var(--font-size-small); +} + +.fs-default { + font-size: var(--font-size-default); +} + +.fw-medium { + font-weight: var(--font-weight-medium); +} + +.p-normal { + padding: var(--space-normal); +} + +.overflow-scroll { + overflow: scroll; +} + +.overflow-auto { + overflow: auto; +} + +.overflow-hidden { + overflow: hidden; +} + + +.border-right { + border-right: 1px solid var(--color-border); +} + +.border-left { + border-left: 1px solid var(--color-border); +} + +.bg-white { + background-color: var(--white); +} diff --git a/app/javascript/dashboard/assets/scss/_woot.scss b/app/javascript/dashboard/assets/scss/_woot.scss index 7debac956..e709327dc 100644 --- a/app/javascript/dashboard/assets/scss/_woot.scss +++ b/app/javascript/dashboard/assets/scss/_woot.scss @@ -14,7 +14,6 @@ @import 'helper-classes'; @import 'formulate'; @import 'date-picker'; -@import 'utility-helpers'; @import 'foundation-sites/scss/foundation'; @import '~bourbon/core/bourbon'; @@ -50,3 +49,4 @@ @import 'plugins/multiselect'; @import 'plugins/dropdown'; @import '~shared/assets/stylesheets/ionicons'; +@import 'utility-helpers'; diff --git a/app/javascript/dashboard/assets/scss/widgets/_reports.scss b/app/javascript/dashboard/assets/scss/widgets/_reports.scss index 7280b6d4d..eeb958ff3 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_reports.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_reports.scss @@ -21,10 +21,6 @@ border: 1px solid var(--color-border); } -.margin-right-small { - margin-right: var(--space-small); -} - .display-flex { display: flex; } diff --git a/app/javascript/dashboard/components/widgets/BackButton.vue b/app/javascript/dashboard/components/widgets/BackButton.vue index 9bf87d131..758fd7ae2 100644 --- a/app/javascript/dashboard/components/widgets/BackButton.vue +++ b/app/javascript/dashboard/components/widgets/BackButton.vue @@ -1,6 +1,6 @@ - {{ $t('GENERAL_SETTINGS.BACK') }} + {{ buttonLabel || $t('GENERAL_SETTINGS.BACK') }} - - diff --git a/app/javascript/dashboard/modules/notes/NotesOnContactPage.vue b/app/javascript/dashboard/modules/notes/NotesOnContactPage.vue index 256f9435e..6ce3d9c1a 100644 --- a/app/javascript/dashboard/modules/notes/NotesOnContactPage.vue +++ b/app/javascript/dashboard/modules/notes/NotesOnContactPage.vue @@ -1,8 +1,14 @@ - + - - diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactInfoPanel.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactInfoPanel.vue index 041052a09..3c32b7212 100644 --- a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactInfoPanel.vue +++ b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactInfoPanel.vue @@ -1,9 +1,17 @@ - - + + - + {}, }, + showAvatar: { + type: Boolean, + default: true, + }, }, computed: { hasContactAttributes() { @@ -85,7 +97,7 @@ export default { overflow-y: auto; overflow: auto; position: relative; - border-left: 1px solid var(--color-border); + border-right: 1px solid var(--color-border); } .close-button { diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsTable.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsTable.vue index 0cc41d17e..4612c99be 100644 --- a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsTable.vue +++ b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsTable.vue @@ -28,6 +28,7 @@ diff --git a/app/javascript/dashboard/routes/dashboard/contacts/routes.js b/app/javascript/dashboard/routes/dashboard/contacts/routes.js index b8b129435..054b16d7c 100644 --- a/app/javascript/dashboard/routes/dashboard/contacts/routes.js +++ b/app/javascript/dashboard/routes/dashboard/contacts/routes.js @@ -21,7 +21,7 @@ export const routes = [ }, { path: frontendURL('accounts/:accountId/contacts/:contactId'), - name: 'contacts_dashboard_manage', + name: 'contact_profile_dashboard', roles: ['administrator', 'agent'], component: ContactManageView, props: route => { diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue index 8d30ac944..8f7bbc91e 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue @@ -222,9 +222,8 @@ export default { return this.additionalAttributes.initiated_at; }, browserName() { - return `${this.browser.browser_name || ''} ${ - this.browser.browser_version || '' - }`; + return `${this.browser.browser_name || ''} ${this.browser + .browser_version || ''}`; }, contactAdditionalAttributes() { return this.contact.additional_attributes || {}; @@ -248,8 +247,10 @@ export default { return `${cityAndCountry} ${countryFlag}`; }, platformName() { - const { platform_name: platformName, platform_version: platformVersion } = - this.browser; + const { + platform_name: platformName, + platform_version: platformVersion, + } = this.browser; return `${platformName || ''} ${platformVersion || ''}`; }, channelType() { @@ -403,7 +404,7 @@ export default { ::v-deep { .contact--profile { padding-bottom: var(--space-slab); - border-bottom: 1px solid var(--color-border-light); + border-bottom: 1px solid var(--color-border); } .conversation--actions .multiselect-wrap--small { .multiselect { diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue index 829b28e18..8800a6fc7 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue @@ -2,6 +2,7 @@ - - {{ contact.name }} + + + {{ contact.name }} + + {{ additionalAttributes.description }} @@ -25,7 +34,6 @@ :title="$t('CONTACT_PANEL.EMAIL_ADDRESS')" show-copy /> - - - + + + {{ headerTitle }} contactId => { - return _state.records[contactId] || []; + const records = _state.records[contactId] || []; + return records.sort((r1, r2) => r2.id - r1.id); }, getUIFlags(_state) { return _state.uiFlags; @@ -22,76 +23,58 @@ export const getters = { export const actions = { async get({ commit }, { contactId }) { - commit(types.default.SET_CONTACT_NOTES_UI_FLAG, { - isFetching: true, - }); + commit(types.SET_CONTACT_NOTES_UI_FLAG, { isFetching: true }); try { const { data } = await ContactNotesAPI.get(contactId); - commit(types.default.SET_CONTACT_NOTES, { contactId, data }); + commit(types.SET_CONTACT_NOTES, { contactId, data }); } catch (error) { throw new Error(error); } finally { - commit(types.default.SET_CONTACT_NOTES_UI_FLAG, { - isFetching: false, - }); + commit(types.SET_CONTACT_NOTES_UI_FLAG, { isFetching: false }); } }, async create({ commit }, { contactId, content }) { - commit(types.default.SET_CONTACT_NOTES_UI_FLAG, { - isCreating: true, - }); + commit(types.SET_CONTACT_NOTES_UI_FLAG, { isCreating: true }); try { - const { data } = await ContactNotesAPI.create({ - content, - contactId, - }); - commit(types.default.ADD_CONTACT_NOTE, { - contactId, - data, - }); + const { data } = await ContactNotesAPI.create(contactId, content); + commit(types.ADD_CONTACT_NOTE, { contactId, data }); } catch (error) { throw new Error(error); } finally { - commit(types.default.SET_CONTACT_NOTES_UI_FLAG, { - isCreating: false, - }); + commit(types.SET_CONTACT_NOTES_UI_FLAG, { isCreating: false }); } }, async delete({ commit }, { noteId, contactId }) { - commit(types.default.SET_CONTACT_NOTES_UI_FLAG, { - isDeleting: true, - }); + commit(types.SET_CONTACT_NOTES_UI_FLAG, { isDeleting: true }); try { await ContactNotesAPI.delete(contactId, noteId); - commit(types.default.DELETE_CONTACT_NOTE, { contactId, noteId }); + commit(types.DELETE_CONTACT_NOTE, { contactId, noteId }); } catch (error) { throw new Error(error); } finally { - commit(types.default.SET_CONTACT_NOTES_UI_FLAG, { - isDeleting: false, - }); + commit(types.SET_CONTACT_NOTES_UI_FLAG, { isDeleting: false }); } }, }; export const mutations = { - [types.default.SET_CONTACT_NOTES_UI_FLAG](_state, data) { + [types.SET_CONTACT_NOTES_UI_FLAG](_state, data) { _state.uiFlags = { ..._state.uiFlags, ...data, }; }, - [types.default.SET_CONTACT_NOTES]($state, { data, contactId }) { + [types.SET_CONTACT_NOTES]($state, { data, contactId }) { Vue.set($state.records, contactId, data); }, - [types.default.ADD_CONTACT_NOTE]($state, { data, contactId }) { + [types.ADD_CONTACT_NOTE]($state, { data, contactId }) { const contactNotes = $state.records[contactId] || []; $state.records[contactId] = [...contactNotes, data]; }, - [types.default.DELETE_CONTACT_NOTE]($state, { noteId, contactId }) { + [types.DELETE_CONTACT_NOTE]($state, { noteId, contactId }) { const contactNotes = $state.records[contactId]; const withoutDeletedNote = contactNotes.filter(note => note.id !== noteId); $state.records[contactId] = [...withoutDeletedNote]; diff --git a/app/javascript/shared/helpers/KeyboardHelpers.js b/app/javascript/shared/helpers/KeyboardHelpers.js index f5ff556c6..c9a6c6233 100644 --- a/app/javascript/shared/helpers/KeyboardHelpers.js +++ b/app/javascript/shared/helpers/KeyboardHelpers.js @@ -10,6 +10,10 @@ export const hasPressedShift = e => { return e.shiftKey; }; +export const hasPressedCommandAndEnter = e => { + return e.metaKey && e.keyCode === 13; +}; + export const hasPressedCommandAndForwardSlash = e => { return e.metaKey && e.keyCode === 191; }; diff --git a/app/models/note.rb b/app/models/note.rb index fff7d3780..114a6fdbb 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -33,6 +33,8 @@ class Note < ApplicationRecord belongs_to :contact belongs_to :user + scope :latest, -> { order(created_at: :desc) } + private def ensure_account_id diff --git a/app/views/api/v1/models/_note.json.jbuilder b/app/views/api/v1/models/_note.json.jbuilder index 93a6a615e..3640bbc2c 100644 --- a/app/views/api/v1/models/_note.json.jbuilder +++ b/app/views/api/v1/models/_note.json.jbuilder @@ -2,8 +2,10 @@ json.id resource.id json.content resource.content json.account_id json.account_id json.contact_id json.contact_id -json.user do - json.partial! 'api/v1/models/agent.json.jbuilder', resource: resource.user +if resource.user.present? + json.user do + json.partial! 'api/v1/models/agent.json.jbuilder', resource: resource.user + end end -json.created_at resource.created_at -json.updated_at resource.updated_at +json.created_at resource.created_at.to_i +json.updated_at resource.updated_at.to_i diff --git a/yarn.lock b/yarn.lock index e68f62084..8750fafe6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4309,9 +4309,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001214: - version "1.0.30001219" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001219.tgz#5bfa5d0519f41f993618bd318f606a4c4c16156b" - integrity sha512-c0yixVG4v9KBc/tQ2rlbB3A/bgBFRvl8h8M4IeUbqCca4gsiCfvtaheUssbnux/Mb66Vjz7x8yYjDgYcNQOhyQ== + version "1.0.30001271" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001271.tgz" + integrity sha512-BBruZFWmt3HFdVPS8kceTBIguKxu4f99n5JNp06OlPD/luoAMIaIK5ieV5YjnBLH3Nysai9sxj9rpJj4ZisXOA== capture-exit@^2.0.0: version "2.0.0"
{{ additionalAttributes.description }} @@ -25,7 +34,6 @@ :title="$t('CONTACT_PANEL.EMAIL_ADDRESS')" show-copy /> - - - + + + {{ headerTitle }} contactId => { - return _state.records[contactId] || []; + const records = _state.records[contactId] || []; + return records.sort((r1, r2) => r2.id - r1.id); }, getUIFlags(_state) { return _state.uiFlags; @@ -22,76 +23,58 @@ export const getters = { export const actions = { async get({ commit }, { contactId }) { - commit(types.default.SET_CONTACT_NOTES_UI_FLAG, { - isFetching: true, - }); + commit(types.SET_CONTACT_NOTES_UI_FLAG, { isFetching: true }); try { const { data } = await ContactNotesAPI.get(contactId); - commit(types.default.SET_CONTACT_NOTES, { contactId, data }); + commit(types.SET_CONTACT_NOTES, { contactId, data }); } catch (error) { throw new Error(error); } finally { - commit(types.default.SET_CONTACT_NOTES_UI_FLAG, { - isFetching: false, - }); + commit(types.SET_CONTACT_NOTES_UI_FLAG, { isFetching: false }); } }, async create({ commit }, { contactId, content }) { - commit(types.default.SET_CONTACT_NOTES_UI_FLAG, { - isCreating: true, - }); + commit(types.SET_CONTACT_NOTES_UI_FLAG, { isCreating: true }); try { - const { data } = await ContactNotesAPI.create({ - content, - contactId, - }); - commit(types.default.ADD_CONTACT_NOTE, { - contactId, - data, - }); + const { data } = await ContactNotesAPI.create(contactId, content); + commit(types.ADD_CONTACT_NOTE, { contactId, data }); } catch (error) { throw new Error(error); } finally { - commit(types.default.SET_CONTACT_NOTES_UI_FLAG, { - isCreating: false, - }); + commit(types.SET_CONTACT_NOTES_UI_FLAG, { isCreating: false }); } }, async delete({ commit }, { noteId, contactId }) { - commit(types.default.SET_CONTACT_NOTES_UI_FLAG, { - isDeleting: true, - }); + commit(types.SET_CONTACT_NOTES_UI_FLAG, { isDeleting: true }); try { await ContactNotesAPI.delete(contactId, noteId); - commit(types.default.DELETE_CONTACT_NOTE, { contactId, noteId }); + commit(types.DELETE_CONTACT_NOTE, { contactId, noteId }); } catch (error) { throw new Error(error); } finally { - commit(types.default.SET_CONTACT_NOTES_UI_FLAG, { - isDeleting: false, - }); + commit(types.SET_CONTACT_NOTES_UI_FLAG, { isDeleting: false }); } }, }; export const mutations = { - [types.default.SET_CONTACT_NOTES_UI_FLAG](_state, data) { + [types.SET_CONTACT_NOTES_UI_FLAG](_state, data) { _state.uiFlags = { ..._state.uiFlags, ...data, }; }, - [types.default.SET_CONTACT_NOTES]($state, { data, contactId }) { + [types.SET_CONTACT_NOTES]($state, { data, contactId }) { Vue.set($state.records, contactId, data); }, - [types.default.ADD_CONTACT_NOTE]($state, { data, contactId }) { + [types.ADD_CONTACT_NOTE]($state, { data, contactId }) { const contactNotes = $state.records[contactId] || []; $state.records[contactId] = [...contactNotes, data]; }, - [types.default.DELETE_CONTACT_NOTE]($state, { noteId, contactId }) { + [types.DELETE_CONTACT_NOTE]($state, { noteId, contactId }) { const contactNotes = $state.records[contactId]; const withoutDeletedNote = contactNotes.filter(note => note.id !== noteId); $state.records[contactId] = [...withoutDeletedNote]; diff --git a/app/javascript/shared/helpers/KeyboardHelpers.js b/app/javascript/shared/helpers/KeyboardHelpers.js index f5ff556c6..c9a6c6233 100644 --- a/app/javascript/shared/helpers/KeyboardHelpers.js +++ b/app/javascript/shared/helpers/KeyboardHelpers.js @@ -10,6 +10,10 @@ export const hasPressedShift = e => { return e.shiftKey; }; +export const hasPressedCommandAndEnter = e => { + return e.metaKey && e.keyCode === 13; +}; + export const hasPressedCommandAndForwardSlash = e => { return e.metaKey && e.keyCode === 191; }; diff --git a/app/models/note.rb b/app/models/note.rb index fff7d3780..114a6fdbb 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -33,6 +33,8 @@ class Note < ApplicationRecord belongs_to :contact belongs_to :user + scope :latest, -> { order(created_at: :desc) } + private def ensure_account_id diff --git a/app/views/api/v1/models/_note.json.jbuilder b/app/views/api/v1/models/_note.json.jbuilder index 93a6a615e..3640bbc2c 100644 --- a/app/views/api/v1/models/_note.json.jbuilder +++ b/app/views/api/v1/models/_note.json.jbuilder @@ -2,8 +2,10 @@ json.id resource.id json.content resource.content json.account_id json.account_id json.contact_id json.contact_id -json.user do - json.partial! 'api/v1/models/agent.json.jbuilder', resource: resource.user +if resource.user.present? + json.user do + json.partial! 'api/v1/models/agent.json.jbuilder', resource: resource.user + end end -json.created_at resource.created_at -json.updated_at resource.updated_at +json.created_at resource.created_at.to_i +json.updated_at resource.updated_at.to_i diff --git a/yarn.lock b/yarn.lock index e68f62084..8750fafe6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4309,9 +4309,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001214: - version "1.0.30001219" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001219.tgz#5bfa5d0519f41f993618bd318f606a4c4c16156b" - integrity sha512-c0yixVG4v9KBc/tQ2rlbB3A/bgBFRvl8h8M4IeUbqCca4gsiCfvtaheUssbnux/Mb66Vjz7x8yYjDgYcNQOhyQ== + version "1.0.30001271" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001271.tgz" + integrity sha512-BBruZFWmt3HFdVPS8kceTBIguKxu4f99n5JNp06OlPD/luoAMIaIK5ieV5YjnBLH3Nysai9sxj9rpJj4ZisXOA== capture-exit@^2.0.0: version "2.0.0"