Merge pull request #954 from frappe/mergify/bp/main-hotfix/pr-953

fix: Added search & role filter in users page (backport #953)
This commit is contained in:
Shariq Ansari 2025-06-23 21:06:56 +05:30 committed by GitHub
commit ce60dddbc9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -48,11 +48,7 @@
<!-- Empty State --> <!-- Empty State -->
<div <div
v-if=" v-if="!users.loading && users.data?.crmUsers?.length == 1"
!users.loading &&
users.data?.crmUsers?.length === 1 &&
users.data?.crmUsers[0].name == 'Administrator'
"
class="flex justify-between w-full h-full" class="flex justify-between w-full h-full"
> >
<div <div
@ -63,63 +59,82 @@
</div> </div>
<!-- Users List --> <!-- Users List -->
<ul <div v-if="!users.loading && users.data?.crmUsers?.length > 1">
v-if="!users.loading && Boolean(users.data?.crmUsers?.length)" <div class="flex items-center justify-between mb-4">
class="divide-y divide-outline-gray-modals overflow-auto" <TextInput
> ref="searchRef"
<template v-for="user in users.data?.crmUsers" :key="user.name"> v-model="search"
<li :placeholder="__('Search user')"
v-if="user.name !== 'Administrator'" class="w-1/3"
class="flex items-center justify-between py-2" :debounce="300"
> >
<div class="flex items-center"> <template #prefix>
<Avatar <FeatherIcon name="search" class="h-4 w-4 text-ink-gray-6" />
:image="user.user_image" </template>
:label="user.full_name" </TextInput>
size="xl" <FormControl
/> type="select"
<div class="flex flex-col gap-1 ml-3"> v-model="currentRole"
<div class="flex items-center text-base text-ink-gray-8 h-4"> :options="[
{{ user.full_name }} { label: __('All'), value: 'All' },
</div> { label: __('Admin'), value: 'System Manager' },
<div class="text-base text-ink-gray-5"> { label: __('Manager'), value: 'Sales Manager' },
{{ user.name }} { label: __('Sales User'), value: 'Sales User' },
</div> ]"
</div>
</div>
<div class="flex gap-2 items-center flex-row-reverse">
<Dropdown
:options="getMoreOptions(user)"
:button="{
icon: 'more-horizontal',
}"
placement="right"
/>
<Dropdown
:options="getDropdownOptions(user)"
:button="{
label: roleMap[getUserRole(user.name)],
iconRight: 'chevron-down',
}"
placement="right"
/>
</div>
</li>
</template>
<!-- Load More Button -->
<div
v-if="!users.loading && users.hasNextPage"
class="flex justify-center"
>
<Button
class="mt-3.5 p-2"
@click="() => users.next()"
:loading="users.loading"
:label="__('Load More')"
icon-left="refresh-cw"
/> />
</div> </div>
</ul> <ul class="divide-y divide-outline-gray-modals overflow-auto">
<template v-for="user in usersList" :key="user.name">
<li class="flex items-center justify-between py-2">
<div class="flex items-center">
<Avatar
:image="user.user_image"
:label="user.full_name"
size="xl"
/>
<div class="flex flex-col gap-1 ml-3">
<div class="flex items-center text-base text-ink-gray-8 h-4">
{{ user.full_name }}
</div>
<div class="text-base text-ink-gray-5">
{{ user.name }}
</div>
</div>
</div>
<div class="flex gap-2 items-center flex-row-reverse">
<Dropdown
:options="getMoreOptions(user)"
:button="{
icon: 'more-horizontal',
}"
placement="right"
/>
<Dropdown
:options="getDropdownOptions(user)"
:button="{
label: roleMap[user.role],
iconRight: 'chevron-down',
}"
placement="right"
/>
</div>
</li>
</template>
<!-- Load More Button -->
<div
v-if="!users.loading && users.hasNextPage"
class="flex justify-center"
>
<Button
class="mt-3.5 p-2"
@click="() => users.next()"
:loading="users.loading"
:label="__('Load More')"
icon-left="refresh-cw"
/>
</div>
</ul>
</div>
</div> </div>
<AddExistingUserModal <AddExistingUserModal
v-if="showAddExistingModal" v-if="showAddExistingModal"
@ -132,12 +147,15 @@ import LucideCheck from '~icons/lucide/check'
import AddExistingUserModal from '@/components/Modals/AddExistingUserModal.vue' import AddExistingUserModal from '@/components/Modals/AddExistingUserModal.vue'
import { activeSettingsPage } from '@/composables/settings' import { activeSettingsPage } from '@/composables/settings'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { Avatar, toast, call } from 'frappe-ui' import { Avatar, TextInput, toast, call } from 'frappe-ui'
import { ref, h } from 'vue' import { ref, computed, h, onMounted } from 'vue'
const { users, getUserRole, isAdmin, isManager } = usersStore() const { users, getUserRole, isAdmin, isManager } = usersStore()
const showAddExistingModal = ref(false) const showAddExistingModal = ref(false)
const searchRef = ref(null)
const search = ref('')
const currentRole = ref('All')
const roleMap = { const roleMap = {
'System Manager': __('Admin'), 'System Manager': __('Admin'),
@ -145,6 +163,22 @@ const roleMap = {
'Sales User': __('Sales User'), 'Sales User': __('Sales User'),
} }
const usersList = computed(() => {
let filteredUsers =
users.data?.crmUsers?.filter((user) => user.name !== 'Administrator') || []
return filteredUsers
.filter(
(user) =>
user.name?.includes(search.value) ||
user.full_name?.includes(search.value),
)
.filter((user) => {
if (currentRole.value === 'All') return true
return user.role === currentRole.value
})
})
function getMoreOptions(user) { function getMoreOptions(user) {
let options = [ let options = [
{ {
@ -158,7 +192,6 @@ function getMoreOptions(user) {
} }
function getDropdownOptions(user) { function getDropdownOptions(user) {
const userRole = getUserRole(user.name)
let options = [ let options = [
{ {
label: __('Admin'), label: __('Admin'),
@ -166,7 +199,7 @@ function getDropdownOptions(user) {
RoleOption({ RoleOption({
role: __('Admin'), role: __('Admin'),
active: props.active, active: props.active,
selected: userRole === 'System Manager', selected: user.role === 'System Manager',
onClick: () => updateRole(user, 'System Manager'), onClick: () => updateRole(user, 'System Manager'),
}), }),
condition: () => isAdmin(), condition: () => isAdmin(),
@ -177,7 +210,7 @@ function getDropdownOptions(user) {
RoleOption({ RoleOption({
role: __('Manager'), role: __('Manager'),
active: props.active, active: props.active,
selected: userRole === 'Sales Manager', selected: user.role === 'Sales Manager',
onClick: () => updateRole(user, 'Sales Manager'), onClick: () => updateRole(user, 'Sales Manager'),
}), }),
condition: () => isManager(), condition: () => isManager(),
@ -188,7 +221,7 @@ function getDropdownOptions(user) {
RoleOption({ RoleOption({
role: __('Sales User'), role: __('Sales User'),
active: props.active, active: props.active,
selected: userRole === 'Sales User', selected: user.role === 'Sales User',
onClick: () => updateRole(user, 'Sales User'), onClick: () => updateRole(user, 'Sales User'),
}), }),
}, },
@ -241,4 +274,10 @@ function removeUser(user) {
users.reload() users.reload()
}) })
} }
onMounted(() => {
if (searchRef.value) {
searchRef.value.el.focus()
}
})
</script> </script>