From 47e254ed9b9e987166e8f9b99c8e241742f0ab24 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 7 Aug 2023 21:17:23 +0530 Subject: [PATCH] fix: flow UI and quiz progress --- .../doctype/course_lesson/course_lesson.py | 12 ++- lms/lms/doctype/lms_class/lms_class.js | 3 + .../scheduled_flow/scheduled_flow.json | 5 +- lms/lms/utils.py | 26 +++++-- lms/lms/widgets/CourseOutline.html | 5 +- lms/public/css/style.css | 73 +++++++++--------- lms/templates/quiz/quiz.js | 3 + lms/www/batch/learn.html | 4 +- lms/www/batch/learn.js | 8 +- lms/www/classes/class.html | 74 +++++++++++++++---- lms/www/classes/class.py | 55 +++++++++----- 11 files changed, 182 insertions(+), 86 deletions(-) diff --git a/lms/lms/doctype/course_lesson/course_lesson.py b/lms/lms/doctype/course_lesson/course_lesson.py index 96e593d0..9ff38e84 100644 --- a/lms/lms/doctype/course_lesson/course_lesson.py +++ b/lms/lms/doctype/course_lesson/course_lesson.py @@ -92,7 +92,17 @@ def save_progress(lesson, course, status): "LMS Batch Membership", {"member": frappe.session.user, "course": course} ) if not membership: - return + return 0 + + body = frappe.db.get_value("Course Lesson", lesson, "body") + macros = find_macros(body) + quizzes = [value for name, value in macros if name == "Quiz"] + + for quiz in quizzes: + if not frappe.db.exists( + "LMS Quiz Submission", {"quiz": quiz, "owner": frappe.session.user} + ): + return 0 filters = {"lesson": lesson, "owner": frappe.session.user, "course": course} if frappe.db.exists("LMS Course Progress", filters): diff --git a/lms/lms/doctype/lms_class/lms_class.js b/lms/lms/doctype/lms_class/lms_class.js index 7f3c4299..d8a510ef 100644 --- a/lms/lms/doctype/lms_class/lms_class.js +++ b/lms/lms/doctype/lms_class/lms_class.js @@ -21,10 +21,13 @@ frappe.ui.form.on("LMS Class", { }, callback: (r) => { if (r.message) { + let date = frappe.datetime.get_today(); r.message.forEach((lesson) => { let row = frm.add_child("scheduled_flow"); row.lesson = lesson.name; row.lesson_title = lesson.title; + row.date = date; + date = frappe.datetime.add_days(date, 1); }); frm.refresh_field("scheduled_flow"); } diff --git a/lms/lms/doctype/scheduled_flow/scheduled_flow.json b/lms/lms/doctype/scheduled_flow/scheduled_flow.json index 99ef1c33..18a602b5 100644 --- a/lms/lms/doctype/scheduled_flow/scheduled_flow.json +++ b/lms/lms/doctype/scheduled_flow/scheduled_flow.json @@ -43,7 +43,8 @@ "fieldname": "date", "fieldtype": "Date", "in_list_view": 1, - "label": "Date" + "label": "Date", + "reqd": 1 }, { "fetch_from": "lesson.title", @@ -56,7 +57,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-08-01 12:31:27.512437", + "modified": "2023-08-07 12:10:28.095018", "modified_by": "Administrator", "module": "LMS", "name": "Scheduled Flow", diff --git a/lms/lms/utils.py b/lms/lms/utils.py index db62cdcd..7bed5c92 100644 --- a/lms/lms/utils.py +++ b/lms/lms/utils.py @@ -136,18 +136,28 @@ def get_lesson_details(chapter): as_dict=True, ) lesson_details.number = flt(f"{chapter.idx}.{row.idx}") - lesson_details.icon = "icon-list" - macros = find_macros(lesson_details.body) + lesson_details.icon = get_lesson_icon(lesson_details.body) - for macro in macros: - if macro[0] == "YouTubeVideo" or macro[0] == "Video": - lesson_details.icon = "icon-youtube" - elif macro[0] == "Quiz": - lesson_details.icon = "icon-quiz" lessons.append(lesson_details) return lessons +def get_lesson_icon(content): + icon = None + macros = find_macros(content) + + for macro in macros: + if macro[0] == "YouTubeVideo" or macro[0] == "Video": + icon = "icon-youtube" + elif macro[0] == "Quiz": + icon = "icon-quiz" + + if not icon: + icon = "icon-list" + + return icon + + def get_tags(course): tags = frappe.db.get_value("LMS Course", course, "tags") return tags.split(",") if tags else [] @@ -336,7 +346,7 @@ def is_eligible_to_review(course, membership): def get_course_progress(course, member=None): """Returns the course progress of the session user""" - lesson_count = len(get_lessons(course)) + lesson_count = get_lessons(course, get_details=False) if not lesson_count: return 0 completed_lessons = frappe.db.count( diff --git a/lms/lms/widgets/CourseOutline.html b/lms/lms/widgets/CourseOutline.html index b513ceee..b95fbdba 100644 --- a/lms/lms/widgets/CourseOutline.html +++ b/lms/lms/widgets/CourseOutline.html @@ -36,7 +36,6 @@ - @@ -58,9 +57,7 @@ {% if membership or lesson.include_in_preview or is_instructor or has_course_moderator_role() %} diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 7037a6f6..e3a4e0be 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -58,10 +58,10 @@ body { } .rating .star-click { - --star-fill: var(--orange-500); - background: var(--gray-200); - border-radius: var(--border-radius-md); - padding: var(--padding-xs); + --star-fill: var(--orange-500); + background: var(--gray-200); + border-radius: var(--border-radius-md); + padding: var(--padding-xs); } .cta-parent { @@ -80,10 +80,10 @@ body { .field-input { border: 1px solid var(--gray-300); - border-radius: var(--border-radius-md); - padding: 0.5rem; - width: 100%; - margin-top: 0.25rem; + border-radius: var(--border-radius-md); + padding: 0.5rem; + width: 100%; + margin-top: 0.25rem; } .field-input:focus-visible { @@ -151,9 +151,9 @@ textarea.field-input { } .ce-block__content { - max-width: 100%; - padding: 0 0.5rem; - margin: 0; + max-width: 100%; + padding: 0 0.5rem; + margin: 0; } .ce-toolbar__content { @@ -206,7 +206,7 @@ textarea.field-input { } .codex-editor path { - stroke: var(--gray-800); + stroke: var(--gray-800); } .drag-handle { @@ -617,19 +617,18 @@ input[type=checkbox] { } .reviews-parent { - color: var(--gray-900); + color: var(--gray-900); } .lesson-info { - font-size: 16px; - color: var(--gray-900); - letter-spacing: -0.011em; + padding: 0.5rem; + color: var(--gray-900); + letter-spacing: -0.011em; } .lesson-links { display: flex; align-items: center; - padding: 0.5rem; color: var(--gray-900); font-size: var(--text-base); } @@ -1045,42 +1044,42 @@ pre { .certificate-parent { display: grid; - grid-template-columns: 10fr 2fr; - grid-gap: 3rem; + grid-template-columns: 10fr 2fr; + grid-gap: 3rem; } .certificate-logo { - height: 1.5rem; - margin-bottom: 4rem; + height: 1.5rem; + margin-bottom: 4rem; } .certificate-name { - font-size: 2rem; - font-weight: 500; - color: #192734; - margin-bottom: 0.25rem; + font-size: 2rem; + font-weight: 500; + color: #192734; + margin-bottom: 0.25rem; } .certificate-footer { - margin: 4rem auto 0; - width: fit-content; + margin: 4rem auto 0; + width: fit-content; } .certificate-footer-item { - color: #192734; + color: #192734; } .cursive-font { - font-family: cursive; + font-family: cursive; font-weight: 600; } .certificate-divider { - margin: 0.5rem 0; + margin: 0.5rem 0; } .certificate-expiry { - margin-left: 2rem; + margin-left: 2rem; } .column-card { @@ -2121,10 +2120,10 @@ select { .lms-card { display: flex; flex-direction: column; - border-radius: 0.75rem; - border: 1px solid var(--gray-300); + border-radius: 0.75rem; + border: 1px solid var(--gray-300); /* box-shadow: var(--shadow-sm); */ - padding: 0.5rem; + padding: 0.5rem; height: 100%; position: relative; } @@ -2203,4 +2202,10 @@ select { .rows .grid-row .grid-footer-toolbar, .grid-form-heading { cursor: none; +} + +.schedule-header { + display: flex; + font-size: var(--text-sm); + padding: 0.5rem 0.5rem 0 0.5rem; } \ No newline at end of file diff --git a/lms/templates/quiz/quiz.js b/lms/templates/quiz/quiz.js index 36c2ae08..1de0b05a 100644 --- a/lms/templates/quiz/quiz.js +++ b/lms/templates/quiz/quiz.js @@ -142,6 +142,9 @@ const quiz_summary = (e = undefined) => { $("#try-again").attr("data-submission", data.message.submission); $("#try-again").removeClass("hide"); self.quiz_submitted = true; + if (this.hasOwnProperty("marked_as_complete")) { + mark_progress(); + } }, }); }; diff --git a/lms/www/batch/learn.html b/lms/www/batch/learn.html index f268c076..71d4ef9c 100644 --- a/lms/www/batch/learn.html +++ b/lms/www/batch/learn.html @@ -39,8 +39,8 @@ {% endif %}
- {% set from_class = True if class_info else False %} - {{ widgets.CourseOutline(course=course, membership=membership, lesson_page=True, from_class=from_class) }} + {% set classname = class_info.name if class_info else False %} + {{ widgets.CourseOutline(course=course, membership=membership, lesson_page=True, classname=classname) }}
diff --git a/lms/www/batch/learn.js b/lms/www/batch/learn.js index 3b1554f3..4dccb66f 100644 --- a/lms/www/batch/learn.js +++ b/lms/www/batch/learn.js @@ -15,7 +15,6 @@ frappe.ready(() => { !self.marked_as_complete && $(".title").hasClass("is-member") ) { - self.marked_as_complete = true; mark_progress(); } }); @@ -61,8 +60,11 @@ const mark_progress = () => { status: status, }, callback: (data) => { - change_progress_indicators(); - show_certificate_if_course_completed(data); + if (data.message) { + change_progress_indicators(); + show_certificate_if_course_completed(data); + self.marked_as_complete = true; + } }, }); }; diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index fca30578..0097f450 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -10,7 +10,7 @@ {{ BreadCrumb(class_info) }}
{{ ClassDetails(class_info) }} - {{ ClassSections(class_info, class_courses, class_students, published_courses, flow) }} + {{ ClassSections(class_info, class_courses, class_students, flow) }}
@@ -82,7 +82,7 @@ -{% macro ClassSections(class_info, class_courses, class_students, published_courses, flow) %} +{% macro ClassSections(class_info, class_courses, class_students, flow) %}
{% if is_moderator %} @@ -150,7 +150,7 @@
- {{ CoursesSection(class_info, class_courses, published_courses) }} + {{ CoursesSection(class_info, class_courses) }}
{% if flow | length %} @@ -180,7 +180,7 @@ {% endmacro %} -{% macro CoursesSection(class_info, class_courses, published_courses) %} +{% macro CoursesSection(class_info, class_courses) %}
@@ -476,20 +476,64 @@ {{ _("Schedule") }}
-
-
-
- {% for lesson in flow %} -
-
- - {{ lesson.title }} - + +
+ {% for chapter in flow %} +
+
+ +
+ {{ chapter.chapter_title }} +
+
+
-
+ {% endfor %}
{% endmacro %} diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index f8380b25..fe48432f 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -10,6 +10,7 @@ from lms.lms.utils import ( has_graded_assessment, get_lesson_index, get_lesson_url, + get_lesson_icon, ) @@ -38,10 +39,6 @@ def get_context(context): as_dict=True, ) - context.published_courses = frappe.get_all( - "LMS Course", {"published": 1}, ["name", "title"] - ) - class_courses = frappe.get_all( "Class Course", {"parent": class_name}, @@ -56,15 +53,8 @@ def get_context(context): order_by="creation desc", ) - context.live_classes = frappe.get_all( - "LMS Live Class", - {"class_name": class_name, "date": [">=", getdate()]}, - ["title", "description", "time", "date", "start_url", "join_url", "owner"], - order_by="date", - ) - context.class_courses = get_class_course_details(class_courses) - context.all_courses = frappe.get_list( + context.all_courses = frappe.get_all( "LMS Course", fields=["name", "title"], limit_page_length=0 ) context.course_name_list = [course.course for course in context.class_courses] @@ -73,6 +63,17 @@ def get_context(context): class_students, class_courses, context.assessments ) context.is_student = is_student(class_students) + + if not context.is_student and not context.is_moderator and not context.is_evaluator: + raise frappe.PermissionError(_("You don't have permission to access this page.")) + + context.live_classes = frappe.get_all( + "LMS Live Class", + {"class_name": class_name, "date": [">=", getdate()]}, + ["title", "description", "time", "date", "start_url", "join_url", "owner"], + order_by="date", + ) + context.all_assignments = get_all_assignments(class_name) context.all_quizzes = get_all_quizzes(class_name) context.flow = get_scheduled_flow(class_name) @@ -191,17 +192,37 @@ def is_student(class_students): def get_scheduled_flow(class_name): + chapters = [] + lessons = frappe.get_all( - "Scheduled Flow", {"parent": class_name}, ["name", "lesson"], order_by="idx" + "Scheduled Flow", + {"parent": class_name}, + ["name", "lesson", "date", "start_time", "end_time"], + order_by="idx", ) for lesson in lessons: lesson.update( frappe.db.get_value( - "Course Lesson", lesson.lesson, ["title", "body", "course"], as_dict=True + "Course Lesson", lesson.lesson, ["title", "body", "course", "chapter"], as_dict=True ) ) - lesson["index"] = get_lesson_index(lesson.lesson) - lesson["url"] = get_lesson_url(lesson.course, lesson.index) + "?class=" + class_name + lesson.index = get_lesson_index(lesson.lesson) + lesson.url = get_lesson_url(lesson.course, lesson.index) + "?class=" + class_name + lesson.icon = get_lesson_icon(lesson.body) - return lessons + chapter_exists = list(filter(lambda x: x.chapter == lesson.chapter, chapters)) + if len(chapter_exists) == 0: + chapters.append( + frappe._dict( + { + "chapter": lesson.chapter, + "chapter_title": frappe.db.get_value("Course Chapter", lesson.chapter, "title"), + "lessons": [lesson], + } + ) + ) + else: + chapter_exists[0]["lessons"].append(lesson) + + return chapters