feat: timetable legends and template

This commit is contained in:
Jannat Patel
2023-09-18 19:16:57 +05:30
parent 24e9f46e2f
commit 8098532215
11 changed files with 266 additions and 93 deletions

View File

@@ -26,22 +26,41 @@ frappe.ui.form.on("LMS Batch", {
}); });
}, },
fetch_lessons: (frm) => { timetable_template: function (frm) {
frappe.call({ if (frm.doc.timetable_template) {
method: "lms.lms.doctype.lms_batch.lms_batch.fetch_lessons", frm.clear_table("timetable");
args: { frm.refresh_fields();
courses: frm.doc.courses,
}, frappe.call({
callback: (r) => { method: "frappe.client.get_list",
if (r.message) { args: {
r.message.forEach((lesson) => { doctype: "LMS Batch Timetable",
let row = frm.add_child("timetable"); parent: "LMS Timetable Template",
row.lesson = lesson.name; fields: [
row.lesson_title = lesson.title; "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();
}, },
}); });
}
}, },
}); });

View File

@@ -35,7 +35,7 @@
"assessment_tab", "assessment_tab",
"assessment", "assessment",
"schedule_tab", "schedule_tab",
"fetch_lessons", "timetable_template",
"timetable" "timetable"
], ],
"fields": [ "fields": [
@@ -150,11 +150,6 @@
"fieldname": "section_break_ubxi", "fieldname": "section_break_ubxi",
"fieldtype": "Section Break" "fieldtype": "Section Break"
}, },
{
"fieldname": "fetch_lessons",
"fieldtype": "Button",
"label": "Fetch Lessons"
},
{ {
"fieldname": "schedule_tab", "fieldname": "schedule_tab",
"fieldtype": "Tab Break", "fieldtype": "Tab Break",
@@ -199,11 +194,17 @@
"fieldtype": "Table", "fieldtype": "Table",
"label": "Timetable", "label": "Timetable",
"options": "LMS Batch Timetable" "options": "LMS Batch Timetable"
},
{
"fieldname": "timetable_template",
"fieldtype": "Link",
"label": "Timetable Template",
"options": "LMS Timetable Template"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2023-09-14 12:51:11.847853", "modified": "2023-09-18 17:36:03.621651",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "LMS", "module": "LMS",
"name": "LMS Batch", "name": "LMS Batch",

View 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],
},
};
});
},
});

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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

View File

@@ -2414,7 +2414,32 @@ select {
} }
.calendar-event-title { .calendar-event-title {
font-size: var(--text-base); font-size: var(--text-md);
font-weight: 500; 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;
} }

View File

@@ -88,7 +88,7 @@
<ul class="nav lms-nav" id="batches-tab"> <ul class="nav lms-nav" id="batches-tab">
{% if is_student or is_moderator %} {% if is_student %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if is_student %} active {% endif %}" data-toggle="tab" href="#dashboard"> <a class="nav-link {% if is_student %} active {% endif %}" data-toggle="tab" href="#dashboard">
{{ _("Dashboard") }} {{ _("Dashboard") }}
@@ -530,29 +530,18 @@
</svg> </svg>
</button> </button>
</div> </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" <div id="calendar" class="timetable-calendar" style="height: 700px"
data-start="{{ batch_info.start_time }}" data-end="{{ batch_info.end_time }}"> data-start="{{ batch_info.start_time }}" data-end="{{ batch_info.end_time }}">
</div> </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> </article>
{% endmacro %} {% endmacro %}

View File

