Skip to content

Commit

Permalink
Merge pull request #1183 from pateljannat/batch-dashboard
Browse files Browse the repository at this point in the history
feat: show student course and assessment progress on batch page
  • Loading branch information
pateljannat authored Dec 13, 2024
2 parents 5cc12e7 + 40aefba commit 76a84c7
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 69 deletions.
12 changes: 9 additions & 3 deletions frontend/src/components/Assessments.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div>
<div class="flex items-center justify-between">
<div class="text-lg font-semibold mb-4">
<div class="flex items-center justify-between mb-4">
<div class="text-lg font-semibold">
{{ __('Assessments') }}
</div>
<Button v-if="canSeeAddButton()" @click="showModal = true">
Expand Down Expand Up @@ -38,7 +38,10 @@
<ListRow :row="row" v-for="row in assessments.data">
<template #default="{ column, item }">
<ListRowItem :item="row[column.key]" :align="column.align">
<div>
<div v-if="column.key == 'assessment_type'">
{{ row[column.key] == 'LMS Quiz' ? 'Quiz' : 'Assignment' }}
</div>
<div v-else>
{{ row[column.key] }}
</div>
</ListRowItem>
Expand Down Expand Up @@ -177,10 +180,12 @@ const getAssessmentColumns = () => {
{
label: 'Assessment',
key: 'title',
width: '30rem',
},
{
label: 'Type',
key: 'assessment_type',
width: '10rem',
},
]
Expand All @@ -189,6 +194,7 @@ const getAssessmentColumns = () => {
label: 'Status/Score',
key: 'status',
align: 'center',
width: '10rem',
})
}
return columns
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/BatchCourses.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div>
<div class="flex items-center justify-between mb-4">
<div class="text-xl font-semibold">
<div class="text-lg font-semibold">
{{ __('Courses') }}
</div>
<Button v-if="canSeeAddButton()" @click="openCourseModal()">
Expand Down Expand Up @@ -118,13 +118,13 @@ const getCoursesColumns = () => {
},
{
label: 'Lessons',
key: 'lesson_count',
key: 'lessons',
align: 'right',
},
{
label: 'Enrollments',
align: 'right',
key: 'enrollment_count',
key: 'enrollments',
},
]
}
Expand Down
110 changes: 79 additions & 31 deletions frontend/src/components/BatchStudents.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<template>
<Button class="float-right mb-3" @click="openStudentModal()">
<template #prefix>
<Plus class="h-4 w-4" />
</template>
{{ __('Add') }}
</Button>
<div class="text-lg font-semibold mb-4">
{{ __('Students') }}
<div class="flex items-center justify-between mb-4">
<div class="text-lg font-semibold">
{{ __('Students') }}
</div>
<Button @click="openStudentModal()">
<template #prefix>
<Plus class="h-4 w-4" />
</template>
{{ __('Add') }}
</Button>
</div>
<div v-if="students.data?.length">
<ListView
Expand All @@ -18,12 +20,16 @@
<ListHeader
class="mb-2 grid items-center space-x-4 rounded bg-gray-100 p-2"
>
<ListHeaderItem :item="item" v-for="item in getStudentColumns()">
<ListHeaderItem
:item="item"
v-for="item in getStudentColumns()"
:title="item.label"
>
<template #prefix="{ item }">
<component
<FeatherIcon
v-if="item.icon"
:is="item.icon"
class="h-4 w-4 stroke-1.5 ml-4"
:name="item.icon"
class="h-4 w-4 stroke-1.5"
/>
</template>
</ListHeaderItem>
Expand All @@ -42,9 +48,22 @@
/>
</div>
</template>
<div>
<div v-if="column.key == 'courses'">
{{ row[column.key] }}
</div>
<div v-else-if="column.icon == 'book-open'">
{{ Math.ceil(row.courses[column.key]) }}%
</div>
<div v-else-if="column.icon == 'help-circle'">
<Badge
v-if="isAssignment(row.assessments[column.key])"
:theme="getStatusTheme(row.assessments[column.key])"
class="text-xs"
>
{{ row.assessments[column.key] }}
</Badge>
<div v-else>{{ parseInt(row.assessments[column.key]) }}%</div>
</div>
</ListRowItem>
</template>
</ListRow>
Expand Down Expand Up @@ -74,16 +93,18 @@
</template>
<script setup>
import {
Avatar,
Badge,
Button,
createResource,
FeatherIcon,
ListHeader,
ListHeaderItem,
ListSelectBanner,
ListRow,
ListRows,
ListView,
ListRowItem,
Avatar,
Button,
} from 'frappe-ui'
import { Trash2, Plus } from 'lucide-vue-next'
import { ref } from 'vue'
Expand All @@ -109,27 +130,40 @@ const students = createResource({
})
const getStudentColumns = () => {
return [
let columns = [
{
label: 'Full Name',
key: 'full_name',
width: 2,
},
{
label: 'Courses Done',
key: 'courses_completed',
align: 'center',
},
{
label: 'Assessments Done',
key: 'assessments_completed',
align: 'center',
},
{
label: 'Last Active',
key: 'last_active',
width: '10rem',
},
]
if (students.data?.[0].courses) {
Object.keys(students.data?.[0].courses).forEach((course) => {
columns.push({
label: course,
key: course,
width: '10rem',
icon: 'book-open',
align: 'center',
})
})
}
if (students.data?.[0].assessments) {
Object.keys(students.data?.[0].assessments).forEach((assessment) => {
columns.push({
label: assessment,
key: assessment,
width: '10rem',
icon: 'help-circle',
align: isAssignment(students.data?.[0].assessments[assessment])
? 'left'
: 'center',
})
})
}
return columns
}
const openStudentModal = () => {
Expand Down Expand Up @@ -160,4 +194,18 @@ const removeStudents = (selections, unselectAll) => {
}
)
}
const getStatusTheme = (status) => {
if (status === 'Pass') {
return 'green'
} else if (status == 'Not Graded') {
return 'orange'
} else {
return 'red'
}
}
const isAssignment = (value) => {
return isNaN(value)
}
</script>
6 changes: 5 additions & 1 deletion frontend/src/components/Modals/StudentModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import { Dialog, createResource } from 'frappe-ui'
import { ref } from 'vue'
import Link from '@/components/Controls/Link.vue'
import { showToast } from '@/utils'
const students = defineModel('reloadStudents')
const student = ref()
Expand Down Expand Up @@ -61,8 +62,11 @@ const addStudent = (close) => {
{
onSuccess() {
students.value.reload()
close()
student.value = null
close()
},
onError(err) {
showToast(__('Error'), __(err.messages?.[0] || err), 'x')
},
}
)
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/pages/BatchForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ import {
} from 'frappe-ui'
import Link from '@/components/Controls/Link.vue'
import { useRouter } from 'vue-router'
import { showToast } from '../utils'
import { showToast } from '@/utils'
import { Image } from 'lucide-vue-next'
import { capture } from '@/telemetry'
import MultiSelect from '@/components/Controls/MultiSelect.vue'
Expand Down Expand Up @@ -345,6 +345,10 @@ const batchDetail = createResource({
data.instructors.forEach((instructor) => {
instructors.value.push(instructor.instructor)
})
} else if (['start_time', 'end_time'].includes(key)) {
let [hours, minutes, seconds] = data[key].split(':')
hours = hours.length == 1 ? '0' + hours : hours
batch[key] = `${hours}:${minutes}`
} else if (Object.hasOwn(batch, key)) batch[key] = data[key]
})
let checkboxes = ['published', 'paid_batch', 'allow_self_enrollment']
Expand Down
73 changes: 43 additions & 30 deletions lms/lms/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -874,26 +874,6 @@ def is_onboarding_complete():
}


