Compare commits

...

17 commits

Author SHA1 Message Date
Sivin Varghese
f5df7b45f2 fixes console error 2021-06-16 15:38:47 +05:30
Sivin Varghese
309eba5d6a
Merge branch 'develop' into ui/agent-dropdown 2021-06-15 20:48:55 +05:30
Sivin Varghese
cd9b81f075 Adds agent component to conversation header 2021-06-15 20:37:44 +05:30
Sivin Varghese
422177f72c
Merge branch 'develop' into ui/agent-dropdown 2021-06-15 11:01:09 +05:30
Sivin Varghese
3e5e6e8eaa Adds component and stories 2021-06-15 10:56:43 +05:30
Sivin Varghese
4f79c13977 Merge branch 'develop' of https://github.com/chatwoot/chatwoot into ui/agent-dropdown 2021-06-11 16:30:43 +05:30
Sivin Varghese
3d27d5a1d5
Merge branch 'develop' into ui/agent-dropdown 2021-05-26 13:01:18 +05:30
Sivin Varghese
6884771911
Merge branch 'develop' into ui/agent-dropdown 2021-05-25 19:53:40 +05:30
Sivin Varghese
9316995e62
Merge branch 'develop' into ui/agent-dropdown 2021-05-13 21:48:28 +05:30
Sivin Varghese
37b722bb4d
Merge branch 'develop' into ui/agent-dropdown 2021-05-01 15:43:14 +05:30
Sivin Varghese
2ea7b440e5
Merge branch 'develop' into ui/agent-dropdown 2021-04-30 18:45:50 +05:30
Sivin Varghese
0e88f1fc38
Merge branch 'develop' into ui/agent-dropdown 2021-04-28 11:40:43 +05:30
Sivin Varghese
80d00bcc76
Merge branch 'develop' into ui/agent-dropdown 2021-04-27 14:36:07 +05:30
sivin-git
ffcec61a1f Minor Fixes 2021-04-27 14:29:41 +05:30
sivin-git
59f5121676 Minor height fixes 2021-04-23 20:23:08 +05:30
Sivin Varghese
cbf7b7211d
Merge branch 'develop' into ui/agent-dropdown 2021-04-19 11:35:44 +05:30
sivin-git
f6a56b6c7e Enhancement: New dropdown component for agents and team. 2021-04-19 11:21:32 +05:30
10 changed files with 923 additions and 125 deletions

View file

