fix: improved evaluators list
This commit is contained in:
@@ -23,8 +23,8 @@ describe("Batch Creation", () => {
|
|||||||
const randomEmail = `testuser_${dateNow}@example.com`;
|
const randomEmail = `testuser_${dateNow}@example.com`;
|
||||||
const randomName = `Test User ${dateNow}`;
|
const randomName = `Test User ${dateNow}`;
|
||||||
|
|
||||||
cy.get("input[placeholder='Email']").type(randomEmail);
|
cy.get("input[placeholder='jane@doe.com']").type(randomEmail);
|
||||||
cy.get("input[placeholder='First Name']").type(randomName);
|
cy.get("input[placeholder='Jane']").type(randomName);
|
||||||
cy.get("button").contains("Add").click();
|
cy.get("button").contains("Add").click();
|
||||||
|
|
||||||
// Add evaluator
|
// Add evaluator
|
||||||
@@ -39,7 +39,7 @@ describe("Batch Creation", () => {
|
|||||||
.click();
|
.click();
|
||||||
const randomEvaluator = `evaluator${dateNow}@example.com`;
|
const randomEvaluator = `evaluator${dateNow}@example.com`;
|
||||||
|
|
||||||
cy.get("input[placeholder='Email']").type(randomEvaluator);
|
cy.get("input[placeholder='jane@doe.com']").type(randomEvaluator);
|
||||||
cy.get("button").contains("Add").click();
|
cy.get("button").contains("Add").click();
|
||||||
cy.get("div").contains(randomEvaluator).should("be.visible").click();
|
cy.get("div").contains(randomEvaluator).should("be.visible").click();
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +1,129 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex min-h-0 flex-col text-base">
|
<div class="flex min-h-0 flex-col text-base">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div class="text-xl font-semibold mb-1 text-ink-gray-9">
|
<div class="text-xl font-semibold mb-1 text-ink-gray-9">
|
||||||
{{ __(label) }}
|
{{ __(label) }}
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="text-xs text-ink-gray-5">
|
<div class="text-ink-gray-6 leading-5">
|
||||||
{{ __(description) }}
|
{{ __(description) }}
|
||||||
</div> -->
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex item-center space-x-2">
|
<div class="flex item-center space-x-2">
|
||||||
<FormControl
|
<Button variant="solid" @click="() => (showForm = !showForm)">
|
||||||
v-model="search"
|
|
||||||
:placeholder="__('Search')"
|
|
||||||
type="text"
|
|
||||||
:debounce="300"
|
|
||||||
/>
|
|
||||||
<Button @click="() => (showForm = !showForm)">
|
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<Plus v-if="!showForm" class="size-4 stroke-1.5" />
|
<Plus class="size-4 stroke-1.5" />
|
||||||
<X v-else class="size-4 stroke-1.5" />
|
|
||||||
</template>
|
</template>
|
||||||
{{ showForm ? __('Close') : __('New') }}
|
{{ __('New') }}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Form to add new member -->
|
<div class="mt-8 pb-5">
|
||||||
<div v-if="showForm" class="flex items-center space-x-2 my-4">
|
|
||||||
<FormControl
|
<FormControl
|
||||||
v-model="email"
|
v-model="search"
|
||||||
:placeholder="__('Email')"
|
:placeholder="__('Search')"
|
||||||
type="email"
|
type="text"
|
||||||
class="w-full"
|
:debounce="300"
|
||||||
@keydown.enter="addEvaluator"
|
class="w-1/4 mb-4"
|
||||||
/>
|
>
|
||||||
<Button @click="addEvaluator()" variant="subtle">
|
<template #prefix>
|
||||||
{{ __('Add') }}
|
<Search class="size-4 stroke-1.5 text-ink-gray-5" />
|
||||||
</Button>
|
</template>
|
||||||
</div>
|
</FormControl>
|
||||||
|
<div class="overflow-auto h-[60vh]">
|
||||||
<div class="overflow-y-scroll">
|
<div class="divide-y">
|
||||||
<div class="divide-y">
|
<div
|
||||||
<div
|
v-for="evaluator in evaluators.data"
|
||||||
v-for="evaluator in evaluators.data"
|
:key="evaluator.evaluator"
|
||||||
@click="openProfile(evaluator.username)"
|
class="cursor-pointer"
|
||||||
class="cursor-pointer"
|
>
|
||||||
>
|
<div class="flex items-center justify-between group py-3">
|
||||||
<div class="flex items-center justify-between py-3">
|
<div
|
||||||
<div class="flex items-center space-x-3">
|
class="flex items-center space-x-3"
|
||||||
<Avatar
|
@click="openProfile(evaluator.username)"
|
||||||
:image="evaluator.user_image"
|
>
|
||||||
:label="evaluator.full_name"
|
<Avatar
|
||||||
size="lg"
|
:image="evaluator.user_image"
|
||||||
/>
|
:label="evaluator.full_name"
|
||||||
<div>
|
size="xl"
|
||||||
<div class="text-base font-semibold text-ink-gray-9">
|
/>
|
||||||
{{ evaluator.full_name }}
|
<div class="space-y-1">
|
||||||
</div>
|
<div class="text-base font-semibold text-ink-gray-9">
|
||||||
<div class="text-xs text-ink-gray-5">
|
{{ evaluator.full_name }}
|
||||||
{{ evaluator.evaluator }}
|
</div>
|
||||||
|
<div class="text-xs text-ink-gray-5">
|
||||||
|
{{ evaluator.evaluator }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="invisible group-hover:visible">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
@click="deleteEvaluator(evaluator.evaluator)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<Trash2 class="size-4 stroke-1.5 text-ink-red-3" />
|
||||||
|
</template>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="evaluators.length && hasNextPage"
|
||||||
|
class="flex justify-center mt-4"
|
||||||
|
>
|
||||||
|
<Button @click="evaluators.reload()">
|
||||||
|
<template #prefix>
|
||||||
|
<RefreshCw class="h-3 w-3 stroke-1.5" />
|
||||||
|
</template>
|
||||||
|
{{ __('Load More') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Dialog
|
||||||
|
v-model="showForm"
|
||||||
|
:options="{
|
||||||
|
size: 'xl',
|
||||||
|
title: __('Add Evaluator'),
|
||||||
|
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 { createResource, Button, FormControl, call, Avatar } from 'frappe-ui'
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
call,
|
||||||
|
createListResource,
|
||||||
|
Dialog,
|
||||||
|
FormControl,
|
||||||
|
toast,
|
||||||
|
} from 'frappe-ui'
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { Plus, X } from 'lucide-vue-next'
|
import { Plus, Search, Trash2, RefreshCw } from 'lucide-vue-next'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const show = defineModel('show')
|
const show = defineModel('show')
|
||||||
@@ -95,33 +146,39 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const evaluators = createResource({
|
const evaluators = createListResource({
|
||||||
url: 'frappe.client.get_list',
|
doctype: 'Course Evaluator',
|
||||||
makeParams: () => {
|
fields: ['evaluator', 'username', 'full_name', 'user_image'],
|
||||||
return {
|
|
||||||
doctype: 'Course Evaluator',
|
|
||||||
fields: ['evaluator', 'full_name', 'user_image', 'username'],
|
|
||||||
filters: search.value ? { evaluator: ['like', `%${search.value}%`] } : {},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
auto: true,
|
auto: true,
|
||||||
|
orderBy: 'creation desc',
|
||||||
})
|
})
|
||||||
|
|
||||||
const addEvaluator = () => {
|
const addEvaluator = (close: () => void) => {
|
||||||
call('lms.lms.api.add_an_evaluator', {
|
call('lms.lms.api.add_an_evaluator', {
|
||||||
email: email.value,
|
email: email.value,
|
||||||
}).then((data) => {
|
|
||||||
showForm.value = false
|
|
||||||
email.value = ''
|
|
||||||
evaluators.reload()
|
|
||||||
})
|
})
|
||||||
|
.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, () => {
|
||||||
|
evaluators.update({
|
||||||
|
filters: {
|
||||||
|
full_name: ['like', `%${search.value}%`],
|
||||||
|
},
|
||||||
|
})
|
||||||
evaluators.reload()
|
evaluators.reload()
|
||||||
})
|
})
|
||||||
|
|
||||||
const openProfile = (username) => {
|
const openProfile = (username: string) => {
|
||||||
show.value = false
|
show.value = false
|
||||||
router.push({
|
router.push({
|
||||||
name: 'Profile',
|
name: 'Profile',
|
||||||
@@ -130,4 +187,18 @@ const openProfile = (username) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteEvaluator = (evaluator: string) => {
|
||||||
|
call('lms.lms.api.delete_evaluator', {
|
||||||
|
evaluator: evaluator,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
toast.success(__('Evaluator deleted successfully'))
|
||||||
|
evaluators.reload()
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
toast.error(__(error.messages[0] || error.messages))
|
||||||
|
console.error('Error deleting evaluator:', error)
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 pb-10 overflow-auto">
|
<div class="mt-8 pb-10">
|
||||||
<FormControl
|
<FormControl
|
||||||
v-model="search"
|
v-model="search"
|
||||||
:placeholder="__('Search')"
|
:placeholder="__('Search')"
|
||||||
@@ -31,8 +31,7 @@
|
|||||||
<Search class="size-4 stroke-1.5 text-ink-gray-5" />
|
<Search class="size-4 stroke-1.5 text-ink-gray-5" />
|
||||||
</template>
|
</template>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<!-- Member list -->
|
<div class="overflow-y-scroll h-[60vh]">
|
||||||
<div class="overflow-y-scroll">
|
|
||||||
<ul class="divide-y">
|
<ul class="divide-y">
|
||||||
<li
|
<li
|
||||||
v-for="member in memberList"
|
v-for="member in memberList"
|
||||||
@@ -69,17 +68,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
<div
|
||||||
<div
|
v-if="memberList.length && hasNextPage"
|
||||||
v-if="memberList.length && hasNextPage"
|
class="flex justify-center mt-4"
|
||||||
class="flex justify-center mt-4"
|
>
|
||||||
>
|
<Button @click="members.reload()">
|
||||||
<Button @click="members.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>
|
{{ __('Load More') }}
|
||||||
{{ __('Load More') }}
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -210,7 +209,6 @@ const newMember = createResource({
|
|||||||
auto: false,
|
auto: false,
|
||||||
onSuccess(data: Member) {
|
onSuccess(data: Member) {
|
||||||
show.value = false
|
show.value = false
|
||||||
|
|
||||||
if (user?.data?.is_system_manager) updateOnboardingStep('invite_students')
|
if (user?.data?.is_system_manager) updateOnboardingStep('invite_students')
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
|
|||||||
@@ -248,8 +248,10 @@ const tabsStructure = computed(() => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Evaluators',
|
label: 'Evaluators',
|
||||||
description: 'Manage the evaluators of your learning system',
|
description: '',
|
||||||
icon: 'UserCheck',
|
icon: 'UserCheck',
|
||||||
|
description:
|
||||||
|
'Add new evaluators or check the slots existing evaluators',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Categories',
|
label: 'Categories',
|
||||||
|
|||||||
@@ -1392,6 +1392,7 @@ def save_role(user, role, value):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def add_an_evaluator(email):
|
def add_an_evaluator(email):
|
||||||
|
frappe.only_for("Moderator")
|
||||||
if not frappe.db.exists("User", email):
|
if not frappe.db.exists("User", email):
|
||||||
user = frappe.new_doc("User")
|
user = frappe.new_doc("User")
|
||||||
user.update(
|
user.update(
|
||||||
@@ -1411,6 +1412,16 @@ def add_an_evaluator(email):
|
|||||||
return evaluator
|
return evaluator
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def delete_evaluator(evaluator):
|
||||||
|
frappe.only_for("Moderator")
|
||||||
|
if not frappe.db.exists("Course Evaluator", evaluator):
|
||||||
|
frappe.throw(_("Evaluator does not exist."))
|
||||||
|
|
||||||
|
frappe.db.delete("Has Role", {"parent": evaluator, "role": "Batch Evaluator"})
|
||||||
|
frappe.db.delete("Course Evaluator", evaluator)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def capture_user_persona(responses):
|
def capture_user_persona(responses):
|
||||||
frappe.only_for("System Manager")
|
frappe.only_for("System Manager")
|
||||||
|
|||||||
@@ -58,8 +58,7 @@
|
|||||||
"fetch_from": "evaluator.full_name",
|
"fetch_from": "evaluator.full_name",
|
||||||
"fieldname": "full_name",
|
"fieldname": "full_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Full Name",
|
"label": "Full Name"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_casg",
|
"fieldname": "column_break_casg",
|
||||||
@@ -73,21 +72,19 @@
|
|||||||
"fetch_from": "evaluator.user_image",
|
"fetch_from": "evaluator.user_image",
|
||||||
"fieldname": "user_image",
|
"fieldname": "user_image",
|
||||||
"fieldtype": "Attach Image",
|
"fieldtype": "Attach Image",
|
||||||
"label": "User Image",
|
"label": "User Image"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "evaluator.username",
|
"fetch_from": "evaluator.username",
|
||||||
"fieldname": "username",
|
"fieldname": "username",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Username",
|
"label": "Username"
|
||||||
"read_only": 1
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-06-05 11:04:32.475711",
|
"modified": "2025-07-04 12:04:11.007945",
|
||||||
"modified_by": "sayali@frappe.io",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Course Evaluator",
|
"name": "Course Evaluator",
|
||||||
"naming_rule": "By fieldname",
|
"naming_rule": "By fieldname",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"attach_print": 0,
|
"attach_print": 0,
|
||||||
"channel": "Email",
|
"channel": "Email",
|
||||||
|
"condition": "doc.status == \"Upcoming\"",
|
||||||
"creation": "2022-06-03 11:51:02.681803",
|
"creation": "2022-06-03 11:51:02.681803",
|
||||||
"date_changed": "date",
|
"date_changed": "date",
|
||||||
"days_in_advance": 1,
|
"days_in_advance": 1,
|
||||||
@@ -13,7 +14,8 @@
|
|||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"message": "<p> {{ _(\"Hey {0}\").format(doc.member_name) }} </p>\n<br>\n<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(doc.course_title, frappe.utils.format_date(doc.date, \"medium\"), frappe.utils.format_time(doc.start_time, \"short\"), doc.timezone) }}</p>\n<br>\n<p> {{ _(\"{0} is your evaluator\").format(doc.evaluator_name) }} </p>\n<br>\n<p> {{ _(\"Please prepare well and be on time for the evaluations.\") }} </p>\n",
|
"message": "<p> {{ _(\"Hey {0}\").format(doc.member_name) }} </p>\n<br>\n<p> {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(doc.course_title, frappe.utils.format_date(doc.date, \"medium\"), frappe.utils.format_time(doc.start_time, \"short\"), doc.timezone) }}</p>\n<br>\n<p> {{ _(\"{0} is your evaluator\").format(doc.evaluator_name) }} </p>\n<br>\n<p> {{ _(\"Please prepare well and be on time for the evaluations.\") }} </p>\n",
|
||||||
"message_type": "HTML",
|
"message_type": "HTML",
|
||||||
"modified": "2024-09-05 16:33:42.212842",
|
"minutes_offset": 0,
|
||||||
|
"modified": "2025-07-04 10:47:58.448814",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "Certificate Request Reminder",
|
"name": "Certificate Request Reminder",
|
||||||
@@ -22,6 +24,12 @@
|
|||||||
{
|
{
|
||||||
"receiver_by_document_field": "member"
|
"receiver_by_document_field": "member"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"receiver_by_document_field": "member"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"receiver_by_document_field": "evaluator"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"receiver_by_document_field": "evaluator"
|
"receiver_by_document_field": "evaluator"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user