feat: batch dashboard for instructors
This commit is contained in:
@@ -1,13 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="">
|
||||||
<!-- <Bar v-if="chartData" :data="chartData" :options="chartOptions" /> -->
|
<div class="grid grid-cols-3 gap-5 mb-8">
|
||||||
<ApexChart
|
<div class="flex items-center shadow py-2 px-3 rounded-md">
|
||||||
v-if="chartData"
|
<div class="p-2 rounded-md bg-gray-100 mr-3">
|
||||||
:options="chartOptions"
|
<User class="w-18 h-18 stroke-1.5 text-gray-700" />
|
||||||
:series="chartData"
|
</div>
|
||||||
type="bar"
|
<div class="flex flex-col">
|
||||||
height="350"
|
<span class="text-xl font-semibold mb-1">
|
||||||
/>
|
{{ students.data?.length }}
|
||||||
|
</span>
|
||||||
|
<span class="text-gray-700">
|
||||||
|
{{ __('Students') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center shadow py-2 px-3 rounded-md">
|
||||||
|
<div class="p-2 rounded-md bg-gray-100 mr-3">
|
||||||
|
<BookOpen class="w-18 h-18 stroke-1.5 text-gray-700" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-xl font-semibold mb-1">
|
||||||
|
{{ batch.courses?.length }}
|
||||||
|
</span>
|
||||||
|
<span class="text-gray-700">
|
||||||
|
{{ __('Courses') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center shadow py-2 px-3 rounded-md">
|
||||||
|
<div class="p-2 rounded-md bg-gray-100 mr-3">
|
||||||
|
<ShieldCheck class="w-18 h-18 stroke-1.5 text-gray-700" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-xl font-semibold mb-1">
|
||||||
|
{{ assessmentCount }}
|
||||||
|
</span>
|
||||||
|
<span class="text-gray-700">
|
||||||
|
{{ __('Assessments') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-8">
|
||||||
|
<div class="text-lg font-semibold">
|
||||||
|
{{ __('Progress') }}
|
||||||
|
</div>
|
||||||
|
<ApexChart
|
||||||
|
v-if="showProgressChart"
|
||||||
|
:options="chartOptions"
|
||||||
|
:series="chartData"
|
||||||
|
type="bar"
|
||||||
|
height="350"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<div class="text-lg font-semibold">
|
<div class="text-lg font-semibold">
|
||||||
{{ __('Students') }}
|
{{ __('Students') }}
|
||||||
@@ -72,19 +119,6 @@
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
{{ row[column.key] }}
|
{{ row[column.key] }}
|
||||||
</div>
|
</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>
|
</ListRowItem>
|
||||||
</template>
|
</template>
|
||||||
</ListRow>
|
</ListRow>
|
||||||
@@ -107,7 +141,7 @@
|
|||||||
{{ __('There are no students in this batch.') }}
|
{{ __('There are no students in this batch.') }}
|
||||||
</div>
|
</div>
|
||||||
<StudentModal
|
<StudentModal
|
||||||
:batch="props.batch"
|
:batch="props.batch.name"
|
||||||
v-model="showStudentModal"
|
v-model="showStudentModal"
|
||||||
v-model:reloadStudents="students"
|
v-model:reloadStudents="students"
|
||||||
/>
|
/>
|
||||||
@@ -130,32 +164,12 @@ import {
|
|||||||
ListView,
|
ListView,
|
||||||
ListRowItem,
|
ListRowItem,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { Trash2, Plus } from 'lucide-vue-next'
|
import { BookOpen, Plus, ShieldCheck, Trash2, User } from 'lucide-vue-next'
|
||||||
import { computed, ref } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import StudentModal from '@/components/Modals/StudentModal.vue'
|
import StudentModal from '@/components/Modals/StudentModal.vue'
|
||||||
import { showToast } from '@/utils'
|
import { showToast } from '@/utils'
|
||||||
import ProgressBar from '@/components/ProgressBar.vue'
|
import ProgressBar from '@/components/ProgressBar.vue'
|
||||||
import BatchStudentProgress from '@/components/Modals/BatchStudentProgress.vue'
|
import BatchStudentProgress from '@/components/Modals/BatchStudentProgress.vue'
|
||||||
import { Bar } from 'vue-chartjs'
|
|
||||||
import {
|
|
||||||
Chart as ChartJS,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
BarElement,
|
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
Filler,
|
|
||||||
} from 'chart.js'
|
|
||||||
ChartJS.register(
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
BarElement,
|
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
Filler
|
|
||||||
)
|
|
||||||
import ApexChart from 'vue3-apexcharts'
|
import ApexChart from 'vue3-apexcharts'
|
||||||
|
|
||||||
const showStudentModal = ref(false)
|
const showStudentModal = ref(false)
|
||||||
@@ -163,24 +177,26 @@ const showStudentProgressModal = ref(false)
|
|||||||
const selectedStudent = ref(null)
|
const selectedStudent = ref(null)
|
||||||
const chartData = ref(null)
|
const chartData = ref(null)
|
||||||
const chartOptions = ref(null)
|
const chartOptions = ref(null)
|
||||||
|
const showProgressChart = ref(false)
|
||||||
|
const assessmentCount = ref(0)
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
batch: {
|
batch: {
|
||||||
type: String,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const students = createResource({
|
const students = createResource({
|
||||||
url: 'lms.lms.utils.get_batch_students',
|
url: 'lms.lms.utils.get_batch_students',
|
||||||
cache: ['students', props.batch],
|
cache: ['students', props.batch.name],
|
||||||
params: {
|
params: {
|
||||||
batch: props.batch,
|
batch: props.batch?.name,
|
||||||
},
|
},
|
||||||
auto: true,
|
auto: true,
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
chartData.value = getChartData()
|
chartData.value = getChartData()
|
||||||
console.log(chartData.value)
|
showProgressChart.value = true
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -245,11 +261,8 @@ const removeStudents = (selections, unselectAll) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getChartData = () => {
|
const getChartData = () => {
|
||||||
console.log('called')
|
|
||||||
|
|
||||||
let categories = {}
|
let categories = {}
|
||||||
|
|
||||||
// Initialize categories with categories
|
|
||||||
Object.keys(students.data?.[0].courses).forEach((course) => {
|
Object.keys(students.data?.[0].courses).forEach((course) => {
|
||||||
categories[course] = {
|
categories[course] = {
|
||||||
value: 0,
|
value: 0,
|
||||||
@@ -264,7 +277,6 @@ const getChartData = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Populate data
|
|
||||||
students.data.forEach((student) => {
|
students.data.forEach((student) => {
|
||||||
Object.keys(student.courses).forEach((course) => {
|
Object.keys(student.courses).forEach((course) => {
|
||||||
if (student.courses[course] === 100) {
|
if (student.courses[course] === 100) {
|
||||||
@@ -279,84 +291,33 @@ const getChartData = () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Transform data for ApexCharts
|
|
||||||
console.log(Object.values(categories).map((item) => item.value))
|
|
||||||
chartOptions.value = getChartOptions(categories)
|
chartOptions.value = getChartOptions(categories)
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: __('Student Progress'),
|
name: __('Student Progress'),
|
||||||
data: Object.values(categories).map((item) => item.value),
|
data: Object.values(categories).map((item) => item.value),
|
||||||
/* colors: Object.values(categories).map(item =>
|
|
||||||
item.type === 'course' ? courseColor : assessmentColor
|
|
||||||
), */
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/* const chartOptions = computed(() => {
|
|
||||||
return {
|
|
||||||
responsive: true,
|
|
||||||
fill: true,
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
ticks: {
|
|
||||||
maxRotation: 0,
|
|
||||||
minRotation: 0,
|
|
||||||
autoSkip: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
beginAtZero: true,
|
|
||||||
max: students.data?.length,
|
|
||||||
ticks: {
|
|
||||||
stepSize: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
legends: {
|
|
||||||
display: false,
|
|
||||||
title: {
|
|
||||||
text: __("Student Progress 1111"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: __("Student Progress"),
|
|
||||||
font: {
|
|
||||||
size: 14,
|
|
||||||
weight: '500',
|
|
||||||
},
|
|
||||||
color: '#171717',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) */
|
|
||||||
|
|
||||||
const chartSeries = ref([
|
|
||||||
{
|
|
||||||
name: 'Courses',
|
|
||||||
data: [20, 30, 50], // Example data for courses
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Assessments',
|
|
||||||
data: [10, 40, 60], // Example data for assessments
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
const getChartOptions = (categories) => {
|
const getChartOptions = (categories) => {
|
||||||
const courseColor = '#3498db' // Blue for courses
|
const courseColor = '#318AD8'
|
||||||
const assessmentColor = '#e74c3c' // Red for assessments
|
const assessmentColor = '#F683AE'
|
||||||
|
const maxY = Math.ceil(students.data?.length / 10) * 10
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chart: {
|
chart: {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
height: 350,
|
height: 350,
|
||||||
|
toolbar: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plotOptions: {
|
plotOptions: {
|
||||||
bar: {
|
bar: {
|
||||||
distributed: true, // Allows individual bar colors
|
distributed: true,
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
horizontal: false, // Set to true for horizontal bars
|
horizontal: false,
|
||||||
columnWidth: '30%',
|
columnWidth: '30%',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -365,20 +326,35 @@ const getChartOptions = (categories) => {
|
|||||||
),
|
),
|
||||||
legends: {
|
legends: {
|
||||||
show: true,
|
show: true,
|
||||||
|
markers: {
|
||||||
|
fillColors: [courseColor, assessmentColor],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
xaxis: {
|
xaxis: {
|
||||||
categories: Object.keys(categories),
|
categories: ['Courses', 'Assessments'],
|
||||||
|
overwriteCategories: Object.keys(categories),
|
||||||
labels: {
|
labels: {
|
||||||
style: {
|
style: {
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
},
|
},
|
||||||
rotate: 0,
|
rotate: 0,
|
||||||
formatter: function (value) {
|
formatter: function (value) {
|
||||||
console.log(value)
|
return value.length > 25 ? `${value.substring(0, 25)}...` : value // Trim long labels
|
||||||
return value.length > 20 ? `${value.substring(0, 20)}...` : value // Trim long labels
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
yaxis: {
|
||||||
|
max: maxY,
|
||||||
|
min: 0,
|
||||||
|
stepSize: 10,
|
||||||
|
tickAmount: maxY / 10,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(students, () => {
|
||||||
|
if (students.data?.length) {
|
||||||
|
assessmentCount.value = Object.keys(students.data?.[0].assessments).length
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div v-if="batch.data" class="grid grid-cols-[70%,30%] h-screen">
|
<div v-if="batch.data" class="grid grid-cols-[70%,30%] h-screen">
|
||||||
<div class="border-r-2">
|
<div class="border-r">
|
||||||
<Tabs
|
<Tabs
|
||||||
v-model="tabIndex"
|
v-model="tabIndex"
|
||||||
:tabs="tabs"
|
:tabs="tabs"
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
<LiveClass :batch="batch.data.name" />
|
<LiveClass :batch="batch.data.name" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab.label == 'Students'">
|
<div v-else-if="tab.label == 'Students'">
|
||||||
<BatchStudents :batch="batch.data.name" />
|
<BatchStudents :batch="batch.data" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="tab.label == 'Assessments'">
|
<div v-else-if="tab.label == 'Assessments'">
|
||||||
<Assessments :batch="batch.data.name" />
|
<Assessments :batch="batch.data.name" />
|
||||||
|
|||||||
Reference in New Issue
Block a user