From e25f1619805cd5a2db44cf62fc2ad9b0eab5f498 Mon Sep 17 00:00:00 2001 From: Ejaaz Khan Date: Sun, 10 Nov 2024 17:48:26 +0530 Subject: [PATCH 01/30] feat: allow same date live class creation --- .../src/components/Modals/LiveClassModal.vue | 21 +++++++++++++------ frontend/src/utils/dayjs.js | 4 ++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/Modals/LiveClassModal.vue b/frontend/src/components/Modals/LiveClassModal.vue index 71f5e9ab..05918851 100644 --- a/frontend/src/components/Modals/LiveClassModal.vue +++ b/frontend/src/components/Modals/LiveClassModal.vue @@ -161,21 +161,30 @@ const submitLiveClass = (close) => { if (!liveClass.date) { return 'Please select a date.' } - if (dayjs(liveClass.date).isSameOrBefore(dayjs(), 'day')) { - return 'Please select a future date.' - } if (!liveClass.time) { return 'Please select a time.' } + if (!liveClass.timezone) { + return 'Please select a timezone.' + } if (!valideTime()) { return 'Please enter a valid time in the format HH:mm.' } + const liveClassDateTime = dayjs(`${liveClass.date}T${liveClass.time}`).tz( + liveClass.timezone, + true + ) + if ( + liveClassDateTime.isSameOrBefore( + dayjs().tz(liveClass.timezone, false), + 'minute' + ) + ) { + return 'Please select a future date and time.' + } if (!liveClass.duration) { return 'Please select a duration.' } - if (!liveClass.timezone) { - return 'Please select a timezone.' - } }, onSuccess() { liveClasses.value.reload() diff --git a/frontend/src/utils/dayjs.js b/frontend/src/utils/dayjs.js index b5cecdc0..d7057b12 100644 --- a/frontend/src/utils/dayjs.js +++ b/frontend/src/utils/dayjs.js @@ -5,6 +5,8 @@ import updateLocale from 'dayjs/esm/plugin/updateLocale' import isToday from 'dayjs/esm/plugin/isToday' import isSameOrBefore from 'dayjs/esm/plugin/isSameOrBefore' import isSameOrAfter from 'dayjs/esm/plugin/isSameOrAfter' +import utc from 'dayjs/esm/plugin/utc' +import timezone from 'dayjs/esm/plugin/timezone' dayjs.extend(updateLocale) dayjs.extend(relativeTime) @@ -12,5 +14,7 @@ dayjs.extend(localizedFormat) dayjs.extend(isToday) dayjs.extend(isSameOrBefore) dayjs.extend(isSameOrAfter) +dayjs.extend(utc) +dayjs.extend(timezone) export default dayjs From be49ba6d04de8e5ccd5967b98c89de4e8f475302 Mon Sep 17 00:00:00 2001 From: Ejaaz Khan Date: Wed, 13 Nov 2024 00:37:15 +0530 Subject: [PATCH 02/30] refactor: add translate in all error messages --- frontend/src/components/Modals/LiveClassModal.vue | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Modals/LiveClassModal.vue b/frontend/src/components/Modals/LiveClassModal.vue index 05918851..d5a6bb29 100644 --- a/frontend/src/components/Modals/LiveClassModal.vue +++ b/frontend/src/components/Modals/LiveClassModal.vue @@ -156,19 +156,19 @@ const submitLiveClass = (close) => { return createLiveClass.submit(liveClass, { validate() { if (!liveClass.title) { - return 'Please enter a title.' + return __('Please enter a title.') } if (!liveClass.date) { - return 'Please select a date.' + return __('Please select a date.') } if (!liveClass.time) { - return 'Please select a time.' + return __('Please select a time.') } if (!liveClass.timezone) { - return 'Please select a timezone.' + return __('Please select a timezone.') } if (!valideTime()) { - return 'Please enter a valid time in the format HH:mm.' + return __('Please enter a valid time in the format HH:mm.') } const liveClassDateTime = dayjs(`${liveClass.date}T${liveClass.time}`).tz( liveClass.timezone, @@ -180,10 +180,10 @@ const submitLiveClass = (close) => { 'minute' ) ) { - return 'Please select a future date and time.' + return __('Please select a future date and time.') } if (!liveClass.duration) { - return 'Please select a duration.' + return __('Please select a duration.') } }, onSuccess() { From 6a70ed18d8841b1b1e34a7e2d2770afdd7e78f73 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Fri, 15 Nov 2024 20:36:15 +0530 Subject: [PATCH 03/30] fix: misc issues --- frontend/src/components/AppSidebar.vue | 1 - frontend/src/components/Modals/ChapterModal.vue | 16 +--------------- frontend/src/pages/Courses.vue | 3 ++- frontend/src/pages/SCORMChapter.vue | 5 ++--- 4 files changed, 5 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue index e350dbbb..a0b94a83 100644 --- a/frontend/src/components/AppSidebar.vue +++ b/frontend/src/components/AppSidebar.vue @@ -219,7 +219,6 @@ watch(userResource, () => { }) const toggleSidebar = () => { - console.log(sidebarStore.isSidebarCollapsed) sidebarStore.isSidebarCollapsed = !sidebarStore.isSidebarCollapsed } diff --git a/frontend/src/components/Modals/ChapterModal.vue b/frontend/src/components/Modals/ChapterModal.vue index 6a9f9146..3d041e84 100644 --- a/frontend/src/components/Modals/ChapterModal.vue +++ b/frontend/src/components/Modals/ChapterModal.vue @@ -16,12 +16,7 @@ > +

{{ description }}

@@ -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, } diff --git a/frontend/src/components/Modals/Settings.vue b/frontend/src/components/Modals/Settings.vue index bf6801d4..7f49ac6f 100644 --- a/frontend/src/components/Modals/Settings.vue +++ b/frontend/src/components/Modals/Settings.vue @@ -115,21 +115,21 @@ const tabsStructure = computed(() => { label: 'Enable Learning Paths', name: 'enable_learning_paths', description: - 'This will change the default flow of the system and enforce students to go through programs assigned to them in the correct order.', + '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 and Google Calendar of the evaluator is set in the system, students will receive calendar invites to remind them of their evaluations.', + '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. Refer the docs to know more https://unsplash.com/documentation#getting-started.', + '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', }, ], diff --git a/frontend/src/pages/Courses.vue b/frontend/src/pages/Courses.vue index 114a3c4c..8fc7b1e8 100644 --- a/frontend/src/pages/Courses.vue +++ b/frontend/src/pages/Courses.vue @@ -160,30 +160,45 @@ diff --git a/frontend/src/router.js b/frontend/src/router.js index d69a1e46..d459209c 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -182,17 +182,17 @@ const routes = [ component: () => import('@/pages/QuizSubmission.vue'), props: true, }, - { - path: '/programs', - name: 'Programs', - component: () => import('@/pages/Programs.vue'), - }, { path: '/programs/:programName', name: 'ProgramForm', component: () => import('@/pages/ProgramForm.vue'), props: true, }, + { + path: '/programs', + name: 'Programs', + component: () => import('@/pages/Programs.vue'), + }, ] let router = createRouter({ diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js index aaf79771..1f3eaa11 100644 --- a/frontend/src/utils/index.js +++ b/frontend/src/utils/index.js @@ -438,6 +438,8 @@ export function getSidebarLinks() { 'Lesson', 'CourseForm', 'LessonForm', + 'Programs', + 'ProgramForm', ], }, { diff --git a/lms/lms/doctype/lms_program/lms_program.json b/lms/lms/doctype/lms_program/lms_program.json index f2806e72..5b4843fb 100644 --- a/lms/lms/doctype/lms_program/lms_program.json +++ b/lms/lms/doctype/lms_program/lms_program.json @@ -34,7 +34,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-11-18 14:08:26.958831", + "modified": "2024-11-20 12:26:02.214628", "modified_by": "Administrator", "module": "LMS", "name": "LMS Program", @@ -52,6 +52,30 @@ "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", diff --git a/lms/lms/doctype/lms_program/lms_program.py b/lms/lms/doctype/lms_program/lms_program.py index c07e3df5..0de0f5a7 100644 --- a/lms/lms/doctype/lms_program/lms_program.py +++ b/lms/lms/doctype/lms_program/lms_program.py @@ -1,9 +1,108 @@ # Copyright (c) 2024, Frappe and contributors # For license information, please see license.txt -# import frappe +import frappe +from frappe import _ from frappe.model.document import Document class LMSProgram(Document): - pass + def validate(self): + self.validate_program_courses() + self.validate_program_members() + + def on_update(self): + self.manage_acccess() + + def manage_acccess(self): + old_doc = self.get_doc_before_save() + + if not old_doc: + return + + previous_courses = [row.course for row in old_doc.program_courses] + current_courses = [row.course for row in self.program_courses] + + print("previous_courses", previous_courses) + print("current_courses", current_courses) + + previous_members = [row.member for row in old_doc.program_members] + current_members = [row.member for row in self.program_members] + + print("previous_members", previous_members) + print("current_members", current_members) + + courses_added = [ + course for course in current_courses if course not in previous_courses + ] + courses_removed = [ + course for course in previous_courses if course not in current_courses + ] + + members_added = [ + member for member in current_members if member not in previous_members + ] + members_removed = [ + member for member in previous_members if member not in current_members + ] + + print(courses_removed) + print(members_removed) + + if len(courses_added) > 0: + self.grant_program_access(current_members, courses_added) + + if len(courses_removed) > 0: + print(courses_removed) + self.revoke_program_access(current_members, courses_removed) + + if len(members_added) > 0: + self.grant_program_access(members_added, current_courses) + + if len(members_removed) > 0: + print(members_removed) + self.revoke_program_access(members_removed, current_courses) + + def grant_program_access(self, members, courses): + for course in courses: + for member in members: + enrollment = frappe.db.exists( + "LMS Enrollment", {"course": course, "member": member} + ) + if not enrollment: + enrollment = frappe.new_doc("LMS Enrollment") + enrollment.course = course + enrollment.member = member + enrollment.insert() + + def revoke_program_access(self, members, courses): + for course in courses: + print(course) + for member in members: + print(member) + enrollment = frappe.db.exists( + "LMS Enrollment", {"course": course, "member": member} + ) + print(enrollment) + if enrollment: + frappe.delete_doc("LMS Enrollment", enrollment) + + 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))) + ) + ) diff --git a/lms/lms/doctype/lms_settings/lms_settings.json b/lms/lms/doctype/lms_settings/lms_settings.json index 87a595ed..27bba9ff 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.json +++ b/lms/lms/doctype/lms_settings/lms_settings.json @@ -356,7 +356,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-11-18 12:52:41.236252", + "modified": "2024-11-20 11:55:05.358421", "modified_by": "Administrator", "module": "LMS", "name": "LMS Settings", @@ -371,6 +371,13 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "role": "LMS Student", + "share": 1 } ], "sort_field": "modified", diff --git a/lms/lms/utils.py b/lms/lms/utils.py index a02d73ab..b94c0350 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -1751,3 +1751,29 @@ 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"] + ) + + for program in programs: + program_courses = frappe.get_all( + "LMS Program Course", {"parent": program.name}, ["course"] + ) + 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 From 460edc7bc725881d5a97005d0fe5a13b18a21e4f Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Wed, 20 Nov 2024 19:52:28 +0530 Subject: [PATCH 27/30] fix: changed SCORM input from checkbox to switch with better description --- frontend/src/components/Modals/ChapterModal.vue | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Modals/ChapterModal.vue b/frontend/src/components/Modals/ChapterModal.vue index 3d041e84..0906b72b 100644 --- a/frontend/src/components/Modals/ChapterModal.vue +++ b/frontend/src/components/Modals/ChapterModal.vue @@ -17,10 +17,15 @@