Merge pull request #1156 from pateljannat/program-saving-issue
fix: misc issues
This commit is contained in:
@@ -186,18 +186,27 @@ const addQuizzes = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const addPrograms = () => {
|
const addPrograms = () => {
|
||||||
if (settingsStore.learningPaths.data) {
|
let activeFor = ['Programs', 'ProgramForm']
|
||||||
let activeFor = ['Programs', 'ProgramForm']
|
let index = 1
|
||||||
let index = 1
|
let canAddProgram = false
|
||||||
if (!isInstructor.value && !isModerator.value) {
|
|
||||||
sidebarLinks.value = sidebarLinks.value.filter(
|
|
||||||
(link) => link.label !== 'Courses'
|
|
||||||
)
|
|
||||||
activeFor.push('CourseDetail')
|
|
||||||
activeFor.push('Lesson')
|
|
||||||
index = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isInstructor.value &&
|
||||||
|
!isModerator.value &&
|
||||||
|
settingsStore.learningPaths.data
|
||||||
|
) {
|
||||||
|
sidebarLinks.value = sidebarLinks.value.filter(
|
||||||
|
(link) => link.label !== 'Courses'
|
||||||
|
)
|
||||||
|
activeFor.push('CourseDetail')
|
||||||
|
activeFor.push('Lesson')
|
||||||
|
index = 0
|
||||||
|
canAddProgram = true
|
||||||
|
} else if (isInstructor.value || isModerator.value) {
|
||||||
|
canAddProgram = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canAddProgram) {
|
||||||
sidebarLinks.value.splice(index, 0, {
|
sidebarLinks.value.splice(index, 0, {
|
||||||
label: 'Programs',
|
label: 'Programs',
|
||||||
icon: 'Route',
|
icon: 'Route',
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full overflow-auto" id="scrollContainer">
|
<div class="w-full overflow-auto" id="scrollContainer">
|
||||||
<OnboardingBanner />
|
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -17,5 +16,4 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import AppSidebar from './AppSidebar.vue'
|
import AppSidebar from './AppSidebar.vue'
|
||||||
import OnboardingBanner from '@/components/OnboardingBanner.vue'
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
@click="openHelpDialog('upload')"
|
@click="openHelpDialog('upload')"
|
||||||
>
|
>
|
||||||
<span class="leading-5">
|
<span class="leading-5">
|
||||||
{{ __('How to upload content from your system?') }}
|
{{ __(contentMap['upload']) }}
|
||||||
</span>
|
</span>
|
||||||
<Info class="w-3 h-3 text-gray-700" />
|
<Info class="w-3 h-3 text-gray-700" />
|
||||||
</div>
|
</div>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
@click="openHelpDialog('youtube')"
|
@click="openHelpDialog('youtube')"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
{{ __('How to add a YouTube Video?') }}
|
{{ __(contentMap['youtube']) }}
|
||||||
</span>
|
</span>
|
||||||
<Info class="w-3 h-3 text-gray-700" />
|
<Info class="w-3 h-3 text-gray-700" />
|
||||||
</div>
|
</div>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ExplanationVideos v-model="showExplanation" :type="type" />
|
<ExplanationVideos v-model="showExplanation" :title="title" :type="type" />
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Info } from 'lucide-vue-next'
|
import { Info } from 'lucide-vue-next'
|
||||||
@@ -81,9 +81,16 @@ import ExplanationVideos from '@/components/Modals/ExplanationVideos.vue'
|
|||||||
|
|
||||||
const showExplanation = ref(false)
|
const showExplanation = ref(false)
|
||||||
const type = ref(null)
|
const type = ref(null)
|
||||||
|
const title = ref(null)
|
||||||
|
const contentMap = {
|
||||||
|
quiz: 'How to add a Quiz?',
|
||||||
|
upload: 'How to upload content from your system?',
|
||||||
|
youtube: 'How to add a YouTube Video?',
|
||||||
|
}
|
||||||
|
|
||||||
const openHelpDialog = (contentType) => {
|
const openHelpDialog = (contentType) => {
|
||||||
type.value = contentType
|
type.value = contentType
|
||||||
|
title.value = contentMap[contentType]
|
||||||
showExplanation.value = true
|
showExplanation.value = true
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
v-model="show"
|
v-model="show"
|
||||||
:options="{
|
:options="{
|
||||||
size: '4xl',
|
size: '4xl',
|
||||||
|
title: title,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body-content>
|
||||||
<div class="p-4">
|
<div>
|
||||||
<VideoBlock :file="file" />
|
<VideoBlock :file="file" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -24,6 +25,10 @@ const props = defineProps({
|
|||||||
type: [String, null],
|
type: [String, null],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const file = computed(() => {
|
const file = computed(() => {
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
@click="redirectToCourseForm()"
|
@click="redirectToCourseForm()"
|
||||||
class="flex items-center space-x-2"
|
class="flex items-center space-x-2"
|
||||||
:class="{
|
:class="{
|
||||||
'cursor-pointer': !onboardingDetails.data.course_created.length,
|
'cursor-pointer': !onboardingDetails.data.course_created?.length,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="onboardingDetails.data.course_created.length"
|
v-if="onboardingDetails.data.course_created?.length"
|
||||||
class="py-1 px-1 bg-white rounded-full"
|
class="py-1 px-1 bg-white rounded-full"
|
||||||
>
|
>
|
||||||
<Check class="h-4 w-4 stroke-2 text-green-600" />
|
<Check class="h-4 w-4 stroke-2 text-green-600" />
|
||||||
@@ -32,13 +32,13 @@
|
|||||||
class="flex items-center space-x-2"
|
class="flex items-center space-x-2"
|
||||||
:class="{
|
:class="{
|
||||||
'cursor-pointer':
|
'cursor-pointer':
|
||||||
onboardingDetails.data.course_created.length &&
|
onboardingDetails.data.course_created?.length &&
|
||||||
!onboardingDetails.data.chapter_created.length,
|
!onboardingDetails.data.chapter_created?.length,
|
||||||
'text-gray-400': !onboardingDetails.data.course_created.length,
|
'text-gray-400': !onboardingDetails.data.course_created?.length,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="onboardingDetails.data.chapter_created.length"
|
v-if="onboardingDetails.data.chapter_created?.length"
|
||||||
class="py-1 px-1 bg-white rounded-full"
|
class="py-1 px-1 bg-white rounded-full"
|
||||||
>
|
>
|
||||||
<Check class="h-4 w-4 stroke-2 text-green-600" />
|
<Check class="h-4 w-4 stroke-2 text-green-600" />
|
||||||
@@ -55,15 +55,15 @@
|
|||||||
class="flex items-center space-x-2"
|
class="flex items-center space-x-2"
|
||||||
:class="{
|
:class="{
|
||||||
'cursor-pointer':
|
'cursor-pointer':
|
||||||
onboardingDetails.data.course_created.length &&
|
onboardingDetails.data.course_created?.length &&
|
||||||
onboardingDetails.data.chapter_created.length,
|
onboardingDetails.data.chapter_created?.length,
|
||||||
'text-gray-400':
|
'text-gray-400':
|
||||||
!onboardingDetails.data.course_created.length ||
|
!onboardingDetails.data.course_created?.length ||
|
||||||
!onboardingDetails.data.chapter_created.length,
|
!onboardingDetails.data.chapter_created?.length,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="onboardingDetails.data.lesson_created.length"
|
v-if="onboardingDetails.data.lesson_created?.length"
|
||||||
class="py-1 px-1 bg-white rounded-full"
|
class="py-1 px-1 bg-white rounded-full"
|
||||||
>
|
>
|
||||||
<Check class="h-4 w-4 stroke-2 text-green-600" />
|
<Check class="h-4 w-4 stroke-2 text-green-600" />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
class="sticky top-0 z-10 flex flex-col md:flex-row md:items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5"
|
class="sticky top-0 z-10 flex flex-col md:flex-row md:items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5"
|
||||||
>
|
>
|
||||||
<Breadcrumbs :items="breadbrumbs" />
|
<Breadcrumbs :items="breadbrumbs" />
|
||||||
<Button variant="solid">
|
<Button variant="solid" @click="saveProgram()">
|
||||||
{{ __('Save') }}
|
{{ __('Save') }}
|
||||||
</Button>
|
</Button>
|
||||||
</header>
|
</header>
|
||||||
@@ -50,6 +50,7 @@
|
|||||||
item-key="name"
|
item-key="name"
|
||||||
group="items"
|
group="items"
|
||||||
@end="updateOrder"
|
@end="updateOrder"
|
||||||
|
class="cursor-move"
|
||||||
>
|
>
|
||||||
<template #item="{ element: row }">
|
<template #item="{ element: row }">
|
||||||
<ListRow :row="row" />
|
<ListRow :row="row" />
|
||||||
@@ -191,11 +192,13 @@ import { Plus, Trash2 } from 'lucide-vue-next'
|
|||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import { showToast } from '@/utils/'
|
import { showToast } from '@/utils/'
|
||||||
import Draggable from 'vuedraggable'
|
import Draggable from 'vuedraggable'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const showDialog = ref(false)
|
const showDialog = ref(false)
|
||||||
const currentForm = ref(null)
|
const currentForm = ref(null)
|
||||||
const course = ref(null)
|
const course = ref(null)
|
||||||
const member = ref(null)
|
const member = ref(null)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
programName: {
|
programName: {
|
||||||
@@ -302,6 +305,16 @@ const updateOrder = (e) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const saveProgram = () => {
|
||||||
|
call('frappe.model.rename_doc.update_document_title', {
|
||||||
|
doctype: 'LMS Program',
|
||||||
|
docname: program.doc.name,
|
||||||
|
name: program.doc.title,
|
||||||
|
}).then((data) => {
|
||||||
|
router.push({ name: 'ProgramForm', params: { programName: data } })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const courseColumns = computed(() => {
|
const courseColumns = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -332,10 +345,10 @@ const memberColumns = computed(() => {
|
|||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Progress',
|
label: 'Progress (%)',
|
||||||
key: 'progress',
|
key: 'progress',
|
||||||
width: 3,
|
width: 3,
|
||||||
align: 'left',
|
align: 'right',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</header>
|
</header>
|
||||||
<div v-if="programs.data?.length" class="pt-5 px-5">
|
<div v-if="programs.data?.length" class="pt-5 px-5">
|
||||||
<div v-for="program in programs.data" class="mb-20">
|
<div v-for="program in programs.data" class="mb-10">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="text-xl font-semibold">
|
<div class="text-xl font-semibold">
|
||||||
{{ program.name }}
|
{{ program.name }}
|
||||||
@@ -61,12 +61,23 @@
|
|||||||
v-if="program.courses?.length"
|
v-if="program.courses?.length"
|
||||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 mt-5"
|
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 mt-5"
|
||||||
>
|
>
|
||||||
<CourseCard
|
<div v-for="course in program.courses" class="relative group">
|
||||||
v-for="course in program.courses"
|
<CourseCard
|
||||||
:course="course"
|
:course="course"
|
||||||
@click="enrollMember(program.name, course.name)"
|
@click="enrollMember(program.name, course.name)"
|
||||||
class="cursor-pointer"
|
class="cursor-pointer"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
v-if="lockCourse(course)"
|
||||||
|
class="absolute inset-0 bg-black-overlay-500 opacity-60 rounded-md"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
v-if="lockCourse(course)"
|
||||||
|
class="absolute inset-0 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<LockKeyhole class="size-10 text-white" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-sm italic text-gray-600 mt-4">
|
<div v-else class="text-sm italic text-gray-600 mt-4">
|
||||||
{{ __('No courses in this program') }}
|
{{ __('No courses in this program') }}
|
||||||
@@ -118,16 +129,28 @@ import {
|
|||||||
Dialog,
|
Dialog,
|
||||||
FormControl,
|
FormControl,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, inject, ref } from 'vue'
|
import { computed, inject, onMounted, ref } from 'vue'
|
||||||
import { BookOpen, Edit, Plus } from 'lucide-vue-next'
|
import { BookOpen, Edit, Plus, LockKeyhole } from 'lucide-vue-next'
|
||||||
import CourseCard from '@/components/CourseCard.vue'
|
import CourseCard from '@/components/CourseCard.vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { showToast, singularize } from '@/utils'
|
import { showToast, singularize } from '@/utils'
|
||||||
|
import { useSettings } from '@/stores/settings'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const showDialog = ref(false)
|
const showDialog = ref(false)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const title = ref('')
|
const title = ref('')
|
||||||
|
const settings = useSettings()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (
|
||||||
|
!settings.learningPaths.data &&
|
||||||
|
!user.data?.is_moderator &&
|
||||||
|
!user.data?.is_instructor
|
||||||
|
) {
|
||||||
|
router.push({ name: 'Courses' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const programs = createResource({
|
const programs = createResource({
|
||||||
url: 'lms.lms.utils.get_programs',
|
url: 'lms.lms.utils.get_programs',
|
||||||
@@ -177,6 +200,13 @@ const enrollMember = (program, course) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lockCourse = (course) => {
|
||||||
|
if (user.data?.is_moderator || user.data?.is_instructor) return false
|
||||||
|
if (course.membership) return false
|
||||||
|
if (course.eligible) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
const breadbrumbs = computed(() => [
|
const breadbrumbs = computed(() => [
|
||||||
{
|
{
|
||||||
label: 'Programs',
|
label: 'Programs',
|
||||||
|
|||||||
@@ -206,7 +206,6 @@ import {
|
|||||||
inject,
|
inject,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
watch,
|
watch,
|
||||||
isReactive,
|
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { Plus, Trash2 } from 'lucide-vue-next'
|
import { Plus, Trash2 } from 'lucide-vue-next'
|
||||||
import Question from '@/components/Modals/Question.vue'
|
import Question from '@/components/Modals/Question.vue'
|
||||||
|
|||||||
@@ -15,38 +15,45 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div v-if="submisisonDetails.doc" class="w-1/2 mx-auto py-5 space-y-4">
|
<div v-if="submisisonDetails.doc" class="w-1/2 mx-auto py-5 space-y-5">
|
||||||
<div class="grid grid-cols-2 gap-5">
|
<div class="text-xl font-semibold">
|
||||||
<FormControl
|
{{ submisisonDetails.doc.member_name }}
|
||||||
v-model="submisisonDetails.doc.quiz_title"
|
|
||||||
:label="__('Quiz')"
|
|
||||||
:disabled="true"
|
|
||||||
/>
|
|
||||||
<FormControl
|
|
||||||
v-model="submisisonDetails.doc.member_name"
|
|
||||||
:label="__('Member')"
|
|
||||||
:disabled="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="space-y-4 border p-5 rounded-md">
|
||||||
|
<div class="grid grid-cols-2 gap-5">
|
||||||
|
<FormControl
|
||||||
|
v-model="submisisonDetails.doc.quiz_title"
|
||||||
|
:label="__('Quiz')"
|
||||||
|
:disabled="true"
|
||||||
|
/>
|
||||||
|
<FormControl
|
||||||
|
v-model="submisisonDetails.doc.member_name"
|
||||||
|
:label="__('Member')"
|
||||||
|
:disabled="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-5">
|
<div class="grid grid-cols-2 gap-5">
|
||||||
<FormControl
|
<FormControl
|
||||||
v-model="submisisonDetails.doc.score"
|
v-model="submisisonDetails.doc.score"
|
||||||
:label="__('Score')"
|
:label="__('Score')"
|
||||||
:disabled="true"
|
:disabled="true"
|
||||||
/>
|
/>
|
||||||
<FormControl
|
<FormControl
|
||||||
v-model="submisisonDetails.doc.percentage"
|
v-model="submisisonDetails.doc.percentage"
|
||||||
:label="__('Percentage')"
|
:label="__('Percentage')"
|
||||||
:disabled="true"
|
:disabled="true"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-for="row in submisisonDetails.doc.result"
|
v-for="row in submisisonDetails.doc.result"
|
||||||
class="border p-5 rounded-md space-y-4"
|
class="border p-5 rounded-md space-y-4"
|
||||||
>
|
>
|
||||||
<div class="font-semibold">{{ row.idx }}. {{ row.question }}</div>
|
<div class="flex space-x-1 font-semibold">
|
||||||
|
<span class="leading-5" v-html="row.question"> </span>
|
||||||
|
</div>
|
||||||
<div v-html="row.answer" class="leading-5"></div>
|
<div v-html="row.answer" class="leading-5"></div>
|
||||||
<div class="grid grid-cols-2 gap-5">
|
<div class="grid grid-cols-2 gap-5">
|
||||||
<FormControl v-model="row.marks" :label="__('Marks')" />
|
<FormControl v-model="row.marks" :label="__('Marks')" />
|
||||||
@@ -67,7 +74,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Badge,
|
Badge,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, onMounted, inject } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, inject } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { showToast } from '@/utils'
|
import { showToast } from '@/utils'
|
||||||
|
|
||||||
@@ -77,8 +84,25 @@ const user = inject('$user')
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!user.data?.is_instructor && !user.data?.is_moderator)
|
if (!user.data?.is_instructor && !user.data?.is_moderator)
|
||||||
router.push({ name: 'Courses' })
|
router.push({ name: 'Courses' })
|
||||||
|
|
||||||
|
window.addEventListener('keydown', keyboardShortcut)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('keydown', keyboardShortcut)
|
||||||
|
})
|
||||||
|
|
||||||
|
const keyboardShortcut = (e) => {
|
||||||
|
if (
|
||||||
|
e.key === 's' &&
|
||||||
|
(e.ctrlKey || e.metaKey) &&
|
||||||
|
!e.target.classList.contains('ProseMirror')
|
||||||
|
) {
|
||||||
|
saveSubmission()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
submission: {
|
submission: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ export class Quiz {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderQuizModal() {
|
renderQuizModal() {
|
||||||
|
if (this.readOnly) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const app = createApp(QuizPlugin, {
|
const app = createApp(QuizPlugin, {
|
||||||
onQuizAddition: (quiz) => {
|
onQuizAddition: (quiz) => {
|
||||||
this.data.quiz = quiz
|
this.data.quiz = quiz
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-11-20 12:26:02.214628",
|
"modified": "2024-11-28 22:06:16.742867",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Program",
|
"name": "LMS Program",
|
||||||
@@ -80,5 +80,6 @@
|
|||||||
],
|
],
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": [],
|
||||||
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,6 @@ def quiz_summary(quiz, results):
|
|||||||
result["marks"] = marks
|
result["marks"] = marks
|
||||||
score += marks
|
score += marks
|
||||||
|
|
||||||
del result["question_name"]
|
|
||||||
else:
|
else:
|
||||||
result["is_correct"] = 0
|
result["is_correct"] = 0
|
||||||
is_open_ended = True
|
is_open_ended = True
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import frappe
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
|
||||||
|
|
||||||
|
|
||||||
class LMSQuizSubmission(Document):
|
class LMSQuizSubmission(Document):
|
||||||
@@ -12,6 +13,9 @@ class LMSQuizSubmission(Document):
|
|||||||
self.validate_marks()
|
self.validate_marks()
|
||||||
self.set_percentage()
|
self.set_percentage()
|
||||||
|
|
||||||
|
def on_update(self):
|
||||||
|
self.notify_member()
|
||||||
|
|
||||||
def validate_marks(self):
|
def validate_marks(self):
|
||||||
for row in self.result:
|
for row in self.result:
|
||||||
if cint(row.marks) > cint(row.marks_out_of):
|
if cint(row.marks) > cint(row.marks_out_of):
|
||||||
@@ -26,3 +30,24 @@ class LMSQuizSubmission(Document):
|
|||||||
def set_percentage(self):
|
def set_percentage(self):
|
||||||
if self.score and self.score_out_of:
|
if self.score and self.score_out_of:
|
||||||
self.percentage = (self.score / self.score_out_of) * 100
|
self.percentage = (self.score / self.score_out_of) * 100
|
||||||
|
|
||||||
|
def notify_member(self):
|
||||||
|
if self.score != 0 and self.has_value_changed("score"):
|
||||||
|
notification = frappe._dict(
|
||||||
|
{
|
||||||
|
"subject": _("You have got a score of {0} for the quiz {1}").format(
|
||||||
|
self.score, self.quiz_title
|
||||||
|
),
|
||||||
|
"email_content": _(
|
||||||
|
"There has been an update on your submission. You have got a score of {0} for the quiz {1}"
|
||||||
|
).format(self.score, self.quiz_title),
|
||||||
|
"document_type": self.doctype,
|
||||||
|
"document_name": self.name,
|
||||||
|
"for_user": self.member,
|
||||||
|
"from_user": "Administrator",
|
||||||
|
"type": "Alert",
|
||||||
|
"link": "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
make_notification_logs(notification, [self.member])
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
"""Handy module to make access to all doctypes from a single place.
|
|
||||||
"""
|
|
||||||
from .doctype.lms_enrollment.lms_enrollment import (
|
|
||||||
LMSBatchMembership as Membership,
|
|
||||||
)
|
|
||||||
from .doctype.lms_course.lms_course import LMSCourse as Course
|
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from payments.utils import get_payment_gateway_controller
|
|
||||||
|
|
||||||
|
|
||||||
def get_payment_gateway():
|
def get_payment_gateway():
|
||||||
|
|
||||||
return frappe.db.get_single_value("LMS Settings", "payment_gateway")
|
return frappe.db.get_single_value("LMS Settings", "payment_gateway")
|
||||||
|
|
||||||
|
|
||||||
def get_controller(payment_gateway):
|
def get_controller(payment_gateway):
|
||||||
return get_payment_gateway_controller(payment_gateway)
|
if "payments" in frappe.get_installed_apps():
|
||||||
|
from payments.utils import get_payment_gateway_controller
|
||||||
|
|
||||||
|
return get_payment_gateway_controller(payment_gateway)
|
||||||
|
|
||||||
|
|
||||||
def validate_currency(payment_gateway, currency):
|
def validate_currency(payment_gateway, currency):
|
||||||
|
|||||||
@@ -6,11 +6,7 @@ import razorpay
|
|||||||
import requests
|
import requests
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result
|
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_result
|
||||||
from frappe.desk.doctype.notification_log.notification_log import (
|
from frappe.desk.doctype.notification_log.notification_log import make_notification_logs
|
||||||
make_notification_logs,
|
|
||||||
enqueue_create_notification,
|
|
||||||
get_title,
|
|
||||||
)
|
|
||||||
from frappe.desk.search import get_user_groups
|
from frappe.desk.search import get_user_groups
|
||||||
from frappe.desk.notifications import extract_mentions
|
from frappe.desk.notifications import extract_mentions
|
||||||
from frappe.utils import (
|
from frappe.utils import (
|
||||||
@@ -858,7 +854,8 @@ def get_telemetry_boot_info():
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def is_onboarding_complete():
|
def is_onboarding_complete():
|
||||||
if not has_course_moderator_role():
|
if not has_course_moderator_role():
|
||||||
return {"is_onboarded": False}
|
return {"is_onboarded": True}
|
||||||
|
|
||||||
course_created = frappe.db.a_row_exists("LMS Course")
|
course_created = frappe.db.a_row_exists("LMS Course")
|
||||||
chapter_created = frappe.db.a_row_exists("Course Chapter")
|
chapter_created = frappe.db.a_row_exists("Course Chapter")
|
||||||
lesson_created = frappe.db.a_row_exists("Course Lesson")
|
lesson_created = frappe.db.a_row_exists("Course Lesson")
|
||||||
@@ -1774,8 +1771,18 @@ def get_programs():
|
|||||||
"LMS Program Course", {"parent": program.name}, ["course"], order_by="idx"
|
"LMS Program Course", {"parent": program.name}, ["course"], order_by="idx"
|
||||||
)
|
)
|
||||||
program.courses = []
|
program.courses = []
|
||||||
for course in program_courses:
|
previous_progress = 0
|
||||||
program.courses.append(get_course_details(course.course))
|
for i, course in enumerate(program_courses):
|
||||||
|
details = get_course_details(course.course)
|
||||||
|
if i == 0:
|
||||||
|
details.eligible = True
|
||||||
|
elif previous_progress == 100:
|
||||||
|
details.eligible = True
|
||||||
|
else:
|
||||||
|
details.eligible = False
|
||||||
|
|
||||||
|
previous_progress = details.membership.progress if details.membership else 0
|
||||||
|
program.courses.append(details)
|
||||||
|
|
||||||
program.members = frappe.db.count("LMS Program Member", {"parent": program.name})
|
program.members = frappe.db.count("LMS Program Member", {"parent": program.name})
|
||||||
|
|
||||||
|
|||||||
@@ -94,4 +94,5 @@ lms.patches.v2_0.delete_certificate_request_notification #18-09-2024
|
|||||||
lms.patches.v2_0.add_course_statistics #21-10-2024
|
lms.patches.v2_0.add_course_statistics #21-10-2024
|
||||||
lms.patches.v2_0.give_discussions_permissions
|
lms.patches.v2_0.give_discussions_permissions
|
||||||
lms.patches.v2_0.delete_web_forms
|
lms.patches.v2_0.delete_web_forms
|
||||||
lms.patches.v2_0.update_desk_access_for_lms_roles
|
lms.patches.v2_0.update_desk_access_for_lms_roles
|
||||||
|
lms.patches.v2_0.update_quiz_submission_data
|
||||||
47
lms/patches/v2_0/update_quiz_submission_data.py
Normal file
47
lms/patches/v2_0/update_quiz_submission_data.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
set_question_data()
|
||||||
|
set_submission_data()
|
||||||
|
|
||||||
|
|
||||||
|
def set_question_data():
|
||||||
|
questions = frappe.get_all("LMS Quiz Question", fields=["name", "question"])
|
||||||
|
|
||||||
|
for question in questions:
|
||||||
|
question_doc = frappe.db.get_value(
|
||||||
|
"LMS Question", question.question, ["question", "type"], as_dict=1
|
||||||
|
)
|
||||||
|
frappe.db.set_value(
|
||||||
|
"LMS Quiz Question",
|
||||||
|
question.name,
|
||||||
|
{"question_detail": question_doc.question, "type": question_doc.type},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_submission_data():
|
||||||
|
submissions = frappe.get_all("LMS Quiz Submission", fields=["name", "quiz"])
|
||||||
|
|
||||||
|
for submission in submissions:
|
||||||
|
quiz_title = frappe.db.get_value("LMS Quiz", submission.quiz, "title")
|
||||||
|
frappe.db.set_value("LMS Quiz Submission", submission.name, "quiz_title", quiz_title)
|
||||||
|
|
||||||
|
questions = frappe.get_all(
|
||||||
|
"LMS Quiz Result", filters={"parent": submission.name}, fields=["question_name"]
|
||||||
|
)
|
||||||
|
|
||||||
|
for question in questions:
|
||||||
|
if question.question_name:
|
||||||
|
marks_out_of = frappe.db.get_value(
|
||||||
|
"LMS Quiz Question",
|
||||||
|
{"parent": submission.quiz, "question": question.question_name},
|
||||||
|
["marks"],
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.set_value(
|
||||||
|
"LMS Quiz Result",
|
||||||
|
{"parent": submission.name, "question_name": question.question_name},
|
||||||
|
"marks_out_of",
|
||||||
|
marks_out_of,
|
||||||
|
)
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import frappe
|
|
||||||
|
|
||||||
from lms.lms.utils import get_lesson_url, get_lessons, get_membership
|
|
||||||
from frappe.utils import cstr
|
|
||||||
from lms.lms.utils import redirect_to_courses_list
|
|
||||||
|
|
||||||
|
|
||||||
def get_common_context(context):
|
|
||||||
context.no_cache = 1
|
|
||||||
|
|
||||||
try:
|
|
||||||
batch_name = frappe.form_dict["batch"]
|
|
||||||
except KeyError:
|
|
||||||
batch_name = None
|
|
||||||
|
|
||||||
course = frappe.db.get_value(
|
|
||||||
"LMS Course",
|
|
||||||
frappe.form_dict["course"],
|
|
||||||
["name", "title", "video_link", "enable_certification", "status"],
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
if not course:
|
|
||||||
redirect_to_courses_list()
|
|
||||||
|
|
||||||
context.course = course
|
|
||||||
context.lessons = get_lessons(course.name)
|
|
||||||
membership = get_membership(course.name, frappe.session.user, batch_name)
|
|
||||||
context.membership = membership
|
|
||||||
context.progress = frappe.utils.cint(membership.progress) if membership else 0
|
|
||||||
context.batch_old = (
|
|
||||||
membership.batch_old if membership and membership.batch_old else None
|
|
||||||
)
|
|
||||||
context.course.query_parameter = (
|
|
||||||
"?batch=" + membership.batch_old if membership and membership.batch_old else ""
|
|
||||||
)
|
|
||||||
context.livecode_url = get_livecode_url()
|
|
||||||
|
|
||||||
|
|
||||||
def get_livecode_url():
|
|
||||||
return frappe.db.get_single_value("LMS Settings", "livecode_url")
|
|
||||||
|
|
||||||
|
|
||||||
def redirect_to_lesson(course, index_="1.1"):
|
|
||||||
frappe.local.flags.redirect_location = (
|
|
||||||
get_lesson_url(course.name, index_) + course.query_parameter
|
|
||||||
)
|
|
||||||
raise frappe.Redirect
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_lesson_details(lesson_number, context, is_edit=False):
|
|
||||||
details_list = list(filter(lambda x: cstr(x.number) == lesson_number, context.lessons))
|
|
||||||
|
|
||||||
if not len(details_list):
|
|
||||||
if is_edit:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
redirect_to_lesson(context.course)
|
|
||||||
|
|
||||||
lesson_info = details_list[0]
|
|
||||||
lesson_info.body = lesson_info.body.replace('"', "'")
|
|
||||||
return lesson_info
|
|
||||||
|
|
||||||
|
|
||||||
def is_student(batch, member=None):
|
|
||||||
if not member:
|
|
||||||
member = frappe.session.user
|
|
||||||
|
|
||||||
return frappe.db.exists(
|
|
||||||
"Batch Student",
|
|
||||||
{
|
|
||||||
"student": member,
|
|
||||||
"parent": batch,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
Reference in New Issue
Block a user