From 625ddac65a5d53cfb88aa643ce4da255d42eb206 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 14 Aug 2025 20:26:46 +0530 Subject: [PATCH 1/6] refactor: learning path --- frontend/components.d.ts | 1 + frontend/src/components/AppSidebar.vue | 30 +- frontend/src/components/BatchOverlay.vue | 16 +- frontend/src/components/CourseCard.vue | 4 +- frontend/src/components/Settings/Members.vue | 10 +- frontend/src/components/Settings/Settings.vue | 7 - frontend/src/components/SidebarLink.vue | 2 +- frontend/src/pages/ProgramForm.vue | 379 ------------- frontend/src/pages/Programs.vue | 216 ------- frontend/src/pages/Programs/ProgramForm.vue | 528 ++++++++++++++++++ frontend/src/pages/Programs/Programs.vue | 103 ++++ frontend/src/pages/Programs/types.ts | 51 ++ frontend/src/router.js | 8 +- frontend/src/stores/settings.js | 8 - frontend/src/stores/sidebar.js | 1 + frontend/src/stores/user.js | 5 +- lms/lms/doctype/lms_program/lms_program.json | 68 ++- .../lms_program_course.json | 8 +- .../lms_program_member.json | 8 +- .../doctype/lms_settings/lms_settings.json | 9 +- lms/lms/utils.py | 4 - 21 files changed, 785 insertions(+), 681 deletions(-) delete mode 100644 frontend/src/pages/ProgramForm.vue delete mode 100644 frontend/src/pages/Programs.vue create mode 100644 frontend/src/pages/Programs/ProgramForm.vue create mode 100644 frontend/src/pages/Programs/Programs.vue create mode 100644 frontend/src/pages/Programs/types.ts diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 88e57b82..cd85de64 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -88,6 +88,7 @@ declare module 'vue' { PageModal: typeof import('./src/components/Modals/PageModal.vue')['default'] PaymentSettings: typeof import('./src/components/Settings/PaymentSettings.vue')['default'] Play: typeof import('./src/components/Icons/Play.vue')['default'] + ProgramForm: typeof import('./src/components/Modals/ProgramForm.vue')['default'] ProgressBar: typeof import('./src/components/ProgressBar.vue')['default'] Question: typeof import('./src/components/Modals/Question.vue')['default'] Quiz: typeof import('./src/components/Quiz.vue')['default'] diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue index f1421537..c76576b9 100644 --- a/frontend/src/components/AppSidebar.vue +++ b/frontend/src/components/AppSidebar.vue @@ -361,34 +361,20 @@ const addProgrammingExercises = () => { } const addPrograms = () => { - let activeFor = ['Programs', 'ProgramForm'] + let activeFor = ['Programs'] let index = 1 - let canAddProgram = false - if ( - !isInstructor.value && - !isModerator.value && - settingsStore.learningPaths.data - ) { - sidebarLinks.value = sidebarLinks.value.filter( - (link) => link.label !== 'Courses' - ) + if (!isInstructor.value && !isModerator.value) { activeFor.push('CourseDetail') activeFor.push('Lesson') - index = 0 - canAddProgram = true - } else if (isInstructor.value || isModerator.value) { - canAddProgram = true } - if (canAddProgram) { - sidebarLinks.value.splice(index, 0, { - label: 'Programs', - icon: 'Route', - to: 'Programs', - activeFor: activeFor, - }) - } + sidebarLinks.value.splice(index, 0, { + label: 'Programs', + icon: 'Route', + to: 'Programs', + activeFor: activeFor, + }) } const openPageModal = (link) => { diff --git a/frontend/src/components/BatchOverlay.vue b/frontend/src/components/BatchOverlay.vue index fdd3a893..cc846d37 100644 --- a/frontend/src/components/BatchOverlay.vue +++ b/frontend/src/components/BatchOverlay.vue @@ -56,7 +56,7 @@
- {{ isModerator ? __('Manage Batch') : __('Visit Batch') }} + {{ isStudent ? __('Visit Batch') : __('Manage Batch') }} @@ -204,4 +204,12 @@ const isStudent = computed(() => { const isModerator = computed(() => { return user.data?.is_moderator }) + +const isEvaluator = computed(() => { + return user.data?.is_evaluator +}) + +const canAccessBatch = computed(() => { + return isModerator.value || isStudent.value || isEvaluator.value +}) diff --git a/frontend/src/components/CourseCard.vue b/frontend/src/components/CourseCard.vue index 00b13bb1..c1740b46 100644 --- a/frontend/src/components/CourseCard.vue +++ b/frontend/src/components/CourseCard.vue @@ -1,7 +1,7 @@ diff --git a/frontend/src/pages/Programs.vue b/frontend/src/pages/Programs.vue deleted file mode 100644 index 15cd828a..00000000 --- a/frontend/src/pages/Programs.vue +++ /dev/null @@ -1,216 +0,0 @@ - - diff --git a/frontend/src/pages/Programs/ProgramForm.vue b/frontend/src/pages/Programs/ProgramForm.vue new file mode 100644 index 00000000..02ca95ce --- /dev/null +++ b/frontend/src/pages/Programs/ProgramForm.vue @@ -0,0 +1,528 @@ + + diff --git a/frontend/src/pages/Programs/Programs.vue b/frontend/src/pages/Programs/Programs.vue new file mode 100644 index 00000000..dad58e58 --- /dev/null +++ b/frontend/src/pages/Programs/Programs.vue @@ -0,0 +1,103 @@ + + diff --git a/frontend/src/pages/Programs/types.ts b/frontend/src/pages/Programs/types.ts new file mode 100644 index 00000000..2b352c27 --- /dev/null +++ b/frontend/src/pages/Programs/types.ts @@ -0,0 +1,51 @@ +interface Program { + name: string; + title: string; + published: boolean; + enforce_course_order: boolean; + allow_self_enrollment: boolean; + program_courses: ProgramCourse[]; + program_batches: ProgramMember[]; + course_count: number; + member_count: number; +} + +interface ProgramCourse { + course: string; + course_title: string; + idx: number; + name: string; +} + +interface ProgramMember { + member: string; + full_name: string; + progress: number; + idx: number; + name: string; +} + +interface Programs { + data: Program[]; + reload: () => void; + hasNextPage: boolean; + next: () => void; + setValue: { + submit: ( + data: Program, + options?: { onSuccess?: () => void } + ) => void; + }; + insert: { + submit: ( + data: Program, + options?: { onSuccess?: () => void } + ) => void; + }; + delete: { + submit: ( + name: string, + options?: { onSuccess?: () => void } + ) => void; + }; +} \ No newline at end of file diff --git a/frontend/src/router.js b/frontend/src/router.js index c9db6b26..bab1d69b 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -183,16 +183,10 @@ 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'), + component: () => import('@/pages/Programs/Programs.vue'), }, { path: '/assignments', diff --git a/frontend/src/stores/settings.js b/frontend/src/stores/settings.js index b323ef0f..68c3cd06 100644 --- a/frontend/src/stores/settings.js +++ b/frontend/src/stores/settings.js @@ -8,13 +8,6 @@ export const useSettings = defineStore('settings', () => { const isSettingsOpen = ref(false) const activeTab = ref(null) - const learningPaths = createResource({ - url: 'lms.lms.api.get_lms_setting', - params: { field: 'enable_learning_paths' }, - auto: true, - cache: ['learningPath'], - }) - const allowGuestAccess = createResource({ url: 'lms.lms.api.get_lms_setting', params: { field: 'allow_guest_access' }, @@ -38,7 +31,6 @@ export const useSettings = defineStore('settings', () => { return { isSettingsOpen, activeTab, - learningPaths, allowGuestAccess, preventSkippingVideos, sidebarSettings, diff --git a/frontend/src/stores/sidebar.js b/frontend/src/stores/sidebar.js index f7faa9f5..734d3485 100644 --- a/frontend/src/stores/sidebar.js +++ b/frontend/src/stores/sidebar.js @@ -4,6 +4,7 @@ import { ref } from 'vue' export const useSidebar = defineStore('sidebar', () => { const isSidebarCollapsed = ref(false) const isWebpagesCollapsed = ref(true) + const canAccessPrograms = ref(false) if (localStorage.getItem('isSidebarCollapsed')) { isSidebarCollapsed.value = JSON.parse( diff --git a/frontend/src/stores/user.js b/frontend/src/stores/user.js index cb744ab5..1148bd29 100644 --- a/frontend/src/stores/user.js +++ b/frontend/src/stores/user.js @@ -1,15 +1,12 @@ import { defineStore } from 'pinia' import { createResource } from 'frappe-ui' -import { useRouter } from 'vue-router' - -const router = useRouter() export const usersStore = defineStore('lms-users', () => { let userResource = createResource({ url: 'lms.lms.api.get_user_info', onError(error) { if (error && error.exc_type === 'AuthenticationError') { - router.push('/login') + window.location.href = '/login' } }, auto: true, diff --git a/lms/lms/doctype/lms_program/lms_program.json b/lms/lms/doctype/lms_program/lms_program.json index f92c689a..0684f481 100644 --- a/lms/lms/doctype/lms_program/lms_program.json +++ b/lms/lms/doctype/lms_program/lms_program.json @@ -6,9 +6,19 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ + "published", "title", + "column_break_cwjx", + "enforce_course_order", + "column_break_mikl", + "allow_self_enrollment", + "section_break_vhhu", "program_courses", - "program_members" + "program_members", + "section_break_pppe", + "course_count", + "column_break_qwhf", + "member_count" ], "fields": [ { @@ -30,12 +40,61 @@ "label": "Title", "reqd": 1, "unique": 1 + }, + { + "default": "0", + "fieldname": "published", + "fieldtype": "Check", + "label": "Published" + }, + { + "default": "0", + "fieldname": "enforce_course_order", + "fieldtype": "Check", + "label": "Enforce Course Order" + }, + { + "fieldname": "section_break_vhhu", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_cwjx", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "allow_self_enrollment", + "fieldtype": "Check", + "label": "Allow Self Enrollment" + }, + { + "fieldname": "section_break_pppe", + "fieldtype": "Section Break" + }, + { + "fieldname": "course_count", + "fieldtype": "Int", + "label": "Course Count" + }, + { + "fieldname": "column_break_qwhf", + "fieldtype": "Column Break" + }, + { + "fieldname": "member_count", + "fieldtype": "Int", + "label": "Member Count" + }, + { + "fieldname": "column_break_mikl", + "fieldtype": "Column Break" } ], + "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-11-28 22:06:16.742867", - "modified_by": "Administrator", + "modified": "2025-08-13 14:36:59.168945", + "modified_by": "sayali@frappe.io", "module": "LMS", "name": "LMS Program", "naming_rule": "By fieldname", @@ -78,8 +137,9 @@ "write": 1 } ], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/lms/lms/doctype/lms_program_course/lms_program_course.json b/lms/lms/doctype/lms_program_course/lms_program_course.json index a7edc7ed..d504946d 100644 --- a/lms/lms/doctype/lms_program_course/lms_program_course.json +++ b/lms/lms/doctype/lms_program_course/lms_program_course.json @@ -27,16 +27,18 @@ "read_only": 1 } ], + "grid_page_length": 50, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-11-18 12:43:46.800199", - "modified_by": "Administrator", + "modified": "2025-08-13 17:32:43.554055", + "modified_by": "sayali@frappe.io", "module": "LMS", "name": "LMS Program Course", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/lms/lms/doctype/lms_program_member/lms_program_member.json b/lms/lms/doctype/lms_program_member/lms_program_member.json index f629e1f2..bd5d5ff0 100644 --- a/lms/lms/doctype/lms_program_member/lms_program_member.json +++ b/lms/lms/doctype/lms_program_member/lms_program_member.json @@ -35,16 +35,18 @@ "label": "Progress" } ], + "grid_page_length": 50, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-11-21 12:51:31.882576", - "modified_by": "Administrator", + "modified": "2025-08-13 17:33:00.265037", + "modified_by": "sayali@frappe.io", "module": "LMS", "name": "LMS Program Member", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/lms/lms/doctype/lms_settings/lms_settings.json b/lms/lms/doctype/lms_settings/lms_settings.json index 226da133..8be2517c 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.json +++ b/lms/lms/doctype/lms_settings/lms_settings.json @@ -11,7 +11,6 @@ "persona_captured", "column_break_zdel", "allow_guest_access", - "enable_learning_paths", "prevent_skipping_videos", "column_break_bjis", "unsplash_access_key", @@ -339,12 +338,6 @@ "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", @@ -429,7 +422,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-07-01 17:01:58.466698", + "modified": "2025-08-12 16:47:49.983018", "modified_by": "sayali@frappe.io", "module": "LMS", "name": "LMS Settings", diff --git a/lms/lms/utils.py b/lms/lms/utils.py index d81ff843..b39dc544 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -1921,10 +1921,6 @@ def update_certificate_purchase(course, payment_name): 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( From acd003814aae23c199c2b2196de80f7abf9f2e0c Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 18 Aug 2025 15:51:07 +0530 Subject: [PATCH 2/6] refactor: program list for students --- frontend/src/pages/Programs/ProgramForm.vue | 45 +++++++--- frontend/src/pages/Programs/Programs.vue | 74 +++++++++------- .../src/pages/Programs/StudentPrograms.vue | 87 +++++++++++++++++++ lms/lms/doctype/lms_program/lms_program.json | 11 ++- lms/lms/doctype/lms_program/lms_program.py | 11 +++ lms/lms/utils.py | 33 +++++-- lms/patches.txt | 3 +- lms/patches/v2_0/count_in_program.py | 18 ++++ 8 files changed, 235 insertions(+), 47 deletions(-) create mode 100644 frontend/src/pages/Programs/StudentPrograms.vue create mode 100644 lms/patches/v2_0/count_in_program.py diff --git a/frontend/src/pages/Programs/ProgramForm.vue b/frontend/src/pages/Programs/ProgramForm.vue index 02ca95ce..b2db0621 100644 --- a/frontend/src/pages/Programs/ProgramForm.vue +++ b/frontend/src/pages/Programs/ProgramForm.vue @@ -2,16 +2,9 @@ + diff --git a/lms/lms/doctype/lms_program/lms_program.json b/lms/lms/doctype/lms_program/lms_program.json index 0684f481..bd3936ad 100644 --- a/lms/lms/doctype/lms_program/lms_program.json +++ b/lms/lms/doctype/lms_program/lms_program.json @@ -93,7 +93,7 @@ "grid_page_length": 50, "index_web_pages_for_search": 1, "links": [], - "modified": "2025-08-13 14:36:59.168945", + "modified": "2025-08-18 13:08:04.993241", "modified_by": "sayali@frappe.io", "module": "LMS", "name": "LMS Program", @@ -135,6 +135,15 @@ "role": "Course Creator", "share": 1, "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "LMS Student", + "share": 1 } ], "row_format": "Dynamic", diff --git a/lms/lms/doctype/lms_program/lms_program.py b/lms/lms/doctype/lms_program/lms_program.py index d0d6fa13..95bd312b 100644 --- a/lms/lms/doctype/lms_program/lms_program.py +++ b/lms/lms/doctype/lms_program/lms_program.py @@ -10,6 +10,7 @@ class LMSProgram(Document): def validate(self): self.validate_program_courses() self.validate_program_members() + self.update_count() def validate_program_courses(self): courses = [row.course for row in self.program_courses] @@ -30,3 +31,13 @@ class LMSProgram(Document): frappe.bold(next(iter(duplicates))) ) ) + + def update_count(self): + course_count = len(self.program_courses) + member_count = len(self.program_members) + + if self.course_count != course_count: + self.course_count = course_count + + if self.member_count != member_count: + self.member_count = member_count diff --git a/lms/lms/utils.py b/lms/lms/utils.py index b39dc544..c0682a44 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -1919,9 +1919,33 @@ def update_certificate_purchase(course, payment_name): @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"]) + enrolled_programs = frappe.get_all( + "LMS Program Member", {"member": frappe.session.user}, ["parent as name", "progress"] + ) + for program in enrolled_programs: + program.update( + frappe.db.get_value( + "LMS Program", program.name, ["name", "course_count", "member_count"], as_dict=True + ) + ) + published_programs = frappe.get_all( + "LMS Program", + { + "published": 1, + "allow_self_enrollment": 1, + }, + ["name", "course_count", "member_count"], + ) + published_programs = [program for program in published_programs if program not in enrolled_programs] + + return { + "enrolled": enrolled_programs, + "published": published_programs, + } + + +""" def set_program_details(programs): for program in programs: program_courses = frappe.get_all( "LMS Program Course", {"parent": program.name}, ["course"], order_by="idx" @@ -1939,10 +1963,7 @@ def get_programs(): previous_progress = details.membership.progress if details.membership else 0 program.courses.append(details) - - program.members = frappe.db.count("LMS Program Member", {"parent": program.name}) - - return programs + """ @frappe.whitelist() diff --git a/lms/patches.txt b/lms/patches.txt index efc2345c..79b4afad 100644 --- a/lms/patches.txt +++ b/lms/patches.txt @@ -109,4 +109,5 @@ lms.patches.v2_0.link_zoom_account_to_live_class lms.patches.v2_0.link_zoom_account_to_batch lms.patches.v2_0.sidebar_for_certified_members lms.patches.v2_0.move_batch_instructors_to_evaluators -lms.patches.v2_0.enable_programming_exercises_in_sidebar \ No newline at end of file +lms.patches.v2_0.enable_programming_exercises_in_sidebar +lms.patches.v2_0.count_in_program \ No newline at end of file diff --git a/lms/patches/v2_0/count_in_program.py b/lms/patches/v2_0/count_in_program.py new file mode 100644 index 00000000..fb263a91 --- /dev/null +++ b/lms/patches/v2_0/count_in_program.py @@ -0,0 +1,18 @@ +import frappe + + +def execute(): + programs = frappe.get_all("LMS Program", pluck="name") + + for program in programs: + course_count = frappe.db.count( + "LMS Program Course", + {"parent": program, "parenttype": "LMS Program", "parentfield": "program_courses"}, + ) + frappe.db.set_value("LMS Program", program, "course_count", course_count) + + member_count = frappe.db.count( + "LMS Program Member", + {"parent": program, "parenttype": "LMS Program", "parentfield": "program_members"}, + ) + frappe.db.set_value("LMS Program", program, "member_count", member_count) From 9d3b6e0556a8b651e02930df60005e407c93e2f3 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Tue, 19 Aug 2025 17:33:20 +0530 Subject: [PATCH 3/6] feat: program self enrollment --- frontend/src/components/AppSidebar.vue | 7 +- frontend/src/pages/Lesson.vue | 5 +- frontend/src/pages/Programs/ProgramDetail.vue | 135 +++++++++++++++ .../src/pages/Programs/ProgramEnrollment.vue | 157 ++++++++++++++++++ frontend/src/pages/Programs/ProgramForm.vue | 27 ++- frontend/src/pages/Programs/Programs.vue | 3 +- .../src/pages/Programs/StudentPrograms.vue | 101 ++++++----- frontend/src/pages/Programs/types.ts | 1 - frontend/src/router.js | 6 + lms/lms/doctype/lms_payment/lms_payment.json | 10 +- lms/lms/doctype/lms_program/lms_program.json | 9 +- lms/lms/utils.py | 114 ++++++------- 12 files changed, 437 insertions(+), 138 deletions(-) create mode 100644 frontend/src/pages/Programs/ProgramDetail.vue create mode 100644 frontend/src/pages/Programs/ProgramEnrollment.vue diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue index c76576b9..b09f2536 100644 --- a/frontend/src/components/AppSidebar.vue +++ b/frontend/src/components/AppSidebar.vue @@ -361,14 +361,9 @@ const addProgrammingExercises = () => { } const addPrograms = () => { - let activeFor = ['Programs'] + let activeFor = ['Programs', 'ProgramDetail'] let index = 1 - if (!isInstructor.value && !isModerator.value) { - activeFor.push('CourseDetail') - activeFor.push('Lesson') - } - sidebarLinks.value.splice(index, 0, { label: 'Programs', icon: 'Route', diff --git a/frontend/src/pages/Lesson.vue b/frontend/src/pages/Lesson.vue index 5b869c96..cb0bf7a0 100644 --- a/frontend/src/pages/Lesson.vue +++ b/frontend/src/pages/Lesson.vue @@ -209,12 +209,13 @@ v-else class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-outline-gray-2 prose-th:border-outline-gray-2 prose-td:relative prose-th:relative prose-th:bg-surface-gray-2 prose-sm max-w-none !whitespace-normal mt-8" > - + /> -->
+
+ +
+
+
+
+ {{ program.data.name }} +
+ + + +
+
+
+ + +
+
+
+ + diff --git a/frontend/src/pages/Programs/ProgramEnrollment.vue b/frontend/src/pages/Programs/ProgramEnrollment.vue new file mode 100644 index 00000000..b82ba8c6 --- /dev/null +++ b/frontend/src/pages/Programs/ProgramEnrollment.vue @@ -0,0 +1,157 @@ + + diff --git a/frontend/src/pages/Programs/ProgramForm.vue b/frontend/src/pages/Programs/ProgramForm.vue index b2db0621..0e1783b6 100644 --- a/frontend/src/pages/Programs/ProgramForm.vue +++ b/frontend/src/pages/Programs/ProgramForm.vue @@ -2,10 +2,21 @@ + diff --git a/lms/lms/utils.py b/lms/lms/utils.py index 012ca815..6faecf07 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -1965,6 +1965,7 @@ def get_program_details(program_name): program_courses = frappe.get_all( "LMS Program Course", {"parent": program_name}, ["course"], order_by="idx" ) + program.courses = [] previous_progress = 0 for i, course in enumerate(program_courses): @@ -1978,6 +1979,13 @@ def get_program_details(program_name): previous_progress = details.membership.progress if details.membership else 0 program.courses.append(details) + if frappe.session.user != "Guest": + program.progress = frappe.db.get_value( + "LMS Program Member", + {"parent": program_name, "member": frappe.session.user}, + "progress", + ) + return program From 5e607c3b8e76ed085dc23ea29c35dc626f5002ad Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 20 Aug 2025 13:11:22 +0530 Subject: [PATCH 5/6] refactor: sidebar visibility of programs --- frontend/src/components/AppSidebar.vue | 15 ++++++- frontend/src/components/MobileLayout.vue | 40 ++++++++++++++++--- frontend/src/pages/Programs/Programs.vue | 2 +- .../src/pages/Programs/StudentPrograms.vue | 7 +++- .../lms_course_progress.py | 2 + .../doctype/lms_enrollment/lms_enrollment.py | 27 ++++++------- lms/lms/doctype/lms_program/lms_program.json | 11 ++++- lms/lms/utils.py | 4 +- 8 files changed, 80 insertions(+), 28 deletions(-) diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue index b09f2536..2f99bbed 100644 --- a/frontend/src/components/AppSidebar.vue +++ b/frontend/src/components/AppSidebar.vue @@ -196,7 +196,7 @@ import { usersStore } from '@/stores/user' import { sessionStore } from '@/stores/session' import { useSidebar } from '@/stores/sidebar' 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 { capture } from '@/telemetry' import LMSLogo from '@/components/Icons/LMSLogo.vue' @@ -214,6 +214,7 @@ import { Users, BookText, Zap, + Check, } from 'lucide-vue-next' import { TrialBanner, @@ -360,7 +361,9 @@ const addProgrammingExercises = () => { } } -const addPrograms = () => { +const addPrograms = async () => { + let canAddProgram = await checkIfCanAddProgram() + if (!canAddProgram) return let activeFor = ['Programs', 'ProgramDetail'] 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) => { showPageModal.value = true pageToEdit.value = link diff --git a/frontend/src/components/MobileLayout.vue b/frontend/src/components/MobileLayout.vue index 40cb3855..7fb29b87 100644 --- a/frontend/src/components/MobileLayout.vue +++ b/frontend/src/components/MobileLayout.vue @@ -56,6 +56,7 @@