Skip to content

Commit 4d18580

Browse files
committed
feat: batch student progress modal
1 parent b48e007 commit 4d18580

File tree

7 files changed

+422
-473
lines changed

7 files changed

+422
-473
lines changed

frontend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@editorjs/simple-image": "^1.6.0",
2121
"@editorjs/table": "^2.4.2",
2222
"ace-builds": "^1.36.2",
23+
"apexcharts": "^4.3.0",
2324
"chart.js": "^4.4.1",
2425
"codemirror-editor-vue3": "^2.8.0",
2526
"dayjs": "^1.11.6",
@@ -35,6 +36,7 @@
3536
"vue-chartjs": "^5.3.0",
3637
"vue-draggable-next": "^2.2.1",
3738
"vue-router": "^4.0.12",
39+
"vue3-apexcharts": "^1.8.0",
3840
"vuedraggable": "4.1.0"
3941
},
4042
"devDependencies": {

frontend/src/components/BatchStudents.vue

Lines changed: 223 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
11
<template>
2-
<div class="flex items-center justify-between mb-4">
3-
<div class="text-lg font-semibold">
4-
{{ __('Students') }}
2+
<div>
3+
<!-- <Bar v-if="chartData" :data="chartData" :options="chartOptions" /> -->
4+
<ApexChart
5+
v-if="chartData"
6+
:options="chartOptions"
7+
:series="chartData"
8+
type="bar"
9+
height="350"
10+
/>
11+
<div class="flex items-center justify-between mb-4">
12+
<div class="text-lg font-semibold">
13+
{{ __('Students') }}
14+
</div>
15+
<Button @click="openStudentModal()">
16+
<template #prefix>
17+
<Plus class="h-4 w-4" />
18+
</template>
19+
{{ __('Add') }}
20+
</Button>
521
</div>
6-
<Button @click="openStudentModal()">
7-
<template #prefix>
8-
<Plus class="h-4 w-4" />
9-
</template>
10-
{{ __('Add') }}
11-
</Button>
1222
</div>
1323
<div v-if="students.data?.length">
1424
<ListView
1525
:columns="getStudentColumns()"
1626
:rows="students.data"
1727
row-key="name"
18-
:options="{ showTooltip: false }"
28+
:options="{
29+
showTooltip: false,
30+
onRowClick: (row) => {
31+
openStudentProgressModal(row)
32+
},
33+
}"
1934
>
2035
<ListHeader
2136
class="mb-2 grid items-center space-x-4 rounded bg-gray-100 p-2"
@@ -48,10 +63,16 @@
4863
/>
4964
</div>
5065
</template>
51-
<div v-if="column.key == 'courses'">
66+
<div
67+
v-if="column.key == 'progress'"
68+
class="flex items-center space-x-4 w-full"
69+
>
70+
<ProgressBar :progress="row[column.key]" size="sm" />
71+
</div>
72+
<div v-else>
5273
{{ row[column.key] }}
5374
</div>
54-
<div v-else-if="column.icon == 'book-open'">
75+
<!-- <div v-else-if="column.icon == 'book-open'">
5576
{{ Math.ceil(row.courses[column.key]) }}
5677
</div>
5778
<div v-else-if="column.icon == 'help-circle'">
@@ -63,7 +84,7 @@
6384
{{ row.assessments[column.key] }}
6485
</Badge>
6586
<div v-else>{{ parseInt(row.assessments[column.key]) }}</div>
66-
</div>
87+
</div> -->
6788
</ListRowItem>
6889
</template>
6990
</ListRow>
@@ -90,11 +111,14 @@
90111
v-model="showStudentModal"
91112
v-model:reloadStudents="students"
92113
/>
114+
<BatchStudentProgress
115+
:student="selectedStudent"
116+
v-model="showStudentProgressModal"
117+
/>
93118
</template>
94119
<script setup>
95120
import {
96121
Avatar,
97-
Badge,
98122
Button,
99123
createResource,
100124
FeatherIcon,
@@ -107,11 +131,38 @@ import {
107131
ListRowItem,
108132
} from 'frappe-ui'
109133
import { Trash2, Plus } from 'lucide-vue-next'
110-
import { ref } from 'vue'
134+
import { computed, ref } from 'vue'
111135
import StudentModal from '@/components/Modals/StudentModal.vue'
112136
import { showToast } from '@/utils'
137+
import ProgressBar from '@/components/ProgressBar.vue'
138+
import BatchStudentProgress from '@/components/Modals/BatchStudentProgress.vue'
139+
import { Bar } from 'vue-chartjs'
140+
import {
141+
Chart as ChartJS,
142+
Title,
143+
Tooltip,
144+
Legend,
145+
BarElement,
146+
CategoryScale,
147+
LinearScale,
148+
Filler,
149+
} from 'chart.js'
150+
ChartJS.register(
151+
Title,
152+
Tooltip,
153+
Legend,
154+
BarElement,
155+
CategoryScale,
156+
LinearScale,
157+
Filler
158+
)
159+
import ApexChart from 'vue3-apexcharts'
113160
114161
const showStudentModal = ref(false)
162+
const showStudentProgressModal = ref(false)
163+
const selectedStudent = ref(null)
164+
const chartData = ref(null)
165+
const chartOptions = ref(null)
115166
116167
const props = defineProps({
117168
batch: {
@@ -127,50 +178,47 @@ const students = createResource({
127178
batch: props.batch,
128179
},
129180
auto: true,
181+
onSuccess(data) {
182+
chartData.value = getChartData()
183+
console.log(chartData.value)
184+
},
130185
})
131186
132187
const getStudentColumns = () => {
133188
let columns = [
134189
{
135190
label: 'Full Name',
136191
key: 'full_name',
192+
width: '20rem',
193+
icon: 'user',
194+
},
195+
{
196+
label: 'Progress',
197+
key: 'progress',
198+
width: '10rem',
199+
icon: 'activity',
200+
},
201+
{
202+
label: 'Last Active',
203+
key: 'last_active',
137204
width: '15rem',
205+
align: 'center',
206+
icon: 'clock',
138207
},
139208
]
140209
141-
if (students.data?.[0].assessments) {
142-
Object.keys(students.data?.[0].assessments).forEach((assessment) => {
143-
columns.push({
144-
label: assessment,
145-
key: assessment,
146-
width: '10rem',
147-
icon: 'help-circle',
148-
align: isAssignment(students.data?.[0].assessments[assessment])
149-
? 'left'
150-
: 'center',
151-
})
152-
})
153-
}
154-
155-
if (students.data?.[0].courses) {
156-
Object.keys(students.data?.[0].courses).forEach((course) => {
157-
columns.push({
158-
label: course,
159-
key: course,
160-
width: '10rem',
161-
icon: 'book-open',
162-
align: 'center',
163-
})
164-
})
165-
}
166-
167210
return columns
168211
}
169212
170213
const openStudentModal = () => {
171214
showStudentModal.value = true
172215
}
173216
217+
const openStudentProgressModal = (row) => {
218+
showStudentProgressModal.value = true
219+
selectedStudent.value = row
220+
}
221+
174222
const deleteStudents = createResource({
175223
url: 'lms.lms.api.delete_documents',
176224
makeParams(values) {
@@ -196,17 +244,141 @@ const removeStudents = (selections, unselectAll) => {
196244
)
197245
}
198246
199-
const getStatusTheme = (status) => {
200-
if (status === 'Pass') {
201-
return 'green'
202-
} else if (status == 'Not Graded') {
203-
return 'orange'
204-
} else {
205-
return 'red'
206-
}
247+
const getChartData = () => {
248+
console.log('called')
249+
250+
let categories = {}
251+
252+
// Initialize categories with categories
253+
Object.keys(students.data?.[0].courses).forEach((course) => {
254+
categories[course] = {
255+
value: 0,
256+
type: 'course',
257+
}
258+
})
259+
260+
Object.keys(students.data?.[0].assessments).forEach((assessment) => {
261+
categories[assessment] = {
262+
value: 0,
263+
type: 'assessment',
264+
}
265+
})
266+
267+
// Populate data
268+
students.data.forEach((student) => {
269+
Object.keys(student.courses).forEach((course) => {
270+
if (student.courses[course] === 100) {
271+
categories[course].value += 1
272+
}
273+
})
274+
275+
Object.keys(student.assessments).forEach((assessment) => {
276+
if (student.assessments[assessment] === 100) {
277+
categories[assessment].value += 1
278+
}
279+
})
280+
})
281+
282+
// Transform data for ApexCharts
283+
console.log(Object.values(categories).map((item) => item.value))
284+
chartOptions.value = getChartOptions(categories)
285+
return [
286+
{
287+
name: __('Student Progress'),
288+
data: Object.values(categories).map((item) => item.value),
289+
/* colors: Object.values(categories).map(item =>
290+
item.type === 'course' ? courseColor : assessmentColor
291+
), */
292+
},
293+
]
207294
}
208295
209-
const isAssignment = (value) => {
210-
return isNaN(value)
296+
/* const chartOptions = computed(() => {
297+
return {
298+
responsive: true,
299+
fill: true,
300+
scales: {
301+
x: {
302+
ticks: {
303+
maxRotation: 0,
304+
minRotation: 0,
305+
autoSkip: false,
306+
}
307+
},
308+
y: {
309+
beginAtZero: true,
310+
max: students.data?.length,
311+
ticks: {
312+
stepSize: 5,
313+
},
314+
},
315+
},
316+
plugins: {
317+
legends: {
318+
display: false,
319+
title: {
320+
text: __("Student Progress 1111"),
321+
}
322+
},
323+
title: {
324+
display: true,
325+
text: __("Student Progress"),
326+
font: {
327+
size: 14,
328+
weight: '500',
329+
},
330+
color: '#171717',
331+
}
332+
}
333+
}
334+
}) */
335+
336+
const chartSeries = ref([
337+
{
338+
name: 'Courses',
339+
data: [20, 30, 50], // Example data for courses
340+
},
341+
{
342+
name: 'Assessments',
343+
data: [10, 40, 60], // Example data for assessments
344+
},
345+
])
346+
347+
const getChartOptions = (categories) => {
348+
const courseColor = '#3498db' // Blue for courses
349+
const assessmentColor = '#e74c3c' // Red for assessments
350+
return {
351+
chart: {
352+
type: 'bar',
353+
height: 350,
354+
},
355+
plotOptions: {
356+
bar: {
357+
distributed: true, // Allows individual bar colors
358+
borderRadius: 0,
359+
horizontal: false, // Set to true for horizontal bars
360+
columnWidth: '30%',
361+
},
362+
},
363+
colors: Object.values(categories).map((item) =>
364+
item.type === 'course' ? courseColor : assessmentColor
365+
),
366+
legends: {
367+
show: true,
368+
},
369+
xaxis: {
370+
categories: Object.keys(categories),
371+
labels: {
372+
style: {
373+
fontSize: '10px',
374+
},
375+
rotate: 0,
376+
formatter: function (value) {
377+
console.log(value)
378+
return value.length > 20 ? `${value.substring(0, 20)}...` : value // Trim long labels
379+
},
380+
},
381+
},
382+
}
211383
}
212384
</script>

0 commit comments

Comments
 (0)