refactor: sidebar visibility of programs
This commit is contained in:
@@ -196,7 +196,7 @@ 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 { Button, createResource, Tooltip } from 'frappe-ui'
|
import { Button, call, createResource, Tooltip } from 'frappe-ui'
|
||||||
import PageModal from '@/components/Modals/PageModal.vue'
|
import PageModal from '@/components/Modals/PageModal.vue'
|
||||||
import { capture } from '@/telemetry'
|
import { capture } from '@/telemetry'
|
||||||
import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
import LMSLogo from '@/components/Icons/LMSLogo.vue'
|
||||||
@@ -214,6 +214,7 @@ import {
|
|||||||
Users,
|
Users,
|
||||||
BookText,
|
BookText,
|
||||||
Zap,
|
Zap,
|
||||||
|
Check,
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import {
|
import {
|
||||||
TrialBanner,
|
TrialBanner,
|
||||||
@@ -360,7 +361,9 @@ const addProgrammingExercises = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addPrograms = () => {
|
const addPrograms = async () => {
|
||||||
|
let canAddProgram = await checkIfCanAddProgram()
|
||||||
|
if (!canAddProgram) return
|
||||||
let activeFor = ['Programs', 'ProgramDetail']
|
let activeFor = ['Programs', 'ProgramDetail']
|
||||||
let index = 1
|
let index = 1
|
||||||
|
|
||||||
@@ -372,6 +375,14 @@ const addPrograms = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkIfCanAddProgram = async () => {
|
||||||
|
if (isModerator.value || isInstructor.value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const programs = await call('lms.lms.utils.get_programs')
|
||||||
|
return programs.enrolled.length > 0 || programs.published.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
const openPageModal = (link) => {
|
const openPageModal = (link) => {
|
||||||
showPageModal.value = true
|
showPageModal.value = true
|
||||||
pageToEdit.value = link
|
pageToEdit.value = link
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { getSidebarLinks } from '@/utils'
|
import { getSidebarLinks } from '@/utils'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { call } from 'frappe-ui'
|
||||||
import { watch, ref, onMounted } from 'vue'
|
import { watch, ref, onMounted } from 'vue'
|
||||||
import { sessionStore } from '@/stores/session'
|
import { sessionStore } from '@/stores/session'
|
||||||
import { useSettings } from '@/stores/settings'
|
import { useSettings } from '@/stores/settings'
|
||||||
@@ -71,6 +72,8 @@ const sidebarLinks = ref(getSidebarLinks())
|
|||||||
const otherLinks = ref([])
|
const otherLinks = ref([])
|
||||||
const showMenu = ref(false)
|
const showMenu = ref(false)
|
||||||
const menu = ref(null)
|
const menu = ref(null)
|
||||||
|
const isModerator = ref(false)
|
||||||
|
const isInstructor = ref(false)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
sidebarSettings.reload(
|
sidebarSettings.reload(
|
||||||
@@ -134,12 +137,15 @@ const addOtherLinks = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
watch(userResource, () => {
|
watch(userResource, () => {
|
||||||
if (
|
if (userResource.data) {
|
||||||
userResource.data &&
|
isModerator.value = userResource.data.is_moderator
|
||||||
(userResource.data.is_moderator || userResource.data.is_instructor)
|
isInstructor.value = userResource.data.is_instructor
|
||||||
) {
|
addPrograms()
|
||||||
addQuizzes()
|
if (isModerator.value || isInstructor.value) {
|
||||||
addAssignments()
|
addProgrammingExercises()
|
||||||
|
addQuizzes()
|
||||||
|
addAssignments()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -159,6 +165,28 @@ const addAssignments = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addPrograms = async () => {
|
||||||
|
let canAddProgram = await checkIfCanAddProgram()
|
||||||
|
if (!canAddProgram) return
|
||||||
|
let activeFor = ['Programs', 'ProgramDetail']
|
||||||
|
let index = 1
|
||||||
|
|
||||||
|
sidebarLinks.value.splice(index, 0, {
|
||||||
|
label: 'Programs',
|
||||||
|
icon: 'Route',
|
||||||
|
to: 'Programs',
|
||||||
|
activeFor: activeFor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkIfCanAddProgram = async () => {
|
||||||
|
if (isModerator.value || isInstructor.value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const programs = await call('lms.lms.utils.get_programs')
|
||||||
|
return programs.enrolled.length > 0 || programs.published.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
let isActive = (tab) => {
|
let isActive = (tab) => {
|
||||||
return tab.activeFor?.includes(router.currentRoute.value.name)
|
return tab.activeFor?.includes(router.currentRoute.value.name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<div class="text-lg font-semibold text-ink-gray-9 mb-5">
|
<div class="text-lg font-semibold text-ink-gray-9 mb-5">
|
||||||
{{ __('{0} Programs').format(programs.data.length) }}
|
{{ __('{0} Programs').format(programs.data.length) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-3 gap-5">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-5">
|
||||||
<div
|
<div
|
||||||
v-for="program in programs.data"
|
v-for="program in programs.data"
|
||||||
@click="openForm(program.name)"
|
@click="openForm(program.name)"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="py-10 w-3/4 mx-auto">
|
<div class="py-5 px-5 w-full lg:w-3/4 lg:px-0 mx-auto">
|
||||||
<div class="flex items-center justify-between mb-5">
|
<div class="flex items-center justify-between mb-5">
|
||||||
<div class="text-lg text-ink-gray-9 font-semibold">
|
<div class="text-lg text-ink-gray-9 font-semibold">
|
||||||
{{ __('All Programs') }}
|
{{ __('All Programs') }}
|
||||||
@@ -8,7 +8,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-for="(data, category) in programs.data">
|
<div v-for="(data, category) in programs.data">
|
||||||
<div v-if="category == currentTab">
|
<div v-if="category == currentTab">
|
||||||
<div v-if="data.length > 0" class="grid grid-cols-3 gap-5">
|
<div
|
||||||
|
v-if="data.length > 0"
|
||||||
|
class="grid grid-cols-1 lg:grid-cols-3 gap-5"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="program in data"
|
v-for="program in data"
|
||||||
@click="openDetails(program.name, category)"
|
@click="openDetails(program.name, category)"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
from lms.lms.doctype.lms_enrollment.lms_enrollment import update_program_progress
|
||||||
from lms.lms.utils import get_course_progress
|
from lms.lms.utils import get_course_progress
|
||||||
|
|
||||||
|
|
||||||
@@ -19,3 +20,4 @@ class LMSCourseProgress(Document):
|
|||||||
"name",
|
"name",
|
||||||
)
|
)
|
||||||
frappe.db.set_value("LMS Enrollment", membership, "progress", progress)
|
frappe.db.set_value("LMS Enrollment", membership, "progress", progress)
|
||||||
|
update_program_progress(self.member)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class LMSEnrollment(Document):
|
|||||||
self.validate_membership_in_different_batch_same_course()
|
self.validate_membership_in_different_batch_same_course()
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.update_program_progress()
|
update_program_progress(self.member)
|
||||||
|
|
||||||
def validate_membership_in_same_batch(self):
|
def validate_membership_in_same_batch(self):
|
||||||
filters = {"member": self.member, "course": self.course, "name": ["!=", self.name]}
|
filters = {"member": self.member, "course": self.course, "name": ["!=", self.name]}
|
||||||
@@ -59,21 +59,20 @@ class LMSEnrollment(Document):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_program_progress(self):
|
|
||||||
programs = frappe.get_all("LMS Program Member", {"member": self.member}, ["parent", "name"])
|
|
||||||
|
|
||||||
for program in programs:
|
def update_program_progress(member):
|
||||||
total_progress = 0
|
programs = frappe.get_all("LMS Program Member", {"member": member}, ["parent", "name"])
|
||||||
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))
|
for program in programs:
|
||||||
frappe.db.set_value("LMS Program Member", program.name, "progress", average_progress)
|
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": 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()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"published",
|
|
||||||
"title",
|
"title",
|
||||||
|
"published",
|
||||||
"column_break_cwjx",
|
"column_break_cwjx",
|
||||||
"enforce_course_order",
|
"enforce_course_order",
|
||||||
"column_break_mikl",
|
"column_break_mikl",
|
||||||
@@ -44,12 +44,16 @@
|
|||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "published",
|
"fieldname": "published",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
"label": "Published"
|
"label": "Published"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "enforce_course_order",
|
"fieldname": "enforce_course_order",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
"label": "Enforce Course Order"
|
"label": "Enforce Course Order"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -67,6 +71,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "course_count",
|
"fieldname": "course_count",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Course Count"
|
"label": "Course Count"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -76,6 +81,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "member_count",
|
"fieldname": "member_count",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Member Count"
|
"label": "Member Count"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -86,7 +92,7 @@
|
|||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-08-19 17:31:23.516060",
|
"modified": "2025-08-20 12:28:57.238902",
|
||||||
"modified_by": "sayali@frappe.io",
|
"modified_by": "sayali@frappe.io",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Program",
|
"name": "LMS Program",
|
||||||
@@ -143,5 +149,6 @@
|
|||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
|
"title_field": "title",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1937,9 +1937,11 @@ def get_programs():
|
|||||||
["name", "course_count", "member_count"],
|
["name", "course_count", "member_count"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
programs_to_remove = []
|
||||||
for program in published_programs:
|
for program in published_programs:
|
||||||
if program.name in [p.name for p in enrolled_programs]:
|
if program.name in [p.name for p in enrolled_programs]:
|
||||||
published_programs.remove(program)
|
programs_to_remove.append(program)
|
||||||
|
published_programs = [program for program in published_programs if program not in programs_to_remove]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"enrolled": enrolled_programs,
|
"enrolled": enrolled_programs,
|
||||||
|
|||||||
Reference in New Issue
Block a user