feat: onboarding
This commit is contained in:
Submodule frappe-ui updated: c795670f39...10635f3120
@@ -26,7 +26,7 @@
|
|||||||
"codemirror-editor-vue3": "^2.8.0",
|
"codemirror-editor-vue3": "^2.8.0",
|
||||||
"dayjs": "^1.11.6",
|
"dayjs": "^1.11.6",
|
||||||
"feather-icons": "^4.28.0",
|
"feather-icons": "^4.28.0",
|
||||||
"frappe-ui": "^0.1.118",
|
"frappe-ui": "^0.1.121",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"lucide-vue-next": "^0.383.0",
|
"lucide-vue-next": "^0.383.0",
|
||||||
"markdown-it": "^14.0.0",
|
"markdown-it": "^14.0.0",
|
||||||
|
|||||||
@@ -62,20 +62,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="m-2 flex flex-col gap-1">
|
||||||
<TrialBanner
|
<TrialBanner
|
||||||
v-if="
|
v-if="
|
||||||
userResource.data?.is_system_manager && userResource.data?.is_fc_site
|
userResource.data?.is_system_manager && userResource.data?.is_fc_site
|
||||||
"
|
"
|
||||||
:isSidebarCollapsed="sidebarStore.isSidebarCollapsed"
|
:isSidebarCollapsed="sidebarStore.isSidebarCollapsed"
|
||||||
/>
|
/>
|
||||||
|
<GettingStartedBanner
|
||||||
|
v-if="showOnboarding && !isOnboardingStepsCompleted"
|
||||||
|
:isSidebarCollapsed="sidebarStore.isSidebarCollapsed"
|
||||||
|
appName="learning"
|
||||||
|
/>
|
||||||
|
<SidebarLink
|
||||||
|
v-if="isOnboardingStepsCompleted"
|
||||||
|
:link="{
|
||||||
|
label: __('Help'),
|
||||||
|
}"
|
||||||
|
:isCollapsed="sidebarStore.isSidebarCollapsed"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
showHelpModal = minimize ? true : !showHelpModal
|
||||||
|
minimize = !showHelpModal
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<CircleHelp class="h-4 w-4" />
|
||||||
|
</template>
|
||||||
|
</SidebarLink>
|
||||||
<SidebarLink
|
<SidebarLink
|
||||||
:link="{
|
:link="{
|
||||||
label: sidebarStore.isSidebarCollapsed ? 'Expand' : 'Collapse',
|
label: sidebarStore.isSidebarCollapsed ? 'Expand' : 'Collapse',
|
||||||
}"
|
}"
|
||||||
:isCollapsed="sidebarStore.isSidebarCollapsed"
|
:isCollapsed="sidebarStore.isSidebarCollapsed"
|
||||||
@click="toggleSidebar()"
|
@click="toggleSidebar()"
|
||||||
class="m-2"
|
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<span class="grid h-5 w-6 flex-shrink-0 place-items-center">
|
<span class="grid h-5 w-6 flex-shrink-0 place-items-center">
|
||||||
@@ -95,6 +116,23 @@
|
|||||||
v-model:reloadSidebar="sidebarSettings"
|
v-model:reloadSidebar="sidebarSettings"
|
||||||
:page="pageToEdit"
|
:page="pageToEdit"
|
||||||
/>
|
/>
|
||||||
|
<HelpModal
|
||||||
|
v-if="showOnboarding && showHelpModal"
|
||||||
|
v-model="showHelpModal"
|
||||||
|
v-model:articles="articles"
|
||||||
|
appName="learning"
|
||||||
|
title="Frappe Learning"
|
||||||
|
:logo="LMSLogo"
|
||||||
|
:afterSkip="(step) => capture('onboarding_step_skipped_' + step)"
|
||||||
|
:afterSkipAll="() => capture('onboarding_steps_skipped')"
|
||||||
|
:afterReset="(step) => capture('onboarding_step_reset_' + step)"
|
||||||
|
:afterResetAll="() => capture('onboarding_steps_reset')"
|
||||||
|
docsLink="https://docs.frappe.io/learning"
|
||||||
|
/>
|
||||||
|
<IntermediateStepModal
|
||||||
|
v-model="showIntermediateModal"
|
||||||
|
:currentStep="currentStep"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -102,16 +140,36 @@ import UserDropdown from '@/components/UserDropdown.vue'
|
|||||||
import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue'
|
import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue'
|
||||||
import SidebarLink from '@/components/SidebarLink.vue'
|
import SidebarLink from '@/components/SidebarLink.vue'
|
||||||
import { useStorage } from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
import { ref, onMounted, inject, watch } from 'vue'
|
import { ref, onMounted, inject, watch, reactive, markRaw } from 'vue'
|
||||||
import { getSidebarLinks } from '../utils'
|
import { getSidebarLinks } from '../utils'
|
||||||
import { usersStore } from '@/stores/user'
|
import { usersStore } from '@/stores/user'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import { useSidebar } from '@/stores/sidebar'
|
import { useSidebar } from '@/stores/sidebar'
|
||||||
import { useSettings } from '@/stores/settings'
|
import { useSettings } from '@/stores/settings'
|
||||||
import { ChevronRight, Plus } from 'lucide-vue-next'
|
import {
|
||||||
|
BookOpen,
|
||||||
|
ChevronRight,
|
||||||
|
Plus,
|
||||||
|
CircleHelp,
|
||||||
|
FolderTree,
|
||||||
|
FileText,
|
||||||
|
UserPlus,
|
||||||
|
Users,
|
||||||
|
} from 'lucide-vue-next'
|
||||||
import { Button, createResource } from 'frappe-ui'
|
import { Button, createResource } from 'frappe-ui'
|
||||||
import { TrialBanner } from 'frappe-ui/frappe'
|
import {
|
||||||
|
TrialBanner,
|
||||||
|
HelpModal,
|
||||||
|
GettingStartedBanner,
|
||||||
|
useOnboarding,
|
||||||
|
showHelpModal,
|
||||||
|
minimize,
|
||||||
|
IntermediateStepModal,
|
||||||
|
} from 'frappe-ui/frappe'
|
||||||
import PageModal from '@/components/Modals/PageModal.vue'
|
import PageModal from '@/components/Modals/PageModal.vue'
|
||||||
|
import { capture } from '@/telemetry'
|
||||||
|
import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const { user, sidebarSettings } = sessionStore()
|
const { user, sidebarSettings } = sessionStore()
|
||||||
const { userResource } = usersStore()
|
const { userResource } = usersStore()
|
||||||
@@ -124,12 +182,22 @@ const isModerator = ref(false)
|
|||||||
const isInstructor = ref(false)
|
const isInstructor = ref(false)
|
||||||
const pageToEdit = ref(null)
|
const pageToEdit = ref(null)
|
||||||
const settingsStore = useSettings()
|
const settingsStore = useSettings()
|
||||||
|
const showOnboarding = ref(false)
|
||||||
|
const showIntermediateModal = ref(false)
|
||||||
|
const currentStep = ref({})
|
||||||
|
const router = useRouter()
|
||||||
|
let onboardingDetails
|
||||||
|
let isOnboardingStepsCompleted = false
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
addNotifications()
|
||||||
|
setSidebarLinks()
|
||||||
socket.on('publish_lms_notifications', (data) => {
|
socket.on('publish_lms_notifications', (data) => {
|
||||||
unreadNotifications.reload()
|
unreadNotifications.reload()
|
||||||
})
|
})
|
||||||
addNotifications()
|
})
|
||||||
|
|
||||||
|
const setSidebarLinks = () => {
|
||||||
sidebarSettings.reload(
|
sidebarSettings.reload(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
@@ -144,7 +212,7 @@ onMounted(() => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|
||||||
const unreadNotifications = createResource({
|
const unreadNotifications = createResource({
|
||||||
cache: 'Unread Notifications Count',
|
cache: 'Unread Notifications Count',
|
||||||
@@ -272,16 +340,6 @@ const getSidebarFromStorage = () => {
|
|||||||
return useStorage('sidebar_is_collapsed', false)
|
return useStorage('sidebar_is_collapsed', false)
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(userResource, () => {
|
|
||||||
if (userResource.data) {
|
|
||||||
isModerator.value = userResource.data.is_moderator
|
|
||||||
isInstructor.value = userResource.data.is_instructor
|
|
||||||
addPrograms()
|
|
||||||
addQuizzes()
|
|
||||||
addAssignments()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
sidebarStore.isSidebarCollapsed = !sidebarStore.isSidebarCollapsed
|
sidebarStore.isSidebarCollapsed = !sidebarStore.isSidebarCollapsed
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@@ -297,4 +355,221 @@ const toggleWebPages = () => {
|
|||||||
JSON.stringify(sidebarStore.isWebpagesCollapsed)
|
JSON.stringify(sidebarStore.isWebpagesCollapsed)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getFirstCourse = async () => {
|
||||||
|
let firstCourse = localStorage.getItem('firstCourse')
|
||||||
|
if (firstCourse) return firstCourse
|
||||||
|
return await call('lms.lms.onboarding.get_first_course')
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFirstBatch = async () => {
|
||||||
|
let firstBatch = localStorage.getItem('firstBatch')
|
||||||
|
if (firstBatch) return firstBatch
|
||||||
|
return await call('lms.lms.onboarding.get_first_batch')
|
||||||
|
}
|
||||||
|
|
||||||
|
const steps = reactive([
|
||||||
|
{
|
||||||
|
name: 'create_first_course',
|
||||||
|
title: __('Create your first course'),
|
||||||
|
icon: markRaw(BookOpen),
|
||||||
|
completed: false,
|
||||||
|
onClick: () => {
|
||||||
|
minimize.value = true
|
||||||
|
router.push({
|
||||||
|
name: 'CourseForm',
|
||||||
|
params: {
|
||||||
|
courseName: 'new',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_first_chapter',
|
||||||
|
title: __('Add your first chapter'),
|
||||||
|
icon: markRaw(FolderTree),
|
||||||
|
completed: false,
|
||||||
|
onClick: async () => {
|
||||||
|
minimize.value = true
|
||||||
|
let course = await getFirstCourse()
|
||||||
|
if (course) {
|
||||||
|
router.push({ name: 'CourseForm', params: { courseName: course } })
|
||||||
|
} else {
|
||||||
|
router.push({ name: 'CourseForm' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_first_lesson',
|
||||||
|
title: __('Add your first lesson'),
|
||||||
|
icon: markRaw(FileText),
|
||||||
|
completed: false,
|
||||||
|
onClick: async () => {
|
||||||
|
minimize.value = true
|
||||||
|
let course = await getFirstCourse()
|
||||||
|
if (course) {
|
||||||
|
router.push({
|
||||||
|
name: 'LessonForm',
|
||||||
|
params: { courseName: course, chapterNumber: 1, lessonNumber: 1 },
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({ name: 'CourseForm' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_first_quiz',
|
||||||
|
title: __('Create your first quiz'),
|
||||||
|
icon: markRaw(CircleHelp),
|
||||||
|
completed: false,
|
||||||
|
onClick: () => {
|
||||||
|
minimize.value = true
|
||||||
|
router.push({ name: 'QuizForm', params: { quizID: 'new' } })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invite_students',
|
||||||
|
title: __('Invite your team and students'),
|
||||||
|
icon: markRaw(UserPlus),
|
||||||
|
completed: false,
|
||||||
|
onClick: () => {
|
||||||
|
minimize.value = true
|
||||||
|
settingsStore.activeTab = 'Members'
|
||||||
|
settingsStore.isSettingsOpen = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'create_first_batch',
|
||||||
|
title: __('Create your first batch'),
|
||||||
|
icon: markRaw(Users),
|
||||||
|
completed: false,
|
||||||
|
onClick: () => {
|
||||||
|
minimize.value = true
|
||||||
|
router.push({ name: 'BatchForm', params: { batchName: 'new' } })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'add_batch_student',
|
||||||
|
title: __('Add students to your batch'),
|
||||||
|
icon: markRaw(UserPlus),
|
||||||
|
completed: false,
|
||||||
|
onClick: async () => {
|
||||||
|
minimize.value = true
|
||||||
|
let batch = await getFirstBatch()
|
||||||
|
if (batch) {
|
||||||
|
router.push({
|
||||||
|
name: 'Batch',
|
||||||
|
params: {
|
||||||
|
batchName: batch,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({ name: 'Batch' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'add_batch_course',
|
||||||
|
title: __('Add courses to your batch'),
|
||||||
|
icon: markRaw(BookOpen),
|
||||||
|
completed: false,
|
||||||
|
onClick: async () => {
|
||||||
|
minimize.value = true
|
||||||
|
let batch = await getFirstBatch()
|
||||||
|
if (batch) {
|
||||||
|
router.push({
|
||||||
|
name: 'Batch',
|
||||||
|
params: {
|
||||||
|
batchName: batch,
|
||||||
|
},
|
||||||
|
hash: 'courses',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router.push({ name: 'Batch' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const articles = ref([
|
||||||
|
{
|
||||||
|
title: __('Introduction'),
|
||||||
|
opened: false,
|
||||||
|
subArticles: [
|
||||||
|
{ name: 'introduction', title: __('Introduction') },
|
||||||
|
{ name: 'setting-up', title: __('Setting up') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: __('Creating a course'),
|
||||||
|
opened: false,
|
||||||
|
subArticles: [
|
||||||
|
{ name: 'create-a-course', title: __('Create a course') },
|
||||||
|
{ name: 'add-a-chapter', title: __('Add a chapter') },
|
||||||
|
{ name: 'add-a-lesson', title: __('Add a lesson') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: __('Creating a batch'),
|
||||||
|
opened: false,
|
||||||
|
subArticles: [
|
||||||
|
{ name: 'create-a-batch', title: __('Create a batch') },
|
||||||
|
{ name: 'create-a-live-class', title: __('Create a live class') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: __('Assessments'),
|
||||||
|
opened: false,
|
||||||
|
subArticles: [
|
||||||
|
{ name: 'quizzes', title: __('Quizzes') },
|
||||||
|
{ name: 'assignments', title: __('Assignments') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: __('Certification'),
|
||||||
|
opened: false,
|
||||||
|
subArticles: [
|
||||||
|
{ name: 'issue-a-certificate', title: __('Issue a Certificate') },
|
||||||
|
{
|
||||||
|
name: 'custom-certificate-templates',
|
||||||
|
title: __('Custom Certificate Templates'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: __('Monetization'),
|
||||||
|
opened: false,
|
||||||
|
subArticles: [
|
||||||
|
{
|
||||||
|
name: 'setting-up-payment-gateway',
|
||||||
|
title: __('Setting up payment gateway'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: __('Settings'),
|
||||||
|
opened: false,
|
||||||
|
subArticles: [{ name: 'roles', title: __('Roles') }],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const setUpOnboarding = () => {
|
||||||
|
if (userResource.data?.is_system_manager) {
|
||||||
|
onboardingDetails = useOnboarding('learning')
|
||||||
|
onboardingDetails.setUp(steps)
|
||||||
|
isOnboardingStepsCompleted = onboardingDetails.isOnboardingStepsCompleted
|
||||||
|
showOnboarding.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(userResource, () => {
|
||||||
|
if (userResource.data) {
|
||||||
|
isModerator.value = userResource.data.is_moderator
|
||||||
|
isInstructor.value = userResource.data.is_instructor
|
||||||
|
addPrograms()
|
||||||
|
addQuizzes()
|
||||||
|
addAssignments()
|
||||||
|
setUpOnboarding()
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -63,6 +63,9 @@
|
|||||||
</ListSelectBanner>
|
</ListSelectBanner>
|
||||||
</ListView>
|
</ListView>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="text-sm italic text-ink-gray-5">
|
||||||
|
{{ __('No courses added') }}
|
||||||
|
</div>
|
||||||
<BatchCourseModal
|
<BatchCourseModal
|
||||||
v-model="showCourseModal"
|
v-model="showCourseModal"
|
||||||
:batch="batch"
|
:batch="batch"
|
||||||
|
|||||||
@@ -264,7 +264,8 @@ const students = createResource({
|
|||||||
auto: true,
|
auto: true,
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
chartData.value = getChartData()
|
chartData.value = getChartData()
|
||||||
showProgressChart.value = data.length && true
|
showProgressChart.value =
|
||||||
|
data.length && (props.batch?.courses?.length || assessmentCount.value)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ import { createResource, Avatar, Button, FormControl, Badge } from 'frappe-ui'
|
|||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ref, watch, reactive, inject } from 'vue'
|
import { ref, watch, reactive, inject } from 'vue'
|
||||||
import { RefreshCw, Plus, X } from 'lucide-vue-next'
|
import { RefreshCw, Plus, X } from 'lucide-vue-next'
|
||||||
|
import { useOnboarding } from 'frappe-ui/frappe'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const show = defineModel('show')
|
const show = defineModel('show')
|
||||||
@@ -125,6 +126,7 @@ const memberList = ref([])
|
|||||||
const hasNextPage = ref(false)
|
const hasNextPage = ref(false)
|
||||||
const showForm = ref(false)
|
const showForm = ref(false)
|
||||||
const dayjs = inject('$dayjs')
|
const dayjs = inject('$dayjs')
|
||||||
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
|
|
||||||
const member = reactive({
|
const member = reactive({
|
||||||
email: '',
|
email: '',
|
||||||
@@ -185,6 +187,7 @@ const newMember = createResource({
|
|||||||
auto: false,
|
auto: false,
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
show.value = false
|
show.value = false
|
||||||
|
updateOnboardingStep('invite_students')
|
||||||
router.push({
|
router.push({
|
||||||
name: 'Profile',
|
name: 'Profile',
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
doctype="Course Evaluator"
|
doctype="Course Evaluator"
|
||||||
v-model="evaluator"
|
v-model="evaluator"
|
||||||
:label="__('Evaluator')"
|
:label="__('Evaluator')"
|
||||||
|
:onCreate="(value, close) => openSettings(close)"
|
||||||
class="mt-4"
|
class="mt-4"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -34,11 +35,15 @@ import { Dialog, createResource } from 'frappe-ui'
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import { showToast } from '@/utils'
|
import { showToast } from '@/utils'
|
||||||
|
import { useOnboarding } from 'frappe-ui/frappe'
|
||||||
|
import { useSettings } from '@/stores/settings'
|
||||||
|
|
||||||
const show = defineModel()
|
const show = defineModel()
|
||||||
const course = ref(null)
|
const course = ref(null)
|
||||||
const evaluator = ref(null)
|
const evaluator = ref(null)
|
||||||
const courses = defineModel('courses')
|
const courses = defineModel('courses')
|
||||||
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
|
const settingsStore = useSettings()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
batch: {
|
batch: {
|
||||||
@@ -69,6 +74,7 @@ const addCourse = (close) => {
|
|||||||
{
|
{
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
courses.value.reload()
|
courses.value.reload()
|
||||||
|
updateOnboardingStep('add_batch_course')
|
||||||
close()
|
close()
|
||||||
course.value = null
|
course.value = null
|
||||||
evaluator.value = null
|
evaluator.value = null
|
||||||
@@ -79,4 +85,10 @@ const addCourse = (close) => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openSettings = (close) => {
|
||||||
|
close()
|
||||||
|
settingsStore.activeTab = 'Categories'
|
||||||
|
settingsStore.isSettingsOpen = true
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -81,11 +81,11 @@ import { reactive, watch } from 'vue'
|
|||||||
import { showToast, getFileSize } from '@/utils/'
|
import { showToast, getFileSize } from '@/utils/'
|
||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
import { FileText, X } from 'lucide-vue-next'
|
import { FileText, X } from 'lucide-vue-next'
|
||||||
import { useSettings } from '@/stores/settings'
|
import { useOnboarding } from 'frappe-ui/frappe'
|
||||||
|
|
||||||
const show = defineModel()
|
const show = defineModel()
|
||||||
const outline = defineModel('outline')
|
const outline = defineModel('outline')
|
||||||
const settingsStore = useSettings()
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
course: {
|
course: {
|
||||||
@@ -140,14 +140,12 @@ const addChapter = async (close) => {
|
|||||||
},
|
},
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
capture('chapter_created')
|
capture('chapter_created')
|
||||||
|
updateOnboardingStep('create_first_chapter')
|
||||||
chapterReference.submit(
|
chapterReference.submit(
|
||||||
{ name: data.name },
|
{ name: data.name },
|
||||||
{
|
{
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
cleanChapter()
|
cleanChapter()
|
||||||
/* if (!settingsStore.onboardingDetails.data?.is_onboarded) {
|
|
||||||
settingsStore.onboardingDetails.reload()
|
|
||||||
} */
|
|
||||||
outline.value.reload()
|
outline.value.reload()
|
||||||
showToast(
|
showToast(
|
||||||
__('Success'),
|
__('Success'),
|
||||||
|
|||||||
@@ -29,9 +29,11 @@ import { Dialog, createResource } from 'frappe-ui'
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import { showToast } from '@/utils'
|
import { showToast } from '@/utils'
|
||||||
|
import { useOnboarding } from 'frappe-ui/frappe'
|
||||||
|
|
||||||
const students = defineModel('reloadStudents')
|
const students = defineModel('reloadStudents')
|
||||||
const student = ref()
|
const student = ref()
|
||||||
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
const show = defineModel()
|
const show = defineModel()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -61,6 +63,7 @@ const addStudent = (close) => {
|
|||||||
onSuccess() {
|
onSuccess() {
|
||||||
students.value.reload()
|
students.value.reload()
|
||||||
student.value = null
|
student.value = null
|
||||||
|
updateOnboardingStep('add_batch_student')
|
||||||
close()
|
close()
|
||||||
},
|
},
|
||||||
onError(err) {
|
onError(err) {
|
||||||
|
|||||||
@@ -190,11 +190,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<BulkCertificates v-model="openCertificateDialog" :batch="batch.data" />
|
<BulkCertificates
|
||||||
|
v-if="batch.data"
|
||||||
|
v-model="openCertificateDialog"
|
||||||
|
:batch="batch.data"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, inject, ref } from 'vue'
|
import { computed, inject, ref, onMounted, watch } from 'vue'
|
||||||
import { useRouteQuery } from '@vueuse/router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { Breadcrumbs, Button, createResource, Tabs, Badge } from 'frappe-ui'
|
import { Breadcrumbs, Button, createResource, Tabs, Badge } from 'frappe-ui'
|
||||||
import CourseInstructors from '@/components/CourseInstructors.vue'
|
import CourseInstructors from '@/components/CourseInstructors.vue'
|
||||||
import UserAvatar from '@/components/UserAvatar.vue'
|
import UserAvatar from '@/components/UserAvatar.vue'
|
||||||
@@ -226,52 +230,10 @@ import BatchFeedback from '@/components/BatchFeedback.vue'
|
|||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const showAnnouncementModal = ref(false)
|
const showAnnouncementModal = ref(false)
|
||||||
const openCertificateDialog = ref(false)
|
const openCertificateDialog = ref(false)
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const tabIndex = ref(0)
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
batchName: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const batch = createResource({
|
|
||||||
url: 'lms.lms.utils.get_batch_details',
|
|
||||||
cache: ['batch', props.batchName],
|
|
||||||
params: {
|
|
||||||
batch: props.batchName,
|
|
||||||
},
|
|
||||||
auto: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const breadcrumbs = computed(() => {
|
|
||||||
let crumbs = [{ label: 'Batches', route: { name: 'Batches' } }]
|
|
||||||
if (!isStudent.value) {
|
|
||||||
crumbs.push({
|
|
||||||
label: 'Details',
|
|
||||||
route: {
|
|
||||||
name: 'BatchDetail',
|
|
||||||
params: {
|
|
||||||
batchName: batch.data?.name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
crumbs.push({
|
|
||||||
label: batch?.data?.title,
|
|
||||||
route: { name: 'Batch', params: { batchName: props.batchName } },
|
|
||||||
})
|
|
||||||
return crumbs
|
|
||||||
})
|
|
||||||
|
|
||||||
const isStudent = computed(() => {
|
|
||||||
return (
|
|
||||||
user?.data &&
|
|
||||||
batch.data?.students?.length &&
|
|
||||||
batch.data?.students.includes(user.data.name)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const tabIndex = useRouteQuery('tab', 0, { transform: Number })
|
|
||||||
const tabs = computed(() => {
|
const tabs = computed(() => {
|
||||||
let batchTabs = []
|
let batchTabs = []
|
||||||
batchTabs.push({
|
batchTabs.push({
|
||||||
@@ -313,6 +275,61 @@ const tabs = computed(() => {
|
|||||||
return batchTabs
|
return batchTabs
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
batchName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const hash = route.hash
|
||||||
|
if (hash) {
|
||||||
|
tabs.value.forEach((tab, index) => {
|
||||||
|
if (tab.label?.toLowerCase() === hash.replace('#', '')) {
|
||||||
|
tabIndex.value = index
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const batch = createResource({
|
||||||
|
url: 'lms.lms.utils.get_batch_details',
|
||||||
|
cache: ['batch', props.batchName],
|
||||||
|
params: {
|
||||||
|
batch: props.batchName,
|
||||||
|
},
|
||||||
|
auto: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const breadcrumbs = computed(() => {
|
||||||
|
let crumbs = [{ label: 'Batches', route: { name: 'Batches' } }]
|
||||||
|
if (!isStudent.value) {
|
||||||
|
crumbs.push({
|
||||||
|
label: 'Details',
|
||||||
|
route: {
|
||||||
|
name: 'BatchDetail',
|
||||||
|
params: {
|
||||||
|
batchName: batch.data?.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
crumbs.push({
|
||||||
|
label: batch?.data?.title,
|
||||||
|
route: { name: 'Batch', params: { batchName: props.batchName } },
|
||||||
|
})
|
||||||
|
return crumbs
|
||||||
|
})
|
||||||
|
|
||||||
|
const isStudent = computed(() => {
|
||||||
|
return (
|
||||||
|
user?.data &&
|
||||||
|
batch.data?.students?.length &&
|
||||||
|
batch.data?.students.includes(user.data.name)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const redirectToLogin = () => {
|
const redirectToLogin = () => {
|
||||||
window.location.href = `/login?redirect-to=/lms/batches/${props.batchName}`
|
window.location.href = `/login?redirect-to=/lms/batches/${props.batchName}`
|
||||||
}
|
}
|
||||||
@@ -321,6 +338,13 @@ const openAnnouncementModal = () => {
|
|||||||
showAnnouncementModal.value = true
|
showAnnouncementModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(tabIndex, () => {
|
||||||
|
const tab = tabs.value[tabIndex.value]
|
||||||
|
if (tab.label != route.hash.replace('#', '')) {
|
||||||
|
router.push({ ...route, hash: `#${tab.label.toLowerCase()}` })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const pageMeta = computed(() => {
|
const pageMeta = computed(() => {
|
||||||
return {
|
return {
|
||||||
title: batch.data?.title,
|
title: batch.data?.title,
|
||||||
|
|||||||
@@ -271,9 +271,11 @@ import { showToast } from '@/utils'
|
|||||||
import { Image } from 'lucide-vue-next'
|
import { Image } from 'lucide-vue-next'
|
||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
||||||
|
import { useOnboarding } from 'frappe-ui/frappe'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
batchName: {
|
batchName: {
|
||||||
@@ -426,6 +428,9 @@ const createNewBatch = () => {
|
|||||||
{
|
{
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
capture('batch_created')
|
capture('batch_created')
|
||||||
|
updateOnboardingStep('create_first_batch', true, false, () => {
|
||||||
|
localStorage.setItem('firstBatch', data.name)
|
||||||
|
})
|
||||||
router.push({
|
router.push({
|
||||||
name: 'BatchDetail',
|
name: 'BatchDetail',
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
TabButtons,
|
TabButtons,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { useRouteQuery } from '@vueuse/router'
|
|
||||||
import { computed, inject, onMounted, ref, watch } from 'vue'
|
import { computed, inject, onMounted, ref, watch } from 'vue'
|
||||||
import { BookOpen, Plus } from 'lucide-vue-next'
|
import { BookOpen, Plus } from 'lucide-vue-next'
|
||||||
import { updateDocumentTitle } from '@/utils'
|
import { updateDocumentTitle } from '@/utils'
|
||||||
@@ -120,10 +119,8 @@ const currentCategory = ref(null)
|
|||||||
const title = ref('')
|
const title = ref('')
|
||||||
const certification = ref(false)
|
const certification = ref(false)
|
||||||
const filters = ref({})
|
const filters = ref({})
|
||||||
const currentTab = useRouteQuery(
|
const is_student = computed(() => user.data?.is_student)
|
||||||
'tab',
|
const currentTab = ref(is_student.value ? 'All' : 'Upcoming')
|
||||||
user.data?.is_student ? 'All' : 'Upcoming'
|
|
||||||
)
|
|
||||||
const orderBy = ref('start_date')
|
const orderBy = ref('start_date')
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -208,12 +205,12 @@ const updateTabFilter = () => {
|
|||||||
if (!user.data) {
|
if (!user.data) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (currentTab.value == 'Enrolled' && user.data?.is_student) {
|
if (currentTab.value == 'Enrolled' && is_student.value) {
|
||||||
filters.value['enrolled'] = 1
|
filters.value['enrolled'] = 1
|
||||||
delete filters.value['start_date']
|
delete filters.value['start_date']
|
||||||
delete filters.value['published']
|
delete filters.value['published']
|
||||||
orderBy.value = 'start_date desc'
|
orderBy.value = 'start_date desc'
|
||||||
} else if (user.data?.is_student) {
|
} else if (is_student.value) {
|
||||||
delete filters.value['enrolled']
|
delete filters.value['enrolled']
|
||||||
} else {
|
} else {
|
||||||
delete filters.value['start_date']
|
delete filters.value['start_date']
|
||||||
@@ -232,7 +229,7 @@ const updateTabFilter = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateStudentFilter = () => {
|
const updateStudentFilter = () => {
|
||||||
if (!user.data || (user.data?.is_student && currentTab.value != 'Enrolled')) {
|
if (!user.data || (is_student.value && currentTab.value != 'Enrolled')) {
|
||||||
filters.value['start_date'] = ['>=', dayjs().format('YYYY-MM-DD')]
|
filters.value['start_date'] = ['>=', dayjs().format('YYYY-MM-DD')]
|
||||||
filters.value['published'] = 1
|
filters.value['published'] = 1
|
||||||
}
|
}
|
||||||
@@ -285,12 +282,17 @@ const batchTabs = computed(() => {
|
|||||||
label: __('All'),
|
label: __('All'),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if (user.data?.is_student) {
|
|
||||||
tabs.push({ label: __('Enrolled') })
|
if (
|
||||||
} else {
|
user.data?.is_moderator ||
|
||||||
|
user.data?.is_instructor ||
|
||||||
|
user.data?.is_evaluator
|
||||||
|
) {
|
||||||
tabs.push({ label: __('Upcoming') })
|
tabs.push({ label: __('Upcoming') })
|
||||||
tabs.push({ label: __('Archived') })
|
tabs.push({ label: __('Archived') })
|
||||||
tabs.push({ label: __('Unpublished') })
|
tabs.push({ label: __('Unpublished') })
|
||||||
|
} else if (user.data) {
|
||||||
|
tabs.push({ label: __('Enrolled') })
|
||||||
}
|
}
|
||||||
return tabs
|
return tabs
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -270,6 +270,7 @@ import { useRouter } from 'vue-router'
|
|||||||
import CourseOutline from '@/components/CourseOutline.vue'
|
import CourseOutline from '@/components/CourseOutline.vue'
|
||||||
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
import MultiSelect from '@/components/Controls/MultiSelect.vue'
|
||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
|
import { useOnboarding } from 'frappe-ui/frappe'
|
||||||
import { useSettings } from '@/stores/settings'
|
import { useSettings } from '@/stores/settings'
|
||||||
|
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
@@ -278,6 +279,7 @@ const router = useRouter()
|
|||||||
const instructors = ref([])
|
const instructors = ref([])
|
||||||
const settingsStore = useSettings()
|
const settingsStore = useSettings()
|
||||||
const app = getCurrentInstance()
|
const app = getCurrentInstance()
|
||||||
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
const { $dialog } = app.appContext.config.globalProperties
|
const { $dialog } = app.appContext.config.globalProperties
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -443,9 +445,9 @@ const submitCourse = () => {
|
|||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
capture('course_created')
|
capture('course_created')
|
||||||
showToast('Success', 'Course created successfully', 'check')
|
showToast('Success', 'Course created successfully', 'check')
|
||||||
/* if (!settingsStore.onboardingDetails.data?.is_onboarded) {
|
updateOnboardingStep('create_first_course', true, false, () => {
|
||||||
settingsStore.onboardingDetails.reload()
|
localStorage.setItem('firstCourse', data.name)
|
||||||
} */
|
})
|
||||||
router.push({
|
router.push({
|
||||||
name: 'CourseForm',
|
name: 'CourseForm',
|
||||||
params: { courseName: data.name },
|
params: { courseName: data.name },
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
TabButtons,
|
TabButtons,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { useRouteQuery } from '@vueuse/router'
|
|
||||||
import { computed, inject, onMounted, ref, watch } from 'vue'
|
import { computed, inject, onMounted, ref, watch } from 'vue'
|
||||||
import { BookOpen, Plus } from 'lucide-vue-next'
|
import { BookOpen, Plus } from 'lucide-vue-next'
|
||||||
import { updateDocumentTitle } from '@/utils'
|
import { updateDocumentTitle } from '@/utils'
|
||||||
@@ -116,7 +115,7 @@ const currentCategory = ref(null)
|
|||||||
const title = ref('')
|
const title = ref('')
|
||||||
const certification = ref(false)
|
const certification = ref(false)
|
||||||
const filters = ref({})
|
const filters = ref({})
|
||||||
const currentTab = useRouteQuery('tab', 'Live')
|
const currentTab = ref('Live')
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setFiltersFromQuery()
|
setFiltersFromQuery()
|
||||||
@@ -285,10 +284,14 @@ const courseTabs = computed(() => {
|
|||||||
label: __('Upcoming'),
|
label: __('Upcoming'),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
if (user.data?.is_student) {
|
if (
|
||||||
tabs.push({ label: __('Enrolled') })
|
user.data?.is_moderator ||
|
||||||
} else if (user.data) {
|
user.data?.is_instructor ||
|
||||||
|
user.data?.is_evaluator
|
||||||
|
) {
|
||||||
tabs.push({ label: __('Created') })
|
tabs.push({ label: __('Created') })
|
||||||
|
} else if (user.data) {
|
||||||
|
tabs.push({ label: __('Enrolled') })
|
||||||
}
|
}
|
||||||
return tabs
|
return tabs
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -92,13 +92,13 @@ import LessonHelp from '@/components/LessonHelp.vue'
|
|||||||
import { ChevronRight } from 'lucide-vue-next'
|
import { ChevronRight } from 'lucide-vue-next'
|
||||||
import { updateDocumentTitle, createToast, getEditorTools } from '@/utils'
|
import { updateDocumentTitle, createToast, getEditorTools } from '@/utils'
|
||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
import { useSettings } from '@/stores/settings'
|
import { useOnboarding } from 'frappe-ui/frappe'
|
||||||
|
|
||||||
const editor = ref(null)
|
const editor = ref(null)
|
||||||
const instructorEditor = ref(null)
|
const instructorEditor = ref(null)
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const openInstructorEditor = ref(false)
|
const openInstructorEditor = ref(false)
|
||||||
const settingsStore = useSettings()
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
let autoSaveInterval
|
let autoSaveInterval
|
||||||
let showSuccessMessage = false
|
let showSuccessMessage = false
|
||||||
|
|
||||||
@@ -395,10 +395,8 @@ const createNewLesson = () => {
|
|||||||
{
|
{
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
capture('lesson_created')
|
capture('lesson_created')
|
||||||
|
updateOnboardingStep('create_first_lesson')
|
||||||
showToast('Success', 'Lesson created successfully', 'check')
|
showToast('Success', 'Lesson created successfully', 'check')
|
||||||
/* if (!settingsStore.onboardingDetails.data?.is_onboarded) {
|
|
||||||
settingsStore.onboardingDetails.reload()
|
|
||||||
} */
|
|
||||||
lessonDetails.reload()
|
lessonDetails.reload()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ import { Plus, Trash2 } from 'lucide-vue-next'
|
|||||||
import Question from '@/components/Modals/Question.vue'
|
import Question from '@/components/Modals/Question.vue'
|
||||||
import { showToast, updateDocumentTitle } from '@/utils'
|
import { showToast, updateDocumentTitle } from '@/utils'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useOnboarding } from 'frappe-ui/frappe'
|
||||||
|
|
||||||
const showQuestionModal = ref(false)
|
const showQuestionModal = ref(false)
|
||||||
const currentQuestion = reactive({
|
const currentQuestion = reactive({
|
||||||
@@ -220,6 +221,7 @@ const currentQuestion = reactive({
|
|||||||
})
|
})
|
||||||
const user = inject('$user')
|
const user = inject('$user')
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const { updateOnboardingStep } = useOnboarding('learning')
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
quizID: {
|
quizID: {
|
||||||
@@ -337,6 +339,7 @@ const createQuiz = () => {
|
|||||||
{
|
{
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
showToast(__('Success'), __('Quiz created successfully'), 'check')
|
showToast(__('Success'), __('Quiz created successfully'), 'check')
|
||||||
|
updateOnboardingStep('create_first_quiz')
|
||||||
router.push({
|
router.push({
|
||||||
name: 'QuizForm',
|
name: 'QuizForm',
|
||||||
params: { quizID: data.name },
|
params: { quizID: data.name },
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default defineConfig({
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
allowedHosts: ['fs', 'bs'],
|
allowedHosts: ['fs', 'onb'],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
2748
frontend/yarn.lock
2748
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -177,7 +177,9 @@ def get_user_info():
|
|||||||
user.is_instructor = "Course Creator" in user.roles
|
user.is_instructor = "Course Creator" in user.roles
|
||||||
user.is_moderator = "Moderator" in user.roles
|
user.is_moderator = "Moderator" in user.roles
|
||||||
user.is_evaluator = "Batch Evaluator" in user.roles
|
user.is_evaluator = "Batch Evaluator" in user.roles
|
||||||
user.is_student = "LMS Student" in user.roles
|
user.is_student = (
|
||||||
|
not user.is_instructor and not user.is_moderator and not user.is_evaluator
|
||||||
|
)
|
||||||
user.is_fc_site = is_fc_site()
|
user.is_fc_site = is_fc_site()
|
||||||
user.is_system_manager = "System Manager" in user.roles
|
user.is_system_manager = "System Manager" in user.roles
|
||||||
if user.is_fc_site and user.is_system_manager:
|
if user.is_fc_site and user.is_system_manager:
|
||||||
|
|||||||
19
lms/lms/onboarding.py
Normal file
19
lms/lms/onboarding.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
def get_first_course():
|
||||||
|
course = frappe.get_all(
|
||||||
|
"LMS Course",
|
||||||
|
fields=["name"],
|
||||||
|
order_by="creation",
|
||||||
|
limit=1,
|
||||||
|
)
|
||||||
|
return course[0].name if course else None
|
||||||
|
|
||||||
|
def get_first_batch():
|
||||||
|
batch = frappe.get_all(
|
||||||
|
"LMS Batch",
|
||||||
|
fields=["name"],
|
||||||
|
order_by="creation",
|
||||||
|
limit=1,
|
||||||
|
)
|
||||||
|
return batch[0].name if batch else None
|
||||||
@@ -533,10 +533,11 @@ def has_course_evaluator_role(member=None):
|
|||||||
|
|
||||||
|
|
||||||
def has_student_role(member=None):
|
def has_student_role(member=None):
|
||||||
return frappe.db.get_value(
|
roles = frappe.get_roles(member or frappe.session.user)
|
||||||
"Has Role",
|
return (
|
||||||
{"parent": member or frappe.session.user, "role": "LMS Student"},
|
"Moderator" not in roles
|
||||||
"name",
|
and "Course Creator" not in roles
|
||||||
|
and "Batch Evaluator" not in roles
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "frappe_lms",
|
"name": "frappe_lms",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Easy to use, open-source, Learning Management System",
|
"description": "Easy to use, open-source, Learning Management System",
|
||||||
"workspaces1": [
|
"workspaces": [
|
||||||
"frappe-ui",
|
"frappe-ui",
|
||||||
"frontend"
|
"frontend"
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user