Merge pull request #1992 from raizasafeel/improve_batch_performance

feat(batch): add student pagination and optimize dashboard queries
This commit is contained in:
Jannat Patel
2026-01-15 14:54:48 +05:30
committed by GitHub
3 changed files with 199 additions and 110 deletions

View File

@@ -8,7 +8,7 @@
<div class="grid grid-cols-2 md:grid-cols-4 gap-5 mb-8">
<NumberChart
class="border rounded-md"
:config="{ title: __('Students'), value: students.data?.length || 0 }"
:config="{ title: __('Students'), value: studentCount.data || 0 }"
/>
<NumberChart
@@ -29,7 +29,7 @@
<NumberChart
class="border rounded-md"
:config="{ title: __('Assessments'), value: assessmentCount || 0 }"
:config="{ title: __('Assessments'), value: assessmentCount.data || 0 }"
/>
</div>
@@ -37,7 +37,7 @@
v-if="showProgressChart"
class="border"
:config="{
data: chartData || [],
data: filteredChartData,
title: __('Batch Summary'),
subtitle: __('Progress of students in courses and assessments'),
xAxis: {
@@ -64,96 +64,55 @@
</template>
<script setup lang="ts">
import { AxisChart, createResource, NumberChart } from 'frappe-ui'
import { ref, watch } from 'vue'
const chartData = ref<null | any[]>(null)
const showProgressChart = ref(false)
const assessmentCount = ref(0)
import { computed } from 'vue'
const props = defineProps<{
batch: { [key: string]: any } | null
}>()
const students = createResource({
url: 'lms.lms.utils.get_batch_students',
const studentCount = createResource({
url: 'frappe.client.get_count',
cache: ['batch_student_count', props.batch?.data?.name],
params: {
doctype: 'LMS Batch Enrollment',
filters: { batch: props.batch?.data?.name },
},
auto: true,
})
const assessmentCount = createResource({
url: 'lms.lms.utils.get_batch_assessment_count',
cache: ['batch_assessment_count', props.batch?.data?.name],
params: {
batch: props.batch?.data?.name,
},
auto: true,
onSuccess(data: any[]) {
chartData.value = getChartData()
showProgressChart.value =
data.length &&
(props.batch?.data?.courses?.length || assessmentCount.value)
},
})
const getChartData = () => {
let tasks: any[] = []
let data: { task: any; value: any }[] = []
students.data.forEach((row: any) => {
tasks = countAssessments(row, tasks)
tasks = countCourses(row, tasks)
})
tasks.forEach((task) => {
data.push({
task: task.label,
value: task.value,
})
})
return data
}
const countAssessments = (
row: { assessments: { [x: string]: { result: string } } },
tasks: any[]
) => {
Object.keys(row.assessments).forEach((assessment) => {
if (row.assessments[assessment].result === 'Pass') {
tasks.filter((task) => task.label === assessment).length
? tasks.filter((task) => task.label === assessment)[0].value++
: tasks.push({
value: 1,
label: assessment,
})
}
})
return tasks
}
const countCourses = (
row: { courses: { [x: string]: number } },
tasks: any[]
) => {
Object.keys(row.courses).forEach((course) => {
if (row.courses[course] === 100) {
tasks.filter((task) => task.label === course).length
? tasks.filter((task) => task.label === course)[0].value++
: tasks.push({
value: 1,
label: course,
})
}
})
return tasks
}
const chartData = createResource({
url: 'lms.lms.utils.get_batch_chart_data',
cache: ['batch_chart_data', props.batch?.data?.name],
params: { batch: props.batch?.data?.name },
auto: true,
})
const certificationCount = createResource({
url: 'frappe.client.get_count',
cache: ['batch_certificate_count', props.batch?.data?.name],
params: {
doctype: 'LMS Certificate',
filters: {
batch_name: props.batch?.data?.name,
},
filters: { batch_name: props.batch?.data?.name },
},
auto: true,
})
watch(students, () => {
if (students.data?.length) {
assessmentCount.value = Object.keys(students.data?.[0].assessments).length
}
})
const filteredChartData = computed(() =>
(chartData.data || []).filter((item: { value: number }) => item.value > 0)
)
const showProgressChart = computed(
() =>
studentCount.data &&
(props.batch?.data?.courses?.length || assessmentCount.data)
)
</script>

View File

@@ -2,7 +2,7 @@
<div>
<div class="flex items-center justify-between mb-4">
<div class="text-ink-gray-9 font-medium">
{{ students.data?.length }} {{ __('Students') }}
{{ studentCount.data ?? 0 }} {{ __('Students') }}
</div>
<Button v-if="!readOnlyMode" @click="openStudentModal()">
<template #prefix>
@@ -15,7 +15,7 @@
<div v-if="students.data?.length">
<ListView
class="max-h-[75vh]"
:columns="getStudentColumns()"
:columns="studentColumns"
:rows="students.data"
row-key="name"
:options="{
@@ -27,7 +27,7 @@
>
<ListHeaderItem
:item="item"
v-for="item in getStudentColumns()"
v-for="item in studentColumns"
:title="item.label"
>
<template #prefix="{ item }">
@@ -88,6 +88,11 @@
</div>
</template>
</ListSelectBanner>
<div class="mt-4" v-if="students.hasNextPage">
<Button @click="students.next()">
{{ __('Load More') }}
</Button>
</div>
</ListView>
</div>
<div v-else-if="!students.loading" class="text-sm italic text-ink-gray-5">
@@ -110,6 +115,7 @@
import {
Avatar,
Button,
createListResource,
createResource,
FeatherIcon,
ListHeader,
@@ -139,39 +145,48 @@ const props = defineProps({
},
})
const students = createResource({
url: 'lms.lms.utils.get_batch_students',
const studentCount = createResource({
url: 'frappe.client.get_count',
cache: ['batch_student_count', props.batch?.data?.name],
params: {
doctype: 'LMS Batch Enrollment',
filters: { batch: props.batch?.data?.name },
},
auto: true,
})
const students = createListResource({
doctype: 'LMS Batch Enrollment',
url: 'lms.lms.utils.get_batch_students',
cache: ['batch_students', props.batch?.data?.name],
pageLength: 50,
filters: {
batch: props.batch?.data?.name,
},
auto: true,
})
const getStudentColumns = () => {
let columns = [
{
label: 'Full Name',
key: 'full_name',
width: '20rem',
icon: 'user',
},
{
label: 'Progress',
key: 'progress',
width: '15rem',
icon: 'activity',
},
{
label: 'Last Active',
key: 'last_active',
width: '10rem',
align: 'center',
icon: 'clock',
},
]
return columns
}
const studentColumns = [
{
label: 'Full Name',
key: 'full_name',
width: '20rem',
icon: 'user',
},
{
label: 'Progress',
key: 'progress',
width: '15rem',
icon: 'activity',
},
{
label: 'Last Active',
key: 'last_active',
width: '10rem',
align: 'center',
icon: 'clock',
},
]
const openStudentModal = () => {
showStudentModal.value = true
@@ -200,6 +215,7 @@ const removeStudents = (selections, unselectAll) => {
{
onSuccess(data) {
students.reload()
studentCount.reload()
props.batch.reload()
toast.success(__('Students deleted successfully'))
unselectAll()