feat: timetable legends and template
This commit is contained in:
@@ -26,22 +26,41 @@ frappe.ui.form.on("LMS Batch", {
|
||||
});
|
||||
},
|
||||
|
||||
fetch_lessons: (frm) => {
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_batch.lms_batch.fetch_lessons",
|
||||
args: {
|
||||
courses: frm.doc.courses,
|
||||
},
|
||||
callback: (r) => {
|
||||
if (r.message) {
|
||||
r.message.forEach((lesson) => {
|
||||
let row = frm.add_child("timetable");
|
||||
row.lesson = lesson.name;
|
||||
row.lesson_title = lesson.title;
|
||||
timetable_template: function (frm) {
|
||||
if (frm.doc.timetable_template) {
|
||||
frm.clear_table("timetable");
|
||||
frm.refresh_fields();
|
||||
|
||||
frappe.call({
|
||||
method: "frappe.client.get_list",
|
||||
args: {
|
||||
doctype: "LMS Batch Timetable",
|
||||
parent: "LMS Timetable Template",
|
||||
fields: [
|
||||
"reference_doctype",
|
||||
"reference_docname",
|
||||
"date",
|
||||
"start_time",
|
||||
"end_time",
|
||||
],
|
||||
filters: {
|
||||
parent: frm.doc.timetable_template,
|
||||
},
|
||||
order_by: "idx",
|
||||
},
|
||||
callback: (data) => {
|
||||
data.message.forEach((row) => {
|
||||
let child = frm.add_child("timetable");
|
||||
child.reference_doctype = row.reference_doctype;
|
||||
child.reference_docname = row.reference_docname;
|
||||
child.date = row.date;
|
||||
child.start_time = row.start_time;
|
||||
child.end_time = row.end_time;
|
||||
});
|
||||
frm.refresh_field("scheduled_flow");
|
||||
}
|
||||
},
|
||||
});
|
||||
frm.refresh_field("timetable");
|
||||
frm.save();
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"assessment_tab",
|
||||
"assessment",
|
||||
"schedule_tab",
|
||||
"fetch_lessons",
|
||||
"timetable_template",
|
||||
"timetable"
|
||||
],
|
||||
"fields": [
|
||||
@@ -150,11 +150,6 @@
|
||||
"fieldname": "section_break_ubxi",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "fetch_lessons",
|
||||
"fieldtype": "Button",
|
||||
"label": "Fetch Lessons"
|
||||
},
|
||||
{
|
||||
"fieldname": "schedule_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
@@ -199,11 +194,17 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Timetable",
|
||||
"options": "LMS Batch Timetable"
|
||||
},
|
||||
{
|
||||
"fieldname": "timetable_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Timetable Template",
|
||||
"options": "LMS Timetable Template"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-14 12:51:11.847853",
|
||||
"modified": "2023-09-18 17:36:03.621651",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Batch",
|
||||
|
||||
0
lms/lms/doctype/lms_timetable_template/__init__.py
Normal file
0
lms/lms/doctype/lms_timetable_template/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2023, Frappe and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("LMS Timetable Template", {
|
||||
refresh(frm) {
|
||||
frm.set_query("reference_doctype", "timetable", function () {
|
||||
let doctypes = [
|
||||
"Course Lesson",
|
||||
"LMS Quiz",
|
||||
"LMS Assignment",
|
||||
"LMS Live Class",
|
||||
];
|
||||
return {
|
||||
filters: {
|
||||
name: ["in", doctypes],
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "hash",
|
||||
"creation": "2023-09-18 14:16:16.964077",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"timetable"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"fieldname": "timetable",
|
||||
"fieldtype": "Table",
|
||||
"label": "Timetable",
|
||||
"options": "LMS Batch Timetable"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-18 17:57:15.819072",
|
||||
"modified_by": "Administrator",
|
||||
"module": "LMS",
|
||||
"name": "LMS Timetable Template",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Moderator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"show_title_field_in_link": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class LMSTimetableTemplate(Document):
|
||||
pass
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestLMSTimetableTemplate(FrappeTestCase):
|
||||
pass
|
||||
@@ -2414,7 +2414,32 @@ select {
|
||||
}
|
||||
|
||||
.calendar-event-title {
|
||||
font-size: var(--text-base);
|
||||
font-size: var(--text-md);
|
||||
font-weight: 500;
|
||||
margin-top: 0.25rem;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 50px;
|
||||
height: 20px;
|
||||
border-radius: var(--border-radius-sm);
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.legend-text {
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.calendar-legends {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 50%;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
<ul class="nav lms-nav" id="batches-tab">
|
||||
|
||||
{% if is_student or is_moderator %}
|
||||
{% if is_student %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if is_student %} active {% endif %}" data-toggle="tab" href="#dashboard">
|
||||
{{ _("Dashboard") }}
|
||||
@@ -530,29 +530,18 @@
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="calendar-legends">
|
||||
{% for legend in legends %}
|
||||
<div class="legend-item">
|
||||
<div class="legend-color" style="background-color: {{ legend.color }}"></div>
|
||||
<div class="legend-text">{{ legend.title }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div id="calendar" class="timetable-calendar" style="height: 700px"
|
||||
data-start="{{ batch_info.start_time }}" data-end="{{ batch_info.end_time }}">
|
||||
</div>
|
||||
|
||||
<!-- <div>
|
||||
{% for week in timetable %}
|
||||
<div>
|
||||
<div class="bold-heading">
|
||||
{{ _("Week ") }} {{ loop.index }}
|
||||
</div>
|
||||
<div>
|
||||
{% for entry in timetable[week] %}
|
||||
<a {% if is_student %} href="{{ entry.url }}" {% endif %}>
|
||||
<svg class="icon icon-md">
|
||||
<use href="#{{ entry.icon }}"></use>
|
||||
</svg>
|
||||
{{ entry.title }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div> -->
|
||||
</article>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -9,12 +9,14 @@ frappe.ready(() => {
|
||||
if ($("#calendar").length) {
|
||||
$(document).on("click", "#prev-week", (e) => {
|
||||
this.calendar_ && this.calendar_.prev();
|
||||
set_calendar_range(this.calendar_, this.events);
|
||||
});
|
||||
}
|
||||
|
||||
if ($("#calendar").length) {
|
||||
$(document).on("click", "#next-week", (e) => {
|
||||
this.calendar_ && this.calendar_.next();
|
||||
set_calendar_range(this.calendar_, this.events);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -624,6 +626,7 @@ const submit_evaluation_form = (values) => {
|
||||
};
|
||||
|
||||
const setup_timetable = () => {
|
||||
let self = this;
|
||||
frappe.call({
|
||||
method: "lms.lms.doctype.lms_batch.lms_batch.get_batch_timetable",
|
||||
args: {
|
||||
@@ -632,6 +635,7 @@ const setup_timetable = () => {
|
||||
callback: (r) => {
|
||||
if (r.message.length) {
|
||||
setup_calendar(r.message);
|
||||
self.events = r.message;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -640,19 +644,29 @@ const setup_timetable = () => {
|
||||
const setup_calendar = (events) => {
|
||||
const element = $("#calendar");
|
||||
const Calendar = tui.Calendar;
|
||||
let calendar_events = [];
|
||||
let calendar_id = "calendar1";
|
||||
const calendar_id = "calendar1";
|
||||
const container = element[0];
|
||||
const start_time = $(elemet).data("start");
|
||||
const end_time = $(elemet).data("end");
|
||||
const options = get_calendar_options(element, calendar_id);
|
||||
const calendar = new Calendar(container, options);
|
||||
this.calendar_ = calendar;
|
||||
console.log(options);
|
||||
create_events(calendar, events);
|
||||
add_links_to_events(calendar, events);
|
||||
scroll_to_date(calendar, events);
|
||||
set_calendar_range(calendar, events);
|
||||
};
|
||||
|
||||
const options = {
|
||||
const get_calendar_options = (element, calendar_id) => {
|
||||
const start_time = element.data("start");
|
||||
const end_time = element.data("end");
|
||||
|
||||
return {
|
||||
defaultView: "week",
|
||||
usageStatistics: false,
|
||||
week: {
|
||||
narrowWeekend: true,
|
||||
hourStart: 7,
|
||||
hourEnd: 18,
|
||||
hourStart: parseInt(start_time.split(":")[0]) - 1,
|
||||
/* hourEnd: parseInt(end_time.split(":")[0]) + 1, */
|
||||
},
|
||||
month: {
|
||||
narrowWeekend: true,
|
||||
@@ -663,7 +677,7 @@ const setup_calendar = (events) => {
|
||||
{
|
||||
id: calendar_id,
|
||||
name: "Timetable",
|
||||
backgroundColor: "#ffffff",
|
||||
backgroundColor: "var(--fg-color)",
|
||||
},
|
||||
],
|
||||
template: {
|
||||
@@ -676,11 +690,12 @@ const setup_calendar = (events) => {
|
||||
},
|
||||
},
|
||||
};
|
||||
const calendar = new Calendar(container, options);
|
||||
this.calendar_ = calendar;
|
||||
};
|
||||
|
||||
const create_events = (calendar, events, calendar_id) => {
|
||||
let calendar_events = [];
|
||||
|
||||
events.forEach((event, idx) => {
|
||||
let colors = get_background_color(event.reference_doctype);
|
||||
calendar_events.push({
|
||||
id: `event${idx}`,
|
||||
calendarId: calendar_id,
|
||||
@@ -688,12 +703,13 @@ const setup_calendar = (events) => {
|
||||
start: `${event.date}T${event.start_time}`,
|
||||
end: `${event.date}T${event.end_time}`,
|
||||
isAllday: event.start_time ? false : true,
|
||||
borderColor: colors.dark,
|
||||
borderColor: get_background_color(event.reference_doctype),
|
||||
backgroundColor: "var(--fg-color)",
|
||||
customStyle: {
|
||||
borderRadius: "var(--border-radius-md)",
|
||||
boxShadow: "var(--shadow-base)",
|
||||
borderWidth: "8px",
|
||||
padding: "1rem",
|
||||
padding: "0.25rem 0.5rem 0.5rem",
|
||||
},
|
||||
raw: {
|
||||
url: event.url,
|
||||
@@ -702,44 +718,50 @@ const setup_calendar = (events) => {
|
||||
});
|
||||
|
||||
calendar.createEvents(calendar_events);
|
||||
};
|
||||
|
||||
const add_links_to_events = (calendar, events) => {
|
||||
calendar.on("clickEvent", ({ event }) => {
|
||||
const el = document.getElementById("clicked-event");
|
||||
window.open(event.raw.url, "_blank");
|
||||
});
|
||||
};
|
||||
|
||||
if (new Date().getMonth() < new Date(events[0].date).getMonth()) {
|
||||
const scroll_to_date = (calendar, events) => {
|
||||
if (
|
||||
new Date() < new Date(events[0].date) ||
|
||||
new Date() > new Date(events.slice(-1).date)
|
||||
) {
|
||||
calendar.setDate(new Date(events[0].date));
|
||||
}
|
||||
};
|
||||
|
||||
let week_start = frappe.datetime.global_date_format(
|
||||
calendar.getDateRangeStart().d.d
|
||||
const set_calendar_range = (calendar, events) => {
|
||||
let week_start = moment(calendar.getDateRangeStart().d.d);
|
||||
let week_end = moment(calendar.getDateRangeEnd().d.d);
|
||||
|
||||
$(".calendar-range").text(
|
||||
`${moment(week_start).format("DD MMMM YYYY")} - ${moment(
|
||||
week_end
|
||||
).format("DD MMMM YYYY")}`
|
||||
);
|
||||
let week_end = frappe.datetime.global_date_format(
|
||||
calendar.getDateRangeEnd().d.d
|
||||
);
|
||||
$(".calendar-range").text(`${week_start} - ${week_end}`);
|
||||
|
||||
if (week_start.diff(moment(events[0].date), "days") <= 0) {
|
||||
$("#prev-week").hide();
|
||||
} else {
|
||||
$("#prev-week").show();
|
||||
}
|
||||
|
||||
if (week_end.diff(moment(events.slice(-1)[0].date), "days") > 0) {
|
||||
$("#next-week").hide();
|
||||
} else {
|
||||
$("#next-week").show();
|
||||
}
|
||||
};
|
||||
|
||||
const get_background_color = (doctype) => {
|
||||
if (doctype == "Course Lesson")
|
||||
return {
|
||||
light: "var(--blue-50)",
|
||||
dark: "var(--blue-400)",
|
||||
};
|
||||
if (doctype == "LMS Quiz")
|
||||
return {
|
||||
light: "var(--green-50)",
|
||||
dark: "var(--green-400)",
|
||||
};
|
||||
if (doctype == "LMS Assignment")
|
||||
return {
|
||||
light: "var(--orange-50)",
|
||||
dark: "var(--orange-400)",
|
||||
};
|
||||
if (doctype == "LMS Live Class")
|
||||
return {
|
||||
light: "var(--red-50)",
|
||||
dark: "var(--red-400)",
|
||||
};
|
||||
if (doctype == "Course Lesson") return "var(--blue-400)";
|
||||
if (doctype == "LMS Quiz") return "var(--green-400)";
|
||||
if (doctype == "LMS Assignment") return "var(--orange-400)";
|
||||
if (doctype == "LMS Live Class") return "var(--purple-400)";
|
||||
};
|
||||
|
||||
@@ -95,14 +95,7 @@ def get_context(context):
|
||||
"parent": batch_name,
|
||||
},
|
||||
)
|
||||
print(
|
||||
frappe.db.count(
|
||||
"LMS Batch Timetable",
|
||||
{
|
||||
"parent": batch_name,
|
||||
},
|
||||
)
|
||||
)
|
||||
context.legends = get_legends()
|
||||
|
||||
|
||||
def get_all_quizzes(batch_name):
|
||||
@@ -258,3 +251,24 @@ def get_course_progress(batch_courses, student_details):
|
||||
student_details.courses[course.course] = membership.progress
|
||||
else:
|
||||
student_details.courses[course.course] = 0
|
||||
|
||||
|
||||
def get_legends():
|
||||
return [
|
||||
{
|
||||
"title": "Lesson",
|
||||
"color": "var(--blue-400)",
|
||||
},
|
||||
{
|
||||
"title": "Quiz",
|
||||
"color": "var(--green-400)",
|
||||
},
|
||||
{
|
||||
"title": "Assignment",
|
||||
"color": "var(--orange-400)",
|
||||
},
|
||||
{
|
||||
"title": "Live Class",
|
||||
"color": "var(--purple-400)",
|
||||
},
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user