mirror of
https://github.com/frappe/lms.git
synced 2026-05-02 13:39:31 +03:00
feat: add member modal, evaluator add refactor
This commit is contained in:
Vendored
+2
@@ -8,6 +8,7 @@ export {}
|
|||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
AddEvaluatorModal: typeof import('./src/components/Modals/AddEvaluatorModal.vue')['default']
|
||||||
Apps: typeof import('./src/components/Sidebar/Apps.vue')['default']
|
Apps: typeof import('./src/components/Sidebar/Apps.vue')['default']
|
||||||
AppSidebar: typeof import('./src/components/Sidebar/AppSidebar.vue')['default']
|
AppSidebar: typeof import('./src/components/Sidebar/AppSidebar.vue')['default']
|
||||||
AssessmentModal: typeof import('./src/components/Modals/AssessmentModal.vue')['default']
|
AssessmentModal: typeof import('./src/components/Modals/AssessmentModal.vue')['default']
|
||||||
@@ -78,6 +79,7 @@ declare module 'vue' {
|
|||||||
Members: typeof import('./src/components/Settings/Members.vue')['default']
|
Members: typeof import('./src/components/Settings/Members.vue')['default']
|
||||||
MobileLayout: typeof import('./src/components/MobileLayout.vue')['default']
|
MobileLayout: typeof import('./src/components/MobileLayout.vue')['default']
|
||||||
MultiSelect: typeof import('./src/components/Controls/MultiSelect.vue')['default']
|
MultiSelect: typeof import('./src/components/Controls/MultiSelect.vue')['default']
|
||||||
|
NewMemberModal: typeof import('./src/components/Modals/NewMemberModal.vue')['default']
|
||||||
NoPermission: typeof import('./src/components/NoPermission.vue')['default']
|
NoPermission: typeof import('./src/components/NoPermission.vue')['default']
|
||||||
NoSidebarLayout: typeof import('./src/components/NoSidebarLayout.vue')['default']
|
NoSidebarLayout: typeof import('./src/components/NoSidebarLayout.vue')['default']
|
||||||
Notes: typeof import('./src/components/Notes/Notes.vue')['default']
|
Notes: typeof import('./src/components/Notes/Notes.vue')['default']
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog
|
||||||
|
v-model="show"
|
||||||
|
:options="{
|
||||||
|
title: __('Add Existing User as Evaluator'),
|
||||||
|
size: 'md',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: __('Add'),
|
||||||
|
variant: 'solid',
|
||||||
|
loading: submitting,
|
||||||
|
onClick: ({ close }: any) => addEvaluator(close),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #body-content>
|
||||||
|
<Link doctype="User" v-model="selectedUser" :label="__('Select User')" />
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { call, Dialog, toast } from 'frappe-ui'
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { cleanError } from '@/utils'
|
||||||
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
|
||||||
|
const show = defineModel<boolean>({ default: false })
|
||||||
|
const selectedUser = ref('')
|
||||||
|
const submitting = ref(false)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
added: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
watch(show, (isOpen) => {
|
||||||
|
if (isOpen) {
|
||||||
|
selectedUser.value = ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const addEvaluator = async (close?: () => void) => {
|
||||||
|
if (!selectedUser.value?.trim()) {
|
||||||
|
toast.error(__('Please select a user'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
await call('lms.lms.api.save_role', {
|
||||||
|
user: selectedUser.value,
|
||||||
|
role: 'Batch Evaluator',
|
||||||
|
value: 1,
|
||||||
|
})
|
||||||
|
toast.success(__('Evaluator added successfully'))
|
||||||
|
emit('added')
|
||||||
|
close?.()
|
||||||
|
} catch (err: any) {
|
||||||
|
toast.error(cleanError(err.messages?.[0]) || __('Unable to add evaluator'))
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog
|
||||||
|
v-model="show"
|
||||||
|
:options="{
|
||||||
|
title: __('Add New Member'),
|
||||||
|
size: 'lg',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: __('Add'),
|
||||||
|
variant: 'solid',
|
||||||
|
loading: submitting,
|
||||||
|
onClick: ({ close }: any) => addMember(close),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #body-content>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<FormControl
|
||||||
|
v-model="member.email"
|
||||||
|
:label="__('Email')"
|
||||||
|
placeholder="jane@doe.com"
|
||||||
|
type="email"
|
||||||
|
:required="true"
|
||||||
|
@keyup.enter="addMember()"
|
||||||
|
/>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<FormControl
|
||||||
|
v-model="member.first_name"
|
||||||
|
:label="__('First Name')"
|
||||||
|
placeholder="Jane"
|
||||||
|
type="text"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
<FormControl
|
||||||
|
v-model="member.last_name"
|
||||||
|
:label="__('Last Name')"
|
||||||
|
placeholder="Doe"
|
||||||
|
type="text"
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="text-sm text-ink-gray-5">
|
||||||
|
{{ __('Roles') }}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-x-6">
|
||||||
|
<FormControl
|
||||||
|
:label="__('Student')"
|
||||||
|
v-model="roles.lms_student"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<FormControl
|
||||||
|
:label="__('Course Creator')"
|
||||||
|
v-model="roles.course_creator"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<FormControl
|
||||||
|
:label="__('Evaluator')"
|
||||||
|
v-model="roles.batch_evaluator"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<FormControl
|
||||||
|
:label="__('Moderator')"
|
||||||
|
v-model="roles.moderator"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { call, Dialog, FormControl, toast } from 'frappe-ui'
|
||||||
|
import { reactive, ref, watch } from 'vue'
|
||||||
|
import { cleanError } from '@/utils'
|
||||||
|
|
||||||
|
const show = defineModel<boolean>({ default: false })
|
||||||
|
const submitting = ref(false)
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
defaultRoles?: string[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
created: [user: any]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const ROLE_MAP: Record<string, string> = {
|
||||||
|
moderator: 'Moderator',
|
||||||
|
course_creator: 'Course Creator',
|
||||||
|
batch_evaluator: 'Batch Evaluator',
|
||||||
|
lms_student: 'LMS Student',
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = reactive({
|
||||||
|
email: '',
|
||||||
|
first_name: '',
|
||||||
|
last_name: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const roles = reactive({
|
||||||
|
moderator: false,
|
||||||
|
course_creator: false,
|
||||||
|
batch_evaluator: false,
|
||||||
|
lms_student: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
member.email = ''
|
||||||
|
member.first_name = ''
|
||||||
|
member.last_name = ''
|
||||||
|
applyDefaultRoles()
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyDefaultRoles = () => {
|
||||||
|
roles.moderator = props.defaultRoles?.includes('moderator') ?? false
|
||||||
|
roles.course_creator = props.defaultRoles?.includes('course_creator') ?? false
|
||||||
|
roles.batch_evaluator =
|
||||||
|
props.defaultRoles?.includes('batch_evaluator') ?? false
|
||||||
|
roles.lms_student = props.defaultRoles?.includes('lms_student') ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(show, (isOpen) => {
|
||||||
|
if (isOpen) {
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const assignRoles = async (userEmail: string) => {
|
||||||
|
const selectedRoles = Object.entries(roles).filter(([_, checked]) => checked)
|
||||||
|
|
||||||
|
for (const [key, _] of selectedRoles) {
|
||||||
|
await call('lms.lms.api.save_role', {
|
||||||
|
user: userEmail,
|
||||||
|
role: ROLE_MAP[key],
|
||||||
|
value: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addMember = async (close?: () => void) => {
|
||||||
|
if (!member.email?.trim()) {
|
||||||
|
toast.error(__('Email is required'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
const user = await call('frappe.client.insert', {
|
||||||
|
doc: {
|
||||||
|
doctype: 'User',
|
||||||
|
email: member.email.trim(),
|
||||||
|
first_name: member.first_name.trim() || undefined,
|
||||||
|
last_name: member.last_name.trim() || undefined,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await assignRoles(user.name)
|
||||||
|
|
||||||
|
toast.success(__('Member added successfully'))
|
||||||
|
emit('created', user)
|
||||||
|
resetForm()
|
||||||
|
close?.()
|
||||||
|
} catch (err: any) {
|
||||||
|
toast.error(cleanError(err.messages?.[0]) || __('Unable to add member'))
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -10,12 +10,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex item-center space-x-2">
|
<div class="flex item-center space-x-2">
|
||||||
<Button @click="() => (showForm = !showForm)">
|
<Dropdown
|
||||||
<template #prefix>
|
placement="right"
|
||||||
<Plus class="size-4 stroke-1.5" />
|
side="bottom"
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
label: __('New Evaluator'),
|
||||||
|
icon: 'user-plus',
|
||||||
|
onClick() {
|
||||||
|
showNewEvaluator = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __('Existing User'),
|
||||||
|
icon: 'user-check',
|
||||||
|
onClick() {
|
||||||
|
showExistingUser = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<template v-slot="{ open }">
|
||||||
|
<Button variant="solid">
|
||||||
|
<template #prefix>
|
||||||
|
<Plus class="size-4 stroke-1.5" />
|
||||||
|
</template>
|
||||||
|
{{ __('New') }}
|
||||||
|
<template #suffix>
|
||||||
|
<ChevronDown
|
||||||
|
:class="[
|
||||||
|
'w-4 h-4 stroke-1.5 ml-1 transform transition-transform',
|
||||||
|
open ? 'rotate-180' : '',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
{{ __('New') }}
|
</Dropdown>
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -71,11 +102,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="evaluators.hasNextPage" class="flex justify-center mt-4">
|
||||||
v-if="evaluators.length && hasNextPage"
|
<Button @click="evaluators.next()">
|
||||||
class="flex justify-center mt-4"
|
|
||||||
>
|
|
||||||
<Button @click="evaluators.reload()">
|
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<RefreshCw class="h-3 w-3 stroke-1.5" />
|
<RefreshCw class="h-3 w-3 stroke-1.5" />
|
||||||
</template>
|
</template>
|
||||||
@@ -85,33 +113,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Dialog
|
<AddEvaluatorModal v-model="showExistingUser" @added="evaluators.reload()" />
|
||||||
v-model="showForm"
|
<NewMemberModal
|
||||||
:options="{
|
v-model="showNewEvaluator"
|
||||||
size: 'xl',
|
:defaultRoles="['batch_evaluator']"
|
||||||
title: __('Add Evaluator'),
|
@created="onMemberCreated"
|
||||||
actions: [{
|
/>
|
||||||
label: __('Add'),
|
|
||||||
variant: 'solid',
|
|
||||||
onClick({ close }: any) {
|
|
||||||
addEvaluator(close)
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #body-content>
|
|
||||||
<div v-if="showForm" class="flex items-center">
|
|
||||||
<FormControl
|
|
||||||
v-model="email"
|
|
||||||
:label="__('Email')"
|
|
||||||
placeholder="jane@doe.com"
|
|
||||||
type="email"
|
|
||||||
class="w-full"
|
|
||||||
@keydown.enter="addEvaluator"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Dialog>
|
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
@@ -119,18 +126,19 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
call,
|
call,
|
||||||
createListResource,
|
createListResource,
|
||||||
Dialog,
|
Dropdown,
|
||||||
FormControl,
|
FormControl,
|
||||||
toast,
|
toast,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { Plus, Search, Trash2, RefreshCw } from 'lucide-vue-next'
|
import { Plus, Search, Trash2, RefreshCw, ChevronDown } from 'lucide-vue-next'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import NewMemberModal from '@/components/Modals/NewMemberModal.vue'
|
||||||
|
import AddEvaluatorModal from '@/components/Modals/AddEvaluatorModal.vue'
|
||||||
|
|
||||||
const show = defineModel('show')
|
|
||||||
const search = ref('')
|
const search = ref('')
|
||||||
const showForm = ref(false)
|
const showExistingUser = ref(false)
|
||||||
const email = ref('')
|
const showNewEvaluator = ref(false)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -151,20 +159,8 @@ const evaluators = createListResource({
|
|||||||
orderBy: 'creation desc',
|
orderBy: 'creation desc',
|
||||||
})
|
})
|
||||||
|
|
||||||
const addEvaluator = (close: () => void) => {
|
const onMemberCreated = () => {
|
||||||
call('lms.lms.api.add_an_evaluator', {
|
evaluators.reload()
|
||||||
email: email.value,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
email.value = ''
|
|
||||||
evaluators.reload()
|
|
||||||
toast.success(__('Evaluator added successfully'))
|
|
||||||
close()
|
|
||||||
})
|
|
||||||
.catch((error: any) => {
|
|
||||||
toast.error(__(error.messages[0] || error.messages))
|
|
||||||
console.error('Error adding evaluator:', error)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(search, () => {
|
watch(search, () => {
|
||||||
@@ -177,7 +173,6 @@ watch(search, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const openProfile = (username: string) => {
|
const openProfile = (username: string) => {
|
||||||
show.value = false
|
|
||||||
router.push({
|
router.push({
|
||||||
name: 'Profile',
|
name: 'Profile',
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex item-center space-x-2">
|
<div class="flex item-center space-x-2">
|
||||||
<Button @click="() => (showForm = !showForm)">
|
<Button @click="showNewMember = true">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<Plus class="size-4 stroke-1.5" />
|
<Plus class="size-4 stroke-1.5" />
|
||||||
</template>
|
</template>
|
||||||
@@ -82,56 +82,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Dialog
|
<NewMemberModal v-model="showNewMember" @created="onMemberCreated" />
|
||||||
v-model="showForm"
|
|
||||||
:options="{
|
|
||||||
title: __('Add a new member'),
|
|
||||||
size: 'lg',
|
|
||||||
actions: [{
|
|
||||||
label: __('Add'),
|
|
||||||
variant: 'solid',
|
|
||||||
onClick({ close }: any) {
|
|
||||||
addMember(close)
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #body-content>
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<FormControl
|
|
||||||
v-model="member.email"
|
|
||||||
:label="__('Email')"
|
|
||||||
placeholder="jane@doe.com"
|
|
||||||
type="email"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
<FormControl
|
|
||||||
v-model="member.first_name"
|
|
||||||
:label="__('First Name')"
|
|
||||||
placeholder="Jane"
|
|
||||||
type="text"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Dialog>
|
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import { Avatar, Button, createResource, FormControl } from 'frappe-ui'
|
||||||
Avatar,
|
|
||||||
Button,
|
|
||||||
call,
|
|
||||||
createResource,
|
|
||||||
Dialog,
|
|
||||||
FormControl,
|
|
||||||
toast,
|
|
||||||
} from 'frappe-ui'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ref, watch, reactive, inject } from 'vue'
|
import { ref, watch, inject } from 'vue'
|
||||||
import { RefreshCw, Plus, Search, Shield } from 'lucide-vue-next'
|
import { RefreshCw, Plus, Search, Shield } from 'lucide-vue-next'
|
||||||
import { useOnboarding } from 'frappe-ui/frappe'
|
import { useOnboarding, useTelemetry } from 'frappe-ui/frappe'
|
||||||
import type { User } from '@/components/Settings/types'
|
import type { User } from '@/components/Settings/types'
|
||||||
import { useTelemetry } from 'frappe-ui/frappe'
|
import NewMemberModal from '@/components/Modals/NewMemberModal.vue'
|
||||||
|
|
||||||
type Member = {
|
type Member = {
|
||||||
username: string
|
username: string
|
||||||
@@ -147,16 +107,11 @@ const search = ref('')
|
|||||||
const start = ref(0)
|
const start = ref(0)
|
||||||
const memberList = ref<Member[]>([])
|
const memberList = ref<Member[]>([])
|
||||||
const hasNextPage = ref(false)
|
const hasNextPage = ref(false)
|
||||||
const showForm = ref(false)
|
const showNewMember = ref(false)
|
||||||
const user = inject<User | null>('$user')
|
const user = inject<User | null>('$user')
|
||||||
const { updateOnboardingStep } = useOnboarding('learning')
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
const { capture } = useTelemetry()
|
const { capture } = useTelemetry()
|
||||||
|
|
||||||
const member = reactive({
|
|
||||||
email: '',
|
|
||||||
first_name: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -194,30 +149,12 @@ const openProfile = (username: string) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const addMember = (close: () => void) => {
|
const onMemberCreated = (data: any) => {
|
||||||
call('frappe.client.insert', {
|
if (user?.data?.is_system_manager) updateOnboardingStep('invite_students')
|
||||||
doc: {
|
capture('user_added')
|
||||||
doctype: 'User',
|
memberList.value = []
|
||||||
first_name: member.first_name,
|
start.value = 0
|
||||||
email: member.email,
|
members.reload()
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((data: Member) => {
|
|
||||||
if (user?.data?.is_system_manager) updateOnboardingStep('invite_students')
|
|
||||||
capture('user_added')
|
|
||||||
show.value = false
|
|
||||||
router.push({
|
|
||||||
name: 'ProfileRoles',
|
|
||||||
params: {
|
|
||||||
username: data.username,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
close()
|
|
||||||
})
|
|
||||||
.catch((err: any) => {
|
|
||||||
console.error(err)
|
|
||||||
toast.error(__(err.messages?.[0] || err))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(search, () => {
|
watch(search, () => {
|
||||||
|
|||||||
@@ -42,8 +42,7 @@
|
|||||||
...(activeTab.label == 'Branding'
|
...(activeTab.label == 'Branding'
|
||||||
? { sections: activeTab.sections }
|
? { sections: activeTab.sections }
|
||||||
: {}),
|
: {}),
|
||||||
...(activeTab.label == 'Evaluators' ||
|
...(activeTab.label == 'Members' ||
|
||||||
activeTab.label == 'Members' ||
|
|
||||||
activeTab.label == 'Transactions'
|
activeTab.label == 'Transactions'
|
||||||
? { 'onUpdate:show': (val) => (show = val), show }
|
? { 'onUpdate:show': (val) => (show = val), show }
|
||||||
: {}),
|
: {}),
|
||||||
|
|||||||
+46
-22
@@ -1468,28 +1468,6 @@ def save_evaluator_role(user: str, value: int):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def add_an_evaluator(email: str):
|
|
||||||
frappe.only_for("Moderator")
|
|
||||||
if not frappe.db.exists("User", email):
|
|
||||||
user = frappe.new_doc("User")
|
|
||||||
user.update(
|
|
||||||
{
|
|
||||||
"email": email,
|
|
||||||
"first_name": email.split("@")[0].capitalize(),
|
|
||||||
"enabled": 1,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
user.insert()
|
|
||||||
user.add_roles("Batch Evaluator")
|
|
||||||
|
|
||||||
evaluator = frappe.new_doc("Course Evaluator")
|
|
||||||
evaluator.evaluator = email
|
|
||||||
evaluator.insert()
|
|
||||||
|
|
||||||
return evaluator
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def capture_user_persona(responses: str):
|
def capture_user_persona(responses: str):
|
||||||
frappe.only_for("System Manager")
|
frappe.only_for("System Manager")
|
||||||
@@ -2329,3 +2307,49 @@ def clear_demo_data():
|
|||||||
frappe.delete_doc("User", user, ignore_permissions=True)
|
frappe.delete_doc("User", user, ignore_permissions=True)
|
||||||
|
|
||||||
frappe.db.set_single_value("LMS Settings", "demo_data_present", False)
|
frappe.db.set_single_value("LMS Settings", "demo_data_present", False)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def search_users_by_role(txt: str = "", roles: str | list | None = None, page_length: int = 10):
|
||||||
|
"""Returns users with `roles` in search_link format"""
|
||||||
|
frappe.only_for(["Moderator", "Course Creator", "Batch Evaluator"])
|
||||||
|
if not roles:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if isinstance(roles, str):
|
||||||
|
roles = json.loads(roles)
|
||||||
|
|
||||||
|
invalid_roles = set(roles) - set(LMS_ROLES)
|
||||||
|
if invalid_roles:
|
||||||
|
frappe.throw(_("Cannot search for roles: {0}").format(", ".join(invalid_roles)))
|
||||||
|
|
||||||
|
users_with_roles = frappe.get_all(
|
||||||
|
"Has Role",
|
||||||
|
filters={"role": ["in", roles], "parenttype": "User"},
|
||||||
|
pluck="parent",
|
||||||
|
distinct=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not users_with_roles:
|
||||||
|
return []
|
||||||
|
|
||||||
|
results = frappe.get_all(
|
||||||
|
"User",
|
||||||
|
filters=[
|
||||||
|
["name", "in", users_with_roles],
|
||||||
|
["name", "not in", ["Administrator", "Guest"]],
|
||||||
|
["enabled", "=", 1],
|
||||||
|
],
|
||||||
|
or_filters=[
|
||||||
|
["full_name", "like", f"%{txt}%"],
|
||||||
|
["name", "like", f"%{txt}%"],
|
||||||
|
],
|
||||||
|
fields=["name", "full_name"],
|
||||||
|
limit_page_length=cint(page_length),
|
||||||
|
order_by="full_name asc",
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
{"value": r.name, "description": r.full_name or r.name, "label": r.full_name or r.name}
|
||||||
|
for r in results
|
||||||
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user