def has_submitted_assessment(assessment, type, member=None):
if not member:
member = frappe.session.user

doctype = (
"LMS Assignment Submission" if type == "LMS Assignment" else "LMS Quiz Submission"
)
docfield = "assignment" if type == "LMS Assignment" else "quiz"

filters = {}
filters[docfield] = assessment
filters["member"] = member
return frappe.db.exists(doctype, filters)


def has_graded_assessment(submission):
status = frappe.db.get_value("LMS Assignment Submission", submission, "status")
return False if status == "Not Graded" else True


def get_evaluator(course, batch):
evaluator = None
evaluator = frappe.db.get_value(
Expand Down Expand Up @@ -1459,13 +1439,11 @@ def get_quiz_details(assessment, member):
@frappe.whitelist()
def get_batch_students(batch):
students = []

students_list = frappe.get_all(
"Batch Student", filters={"parent": batch}, fields=["student", "name"]
)

batch_courses = frappe.get_all("Batch Course", {"parent": batch}, pluck="course")

batch_courses = frappe.get_all("Batch Course", {"parent": batch}, ["course", "title"])
assessments = frappe.get_all(
"LMS Assessment",
filters={"parent": batch},
Expand All @@ -1483,29 +1461,64 @@ def get_batch_students(batch):
)
detail.last_active = format_datetime(detail.last_active, "dd MMM YY")
detail.name = student.name
students.append(detail)
detail.courses = frappe._dict()
detail.assessments = frappe._dict()

""" Iterate through courses and track their progress """
for course in batch_courses:
progress = frappe.db.get_value(
"LMS Enrollment", {"course": course, "member": student.student}, "progress"
"LMS Enrollment", {"course": course.course, "member": student.student}, "progress"
)

detail.courses[course.title] = progress
if progress == 100:
courses_completed += 1

detail.courses_completed = courses_completed

""" Iterate through assessments and track their progress """
for assessment in assessments:
if has_submitted_assessment(
title = frappe.db.get_value(
assessment.assessment_type, assessment.assessment_name, "title"
)
status = has_submitted_assessment(
assessment.assessment_name, assessment.assessment_type, student.student
):
)
detail.assessments[title] = status
if status not in ["Not Attempted", 0]:
assessments_completed += 1

detail.courses_completed = courses_completed
detail.assessments_completed = assessments_completed
students.append(detail)

return students


def has_submitted_assessment(assessment, assessment_type, member=None):
if not member:
member = frappe.session.user

if assessment_type == "LMS Assignment":
doctype = "LMS Assignment Submission"
docfield = "assignment"
fields = ["status"]
not_attempted = "Not Attempted"
elif assessment_type == "LMS Quiz":
doctype = "LMS Quiz Submission"
docfield = "quiz"
fields = ["percentage"]
not_attempted = 0

filters = {}
filters[docfield] = assessment
filters["member"] = member

attempt = frappe.db.exists(doctype, filters)
if attempt:
attempt_details = frappe.db.get_value(doctype, filters, fields)
return attempt_details
else:
return not_attempted


@frappe.whitelist()
def get_discussion_topics(doctype, docname, single_thread):
if single_thread:
Expand Down

0 comments on commit 76a84c7

Please sign in to comment.