fix: flow UI and quiz progress

This commit is contained in:
Jannat Patel
2023-08-07 21:17:23 +05:30
parent 845b906851
commit 47e254ed9b
11 changed files with 182 additions and 86 deletions

View File

@@ -92,7 +92,17 @@ def save_progress(lesson, course, status):
"LMS Batch Membership", {"member": frappe.session.user, "course": course} "LMS Batch Membership", {"member": frappe.session.user, "course": course}
) )
if not membership: 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} filters = {"lesson": lesson, "owner": frappe.session.user, "course": course}
if frappe.db.exists("LMS Course Progress", filters): if frappe.db.exists("LMS Course Progress", filters):

View File

@@ -21,10 +21,13 @@ frappe.ui.form.on("LMS Class", {
}, },
callback: (r) => { callback: (r) => {
if (r.message) { if (r.message) {
let date = frappe.datetime.get_today();
r.message.forEach((lesson) => { r.message.forEach((lesson) => {
let row = frm.add_child("scheduled_flow"); let row = frm.add_child("scheduled_flow");
row.lesson = lesson.name; row.lesson = lesson.name;
row.lesson_title = lesson.title; row.lesson_title = lesson.title;
row.date = date;
date = frappe.datetime.add_days(date, 1);
}); });
frm.refresh_field("scheduled_flow"); frm.refresh_field("scheduled_flow");
} }

View File

@@ -43,7 +43,8 @@
"fieldname": "date", "fieldname": "date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1, "in_list_view": 1,
"label": "Date" "label": "Date",
"reqd": 1
}, },
{ {
"fetch_from": "lesson.title", "fetch_from": "lesson.title",
@@ -56,7 +57,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2023-08-01 12:31:27.512437", "modified": "2023-08-07 12:10:28.095018",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "Scheduled Flow", "name": "Scheduled Flow",

View File

@@ -136,18 +136,28 @@ def get_lesson_details(chapter):
as_dict=True, as_dict=True,
) )
lesson_details.number = flt(f"{chapter.idx}.{row.idx}") lesson_details.number = flt(f"{chapter.idx}.{row.idx}")
lesson_details.icon = "icon-list" lesson_details.icon = get_lesson_icon(lesson_details.body)
macros = find_macros(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) lessons.append(lesson_details)
return lessons 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): def get_tags(course):
tags = frappe.db.get_value("LMS Course", course, "tags") tags = frappe.db.get_value("LMS Course", course, "tags")
return tags.split(",") if tags else [] return tags.split(",") if tags else []
@@ -336,7 +346,7 @@ def is_eligible_to_review(course, membership):
def get_course_progress(course, member=None): def get_course_progress(course, member=None):
"""Returns the course progress of the session user""" """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: if not lesson_count:
return 0 return 0
completed_lessons = frappe.db.count( completed_lessons = frappe.db.count(

View File

@@ -36,7 +36,6 @@
<!-- <div class="small ml-auto"> <!-- <div class="small ml-auto">
{{ lessons | length }} lessons {{ lessons | length }} lessons
</div> --> </div> -->
</div> </div>
@@ -58,9 +57,7 @@
{% if membership or lesson.include_in_preview or is_instructor or has_course_moderator_role() %} {% if membership or lesson.include_in_preview or is_instructor or has_course_moderator_role() %}
<a class="lesson-links" <a class="lesson-links"
{% if not from_class %} href="{{ get_lesson_url(course.name, lesson.number) }}{% if classname %}?class={{ classname }}{% endif %}{{course.query_parameter}}"
href="{{ get_lesson_url(course.name, lesson.number) }}{{course.query_parameter}}"
{% endif %}
{% if is_instructor and not lesson.include_in_preview %} {% if is_instructor and not lesson.include_in_preview %}
title="{{ _('This lesson is not available for preview. As you are the Instructor of the course only you can see it.') }}" title="{{ _('This lesson is not available for preview. As you are the Instructor of the course only you can see it.') }}"
{% endif %}> {% endif %}>

View File

@@ -58,10 +58,10 @@ body {
} }
.rating .star-click { .rating .star-click {
--star-fill: var(--orange-500); --star-fill: var(--orange-500);
background: var(--gray-200); background: var(--gray-200);
border-radius: var(--border-radius-md); border-radius: var(--border-radius-md);
padding: var(--padding-xs); padding: var(--padding-xs);
} }
.cta-parent { .cta-parent {
@@ -80,10 +80,10 @@ body {
.field-input { .field-input {
border: 1px solid var(--gray-300); border: 1px solid var(--gray-300);
border-radius: var(--border-radius-md); border-radius: var(--border-radius-md);
padding: 0.5rem; padding: 0.5rem;
width: 100%; width: 100%;
margin-top: 0.25rem; margin-top: 0.25rem;
} }
.field-input:focus-visible { .field-input:focus-visible {
@@ -151,9 +151,9 @@ textarea.field-input {
} }
.ce-block__content { .ce-block__content {
max-width: 100%; max-width: 100%;
padding: 0 0.5rem; padding: 0 0.5rem;
margin: 0; margin: 0;
} }
.ce-toolbar__content { .ce-toolbar__content {
@@ -206,7 +206,7 @@ textarea.field-input {
} }
.codex-editor path { .codex-editor path {
stroke: var(--gray-800); stroke: var(--gray-800);
} }
.drag-handle { .drag-handle {
@@ -617,19 +617,18 @@ input[type=checkbox] {
} }
.reviews-parent { .reviews-parent {
color: var(--gray-900); color: var(--gray-900);
} }
.lesson-info { .lesson-info {
font-size: 16px; padding: 0.5rem;
color: var(--gray-900); color: var(--gray-900);
letter-spacing: -0.011em; letter-spacing: -0.011em;
} }
.lesson-links { .lesson-links {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0.5rem;
color: var(--gray-900); color: var(--gray-900);
font-size: var(--text-base); font-size: var(--text-base);
} }
@@ -1045,42 +1044,42 @@ pre {
.certificate-parent { .certificate-parent {
display: grid; display: grid;
grid-template-columns: 10fr 2fr; grid-template-columns: 10fr 2fr;
grid-gap: 3rem; grid-gap: 3rem;
} }
.certificate-logo { .certificate-logo {
height: 1.5rem; height: 1.5rem;
margin-bottom: 4rem; margin-bottom: 4rem;
} }
.certificate-name { .certificate-name {
font-size: 2rem; font-size: 2rem;
font-weight: 500; font-weight: 500;
color: #192734; color: #192734;
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
} }
.certificate-footer { .certificate-footer {
margin: 4rem auto 0; margin: 4rem auto 0;
width: fit-content; width: fit-content;
} }
.certificate-footer-item { .certificate-footer-item {
color: #192734; color: #192734;
} }
.cursive-font { .cursive-font {
font-family: cursive; font-family: cursive;
font-weight: 600; font-weight: 600;
} }
.certificate-divider { .certificate-divider {
margin: 0.5rem 0; margin: 0.5rem 0;
} }
.certificate-expiry { .certificate-expiry {
margin-left: 2rem; margin-left: 2rem;
} }
.column-card { .column-card {
@@ -2121,10 +2120,10 @@ select {
.lms-card { .lms-card {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-radius: 0.75rem; border-radius: 0.75rem;
border: 1px solid var(--gray-300); border: 1px solid var(--gray-300);
/* box-shadow: var(--shadow-sm); */ /* box-shadow: var(--shadow-sm); */
padding: 0.5rem; padding: 0.5rem;
height: 100%; height: 100%;
position: relative; position: relative;
} }
@@ -2203,4 +2202,10 @@ select {
.rows .grid-row .grid-footer-toolbar, .rows .grid-row .grid-footer-toolbar,
.grid-form-heading { .grid-form-heading {
cursor: none; cursor: none;
}
.schedule-header {
display: flex;
font-size: var(--text-sm);
padding: 0.5rem 0.5rem 0 0.5rem;
} }

View File

@@ -142,6 +142,9 @@ const quiz_summary = (e = undefined) => {
$("#try-again").attr("data-submission", data.message.submission); $("#try-again").attr("data-submission", data.message.submission);
$("#try-again").removeClass("hide"); $("#try-again").removeClass("hide");
self.quiz_submitted = true; self.quiz_submitted = true;
if (this.hasOwnProperty("marked_as_complete")) {
mark_progress();
}
}, },
}); });
}; };

View File

@@ -39,8 +39,8 @@
{% endif %} {% endif %}
<div class="course-details-outline"> <div class="course-details-outline">
{% set from_class = True if class_info else False %} {% set classname = class_info.name if class_info else False %}
{{ widgets.CourseOutline(course=course, membership=membership, lesson_page=True, from_class=from_class) }} {{ widgets.CourseOutline(course=course, membership=membership, lesson_page=True, classname=classname) }}
</div> </div>
</div> </div>
<div class="lesson-parent"> <div class="lesson-parent">

View File

@@ -15,7 +15,6 @@ frappe.ready(() => {
!self.marked_as_complete && !self.marked_as_complete &&
$(".title").hasClass("is-member") $(".title").hasClass("is-member")
) { ) {
self.marked_as_complete = true;
mark_progress(); mark_progress();
} }
}); });
@@ -61,8 +60,11 @@ const mark_progress = () => {
status: status, status: status,
}, },
callback: (data) => { callback: (data) => {
change_progress_indicators(); if (data.message) {
show_certificate_if_course_completed(data); change_progress_indicators();
show_certificate_if_course_completed(data);
self.marked_as_complete = true;
}
}, },
}); });
}; };

View File

@@ -10,7 +10,7 @@
{{ BreadCrumb(class_info) }} {{ BreadCrumb(class_info) }}
<div class=""> <div class="">
{{ ClassDetails(class_info) }} {{ ClassDetails(class_info) }}
{{ ClassSections(class_info, class_courses, class_students, published_courses, flow) }} {{ ClassSections(class_info, class_courses, class_students, flow) }}
</div> </div>
</div> </div>
</div> </div>
@@ -82,7 +82,7 @@
<!-- Class Sections --> <!-- Class Sections -->
{% macro ClassSections(class_info, class_courses, class_students, published_courses, flow) %} {% macro ClassSections(class_info, class_courses, class_students, flow) %}
<div class="mt-4"> <div class="mt-4">
{% if is_moderator %} {% if is_moderator %}
@@ -150,7 +150,7 @@
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="courses" role="tabpanel" aria-labelledby="courses"> <div class="tab-pane active" id="courses" role="tabpanel" aria-labelledby="courses">
{{ CoursesSection(class_info, class_courses, published_courses) }} {{ CoursesSection(class_info, class_courses) }}
</div> </div>
{% if flow | length %} {% if flow | length %}
@@ -180,7 +180,7 @@
{% endmacro %} {% endmacro %}
{% macro CoursesSection(class_info, class_courses, published_courses) %} {% macro CoursesSection(class_info, class_courses) %}
<article> <article>
<header class="mb-5"> <header class="mb-5">
<div class="edit-header"> <div class="edit-header">
@@ -476,20 +476,64 @@
{{ _("Schedule") }} {{ _("Schedule") }}
</div> </div>
</header> </header>
<div class="form-grid">
<div class="grid-body"> <div>
<div class="rows"> {% for chapter in flow %}
{% for lesson in flow %} <div class="chapter-parent">
<div class="grid-row"> <div class="chapter-title" data-toggle="collapse" data-target="#{{ get_slugified_chapter_title(chapter.chapter_title) }}">
<div class="row data-row"> <img class="chapter-icon" src="/assets/lms/icons/chevron-right.svg">
<a class="col grid-static-col clickable" href="{{ lesson.url }}"> <div class="chapter-title-main">
{{ lesson.title }} {{ chapter.chapter_title }}
</a> </div>
</div>
<div class="chapter-content lessons collapse navbar-collapse" id="{{ get_slugified_chapter_title(chapter.chapter_title) }}">
<div class="schedule-header">
<div class="w-50">
{{ _("Lesson") }}
</div>
<div class="w-25">
{{ _("Date") }}
</div>
<div class="w-25 text-center">
{{ _("Start Time") }}
</div>
<div class="w-25 text-center">
{{ _("End Time") }}
</div> </div>
</div> </div>
{% endfor %}
{% for lesson in chapter.lessons %}
<div class="lesson-info flex align-center">
<a class="lesson-links w-50" href="{{ lesson.url }}">
<svg class="icon icon-sm mr-2">
<use class="" href="#{{ lesson.icon }}">
</svg>
{{ lesson.title }}
</a>
<div class="w-25">
{{ frappe.utils.format_date(lesson.date, "medium") }}
</div>
<div class="w-25 text-center">
{% if lesson.start_time %}
{{ frappe.utils.format_time(lesson.start_time, "HH:mm a") }}
{% else %}
-
{% endif %}
</div>
<div class="w-25 text-center">
{% if lesson.end_time %}
{{ frappe.utils.format_time(lesson.end_time, "HH:mm a") }}
{% else %}
-
{% endif %}
</div>
</div>
{% endfor %}
</div>
</div> </div>
</div> {% endfor %}
</div> </div>
</article> </article>
{% endmacro %} {% endmacro %}

View File

@@ -10,6 +10,7 @@ from lms.lms.utils import (
has_graded_assessment, has_graded_assessment,
get_lesson_index, get_lesson_index,
get_lesson_url, get_lesson_url,
get_lesson_icon,
) )
@@ -38,10 +39,6 @@ def get_context(context):
as_dict=True, as_dict=True,
) )
context.published_courses = frappe.get_all(
"LMS Course", {"published": 1}, ["name", "title"]
)
class_courses = frappe.get_all( class_courses = frappe.get_all(
"Class Course", "Class Course",
{"parent": class_name}, {"parent": class_name},
@@ -56,15 +53,8 @@ def get_context(context):
order_by="creation desc", 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.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 "LMS Course", fields=["name", "title"], limit_page_length=0
) )
context.course_name_list = [course.course for course in context.class_courses] 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 class_students, class_courses, context.assessments
) )
context.is_student = is_student(class_students) 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_assignments = get_all_assignments(class_name)
context.all_quizzes = get_all_quizzes(class_name) context.all_quizzes = get_all_quizzes(class_name)
context.flow = get_scheduled_flow(class_name) context.flow = get_scheduled_flow(class_name)
@@ -191,17 +192,37 @@ def is_student(class_students):
def get_scheduled_flow(class_name): def get_scheduled_flow(class_name):
chapters = []
lessons = frappe.get_all( 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: for lesson in lessons:
lesson.update( lesson.update(
frappe.db.get_value( 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.index = get_lesson_index(lesson.lesson)
lesson["url"] = get_lesson_url(lesson.course, lesson.index) + "?class=" + class_name 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