fix: misc issues

This commit is contained in:
Jannat Patel
2025-11-14 12:48:46 +05:30
parent 8bfc2a5297
commit d86fd0f6f6
12 changed files with 299 additions and 209 deletions

View File

@@ -8,6 +8,7 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AdminBatchDashboard: typeof import('./src/components/AdminBatchDashboard.vue')['default']
Annoucements: typeof import('./src/components/Annoucements.vue')['default']
AnnouncementModal: typeof import('./src/components/Modals/AnnouncementModal.vue')['default']
Apps: typeof import('./src/components/Apps.vue')['default']

View File

@@ -0,0 +1,159 @@
<template>
<div v-if="batch?.data" class="">
<div class="w-full flex items-center justify-between pb-4">
<div class="font-medium text-ink-gray-7">
{{ __('Statistics') }}
</div>
</div>
<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 }"
/>
<NumberChart
class="border rounded-md"
:config="{
title: __('Certified'),
value: certificationCount.data || 0,
}"
/>
<NumberChart
class="border rounded-md"
:config="{
title: __('Courses'),
value: batch?.data?.courses?.length || 0,
}"
/>
<NumberChart
class="border rounded-md"
:config="{ title: __('Assessments'), value: assessmentCount || 0 }"
/>
</div>
<AxisChart
v-if="showProgressChart"
class="border"
:config="{
data: chartData || [],
title: __('Batch Summary'),
subtitle: __('Progress of students in courses and assessments'),
xAxis: {
key: 'task',
title: 'Tasks',
type: 'category',
},
yAxis: {
title: __('Number of Students'),
echartOptions: {
minInterval: 1,
},
},
swapXY: true,
series: [
{
name: 'value',
type: 'bar',
},
],
}"
/>
</div>
</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)
const props = defineProps<{
batch: { [key: string]: any } | null
}>()
const students = createResource({
url: 'lms.lms.utils.get_batch_students',
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 certificationCount = createResource({
url: 'frappe.client.get_count',
params: {
doctype: 'LMS Certificate',
filters: {
batch_name: props.batch?.data?.name,
},
},
auto: true,
})
watch(students, () => {
if (students.data?.length) {
assessmentCount.value = Object.keys(students.data?.[0].assessments).length
}
})
</script>

View File

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

View File

@@ -1,70 +1,8 @@
<template>
<div v-if="batch.data" class="">
<div class="w-full flex items-center justify-between pb-4">
<div class="font-medium text-ink-gray-7">
{{ __('Statistics') }}
</div>
</div>
<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 }"
/>
<NumberChart
class="border rounded-md"
:config="{
title: __('Certified'),
value: certificationCount.data || 0,
}"
/>
<NumberChart
class="border rounded-md"
:config="{
title: __('Courses'),
value: batch.data.courses?.length || 0,
}"
/>
<NumberChart
class="border rounded-md"
:config="{ title: __('Assessments'), value: assessmentCount || 0 }"
/>
</div>
<AxisChart
v-if="showProgressChart"
:config="{
data: chartData,
title: __('Batch Summary'),
subtitle: __('Progress of students in courses and assessments'),
xAxis: {
key: 'task',
title: 'Tasks',
type: 'category',
},
yAxis: {
title: __('Number of Students'),
echartOptions: {
minInterval: 1,
},
},
swapXY: true,
series: [
{
name: 'value',
type: 'bar',
},
],
}"
/>
</div>
<div>
<div class="flex items-center justify-between mb-4">
<div class="text-ink-gray-7 font-medium">
{{ __('Students') }}
<div class="text-ink-gray-9 font-medium">
{{ students.data?.length }} {{ __('Students') }}
</div>
<Button v-if="!readOnlyMode" @click="openStudentModal()">
<template #prefix>
@@ -76,6 +14,7 @@
<div v-if="students.data?.length">
<ListView
class="max-h-[75vh]"
:columns="getStudentColumns()"
:rows="students.data"
row-key="name"
@@ -151,7 +90,7 @@
</ListSelectBanner>
</ListView>
</div>
<div v-else class="text-sm italic text-ink-gray-5">
<div v-else-if="!students.loading" class="text-sm italic text-ink-gray-5">
{{ __('There are no students in this batch.') }}
</div>
</div>
@@ -170,7 +109,6 @@
<script setup>
import {
Avatar,
AxisChart,
Button,
createResource,
FeatherIcon,
@@ -181,30 +119,17 @@ import {
ListRows,
ListView,
ListRowItem,
NumberChart,
toast,
} from 'frappe-ui'
import {
BookOpen,
GraduationCap,
Plus,
ShieldCheck,
Trash2,
User,
} from 'lucide-vue-next'
import { ref, watch } from 'vue'
import { Plus, Trash2 } from 'lucide-vue-next'
import { ref } from 'vue'
import StudentModal from '@/components/Modals/StudentModal.vue'
import ProgressBar from '@/components/ProgressBar.vue'
import BatchStudentProgress from '@/components/Modals/BatchStudentProgress.vue'
import ApexChart from 'vue3-apexcharts'
import { theme } from '@/utils/theme'
const showStudentModal = ref(false)
const showStudentProgressModal = ref(false)
const selectedStudent = ref(null)
const chartData = ref(null)
const showProgressChart = ref(false)
const assessmentCount = ref(0)
const readOnlyMode = window.read_only_mode
const props = defineProps({
@@ -220,12 +145,6 @@ const students = createResource({
batch: props.batch?.data?.name,
},
auto: true,
onSuccess(data) {
chartData.value = getChartData()
showProgressChart.value =
data.length &&
(props.batch?.data?.courses?.length || assessmentCount.value)
},
})
const getStudentColumns = () => {
@@ -288,67 +207,4 @@ const removeStudents = (selections, unselectAll) => {
}
)
}
const getChartData = () => {
let tasks = []
let data = []
students.data.forEach((row) => {
tasks = countAssessments(row, tasks)
tasks = countCourses(row, tasks)
})
tasks.forEach((task) => {
data.push({
task: task.label,
value: task.value,
})
})
return data
}
const countAssessments = (row, tasks) => {
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, tasks) => {
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
}
watch(students, () => {
if (students.data?.length) {
assessmentCount.value = Object.keys(students.data?.[0].assessments).length
}
})
const certificationCount = createResource({
url: 'frappe.client.get_count',
params: {
doctype: 'LMS Certificate',
filters: {
batch_name: props.batch?.data?.name,
},
},
auto: true,
})
</script>

View File

@@ -1,7 +1,7 @@
<template>
<div
v-if="hasPermission() && !props.zoomAccount"
class="flex items-center space-x-2 mb-5 bg-surface-amber-1 py-1 px-2 rounded-md text-ink-amber-3"
class="flex items-center space-x-2 mb-5 bg-surface-amber-1 py-1 px-2 rounded-md text-ink-amber-3 text-xs"
>
<AlertCircle class="size-4 stroke-1.5" />
<span>

View File

@@ -22,6 +22,7 @@
:onCreate="
(value, close) => {
openSettings('Members', close)
show = false
}
"
/>

View File

@@ -1,12 +1,12 @@
<template>
<div v-if="user.data?.is_moderator || isStudent" class="">
<div v-if="isAdmin || isStudent" class="">
<header
class="sticky top-0 z-10 flex items-center justify-between border-b bg-surface-white px-3 py-2.5 sm:px-5"
>
<Breadcrumbs class="h-7" :items="breadcrumbs" />
<div class="flex items-center space-x-2">
<Button
v-if="user.data?.is_moderator && batch.data?.certification"
v-if="isAdmin && batch.data?.certification"
@click="openCertificateDialog = true"
>
{{ __('Generate Certificates') }}
@@ -67,6 +67,9 @@
<BatchDashboard :batch="batch" :isStudent="isStudent" />
</div>
<div v-else-if="tab.label == 'Dashboard'">
<AdminBatchDashboard :batch="batch" />
</div>
<div v-else-if="tab.label == 'Students'">
<BatchStudents :batch="batch" />
</div>
<div v-else-if="tab.label == 'Classes'">
@@ -235,6 +238,7 @@ import BatchDashboard from '@/components/BatchDashboard.vue'
import BatchCourses from '@/components/BatchCourses.vue'
import LiveClass from '@/components/LiveClass.vue'
import BatchStudents from '@/components/BatchStudents.vue'
import AdminBatchDashboard from '@/components/AdminBatchDashboard.vue'
import Assessments from '@/components/Assessments.vue'
import Announcements from '@/components/Annoucements.vue'
import AnnouncementModal from '@/components/Modals/AnnouncementModal.vue'
@@ -260,6 +264,13 @@ const tabs = computed(() => {
icon: LayoutDashboard,
})
if (isAdmin.value) {
batchTabs.push({
label: 'Students',
icon: ClipboardPen,
})
}
batchTabs.push({
label: 'Courses',
icon: BookOpen,
@@ -270,7 +281,7 @@ const tabs = computed(() => {
icon: Laptop,
})
if (user.data?.is_moderator) {
if (isAdmin.value) {
batchTabs.push({
label: 'Assessments',
icon: BookOpenCheck,
@@ -367,6 +378,10 @@ const canMakeAnnouncement = () => {
return user.data?.is_moderator || user.data?.is_evaluator
}
const isAdmin = computed(() => {
return user.data?.is_moderator || user.data?.is_evaluator
})
usePageMeta(() => {
return {
title: batch?.data?.title,

View File

@@ -244,12 +244,11 @@ const setQueryParams = () => {
}
})
let queryString = ''
if (queries.toString()) {
queryString = `?${queries.toString()}`
}
history.replaceState({}, '', `${location.pathname}${queryString}`)
history.replaceState(
{},
'',
`${location.pathname}${queries.size > 0 ? `?${queries.toString()}` : ''}`
)
}
const updateCategories = (data) => {

View File

@@ -124,7 +124,7 @@ const memberCount = ref(0)
const dayjs = inject('$dayjs')
onMounted(() => {
getMemberCount()
setFiltersFromQuery()
updateParticipants()
})
@@ -158,6 +158,8 @@ const categories = createListResource({
const updateParticipants = () => {
updateFilters()
getMemberCount()
setQueryParams()
participants.update({
filters: filters.value,
})
@@ -178,6 +180,33 @@ const updateFilters = () => {
}
}
const setQueryParams = () => {
let queries = new URLSearchParams(location.search)
let filterKeys = {
category: currentCategory.value,
name: nameFilter.value,
}
Object.keys(filterKeys).forEach((key) => {
if (filterKeys[key]) {
queries.set(key, filterKeys[key])
} else {
queries.delete(key)
}
})
history.replaceState(
{},
'',
`${location.pathname}${queries.size > 0 ? `?${queries.toString()}` : ''}`
)
}
const setFiltersFromQuery = () => {
let queries = new URLSearchParams(location.search)
nameFilter.value = queries.get('name') || ''
currentCategory.value = queries.get('category') || ''
}
const breadcrumbs = computed(() => [
{
label: __('Certified Members'),

View File

@@ -94,10 +94,10 @@
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
<div
v-for="evaluation in evals?.data"
class="border rounded-md p-3 flex flex-col h-full cursor-pointer"
class="border hover:border-outline-gray-3 rounded-md p-3 flex flex-col h-full cursor-pointer"
@click="redirectToProfile()"
>
<div class="font-semibold text-ink-gray-9 text-lg mb-1">
<div class="font-semibold text-ink-gray-9 text-lg leading-5 mb-1">
{{ evaluation.course_title }}
</div>
<div class="text-ink-gray-7 text-sm">
@@ -128,8 +128,11 @@
{{ __('Upcoming Live Classes') }}
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
<div v-for="cls in liveClasses?.data" class="border rounded-md p-3">
<div class="font-semibold text-ink-gray-9 text-lg mb-1">
<div
v-for="cls in liveClasses?.data"
class="border hover:border-outline-gray-3 rounded-md p-3"
>
<div class="font-semibold text-ink-gray-9 text-lg leading-5 mb-1">
{{ cls.title }}
</div>
<div class="text-ink-gray-7 text-sm leading-5 mb-4">

View File

@@ -71,8 +71,11 @@
{{ __('Upcoming Live Classes') }}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
<div v-for="cls in myLiveClasses.data" class="border rounded-md p-2">
<div class="font-semibold text-ink-gray-9 text-lg mb-1">
<div
v-for="cls in myLiveClasses.data"
class="border rounded-md hover:border-outline-gray-3 p-2"
>
<div class="font-semibold text-ink-gray-9 text-lg leading-5 mb-1">
{{ cls.title }}
</div>
<div class="text-ink-gray-7 text-sm leading-5 mb-4">