Merge pull request #1151 from frappe/develop

chore: merge 'develop' into 'main'
This commit is contained in:
Jannat Patel
2024-11-25 15:06:00 +05:30
committed by GitHub
43 changed files with 2416 additions and 963 deletions

View File

@@ -99,6 +99,7 @@ import { getSidebarLinks } from '../utils'
import { usersStore } from '@/stores/user'
import { sessionStore } from '@/stores/session'
import { useSidebar } from '@/stores/sidebar'
import { useSettings } from '@/stores/settings'
import { ChevronRight, Plus } from 'lucide-vue-next'
import { createResource, Button } from 'frappe-ui'
import PageModal from '@/components/Modals/PageModal.vue'
@@ -114,6 +115,7 @@ const isModerator = ref(false)
const isInstructor = ref(false)
const pageToEdit = ref(null)
const showWebPages = ref(false)
const settingsStore = useSettings()
onMounted(() => {
socket.on('publish_lms_notifications', (data) => {
@@ -183,6 +185,28 @@ const addQuizzes = () => {
}
}
const addPrograms = () => {
if (settingsStore.learningPaths.data) {
let activeFor = ['Programs', 'ProgramForm']
let index = 1
if (!isInstructor.value && !isModerator.value) {
sidebarLinks.value = sidebarLinks.value.filter(
(link) => link.label !== 'Courses'
)
activeFor.push('CourseDetail')
activeFor.push('Lesson')
index = 0
}
sidebarLinks.value.splice(index, 0, {
label: 'Programs',
icon: 'Route',
to: 'Programs',
activeFor: activeFor,
})
}
}
const openPageModal = (link) => {
showPageModal.value = true
pageToEdit.value = link
@@ -215,6 +239,7 @@ watch(userResource, () => {
isModerator.value = userResource.data.is_moderator
isInstructor.value = userResource.data.is_instructor
addQuizzes()
addPrograms()
}
})

View File

@@ -92,7 +92,7 @@
{{ option.label }}
</div>
<div
v-if="option.label != option.description"
v-if="option.description"
class="text-xs text-gray-700"
v-html="option.description"
></div>

View File

@@ -44,6 +44,7 @@
</div>
</template>
</Autocomplete>
<p v-if="description" class="text-sm text-gray-600">{{ description }}</p>
</div>
</template>
@@ -67,6 +68,10 @@ const props = defineProps({
type: String,
default: '',
},
description: {
type: String,
default: '',
},
})
const emit = defineEmits(['update:modelValue', 'change'])
@@ -118,7 +123,7 @@ const options = createResource({
transform: (data) => {
return data.map((option) => {
return {
label: option.value,
label: option.label || option.value,
value: option.value,
description: option.description,
}

View File

@@ -303,9 +303,9 @@ const trashChapter = (chapterName) => {
}
const redirectToChapter = (chapter) => {
if (!chapter.is_scorm_package) return
event.preventDefault()
if (props.allowEdit) return
if (!chapter.is_scorm_package) return
if (!user.data) {
showToast(
__('You are not enrolled'),

View File

@@ -8,6 +8,7 @@
<AppSidebar />
</div>
<div class="w-full overflow-auto" id="scrollContainer">
<OnboardingBanner />
<slot />
</div>
</div>
@@ -16,4 +17,5 @@
</template>
<script setup>
import AppSidebar from './AppSidebar.vue'
import OnboardingBanner from '@/components/OnboardingBanner.vue'
</script>

View File

@@ -17,10 +17,15 @@
<template #body-content>
<div class="space-y-4 text-base">
<FormControl label="Title" v-model="chapter.title" :required="true" />
<FormControl
:label="__('Is SCORM Package')"
<Switch
size="sm"
:label="__('SCORM Package')"
:description="
__(
'Enable this only if you want to upload a SCORM package as a chapter.'
)
"
v-model="chapter.is_scorm_package"
type="checkbox"
/>
<div v-if="chapter.is_scorm_package">
<FileUploader
@@ -70,14 +75,17 @@ import {
Dialog,
FileUploader,
FormControl,
Switch,
} from 'frappe-ui'
import { defineModel, reactive, watch, ref } from 'vue'
import { defineModel, reactive, watch } from 'vue'
import { showToast, getFileSize } from '@/utils/'
import { capture } from '@/telemetry'
import { FileText, X } from 'lucide-vue-next'
import { useSettings } from '@/stores/settings'
const show = defineModel()
const outline = defineModel('outline')
const settingsStore = useSettings()
const props = defineProps({
course: {
@@ -137,6 +145,9 @@ const addChapter = async (close) => {
{
onSuccess(data) {
cleanChapter()
if (!settingsStore.onboardingDetails.data?.is_onboarded) {
settingsStore.onboardingDetails.reload()
}
outline.value.reload()
showToast(
__('Success'),

View File

@@ -108,9 +108,31 @@ const tabsStructure = computed(() => {
hideLabel: true,
items: [
{
label: 'Members',
description: 'Manage the members of your learning system',
icon: 'UserRoundPlus',
label: 'General',
icon: 'Wrench',
fields: [
{
label: 'Enable Learning Paths',
name: 'enable_learning_paths',
description:
'This will enforce students to go through programs assigned to them in the correct order.',
type: 'checkbox',
},
{
label: 'Send calendar invite for evaluations',
name: 'send_calendar_invite_for_evaluations',
description:
'If enabled, it sends google calendar invite to the student for evaluations.',
type: 'checkbox',
},
{
label: 'Unsplash Access Key',
name: 'unsplash_access_key',
description:
'Optional. If this is set, students can pick a cover image from the unsplash library for their profile page. https://unsplash.com/documentation#getting-started.',
type: 'text',
},
],
},
],
},
@@ -156,9 +178,14 @@ const tabsStructure = computed(() => {
],
},
{
label: 'Settings',
hideLabel: true,
label: 'Lists',
hideLabel: false,
items: [
{
label: 'Members',
description: 'Manage the members of your learning system',
icon: 'UserRoundPlus',
},
{
label: 'Categories',
description: 'Manage the members of your learning system',

View File

@@ -0,0 +1,151 @@
<template>
<div v-if="showOnboardingBanner && onboardingDetails.data">
<Tooltip :text="__('Skip Onboarding')" placement="left">
<X
class="w-4 h-4 stroke-1 absolute top-2 right-2 cursor-pointer mr-1"
@click="skipOnboarding.reload()"
/>
</Tooltip>
<div class="flex items-center justify-evenly bg-gray-100 p-10">
<div
@click="redirectToCourseForm()"
class="flex items-center space-x-2"
:class="{
'cursor-pointer': !onboardingDetails.data.course_created.length,
}"
>
<span
v-if="onboardingDetails.data.course_created.length"
class="py-1 px-1 bg-white rounded-full"
>
<Check class="h-4 w-4 stroke-2 text-green-600" />
</span>
<span v-else class="font-semibold bg-white px-2 py-1 rounded-full">
1
</span>
<span class="text-lg font-semibold">
{{ __('Create a course') }}
</span>
</div>
<div
@click="redirectToChapterForm()"
class="flex items-center space-x-2"
:class="{
'cursor-pointer':
onboardingDetails.data.course_created.length &&
!onboardingDetails.data.chapter_created.length,
'text-gray-400': !onboardingDetails.data.course_created.length,
}"
>
<span
v-if="onboardingDetails.data.chapter_created.length"
class="py-1 px-1 bg-white rounded-full"
>
<Check class="h-4 w-4 stroke-2 text-green-600" />
</span>
<span v-else class="font-semibold bg-white px-2 py-1 rounded-full">
2
</span>
<span class="text-lg font-semibold">
{{ __('Add a chapter') }}
</span>
</div>
<div
@click="redirectToLessonForm()"
class="flex items-center space-x-2"
:class="{
'cursor-pointer':
onboardingDetails.data.course_created.length &&
onboardingDetails.data.chapter_created.length,
'text-gray-400':
!onboardingDetails.data.course_created.length ||
!onboardingDetails.data.chapter_created.length,
}"
>
<span
v-if="onboardingDetails.data.lesson_created.length"
class="py-1 px-1 bg-white rounded-full"
>
<Check class="h-4 w-4 stroke-2 text-green-600" />
</span>
<span class="font-semibold bg-white px-2 py-1 rounded-full"> 3 </span>
<span class="text-lg font-semibold">
{{ __('Add a lesson') }}
</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { Check, X } from 'lucide-vue-next'
import { useRouter } from 'vue-router'
import { useSettings } from '@/stores/settings'
import { createResource, Tooltip } from 'frappe-ui'
const showOnboardingBanner = ref(false)
const settings = useSettings()
const onboardingDetails = settings.onboardingDetails
const router = useRouter()
watch(onboardingDetails, () => {
if (!onboardingDetails.data?.is_onboarded) {
showOnboardingBanner.value = true
} else {
showOnboardingBanner.value = false
}
})
const redirectToCourseForm = () => {
if (onboardingDetails.data?.course_created.length) {
return
} else {
router.push({ name: 'CourseForm', params: { courseName: 'new' } })
}
}
const redirectToChapterForm = () => {
if (!onboardingDetails.data?.course_created.length) {
return
} else {
router.push({
name: 'CourseForm',
params: {
courseName: onboardingDetails.data?.first_course,
},
})
}
}
const redirectToLessonForm = () => {
if (!onboardingDetails.data?.course_created.length) {
return
} else if (!onboardingDetails.data?.chapter_created.length) {
return
} else {
router.push({
name: 'LessonForm',
params: {
courseName: onboardingDetails.data?.first_course,
chapterNumber: 1,
lessonNumber: 1,
},
})
}
}
const skipOnboarding = createResource({
url: 'frappe.client.set_value',
makeParams() {
return {
doctype: 'LMS Settings',
name: 'LMS Settings',
fieldname: 'is_onboarding_complete',
value: 1,
}
},
onSuccess(data) {
onboardingDetails.reload()
},
})
</script>

View File

@@ -397,6 +397,9 @@ const attempts = createResource({
watch(
() => quiz.data,
() => {
if (quiz.data) {
populateQuestions()
}
if (quiz.data && quiz.data.max_attempts) {
attempts.reload()
resetQuiz()

View File

@@ -29,6 +29,7 @@
<script setup>
import { Button, Badge } from 'frappe-ui'
import SettingFields from '@/components/SettingFields.vue'
import { showToast } from '@/utils'
const props = defineProps({
fields: {
@@ -54,7 +55,14 @@ const update = () => {
props.data.doc[f.name] = f.value
}
})
props.data.save.submit()
props.data.save.submit(
{},
{
onError(err) {
showToast('Error', err.messages?.[0] || err, 'x')
},
}
)
}
</script>

View File

@@ -90,6 +90,7 @@
:type="field.type"
:rows="field.rows"
:options="field.options"
:description="field.description"
/>
</div>
</div>
@@ -100,7 +101,7 @@
import { FormControl, FileUploader, Button, Switch } from 'frappe-ui'
import { computed } from 'vue'
import { getFileSize, validateFile } from '@/utils'
import { X, FileText } from 'lucide-vue-next'
import { X } from 'lucide-vue-next'
import Link from '@/components/Controls/Link.vue'
import CodeEditor from '@/components/Controls/CodeEditor.vue'

View File

@@ -434,6 +434,9 @@ const submitCourse = () => {
onSuccess(data) {
capture('course_created')
showToast('Success', 'Course created successfully', 'check')
if (!settingsStore.onboardingDetails.data?.is_onboarded) {
settingsStore.onboardingDetails.reload()
}
router.push({
name: 'CourseForm',
params: { courseName: data.name },

View File

@@ -160,30 +160,45 @@
<script setup>
import {
Breadcrumbs,
Tabs,
Badge,
Breadcrumbs,
Button,
FormControl,
call,
createResource,
FormControl,
Tabs,
} from 'frappe-ui'
import CourseCard from '@/components/CourseCard.vue'
import { BookOpen, Plus, Search } from 'lucide-vue-next'
import { ref, computed, inject, onMounted, watch } from 'vue'
import { updateDocumentTitle } from '@/utils'
import { useRouter } from 'vue-router'
import { useSettings } from '@/stores/settings'
const user = inject('$user')
const searchQuery = ref('')
const currentCategory = ref(null)
const hasCourses = ref(false)
const router = useRouter()
const settings = useSettings()
onMounted(() => {
checkLearningPath()
let queries = new URLSearchParams(location.search)
if (queries.has('category')) {
currentCategory.value = queries.get('category')
}
})
const checkLearningPath = () => {
if (
settings.learningPaths.data &&
(!user.data?.is_moderator || !user.data?.is_instructor)
) {
router.push({ name: 'Programs' })
}
}
const courses = createResource({
url: 'lms.lms.utils.get_courses',
cache: ['courses', user.data?.email],

View File

@@ -92,11 +92,13 @@ import LessonHelp from '@/components/LessonHelp.vue'
import { ChevronRight } from 'lucide-vue-next'
import { updateDocumentTitle, createToast, getEditorTools } from '@/utils'
import { capture } from '@/telemetry'
import { useSettings } from '@/stores/settings'
const editor = ref(null)
const instructorEditor = ref(null)
const user = inject('$user')
const openInstructorEditor = ref(false)
const settingsStore = useSettings()
let autoSaveInterval
let showSuccessMessage = false
@@ -393,6 +395,9 @@ const createNewLesson = () => {
onSuccess() {
capture('lesson_created')
showToast('Success', 'Lesson created successfully', 'check')
if (!settingsStore.onboardingDetails.data?.is_onboarded) {
settingsStore.onboardingDetails.reload()
}
lessonDetails.reload()
},
}

View File

@@ -0,0 +1,354 @@
<template>
<header
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" />
<Button variant="solid">
{{ __('Save') }}
</Button>
</header>
<div v-if="program.doc" class="pt-5 px-5 w-3/4 mx-auto space-y-10">
<FormControl v-model="program.doc.title" :label="__('Title')" />
<!-- Courses -->
<div>
<div class="flex items-center justify-between mb-2">
<div class="text-lg font-semibold">
{{ __('Program Courses') }}
</div>
<Button
@click="
() => {
currentForm = 'course'
showDialog = true
}
"
>
<template #prefix>
<Plus class="w-4 h-4" />
</template>
{{ __('Add') }}
</Button>
</div>
<ListView
:columns="courseColumns"
:rows="program.doc.program_courses"
row-key="name"
:options="{
showTooltip: false,
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded bg-gray-100 p-2"
>
<ListHeaderItem :item="item" v-for="item in courseColumns" />
</ListHeader>
<ListRows>
<Draggable
:list="program.doc.program_courses"
item-key="name"
group="items"
@end="updateOrder"
>
<template #item="{ element: row }">
<ListRow :row="row" />
</template>
</Draggable>
</ListRows>
<ListSelectBanner>
<template #actions="{ unselectAll, selections }">
<div class="flex gap-2">
<Button
variant="ghost"
@click="remove(selections, unselectAll, 'program_courses')"
>
<Trash2 class="h-4 w-4 stroke-1.5" />
</Button>
</div>
</template>
</ListSelectBanner>
</ListView>
</div>
<!-- Members -->
<div>
<div class="flex items-center justify-between mb-2">
<div class="text-lg font-semibold">
{{ __('Program Members') }}
</div>
<Button
@click="
() => {
currentForm = 'member'
showDialog = true
}
"
>
<template #prefix>
<Plus class="w-4 h-4" />
</template>
{{ __('Add') }}
</Button>
</div>
<ListView
:columns="memberColumns"
:rows="program.doc.program_members"
row-key="name"
:options="{
showTooltip: false,
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded bg-gray-100 p-2"
>
<ListHeaderItem :item="item" v-for="item in memberColumns" />
</ListHeader>
<ListRows>
<ListRow :row="row" v-for="row in program.doc.program_members" />
</ListRows>
<ListSelectBanner>
<template #actions="{ unselectAll, selections }">
<div class="flex gap-2">
<Button
variant="ghost"
@click="remove(selections, unselectAll, 'program_members')"
>
<Trash2 class="h-4 w-4 stroke-1.5" />
</Button>
</div>
</template>
</ListSelectBanner>
</ListView>
</div>
</div>
<Dialog
v-model="showDialog"
:options="{
title:
currentForm == 'course'
? __('New Program Course')
: __('New Program Member'),
actions: [
{
label: __('Add'),
variant: 'solid',
onClick: () =>
currentForm == 'course'
? addProgramCourse(close)
: addProgramMember(close),
},
],
}"
>
<template #body-content>
<Link
v-if="currentForm == 'course'"
v-model="course"
doctype="LMS Course"
:filters="{
disable_self_learning: 1,
}"
:label="__('Program Course')"
:description="
__(
'Only courses for which self learning is disabled can be added to program.'
)
"
/>
<Link
v-if="currentForm == 'member'"
v-model="member"
doctype="User"
:filters="{
ignore_user_type: 1,
}"
:label="__('Program Member')"
/>
</template>
</Dialog>
</template>
<script setup>
import {
Breadcrumbs,
Button,
call,
createDocumentResource,
Dialog,
FormControl,
ListView,
ListRows,
ListRow,
ListHeader,
ListHeaderItem,
ListSelectBanner,
} from 'frappe-ui'
import { computed, ref } from 'vue'
import { Plus, Trash2 } from 'lucide-vue-next'
import Link from '@/components/Controls/Link.vue'
import { showToast } from '@/utils/'
import Draggable from 'vuedraggable'
const showDialog = ref(false)
const currentForm = ref(null)
const course = ref(null)
const member = ref(null)
const props = defineProps({
programName: {
type: String,
required: true,
},
})
const program = createDocumentResource({
doctype: 'LMS Program',
name: props.programName,
auto: true,
cache: ['program', props.programName],
})
const addProgramCourse = () => {
program.setValue.submit(
{
program_courses: [
...program.doc.program_courses,
{ course: course.value },
],
},
{
onSuccess(data) {
showDialog.value = false
course.value = null
showToast(__('Success'), __('Course added to program'), 'check')
program.reload()
},
onError(err) {
showToast('Error', err.messages?.[0] || err, 'x')
},
}
)
}
const addProgramMember = () => {
program.setValue.submit(
{
program_members: [
...program.doc.program_members,
{ member: member.value },
],
},
{
onSuccess(data) {
showDialog.value = false
member.value = null
showToast(__('Success'), __('Member added to program'), 'check')
program.reload()
},
onError(err) {
showToast('Error', err.messages?.[0] || err, 'x')
},
}
)
}
const remove = (selections, unselectAll, doctype) => {
selections = Array.from(selections)
program.setValue.submit(
{
[doctype]: program.doc[doctype].filter(
(row) => !selections.includes(row.name)
),
},
{
onSuccess(data) {
unselectAll()
showToast(__('Success'), __('Items removed successfully'), 'check')
program.reload()
},
onError(err) {
showToast('Error', err.messages?.[0] || err, 'x')
},
}
)
}
const updateOrder = (e) => {
let sourceIdx = e.from.dataset.idx
let targetIdx = e.to.dataset.idx
let courses = program.doc.program_courses
courses.splice(targetIdx, 0, courses.splice(sourceIdx, 1)[0])
courses.forEach((course, index) => {
course.idx = index + 1
})
program.setValue.submit(
{
program_courses: courses,
},
{
onSuccess(data) {
showToast(__('Success'), __('Course moved successfully'), 'check')
program.reload()
},
onError(err) {
showToast('Error', err.messages?.[0] || err, 'x')
},
}
)
}
const courseColumns = computed(() => {
return [
{
label: 'Title',
key: 'course_title',
width: 3,
},
{
label: 'ID',
key: 'course',
width: 3,
},
]
})
const memberColumns = computed(() => {
return [
{
label: 'Member',
key: 'member',
width: 3,
align: 'left',
},
{
label: 'Full Name',
key: 'full_name',
width: 3,
align: 'left',
},
{
label: 'Progress',
key: 'progress',
width: 3,
align: 'left',
},
]
})
const breadbrumbs = computed(() => {
return [
{
label: 'Programs',
route: { name: 'Programs' },
},
{
label: props.programName === 'new' ? 'New Program' : props.programName,
},
]
})
</script>

View File

@@ -0,0 +1,185 @@
<template>
<header
class="sticky top-0 z-10 flex items-center justify-between border-b bg-white px-3 py-2.5 sm:px-5"
>
<Breadcrumbs :items="breadbrumbs" />
<Button
v-if="user.data?.is_moderator || user.data?.is_instructor"
@click="showDialog = true"
variant="solid"
>
<template #prefix>
<Plus class="h-4 w-4 stroke-1.5" />
</template>
{{ __('New') }}
</Button>
</header>
<div v-if="programs.data?.length" class="pt-5 px-5">
<div v-for="program in programs.data" class="mb-20">
<div class="flex items-center justify-between">
<div class="text-xl font-semibold">
{{ program.name }}
</div>
<div class="flex items-center space-x-2">
<Badge
v-if="program.members"
variant="subtle"
theme="green"
size="lg"
>
{{ program.members }}
{{
program.members == 1 ? __(singularize('members')) : __('members')
}}
</Badge>
<Badge
v-if="program.progress"
variant="subtle"
theme="blue"
size="lg"
>
{{ program.progress }}{{ __('% completed') }}
</Badge>
<router-link
v-if="user.data?.is_moderator || user.data?.is_instructor"
:to="{
name: 'ProgramForm',
params: { programName: program.name },
}"
>
<Button>
<template #prefix>
<Edit class="h-4 w-4 stroke-1.5" />
</template>
{{ __('Edit') }}
</Button>
</router-link>
</div>
</div>
<div
v-if="program.courses?.length"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 mt-5"
>
<CourseCard
v-for="course in program.courses"
:course="course"
@click="enrollMember(program.name, course.name)"
class="cursor-pointer"
/>
</div>
<div v-else class="text-sm italic text-gray-600 mt-4">
{{ __('No courses in this program') }}
</div>
</div>
</div>
<div
v-else
class="text-center p-5 text-gray-600 mt-52 w-3/4 md:w-1/2 mx-auto space-y-2"
>
<BookOpen class="size-10 mx-auto stroke-1 text-gray-500" />
<div class="text-xl font-medium">
{{ __('No programs found') }}
</div>
<div class="leading-5">
{{
__(
'There are no programs available at the moment. Keep an eye out, fresh learning experiences are on the way soon!'
)
}}
</div>
</div>
<Dialog
v-model="showDialog"
:options="{
title: __('New Program'),
actions: [
{
label: __('Create'),
variant: 'solid',
onClick: () => createProgram(close),
},
],
}"
>
<template #body-content>
<FormControl :label="__('Title')" v-model="title" />
</template>
</Dialog>
</template>
<script setup>
import {
Badge,
Breadcrumbs,
Button,
call,
createResource,
Dialog,
FormControl,
} from 'frappe-ui'
import { computed, inject, ref } from 'vue'
import { BookOpen, Edit, Plus } from 'lucide-vue-next'
import CourseCard from '@/components/CourseCard.vue'
import { useRouter } from 'vue-router'
import { showToast, singularize } from '@/utils'
const user = inject('$user')
const showDialog = ref(false)
const router = useRouter()
const title = ref('')
const programs = createResource({
url: 'lms.lms.utils.get_programs',
auto: true,
cache: 'programs',
})
const createProgram = (close) => {
call('frappe.client.insert', {
doc: {
doctype: 'LMS Program',
title: title.value,
},
}).then((res) => {
router.push({ name: 'ProgramForm', params: { programName: res.name } })
})
}
const enrollMember = (program, course) => {
call('lms.lms.utils.enroll_in_program_course', {
program: program,
course: course,
})
.then((data) => {
if (data.current_lesson) {
router.push({
name: 'Lesson',
params: {
courseName: course,
chapterNumber: data.current_lesson.split('-')[0],
lessonNumber: data.current_lesson.split('-')[1],
},
})
} else if (data) {
router.push({
name: 'Lesson',
params: {
courseName: course,
chapterNumber: 1,
lessonNumber: 1,
},
})
}
})
.catch((err) => {
showToast('Error', err.messages?.[0] || err, 'x')
})
}
const breadbrumbs = computed(() => [
{
label: 'Programs',
},
])
</script>

View File

@@ -182,6 +182,17 @@ const routes = [
component: () => import('@/pages/QuizSubmission.vue'),
props: true,
},
{
path: '/programs/:programName',
name: 'ProgramForm',
component: () => import('@/pages/ProgramForm.vue'),
props: true,
},
{
path: '/programs',
name: 'Programs',
component: () => import('@/pages/Programs.vue'),
},
]
let router = createRouter({

View File

@@ -1,12 +1,32 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { createResource } from 'frappe-ui'
export const useSettings = defineStore('settings', () => {
const isSettingsOpen = ref(false)
const activeTab = ref(null)
const learningPaths = createResource({
url: 'frappe.client.get_single_value',
makeParams(values) {
return {
doctype: 'LMS Settings',
field: 'enable_learning_paths',
}
},
auto: true,
cache: ['learningPaths'],
})
const onboardingDetails = createResource({
url: 'lms.lms.utils.is_onboarding_complete',
auto: true,
cache: ['onboardingDetails'],
})
return {
isSettingsOpen,
activeTab,
learningPaths,
onboardingDetails,
}
})

View File

@@ -1 +1 @@
__version__ = "2.12.0"
__version__ = "2.13.0"

View File

@@ -66,7 +66,9 @@ def delete_lms_roles():
def create_course_creator_role():
if not frappe.db.exists("Role", "Course Creator"):
if frappe.db.exists("Role", "Course Creator"):
frappe.db.set_value("Role", "Course Creator", "desk_access", 0)
else:
role = frappe.get_doc(
{
"doctype": "Role",
@@ -79,7 +81,9 @@ def create_course_creator_role():
def create_moderator_role():
if not frappe.db.exists("Role", "Moderator"):
if frappe.db.exists("Role", "Moderator"):
frappe.db.set_value("Role", "Moderator", "desk_access", 0)
else:
role = frappe.get_doc(
{
"doctype": "Role",
@@ -92,7 +96,9 @@ def create_moderator_role():
def create_evaluator_role():
if not frappe.db.exists("Role", "Batch Evaluator"):
if frappe.db.exists("Role", "Batch Evaluator"):
frappe.db.set_value("Role", "Batch Evaluator", "desk_access", 0)
else:
role = frappe.new_doc("Role")
role.update(
{
@@ -105,7 +111,9 @@ def create_evaluator_role():
def create_lms_student_role():
if not frappe.db.exists("Role", "LMS Student"):
if frappe.db.exists("Role", "LMS Student"):
frappe.db.set_value("Role", "LMS Student", "desk_access", 0)
else:
role = frappe.new_doc("Role")
role.update(
{

View File

@@ -93,15 +93,15 @@ def save_progress(lesson, course):
frappe.db.set_value("LMS Enrollment", membership, "current_lesson", lesson)
quiz_completed = get_quiz_progress(lesson)
if not quiz_completed:
return 0
if frappe.db.exists(
"LMS Course Progress", {"lesson": lesson, "member": frappe.session.user}
):
return
quiz_completed = get_quiz_progress(lesson)
if not quiz_completed:
return 0
frappe.get_doc(
{
"doctype": "LMS Course Progress",

View File

@@ -53,7 +53,7 @@ class LMSCourse(Document):
frappe.throw(_("Please install the Payments app to create a paid courses."))
def validate_amount_and_currency(self):
if self.paid_course and (not self.amount and not self.currency):
if self.paid_course and (not self.course_price and not self.currency):
frappe.throw(_("Amount and currency are required for paid courses."))
def on_update(self):

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import ceil
class LMSEnrollment(Document):
@@ -11,6 +12,9 @@ class LMSEnrollment(Document):
self.validate_membership_in_same_batch()
self.validate_membership_in_different_batch_same_course()
def on_update(self):
self.update_program_progress()
def validate_membership_in_same_batch(self):
filters = {"member": self.member, "course": self.course, "name": ["!=", self.name]}
if self.batch_old:
@@ -55,6 +59,26 @@ class LMSEnrollment(Document):
)
)
def update_program_progress(self):
programs = frappe.get_all(
"LMS Program Member", {"member": self.member}, ["parent", "name"]
)
for program in programs:
total_progress = 0
courses = frappe.get_all(
"LMS Program Course", {"parent": program.parent}, pluck="course"
)
for course in courses:
progress = frappe.db.get_value(
"LMS Enrollment", {"course": course, "member": self.member}, "progress"
)
progress = progress or 0
total_progress += progress
average_progress = ceil(total_progress / len(courses))
frappe.db.set_value("LMS Program Member", program.name, "progress", average_progress)
@frappe.whitelist()
def create_membership(

View File

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2024, Frappe and contributors
// For license information, please see license.txt
// frappe.ui.form.on("LMS Program", {
// refresh(frm) {
// },
// });

View File

@@ -0,0 +1,84 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:title",
"creation": "2024-11-18 12:27:13.283169",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"title",
"program_courses",
"program_members"
],
"fields": [
{
"fieldname": "program_courses",
"fieldtype": "Table",
"label": "Program Courses",
"options": "LMS Program Course"
},
{
"fieldname": "program_members",
"fieldtype": "Table",
"label": "Program Members",
"options": "LMS Program Member"
},
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1,
"unique": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-11-20 12:26:02.214628",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Program",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Moderator",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Course Creator",
"share": 1,
"write": 1
}
],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,32 @@
# Copyright (c) 2024, Frappe and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document
class LMSProgram(Document):
def validate(self):
self.validate_program_courses()
self.validate_program_members()
def validate_program_courses(self):
courses = [row.course for row in self.program_courses]
duplicates = {course for course in courses if courses.count(course) > 1}
if len(duplicates):
frappe.throw(
_("Course {0} has already been added to this batch.").format(
frappe.bold(next(iter(duplicates)))
)
)
def validate_program_members(self):
members = [row.member for row in self.program_members]
duplicates = {member for member in members if members.count(member) > 1}
if len(duplicates):
frappe.throw(
_("Member {0} has already been added to this batch.").format(
frappe.bold(next(iter(duplicates)))
)
)

View File

@@ -0,0 +1,21 @@
# Copyright (c) 2024, Frappe and Contributors
# See license.txt
# import frappe
from frappe.tests import IntegrationTestCase, UnitTestCase
# On IntegrationTestCase, the doctype test records and all
# link-field test record depdendencies are recursively loaded
# Use these module variables to add/remove to/from that list
EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"]
class TestLMSProgram(UnitTestCase):
"""
Unit tests for LMSProgram.
Use this class for testing individual functions and methods.
"""
pass

View File

@@ -0,0 +1,42 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-11-18 12:27:37.030302",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"course",
"course_title"
],
"fields": [
{
"fieldname": "course",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Course",
"options": "LMS Course",
"reqd": 1
},
{
"fetch_from": "course.title",
"fieldname": "course_title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Course Title",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-11-18 12:43:46.800199",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Program Course",
"owner": "Administrator",
"permissions": [],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2024, Frappe and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LMSProgramCourse(Document):
pass

View File

@@ -0,0 +1,50 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-11-18 12:29:13.615014",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"member",
"full_name",
"progress"
],
"fields": [
{
"fieldname": "member",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Member",
"options": "User",
"reqd": 1
},
{
"fetch_from": "member.full_name",
"fieldname": "full_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Full Name",
"read_only": 1
},
{
"default": "0",
"fieldname": "progress",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Progress"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-11-21 12:51:31.882576",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Program Member",
"owner": "Administrator",
"permissions": [],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2024, Frappe and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LMSProgramMember(Document):
pass

View File

@@ -5,13 +5,15 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"general_tab",
"default_home",
"send_calendar_invite_for_evaluations",
"is_onboarding_complete",
"column_break_zdel",
"enable_learning_paths",
"unsplash_access_key",
"livecode_url",
"section_break_szgq",
"send_calendar_invite_for_evaluations",
"show_day_view",
"column_break_2",
"show_dashboard",
@@ -80,6 +82,7 @@
{
"fieldname": "mentor_request_section",
"fieldtype": "Section Break",
"hidden": 1,
"label": "Mentor Request"
},
{
@@ -127,6 +130,7 @@
{
"fieldname": "section_break_szgq",
"fieldtype": "Section Break",
"hidden": 1,
"label": "Batch Settings"
},
{
@@ -336,12 +340,23 @@
"fieldname": "payments_app_is_not_installed",
"fieldtype": "HTML",
"label": "Payments app is not installed"
},
{
"default": "0",
"fieldname": "enable_learning_paths",
"fieldtype": "Check",
"label": "Enable Learning Paths"
},
{
"fieldname": "general_tab",
"fieldtype": "Tab Break",
"label": "General"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-10-01 12:15:49.800242",
"modified": "2024-11-20 11:55:05.358421",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS Settings",
@@ -356,6 +371,13 @@
"role": "System Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "LMS Student",
"share": 1
}
],
"sort_field": "modified",

View File

@@ -855,7 +855,10 @@ def get_telemetry_boot_info():
}
@frappe.whitelist()
def is_onboarding_complete():
if not has_course_moderator_role():
return {"is_onboarded": False}
course_created = frappe.db.a_row_exists("LMS Course")
chapter_created = frappe.db.a_row_exists("Course Chapter")
lesson_created = frappe.db.a_row_exists("Course Lesson")
@@ -1751,3 +1754,81 @@ def enroll_in_batch(batch, payment_name=None):
)
student.save(ignore_permissions=True)
@frappe.whitelist()
def get_programs():
if (
has_course_moderator_role()
or has_course_instructor_role()
or has_course_evaluator_role()
):
programs = frappe.get_all("LMS Program", fields=["name"])
else:
programs = frappe.get_all(
"LMS Program Member", {"member": frappe.session.user}, ["parent as name", "progress"]
)
for program in programs:
program_courses = frappe.get_all(
"LMS Program Course", {"parent": program.name}, ["course"], order_by="idx"
)
program.courses = []
for course in program_courses:
program.courses.append(get_course_details(course.course))
program.members = frappe.db.count("LMS Program Member", {"parent": program.name})
return programs
@frappe.whitelist()
def enroll_in_program_course(program, course):
enrollment = frappe.db.exists(
"LMS Enrollment", {"member": frappe.session.user, "course": course}
)
if enrollment:
enrollment = frappe.db.get_value(
"LMS Enrollment", enrollment, ["name", "current_lesson"], as_dict=1
)
enrollment.current_lesson = get_lesson_index(enrollment.current_lesson)
return enrollment
program_courses = frappe.get_all(
"LMS Program Course", {"parent": program}, ["course", "idx"], order_by="idx"
)
current_course_idx = [
program_course.idx
for program_course in program_courses
if program_course.course == course
][0]
for program_course in program_courses:
if program_course.idx < current_course_idx:
enrollment = frappe.db.get_value(
"LMS Enrollment",
{"member": frappe.session.user, "course": program_course.course},
["name", "progress"],
as_dict=1,
)
if enrollment and enrollment.progress != 100:
frappe.throw(
_("Please complete the previous courses in the program to enroll in this course.")
)
elif not enrollment:
frappe.throw(
_("Please complete the previous courses in the program to enroll in this course.")
)
else:
continue
enrollment = frappe.new_doc("LMS Enrollment")
enrollment.update(
{
"member": frappe.session.user,
"course": course,
}
)
enrollment.save()
return enrollment

View File

@@ -1,4 +1,5 @@
{
"app": "lms",
"charts": [
{
"chart_name": "New Signups",
@@ -145,7 +146,7 @@
"type": "Link"
}
],
"modified": "2024-08-09 13:19:06.273056",
"modified": "2024-11-21 12:16:25.886431",
"modified_by": "Administrator",
"module": "LMS",
"name": "LMS",
@@ -212,5 +213,6 @@
"type": "DocType"
}
],
"title": "LMS"
}
"title": "LMS",
"type": "Workspace"
}

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: jannat@frappe.io\n"
"POT-Creation-Date: 2024-11-15 16:04+0000\n"
"PO-Revision-Date: 2024-11-18 17:32\n"
"PO-Revision-Date: 2024-11-23 18:16\n"
"Last-Translator: jannat@frappe.io\n"
"Language-Team: Persian\n"
"MIME-Version: 1.0\n"
@@ -183,7 +183,7 @@ msgstr "آدرس خط 2"
#: lms/lms/doctype/cohort_web_page/cohort_web_page.json
#: lms/lms/doctype/lms_enrollment/lms_enrollment.json
msgid "Admin"
msgstr "اَدمین"
msgstr "ادمین"
#. Name of a role
#: lms/lms/doctype/lms_badge/lms_badge.json
@@ -729,11 +729,11 @@ msgstr ""
#: frontend/src/components/Modals/ChapterModal.vue:149
msgid "Chapter added successfully"
msgstr ""
msgstr "فصل با موفقیت اضافه شد"
#: frontend/src/components/Modals/ChapterModal.vue:193
msgid "Chapter updated successfully"
msgstr ""
msgstr "فصل با موفقیت به روز شد"
#. Label of the chapters (Table) field in DocType 'LMS Course'
#. Group in LMS Course's connections
@@ -1157,7 +1157,7 @@ msgstr "اسم دوره"
#: frontend/src/pages/CourseDetail.vue:74
msgid "Course Outline"
msgstr ""
msgstr "طرح کلی دوره"
#. Label of the course_price (Currency) field in DocType 'LMS Course'
#: frontend/src/pages/CourseForm.vue:219
@@ -1197,7 +1197,7 @@ msgstr "دوره قبلاً به دسته اضافه شده است."
#: frontend/src/pages/CourseForm.vue:457
msgid "Course deleted successfully"
msgstr ""
msgstr "دوره با موفقیت حذف شد"
#: lms/lms/doctype/lms_batch/lms_batch.py:58
msgid "Course {0} has already been added to this batch."
@@ -1366,19 +1366,19 @@ msgstr "حذف"
#: frontend/src/components/CourseOutline.vue:52
msgid "Delete Chapter"
msgstr ""
msgstr "حذف فصل"
#: frontend/src/pages/CourseForm.vue:464
msgid "Delete Course"
msgstr ""
msgstr "حذف دوره"
#: frontend/src/components/CourseOutline.vue:287
msgid "Delete this chapter?"
msgstr ""
msgstr "این فصل حذف شود؟"
#: frontend/src/components/CourseOutline.vue:229
msgid "Delete this lesson?"
msgstr ""
msgstr "این درس حذف شود؟"
#: frontend/src/pages/CourseForm.vue:465
msgid "Deleting the course will also delete all its chapters and lessons. Are you sure you want to delete this course?"
@@ -1386,11 +1386,11 @@ msgstr ""
#: frontend/src/components/CourseOutline.vue:288
msgid "Deleting this chapter will also delete all its lessons and permanently remove it from the course. This action cannot be undone. Are you sure you want to continue?"
msgstr ""
msgstr "با حذف این فصل، تمام دروس آن نیز حذف می شود و برای همیشه از دوره حذف می شود. این عمل قابل بازگشت نیست. آیا مطمئن هستید که می خواهید ادامه دهید؟"
#: frontend/src/components/CourseOutline.vue:230
msgid "Deleting this lesson will permanently remove it from the course. This action cannot be undone. Are you sure you want to continue?"
msgstr ""
msgstr "حذف این درس آن را برای همیشه از دوره حذف می کند. این عمل قابل بازگشت نیست. آیا مطمئن هستید که می خواهید ادامه دهید؟"
#. Label of the description (Text Editor) field in DocType 'Job Opportunity'
#. Label of the description (Small Text) field in DocType 'Certification'
@@ -1763,7 +1763,7 @@ msgstr "رویداد"
#: frontend/src/pages/BatchForm.vue:156
msgid "Example: IST (+5:30)"
msgstr ""
msgstr "مثال: IST (+5:30)"
#. Label of the exercise (Link) field in DocType 'Exercise Latest Submission'
#. Label of the exercise (Link) field in DocType 'Exercise Submission'
@@ -3287,7 +3287,7 @@ msgstr ""
#: frontend/src/components/Modals/ChapterModal.vue:223
msgid "Only zip files are allowed"
msgstr ""
msgstr "فقط فایل های zip مجاز هستند"
#. Option for the 'Status' (Select) field in DocType 'Job Opportunity'
#. Option for the 'Membership' (Select) field in DocType 'LMS Batch Old'
@@ -3551,7 +3551,7 @@ msgstr ""
#: lms/overrides/user.py:240
msgid "Please ask your administrator to verify your sign-up"
msgstr "لطفاً از سرپرست خود بخواهید ثبت نام شما را تأیید کند"
msgstr "لطفاً از ادمین خود بخواهید ثبت نام شما را تأیید کند"
#: lms/overrides/user.py:238
msgid "Please check your email for verification"
@@ -3567,7 +3567,7 @@ msgstr ""
#: frontend/src/components/CourseOutline.vue:312
msgid "Please enroll for this course to view this lesson"
msgstr ""
msgstr "لطفا برای مشاهده این درس در این دوره ثبت نام کنید"
#: frontend/src/components/Quiz.vue:13
msgid "Please ensure that you complete all the questions in {0} minutes."
@@ -4778,7 +4778,7 @@ msgstr ""
#: frontend/src/pages/SCORMChapter.vue:198
msgid "This is a chapter in the course {0}"
msgstr ""
msgstr "این یک فصل از دوره {0} است"
#: lms/lms/widgets/CourseOutline.html:62
msgid "This lesson is not available for preview. As you are the Instructor of the course only you can see it."
@@ -4898,7 +4898,7 @@ msgstr "عنوان"
#: frontend/src/components/Modals/ChapterModal.vue:169
msgid "Title is required"
msgstr ""
msgstr "عنوان الزامی است"
#. Label of the unavailable_to (Date) field in DocType 'Course Evaluator'
#: frontend/src/pages/ProfileEvaluator.vue:98
@@ -5253,11 +5253,11 @@ msgstr ""
#: frontend/src/components/CourseOutline.vue:311
msgid "You are not enrolled"
msgstr ""
msgstr "شما ثبت نام نکرده اید"
#: frontend/src/pages/SCORMChapter.vue:22
msgid "You are not enrolled in this course. Please enroll to access this lesson."
msgstr ""
msgstr "شما در این دوره ثبت نام نکرده اید. لطفا برای دسترسی به این دوره ثبت نام کنید."
#: frontend/src/pages/Courses.vue:134
msgid "You can add chapters and lessons to it."
@@ -5489,7 +5489,7 @@ msgstr ""
#: lms/lms/doctype/lms_certificate/lms_certificate.py:59
msgid "{0} is already certified for the course {1}"
msgstr ""
msgstr "{0} قبلاً برای دوره {1} تایید شده است"
#: lms/lms/notification/certificate_request_reminder/certificate_request_reminder.html:5
msgid "{0} is your evaluator"

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Frappe LMS VERSION\n"
"Report-Msgid-Bugs-To: jannat@frappe.io\n"
"POT-Creation-Date: 2024-11-15 16:04+0000\n"
"PO-Revision-Date: 2024-11-15 16:04+0000\n"
"POT-Creation-Date: 2024-11-22 16:05+0000\n"
"PO-Revision-Date: 2024-11-22 16:05+0000\n"
"Last-Translator: jannat@frappe.io\n"
"Language-Team: jannat@frappe.io\n"
"MIME-Version: 1.0\n"
@@ -20,6 +20,11 @@ msgstr ""
msgid " Please evaluate and grade it."
msgstr ""
#: frontend/src/pages/Programs.vue:41
#, python-format
msgid "% completed"
msgstr ""
#. Paragraph text in the LMS Workspace
#: lms/lms/workspace/lms/lms.json
msgid "<a href=\"/app/lms-settings/LMS%20Settings\">LMS Settings</a>"
@@ -97,7 +102,8 @@ msgstr ""
#: frontend/src/components/BatchStudents.vue:6
#: frontend/src/components/Categories.vue:26
#: frontend/src/components/LiveClass.vue:11
#: frontend/src/components/Members.vue:43
#: frontend/src/components/Members.vue:43 frontend/src/pages/ProgramForm.vue:30
#: frontend/src/pages/ProgramForm.vue:91 frontend/src/pages/ProgramForm.vue:136
msgid "Add"
msgstr ""
@@ -127,12 +133,20 @@ msgstr ""
msgid "Add a Student"
msgstr ""
#: frontend/src/components/OnboardingBanner.vue:50
msgid "Add a chapter"
msgstr ""
#: frontend/src/components/Modals/BatchCourseModal.vue:5
msgid "Add a course"
msgstr ""
#: frontend/src/components/OnboardingBanner.vue:73
msgid "Add a lesson"
msgstr ""
#: frontend/src/components/Modals/Question.vue:141
#: frontend/src/pages/QuizForm.vue:181
#: frontend/src/pages/QuizForm.vue:182
msgid "Add a new question"
msgstr ""
@@ -148,7 +162,7 @@ msgstr ""
msgid "Add an existing question"
msgstr ""
#: lms/lms/doctype/lms_question/lms_question.py:60
#: lms/lms/doctype/lms_question/lms_question.py:66
msgid "Add at least one possible answer for this question: {0}"
msgstr ""
@@ -251,6 +265,14 @@ msgstr ""
msgid "Amount Field"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:70
msgid "Amount and currency are required for paid batches."
msgstr ""
#: lms/lms/doctype/lms_course/lms_course.py:57
msgid "Amount and currency are required for paid courses."
msgstr ""
#. Label of the amount_with_gst (Currency) field in DocType 'LMS Payment'
#: lms/lms/doctype/lms_payment/lms_payment.json
msgid "Amount with GST"
@@ -344,7 +366,7 @@ msgstr ""
msgid "Assessment added successfully"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:75
#: lms/lms/doctype/lms_batch/lms_batch.py:80
msgid "Assessment {0} has already been added to this batch."
msgstr ""
@@ -395,7 +417,7 @@ msgstr ""
msgid "Assignment will appear at the bottom of the lesson."
msgstr ""
#: lms/lms/doctype/lms_question/lms_question.py:42
#: lms/lms/doctype/lms_question/lms_question.py:43
msgid "At least one option must be correct for this question."
msgstr ""
@@ -538,7 +560,7 @@ msgstr ""
msgid "Batch Updated"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:40
#: lms/lms/doctype/lms_batch/lms_batch.py:41
msgid "Batch end date cannot be before the batch start date"
msgstr ""
@@ -725,11 +747,11 @@ msgstr ""
msgid "Chapter Reference"
msgstr ""
#: frontend/src/components/Modals/ChapterModal.vue:149
#: frontend/src/components/Modals/ChapterModal.vue:154
msgid "Chapter added successfully"
msgstr ""
#: frontend/src/components/Modals/ChapterModal.vue:193
#: frontend/src/components/Modals/ChapterModal.vue:198
msgid "Chapter updated successfully"
msgstr ""
@@ -762,7 +784,7 @@ msgstr ""
msgid "Choices"
msgstr ""
#: frontend/src/components/Quiz.vue:578 lms/templates/quiz/quiz.html:53
#: frontend/src/components/Quiz.vue:581 lms/templates/quiz/quiz.html:53
msgid "Choose all answers that apply"
msgstr ""
@@ -770,7 +792,7 @@ msgstr ""
msgid "Choose an icon"
msgstr ""
#: frontend/src/components/Quiz.vue:579 lms/templates/quiz/quiz.html:53
#: frontend/src/components/Quiz.vue:582 lms/templates/quiz/quiz.html:53
msgid "Choose one answer"
msgstr ""
@@ -986,7 +1008,7 @@ msgid "Contact the Administrator to enroll for this course."
msgstr ""
#. Label of the content (Text) field in DocType 'Course Lesson'
#: frontend/src/pages/LessonForm.vue:58
#: frontend/src/pages/LessonForm.vue:62
#: lms/lms/doctype/course_lesson/course_lesson.json
msgid "Content"
msgstr ""
@@ -1055,6 +1077,7 @@ msgstr ""
#. Label of the course (Link) field in DocType 'LMS Enrollment'
#. Label of the course (Link) field in DocType 'LMS Exercise'
#. Label of the course (Link) field in DocType 'LMS Mentor Request'
#. Label of the course (Link) field in DocType 'LMS Program Course'
#. Label of the course (Link) field in DocType 'LMS Quiz'
#. Label of the course (Link) field in DocType 'LMS Quiz Submission'
#. Label of the course (Link) field in DocType 'Related Courses'
@@ -1084,6 +1107,7 @@ msgstr ""
#: lms/lms/doctype/lms_enrollment/lms_enrollment.json
#: lms/lms/doctype/lms_exercise/lms_exercise.json
#: lms/lms/doctype/lms_mentor_request/lms_mentor_request.json
#: lms/lms/doctype/lms_program_course/lms_program_course.json
#: lms/lms/doctype/lms_quiz/lms_quiz.json
#: lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json
#: lms/lms/doctype/related_courses/related_courses.json
@@ -1112,6 +1136,7 @@ msgstr ""
#. Name of a role
#: frontend/src/pages/ProfileRoles.vue:16
#: lms/lms/doctype/lms_course/lms_course.json
#: lms/lms/doctype/lms_program/lms_program.json
#: lms/lms/doctype/lms_question/lms_question.json
#: lms/lms/doctype/lms_quiz/lms_quiz.json
msgid "Course Creator"
@@ -1182,22 +1207,33 @@ msgstr ""
#. Label of the course_title (Data) field in DocType 'Course Chapter'
#. Label of the course_title (Data) field in DocType 'LMS Certificate'
#. Label of the course_title (Data) field in DocType 'LMS Certificate Request'
#. Label of the course_title (Data) field in DocType 'LMS Program Course'
#: lms/lms/doctype/batch_course/batch_course.json
#: lms/lms/doctype/course_chapter/course_chapter.json
#: lms/lms/doctype/lms_certificate/lms_certificate.json
#: lms/lms/doctype/lms_certificate_request/lms_certificate_request.json
#: lms/lms/doctype/lms_program_course/lms_program_course.json
msgid "Course Title"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:340
#: frontend/src/pages/ProgramForm.vue:226
msgid "Course added to program"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:345
msgid "Course already added to the batch."
msgstr ""
#: frontend/src/pages/CourseForm.vue:457
#: frontend/src/pages/CourseForm.vue:460
msgid "Course deleted successfully"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:58
#: frontend/src/pages/ProgramForm.vue:295
msgid "Course moved successfully"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:59
#: lms/lms/doctype/lms_program/lms_program.py:19
msgid "Course {0} has already been added to this batch."
msgstr ""
@@ -1232,6 +1268,7 @@ msgid "Cover Image"
msgstr ""
#: frontend/src/components/Modals/ChapterModal.vue:9
#: frontend/src/pages/Programs.vue:99
msgid "Create"
msgstr ""
@@ -1247,7 +1284,7 @@ msgstr ""
msgid "Create a Batch"
msgstr ""
#: frontend/src/pages/Courses.vue:131 lms/templates/onboarding_header.html:19
#: frontend/src/pages/Courses.vue:132 lms/templates/onboarding_header.html:19
msgid "Create a Course"
msgstr ""
@@ -1255,6 +1292,10 @@ msgstr ""
msgid "Create a Live Class"
msgstr ""
#: frontend/src/components/OnboardingBanner.vue:27
msgid "Create a course"
msgstr ""
#: frontend/src/components/Modals/Question.vue:31
msgid "Create a new question"
msgstr ""
@@ -1358,7 +1399,7 @@ msgstr ""
#: frontend/src/components/CourseOutline.vue:235
#: frontend/src/components/CourseOutline.vue:293
#: frontend/src/pages/CourseForm.vue:15 frontend/src/pages/CourseForm.vue:470
#: frontend/src/pages/CourseForm.vue:15 frontend/src/pages/CourseForm.vue:473
msgid "Delete"
msgstr ""
@@ -1366,7 +1407,7 @@ msgstr ""
msgid "Delete Chapter"
msgstr ""
#: frontend/src/pages/CourseForm.vue:464
#: frontend/src/pages/CourseForm.vue:467
msgid "Delete Course"
msgstr ""
@@ -1378,7 +1419,7 @@ msgstr ""
msgid "Delete this lesson?"
msgstr ""
#: frontend/src/pages/CourseForm.vue:465
#: frontend/src/pages/CourseForm.vue:468
msgid "Deleting the course will also delete all its chapters and lessons. Are you sure you want to delete this course?"
msgstr ""
@@ -1468,7 +1509,7 @@ msgstr ""
msgid "Dream Companies"
msgstr ""
#: lms/lms/doctype/lms_question/lms_question.py:32
#: lms/lms/doctype/lms_question/lms_question.py:33
msgid "Duplicate options found for this question."
msgstr ""
@@ -1506,7 +1547,7 @@ msgstr ""
#: frontend/src/components/CourseCardOverlay.vue:86
#: frontend/src/components/Modals/ChapterModal.vue:9
#: frontend/src/pages/JobDetail.vue:31 frontend/src/pages/Lesson.vue:65
#: frontend/src/pages/Profile.vue:32
#: frontend/src/pages/Profile.vue:32 frontend/src/pages/Programs.vue:55
msgid "Edit"
msgstr ""
@@ -1519,7 +1560,7 @@ msgstr ""
msgid "Edit Profile"
msgstr ""
#: frontend/src/pages/QuizForm.vue:180
#: frontend/src/pages/QuizForm.vue:181
msgid "Edit the question"
msgstr ""
@@ -1580,6 +1621,15 @@ msgstr ""
msgid "Enable Google API in Google Settings to send calendar invites for evaluations."
msgstr ""
#. Label of the enable_learning_paths (Check) field in DocType 'LMS Settings'
#: lms/lms/doctype/lms_settings/lms_settings.json
msgid "Enable Learning Paths"
msgstr ""
#: frontend/src/components/Modals/ChapterModal.vue:24
msgid "Enable this only if you want to upload a SCORM package as a chapter."
msgstr ""
#. Label of the enabled (Check) field in DocType 'LMS Badge'
#: lms/lms/doctype/lms_badge/lms_badge.json
msgid "Enabled"
@@ -1632,7 +1682,7 @@ msgstr ""
msgid "Enrolled successfully"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:98
#: lms/lms/doctype/lms_batch/lms_batch.py:103
msgid "Enrollment Confirmation for the Next Training Batch"
msgstr ""
@@ -1641,7 +1691,7 @@ msgstr ""
msgid "Enrollment Count"
msgstr ""
#: lms/lms/utils.py:1702
#: lms/lms/utils.py:1705
msgid "Enrollment Failed"
msgstr ""
@@ -1670,14 +1720,14 @@ msgid "Enter the correct answer"
msgstr ""
#: frontend/src/components/Modals/AnnouncementModal.vue:105
#: frontend/src/components/Modals/ChapterModal.vue:154
#: frontend/src/components/Modals/ChapterModal.vue:161
#: frontend/src/components/Modals/ChapterModal.vue:197
#: frontend/src/components/Modals/ChapterModal.vue:159
#: frontend/src/components/Modals/ChapterModal.vue:166
#: frontend/src/components/Modals/ChapterModal.vue:202
#: frontend/src/components/Modals/Question.vue:246
#: frontend/src/components/Modals/Question.vue:266
#: frontend/src/components/Modals/Question.vue:323
#: frontend/src/pages/Billing.vue:264 frontend/src/pages/QuizForm.vue:349
#: frontend/src/pages/QuizForm.vue:364
#: frontend/src/pages/Billing.vue:264 frontend/src/pages/QuizForm.vue:350
#: frontend/src/pages/QuizForm.vue:365
#: frontend/src/pages/QuizSubmission.vue:117
msgid "Error"
msgstr ""
@@ -1706,7 +1756,7 @@ msgstr ""
msgid "Evaluation Request"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:82
#: lms/lms/doctype/lms_batch/lms_batch.py:87
msgid "Evaluation end date cannot be less than the batch end date."
msgstr ""
@@ -1872,7 +1922,7 @@ msgstr ""
msgid "Flexible Time"
msgstr ""
#: frontend/src/pages/QuizForm.vue:462
#: frontend/src/pages/QuizForm.vue:463
msgid "Form to create and edit quizzes"
msgstr ""
@@ -1920,7 +1970,9 @@ msgid "From Date"
msgstr ""
#. Label of the full_name (Data) field in DocType 'Invite Request'
#. Label of the full_name (Data) field in DocType 'LMS Program Member'
#: lms/lms/doctype/invite_request/invite_request.json
#: lms/lms/doctype/lms_program_member/lms_program_member.json
#: lms/templates/signup-form.html:5
msgid "Full Name"
msgstr ""
@@ -1952,6 +2004,11 @@ msgstr ""
msgid "GSTIN"
msgstr ""
#. Label of the general_tab (Tab Break) field in DocType 'LMS Settings'
#: lms/lms/doctype/lms_settings/lms_settings.json
msgid "General"
msgstr ""
#: lms/lms/doctype/lms_certificate_request/lms_certificate_request.js:18
msgid "Generate Google Meet Link"
msgstr ""
@@ -2083,7 +2140,7 @@ msgstr ""
msgid "I am unavailable"
msgstr ""
#: frontend/src/pages/QuizForm.vue:383
#: frontend/src/pages/QuizForm.vue:384
msgid "ID"
msgstr ""
@@ -2224,7 +2281,7 @@ msgstr ""
#. Label of the instructor_notes (Markdown Editor) field in DocType 'Course
#. Lesson'
#: frontend/src/pages/Lesson.vue:128 frontend/src/pages/LessonForm.vue:38
#: frontend/src/pages/Lesson.vue:128 frontend/src/pages/LessonForm.vue:42
#: lms/lms/doctype/course_lesson/course_lesson.json
msgid "Instructor Notes"
msgstr ""
@@ -2300,7 +2357,6 @@ msgstr ""
#. Label of the is_scorm_package (Check) field in DocType 'Course Chapter'
#. Label of the is_scorm_package (Check) field in DocType 'Course Lesson'
#: frontend/src/components/Modals/ChapterModal.vue:26
#: lms/lms/doctype/course_chapter/course_chapter.json
#: lms/lms/doctype/course_lesson/course_lesson.json
msgid "Is SCORM Package"
@@ -2330,6 +2386,10 @@ msgstr ""
msgid "Items in Sidebar"
msgstr ""
#: frontend/src/pages/ProgramForm.vue:269
msgid "Items removed successfully"
msgstr ""
#: lms/templates/signup-form.html:6
msgid "Jane Doe"
msgstr ""
@@ -2529,6 +2589,21 @@ msgstr ""
msgid "LMS Payment"
msgstr ""
#. Name of a DocType
#: lms/lms/doctype/lms_program/lms_program.json
msgid "LMS Program"
msgstr ""
#. Name of a DocType
#: lms/lms/doctype/lms_program_course/lms_program_course.json
msgid "LMS Program Course"
msgstr ""
#. Name of a DocType
#: lms/lms/doctype/lms_program_member/lms_program_member.json
msgid "LMS Program Member"
msgstr ""
#. Name of a DocType
#: lms/lms/doctype/lms_question/lms_question.json
msgid "LMS Question"
@@ -2588,6 +2663,7 @@ msgstr ""
#: lms/lms/doctype/lms_live_class/lms_live_class.json
#: lms/lms/doctype/lms_quiz/lms_quiz.json
#: lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json
#: lms/lms/doctype/lms_settings/lms_settings.json
#: lms/lms/doctype/lms_source/lms_source.json
#: lms/lms/doctype/user_skill/user_skill.json
msgid "LMS Student"
@@ -2810,7 +2886,7 @@ msgstr ""
#. Label of the marks (Int) field in DocType 'LMS Quiz Result'
#: frontend/src/components/Modals/Question.vue:50
#: frontend/src/components/Modals/Question.vue:96
#: frontend/src/components/Quiz.vue:94 frontend/src/pages/QuizForm.vue:393
#: frontend/src/components/Quiz.vue:94 frontend/src/pages/QuizForm.vue:394
#: frontend/src/pages/QuizSubmission.vue:52
#: lms/lms/doctype/lms_quiz_question/lms_quiz_question.json
#: lms/lms/doctype/lms_quiz_result/lms_quiz_result.json
@@ -2865,6 +2941,7 @@ msgstr ""
#. Option for the 'Role' (Select) field in DocType 'LMS Enrollment'
#. Label of the member (Link) field in DocType 'LMS Mentor Request'
#. Label of the member (Link) field in DocType 'LMS Payment'
#. Label of the member (Link) field in DocType 'LMS Program Member'
#. Label of the member (Link) field in DocType 'LMS Quiz Submission'
#: frontend/src/pages/QuizSubmission.vue:27
#: frontend/src/pages/QuizSubmissionList.vue:77
@@ -2879,6 +2956,7 @@ msgstr ""
#: lms/lms/doctype/lms_enrollment/lms_enrollment.json
#: lms/lms/doctype/lms_mentor_request/lms_mentor_request.json
#: lms/lms/doctype/lms_payment/lms_payment.json
#: lms/lms/doctype/lms_program_member/lms_program_member.json
#: lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json
#: lms/lms/report/course_progress_summary/course_progress_summary.py:64
msgid "Member"
@@ -2928,6 +3006,14 @@ msgstr ""
msgid "Member Type"
msgstr ""
#: frontend/src/pages/ProgramForm.vue:248
msgid "Member added to program"
msgstr ""
#: lms/lms/doctype/lms_program/lms_program.py:29
msgid "Member {0} has already been added to this batch."
msgstr ""
#. Group in LMS Batch Old's connections
#: lms/lms/doctype/lms_batch_old/lms_batch_old.json
msgid "Members"
@@ -3000,6 +3086,10 @@ msgstr ""
msgid "Milestones"
msgstr ""
#: lms/lms/doctype/lms_question/lms_question.py:48
msgid "Minimum two options are required for multiple choice questions."
msgstr ""
#. Name of a role
#: frontend/src/pages/ProfileRoles.vue:10
#: lms/lms/doctype/course_evaluator/course_evaluator.json
@@ -3011,6 +3101,7 @@ msgstr ""
#: lms/lms/doctype/lms_certificate_request/lms_certificate_request.json
#: lms/lms/doctype/lms_enrollment/lms_enrollment.json
#: lms/lms/doctype/lms_live_class/lms_live_class.json
#: lms/lms/doctype/lms_program/lms_program.json
#: lms/lms/doctype/lms_question/lms_question.json
#: lms/lms/doctype/lms_quiz/lms_quiz.json
#: lms/lms/doctype/lms_source/lms_source.json
@@ -3055,8 +3146,8 @@ msgid "My calendar"
msgstr ""
#. Option for the 'Event' (Select) field in DocType 'LMS Badge'
#: frontend/src/pages/Batches.vue:30 frontend/src/pages/Courses.vue:44
#: lms/lms/doctype/lms_badge/lms_badge.json
#: frontend/src/pages/Batches.vue:30 frontend/src/pages/Courses.vue:45
#: frontend/src/pages/Programs.vue:14 lms/lms/doctype/lms_badge/lms_badge.json
msgid "New"
msgstr ""
@@ -3080,11 +3171,23 @@ msgstr ""
msgid "New Job Applicant"
msgstr ""
#: frontend/src/pages/Programs.vue:96
msgid "New Program"
msgstr ""
#: frontend/src/pages/ProgramForm.vue:132
msgid "New Program Course"
msgstr ""
#: frontend/src/pages/ProgramForm.vue:133
msgid "New Program Member"
msgstr ""
#: frontend/src/pages/QuizForm.vue:122
msgid "New Question"
msgstr ""
#: frontend/src/pages/QuizForm.vue:453 frontend/src/pages/QuizForm.vue:461
#: frontend/src/pages/QuizForm.vue:454 frontend/src/pages/QuizForm.vue:462
#: frontend/src/pages/Quizzes.vue:18
msgid "New Quiz"
msgstr ""
@@ -3147,10 +3250,14 @@ msgstr ""
msgid "No courses created"
msgstr ""
#: frontend/src/pages/Courses.vue:146
#: frontend/src/pages/Courses.vue:147
msgid "No courses found"
msgstr ""
#: frontend/src/pages/Programs.vue:72
msgid "No courses in this program"
msgstr ""
#: lms/templates/courses_under_review.html:14
msgid "No courses under review"
msgstr ""
@@ -3167,6 +3274,10 @@ msgstr ""
msgid "No live classes scheduled"
msgstr ""
#: frontend/src/pages/Programs.vue:82
msgid "No programs found"
msgstr ""
#: frontend/src/pages/Quizzes.vue:56
msgid "No quizzes found"
msgstr ""
@@ -3187,7 +3298,7 @@ msgstr ""
msgid "No {0} batches"
msgstr ""
#: frontend/src/pages/Courses.vue:106
#: frontend/src/pages/Courses.vue:107
msgid "No {0} courses"
msgstr ""
@@ -3275,15 +3386,19 @@ msgstr ""
msgid "Online"
msgstr ""
#: frontend/src/pages/ProgramForm.vue:156
msgid "Only courses for which self learning is disabled can be added to program."
msgstr ""
#: lms/templates/assignment.html:6
msgid "Only files of type {0} will be accepted."
msgstr ""
#: frontend/src/pages/CourseForm.vue:494 frontend/src/utils/index.js:518
#: frontend/src/pages/CourseForm.vue:497 frontend/src/utils/index.js:518
msgid "Only image file is allowed."
msgstr ""
#: frontend/src/components/Modals/ChapterModal.vue:223
#: frontend/src/components/Modals/ChapterModal.vue:220
msgid "Only zip files are allowed"
msgstr ""
@@ -3559,7 +3674,11 @@ msgstr ""
msgid "Please click on the following button to set your new password"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:251
#: lms/lms/utils.py:1817 lms/lms/utils.py:1821
msgid "Please complete the previous courses in the program to enroll in this course."
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:256
msgid "Please enable Zoom Settings to use this feature."
msgstr ""
@@ -3571,11 +3690,19 @@ msgstr ""
msgid "Please ensure that you complete all the questions in {0} minutes."
msgstr ""
#: frontend/src/components/Modals/LiveClassModal.vue:164
msgid "Please enter a title."
msgstr ""
#: lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py:38
#: lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py:98
msgid "Please enter a valid URL."
msgstr ""
#: frontend/src/components/Modals/LiveClassModal.vue:176
msgid "Please enter a valid time in the format HH:mm."
msgstr ""
#: lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.py:92
msgid "Please enter the URL for assignment submission."
msgstr ""
@@ -3584,11 +3711,11 @@ msgstr ""
msgid "Please enter your answer"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:65
#: lms/lms/doctype/lms_batch/lms_batch.py:66
msgid "Please install the Payments app to create a paid batches."
msgstr ""
#: lms/lms/doctype/lms_course/lms_course.py:52
#: lms/lms/doctype/lms_course/lms_course.py:53
msgid "Please install the Payments app to create a paid courses."
msgstr ""
@@ -3613,11 +3740,31 @@ msgstr ""
msgid "Please prepare well and be on time for the evaluations."
msgstr ""
#: frontend/src/components/Modals/LiveClassModal.vue:167
msgid "Please select a date."
msgstr ""
#: frontend/src/components/Modals/LiveClassModal.vue:191
msgid "Please select a duration."
msgstr ""
#: frontend/src/components/Modals/LiveClassModal.vue:188
msgid "Please select a future date and time."
msgstr ""
#: frontend/src/components/Modals/LiveClassModal.vue:170
msgid "Please select a time."
msgstr ""
#: frontend/src/components/Modals/LiveClassModal.vue:173
msgid "Please select a timezone."
msgstr ""
#: lms/templates/emails/job_report.html:6
msgid "Please take appropriate action at {0}"
msgstr ""
#: frontend/src/components/Modals/ChapterModal.vue:172
#: frontend/src/components/Modals/ChapterModal.vue:177
msgid "Please upload a SCORM package"
msgstr ""
@@ -3754,8 +3901,30 @@ msgstr ""
msgid "Profile Image"
msgstr ""
#: frontend/src/pages/ProgramForm.vue:154
msgid "Program Course"
msgstr ""
#. Label of the program_courses (Table) field in DocType 'LMS Program'
#: frontend/src/pages/ProgramForm.vue:17
#: lms/lms/doctype/lms_program/lms_program.json
msgid "Program Courses"
msgstr ""
#: frontend/src/pages/ProgramForm.vue:169
msgid "Program Member"
msgstr ""
#. Label of the program_members (Table) field in DocType 'LMS Program'
#: frontend/src/pages/ProgramForm.vue:78
#: lms/lms/doctype/lms_program/lms_program.json
msgid "Program Members"
msgstr ""
#. Label of the progress (Float) field in DocType 'LMS Enrollment'
#. Label of the progress (Int) field in DocType 'LMS Program Member'
#: lms/lms/doctype/lms_enrollment/lms_enrollment.json
#: lms/lms/doctype/lms_program_member/lms_program_member.json
msgid "Progress"
msgstr ""
@@ -3805,7 +3974,7 @@ msgstr ""
#. Label of the question (Text) field in DocType 'LMS Quiz Result'
#: frontend/src/components/Modals/Question.vue:38
#: frontend/src/pages/AssignmentSubmission.vue:26
#: frontend/src/pages/QuizForm.vue:388
#: frontend/src/pages/QuizForm.vue:389
#: lms/lms/doctype/course_lesson/course_lesson.json
#: lms/lms/doctype/lms_assignment/lms_assignment.json
#: lms/lms/doctype/lms_assignment_submission/lms_assignment_submission.json
@@ -3851,7 +4020,7 @@ msgstr ""
msgid "Questions"
msgstr ""
#: frontend/src/pages/QuizForm.vue:430
#: frontend/src/pages/QuizForm.vue:431
msgid "Questions deleted successfully"
msgstr ""
@@ -3889,7 +4058,7 @@ msgstr ""
msgid "Quiz Title"
msgstr ""
#: frontend/src/pages/QuizForm.vue:342
#: frontend/src/pages/QuizForm.vue:343
msgid "Quiz created successfully"
msgstr ""
@@ -3897,7 +4066,7 @@ msgstr ""
msgid "Quiz is not available to Guest users. Please login to continue."
msgstr ""
#: frontend/src/pages/QuizForm.vue:361
#: frontend/src/pages/QuizForm.vue:362
msgid "Quiz updated successfully"
msgstr ""
@@ -3906,7 +4075,7 @@ msgstr ""
msgid "Quiz will appear at the bottom of the lesson."
msgstr ""
#: frontend/src/pages/QuizForm.vue:441 frontend/src/pages/Quizzes.vue:136
#: frontend/src/pages/QuizForm.vue:442 frontend/src/pages/Quizzes.vue:136
#: frontend/src/pages/Quizzes.vue:146
msgid "Quizzes"
msgstr ""
@@ -4063,19 +4232,19 @@ msgstr ""
msgid "Route"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:194
#: lms/lms/doctype/lms_batch/lms_batch.py:199
msgid "Row #{0} Date cannot be outside the batch duration."
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:189
#: lms/lms/doctype/lms_batch/lms_batch.py:194
msgid "Row #{0} End time cannot be outside the batch duration."
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:171
#: lms/lms/doctype/lms_batch/lms_batch.py:176
msgid "Row #{0} Start time cannot be greater than or equal to end time."
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:180
#: lms/lms/doctype/lms_batch/lms_batch.py:185
msgid "Row #{0} Start time cannot be outside the batch duration."
msgstr ""
@@ -4093,6 +4262,7 @@ msgid "SCORM"
msgstr ""
#. Label of the scorm_package (Link) field in DocType 'Course Chapter'
#: frontend/src/components/Modals/ChapterModal.vue:22
#: lms/lms/doctype/course_chapter/course_chapter.json
msgid "SCORM Package"
msgstr ""
@@ -4115,8 +4285,9 @@ msgstr ""
#: frontend/src/components/QuizPlugin.vue:23
#: frontend/src/pages/AssignmentSubmission.vue:7
#: frontend/src/pages/BatchForm.vue:8 frontend/src/pages/CourseForm.vue:20
#: frontend/src/pages/JobCreation.vue:8 frontend/src/pages/LessonForm.vue:10
#: frontend/src/pages/QuizForm.vue:34 frontend/src/pages/QuizSubmission.vue:14
#: frontend/src/pages/JobCreation.vue:8 frontend/src/pages/LessonForm.vue:14
#: frontend/src/pages/ProgramForm.vue:7 frontend/src/pages/QuizForm.vue:34
#: frontend/src/pages/QuizSubmission.vue:14
#: lms/public/js/common_functions.js:405
msgid "Save"
msgstr ""
@@ -4332,6 +4503,10 @@ msgstr ""
msgid "Skip"
msgstr ""
#: frontend/src/components/OnboardingBanner.vue:3
msgid "Skip Onboarding"
msgstr ""
#: lms/lms/doctype/course_evaluator/course_evaluator.py:57
msgid "Slot Times are overlapping for some schedules."
msgstr ""
@@ -4505,7 +4680,7 @@ msgstr ""
msgid "Student Reviews"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:47
#: lms/lms/doctype/lms_batch/lms_batch.py:48
msgid "Student {0} has already been added to this batch."
msgstr ""
@@ -4573,14 +4748,17 @@ msgstr ""
#: frontend/src/components/CourseCardOverlay.vue:161
#: frontend/src/components/Modals/AnnouncementModal.vue:99
#: frontend/src/components/Modals/AssessmentModal.vue:73
#: frontend/src/components/Modals/ChapterModal.vue:148
#: frontend/src/components/Modals/ChapterModal.vue:193
#: frontend/src/components/Modals/ChapterModal.vue:153
#: frontend/src/components/Modals/ChapterModal.vue:198
#: frontend/src/components/Modals/Event.vue:255
#: frontend/src/components/Modals/Event.vue:310
#: frontend/src/components/Modals/Question.vue:261
#: frontend/src/components/Modals/Question.vue:312
#: frontend/src/pages/CourseForm.vue:457 frontend/src/pages/QuizForm.vue:342
#: frontend/src/pages/QuizForm.vue:361 frontend/src/pages/QuizForm.vue:430
#: frontend/src/pages/CourseForm.vue:460 frontend/src/pages/ProgramForm.vue:226
#: frontend/src/pages/ProgramForm.vue:248
#: frontend/src/pages/ProgramForm.vue:269
#: frontend/src/pages/ProgramForm.vue:295 frontend/src/pages/QuizForm.vue:343
#: frontend/src/pages/QuizForm.vue:362 frontend/src/pages/QuizForm.vue:431
msgid "Success"
msgstr ""
@@ -4635,6 +4813,7 @@ msgstr ""
#: lms/lms/doctype/lms_live_class/lms_live_class.json
#: lms/lms/doctype/lms_mentor_request/lms_mentor_request.json
#: lms/lms/doctype/lms_payment/lms_payment.json
#: lms/lms/doctype/lms_program/lms_program.json
#: lms/lms/doctype/lms_question/lms_question.json
#: lms/lms/doctype/lms_quiz/lms_quiz.json
#: lms/lms/doctype/lms_quiz_submission/lms_quiz_submission.json
@@ -4735,11 +4914,15 @@ msgstr ""
msgid "There are no chapters in this course. Create and manage chapters from here."
msgstr ""
#: frontend/src/pages/Courses.vue:150
#: frontend/src/pages/Courses.vue:151
msgid "There are no courses available at the moment. Keep an eye out, fresh learning experiences are on the way soon!"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:141
#: frontend/src/pages/Programs.vue:86
msgid "There are no programs available at the moment. Keep an eye out, fresh learning experiences are on the way soon!"
msgstr ""
#: lms/lms/doctype/lms_batch/lms_batch.py:146
msgid "There are no seats available in this batch."
msgstr ""
@@ -4770,11 +4953,11 @@ msgstr ""
msgid "This course has:"
msgstr ""
#: lms/lms/utils.py:1582
#: lms/lms/utils.py:1585
msgid "This course is free."
msgstr ""
#: frontend/src/pages/SCORMChapter.vue:198
#: frontend/src/pages/SCORMChapter.vue:197
msgid "This is a chapter in the course {0}"
msgstr ""
@@ -4866,6 +5049,7 @@ msgstr ""
#. Label of the title (Data) field in DocType 'LMS Course'
#. Label of the title (Data) field in DocType 'LMS Exercise'
#. Label of the title (Data) field in DocType 'LMS Live Class'
#. Label of the title (Data) field in DocType 'LMS Program'
#. Label of the title (Data) field in DocType 'LMS Quiz'
#. Label of the title (Data) field in DocType 'LMS Sidebar Item'
#. Label of the title (Data) field in DocType 'LMS Timetable Template'
@@ -4873,7 +5057,8 @@ msgstr ""
#: frontend/src/components/Modals/DiscussionModal.vue:18
#: frontend/src/components/Modals/LiveClassModal.vue:23
#: frontend/src/pages/BatchForm.vue:20 frontend/src/pages/CourseForm.vue:32
#: frontend/src/pages/JobCreation.vue:20 frontend/src/pages/QuizForm.vue:48
#: frontend/src/pages/JobCreation.vue:20 frontend/src/pages/ProgramForm.vue:11
#: frontend/src/pages/Programs.vue:107 frontend/src/pages/QuizForm.vue:48
#: frontend/src/pages/Quizzes.vue:114 lms/lms/doctype/cohort/cohort.json
#: lms/lms/doctype/cohort_subgroup/cohort_subgroup.json
#: lms/lms/doctype/cohort_web_page/cohort_web_page.json
@@ -4886,6 +5071,7 @@ msgstr ""
#: lms/lms/doctype/lms_course/lms_course.json
#: lms/lms/doctype/lms_exercise/lms_exercise.json
#: lms/lms/doctype/lms_live_class/lms_live_class.json
#: lms/lms/doctype/lms_program/lms_program.json
#: lms/lms/doctype/lms_quiz/lms_quiz.json
#: lms/lms/doctype/lms_sidebar_item/lms_sidebar_item.json
#: lms/lms/doctype/lms_timetable_template/lms_timetable_template.json
@@ -4894,7 +5080,7 @@ msgstr ""
msgid "Title"
msgstr ""
#: frontend/src/components/Modals/ChapterModal.vue:169
#: frontend/src/components/Modals/ChapterModal.vue:174
msgid "Title is required"
msgstr ""
@@ -4909,7 +5095,7 @@ msgstr ""
msgid "To Date"
msgstr ""
#: lms/lms/utils.py:1593
#: lms/lms/utils.py:1596
msgid "To join this batch, please contact the Administrator."
msgstr ""
@@ -4973,7 +5159,7 @@ msgstr ""
msgid "Type"
msgstr ""
#: frontend/src/components/Quiz.vue:580
#: frontend/src/components/Quiz.vue:583
msgid "Type your answer"
msgstr ""
@@ -5257,7 +5443,7 @@ msgstr ""
msgid "You are not enrolled in this course. Please enroll to access this lesson."
msgstr ""
#: frontend/src/pages/Courses.vue:134
#: frontend/src/pages/Courses.vue:135
msgid "You can add chapters and lessons to it."
msgstr ""
@@ -5429,6 +5615,10 @@ msgstr ""
msgid "jane@example.com"
msgstr ""
#: frontend/src/pages/Programs.vue:32
msgid "members"
msgstr ""
#: lms/templates/quiz/quiz.html:106
msgid "of"
msgstr ""
@@ -5441,7 +5631,7 @@ msgstr ""
msgid "posted by"
msgstr ""
#: frontend/src/pages/QuizForm.vue:389
#: frontend/src/pages/QuizForm.vue:390
msgid "question_detail"
msgstr ""
@@ -5473,7 +5663,7 @@ msgstr ""
msgid "{0} has submitted the assignment {1}"
msgstr ""
#: lms/lms/doctype/lms_enrollment/lms_enrollment.py:53
#: lms/lms/doctype/lms_enrollment/lms_enrollment.py:57
msgid "{0} is already a Student of {1} course through {2} batch"
msgstr ""
@@ -5481,7 +5671,7 @@ msgstr ""
msgid "{0} is already a mentor for course {1}"
msgstr ""
#: lms/lms/doctype/lms_enrollment/lms_enrollment.py:26
#: lms/lms/doctype/lms_enrollment/lms_enrollment.py:30
msgid "{0} is already a {1} of the course {2}"
msgstr ""

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: jannat@frappe.io\n"
"POT-Creation-Date: 2024-11-15 16:04+0000\n"
"PO-Revision-Date: 2024-11-18 17:31\n"
"PO-Revision-Date: 2024-11-24 19:19\n"
"Last-Translator: jannat@frappe.io\n"
"Language-Team: Swedish\n"
"MIME-Version: 1.0\n"
@@ -78,7 +78,7 @@ msgstr "Godkännande av Villkor och/eller Principer"
#. Option for the 'Status' (Select) field in DocType 'Cohort Join Request'
#: lms/lms/doctype/cohort_join_request/cohort_join_request.json
msgid "Accepted"
msgstr "Godkänd"
msgstr "Accepterad"
#. Label of the account_id (Data) field in DocType 'Zoom Settings'
#: lms/lms/doctype/zoom_settings/zoom_settings.json

File diff suppressed because it is too large Load Diff

View File

@@ -93,4 +93,5 @@ lms.patches.v2_0.sidebar_settings
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.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

View File

@@ -0,0 +1,9 @@
import frappe
def execute():
roles = ["Course Creator", "Moderator", "Batch Evaluator", "LMS Student"]
for role in roles:
if frappe.db.exists("Role", role):
frappe.db.set_value("Role", role, "desk_access", 0)