refactor: student batch dashboard

This commit is contained in:
Jannat Patel
2026-02-16 12:17:13 +05:30
parent 944fd6d013
commit 641d729bd1
12 changed files with 248 additions and 102 deletions

View File

@@ -55,6 +55,9 @@
</div>
</div>
</div>
<div v-else-if="!evaluation.course" class="text-ink-gray-7">
{{ __('Please select a course to view available slots.') }}
</div>
<div v-else class="text-ink-red-3">
{{ __('No slots available for the selected course.') }}
</div>

View File

@@ -5,7 +5,7 @@
{{ __('Upcoming Evaluations') }}
</div>
<Button v-if="canScheduleEvals" @click="openEvalModal">
{{ __('Schedule Evaluation') }}
{{ __('Schedule') }}
</Button>
</div>
<div
@@ -31,7 +31,7 @@
<div v-if="upcoming_evals.data?.length">
<div
class="grid gap-4"
:class="forHome ? 'grid-cols-1 md:grid-cols-2' : 'grid-cols-3'"
:class="forHome ? 'grid-cols-1 md:grid-cols-2' : 'grid-cols-1'"
>
<div v-for="evl in upcoming_evals.data">
<div class="border text-ink-gray-7 rounded-md p-3">

View File

@@ -75,6 +75,7 @@
</template>
<script setup>
import {
ClipboardPen,
Laptop,
List,
Mail,
@@ -166,7 +167,7 @@ const updateTabs = () => {
if (isAdmin.value) {
addToTabs('Dashboard', markRaw(AdminBatchDashboard), TrendingUp)
} else if (isStudent.value) {
addToTabs('Dashboard', markRaw(StudentBatchDashboard), null)
addToTabs('Dashboard', markRaw(StudentBatchDashboard), ClipboardPen)
}
addToTabs('Classes', markRaw(LiveClass), Laptop)
addToTabs('Announcements', markRaw(Announcements), Mail)

View File

@@ -2,26 +2,6 @@
<div class="">
<div class="grid grid-cols-[3fr,2fr]">
<div v-if="batchDetail.doc" class="py-5 h-[88vh] overflow-y-auto">
<div class="px-5 pb-5 space-y-5 border-b mb-5">
<div class="grid grid-cols-1 md:grid-cols-3 gap-5">
<FormControl
v-model="batchDetail.doc.published"
type="checkbox"
:label="__('Published')"
/>
<FormControl
v-model="batchDetail.doc.allow_self_enrollment"
type="checkbox"
:label="__('Allow Self Enrollment')"
/>
<FormControl
v-model="batchDetail.doc.certification"
type="checkbox"
:label="__('Certification')"
/>
</div>
</div>
<div class="px-5 pb-5 space-y-5 border-b mb-5">
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
{{ __('Details') }}
@@ -29,6 +9,11 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
<div class="space-y-5">
<FormControl
v-model="batchDetail.doc.published"
type="checkbox"
:label="__('Published')"
/>
<FormControl
v-model="batchDetail.doc.title"
:label="__('Title')"
@@ -58,6 +43,11 @@
/>
</div>
<div class="space-y-5">
<FormControl
v-model="batchDetail.doc.allow_self_enrollment"
type="checkbox"
:label="__('Allow Self Enrollment')"
/>
<FormControl
v-model="batchDetail.doc.start_time"
:label="__('Session Start Time')"
@@ -91,6 +81,35 @@
</div>
</div>
<div class="px-5 pb-5 space-y-5 border-b mb-5">
<div class="text-lg text-ink-gray-9 font-semibold mb-4">
{{ __('Certification') }}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-5 items-start">
<div class="flex flex-col space-y-5">
<FormControl
v-model="batchDetail.doc.evaluation"
type="checkbox"
:label="__('Evaluation')"
/>
<FormControl
v-if="batchDetail.doc.evaluation"
v-model="batchDetail.doc.evaluation_end_date"
:label="__('Evaluation End Date')"
type="date"
class="mb-4"
/>
</div>
<div>
<FormControl
v-model="batchDetail.doc.certification"
type="checkbox"
:label="__('Certification')"
/>
</div>
</div>
</div>
<div class="px-5 pb-5 space-y-5 border-b mb-5">
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
<div class="space-y-5">
@@ -110,9 +129,15 @@
:onCreate="(close) => openSettings('Evaluators', close)"
:filters="{ ignore_user_type: 1 }"
/>
<Uploader
v-model="batchDetail.doc.video_link"
:label="__('Preview Video')"
type="video"
:required="false"
/>
</div>
<div>
<label class="block text-sm text-ink-gray-5 mb-1">
<label class="block text-sm text-ink-gray-5 mb-2">
{{ __('Batch Details') }}
<span class="text-ink-red-3">*</span>
</label>
@@ -139,7 +164,7 @@
/>
<Link
doctype="Email Template"
:label="__('Email Template')"
:label="__('Enrollment Confirmation Email Template')"
v-model="batchDetail.doc.confirmation_email_template"
:onCreate="
(value, close) => {
@@ -147,6 +172,8 @@
}
"
/>
</div>
<div class="space-y-5">
<Link
doctype="LMS Zoom Settings"
:label="__('Zoom Account')"
@@ -158,20 +185,6 @@
"
/>
</div>
<div class="space-y-5">
<FormControl
v-model="batchDetail.doc.evaluation_end_date"
:label="__('Evaluation End Date')"
type="date"
class="mb-4"
/>
<Uploader
v-model="batchDetail.doc.video_link"
:label="__('Preview Video')"
type="video"
:required="false"
/>
</div>
</div>
</div>
@@ -350,6 +363,7 @@ const updateBatchData = () => {
'paid_batch',
'allow_self_enrollment',
'certification',
'evaluation',
]
for (let idx in checkboxes) {
let key = checkboxes[idx]

View File

@@ -32,7 +32,7 @@
<BatchOverlay :batch="batch" />
</div>
</div>
<div v-if="batch.data.courses.length">
<div v-if="courses.data?.length">
<div class="flex items-center mt-10">
<div class="text-2xl font-semibold text-ink-gray-9">
{{ __('Courses') }}
@@ -40,7 +40,7 @@
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mt-5">
<div
v-if="batch.data.courses"
v-if="courses.data?.length"
v-for="course in courses.data"
:key="course.course"
>

View File

@@ -20,7 +20,7 @@
<NumberChartGraph
class="border rounded-md"
:title="__('Assessments')"
:value="assessmentCount.data || 0"
:value="batch?.data?.assessments?.length || 0"
/>
</div>
@@ -198,15 +198,6 @@ const props = defineProps<{
batch: { [key: string]: any } | null
}>()
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,
})
const chartData = createResource({
url: 'lms.lms.utils.get_batch_chart_data',
cache: ['batch_chart_data', props.batch?.data?.name],
@@ -276,6 +267,7 @@ const studentColumns = computed(() => {
const showProgressChart = computed(
() =>
students.data?.length &&
(props.batch?.data?.courses?.length || assessmentCount.data)
(props.batch?.data?.courses?.length ||
props.batch?.data?.assessments?.length)
)
</script>

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div class="flex items-center justify-between mb-4">
<div class="text-lg text-ink-gray-9 font-semibold">
<div class="text-ink-gray-9 font-semibold">
{{ __('Assessments') }}
</div>
<Button v-if="canAddAssessments()" @click="showModal = true">

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div class="flex items-center justify-between mb-4">
<div class="text-lg text-ink-gray-9 font-semibold">
<div class="text-ink-gray-9 font-semibold">
{{ __('Courses') }}
</div>
<Button v-if="isAdmin()" @click="openCourseModal()">

View File

@@ -1,19 +1,101 @@
<template>
<div class="space-y-10 mt-5 w-[75%] mx-auto">
<UpcomingEvaluations
:batch="batch.data.name"
:endDate="batch.data.evaluation_end_date"
:courses="batch.data.courses"
/>
<BatchCourses :batch="batch" />
<Assessments :batch="batch.data.name" />
<div class="grid grid-cols-2 gap-10"></div>
<div class="h-[88vh]">
<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">
{{ __('Curriculum') }}
</div>
<div class="text-ink-gray-7">
{{
__(
"As a part of this batch's curriculum you will have to complete the following courses and assessments."
)
}}
</div>
</div>
<div class="space-y-10">
<div>
<div class="font-semibold mb-4">
{{ __('Courses') }}
</div>
<ListView
v-if="batch.data?.courses?.length"
:columns="courseColumns"
:rows="batch.data?.courses"
row-key="name"
class="border rounded-lg"
:options="{
showTooltip: false,
selectable: user.data?.is_student ? false : true,
getRowRoute: (row) => ({
name: 'CourseDetail',
params: { courseName: row.course },
}),
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded-none rounded-t bg-surface-gray-2 p-2"
>
</ListHeader>
<ListRows>
<ListRow
:row="row"
v-for="row in batch.data?.courses"
class="!rounded-none text-sm"
>
<template #default="{ column, item }">
<ListRowItem :item="row[column.key]" :align="column.align">
<div v-if="column.key === 'progress'">
{{ getProgress(row.course) }}%
</div>
<div v-else>
{{ row[column.key] }}
</div>
</ListRowItem>
</template>
</ListRow>
</ListRows>
</ListView>
<div v-else class="text-ink-gray-7">
{{ __('No courses added to this batch') }}
</div>
</div>
<!-- <BatchCourses :batch="batch" /> -->
<Assessments :batch="batch.data.name" />
</div>
</div>
<div class="border-l h-[88vh] divide-y">
<div v-if="batch.data?.evaluation" class="p-4 mb-5">
<UpcomingEvaluations
:batch="batch.data.name"
:endDate="batch.data.evaluation_end_date"
:courses="batch.data.courses"
/>
</div>
<div class="p-5">
<BatchFeedback :batch="batch.data?.name" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import UpcomingEvaluations from '@/components/UpcomingEvaluations.vue'
import { inject } from 'vue'
import {
createListResource,
ListView,
ListHeader,
ListRows,
ListRow,
ListRowItem,
} from 'frappe-ui'
import Assessments from '@/pages/Batches/components/Assessments.vue'
import BatchCourses from '@/pages/Batches/components/BatchCourses.vue'
import BatchFeedback from '@/pages/Batches/components/BatchFeedback.vue'
import UpcomingEvaluations from '@/components/UpcomingEvaluations.vue'
const user = inject('$user')
const props = defineProps({
batch: {
@@ -25,4 +107,31 @@ const props = defineProps({
default: false,
},
})
const progressList = createListResource({
doctype: 'LMS Enrollment',
filters: {
member: user.data?.name,
course: ['in', props.batch.data?.courses?.map((c) => c.course)],
},
fields: ['course', 'progress', 'name'],
auto: true,
})
const getProgress = (course) => {
const progress = progressList.data?.find((p) => p.course === course)
return progress ? Math.round(progress.progress) : 0
}
const courseColumns = [
{
key: 'title',
label: __('Course'),
},
{
key: 'progress',
label: __('Progress'),
align: 'right',
},
]
</script>

View File

@@ -1,7 +1,24 @@
<template>
<div>
<div class="text-lg text-ink-gray-9 font-semibold mb-5">
{{ __('Feedback') }}
<div class="flex justify-between mb-5">
<div class="space-y-1">
<div class="text-lg text-ink-gray-9 font-semibold">
{{ __('Feedback') }}
</div>
<div
v-if="feedbackList.data?.length && isAdmin"
class="leading-5 text-ink-gray-7 text-sm mb-2 mt-5"
>
{{ __('Average Feedback Received') }}
</div>
</div>
<Button
v-if="feedbackList.data?.length && isAdmin"
variant="outline"
@click="showAllFeedback = true"
>
{{ __('View all feedback') }}
</Button>
</div>
<div v-if="user.data?.is_student">
<div>
@@ -43,10 +60,6 @@
</div>
<div v-else-if="feedbackList.data?.length">
<div class="leading-5 text-sm mb-2 mt-5">
{{ __('Average Feedback Received') }}
</div>
<div class="space-y-4">
<Rating
v-for="key in ratingKeys"
@@ -55,10 +68,6 @@
:readonly="true"
/>
</div>
<Button variant="outline" class="mt-5" @click="showAllFeedback = true">
{{ __('View all feedback') }}
</Button>
</div>
<div v-else class="text-ink-gray-7 leading-5">
{{ __('No feedback received yet.') }}
@@ -71,7 +80,7 @@
/>
</template>
<script setup>
import { inject, onMounted, reactive, ref, watch } from 'vue'
import { computed, inject, onMounted, reactive, ref, watch } from 'vue'
import { convertToTitleCase } from '@/utils'
import { Button, createListResource, FormControl, Rating } from 'frappe-ui'
import FeedbackModal from '@/components/Modals/FeedbackModal.vue'
@@ -164,10 +173,15 @@ const submitFeedback = () => {
onSuccess: () => {
feedbackList.reload()
showFeedbackForm.value = false
readOnly.value = true
},
}
)
}
const isAdmin = computed(() => {
return user.data?.is_moderator || user.data?.is_evaluator
})
</script>
<style>
.feedback-list > button > div {