fix: updated users page to update user directly and removed unnecessary code

(cherry picked from commit 364c369199c4204962a55c58bc0980d3942e3e8b)
This commit is contained in:
Shariq Ansari 2025-06-20 16:46:41 +05:30 committed by Mergify
parent e3cd126304
commit 1b30b3ebec
2 changed files with 95 additions and 183 deletions

View File

@ -21,13 +21,16 @@
</label> </label>
<div class="p-2 group bg-surface-gray-2 hover:bg-surface-gray-3 rounded"> <div class="p-2 group bg-surface-gray-2 hover:bg-surface-gray-3 rounded">
<MultiSelectEmailInput <MultiSelectUserInput
v-if="users?.data?.crmUsers.length"
class="flex-1" class="flex-1"
inputClass="!bg-surface-gray-2 hover:!bg-surface-gray-3 group-hover:!bg-surface-gray-3" inputClass="!bg-surface-gray-2 hover:!bg-surface-gray-3 group-hover:!bg-surface-gray-3"
:placeholder="__('john@doe.com')" :placeholder="__('john@doe.com')"
v-model="newUsers" v-model="newUsers"
:validate="validateEmail" :validate="validateEmail"
:existingEmails="props.users.data.map((user) => user.name)" :existingEmails="
users.data.crmUsers.map((user) => user.name) + ['admin@example.com']
"
:error-message=" :error-message="
(value) => __('{0} is an invalid email address', [value]) (value) => __('{0} is an invalid email address', [value])
" "
@ -57,20 +60,16 @@
</template> </template>
<script setup> <script setup>
import MultiSelectEmailInput from '@/components/Controls/MultiSelectEmailInput.vue' import MultiSelectUserInput from '@/components/Controls/MultiSelectUserInput.vue'
import { validateEmail } from '@/utils' import { validateEmail } from '@/utils'
import { usersStore } from '@/stores/users' import { usersStore } from '@/stores/users'
import { createResource, toast } from 'frappe-ui' import { createResource, toast } from 'frappe-ui'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
const { users: usersResource, isAdmin, isManager } = usersStore() const { users, isAdmin, isManager } = usersStore()
const show = defineModel() const show = defineModel()
const props = defineProps({
users: Object,
})
const newUsers = ref([]) const newUsers = ref([])
const role = ref('Sales User') const role = ref('Sales User')
@ -94,7 +93,7 @@ const roleOptions = computed(() => {
}) })
const addNewUser = createResource({ const addNewUser = createResource({
url: 'crm.fcrm.doctype.crm_user.crm_user.add_existing_users', url: 'crm.api.user.add_existing_users',
makeParams: () => ({ makeParams: () => ({
users: JSON.stringify(newUsers.value), users: JSON.stringify(newUsers.value),
role: role.value, role: role.value,
@ -103,8 +102,7 @@ const addNewUser = createResource({
toast.success(__('Users added successfully')) toast.success(__('Users added successfully'))
newUsers.value = [] newUsers.value = []
show.value = false show.value = false
usersResource.reload() users.reload()
props.users.list.reload()
}, },
onError: (error) => { onError: (error) => {
toast.error(error.messages[0] || __('Failed to add users')) toast.error(error.messages[0] || __('Failed to add users'))

View File

@ -1,32 +1,20 @@
<template> <template>
<div class="flex h-full flex-col gap-8 p-8 text-ink-gray-9"> <div class="flex h-full flex-col gap-6 p-8 text-ink-gray-9">
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between"> <div class="flex justify-between">
<h2 class="flex gap-2 text-xl font-semibold leading-none h-5"> <div class="flex flex-col gap-1 w-9/12">
{{ __('Users') }} <h2 class="flex gap-2 text-xl font-semibold leading-none h-5">
</h2> {{ __('Users') }}
<div class="flex item-center space-x-2"> </h2>
<FormControl <p class="text-p-base text-ink-gray-6">
v-model="search" {{
:placeholder="'Search'" __(
type="text" 'Manage users who can access CRM. Assign roles to control their access. Add/Remove existing users or invite new ones.',
:debounce="300" )
> }}
<template #prefix> </p>
<LucideSearch class="h-4 w-4 text-ink-gray-4" /> </div>
</template> <div class="flex item-center space-x-2 w-3/12 justify-end">
</FormControl>
<FormControl
type="select"
:value="currentStatus"
:options="[
{ label: __('All'), value: 'All' },
{ label: __('Active'), value: 'Active' },
{ label: __('Inactive'), value: 'Inactive' },
]"
@change="(e) => changeStatus(e.target.value)"
>
</FormControl>
<Dropdown <Dropdown
:options="[ :options="[
{ {
@ -48,70 +36,50 @@
</div> </div>
</div> </div>
<!-- loading state -->
<div v-if="users.loading" class="flex mt-28 justify-between w-full h-full">
<Button
:loading="users.loading"
variant="ghost"
class="w-full"
size="2xl"
/>
</div>
<!-- Empty State -->
<div
v-if="!users.loading && !users.data?.length"
class="flex mt-28 justify-between w-full h-full"
>
<p class="text-sm text-gray-500 w-full flex justify-center">
{{ __('No users found') }}
</p>
</div>
<!-- Users List --> <!-- Users List -->
<ul <ul
v-if="!users.loading && Boolean(users.data?.length)" v-if="!users.loading && Boolean(users.data?.crmUsers?.length)"
class="divide-y divide-outline-gray-modals overflow-auto" class="divide-y divide-outline-gray-modals overflow-auto"
> >
<li <template v-for="user in users.data?.crmUsers" :key="user.name">
class="flex items-center justify-between py-2" <li
v-for="user in users.data" v-if="user.name !== 'Administrator'"
:key="user.name" class="flex items-center justify-between py-2"
> >
<div class="flex items-center"> <div class="flex items-center">
<Avatar :image="user.image" :label="user.user_name" size="xl" /> <Avatar
<div class="flex flex-col gap-1 ml-3"> :image="user.user_image"
<div class="flex items-center gap-2 text-base text-ink-gray-9 h-4"> :label="user.full_name"
{{ user.user_name }} size="xl"
<Badge />
v-if="!user.is_active" <div class="flex flex-col gap-1 ml-3">
variant="subtle" <div class="flex items-center text-base text-ink-gray-9 h-4">
theme="gray" {{ user.full_name }}
size="sm" </div>
label="Inactive" <div class="text-base text-ink-gray-5">
/> {{ user.name }}
</div> </div>
<div class="text-base text-ink-gray-5">
{{ user.name }}
</div> </div>
</div> </div>
</div> <div class="flex gap-2 items-center flex-row-reverse">
<div class="flex gap-2 items-center flex-row-reverse"> <Dropdown
<Dropdown :options="getMoreOptions(user)"
:options="getMoreOptions(user)" :button="{
:button="{ icon: 'more-horizontal',
icon: 'more-horizontal', }"
}" placement="right"
placement="right" />
/> <Dropdown
<Dropdown :options="getDropdownOptions(user)"
:options="getDropdownOptions(user)" :button="{
:button="{ label: roleMap[getUserRole(user.name)],
label: roleMap[getUserRole(user.name)], iconRight: 'chevron-down',
iconRight: 'chevron-down', }"
}" placement="right"
placement="right" />
/> </div>
</div> </li>
</li> </template>
<!-- Load More Button --> <!-- Load More Button -->
<div <div
v-if="!users.loading && users.hasNextPage" v-if="!users.loading && users.hasNextPage"
@ -126,11 +94,30 @@
/> />
</div> </div>
</ul> </ul>
<!-- loading state -->
<div v-if="users.loading" class="flex mt-28 justify-between w-full h-full">
<Button
:loading="users.loading"
variant="ghost"
class="w-full"
size="2xl"
/>
</div>
<!-- Empty State -->
<div
v-if="!users.loading && !users.data?.crmUsers?.length"
class="flex mt-28 justify-between w-full h-full"
>
<p class="text-sm text-gray-500 w-full flex justify-center">
{{ __('No users found') }}
</p>
</div>
</div> </div>
<AddExistingUserModal <AddExistingUserModal
v-if="showAddExistingModal" v-if="showAddExistingModal"
v-model="showAddExistingModal" v-model="showAddExistingModal"
:users="users"
/> />
</template> </template>
@ -139,31 +126,13 @@ 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 { import { Avatar, toast, call } from 'frappe-ui'
Avatar, import { ref, h } from 'vue'
Badge,
createListResource,
FormControl,
toast,
call,
} from 'frappe-ui'
import { ref, h, watch, onMounted } from 'vue'
const { users: usersResource, getUserRole, isAdmin, isManager } = usersStore() const { users, getUserRole, isAdmin, isManager } = usersStore()
const showAddExistingModal = ref(false) const showAddExistingModal = ref(false)
const users = createListResource({
doctype: 'CRM User',
cache: 'CRM Users',
fields: ['name', 'image', 'is_active', 'user_name'],
filters: { is_active: ['=', 1] },
auto: true,
start: 0,
pageLength: 20,
orderBy: 'creation desc',
})
const roleMap = { const roleMap = {
'System Manager': __('Admin'), 'System Manager': __('Admin'),
'Sales Manager': __('Manager'), 'Sales Manager': __('Manager'),
@ -173,20 +142,13 @@ const roleMap = {
function getMoreOptions(user) { function getMoreOptions(user) {
let options = [ let options = [
{ {
label: __('Activate'), label: __('Remove'),
icon: 'check-circle', icon: 'trash-2',
onClick: () => updateStatus(user, true), onClick: () => removeUser(user, true),
condition: () => !user.is_active,
},
{
label: __('Deactivate'),
icon: 'x-circle',
onClick: () => updateStatus(user, false),
condition: () => user.is_active,
}, },
] ]
return options.filter((option) => option.condition()) return options.filter((option) => option.condition?.() || true)
} }
function getDropdownOptions(user) { function getDropdownOptions(user) {
@ -252,73 +214,25 @@ function RoleOption({ active, role, onClick, selected }) {
} }
function updateRole(user, newRole) { function updateRole(user, newRole) {
const currentRole = getUserRole(user.name) if (user.role === newRole) return
if (currentRole === newRole) return
call('crm.fcrm.doctype.crm_user.crm_user.update_user_role', { call('crm.api.user.update_user_role', {
user: user.name, user: user.name,
new_role: newRole, new_role: newRole,
}).then(() => { }).then(() => {
toast.success( toast.success(
__('{0} has been granted {1} access', [user.user_name, roleMap[newRole]]), __('{0} has been granted {1} access', [user.full_name, roleMap[newRole]]),
) )
usersResource.reload()
users.reload() users.reload()
}) })
} }
function updateStatus(user, status) { function removeUser(user) {
const currentStatus = user.is_active call('crm.api.user.remove_user', {
if (currentStatus === status) return
call('crm.fcrm.doctype.crm_user.crm_user.update_user_status', {
user: user.name, user: user.name,
status,
}).then(() => { }).then(() => {
toast.success( toast.success(__('User {0} has been removed', [user.full_name]))
__('{0} has been {1}', [
user.user_name,
status ? 'activated' : 'deactivated',
]),
)
users.reload() users.reload()
}) })
} }
const currentStatus = ref('Active')
function changeStatus(status) {
currentStatus.value = status
updateFilters()
}
function updateFilters() {
const status = currentStatus.value || 'Active'
users.filters = {}
if (status === 'Active') {
users.filters.is_active = ['=', 1]
} else if (status === 'Inactive') {
users.filters.is_active = ['=', 0]
}
users.reload()
}
onMounted(() => updateFilters())
const search = ref('')
watch(search, (newValue) => {
users.filters = {
is_active: ['=', 1],
user_name: ['like', `%${newValue}%`],
}
if (!newValue) {
users.filters = {
is_active: ['=', 1],
}
users.start = 0
users.pageLength = 10
}
users.reload()
})
</script> </script>