@ -32,33 +32,11 @@
class="header-actions-wrap"
:class="{ 'has-open-sidebar': isContactPanelOpen }"
>
<div class="multiselect-box multiselect-wrap--small">
<i class="icon ion-headphone" />
<multiselect
v-model="currentChat.meta.assignee"
:loading="uiFlags.isFetching"
:allow-empty="true"
deselect-label=""
:options="agentList"
:placeholder="$t('CONVERSATION.ASSIGNMENT.SELECT_AGENT')"
select-label=""
label="name"
selected-label
track-by="id"
@select="assignAgent"
@remove="removeAgent"
>
<template slot="option" slot-scope="props">
<div class="option__desc">
<availability-status-badge
:status="props.option.availability_status"
/>
<span class="option__title">{{ props.option.name }}</span>
</div>
</template>
<span slot="noResult">{{ $t('AGENT_MGMT.SEARCH.NO_RESULTS') }}</span>
</multiselect>
</div>
<agent-dropdown
:assigned-agent="currentChat.meta.assignee"
:agents-list="agentList"
@click="onClickAssignAgent"
/>
<more-actions :conversation-id="currentChat.id" />
</div>
</div>
@ -67,13 +45,13 @@
import { mapGetters } from 'vuex';
import MoreActions from './MoreActions';
import Thumbnail from '../Thumbnail';
import AvailabilityStatusBadge from '../conversation/AvailabilityStatusBadge';
import AgentDropdown from 'shared/components/ui/AgentDropdown.vue';
export default {
components: {
MoreActions,
Thumbnail,
AvailabilityStatusBadge,
AgentDropdown,
},
props: {
@ -90,6 +68,7 @@ export default {
data() {
return {
currentChatAssignee: null,
showSearchDropdown: false,
};
},
@ -113,17 +92,7 @@ export default {
agentList() {
const { inbox_id: inboxId } = this.chat;
const agents = this.getAgents(inboxId) || [];
return [
{
confirmed: true,
name: 'None',
id: 0,
role: 'agent',
account_id: 0,
email: 'None',
},
...agents,
];
return [...agents];
},
},
@ -139,31 +108,35 @@ export default {
});
},
removeAgent() {},
onClickAssignAgent(selectedItem) {
if (
this.currentChat.meta.assignee &&
this.currentChat.meta.assignee.id === selectedItem.id
) {
this.currentChat.meta.assignee = '';
} else {
this.currentChat.meta.assignee = selectedItem;
}
this.assignAgent(this.currentChat.meta.assignee);
},
},
};
</script>
<style lang="scss" scoped>
.text-truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.conv-header {
flex: 0 0 var(--space-jumbo);
}
.option__desc {
display: flex;
align-items: center;
}
.text-truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.option__desc {
&::v-deep .status-badge {
margin-right: var(--space-small);
min-width: 0;
flex-shrink: 0;
::v-deep.dropdown-wrap {
width: 21rem;
margin-bottom: 0;
}
}
</style>

View file

@ -90,7 +90,14 @@
}
},
"SEARCH": {
"NO_RESULTS": "No agents found."
"NO_RESULTS": "No results found."
},
"SELECTOR": {
"TITLE": {
"AGENT": "Select Agent",
"TEAM": "Select Team"
},
"PLACEHOLDER": "None"
}
}
}

View file

