feat: Update design of Contacts table (#1753)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
parent
03bc4bf224
commit
89e5f18dfb
9 changed files with 243 additions and 126 deletions
|
@ -44,3 +44,7 @@ code {
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -111,12 +111,18 @@
|
|||
"LIST": {
|
||||
"LOADING_MESSAGE": "Loading contacts...",
|
||||
"404": "No contacts matches your search 🔍",
|
||||
"TABLE_HEADER": [
|
||||
"Name",
|
||||
"Phone Number",
|
||||
"Conversations",
|
||||
"Last Contacted"
|
||||
]
|
||||
"TABLE_HEADER": {
|
||||
"NAME": "Name",
|
||||
"PHONE_NUMBER": "Phone Number",
|
||||
"CONVERSATIONS": "Conversations",
|
||||
"LAST_ACTIVITY": "Last Activity",
|
||||
"COUNTRY": "Country",
|
||||
"CITY": "City",
|
||||
"SOCIAL_PROFILES": "Social Profiles",
|
||||
"COMPANY": "Company",
|
||||
"EMAIL_ADDRESS": "Email Address"
|
||||
},
|
||||
"VIEW_DETAILS": "View details"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,53 +1,14 @@
|
|||
<template>
|
||||
<section class="contacts-table-wrap">
|
||||
<table class="woot-table contacts-table">
|
||||
<thead>
|
||||
<th
|
||||
v-for="thHeader in $t('CONTACTS_PAGE.LIST.TABLE_HEADER')"
|
||||
:key="thHeader"
|
||||
>
|
||||
{{ thHeader }}
|
||||
</th>
|
||||
</thead>
|
||||
<tbody v-show="showTableData">
|
||||
<tr
|
||||
v-for="contactItem in contacts"
|
||||
:key="contactItem.id"
|
||||
:class="{ 'is-active': contactItem.id === activeContactId }"
|
||||
@click="() => onClickContact(contactItem.id)"
|
||||
>
|
||||
<td>
|
||||
<div class="row-main-info">
|
||||
<thumbnail
|
||||
:src="contactItem.thumbnail"
|
||||
size="36px"
|
||||
:username="contactItem.name"
|
||||
:status="contactItem.availability_status"
|
||||
<ve-table
|
||||
:fixed-header="true"
|
||||
max-height="calc(100vh - 11.4rem)"
|
||||
scroll-width="187rem"
|
||||
:columns="columns"
|
||||
:table-data="tableData"
|
||||
:border-around="false"
|
||||
/>
|
||||
<div>
|
||||
<h4 class="sub-block-title user-name">
|
||||
{{ contactItem.name }}
|
||||
</h4>
|
||||
<p class="user-email">
|
||||
{{ contactItem.email || '---' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ contactItem.phone_number || '---' }}</td>
|
||||
<td class="conversation-count-item">
|
||||
{{ contactItem.conversations_count }}
|
||||
</td>
|
||||
<td>
|
||||
{{
|
||||
contactItem.last_seen_at
|
||||
? dynamicTime(contactItem.last_seen_at)
|
||||
: '---'
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<empty-state
|
||||
v-if="showSearchEmptyState"
|
||||
:title="$t('CONTACTS_PAGE.LIST.404')"
|
||||
|
@ -61,6 +22,8 @@
|
|||
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { VeTable } from 'vue-easytable';
|
||||
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
||||
|
@ -68,9 +31,9 @@ import timeMixin from 'dashboard/mixins/time';
|
|||
|
||||
export default {
|
||||
components: {
|
||||
Thumbnail,
|
||||
EmptyState,
|
||||
Spinner,
|
||||
VeTable,
|
||||
},
|
||||
mixins: [clickaway, timeMixin],
|
||||
props: {
|
||||
|
@ -95,30 +58,150 @@ export default {
|
|||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
columns: [
|
||||
{
|
||||
field: 'name',
|
||||
key: 'name',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.NAME'),
|
||||
fixed: 'left',
|
||||
align: 'left',
|
||||
width: 300,
|
||||
renderBodyCell: ({ row }) => (
|
||||
<button
|
||||
class="row--user-block cursor-pointer"
|
||||
onClick={() => this.onClickContact(row.id)}
|
||||
>
|
||||
<Thumbnail
|
||||
src={row.thumbnail}
|
||||
size="36px"
|
||||
username={row.name}
|
||||
status={row.availability_status}
|
||||
/>
|
||||
<div>
|
||||
<h6 class="sub-block-title user-name text-truncate">
|
||||
{row.name}
|
||||
</h6>
|
||||
<button class="button clear small">
|
||||
{this.$t('CONTACTS_PAGE.LIST.VIEW_DETAILS')}
|
||||
</button>
|
||||
</div>
|
||||
</button>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'email',
|
||||
key: 'email',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.EMAIL_ADDRESS'),
|
||||
align: 'left',
|
||||
width: 240,
|
||||
renderBodyCell: ({ row }) => {
|
||||
if (row.email)
|
||||
return (
|
||||
<div class="text-truncate">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
href={`mailto:${row.email}`}
|
||||
>
|
||||
{row.email}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
return '---';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'phone',
|
||||
key: 'phone',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.PHONE_NUMBER'),
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
field: 'company',
|
||||
key: 'company',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.COMPANY'),
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
field: 'city',
|
||||
key: 'city',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.CITY'),
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
field: 'country',
|
||||
key: 'country',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.COUNTRY'),
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
field: 'profiles',
|
||||
key: 'profiles',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.SOCIAL_PROFILES'),
|
||||
align: 'left',
|
||||
renderBodyCell: ({ row }) => {
|
||||
const { profiles } = row;
|
||||
|
||||
const items = Object.keys(profiles);
|
||||
|
||||
if (!items.length) return '---';
|
||||
|
||||
return (
|
||||
<div class="cell--social-profiles">
|
||||
{items.map(
|
||||
profile =>
|
||||
profiles[profile] && (
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer nofollow"
|
||||
href={`https://${profile}.com/${profiles[profile]}`}
|
||||
>
|
||||
<i class={`ion-social-${profile}`} />
|
||||
</a>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'lastSeen',
|
||||
key: 'lastSeen',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.LAST_ACTIVITY'),
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
field: 'conversationsCount',
|
||||
key: 'conversationsCount',
|
||||
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.CONVERSATIONS'),
|
||||
width: 150,
|
||||
align: 'left',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
currentRoute() {
|
||||
return ' ';
|
||||
},
|
||||
sidebarClassName() {
|
||||
if (this.isOnDesktop) {
|
||||
return '';
|
||||
tableData() {
|
||||
if (this.isLoading) {
|
||||
return [];
|
||||
}
|
||||
if (this.isSidebarOpen) {
|
||||
return 'off-canvas is-open ';
|
||||
}
|
||||
return 'off-canvas position-left is-transition-push is-closed';
|
||||
},
|
||||
contentClassName() {
|
||||
if (this.isOnDesktop) {
|
||||
return '';
|
||||
}
|
||||
if (this.isSidebarOpen) {
|
||||
return 'off-canvas-content is-open-left has-transition-push has-position-left';
|
||||
}
|
||||
return 'off-canvas-content';
|
||||
},
|
||||
showTableData() {
|
||||
return !this.showSearchEmptyState && !this.isLoading;
|
||||
return this.contacts.map(item => {
|
||||
const additional = item.additional_attributes || {};
|
||||
const { last_seen_at: lastSeenAt } = item;
|
||||
return {
|
||||
...item,
|
||||
phone: item.phone_number || '---',
|
||||
company: additional.company_name || '---',
|
||||
location: additional.location || '---',
|
||||
profiles: additional.social_profiles || {},
|
||||
city: additional.city || '---',
|
||||
country: additional.country || '---',
|
||||
conversationsCount: item.conversations_count || '---',
|
||||
lastSeen: lastSeenAt ? this.dynamicTime(lastSeenAt) : '---',
|
||||
};
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -128,52 +211,19 @@ export default {
|
|||
@import '~dashboard/assets/scss/mixins';
|
||||
|
||||
.contacts-table-wrap {
|
||||
@include scroll-on-hover;
|
||||
flex: 1 1;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.contacts-table {
|
||||
margin-top: -1px;
|
||||
|
||||
> thead {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: white;
|
||||
|
||||
> th:first-child {
|
||||
padding-left: var(--space-medium);
|
||||
width: 30%;
|
||||
.contacts-table-wrap::v-deep {
|
||||
.ve-table {
|
||||
padding-bottom: var(--space-large);
|
||||
}
|
||||
}
|
||||
|
||||
> tbody {
|
||||
> tr {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: var(--b-50);
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
background: var(--b-100);
|
||||
}
|
||||
|
||||
> td {
|
||||
padding: var(--space-slab);
|
||||
|
||||
&:first-child {
|
||||
padding-left: var(--space-medium);
|
||||
}
|
||||
|
||||
&.conversation-count-item {
|
||||
padding-left: var(--space-medium);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.row-main-info {
|
||||
display: flex;
|
||||
.row--user-block {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
text-align: left;
|
||||
|
||||
.user-thumbnail-box {
|
||||
margin-right: var(--space-small);
|
||||
|
@ -189,13 +239,35 @@ export default {
|
|||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ve-table-header-th {
|
||||
padding: var(--space-small) var(--space-two) !important;
|
||||
}
|
||||
|
||||
.ve-table-body-td {
|
||||
padding: var(--space-slab) var(--space-two) !important;
|
||||
}
|
||||
|
||||
.ve-table-header-th {
|
||||
font-size: var(--font-size-mini) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.contacts--loader {
|
||||
font-size: var(--font-size-default);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: var(--font-size-default);
|
||||
justify-content: center;
|
||||
padding: var(--space-big);
|
||||
}
|
||||
|
||||
.cell--social-profiles {
|
||||
a {
|
||||
color: var(--s-300);
|
||||
display: inline-block;
|
||||
font-size: var(--font-size-medium);
|
||||
min-width: var(--space-large);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -141,7 +141,6 @@ export default {
|
|||
.left-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: var(--space-normal);
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -69,11 +69,6 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* TODO-REM; Change variables sizing to rem after html font size change from 1.0 t0 1.6 */
|
||||
|
||||
.header {
|
||||
padding: 0 var(--space-medium);
|
||||
}
|
||||
.page-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -81,7 +76,14 @@ export default {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
margin-bottom: var(--space-slab);
|
||||
padding: var(--space-small) var(--space-small) var(--space-small)
|
||||
var(--space-normal);
|
||||
}
|
||||
|
||||
.left-aligned-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right-aligned-wrap {
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
registerSubscription,
|
||||
} from '../dashboard/helper/pushHelper';
|
||||
import * as Sentry from '@sentry/vue';
|
||||
import 'vue-easytable/libs/theme-default/index.css';
|
||||
|
||||
Vue.config.env = process.env;
|
||||
|
||||
|
|
|
@ -8,6 +8,10 @@ const resolve = {
|
|||
widget: path.resolve('./app/javascript/widget'),
|
||||
assets: path.resolve('./app/javascript/dashboard/assets'),
|
||||
components: path.resolve('./app/javascript/dashboard/components'),
|
||||
'./iconfont.eot': 'vue-easytable/libs/font/iconfont.eot',
|
||||
'./iconfont.woff': 'vue-easytable/libs/font/iconfont.woff',
|
||||
'./iconfont.ttf': 'vue-easytable/libs/font/iconfont.ttf',
|
||||
'./iconfont.svg': 'vue-easytable/libs/font/iconfont.svg',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
"vue-chartjs": "^3.4.2",
|
||||
"vue-clickaway": "~2.1.0",
|
||||
"vue-color": "^2.7.1",
|
||||
"vue-easytable": "^2.1.2",
|
||||
"vue-i18n": "^8.22.1",
|
||||
"vue-loader": "^15.7.0",
|
||||
"vue-multiselect": "~2.1.6",
|
||||
|
|
28
yarn.lock
28
yarn.lock
|
@ -9584,6 +9584,11 @@ requires-port@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
resize-observer-polyfill@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||
|
||||
resolve-cwd@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
||||
|
@ -11224,6 +11229,16 @@ vue-color@^2.7.1:
|
|||
material-colors "^1.0.0"
|
||||
tinycolor2 "^1.1.2"
|
||||
|
||||
vue-easytable@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vue-easytable/-/vue-easytable-2.1.2.tgz#6cb22138b588195ee25ad37bfd660b48ce996a3a"
|
||||
integrity sha512-x/88KdYABxbuJJlw6rAz8SAEJwoicOwUCgwubN/ibTuKT4+dDrFJnqW5LJHEhMWa+VIvTZnmBXZTobkyuak7hw==
|
||||
dependencies:
|
||||
lodash "^4.17.20"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
vue "^2.6.12"
|
||||
vue-template-compiler "^2.6.11"
|
||||
|
||||
vue-eslint-parser@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-7.0.0.tgz#a4ed2669f87179dedd06afdd8736acbb3a3864d6"
|
||||
|
@ -11304,6 +11319,14 @@ vue-template-compiler@^2.6.10:
|
|||
de-indent "^1.0.2"
|
||||
he "^1.1.0"
|
||||
|
||||
vue-template-compiler@^2.6.11:
|
||||
version "2.6.12"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz#947ed7196744c8a5285ebe1233fe960437fcc57e"
|
||||
integrity sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==
|
||||
dependencies:
|
||||
de-indent "^1.0.2"
|
||||
he "^1.1.0"
|
||||
|
||||
vue-template-es2015-compiler@^1.6.0, vue-template-es2015-compiler@^1.9.0:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
||||
|
@ -11319,6 +11342,11 @@ vue@^2.6.0:
|
|||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.11.tgz#76594d877d4b12234406e84e35275c6d514125c5"
|
||||
integrity sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==
|
||||
|
||||
vue@^2.6.12:
|
||||
version "2.6.12"
|
||||
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123"
|
||||
integrity sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==
|
||||
|
||||
vuelidate@~0.7.5:
|
||||
version "0.7.5"
|
||||
resolved "https://registry.yarnpkg.com/vuelidate/-/vuelidate-0.7.5.tgz#ff48c75ae9d24ea24c24e9ea08065eda0a0cba0a"
|
||||
|
|
Loading…
Reference in a new issue