Merge branch 'main' of https://github.com/frappe/lms into class-revamp
This commit is contained in:
@@ -204,7 +204,7 @@
|
||||
{% if class_courses | length %}
|
||||
<div class="cards-parent">
|
||||
{% for course in class_courses %}
|
||||
<div>
|
||||
<div class="h-100">
|
||||
{{ widgets.CourseCard(course=course, read_only=False) }}
|
||||
<button class="btn icon-btn btn-default btn-block btn-remove-course" data-course="{{ course.name }}">
|
||||
<svg class="icon icon-sm">
|
||||
@@ -271,7 +271,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% for student in class_students %}
|
||||
{% set allow_progress = is_moderator or student.student == frappe.session.user %}
|
||||
{% set allow_progress = is_moderator or student.student == frappe.session.user or is_evaluator %}
|
||||
<div class="grid-row">
|
||||
<div class="data-row row">
|
||||
<a class="col grid-static-col {% if allow_progress %} clickable {% endif %}" {% if allow_progress %} href="/classes/{{ class_info.name }}/students/{{ student.username }}" {% endif %}>
|
||||
|
||||
@@ -374,6 +374,7 @@ const show_student_modal = () => {
|
||||
filters: {
|
||||
ignore_user_type: 1,
|
||||
},
|
||||
filter_description: " ",
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Add"),
|
||||
@@ -448,13 +449,10 @@ const show_assessment_modal = (e) => {
|
||||
label: __("Assessment Type"),
|
||||
fieldname: "assessment_type",
|
||||
reqd: 1,
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
name: ["in", ["LMS Assignment", "LMS Quiz"]],
|
||||
},
|
||||
};
|
||||
filters: {
|
||||
name: ["in", ["LMS Assignment", "LMS Quiz"]],
|
||||
},
|
||||
filter_description: " ",
|
||||
},
|
||||
{
|
||||
fieldtype: "Dynamic Link",
|
||||
|
||||
@@ -4,6 +4,7 @@ from frappe.utils import getdate
|
||||
from lms.www.utils import get_assessments
|
||||
from lms.lms.utils import (
|
||||
has_course_moderator_role,
|
||||
has_course_evaluator_role,
|
||||
get_course_progress,
|
||||
has_submitted_assessment,
|
||||
has_graded_assessment,
|
||||
@@ -14,6 +15,7 @@ def get_context(context):
|
||||
context.no_cache = 1
|
||||
class_name = frappe.form_dict["classname"]
|
||||
context.is_moderator = has_course_moderator_role()
|
||||
context.is_evaluator = has_course_evaluator_role()
|
||||
|
||||
context.class_info = frappe.db.get_value(
|
||||
"LMS Class",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="page-title">
|
||||
{{ _("{0}").format(student.full_name) }}
|
||||
</div>
|
||||
<div class="vertically-center small">
|
||||
<div class="vertically-center">
|
||||
<a class="dark-links" href="/classes">
|
||||
{{ _("All Classes") }}
|
||||
</a>
|
||||
@@ -30,21 +30,27 @@
|
||||
{{ class_info.name }}
|
||||
</a>
|
||||
<img class="icon icon-sm mr-0" src="/assets/lms/icons/chevron-right.svg">
|
||||
<span class="breadcrumb-destination">{{ _("Student Progress").format(student.full_name) }}</span>
|
||||
<span class="breadcrumb-destination">
|
||||
{{ _("Student Progress").format(student.full_name) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if is_moderator %}
|
||||
<div class="align-self-center">
|
||||
<a class="btn btn-default btn-sm mr-2" href="/users/{{ student.username }}">
|
||||
<a class="btn btn-default btn-sm" href="/users/{{ student.username }}">
|
||||
{{ _("View Profile") }}
|
||||
</a>
|
||||
<a class="btn btn-primary btn-sm btn-evaluate" href=/evaluation/new?member={{student.name}}&date={{frappe.utils.getdate()}}&class={{class_info.name}}">
|
||||
{% if student.name == frappe.session.user %}
|
||||
<button class="btn btn-default btn-sm btn-schedule-eval ml-2">
|
||||
{{ _("Schedule Evaluation") }}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if is_moderator %}
|
||||
<a class="btn btn-primary btn-sm btn-evaluate ml-2" href=/evaluation/new?member={{student.name}}&date={{frappe.utils.getdate()}}&class={{class_info.name}}">
|
||||
{{ _("Evaluate") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -52,59 +58,132 @@
|
||||
|
||||
|
||||
{% macro Progress(class_info, student) %}
|
||||
{% if assessments | length %}
|
||||
<article class="form-grid">
|
||||
<div class="grid-heading-row">
|
||||
<div class="grid-row">
|
||||
<div class="data-row row">
|
||||
<div class="col grid-static-col">
|
||||
{{ _("Assessment") }}
|
||||
</div>
|
||||
<div class="col grid-static-col col-xs-2">
|
||||
{{ _("Type") }}
|
||||
</div>
|
||||
<div class="col grid-static-col col-xs-2">
|
||||
{{ _("Status/Score") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% for assessment in assessments %}
|
||||
{% set has_access = is_moderator and assessment.submission or frappe.session.user == student.name %}
|
||||
<div class="grid-row">
|
||||
<div class="data-row row">
|
||||
<a class="col grid-static-col {% if has_access %} clickable {% endif %}" {% if has_access %} href="{{ assessment.url }}" {% endif %}>
|
||||
{{ assessment.title }}
|
||||
</a>
|
||||
<div class="col grid-static-col col-xs-2">
|
||||
{{ (assessment.assessment_type).split("LMS ")[1] }}
|
||||
</div>
|
||||
{{ UpcomingEvals(upcoming_evals) }}
|
||||
{{ Assessments(class_info, student) }}
|
||||
{% endmacro %}
|
||||
|
||||
<div class="col grid-static-col col-xs-2 mb-2">
|
||||
{% if assessment.submission %}
|
||||
{% if assessment.assessment_type == "LMS Assignment" %}
|
||||
{% set status = assessment.submission.status %}
|
||||
{% set color = "green" if status == "Pass" else "red" if status == "Fail" else "orange" %}
|
||||
<div class="indicator-pill {{ color }}">
|
||||
{{ status }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div>
|
||||
{{ assessment.submission.score }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="indicator-pill red">
|
||||
{{ _("Not Attempted") }}
|
||||
</div>
|
||||
{% macro UpcomingEvals(upcoming_evals) %}
|
||||
<div class="mb-8">
|
||||
<div class="bold-heading mb-2">
|
||||
{{ _("Upcoming Evaluations") }}
|
||||
</div>
|
||||
{% if upcoming_evals | length %}
|
||||
<article class="cards-parent">
|
||||
{% for eval in upcoming_evals %}
|
||||
<div class="common-card-style column-card">
|
||||
<div class="flex align-center justify-between">
|
||||
<div class="bold-heading">
|
||||
{{ eval.course_title }}
|
||||
</div>
|
||||
{% if eval.google_meet_link %}
|
||||
<a class="btn btn-default btn-sm pull-right" href="{{ eval.google_meet_link }}">
|
||||
{{ _("Join") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="vertically-center">
|
||||
<svg class="icon icon-sm mr-1">
|
||||
<use href="#icon-calendar"></use>
|
||||
</svg>
|
||||
<span>
|
||||
{{ frappe.utils.format_date(eval.date, "medium") }} -
|
||||
</span>
|
||||
<span>
|
||||
{{ frappe.utils.format_time(eval.start_time, "hh:mm a") }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="field-label">
|
||||
{{ _("Evaluator") }}:
|
||||
</span>
|
||||
<span>
|
||||
{{ eval.evaluator_name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</article>
|
||||
{% else %}
|
||||
<p class="text-muted"> {{ _("No Upcoming Evaluations") }} </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro Assessments(class_info, student) %}
|
||||
<div class="mb-8">
|
||||
<div class="bold-heading mb-2">
|
||||
{{ _("Assessments") }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</article>
|
||||
{% else %}
|
||||
<p class="text-muted mt-3"> {{ _("No Assessments") }} </p>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{% if assessments | length %}
|
||||
<article class="form-grid">
|
||||
<div class="grid-heading-row">
|
||||
<div class="grid-row">
|
||||
<div class="data-row row">
|
||||
<div class="col grid-static-col">
|
||||
{{ _("Assessment") }}
|
||||
</div>
|
||||
<div class="col grid-static-col col-xs-2">
|
||||
{{ _("Type") }}
|
||||
</div>
|
||||
<div class="col grid-static-col col-xs-2">
|
||||
{{ _("Status/Score") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% for assessment in assessments %}
|
||||
{% set has_access = is_moderator and assessment.submission or frappe.session.user == student.name %}
|
||||
<div class="grid-row">
|
||||
<div class="data-row row">
|
||||
<a class="col grid-static-col {% if has_access %} clickable {% endif %}" {% if has_access %} href="{{ assessment.url }}" {% endif %}>
|
||||
{{ assessment.title }}
|
||||
</a>
|
||||
<div class="col grid-static-col col-xs-2">
|
||||
{{ (assessment.assessment_type).split("LMS ")[1] }}
|
||||
</div>
|
||||
|
||||
<div class="col grid-static-col col-xs-2 mb-2">
|
||||
{% if assessment.submission %}
|
||||
{% if assessment.assessment_type == "LMS Assignment" %}
|
||||
{% set status = assessment.submission.status %}
|
||||
{% set color = "green" if status == "Pass" else "red" if status == "Fail" else "orange" %}
|
||||
<div class="indicator-pill {{ color }}">
|
||||
{{ status }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div>
|
||||
{{ assessment.submission.score }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="indicator-pill red">
|
||||
{{ _("Not Attempted") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</article>
|
||||
{% else %}
|
||||
<p class="text-muted"> {{ _("No Assessments") }} </p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endmacro %}
|
||||
|
||||
{%- block script %}
|
||||
{{ super() }}
|
||||
<script>
|
||||
frappe.boot.user = {
|
||||
"can_create": [],
|
||||
"can_select": ["LMS Course"],
|
||||
"can_read": ["LMS Course"]
|
||||
};
|
||||
let courses = {{ courses | json }};
|
||||
let class_name = "{{ class_info.name }}";
|
||||
</script>
|
||||
{{ include_script('controls.bundle.js') }}
|
||||
{% endblock %}
|
||||
@@ -2,4 +2,130 @@ frappe.ready(() => {
|
||||
$(".clickable-row").click((e) => {
|
||||
window.location.href = $(e.currentTarget).data("href");
|
||||
});
|
||||
|
||||
$(".btn-schedule-eval").click((e) => {
|
||||
open_evaluation_form(e);
|
||||
});
|
||||
|
||||
$(document).on("click", ".slot", (e) => {
|
||||
mark_active_slot(e);
|
||||
});
|
||||
});
|
||||
|
||||
const open_evaluation_form = (e) => {
|
||||
this.eval_form = new frappe.ui.Dialog({
|
||||
title: __("Schedule Evaluation"),
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldname: "course",
|
||||
label: __("Course"),
|
||||
options: "LMS Course",
|
||||
reqd: 1,
|
||||
filters: {
|
||||
name: ["in", courses],
|
||||
},
|
||||
filter_description: " ",
|
||||
},
|
||||
{
|
||||
fieldtype: "Date",
|
||||
fieldname: "date",
|
||||
label: __("Date"),
|
||||
reqd: 1,
|
||||
min_date: new Date(
|
||||
frappe.datetime.add_days(frappe.datetime.get_today(), 1)
|
||||
),
|
||||
change: () => {
|
||||
get_slots();
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "slots",
|
||||
label: __("Slots"),
|
||||
},
|
||||
],
|
||||
primary_action: (values) => {
|
||||
submit_evaluation_form(values);
|
||||
},
|
||||
});
|
||||
this.eval_form.show();
|
||||
setTimeout(() => {
|
||||
$(".modal-body").css("min-height", "300px");
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const get_slots = () => {
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.course_evaluator.course_evaluator.get_schedule",
|
||||
args: {
|
||||
course: this.eval_form.get_value("course"),
|
||||
date: this.eval_form.get_value("date"),
|
||||
class_name: class_name,
|
||||
},
|
||||
callback: (r) => {
|
||||
if (r.message) {
|
||||
display_slots(r.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const display_slots = (slots) => {
|
||||
let slot_html = "";
|
||||
let day = moment(this.eval_form.get_value("date")).format("dddd");
|
||||
|
||||
slots.forEach((slot) => {
|
||||
if (slot.day == day) {
|
||||
slot_html += `<div class="btn btn-sm btn-default slot" data-day="${
|
||||
slot.day
|
||||
}"
|
||||
data-start="${slot.start_time}" data-end="${slot.end_time}">
|
||||
${moment(slot.start_time, "hh:mm").format("hh:mm a")} -
|
||||
${moment(slot.end_time, "hh:mm").format("hh:mm a")}
|
||||
</div>`;
|
||||
}
|
||||
});
|
||||
|
||||
if (!slot_html) {
|
||||
slot_html = `<div class="alert alert-danger" role="alert">
|
||||
No slots available for this date.
|
||||
</div>`;
|
||||
}
|
||||
|
||||
$("[data-fieldname='slots']").html(slot_html);
|
||||
};
|
||||
|
||||
const mark_active_slot = (e) => {
|
||||
$(".slot").removeClass("btn-outline-primary");
|
||||
$(e.currentTarget).addClass("btn-outline-primary");
|
||||
this.current_slot = $(e.currentTarget);
|
||||
};
|
||||
|
||||
const submit_evaluation_form = (values) => {
|
||||
if (!this.current_slot) {
|
||||
frappe.throw(__("Please select a slot"));
|
||||
}
|
||||
|
||||
this.eval_form.hide();
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.create_certificate_request",
|
||||
args: {
|
||||
course: values.course,
|
||||
date: values.date,
|
||||
start_time: this.current_slot.data("start"),
|
||||
end_time: this.current_slot.data("end"),
|
||||
day: this.current_slot.data("day"),
|
||||
class_name: class_name,
|
||||
},
|
||||
callback: (r) => {
|
||||
frappe.show_alert({
|
||||
message: __("Evaluation scheduled successfully"),
|
||||
indicator: "green",
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import frappe
|
||||
from lms.lms.utils import has_course_moderator_role
|
||||
from lms.lms.utils import has_course_moderator_role, has_course_evaluator_role
|
||||
from frappe import _
|
||||
from lms.www.utils import get_assessments
|
||||
|
||||
@@ -10,6 +10,7 @@ def get_context(context):
|
||||
student = frappe.form_dict["username"]
|
||||
class_name = frappe.form_dict["classname"]
|
||||
context.is_moderator = has_course_moderator_role()
|
||||
context.is_evaluator = has_course_evaluator_role()
|
||||
|
||||
context.student = frappe.db.get_value(
|
||||
"User",
|
||||
@@ -17,8 +18,36 @@ def get_context(context):
|
||||
["first_name", "full_name", "name", "last_active", "username"],
|
||||
as_dict=True,
|
||||
)
|
||||
if (
|
||||
not context.is_moderator
|
||||
and not context.is_evaluator
|
||||
and not context.student.name == frappe.session.user
|
||||
):
|
||||
raise frappe.PermissionError(_("You don't have permission to access this page."))
|
||||
|
||||
context.class_info = frappe.db.get_value(
|
||||
"LMS Class", class_name, ["name"], as_dict=True
|
||||
)
|
||||
|
||||
context.courses = frappe.get_all(
|
||||
"Class Course", {"parent": class_name}, pluck="course"
|
||||
)
|
||||
|
||||
context.assessments = get_assessments(class_name, context.student.name)
|
||||
|
||||
upcoming_evals = frappe.get_all(
|
||||
"LMS certificate Request",
|
||||
{
|
||||
"member": context.student.name,
|
||||
"course": ["in", context.courses],
|
||||
"date": [">=", frappe.utils.nowdate()],
|
||||
},
|
||||
["date", "start_time", "course", "evaluator", "google_meet_link"],
|
||||
order_by="date",
|
||||
)
|
||||
|
||||
for evals in upcoming_evals:
|
||||
evals.course_title = frappe.db.get_value("LMS Course", evals.course, "title")
|
||||
evals.evaluator_name = frappe.db.get_value("User", evals.evaluator, "full_name")
|
||||
|
||||
context.upcoming_evals = upcoming_evals
|
||||
|
||||
Reference in New Issue
Block a user