@ -65,10 +65,6 @@ export default {
position: relative;
border-left: 1px solid var(--color-border);
padding: var(--space-medium) var(--space-two);
.contact-labels {
padding-bottom: var(--space-normal);
}
}
.close-button {

View file

@ -6,45 +6,29 @@
<contact-info :contact="contact" :channel-type="channelType" />
<div class="conversation--actions">
<div class="multiselect-wrap--small">
<contact-details-item
:title="$t('CONVERSATION_SIDEBAR.ASSIGNEE_LABEL')"
icon="ion-headphone"
emoji="🧑‍🚀"
>
<template v-slot:button>
<woot-button
v-if="showSelfAssign"
icon="ion-arrow-right-c"
variant="link"
size="small"
class-names="button-content"
@click="onSelfAssign"
>
{{ $t('CONVERSATION_SIDEBAR.SELF_ASSIGN') }}
</woot-button>
</template>
</contact-details-item>
<multiselect
v-model="assignedAgent"
:options="agentsList"
label="name"
track-by="id"
deselect-label=""
select-label=""
selected-label=""
:placeholder="$t('CONVERSATION_SIDEBAR.SELECT.PLACEHOLDER')"
:allow-empty="true"
>
<template slot="option" slot-scope="props">
<div class="option__desc">
<availability-status-badge
:status="props.option.availability_status"
/>
<span class="option__title">{{ props.option.name }}</span>
</div>
</template>
<span slot="noResult">{{ $t('AGENT_MGMT.SEARCH.NO_RESULTS') }}</span>
</multiselect>
<div class="self-assign">
<contact-details-item
:title="$t('CONVERSATION_SIDEBAR.ASSIGNEE_LABEL')"
icon="ion-headphone"
emoji="🧑‍🚀"
>
</contact-details-item>
<woot-button
v-if="showSelfAssign"
icon="ion-arrow-right-c"
variant="link"
size="small"
class-names="button-content"
@click="onSelfAssign"
>
{{ $t('CONVERSATION_SIDEBAR.SELF_ASSIGN') }}
</woot-button>
</div>
<agent-dropdown
:assigned-agent="assignedAgent"
:agents-list="agentsList"
@click="onClickShowAgent"
/>
</div>
<div class="multiselect-wrap--small">
<contact-details-item
@ -52,19 +36,11 @@
icon="ion-ios-people"
emoji="🎢"
/>
<multiselect
v-model="assignedTeam"
:options="teamsList"
label="name"
track-by="id"
deselect-label=""
select-label=""
selected-label=""
:placeholder="$t('CONVERSATION_SIDEBAR.SELECT.PLACEHOLDER')"
:allow-empty="true"
>
<span slot="noResult">{{ $t('AGENT_MGMT.SEARCH.NO_RESULTS') }}</span>
</multiselect>
<teams-dropdown
:assigned-team="assignedTeam"
:teams-list="teamsList"
@click="onClickShowTeam"
/>
</div>
</div>
<conversation-labels :conversation-id="conversationId" />
@ -131,15 +107,16 @@
<script>
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import { mixin as clickaway } from 'vue-clickaway';
import ContactConversations from './ContactConversations.vue';
import ContactDetailsItem from './ContactDetailsItem.vue';
import ContactInfo from './contact/ContactInfo';
import ConversationLabels from './labels/LabelBox.vue';
import ContactCustomAttributes from './ContactCustomAttributes';
import AvailabilityStatusBadge from 'dashboard/components/widgets/conversation/AvailabilityStatusBadge.vue';
import flag from 'country-code-emoji';
import AgentDropdown from 'shared/components/ui/AgentDropdown.vue';
import TeamsDropdown from 'shared/components/ui/TeamsDropdown.vue';
export default {
components: {
@ -148,9 +125,10 @@ export default {
ContactDetailsItem,
ContactInfo,
ConversationLabels,
AvailabilityStatusBadge,
AgentDropdown,
TeamsDropdown,
},
mixins: [alertMixin],
mixins: [alertMixin, clickaway],
props: {
conversationId: {
type: [Number, String],
@ -165,6 +143,13 @@ export default {
default: () => {},
},
},
data() {
return {
currentChatAssignee: null,
showSearchDropdown: false,
showSearchDropdownTeam: false,
};
},
computed: {
...mapGetters({
currentChat: 'getSelectedChat',
@ -310,6 +295,34 @@ export default {
openTranscriptModal() {
this.showTranscriptModal = true;
},
toggleDropdown() {
this.showSearchDropdown = !this.showSearchDropdown;
},
toggleDropdownTeam() {
this.showSearchDropdownTeam = !this.showSearchDropdownTeam;
},
onClickShowAgent(selectedItem) {
if (this.assignedAgent && this.assignedAgent.id === selectedItem.id) {
this.assignedAgent = null;
} else {
this.assignedAgent = selectedItem;
}
return this.assignedAgent;
},
onClickShowTeam(selectedItemTeam) {
if (this.assignedTeam && this.assignedTeam.id === selectedItemTeam.id) {
this.assignedTeam = null;
} else {
this.assignedTeam = selectedItemTeam;
}
return this.assignedTeam;
},
onCloseDropdown() {
this.showSearchDropdown = false;
},
onCloseDropdownTeam() {
this.showSearchDropdownTeam = false;
},
onSelfAssign() {
const {
account_id,
@ -353,6 +366,11 @@ export default {
}
}
::v-deep .dropdown-wrap {
margin-top: var(--space-micro);
margin-bottom: var(--space-small);
}
::v-deep {
.contact--profile {
padding-bottom: var(--space-slab);
@ -391,7 +409,7 @@ export default {
.label {
color: #fff;
padding: 0.2rem;
padding: var(--space-micro);
}
}
@ -411,14 +429,15 @@ export default {
margin-bottom: var(--space-normal);
}
.option__desc {
display: flex;
align-items: center;
.multiselect__label {
margin-bottom: var(--space-smaller);
}
&::v-deep .status-badge {
margin-right: var(--space-small);
min-width: 0;
flex-shrink: 0;
.self-assign {
display: flex;
justify-content: space-between;
.button-content {
margin-bottom: var(--space-small);
}
}
</style>

View file

@ -0,0 +1,53 @@
import { action } from '@storybook/addon-actions';
import AgentDropdownSelector from './AgentDropdown';
export default {
title: 'Components/Dropdown/Agent Dropdown',
component: AgentDropdownSelector,
argTypes: {
agentsList: {
control: {
type: 'object',
},
},
assignedAgent: {
control: {
type: 'object',
},
},
},
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { AgentDropdownSelector },
template:
'<agent-dropdown-selector v-bind="$props" @click="onClick"></agent-dropdown-selector>',
});
export const AgentDropdown = Template.bind({});
AgentDropdown.args = {
onClick: action('Opened'),
agentsList: [
{
account_id: 1,
availability_status: 'online',
available_name: 'John',
email: 'john@gmail.com',
id: 1,
name: 'John',
role: 'administrator',
thumbnail: 'https://randomuser.me/api/portraits/men/4.jpg',
},
],
assignedAgent: {
account_id: 1,
availability_status: 'online',
available_name: 'John',
email: 'john@gmail.com',
id: 1,
name: 'John',
role: 'administrator',
thumbnail: 'https://randomuser.me/api/portraits/men/4.jpg',
},
};

View file

@ -0,0 +1,167 @@
<template>
<div v-on-clickaway="onCloseDropdownAgent" class="dropdown-wrap">
<button
:v-model="assignedAgent"
class="button-input"
@click="toggleDropdownAgent"
>
<thumbnail
v-if="
assignedAgent &&
assignedAgent.name &&
assignedAgent &&
assignedAgent.id
"
:src="assignedAgent && assignedAgent.thumbnail"
size="24px"
:status="assignedAgent && assignedAgent.availability_status"
:badge="assignedAgent && assignedAgent.channel"
:username="assignedAgent && assignedAgent.name"
/>
<div class="name-icon-wrap">
<div v-if="!noAssignedAgent" class="name select-agent">
{{ $t('AGENT_MGMT.SELECTOR.PLACEHOLDER') }}
</div>
<div v-else class="name" :title="assignedAgent.name">
{{ assignedAgent.name }}
</div>
<i v-if="showSearchDropdownAgent" class="icon ion-chevron-up" />
<i v-else class="icon ion-chevron-down" />
</div>
</button>
<div
:class="{ 'dropdown-pane--open': showSearchDropdownAgent }"
class="dropdown-pane"
>
<h4 class="text-block-title">
{{ $t('AGENT_MGMT.SELECTOR.TITLE.AGENT') }}
</h4>
<agents-list
v-if="showSearchDropdownAgent"
:options="agentsList"
:value="assignedAgent"
@click="showAgent"
/>
</div>
</div>
</template>
<script>
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
import agentsList from 'shared/components/ui/AgentDropdownList.vue';
import { mixin as clickaway } from 'vue-clickaway';
export default {
components: {
Thumbnail,
agentsList,
},
mixins: [clickaway],
props: {
agentsList: {
type: Array,
default: () => [],
},
assignedAgent: {
type: Object,
default: () => ({}),
},
},
data() {
return {
showSearchDropdownAgent: false,
};
},
computed: {
noAssignedAgent() {
if (this.assignedAgent && this.assignedAgent.id) {
return true;
}
return false;
},
},
methods: {
toggleDropdownAgent() {
this.showSearchDropdownAgent = !this.showSearchDropdownAgent;
},
onCloseDropdownAgent() {
this.showSearchDropdownAgent = false;
},
showAgent(value) {
this.$emit('click', value);
},
},
};
</script>
<style lang="scss" scoped>
.dropdown-wrap {
display: flex;
position: relative;
width: 100%;
margin-right: var(--space-one);
margin-bottom: var(--space-small);
.button-input {
display: flex;
width: 100%;
cursor: pointer;
justify-content: flex-start;
background: white;
font-size: var(--font-size-small);
border: 1px solid lightgray;
border-radius: var(--border-radius-normal);
padding: 0.6rem;
}
&::v-deep .user-thumbnail-box {
margin-right: var(--space-one);
}
.name-icon-wrap {
display: flex;
justify-content: space-between;
width: 100%;
padding: var(--space-smaller) 0;
line-height: var(--space-normal);
min-width: 0;
.select-agent {
color: var(--b-600);
}
.name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: var(--space-small);
}
}
.dropdown-pane {
box-sizing: border-box;
top: 4rem;
right: 0;
position: absolute;
width: 100%;
&::v-deep {
.dropdown-menu__item .button {
width: 100%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding: var(--space-smaller) var(--space-small);
.name-icon-wrap {
width: 100%;
}
.name {
width: 100%;
}
}
}
}
}
</style>

View file

@ -0,0 +1,55 @@
import { action } from '@storybook/addon-actions';
import AgentDropdownList from './AgentDropdownList';
export default {
title: 'Components/Dropdown/Agent List',
component: AgentDropdownList,
argTypes: {
options: {
control: {
type: 'object',
},
},
value: {
control: {
type: 'object',
},
},
},
};
const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { AgentDropdownList },
template:
'<agent-dropdown-list v-bind="$props" @click="onClick"></agent-dropdown-list>',
});
export const AgentList = Template.bind({});
AgentList.args = {
onClick: action('Added'),
options: [
{
id: '1',
name: 'John',
availability_status: 'online',
role: 'administrator',
thumbnail: 'https://randomuser.me/api/portraits/men/4.jpg',
},
{
id: '2',
name: 'Tim Donarld',
availability_status: 'offline',
role: 'administrator',
thumbnail: '',
},
{
id: '3',
name: 'James Philip',
availability_status: 'busy',
role: 'agent',
thumbnail: 'https://randomuser.me/api/portraits/men/17.jpg',
},
],
value: { id: '1' },
};

View file

@ -0,0 +1,183 @@
<template>
<div class="dropdown-search-wrap">
<div class="search-wrap">
<input
ref="searchbar"
v-model="search"
type="text"
class="search-input"
autofocus="true"
placeholder="Filter"
/>
</div>
<div class="list-wrap">
<div class="list">
<woot-dropdown-menu>
<woot-dropdown-item
v-for="option in filteredOptions"
:key="option.id"
>
<button
class="button clear"
:class="{ active: option.id === (value && value.id) }"
@click="() => onclick(option)"
>
<Thumbnail
:src="option.thumbnail"
size="25px"
:username="option.name"
:status="option.availability_status"
/>
<div class="name-icon-wrap">
<div class="name" :title="option.name">
{{ option.name }}
</div>
<i
v-if="option.id === (value && value.id)"
class="icon ion-checkmark-round"
/>
</div>
</button>
</woot-dropdown-item>
</woot-dropdown-menu>
<div v-if="noResult" class="no-result">
{{ $t('AGENT_MGMT.SEARCH.NO_RESULTS') }}
</div>
</div>
</div>
</div>
</template>
<script>
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
export default {
components: {
WootDropdownItem,
WootDropdownMenu,
Thumbnail,
},
props: {
options: {
type: Array,
default: () => [],
},
value: {
type: Object,
default: () => ({}),
},
},
data() {
return {
search: '',
};
},
computed: {
filteredOptions() {
return this.options.filter(option => {
return option.name.toLowerCase().includes(this.search.toLowerCase());
});
},
noResult() {
return this.filteredOptions.length === 0 && this.search !== '';
},
},
mounted() {
this.focusInput();
},
methods: {
onclick(option) {
this.$emit('click', option);
},
focusInput() {
this.$refs.searchbar.focus();
},
},
};
</script>
<style lang="scss" scoped>
.dropdown-search-wrap {
width: 100%;
display: flex;
flex-direction: column;
max-height: 16rem;
.search-wrap {
margin-bottom: var(--space-small);
flex: 0 0 auto;
max-height: var(--space-large);
.search-input {
margin: 0;
width: 100%;
border: none;
height: var(--space-large);
font-size: var(--font-size-small);
padding: var(--space-small);
background-color: var(--color-background);
}
input:focus {
border: 1px solid var(--w-500);
}
}
.list-wrap {
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex: 1 1 auto;
overflow: auto;
.list {
width: 100%;
max-height: 12rem;
.button {
display: flex;
justify-content: flex-start;
&.active {
display: flex;
font-weight: var(--font-weight-bold);
color: var(--w-700);
}
.name-icon-wrap {
display: flex;
justify-content: space-between;
min-width: 0;
}
.name {
padding: 0 var(--space-smaller);
line-height: var(--space-normal);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-left: var(--space-smaller);
}
}
.no-result {
display: flex;
justify-content: center;
color: var(--s-700);
width: 100%;
padding: var(--space-small) var(--space-one);
font-weight: var(--font-weight-medium);
font-size: var(--font-size-small);
}
}
}
}
</style>

View file

@ -0,0 +1,163 @@
<template>
<div v-on-clickaway="onCloseDropdownTeam" class="dropdown-wrap">
<button
:v-model="assignedTeam"
class="button-input"
@click="toggleDropdownTeam"
>
<thumbnail
v-if="
assignedTeam && assignedTeam.name && assignedTeam && assignedTeam.id
"
:src="assignedTeam && assignedTeam.thumbnail"
size="24px"
:badge="assignedTeam.channel"
:username="assignedTeam && assignedTeam.name"
/>
<div class="name-icon-wrap">
<div v-if="!noAssignedTeam" class="name select-agent">
{{ $t('AGENT_MGMT.SELECTOR.PLACEHOLDER') }}
</div>
<div v-else class="name" :title="assignedTeam.name">
{{ assignedTeam.name }}
</div>
<i v-if="showSearchDropdownTeam" class="icon ion-chevron-up" />
<i v-else class="icon ion-chevron-down" />
</div>
</button>
<div
:class="{ 'dropdown-pane--open': showSearchDropdownTeam }"
class="dropdown-pane"
>
<h4 class="text-block-title">
{{ $t('AGENT_MGMT.SELECTOR.TITLE.TEAM') }}
</h4>
<teams-list
v-if="showSearchDropdownTeam"
:options="teamsList"
:value="assignedTeam"
@click="showTeam"
/>
</div>
</div>
</template>
<script>
import teamsList from 'shared/components/ui/TeamsDropdownList.vue';
import { mixin as clickaway } from 'vue-clickaway';
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
export default {
components: {
teamsList,
Thumbnail,
},
mixins: [clickaway],
props: {
teamsList: {
type: Array,
default: () => [],
},
assignedTeam: {
type: Object,
default: () => ({}),
},
},
data() {
return {
showSearchDropdownTeam: false,
};
},
computed: {
noAssignedTeam() {
if (this.assignedTeam && this.assignedTeam.id) {
return true;
}
return false;
},
},
methods: {
toggleDropdownTeam() {
this.showSearchDropdownTeam = !this.showSearchDropdownTeam;
},
onCloseDropdownTeam() {
this.showSearchDropdownTeam = false;
},
showTeam(value) {
this.$emit('click', value);
},
},
};
</script>
<style lang="scss" scoped>
.dropdown-wrap {
display: flex;
position: relative;
width: 100%;
margin-right: var(--space-one);
.button-input {
display: flex;
width: 100%;
cursor: pointer;
justify-content: flex-start;
background: white;
font-size: var(--font-size-small);
cursor: pointer;
border: 1px solid lightgray;
border-radius: var(--border-radius-normal);
padding: 0.6rem;
}
&::v-deep .user-thumbnail-box {
margin-right: var(--space-one);
}
.name-icon-wrap {
display: flex;
justify-content: space-between;
width: 100%;
padding: var(--space-smaller) 0;
line-height: var(--space-normal);
min-width: 0;
.select-agent {
color: var(--b-600);
}
.name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: var(--space-small);
}
}
.dropdown-pane {
box-sizing: border-box;
top: 4rem;
right: 0;
position: absolute;
width: 100%;
&::v-deep {
.dropdown-menu__item .button {
width: 100%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding: var(--space-smaller) var(--space-small);
.name-icon-wrap {
width: 100%;
}
.name {
width: 100%;
}
}
}
}
}
</style>

View file

@ -0,0 +1,182 @@
<template>
<div class="dropdown-search-wrap">
<div class="search-wrap">
<input
ref="searchbar"
v-model="search"
type="text"
class="search-input"
autofocus="true"
placeholder="Filter"
/>
</div>
<div class="list-wrap">
<div class="list">
<woot-dropdown-menu>
<woot-dropdown-item
v-for="option in filteredOptions"
:key="option.id"
>
<button
class="button clear"
:class="{ active: option.id === (value && value.id) }"
@click="() => onclick(option)"
>
<Thumbnail
:src="option.thumbnail"
size="25px"
:username="option.name"
/>
<div class="name-icon-wrap">
<div class="name" :title="option.name">
{{ option.name }}
</div>
<i
v-if="option.id === (value && value.id)"
class="icon ion-checkmark-round"
/>
</div>
</button>
</woot-dropdown-item>
</woot-dropdown-menu>
<div v-if="noResult" class="no-result">
{{ $t('AGENT_MGMT.SEARCH.NO_RESULTS') }}
</div>
</div>
</div>
</div>
</template>
<script>
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
export default {
components: {
WootDropdownItem,
WootDropdownMenu,
Thumbnail,
},
props: {
options: {
type: Array,
default: () => [],
},
value: {
type: Object,
default: () => {},
},
},
data() {
return {
search: '',
};
},
computed: {
filteredOptions() {
return this.options.filter(option => {
return option.name.toLowerCase().includes(this.search.toLowerCase());
});
},
noResult() {
return this.filteredOptions.length === 0 && this.search !== '';
},
},
mounted() {
this.focusInput();
},
methods: {
onclick(option) {
this.$emit('click', option);
},
focusInput() {
this.$refs.searchbar.focus();
},
},
};
</script>
<style lang="scss" scoped>
.dropdown-search-wrap {
width: 100%;
display: flex;
flex-direction: column;
max-height: 16rem;
.search-wrap {
margin-bottom: var(--space-small);
flex: 0 0 auto;
max-height: var(--space-large);
.search-input {
margin: 0;
width: 100%;
border: none;
height: var(--space-large);
font-size: var(--font-size-small);
padding: var(--space-small);
background-color: var(--color-background);
}
input:focus {
border: 1px solid var(--w-500);
}
}
.list-wrap {
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex: 1 1 auto;
overflow: auto;
.list {
width: 100%;
max-height: 12rem;
.button {
display: flex;
justify-content: flex-start;
&.active {
display: flex;
font-weight: var(--font-weight-bold);
color: var(--w-700);
}
.name-icon-wrap {
display: flex;
justify-content: space-between;
min-width: 0;
}
.name {
padding: 0 var(--space-smaller);
line-height: var(--space-normal);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon {
margin-left: var(--space-smaller);
}
}
.no-result {
display: flex;
justify-content: center;
color: var(--s-700);
width: 100%;
padding: var(--space-small) var(--space-one);
font-weight: var(--font-weight-medium);
font-size: var(--font-size-small);
}
}
}
}
</style>