feat: assignments list and form

This commit is contained in:
Jannat Patel
2024-12-24 21:48:45 +05:30
parent f331c48e1d
commit a44f59c362
20 changed files with 808 additions and 141 deletions

View File

@@ -185,6 +185,17 @@ const addQuizzes = () => {
}
}
const addAssignments = () => {
if (isInstructor.value || isModerator.value) {
sidebarLinks.value.push({
label: 'Assignments',
icon: 'Pencil',
to: 'Assignments',
activeFor: ['Assignments', 'AssignmentForm'],
})
}
}
const addPrograms = () => {
let activeFor = ['Programs', 'ProgramForm']
let index = 1
@@ -247,8 +258,9 @@ watch(userResource, () => {
if (userResource.data) {
isModerator.value = userResource.data.is_moderator
isInstructor.value = userResource.data.is_instructor
addQuizzes()
addPrograms()
addQuizzes()
addAssignments()
}
})

View File

@@ -0,0 +1,75 @@
<template>
<Dialog
v-model="show"
:options="{
size: 'xl',
}"
>
<template #body>
<div class="p-5 space-y-4">
<div v-if="type == 'quiz'" class="text-lg font-semibold">
{{ __('Add a quiz to your lesson') }}
</div>
<div v-else class="text-lg font-semibold">
{{ __('Add an assignment to your lesson') }}
</div>
<div>
<Link
v-if="type == 'quiz'"
v-model="quiz"
doctype="LMS Quiz"
:label="__('Select a quiz')"
:onCreate="(value, close) => redirectToForm()"
/>
<Link
v-else
v-model="assignment"
doctype="LMS Assignment"
:label="__('Select an assignment')"
:onCreate="(value, close) => redirectToForm()"
/>
</div>
<div class="flex justify-end space-x-2">
<Button variant="solid" @click="addAssessment()">
{{ __('Save') }}
</Button>
</div>
</div>
</template>
</Dialog>
</template>
<script setup>
import { Dialog, Button } from 'frappe-ui'
import { onMounted, ref, nextTick } from 'vue'
import Link from '@/components/Controls/Link.vue'
const show = ref(false)
const quiz = ref(null)
const assignment = ref(null)
const props = defineProps({
type: {
type: String,
required: true,
},
onAddition: {
type: Function,
required: true,
},
})
onMounted(async () => {
await nextTick()
show.value = true
})
const addAssessment = () => {
props.onAddition(props.type == 'quiz' ? quiz.value : assignment.value)
show.value = false
}
const redirectToForm = () => {
if (props.type == 'quiz') window.open('/lms/quizzes/new', '_blank')
else window.open('/lms/assignments/new', '_blank')
}
</script>

View File

