463 lines
16 KiB
Vue
463 lines
16 KiB
Vue
<template>
|
|
<div class="user-select" :class="{ 'at-select--visible': showPopup }" @click="togglePopup">
|
|
<at-input class="user-select-input" :readonly="true" :value="inputValue" :size="size" />
|
|
|
|
<span v-show="userIDs.length" class="user-select__clear icon icon-x at-select__clear" @click="clearSelection" />
|
|
|
|
<span class="icon icon-chevron-down at-select__arrow" />
|
|
|
|
<transition name="slide-up">
|
|
<div v-show="showPopup" class="at-select__dropdown at-select__dropdown--bottom" @click.stop>
|
|
<at-tabs :value="userSelectTab" @on-change="onTabChange">
|
|
<at-tab-pane :label="$t('control.active')" name="active" />
|
|
<at-tab-pane :label="$t('control.inactive')" name="inactive" />
|
|
</at-tabs>
|
|
|
|
<div v-if="userSelectTab == 'active'">
|
|
<div class="user-search">
|
|
<at-input v-model="searchValue" class="user-search-input" :placeholder="$t('control.search')" />
|
|
</div>
|
|
|
|
<div>
|
|
<at-select v-model="userType" placeholder="fields.type" class="user-type-filter">
|
|
<at-option key="all" value="all">
|
|
{{ $t('field.types.all') }}
|
|
</at-option>
|
|
|
|
<at-option key="employee" value="employee">
|
|
{{ $t('field.types.employee') }}
|
|
</at-option>
|
|
|
|
<at-option key="client" value="client">
|
|
{{ $t('field.types.client') }}
|
|
</at-option>
|
|
</at-select>
|
|
</div>
|
|
|
|
<div class="user-select-all" @click="selectAllActiveUsers">
|
|
<span>{{ $t(selectedActiveUsers.length ? 'control.clear_all' : 'control.select_all') }}</span>
|
|
</div>
|
|
|
|
<div class="user-select-list">
|
|
<preloader v-if="isLoading"></preloader>
|
|
<ul>
|
|
<li
|
|
v-for="user in filteredActiveUsers"
|
|
:key="user.id"
|
|
:class="{
|
|
'user-select-item': true,
|
|
active: userIDs.includes(user.id),
|
|
}"
|
|
@click="toggleUser(user.id)"
|
|
>
|
|
<UserAvatar
|
|
class="user-avatar"
|
|
:size="25"
|
|
:borderRadius="5"
|
|
:user="user"
|
|
:online="user.online"
|
|
/>
|
|
|
|
<div class="user-name">{{ user.full_name }}</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="userSelectTab == 'inactive'">
|
|
<div class="user-search">
|
|
<at-input v-model="searchValue" class="user-search-input" :placeholder="$t('control.search')" />
|
|
</div>
|
|
|
|
<div>
|
|
<at-select v-model="userType" placeholder="fields.type" class="user-type-filter">
|
|
<at-option key="all" value="all">
|
|
{{ $t('field.types.all') }}
|
|
</at-option>
|
|
|
|
<at-option key="employee" value="employee">
|
|
{{ $t('field.types.employee') }}
|
|
</at-option>
|
|
|
|
<at-option key="client" value="client">
|
|
{{ $t('field.types.client') }}
|
|
</at-option>
|
|
</at-select>
|
|
</div>
|
|
|
|
<div class="user-select-all" @click="selectAllInactiveUsers">
|
|
<span>{{ $t(selectedInactiveUsers.length ? 'control.clear_all' : 'control.select_all') }}</span>
|
|
</div>
|
|
|
|
<div class="user-select-list">
|
|
<preloader v-if="isLoading"></preloader>
|
|
<ul>
|
|
<li
|
|
v-for="user in filteredInactiveUsers"
|
|
:key="user.id"
|
|
:class="{
|
|
'user-select-item': true,
|
|
active: userIDs.includes(user.id),
|
|
}"
|
|
@click="toggleUser(user.id)"
|
|
>
|
|
<UserAvatar
|
|
class="user-avatar"
|
|
:size="25"
|
|
:borderRadius="5"
|
|
:user="user"
|
|
:online="user.online"
|
|
/>
|
|
|
|
<div class="user-name">{{ user.full_name }}</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import UserAvatar from '@/components/UserAvatar';
|
|
import UsersService from '@/services/resource/user.service';
|
|
import Preloader from '@/components/Preloader';
|
|
|
|
export default {
|
|
name: 'UserSelect',
|
|
components: {
|
|
UserAvatar,
|
|
Preloader,
|
|
},
|
|
props: {
|
|
value: {
|
|
required: false,
|
|
default: () => {
|
|
return [];
|
|
},
|
|
},
|
|
size: {
|
|
type: String,
|
|
default: 'normal',
|
|
},
|
|
localStorageKey: {
|
|
type: String,
|
|
default: 'user-select.users',
|
|
},
|
|
},
|
|
data() {
|
|
let userIDs = [];
|
|
if (typeof this.value !== 'undefined' && this.value.length) {
|
|
userIDs = this.value;
|
|
} else {
|
|
if (localStorage.getItem(this.localStorageKey)) {
|
|
userIDs = JSON.parse(localStorage.getItem(this.localStorageKey));
|
|
}
|
|
}
|
|
|
|
return {
|
|
showPopup: false,
|
|
userSelectTab: 'active',
|
|
userIDs,
|
|
usersService: new UsersService(),
|
|
searchValue: '',
|
|
changed: false,
|
|
users: [],
|
|
userType: 'all',
|
|
isLoading: false,
|
|
};
|
|
},
|
|
async created() {
|
|
window.addEventListener('click', this.hidePopup);
|
|
|
|
this.isLoading = true;
|
|
try {
|
|
this.users = await this.usersService.getAll({ headers: { 'X-Paginate': 'false' } });
|
|
} catch ({ response }) {
|
|
if (process.env.NODE_ENV === 'development') {
|
|
console.warn(response ? response : 'request to users is canceled');
|
|
}
|
|
}
|
|
|
|
if (!localStorage.getItem(this.localStorageKey)) {
|
|
this.userIDs = this.users.filter(user => user.active).map(user => user.id);
|
|
localStorage.setItem(this.localStorageKey, JSON.stringify(this.userIDs));
|
|
}
|
|
|
|
// remove nonexistent users from selected
|
|
const existingUserIDs = this.users.filter(user => this.userIDs.includes(user.id)).map(user => user.id);
|
|
|
|
if (this.userIDs.length > existingUserIDs.length) {
|
|
this.userIDs = existingUserIDs;
|
|
localStorage.setItem(this.localStorageKey, JSON.stringify(this.userIDs));
|
|
}
|
|
|
|
if (this.userIDs.length) {
|
|
this.$emit('change', this.userIDs);
|
|
}
|
|
this.isLoading = false;
|
|
this.$nextTick(() => this.$emit('loaded'));
|
|
},
|
|
beforeDestroy() {
|
|
window.removeEventListener('click', this.hidePopup);
|
|
},
|
|
computed: {
|
|
activeUsers() {
|
|
return this.users.filter(user => user.active);
|
|
},
|
|
inactiveUsers() {
|
|
return this.users.filter(user => !user.active);
|
|
},
|
|
selectedActiveUsers() {
|
|
return this.activeUsers.filter(({ id }) => this.userIDs.includes(id));
|
|
},
|
|
selectedInactiveUsers() {
|
|
return this.inactiveUsers.filter(({ id }) => this.userIDs.includes(id));
|
|
},
|
|
filteredActiveUsers() {
|
|
return this.activeUsers.filter(user => {
|
|
if (this.userType !== 'all' && user.type !== this.userType) {
|
|
return false;
|
|
}
|
|
|
|
const name = user.full_name.toUpperCase();
|
|
const value = this.searchValue.toUpperCase();
|
|
|
|
return name.indexOf(value) !== -1;
|
|
});
|
|
},
|
|
filteredInactiveUsers() {
|
|
return this.inactiveUsers.filter(user => {
|
|
if (this.userType !== 'all' && user.type !== this.userType) {
|
|
return false;
|
|
}
|
|
|
|
const name = user.full_name.toUpperCase();
|
|
const value = this.searchValue.toUpperCase();
|
|
|
|
return name.indexOf(value) !== -1;
|
|
});
|
|
},
|
|
inputValue() {
|
|
return this.$tc('control.user_selected', this.userIDs.length, {
|
|
count: this.userIDs.length,
|
|
});
|
|
},
|
|
},
|
|
methods: {
|
|
togglePopup() {
|
|
this.showPopup = !this.showPopup;
|
|
|
|
if (!this.showPopup && this.changed) {
|
|
this.changed = false;
|
|
this.$emit('change', this.userIDs);
|
|
}
|
|
},
|
|
hidePopup() {
|
|
if (this.$el.contains(event.target)) {
|
|
return;
|
|
}
|
|
this.showPopup = false;
|
|
|
|
if (this.changed) {
|
|
this.changed = false;
|
|
this.$emit('change', this.userIDs);
|
|
}
|
|
},
|
|
clearSelection() {
|
|
this.userIDs = [];
|
|
this.$emit('change', this.userIDs);
|
|
localStorage[this.localStorageKey] = JSON.stringify(this.userIDs);
|
|
},
|
|
toggleUser(userID) {
|
|
if (this.userIDs.includes(userID)) {
|
|
this.userIDs = this.userIDs.filter(id => id !== userID);
|
|
} else {
|
|
this.userIDs.push(userID);
|
|
}
|
|
|
|
this.changed = true;
|
|
localStorage[this.localStorageKey] = JSON.stringify(this.userIDs);
|
|
},
|
|
selectAllActiveUsers() {
|
|
// If some users already selected we are going to clear it
|
|
if (!this.selectedActiveUsers.length) {
|
|
this.userIDs = this.userIDs.concat(
|
|
this.activeUsers
|
|
.filter(({ full_name, type }) => {
|
|
if (this.userType !== 'all' && this.userType !== type) {
|
|
return false;
|
|
}
|
|
|
|
return full_name.toUpperCase().indexOf(this.searchValue.toUpperCase()) !== -1;
|
|
})
|
|
.map(({ id }) => id)
|
|
.filter(id => !this.userIDs.includes(id)),
|
|
);
|
|
} else {
|
|
this.userIDs = this.userIDs.filter(uid => !this.activeUsers.map(({ id }) => id).includes(uid));
|
|
}
|
|
|
|
this.changed = true;
|
|
localStorage[this.localStorageKey] = JSON.stringify(this.userIDs);
|
|
},
|
|
selectAllInactiveUsers() {
|
|
if (!this.selectedInactiveUsers.length) {
|
|
this.userIDs = this.userIDs.concat(
|
|
this.inactiveUsers
|
|
.filter(({ full_name, type }) => {
|
|
if (this.userType !== 'all' && this.userType !== type) {
|
|
return false;
|
|
}
|
|
|
|
return full_name.toUpperCase().indexOf(this.searchValue.toUpperCase()) !== -1;
|
|
})
|
|
.map(({ id }) => id)
|
|
.filter(id => !this.userIDs.includes(id)),
|
|
);
|
|
} else {
|
|
this.userIDs = this.userIDs.filter(uid => !this.inactiveUsers.map(({ id }) => id).includes(uid));
|
|
}
|
|
|
|
this.changed = true;
|
|
localStorage[this.localStorageKey] = JSON.stringify(this.userIDs);
|
|
},
|
|
onTabChange({ name }) {
|
|
this.userSelectTab = name;
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.user-select {
|
|
position: relative;
|
|
min-width: 240px;
|
|
|
|
&::v-deep {
|
|
.at-input__original {
|
|
border-radius: 5px;
|
|
|
|
padding-right: $spacing-08;
|
|
cursor: text;
|
|
}
|
|
|
|
.at-tabs-nav {
|
|
width: 100%;
|
|
}
|
|
|
|
.at-tabs-nav__item {
|
|
color: #b1b1be;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
text-align: center;
|
|
margin: 0;
|
|
line-height: 39px;
|
|
width: 50%;
|
|
|
|
&--active {
|
|
color: #2e2ef9;
|
|
|
|
&::after {
|
|
background-color: #2e2ef9;
|
|
}
|
|
}
|
|
}
|
|
|
|
.at-tabs__nav {
|
|
height: 39px;
|
|
}
|
|
|
|
.at-tabs__header {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.at-tabs__body {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
&__clear {
|
|
margin-right: $spacing-05;
|
|
display: block;
|
|
}
|
|
|
|
&-list {
|
|
overflow-y: scroll;
|
|
max-height: 200px;
|
|
position: relative;
|
|
min-height: 60px;
|
|
}
|
|
|
|
&-all {
|
|
position: relative;
|
|
display: block;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
color: #59566e;
|
|
text-transform: uppercase;
|
|
|
|
padding: 8px 20px;
|
|
|
|
cursor: pointer;
|
|
}
|
|
|
|
&-item {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: #151941;
|
|
cursor: pointer;
|
|
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
padding: 7px 20px;
|
|
|
|
&.active {
|
|
background: #f4f4ff;
|
|
}
|
|
|
|
&::before,
|
|
&::after {
|
|
content: ' ';
|
|
display: table;
|
|
clear: both;
|
|
}
|
|
}
|
|
}
|
|
|
|
.user-search-input {
|
|
margin: 0;
|
|
|
|
&::v-deep {
|
|
.at-input__original {
|
|
border: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
}
|
|
|
|
.user-type-filter {
|
|
padding: 0 12px;
|
|
}
|
|
|
|
.user-avatar {
|
|
float: left;
|
|
margin-right: 10px;
|
|
}
|
|
|
|
.user-name {
|
|
padding-bottom: 3px;
|
|
}
|
|
|
|
.at-select {
|
|
&__dropdown {
|
|
overflow: hidden;
|
|
max-height: 360px;
|
|
}
|
|
}
|
|
</style>
|