diff --git a/lms/lms/doctype/lms_batch/lms_batch.js b/lms/lms/doctype/lms_batch/lms_batch.js index 45f9d0b5..3028e564 100644 --- a/lms/lms/doctype/lms_batch/lms_batch.js +++ b/lms/lms/doctype/lms_batch/lms_batch.js @@ -10,25 +10,56 @@ frappe.ui.form.on("LMS Batch", { }, }; }); - }, - fetch_lessons: (frm) => { - frm.clear_table("scheduled_flow"); - 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("scheduled_flow"); - row.lesson = lesson.name; - row.lesson_title = lesson.title; - }); - frm.refresh_field("scheduled_flow"); - } - }, + frm.set_query("reference_doctype", "timetable", function () { + let doctypes = ["Course Lesson", "LMS Quiz", "LMS Assignment"]; + return { + filters: { + name: ["in", doctypes], + }, + }; }); }, + + 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) => { + add_timetable_rows(frm, data.message); + }, + }); + } + }, }); + +const add_timetable_rows = (frm, timetable) => { + timetable.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("timetable"); + frm.save(); +}; diff --git a/lms/lms/doctype/lms_batch/lms_batch.json b/lms/lms/doctype/lms_batch/lms_batch.json index a7949ce3..2f916303 100644 --- a/lms/lms/doctype/lms_batch/lms_batch.json +++ b/lms/lms/doctype/lms_batch/lms_batch.json @@ -35,8 +35,11 @@ "assessment_tab", "assessment", "schedule_tab", - "fetch_lessons", - "scheduled_flow" + "timetable_template", + "column_break_anya", + "show_live_class", + "section_break_ontp", + "timetable" ], "fields": [ { @@ -146,25 +149,14 @@ "fieldtype": "Autocomplete", "label": "Category" }, - { - "fieldname": "scheduled_flow", - "fieldtype": "Table", - "label": "Scheduled Flow", - "options": "Scheduled Flow" - }, { "fieldname": "section_break_ubxi", "fieldtype": "Section Break" }, - { - "fieldname": "fetch_lessons", - "fieldtype": "Button", - "label": "Fetch Lessons" - }, { "fieldname": "schedule_tab", "fieldtype": "Tab Break", - "label": "Schedule" + "label": "Timetable" }, { "fieldname": "section_break_gsac", @@ -199,11 +191,37 @@ "fieldname": "published", "fieldtype": "Check", "label": "Published" + }, + { + "fieldname": "timetable", + "fieldtype": "Table", + "label": "Timetable", + "options": "LMS Batch Timetable" + }, + { + "fieldname": "timetable_template", + "fieldtype": "Link", + "label": "Timetable Template", + "options": "LMS Timetable Template" + }, + { + "fieldname": "column_break_anya", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "show_live_class", + "fieldtype": "Check", + "label": "Show Live Class" + }, + { + "fieldname": "section_break_ontp", + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-09-12 12:30:06.565104", + "modified": "2023-09-20 11:25:10.683688", "modified_by": "Administrator", "module": "LMS", "name": "LMS Batch", diff --git a/lms/lms/doctype/lms_batch/lms_batch.py b/lms/lms/doctype/lms_batch/lms_batch.py index cff87d28..f7e824d7 100644 --- a/lms/lms/doctype/lms_batch/lms_batch.py +++ b/lms/lms/doctype/lms_batch/lms_batch.py @@ -6,9 +6,11 @@ import requests import base64 import json from frappe import _ +from datetime import timedelta from frappe.model.document import Document from frappe.utils import cint, format_date, format_datetime -from lms.lms.utils import get_lessons +from lms.lms.utils import get_lessons, get_lesson_index, get_lesson_url +from lms.www.utils import get_quiz_details, get_assignment_details class LMSBatch(Document): @@ -19,7 +21,7 @@ class LMSBatch(Document): self.validate_duplicate_students() self.validate_duplicate_assessments() self.validate_membership() - self.validate_schedule() + self.validate_timetable() def validate_duplicate_students(self): students = [row.student for row in self.students] @@ -68,8 +70,8 @@ class LMSBatch(Document): if cint(self.seat_count) < len(self.students): frappe.throw(_("There are no seats available in this batch.")) - def validate_schedule(self): - for schedule in self.scheduled_flow: + def validate_timetable(self): + for schedule in self.timetable: if schedule.start_time and schedule.end_time: if ( schedule.start_time > schedule.end_time or schedule.start_time == schedule.end_time @@ -266,3 +268,66 @@ def add_course(course, parent, name=None, evaluator=None): doc.save() return doc.name + + +@frappe.whitelist() +def get_batch_timetable(batch): + timetable = frappe.get_all( + "LMS Batch Timetable", + filters={"parent": batch}, + fields=["reference_doctype", "reference_docname", "date", "start_time", "end_time"], + order_by="date", + ) + + show_live_class = frappe.db.get_value("LMS Batch", batch, "show_live_class") + if show_live_class: + live_classes = get_live_classes(batch) + timetable.extend(live_classes) + + timetable = get_timetable_details(timetable) + return timetable + + +def get_live_classes(batch): + live_classes = frappe.get_all( + "LMS Live Class", + {"batch_name": batch}, + ["name", "title", "date", "time as start_time", "duration", "join_url as url"], + order_by="date", + ) + for class_ in live_classes: + class_.end_time = class_.start_time + timedelta(minutes=class_.duration) + class_.reference_doctype = "LMS Live Class" + class_.reference_docname = class_.name + class_.icon = "icon-call" + + return live_classes + + +def get_timetable_details(timetable): + for entry in timetable: + entry.title = frappe.db.get_value( + entry.reference_doctype, entry.reference_docname, "title" + ) + assessment = frappe._dict({"assessment_name": entry.reference_docname}) + + if entry.reference_doctype == "Course Lesson": + entry.icon = "icon-list" + course = frappe.db.get_value( + entry.reference_doctype, entry.reference_docname, "course" + ) + entry.url = get_lesson_url(course, get_lesson_index(entry.reference_docname)) + + elif entry.reference_doctype == "LMS Quiz": + entry.icon = "icon-quiz" + entry.url = "/quizzes" + details = get_quiz_details(assessment, frappe.session.user) + entry.update(details) + + elif entry.reference_doctype == "LMS Assignment": + entry.icon = "icon-quiz" + details = get_assignment_details(assessment, frappe.session.user) + entry.update(details) + + timetable = sorted(timetable, key=lambda k: k["date"]) + return timetable diff --git a/lms/lms/doctype/lms_batch_timetable/__init__.py b/lms/lms/doctype/lms_batch_timetable/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.js b/lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.js new file mode 100644 index 00000000..4cecd9c3 --- /dev/null +++ b/lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("LMS Batch Timetable", { +// refresh(frm) { + +// }, +// }); diff --git a/lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.json b/lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.json new file mode 100644 index 00000000..1eacc738 --- /dev/null +++ b/lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.json @@ -0,0 +1,81 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "hash", + "creation": "2023-09-14 12:44:51.098956", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "column_break_htdc", + "reference_doctype", + "reference_docname", + "date", + "column_break_merq", + "start_time", + "end_time", + "duration" + ], + "fields": [ + { + "fieldname": "reference_doctype", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Reference DocType", + "options": "DocType" + }, + { + "fieldname": "reference_docname", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Reference DocName", + "options": "reference_doctype" + }, + { + "fieldname": "column_break_merq", + "fieldtype": "Column Break" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date" + }, + { + "fieldname": "start_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "Start Time" + }, + { + "fieldname": "duration", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Duration" + }, + { + "fieldname": "column_break_htdc", + "fieldtype": "Column Break" + }, + { + "fieldname": "end_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "End Time" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-09-15 10:35:40.642660", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Batch Timetable", + "naming_rule": "Random", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.py b/lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.py new file mode 100644 index 00000000..257896a6 --- /dev/null +++ b/lms/lms/doctype/lms_batch_timetable/lms_batch_timetable.py @@ -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 LMSBatchTimetable(Document): + pass diff --git a/lms/lms/doctype/lms_batch_timetable/test_lms_batch_timetable.py b/lms/lms/doctype/lms_batch_timetable/test_lms_batch_timetable.py new file mode 100644 index 00000000..68754a5f --- /dev/null +++ b/lms/lms/doctype/lms_batch_timetable/test_lms_batch_timetable.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestLMSBatchTimetable(FrappeTestCase): + pass diff --git a/lms/lms/doctype/lms_live_class/lms_live_class.json b/lms/lms/doctype/lms_live_class/lms_live_class.json index 1e68dbfc..74e21479 100644 --- a/lms/lms/doctype/lms_live_class/lms_live_class.json +++ b/lms/lms/doctype/lms_live_class/lms_live_class.json @@ -10,16 +10,16 @@ "title", "host", "batch_name", - "password", - "auto_recording", "column_break_astv", - "description", - "section_break_glxh", "date", - "timezone", - "column_break_spvt", "time", "duration", + "section_break_glxh", + "description", + "column_break_spvt", + "timezone", + "password", + "auto_recording", "section_break_yrpq", "start_url", "column_break_yokr", @@ -126,7 +126,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-03-14 18:44:48.813103", + "modified": "2023-09-20 11:29:20.899897", "modified_by": "Administrator", "module": "LMS", "name": "LMS Live Class", @@ -157,8 +157,10 @@ "write": 1 } ], + "show_title_field_in_link": 1, "sort_field": "modified", "sort_order": "DESC", "states": [], + "title_field": "title", "track_changes": 1 } \ No newline at end of file diff --git a/lms/lms/doctype/lms_timetable_template/__init__.py b/lms/lms/doctype/lms_timetable_template/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_timetable_template/lms_timetable_template.js b/lms/lms/doctype/lms_timetable_template/lms_timetable_template.js new file mode 100644 index 00000000..a9bff04e --- /dev/null +++ b/lms/lms/doctype/lms_timetable_template/lms_timetable_template.js @@ -0,0 +1,15 @@ +// 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"]; + return { + filters: { + name: ["in", doctypes], + }, + }; + }); + }, +}); diff --git a/lms/lms/doctype/lms_timetable_template/lms_timetable_template.json b/lms/lms/doctype/lms_timetable_template/lms_timetable_template.json new file mode 100644 index 00000000..99c88628 --- /dev/null +++ b/lms/lms/doctype/lms_timetable_template/lms_timetable_template.json @@ -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" +} \ No newline at end of file diff --git a/lms/lms/doctype/lms_timetable_template/lms_timetable_template.py b/lms/lms/doctype/lms_timetable_template/lms_timetable_template.py new file mode 100644 index 00000000..21e7c70d --- /dev/null +++ b/lms/lms/doctype/lms_timetable_template/lms_timetable_template.py @@ -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 diff --git a/lms/lms/doctype/lms_timetable_template/test_lms_timetable_template.py b/lms/lms/doctype/lms_timetable_template/test_lms_timetable_template.py new file mode 100644 index 00000000..090d1a58 --- /dev/null +++ b/lms/lms/doctype/lms_timetable_template/test_lms_timetable_template.py @@ -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 diff --git a/lms/public/css/style.css b/lms/public/css/style.css index e414f449..e79eec3b 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -2352,4 +2352,94 @@ select { left: 80%; z-index: 10; width: fit-content; +} + +.toastui-calendar-milestone { + display: none; +} + +.toastui-calendar-task { + display: none; +} + +.toastui-calendar-panel-resizer { + display: none; +} + +.toastui-calendar-day-name__date { + font-size: var(--text-base) !important; +} + +.toastui-calendar-day-name__name { + font-size: var(--text-base) !important; +} + +.toastui-calendar-day-view-day-names, .toastui-calendar-week-view-day-names { + border-bottom: none !important; +} + +.toastui-calendar-layout { + border: 1px solid var(--gray-200) !important; + border-radius: var(--border-radius-md) !important; + background-color: var(--gray-100) !important; +} + +.toastui-calendar-panel .toastui-calendar-day-names.toastui-calendar-week { + border-top: none !important; +} + +.toastui-calendar-panel.toastui-calendar-time { + height: 80% !important; +} + +.toastui-calendar-panel.toastui-calendar-week-view-day-names { + background-color: var(--gray-50) !important; +} + +.toastui-calendar-allday { + border-bottom: 1px solid var(--gray-200) !important; +} + +.calendar-navigation { + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 1rem; +} + +.calendar-range { + margin: 0 2rem; + font-weight: 500; + color: var(--text-color); +} + +.calendar-event-title { + font-size: var(--text-md); + font-weight: 500; + 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; } \ No newline at end of file diff --git a/lms/www/batches/batch.html b/lms/www/batches/batch.html index b6398a26..bbfa676c 100644 --- a/lms/www/batches/batch.html +++ b/lms/www/batches/batch.html @@ -105,13 +105,10 @@ - {% if flow | length %} + {% if show_timetable %}