fix: flow UI and quiz progress
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
<!-- <div class="small ml-auto">
|
||||
{{ lessons | length }} lessons
|
||||
</div> -->
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -58,9 +57,7 @@
|
||||
|
||||
{% if membership or lesson.include_in_preview or is_instructor or has_course_moderator_role() %}
|
||||
<a class="lesson-links"
|
||||
{% if not from_class %}
|
||||
href="{{ get_lesson_url(course.name, lesson.number) }}{{course.query_parameter}}"
|
||||
{% endif %}
|
||||
href="{{ get_lesson_url(course.name, lesson.number) }}{% if classname %}?class={{ classname }}{% endif %}{{course.query_parameter}}"
|
||||
{% 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.') }}"
|
||||
{% endif %}>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="course-details-outline">
|
||||
{% 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) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="lesson-parent">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{{ BreadCrumb(class_info) }}
|
||||
<div class="">
|
||||
{{ ClassDetails(class_info) }}
|
||||
{{ ClassSections(class_info, class_courses, class_students, published_courses, flow) }}
|
||||
{{ ClassSections(class_info, class_courses, class_students, flow) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
|
||||
<!-- 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">
|
||||
|
||||
{% if is_moderator %}
|
||||
@@ -150,7 +150,7 @@
|
||||
|
||||
<div class="tab-content">
|
||||
<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>
|
||||
|
||||
{% if flow | length %}
|
||||
@@ -180,7 +180,7 @@
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro CoursesSection(class_info, class_courses, published_courses) %}
|
||||
{% macro CoursesSection(class_info, class_courses) %}
|
||||
<article>
|
||||
<header class="mb-5">
|
||||
<div class="edit-header">
|
||||
@@ -476,20 +476,64 @@
|
||||
{{ _("Schedule") }}
|
||||
</div>
|
||||
</header>
|
||||
<div class="form-grid">
|
||||
<div class="grid-body">
|
||||
<div class="rows">
|
||||
{% for lesson in flow %}
|
||||
<div class="grid-row">
|
||||
<div class="row data-row">
|
||||
<a class="col grid-static-col clickable" href="{{ lesson.url }}">
|
||||
{{ lesson.title }}
|
||||
</a>
|
||||
|
||||
<div>
|
||||
{% for chapter in flow %}
|
||||
<div class="chapter-parent">
|
||||
<div class="chapter-title" data-toggle="collapse" data-target="#{{ get_slugified_chapter_title(chapter.chapter_title) }}">
|
||||
<img class="chapter-icon" src="/assets/lms/icons/chevron-right.svg">
|
||||
<div class="chapter-title-main">
|
||||
{{ chapter.chapter_title }}
|
||||
</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>
|
||||
{% 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>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</article>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user