iachat/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsTable.vue
Shivam Mishra 48bf8d08e5
feat: Update dependencies and fix import syntax for Vite migration (#7959)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2023-09-21 17:55:54 +05:30

328 lines
9.1 KiB
Vue

<template>
<section
class="contacts-table-wrap bg-white dark:bg-slate-900 flex-1 h-full overflow-hidden -mt-1"
>
<ve-table
:fixed-header="true"
max-height="calc(100vh - 7.125rem)"
scroll-width="187rem"
:columns="columns"
:table-data="tableData"
:border-around="false"
:sort-option="sortOption"
/>
<empty-state
v-if="showSearchEmptyState"
:title="$t('CONTACTS_PAGE.LIST.404')"
/>
<empty-state
v-else-if="!isLoading && !contacts.length"
:title="$t('CONTACTS_PAGE.LIST.NO_CONTACTS')"
/>
<div v-if="isLoading" class="items-center flex text-base justify-center">
<spinner />
<span>{{ $t('CONTACTS_PAGE.LIST.LOADING_MESSAGE') }}</span>
</div>
</section>
</template>
<script>
import { mixin as clickaway } from 'vue-clickaway';
import { VeTable } from 'vue-easytable';
import { getCountryFlag } from 'dashboard/helper/flag';
import Spinner from 'shared/components/Spinner.vue';
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
import timeMixin from 'dashboard/mixins/time';
import rtlMixin from 'shared/mixins/rtlMixin';
import FluentIcon from 'shared/components/FluentIcon/DashboardIcon.vue';
export default {
components: {
EmptyState,
Spinner,
VeTable,
},
mixins: [clickaway, timeMixin, rtlMixin],
props: {
contacts: {
type: Array,
default: () => [],
},
showSearchEmptyState: {
type: Boolean,
default: false,
},
onClickContact: {
type: Function,
default: () => {},
},
isLoading: {
type: Boolean,
default: false,
},
activeContactId: {
type: [String, Number],
default: '',
},
sortParam: {
type: String,
default: 'last_activity_at',
},
sortOrder: {
type: String,
default: 'desc',
},
},
data() {
return {
sortConfig: {},
sortOption: {
sortAlways: true,
sortChange: params => this.$emit('on-sort-change', params),
},
};
},
computed: {
tableData() {
if (this.isLoading) {
return [];
}
return this.contacts.map(item => {
// Note: The attributes used here is in snake case
// as it simplier the sort attribute calculation
const additional = item.additional_attributes || {};
const { last_activity_at: lastActivityAt } = item;
const { created_at: createdAt } = item;
return {
...item,
phone_number: item.phone_number || '---',
company: additional.company_name || '---',
profiles: additional.social_profiles || {},
city: additional.city || '---',
country: additional.country,
countryCode: additional.country_code,
conversationsCount: item.conversations_count || '---',
last_activity_at: lastActivityAt
? this.dynamicTime(lastActivityAt)
: '---',
created_at: createdAt ? this.dynamicTime(createdAt) : '---',
};
});
},
columns() {
return [
{
field: 'name',
key: 'name',
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.NAME'),
fixed: 'left',
align: this.isRTLView ? 'right' : 'left',
sortBy: this.sortConfig.name || '',
width: 300,
renderBodyCell: ({ row }) => (
<woot-button
variant="clear"
onClick={() => this.onClickContact(row.id)}
>
<div class="row--user-block">
<Thumbnail
src={row.thumbnail}
size="32px"
username={row.name}
status={row.availability_status}
/>
<div class="user-block">
<h6 class="sub-block-title overflow-hidden whitespace-nowrap text-ellipsis">
<router-link
to={`/app/accounts/${this.$route.params.accountId}/contacts/${row.id}`}
class="user-name"
>
{row.name}
</router-link>
</h6>
<button class="button clear small link view-details--button">
{this.$t('CONTACTS_PAGE.LIST.VIEW_DETAILS')}
</button>
</div>
</div>
</woot-button>
),
},
{
field: 'email',
key: 'email',
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.EMAIL_ADDRESS'),
align: this.isRTLView ? 'right' : 'left',
sortBy: this.sortConfig.email || '',
width: 240,
renderBodyCell: ({ row }) => {
if (row.email)
return (
<div class="overflow-hidden whitespace-nowrap text-ellipsis text-woot-500 dark:text-woot-500">
<a
target="_blank"
rel="noopener noreferrer nofollow"
href={`mailto:${row.email}`}
>
{row.email}
</a>
</div>
);
return '---';
},
},
{
field: 'phone_number',
key: 'phone_number',
sortBy: this.sortConfig.phone_number || '',
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.PHONE_NUMBER'),
align: this.isRTLView ? 'right' : 'left',
},
{
field: 'company',
key: 'company',
sortBy: this.sortConfig.company_name || '',
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.COMPANY'),
align: this.isRTLView ? 'right' : 'left',
},
{
field: 'city',
key: 'city',
sortBy: this.sortConfig.city || '',
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.CITY'),
align: this.isRTLView ? 'right' : 'left',
},
{
field: 'country',
key: 'country',
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.COUNTRY'),
align: this.isRTLView ? 'right' : 'left',
sortBy: this.sortConfig.country || '',
renderBodyCell: ({ row }) => {
if (row.country) {
return (
<div class="overflow-hidden whitespace-nowrap text-ellipsis">
{`${getCountryFlag(row.countryCode)} ${row.country}`}
</div>
);
}
return '---';
},
},
{
field: 'profiles',
key: 'profiles',
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.SOCIAL_PROFILES'),
align: this.isRTLView ? 'right' : 'left',
renderBodyCell: ({ row }) => {
const { profiles } = row;
const items = Object.keys(profiles);
if (!items.length) return '---';
return (
<div class="cell--social-profiles flex gap-0.5 items-center">
{items.map(
profile =>
profiles[profile] && (
<a
target="_blank"
rel="noopener noreferrer nofollow"
href={`https://${profile}.com/${profiles[profile]}`}
>
<FluentIcon icon={`brand-${profile}`} />
</a>
)
)}
</div>
);
},
},
{
field: 'last_activity_at',
key: 'last_activity_at',
sortBy: this.sortConfig.last_activity_at || '',
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.LAST_ACTIVITY'),
align: this.isRTLView ? 'right' : 'left',
},
{
field: 'created_at',
key: 'created_at',
sortBy: this.sortConfig.created_at || '',
title: this.$t('CONTACTS_PAGE.LIST.TABLE_HEADER.CREATED_AT'),
align: this.isRTLView ? 'right' : 'left',
},
];
},
},
watch: {
sortOrder() {
this.setSortConfig();
},
sortParam() {
this.setSortConfig();
},
},
mounted() {
this.setSortConfig();
},
methods: {
setSortConfig() {
this.sortConfig = { [this.sortParam]: this.sortOrder };
},
},
};
</script>
<style lang="scss" scoped>
.contacts-table-wrap::v-deep {
.ve-table {
@apply pb-8;
}
.row--user-block {
@apply items-center flex text-left;
.user-block {
@apply items-start flex flex-col my-0 mx-2;
}
.user-name {
@apply text-sm font-medium m-0 capitalize;
}
.view-details--button {
@apply text-slate-600 dark:text-slate-200;
}
.user-email {
@apply m-0;
}
}
.ve-table-header-th {
padding: var(--space-small) var(--space-two) !important;
}
.ve-table-body-td {
padding: var(--space-small) var(--space-two) !important;
}
.ve-table-header-th {
font-size: var(--font-size-mini) !important;
}
.ve-table-sort {
@apply -top-1;
}
}
.cell--social-profiles {
a {
@apply text-slate-300 dark:text-slate-400 text-lg min-w-[2rem] text-center;
}
}
</style>