@@ -148,7 +148,7 @@ const getRowRoute = (row) => {
return {
name: 'AssignmentSubmission',
params: {
assignmentName: row.assessment_name,
assignmentID: row.assessment_name,
submissionName: row.submission.name,
},
}
@@ -156,7 +156,7 @@ const getRowRoute = (row) => {
return {
name: 'AssignmentSubmission',
params: {
assignmentName: row.assessment_name,
assignmentID: row.assessment_name,
submissionName: 'new',
},
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="">
<div class="w-full flex items-center justify-between border-b pb-4">
<div class="w-full flex items-center justify-between pb-4">
<div class="font-medium text-gray-600">
{{ __('Statistics') }}
</div>
@@ -98,9 +98,6 @@
row-key="name"
:options="{
showTooltip: false,
onRowClick: (row) => {
openStudentProgressModal(row)
},
}"
>
<ListHeader
@@ -121,7 +118,12 @@
</ListHeaderItem>
</ListHeader>
<ListRows>
<ListRow :row="row" v-for="row in students.data">
<ListRow
:row="row"
v-for="row in students.data"
class="group cursor-pointer"
@click="openStudentProgressModal(row)"
>
<template #default="{ column, item }">
<ListRowItem :item="row[column.key]" :align="column.align">
<template #prefix>
@@ -140,6 +142,16 @@
>
<ProgressBar :progress="row[column.key]" size="sm" />
</div>
<div
v-else-if="column.key == 'copy'"
class="invisible group-hover:visible"
>
<Button variant="ghost" @click="copyEmail(row)">
<template #icon>
<Clipboard class="h-4 w-4 stroke-1.5" />
</template>
</Button>
</div>
<div v-else>
{{ row[column.key] }}
</div>
@@ -190,7 +202,14 @@ import {
ListView,
ListRowItem,
} from 'frappe-ui'
import { BookOpen, Plus, ShieldCheck, Trash2, User } from 'lucide-vue-next'
import {
BookOpen,
Clipboard,
Plus,
ShieldCheck,
Trash2,
User,
} from 'lucide-vue-next'
import { ref, watch } from 'vue'
import StudentModal from '@/components/Modals/StudentModal.vue'
import { showToast } from '@/utils'
@@ -247,6 +266,10 @@ const getStudentColumns = () => {
align: 'center',
icon: 'clock',
},
{
label: '',
key: 'copy',
},
]
return columns
@@ -331,7 +354,10 @@ const getChartData = () => {
const getChartOptions = (categories) => {
const courseColor = '#0289F7'
const assessmentColor = '#E03636'
const maxY = Math.ceil(students.data?.length / 10) * 10
const maxY =
students.data?.length % 5
? students.data?.length + (5 - (students.data?.length % 5))
: students.data?.length
return {
chart: {
@@ -346,7 +372,7 @@ const getChartOptions = (categories) => {
distributed: true,
borderRadius: 0,
horizontal: false,
columnWidth: '30%',
columnWidth: '5%',
},
},
colors: Object.values(categories).map((item) =>
@@ -362,20 +388,22 @@ const getChartOptions = (categories) => {
fontSize: '10px',
},
rotate: 0,
formatter: function (value) {
return value.length > 22 ? `${value.substring(0, 22)}...` : value // Trim long labels
},
},
},
yaxis: {
max: maxY,
min: 0,
stepSize: 10,
tickAmount: maxY / 10,
tickAmount: maxY / 5,
},
}
}
const copyEmail = (row) => {
navigator.clipboard.writeText(row.email)
showToast(__('Success'), __('Email copied to clipboard'), 'check')
}
watch(students, () => {
if (students.data?.length) {
assessmentCount.value = Object.keys(students.data?.[0].assessments).length

View File

@@ -1,5 +1,20 @@
<template>
<div class="space-y-5">
<div class="space-y-2">
<div class="flex items-center text-sm font-medium space-x-2">
<span>
{{ __('What does include in preview mean?') }}
</span>
</div>
<div class="text-xs text-gray-600 mb-1 leading-5">
{{
__(
'If Include in Preview is enabled for a lesson then the lesson will also be accessible to non logged in users.'
)
}}
</div>
</div>
<div class="space-y-2">
<div
class="flex items-center text-sm font-medium space-x-2 cursor-pointer"
@@ -56,21 +71,6 @@
}}
</div>
</div>
<div class="space-y-2">
<div class="flex items-center text-sm font-medium space-x-2">
<span>
{{ __('What does include in preview mean?') }}
</span>
</div>
<div class="text-xs text-gray-600 mb-1 leading-5">
{{
__(
'If Include in Preview is enabled for a lesson then the lesson will also be accessible to non logged in users.'
)
}}
</div>
</div>
</div>
<ExplanationVideos v-model="showExplanation" :title="title" :type="type" />
</template>

View File

@@ -1,7 +1,7 @@
<template>
<Dialog v-model="show" :options="{}">
<template #body>
<div class="p-5 space-y-8">
<div class="p-5 space-y-8 text-base">
<div class="flex items-center space-x-2">
<Avatar :image="student.user_image" size="3xl" />
<div class="space-y-1">

View File

@@ -1,58 +0,0 @@
<template>
<Dialog
v-model="show"
:options="{
size: 'xl',
}"
>
<template #body>
<div class="p-5 space-y-4">
<div class="text-lg font-semibold">
{{ __('Add a quiz to your lesson') }}
</div>
<div>
<Link
v-model="quiz"
doctype="LMS Quiz"
:label="__('Select a quiz')"
:onCreate="(value, close) => redirectToQuizForm()"
/>
</div>
<div class="flex justify-end space-x-2">
<Button variant="solid" @click="addQuiz()">
{{ __('Save') }}
</Button>
</div>
</div>
</template>
</Dialog>
</template>
<script setup>
import { Dialog, Button } from 'frappe-ui'
import { onMounted, ref, nextTick } from 'vue'
import Link from '@/components/Controls/Link.vue'
const show = ref(false)
const quiz = ref(null)
const props = defineProps({
onQuizAddition: {
type: Function,
required: true,
},
})
onMounted(async () => {
await nextTick()
show.value = true
})
const addQuiz = () => {
props.onQuizAddition(quiz.value)
show.value = false
}
const redirectToQuizForm = () => {
window.open('/lms/quizzes/new', '_blank')
}
</script>