refactor: batch student progress

This commit is contained in:
Jannat Patel
2026-02-17 19:38:19 +05:30
parent 44b7a210ce
commit af273a9a1c
11 changed files with 533 additions and 378 deletions
+36 -16
View File
@@ -17,22 +17,20 @@
{{ __('Save') }}
</Button>
</div>
<div v-else-if="isAdmin" class="space-x-2">
<Button
v-if="batch.data?.certification"
@click="openCertificateDialog = true"
>
{{ __('Generate Certificates') }}
</Button>
<Button v-if="canMakeAnnouncement()" @click="openAnnouncementModal()">
<span>
{{ __('Make an Announcement') }}
</span>
<template #suffix>
<SendIcon class="h-4 stroke-1.5" />
</template>
</Button>
</div>
<Dropdown
v-else-if="isAdmin"
:options="batchMenu"
placement="left"
side="left"
>
<template v-slot="{ open }">
<Button variant="ghost">
<template #icon>
<EllipsisVertical class="w-4 h-4 stroke-1.5" />
</template>
</Button>
</template>
</Dropdown>
</header>
<div>
<BatchOverview v-if="!isAdmin && !isStudent" :batch="batch" />
@@ -76,6 +74,7 @@
<script setup>
import {
ClipboardPen,
EllipsisVertical,
Laptop,
List,
Mail,
@@ -92,6 +91,7 @@ import {
Breadcrumbs,
Button,
createResource,
Dropdown,
Tabs,
usePageMeta,
} from 'frappe-ui'
@@ -205,6 +205,26 @@ const canMakeAnnouncement = () => {
return user.data?.is_moderator || user.data?.is_evaluator
}
const batchMenu = computed(() => {
let options = [
{
label: __('Generate Certificates'),
onClick() {
openCertificateDialog.value = true
},
condition: () => batch.data?.certification,
},
{
label: __('Make an Announcement'),
onClick() {
openAnnouncementModal()
},
condition: () => canMakeAnnouncement(),
},
]
return options
})
const breadcrumbs = computed(() => {
let crumbs = [{ label: __('Batches'), route: { name: 'Batches' } }]
crumbs.push({
@@ -55,6 +55,10 @@
:options="{
selectable: false,
showTooltip: false,
onRowClick: (row: any) => {
currentStudent = row.member
showProgressModal = true
},
}"
>
<ListHeader
@@ -68,16 +72,7 @@
</ListHeaderItem>
</ListHeader>
<ListRows v-for="row in students.data" class="max-h-[500px]">
<ListRow
:row="row"
@click="
() => {
/* showProgressModal = true
currentStudent = row */
}
"
class="cursor-pointer"
>
<ListRow :row="row">
<template #default="{ column, item }">
<ListRowItem
:item="row[column.key]"
@@ -167,6 +162,12 @@
:batch="batch"
:students="students"
/>
<BatchStudentProgress
v-if="showProgressModal"
v-model="showProgressModal"
:student="currentStudent"
:batch="batch?.data?.name"
/>
</template>
<script setup lang="ts">
import {
@@ -188,11 +189,14 @@ import { computed, ref, watch } from 'vue'
import { formatAmount } from '@/utils'
import { Plus } from 'lucide-vue-next'
import BatchFeedback from '@/pages/Batches/components/BatchFeedback.vue'
import BatchStudentProgress from '@/pages/Batches/components/BatchStudentProgress.vue'
import NumberChartGraph from '@/components/NumberChartGraph.vue'
import StudentModal from '@/components/Modals/StudentModal.vue'
const searchFilter = ref<string | null>(null)
const showEnrollmentModal = ref<boolean>(false)
const showProgressModal = ref<boolean>(false)
const currentStudent = ref<any>(null)
const props = defineProps<{
batch: { [key: string]: any } | null
@@ -1,6 +1,6 @@
<template>
<div class="w-[75%] mx-auto mt-5">
<div class="font-semibold text-lg mb-5">
<div class="text-ink-gray-9 font-semibold text-lg mb-5">
{{ __('Announcements') }}
</div>
<div v-if="communications.data?.length">
@@ -3,7 +3,7 @@
<div class="grid grid-cols-[2fr,1fr] gap-5">
<div class="p-5">
<div class="mb-8 space-y-2">
<div class="text-lg font-semibold">
<div class="text-lg text-ink-gray-9 font-semibold">
{{ __('Curriculum') }}
</div>
<div class="text-ink-gray-7">
@@ -16,7 +16,7 @@
</div>
<div class="space-y-10">
<div>
<div class="font-semibold mb-4">
<div class="text-ink-gray-9 font-semibold mb-4">
{{ __('Courses') }}
</div>
<ListView
@@ -0,0 +1,222 @@
<template>
<Dialog
v-model="show"
:options="{
size: 'xl',
}"
>
<template #body>
<div v-if="studentDetails.data" class="p-5 space-y-10 text-sm">
<div class="flex items-center space-x-2">
<Avatar :image="studentDetails.data.user_image" size="3xl" />
<div class="space-y-1">
<div class="flex items-center space-x-2">
<div class="text-xl font-semibold text-ink-gray-9">
{{ studentDetails.data.full_name }}
</div>
<Badge
v-if="
Object.keys(studentDetails.data.assessments).length ||
Object.keys(studentDetails.data.courses).length
"
:theme="studentDetails.data.progress === 100 ? 'green' : 'red'"
>
{{ studentDetails.data.progress }}% {{ __('Complete') }}
</Badge>
</div>
<div class="text-sm text-ink-gray-7">
{{ studentDetails.data.email }}
</div>
</div>
</div>
<div class="space-y-8">
<!-- Assessments -->
<ListView
:columns="assessmentColumns"
:rows="studentDetails.data.assessments"
row-key="title"
class="border rounded-lg"
:options="{
selectable: false,
showTooltip: false,
onRowClick: (row: any) => {
redirectToAssessment(row)
}
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded-none rounded-t bg-surface-gray-2 p-2"
>
</ListHeader>
<ListRows v-for="row in studentDetails.data.assessments">
<ListRow :row="row" class="!rounded-none">
<template #default="{ column, item }">
<ListRowItem
:item="row[column.key]"
:align="column.align"
class="w-full"
>
<div
v-if="column.key == 'status' && isAssignment(row.status)"
>
<Badge :theme="getStatusTheme(row[column.key])">
{{ row[column.key] }}
</Badge>
</div>
<div v-else>
{{ row[column.key] }}
</div>
</ListRowItem>
</template>
</ListRow>
</ListRows>
</ListView>
<!-- Courses -->
<ListView
:columns="courseColumns"
:rows="studentDetails.data.courses"
row-key="title"
class="border rounded-lg"
:options="{
selectable: false,
showTooltip: false,
onRowClick: (row: any) => {
redirectToCourse(row)
}
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded-none rounded-t bg-surface-gray-2 p-2"
>
</ListHeader>
<ListRows v-for="row in studentDetails.data.courses">
<ListRow :row="row" class="!rounded-none">
<template #default="{ column, item }">
<ListRowItem
:item="row[column.key]"
:align="column.align"
class="w-full"
>
<template #prefix>
<ProgressBar
v-if="column.key == 'progress'"
:progress="Math.ceil(row[column.key])"
class="!mx-0 !mr-4 max-w-32"
/>
</template>
<div
v-if="column.key == 'progress'"
class="text-xs !ml-0 !mr-3 w-5"
>
{{ Math.ceil(row[column.key]) }}%
</div>
<div v-else>
{{ row[column.key] }}
</div>
</ListRowItem>
</template>
</ListRow>
</ListRows>
</ListView>
</div>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import {
Avatar,
Badge,
createResource,
Dialog,
ListView,
ListHeader,
ListRows,
ListRow,
ListRowItem,
} from 'frappe-ui'
import { useRouter } from 'vue-router'
import ProgressBar from '@/components/ProgressBar.vue'
const show = defineModel()
const router = useRouter()
const props = defineProps<{
student: string
batch: string
}>()
const studentDetails = createResource({
url: 'lms.lms.utils.get_batch_student_progress',
makeParams() {
return {
member: props.student,
batch: props.batch,
}
},
auto: true,
})
const redirectToAssessment = (row: any) => {
console.log(row)
if (!row.submission) return
if (row.type == 'LMS Assignment') {
router.push({
name: 'AssignmentSubmission',
params: {
assignmentID: row.assessment,
submissionName: row.submission,
},
})
} else if (row.type == 'LMS Programming Exercise') {
router.push({
name: 'ProgrammingExerciseSubmission',
params: {
exerciseID: row.assessment,
submissionID: row.submission,
},
})
} else if (row.type == 'LMS Quiz') {
router.push({
name: 'QuizSubmission',
params: {
submission: row.submission,
},
})
}
}
const redirectToCourse = (row: any) => {
router.push({
name: 'CourseDetail',
params: {
courseName: row.course,
},
})
}
const assessmentColumns = [
{ key: 'title', label: 'Assessment', align: 'left' },
{ key: 'status', label: 'Percentage/Status', align: 'right' },
]
const courseColumns = [
{ key: 'title', label: 'Course', align: 'left', width: '70%' },
{ key: 'progress', label: 'Progress', align: 'right' },
]
const isAssignment = (value: any) => {
return isNaN(value)
}
const getStatusTheme = (status: string) => {
if (status === 'Pass') {
return 'green'
} else if (status == 'Not Graded') {
return 'orange'
} else {
return 'red'
}
}
</script>