@@ -9,12 +9,14 @@ frappe.ready(() => {
if ($("#calendar").length) { if ($("#calendar").length) {
$(document).on("click", "#prev-week", (e) => { $(document).on("click", "#prev-week", (e) => {
this.calendar_ && this.calendar_.prev(); this.calendar_ && this.calendar_.prev();
set_calendar_range(this.calendar_, this.events);
}); });
} }
if ($("#calendar").length) { if ($("#calendar").length) {
$(document).on("click", "#next-week", (e) => { $(document).on("click", "#next-week", (e) => {
this.calendar_ && this.calendar_.next(); this.calendar_ && this.calendar_.next();
set_calendar_range(this.calendar_, this.events);
}); });
} }
@@ -624,6 +626,7 @@ const submit_evaluation_form = (values) => {
}; };
const setup_timetable = () => { const setup_timetable = () => {
let self = this;
frappe.call({ frappe.call({
method: "lms.lms.doctype.lms_batch.lms_batch.get_batch_timetable", method: "lms.lms.doctype.lms_batch.lms_batch.get_batch_timetable",
args: { args: {
@@ -632,6 +635,7 @@ const setup_timetable = () => {
callback: (r) => { callback: (r) => {
if (r.message.length) { if (r.message.length) {
setup_calendar(r.message); setup_calendar(r.message);
self.events = r.message;
} }
}, },
}); });
@@ -640,19 +644,29 @@ const setup_timetable = () => {
const setup_calendar = (events) => { const setup_calendar = (events) => {
const element = $("#calendar"); const element = $("#calendar");
const Calendar = tui.Calendar; const Calendar = tui.Calendar;
let calendar_events = []; const calendar_id = "calendar1";
let calendar_id = "calendar1";
const container = element[0]; const container = element[0];
const start_time = $(elemet).data("start"); const options = get_calendar_options(element, calendar_id);
const end_time = $(elemet).data("end"); 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", defaultView: "week",
usageStatistics: false, usageStatistics: false,
week: { week: {
narrowWeekend: true, narrowWeekend: true,
hourStart: 7, hourStart: parseInt(start_time.split(":")[0]) - 1,
hourEnd: 18, /* hourEnd: parseInt(end_time.split(":")[0]) + 1, */
}, },
month: { month: {
narrowWeekend: true, narrowWeekend: true,
@@ -663,7 +677,7 @@ const setup_calendar = (events) => {
{ {
id: calendar_id, id: calendar_id,
name: "Timetable", name: "Timetable",
backgroundColor: "#ffffff", backgroundColor: "var(--fg-color)",
}, },
], ],
template: { 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) => { events.forEach((event, idx) => {
let colors = get_background_color(event.reference_doctype);
calendar_events.push({ calendar_events.push({
id: `event${idx}`, id: `event${idx}`,
calendarId: calendar_id, calendarId: calendar_id,
@@ -688,12 +703,13 @@ const setup_calendar = (events) => {
start: `${event.date}T${event.start_time}`, start: `${event.date}T${event.start_time}`,
end: `${event.date}T${event.end_time}`, end: `${event.date}T${event.end_time}`,
isAllday: event.start_time ? false : true, isAllday: event.start_time ? false : true,
borderColor: colors.dark, borderColor: get_background_color(event.reference_doctype),
backgroundColor: "var(--fg-color)",
customStyle: { customStyle: {
borderRadius: "var(--border-radius-md)", borderRadius: "var(--border-radius-md)",
boxShadow: "var(--shadow-base)", boxShadow: "var(--shadow-base)",
borderWidth: "8px", borderWidth: "8px",
padding: "1rem", padding: "0.25rem 0.5rem 0.5rem",
}, },
raw: { raw: {
url: event.url, url: event.url,
@@ -702,44 +718,50 @@ const setup_calendar = (events) => {
}); });
calendar.createEvents(calendar_events); calendar.createEvents(calendar_events);
};
const add_links_to_events = (calendar, events) => {
calendar.on("clickEvent", ({ event }) => { calendar.on("clickEvent", ({ event }) => {
const el = document.getElementById("clicked-event"); const el = document.getElementById("clicked-event");
window.open(event.raw.url, "_blank"); 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)); calendar.setDate(new Date(events[0].date));
} }
};
let week_start = frappe.datetime.global_date_format( const set_calendar_range = (calendar, events) => {
calendar.getDateRangeStart().d.d 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 if (week_start.diff(moment(events[0].date), "days") <= 0) {
); $("#prev-week").hide();
$(".calendar-range").text(`${week_start} - ${week_end}`); } 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) => { const get_background_color = (doctype) => {
if (doctype == "Course Lesson") if (doctype == "Course Lesson") return "var(--blue-400)";
return { if (doctype == "LMS Quiz") return "var(--green-400)";
light: "var(--blue-50)", if (doctype == "LMS Assignment") return "var(--orange-400)";
dark: "var(--blue-400)", if (doctype == "LMS Live Class") return "var(--purple-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)",
};
}; };

View File

@@ -95,14 +95,7 @@ def get_context(context):
"parent": batch_name, "parent": batch_name,
}, },
) )
print( context.legends = get_legends()
frappe.db.count(
"LMS Batch Timetable",
{
"parent": batch_name,
},
)
)
def get_all_quizzes(batch_name): 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 student_details.courses[course.course] = membership.progress
else: else:
student_details.courses[course.course] = 0 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)",
},
]