style: course home
This commit is contained in:
@@ -6,6 +6,7 @@ from frappe.model.document import Document
|
||||
from frappe.utils import nowdate, add_years
|
||||
from frappe import _
|
||||
from frappe.utils.pdf import get_pdf
|
||||
from school.lms.utils import is_certified
|
||||
|
||||
class LMSCertification(Document):
|
||||
|
||||
@@ -22,8 +23,7 @@ class LMSCertification(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_certificate(course):
|
||||
course_details = frappe.get_doc("LMS Course", course)
|
||||
certificate = course_details.is_certified()
|
||||
certificate = is_certified()
|
||||
|
||||
if certificate:
|
||||
return certificate
|
||||
|
||||
@@ -20,6 +20,14 @@ frappe.ui.form.on('LMS Course', {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("course", "related_courses", function () {
|
||||
return {
|
||||
filters: {
|
||||
"is_published": true,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"allow_guest_to_view": 1,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"creation": "2021-03-01 16:49:33.622422",
|
||||
"creation": "2022-02-08 16:34:42.721203",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
@@ -31,6 +31,7 @@
|
||||
"short_introduction",
|
||||
"description",
|
||||
"chapters",
|
||||
"related_courses",
|
||||
"certification_section",
|
||||
"enable_certification",
|
||||
"expiry"
|
||||
@@ -141,6 +142,12 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Certification Expires After Years",
|
||||
"options": "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10"
|
||||
},
|
||||
{
|
||||
"fieldname": "related_courses",
|
||||
"fieldtype": "Table",
|
||||
"label": "Related Courses",
|
||||
"options": "Related Courses"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
@@ -167,7 +174,7 @@
|
||||
"link_fieldname": "course"
|
||||
}
|
||||
],
|
||||
"modified": "2022-02-07 11:41:39.735325",
|
||||
"modified": "2022-02-16 11:50:20.661085",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Course",
|
||||
@@ -192,4 +199,4 @@
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
0
school/lms/doctype/related_courses/__init__.py
Normal file
0
school/lms/doctype/related_courses/__init__.py
Normal file
8
school/lms/doctype/related_courses/related_courses.js
Normal file
8
school/lms/doctype/related_courses/related_courses.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2022, Frappe and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Related Courses', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
32
school/lms/doctype/related_courses/related_courses.json
Normal file
32
school/lms/doctype/related_courses/related_courses.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2022-02-16 11:45:07.200407",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"course"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Course",
|
||||
"options": "LMS Course"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-16 11:48:30.964916",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "Related Courses",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
8
school/lms/doctype/related_courses/related_courses.py
Normal file
8
school/lms/doctype/related_courses/related_courses.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) 2022, Frappe and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class RelatedCourses(Document):
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
# Copyright (c) 2022, Frappe and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestRelatedCourses(unittest.TestCase):
|
||||
pass
|
||||
@@ -105,7 +105,7 @@ def sanitize_html(html, macro):
|
||||
any broken tags. This makes sures that all those things are fixed
|
||||
before passing to the etree parser.
|
||||
"""
|
||||
soup = BeautifulSoup(html, features="lxml")
|
||||
soup = BeautifulSoup(html, features="html5lib")
|
||||
nodes = soup.body.children
|
||||
classname = ""
|
||||
if macro == "YouTubeVideo":
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import re
|
||||
import frappe
|
||||
from frappe.utils import flt, cint
|
||||
from frappe.utils import flt, cint, cstr
|
||||
from school.lms.md import markdown_to_html
|
||||
|
||||
RE_SLUG_NOTALLOWED = re.compile("[^a-z0-9]+")
|
||||
@@ -128,7 +128,7 @@ def get_reviews(course):
|
||||
{
|
||||
"course": course
|
||||
},
|
||||
["review", "rating", "owner"],
|
||||
["review", "rating", "owner", "creation"],
|
||||
order_by= "creation desc")
|
||||
out_of_ratings = frappe.db.get_all("DocField",
|
||||
{
|
||||
@@ -144,6 +144,22 @@ def get_reviews(course):
|
||||
|
||||
return reviews
|
||||
|
||||
def get_sorted_reviews(course):
|
||||
rating_count = rating_percent = frappe._dict()
|
||||
keys = ["5.0", "4.0", "3.0", "2.0", "1.0"]
|
||||
for key in keys:
|
||||
rating_count[key] = 0
|
||||
|
||||
reviews = get_reviews(course)
|
||||
for review in reviews:
|
||||
rating_count[cstr(review.rating)] += 1
|
||||
|
||||
for key in keys:
|
||||
rating_percent[key] = (rating_count[key]/len(reviews) * 100)
|
||||
|
||||
|
||||
return rating_percent
|
||||
|
||||
def is_certified(course):
|
||||
certificate = frappe.get_all("LMS Certification",
|
||||
{
|
||||
@@ -258,3 +274,18 @@ def get_course_progress(course, member=None):
|
||||
})
|
||||
precision = cint(frappe.db.get_default("float_precision")) or 3
|
||||
return flt(((completed_lessons/lesson_count) * 100), precision)
|
||||
|
||||
def get_initial_members(course):
|
||||
members = frappe.get_all("LMS Batch Membership",
|
||||
{
|
||||
"course": course
|
||||
},
|
||||
["member"],
|
||||
limit=3)
|
||||
|
||||
member_details = []
|
||||
for member in members:
|
||||
member_details.append(frappe.db.get_value("User",
|
||||
member.member, ["name", "username", "full_name", "user_image"], as_dict=True))
|
||||
|
||||
return member_details
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
{{ widgets.CourseCard(course=course, read_only=False) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<a class="button-links d-flex justify-content-center mt-12 intercative-link" style="color: var(--gray-800);" href="/courses">
|
||||
<div>
|
||||
Explore More
|
||||
</div>
|
||||
<img src="/assets/school/icons/arrow.svg" class="ml-2"/>
|
||||
<a class="d-flex justify-content-center align-items-center mt-12" href="/courses">
|
||||
<span>{{ _("Explore More") }}</span>
|
||||
<img src="/assets/school/icons/blue-arrow.svg" class="ml-2"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
<div>
|
||||
|
||||
<div class="chapter-title small-title" data-target="#{{ get_slugified_chapter_title(chapter.title) }}"
|
||||
data-toggle="collapse" aria-expanded="false">
|
||||
<img class="chapter-icon" src="/assets/school/icons/chevron-right.svg">
|
||||
{{ index }}. {{ chapter.title }}
|
||||
</div>
|
||||
|
||||
<div class="chapter-content collapse navbar-collapse" id="{{ get_slugified_chapter_title(chapter.title) }}">
|
||||
|
||||
{% if chapter.description %}
|
||||
<div class="chapter-description muted-text">
|
||||
{{ chapter.description }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set is_instructor = frappe.session.user == course.instructor %}
|
||||
<div class="lessons">
|
||||
|
||||
{% for lesson in get_lessons(course.name, chapter) %}
|
||||
|
||||
<div class="lesson-info {% if membership.current_lesson == lesson.name %} active-lesson {% endif %}">
|
||||
|
||||
{% if membership or lesson.include_in_preview %}
|
||||
<a class="lesson-links" href="{{ get_lesson_url(course.name, lesson.number) }}{{course.query_parameter}}"
|
||||
data-course="{{ course.name }}">
|
||||
{{ lesson.title }}
|
||||
|
||||
{% if membership %}
|
||||
<img class="ml-1 lesson-progress-tick {{ get_progress(course.name, lesson.name) != 'Complete' and 'hide' }}"
|
||||
src="/assets/school/icons/check.svg">
|
||||
{% endif %}
|
||||
|
||||
</a>
|
||||
|
||||
{% elif is_instructor and not lesson.include_in_preview %}
|
||||
<a class="lesson-links"
|
||||
title="This lesson is not available for preview. As you are the Instructor of the course only you can see it."
|
||||
href="{{ course.get_lesson_url(lesson.number) }}{{course.query_parameter}}"
|
||||
data-course="{{ course.name }}">
|
||||
{{ lesson.title }}
|
||||
<img class="ml-2" src="/assets/school/icons/lock.svg">
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
<div class="no-preview" title="This lesson is not available for preview">
|
||||
<div class="lesson-links">
|
||||
{{ lesson.title }}
|
||||
<img class="ml-2" src="/assets/school/icons/lock.svg">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if index != get_chapters(course.name) | length %}
|
||||
<div class="card-divider"></div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
expand_the_active_chapter();
|
||||
|
||||
$(".chapter-title").unbind().click((e) => {
|
||||
rotate_chapter_icon(e);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var expand_the_first_chapter = () => {
|
||||
var elements = $(".course-outline .collapse");
|
||||
elements.each((i, element) => {
|
||||
if (i < 1) {
|
||||
show_section(element);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var expand_the_active_chapter = () => {
|
||||
|
||||
/* Find anchor matching the URL for course details page */
|
||||
var selector = $(`a[href="${decodeURIComponent(window.location.pathname)}"]`).parent();
|
||||
if (!selector.length) {
|
||||
selector = $(`a[href^="${decodeURIComponent(window.location.pathname)}"]`).parent();
|
||||
}
|
||||
if (selector.length && $(".course-details-page").length) {
|
||||
$(".lesson-info").removeClass("active-lesson")
|
||||
selector.addClass("active-lesson");
|
||||
show_section(selector.parent().parent());
|
||||
}
|
||||
|
||||
/* For course home page */
|
||||
else if ($(".active-lesson").length) {
|
||||
selector = $(".active-lesson")
|
||||
show_section(selector.parent().parent());
|
||||
}
|
||||
|
||||
/* If no active chapter then exapand the first chapter */
|
||||
else {
|
||||
expand_the_first_chapter();
|
||||
}
|
||||
}
|
||||
|
||||
var show_section = (element) => {
|
||||
$(element).addClass("show");
|
||||
$(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)");
|
||||
}
|
||||
|
||||
var rotate_chapter_icon = (e) => {
|
||||
var icon = $(e.currentTarget).children(".chapter-icon");
|
||||
if (icon.css("transform") == "none") {
|
||||
icon.css("transform", "rotate(90deg)");
|
||||
} else {
|
||||
icon.css("transform", "none");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -58,11 +58,11 @@
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
{% set student_count = get_students(course.name) | length %}
|
||||
<span class="course-student-count">
|
||||
{% if student_count %}
|
||||
<span class="vertically-center mr-4">
|
||||
<span class="vertically-center mr-3">
|
||||
<img class="icon-background" src="/assets/school/icons/user.svg" />
|
||||
{{ student_count }}
|
||||
</span>
|
||||
@@ -85,15 +85,8 @@
|
||||
membership.current_lesson else '1.1' %}
|
||||
{% set query_parameter = "?batch=" + membership.batch if membership and
|
||||
membership.batch else "" %}
|
||||
{% set certificate = is_certified(course.name) %}
|
||||
|
||||
{% if certificate %}
|
||||
<a class="stretched-link" href="/courses/{{ course.name }}/{{ certificate }}"></a>
|
||||
|
||||
{% elif course.enable_certification and progress == 100 %}
|
||||
<a class="stretched-link" id="certification" data-course="{{ course.name }}"></a>
|
||||
|
||||
{% elif progress == 100 %}
|
||||
{% if progress == 100 %}
|
||||
<a class="stretched-link" href="/courses/{{ course.name }}"></a>
|
||||
|
||||
{% elif course.upcoming %}
|
||||
@@ -109,27 +102,3 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
frappe.ready(() => {
|
||||
|
||||
$("#certification").unbind().click((e) => {
|
||||
create_certificate(e);
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
var create_certificate = (e) => {
|
||||
e.preventDefault();
|
||||
course = $(e.currentTarget).attr("data-course");
|
||||
frappe.call({
|
||||
method: "school.lms.doctype.lms_certification.lms_certification.create_certificate",
|
||||
args: {
|
||||
"course": course
|
||||
},
|
||||
callback: (data) => {
|
||||
window.location.href = `/courses/${course}/${data.message}`;
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,131 @@
|
||||
{% if get_chapters(course.name) | length %}
|
||||
<div class="">
|
||||
<div class="course-home-outline">
|
||||
<div class="course-home-headings">
|
||||
Course Outline
|
||||
{{ _("Course Content") }}
|
||||
</div>
|
||||
<div class="common-card-style course-outline">
|
||||
{% for chapter in get_chapters(course.name) %}
|
||||
{{ widgets.ChapterTeaser(index=loop.index, chapter=chapter, course=course, batch=batch, membership=membership) }}
|
||||
{% endfor %}
|
||||
{% for chapter in get_chapters(course.name) %}
|
||||
<div class="mb-2">
|
||||
<div class="chapter-title" data-target="#{{ get_slugified_chapter_title(chapter.title) }}"
|
||||
data-toggle="collapse" aria-expanded="false">
|
||||
<img class="chapter-icon" src="/assets/school/icons/chevron-right.svg">
|
||||
<div>{{ chapter.title }}</div>
|
||||
</div>
|
||||
|
||||
<div class="chapter-content collapse navbar-collapse" id="{{ get_slugified_chapter_title(chapter.title) }}">
|
||||
|
||||
{% if chapter.description %}
|
||||
<div class="chapter-description muted-text">
|
||||
{{ chapter.description }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% set is_instructor = frappe.session.user == course.instructor %}
|
||||
<div class="lessons">
|
||||
|
||||
{% for lesson in get_lessons(course.name, chapter) %}
|
||||
|
||||
<div class="lesson-info {% if membership.current_lesson == lesson.name %} active-lesson {% endif %}">
|
||||
|
||||
{% if membership or lesson.include_in_preview %}
|
||||
<a class="lesson-links" href="{{ get_lesson_url(course.name, lesson.number) }}{{course.query_parameter}}"
|
||||
data-course="{{ course.name }}">
|
||||
{{ lesson.title }}
|
||||
|
||||
{% if membership %}
|
||||
<img class="ml-1 lesson-progress-tick {{ get_progress(course.name, lesson.name) != 'Complete' and 'hide' }}"
|
||||
src="/assets/school/icons/check.svg">
|
||||
{% endif %}
|
||||
|
||||
</a>
|
||||
|
||||
{% elif is_instructor and not lesson.include_in_preview %}
|
||||
<a class="lesson-links"
|
||||
title="This lesson is not available for preview. As you are the Instructor of the course only you can see it."
|
||||
href="{{ course.get_lesson_url(lesson.number) }}{{course.query_parameter}}"
|
||||
data-course="{{ course.name }}">
|
||||
<img class="mr-3" src="/assets/school/icons/lock.svg">
|
||||
<div>{{ lesson.title }}</div>
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
<div class="no-preview" title="This lesson is not available for preview">
|
||||
<div class="lesson-links">
|
||||
<img class="mr-3" src="/assets/school/icons/lock.svg">
|
||||
<div>{{ lesson.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
|
||||
frappe.ready(() => {
|
||||
|
||||
expand_the_active_chapter();
|
||||
|
||||
$(".chapter-title").unbind().click((e) => {
|
||||
rotate_chapter_icon(e);
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
var expand_the_first_chapter = () => {
|
||||
var elements = $(".course-outline .collapse");
|
||||
elements.each((i, element) => {
|
||||
if (i < 1) {
|
||||
show_section(element);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var expand_the_active_chapter = () => {
|
||||
|
||||
/* Find anchor matching the URL for course details page */
|
||||
var selector = $(`a[href="${decodeURIComponent(window.location.pathname)}"]`).parent();
|
||||
if (!selector.length) {
|
||||
selector = $(`a[href^="${decodeURIComponent(window.location.pathname)}"]`).parent();
|
||||
}
|
||||
if (selector.length && $(".course-details-page").length) {
|
||||
$(".lesson-info").removeClass("active-lesson")
|
||||
selector.addClass("active-lesson");
|
||||
show_section(selector.parent().parent());
|
||||
}
|
||||
|
||||
/* For course home page */
|
||||
else if ($(".active-lesson").length) {
|
||||
selector = $(".active-lesson")
|
||||
show_section(selector.parent().parent());
|
||||
}
|
||||
|
||||
/* If no active chapter then exapand the first chapter */
|
||||
else {
|
||||
expand_the_first_chapter();
|
||||
}
|
||||
}
|
||||
|
||||
var show_section = (element) => {
|
||||
$(element).addClass("show");
|
||||
$(element).siblings(".chapter-title").children(".chapter-icon").css("transform", "rotate(90deg)");
|
||||
$(element).siblings(".chapter-title").attr("aria-expanded", true);
|
||||
}
|
||||
|
||||
var rotate_chapter_icon = (e) => {
|
||||
var icon = $(e.currentTarget).children(".chapter-icon");
|
||||
if (icon.css("transform") == "none") {
|
||||
icon.css("transform", "rotate(90deg)");
|
||||
} else {
|
||||
icon.css("transform", "none");
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<div class="common-card-style member-card">
|
||||
{{ widgets.Avatar(member=member, avatar_class=avatar_class) }}
|
||||
<div class="small-title member-card-title {% if show_course_count %} font-weight-bold {% endif %}">
|
||||
<div class=" member-card-title {% if show_course_count %} font-weight-bold {% endif %}">
|
||||
{{ member.full_name }}
|
||||
</div>
|
||||
{% set course_count = get_authored_courses(member.name) | length %}
|
||||
{% if show_course_count and course_count > 0 %}
|
||||
{% set suffix = "Courses" if course_count > 1 else "Course" %}
|
||||
<div class="small-title">
|
||||
<div class="">
|
||||
Created {{ course_count }} {{ suffix }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -10,29 +10,76 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if reviews | length %}
|
||||
<div class="reviews-section">
|
||||
{% for review in reviews %}
|
||||
<div class="review-card">
|
||||
<div class="common-card-style review-content small-title"> {{ review.review }} </div>
|
||||
<div class="review-card-footer">
|
||||
<div>
|
||||
{{ widgets.Avatar(member=review.owner_details, avatar_class="avatar-medium") }}
|
||||
<a class="button-links" href="{{get_profile_url(review.owner_details.username) }}">
|
||||
<span class="course-instructor">
|
||||
{{ review.owner_details.full_name }}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
{% set avg_rating = get_average_rating(course.name) %}
|
||||
<div class="reviews-header">
|
||||
<div class="text-center">
|
||||
<div class="avg-rating"> {{ avg_rating }} </div>
|
||||
<div class="course-meta"> {{ reviews | length }} {{ _("ratings") }} </div>
|
||||
<div class="avg-rating-stars">
|
||||
<div class="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-md {% if i <= review.rating %} star-click {% endif %}" data-rating="{{ i }}">
|
||||
<svg class="icon icon-md {% if i <= avg_rating %} star-click {% endif %}" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="course-meta"> {{ avg_rating }} {{ _("out of 5 ") }} </div>
|
||||
</div>
|
||||
<div class="vertical-divider"></div>
|
||||
{% set sorted_reviews = get_sorted_reviews(course.name) %}
|
||||
<div>
|
||||
{% for review in sorted_reviews %}
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="course-meta mr-2"> {{ frappe.utils.cint(review) }} {{ _("stars") }} </div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="{{ sorted_reviews[review] }}"
|
||||
aria-valuemin="0" aria-valuemax="100" style="width:{{ sorted_reviews[review] }}%">
|
||||
<span class="sr-only"> {{ sorted_reviews[review] }} Complete</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="course-meta ml-3"> {{ frappe.utils.cint(sorted_reviews[review]) }}% </div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% if reviews | length %}
|
||||
<div class="mt-12">
|
||||
{% for review in reviews %}
|
||||
<div class="">
|
||||
<div class="review-card-footer">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="mr-5">
|
||||
{{ widgets.Avatar(member=review.owner_details, avatar_class="avatar-medium") }}
|
||||
</div>
|
||||
<div>
|
||||
<a class="button-links" href="{{get_profile_url(review.owner_details.username) }}">
|
||||
<span class="review-author">
|
||||
{{ review.owner_details.full_name }}
|
||||
</span>
|
||||
</a>
|
||||
<div class="rating">
|
||||
{% for i in [1, 2, 3, 4, 5] %}
|
||||
<svg class="icon icon-md {% if i <= review.rating %} star-click {% endif %}" data-rating="{{ i }}">
|
||||
<use href="#icon-star"></use>
|
||||
</svg>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-16 mt-4">
|
||||
<div> {{ review.review }} </div>
|
||||
<div class="frappe-timestamp mt-2 mb-6 course-meta" data-timestamp="{{ review.creation }}"> frappe.utils.pretty_date(review.creation) </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if loop.index != reviews | length %}
|
||||
<div class="card-divider"></div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user