diff --git a/lms/lms/doctype/lms_class/lms_class.py b/lms/lms/doctype/lms_class/lms_class.py index 4a91416d..b2186d5d 100644 --- a/lms/lms/doctype/lms_class/lms_class.py +++ b/lms/lms/doctype/lms_class/lms_class.py @@ -4,7 +4,10 @@ import frappe from frappe.model.document import Document from frappe import _ -from frappe.utils import cint +from frappe.utils import cint, format_date, format_datetime +import requests +import base64 +import json class LMSClass(Document): @@ -85,3 +88,72 @@ def update_course(class_name, course, value): else: frappe.db.delete("Class Course", {"parent": class_name, "course": course}) return True + + +@frappe.whitelist() +def create_live_class( + class_name, title, duration, date, time, timezone, auto_recording, description=None +): + date = format_date(date, "yyyy-mm-dd", True) + + payload = { + "topic": title, + "start_time": format_datetime(f"{date} {time}", "yyyy-MM-ddTHH:mm:ssZ"), + "duration": duration, + "agenda": description, + "private_meeting": True, + "auto_recording": "none" + if auto_recording == "No Recording" + else auto_recording.lower(), + "timezone": timezone, + } + headers = { + "Authorization": "Bearer " + authenticate(), + "content-type": "application/json", + } + response = requests.post( + "https://api.zoom.us/v2/users/me/meetings", headers=headers, data=json.dumps(payload) + ) + + if response.status_code == 201: + data = json.loads(response.text) + payload.update( + { + "doctype": "LMS Live Class", + "start_url": data.get("start_url"), + "join_url": data.get("join_url"), + "title": title, + "host": frappe.session.user, + "date": date, + "time": time, + "class_name": class_name, + "password": data.get("password"), + "description": description, + "auto_recording": auto_recording, + } + ) + class_details = frappe.get_doc(payload) + class_details.save() + return class_details + + +def authenticate(): + zoom = frappe.get_single("Zoom Settings") + if not zoom.enable: + frappe.throw(_("Please enable Zoom Settings to use this feature.")) + + authenticate_url = f"https://zoom.us/oauth/token?grant_type=account_credentials&account_id={zoom.account_id}" + + headers = { + "Authorization": "Basic " + + base64.b64encode( + bytes( + zoom.client_id + + ":" + + zoom.get_password(fieldname="client_secret", raise_exception=False), + encoding="utf8", + ) + ).decode() + } + response = requests.request("POST", authenticate_url, headers=headers) + return response.json()["access_token"] diff --git a/lms/lms/doctype/lms_live_class/__init__.py b/lms/lms/doctype/lms_live_class/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_live_class/lms_live_class.js b/lms/lms/doctype/lms_live_class/lms_live_class.js new file mode 100644 index 00000000..ebba6c7d --- /dev/null +++ b/lms/lms/doctype/lms_live_class/lms_live_class.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("LMS Live Class", { +// refresh(frm) { + +// }, +// }); diff --git a/lms/lms/doctype/lms_live_class/lms_live_class.json b/lms/lms/doctype/lms_live_class/lms_live_class.json new file mode 100644 index 00000000..814bc662 --- /dev/null +++ b/lms/lms/doctype/lms_live_class/lms_live_class.json @@ -0,0 +1,164 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-03-02 10:59:01.741349", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "host", + "class_name", + "password", + "auto_recording", + "column_break_astv", + "description", + "section_break_glxh", + "date", + "timezone", + "column_break_spvt", + "time", + "duration", + "section_break_yrpq", + "start_url", + "column_break_yokr", + "join_url" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "description", + "fieldtype": "Text", + "label": "Description" + }, + { + "fieldname": "date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Date", + "reqd": 1 + }, + { + "fieldname": "duration", + "fieldtype": "Int", + "label": "Duration", + "reqd": 1 + }, + { + "fieldname": "timezone", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Timezone", + "reqd": 1 + }, + { + "fieldname": "host", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Host", + "options": "User", + "reqd": 1 + }, + { + "fieldname": "column_break_astv", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_glxh", + "fieldtype": "Section Break", + "label": "Date and Time" + }, + { + "fieldname": "column_break_spvt", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_yrpq", + "fieldtype": "Section Break" + }, + { + "fieldname": "start_url", + "fieldtype": "Small Text", + "label": "Start URL", + "read_only": 1 + }, + { + "fieldname": "column_break_yokr", + "fieldtype": "Column Break" + }, + { + "fieldname": "join_url", + "fieldtype": "Small Text", + "label": "Join URL", + "read_only": 1 + }, + { + "fieldname": "password", + "fieldtype": "Password", + "label": "Password" + }, + { + "fieldname": "time", + "fieldtype": "Time", + "label": "Time", + "reqd": 1 + }, + { + "fieldname": "class_name", + "fieldtype": "Link", + "label": "Class", + "options": "LMS Class" + }, + { + "default": "No Recording", + "fieldname": "auto_recording", + "fieldtype": "Select", + "label": "Auto Recording", + "options": "No Recording\nLocal\nCloud" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2023-03-14 18:44:48.813102", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Live Class", + "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 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/lms/lms/doctype/lms_live_class/lms_live_class.py b/lms/lms/doctype/lms_live_class/lms_live_class.py new file mode 100644 index 00000000..24d239db --- /dev/null +++ b/lms/lms/doctype/lms_live_class/lms_live_class.py @@ -0,0 +1,63 @@ +# Copyright (c) 2023, Frappe and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document +from datetime import timedelta +from frappe.utils import cint, get_datetime + + +class LMSLiveClass(Document): + def after_insert(self): + calendar = frappe.db.get_value( + "Google Calendar", {"user": frappe.session.user, "enable": 1}, "name" + ) + + if calendar: + event = self.create_event() + self.add_event_participants(event, calendar) + + def create_event(self): + start = f"{self.date} {self.time}" + + event = frappe.get_doc( + { + "doctype": "Event", + "subject": f"Live Class on {self.title}", + "starts_on": start, + "ends_on": get_datetime(start) + timedelta(minutes=cint(self.duration)), + } + ) + event.save() + + return event + + def add_event_participants(self, event, calendar): + participants = frappe.get_all( + "Class Student", {"parent": self.class_name}, pluck="student" + ) + + participants.append(frappe.session.user) + for participant in participants: + frappe.get_doc( + { + "doctype": "Event Participants", + "reference_doctype": "User", + "reference_docname": participant, + "email": participant, + "parent": event.name, + "parenttype": "Event", + "parentfield": "event_participants", + } + ).save() + + event.reload() + event.update( + { + "sync_with_google_calendar": 1, + "google_calendar": calendar, + "description": f"A Live Class has been scheduled on {frappe.utils.format_date(self.date, 'medium')} at { frappe.utils.format_time(self.time, 'hh:mm a')}. Click on this link to join. {self.join_url}. {self.description}", + } + ) + + event.save() diff --git a/lms/lms/doctype/lms_live_class/test_lms_live_class.py b/lms/lms/doctype/lms_live_class/test_lms_live_class.py new file mode 100644 index 00000000..b169369a --- /dev/null +++ b/lms/lms/doctype/lms_live_class/test_lms_live_class.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestLMSLiveClass(FrappeTestCase): + pass diff --git a/lms/lms/doctype/zoom_settings/__init__.py b/lms/lms/doctype/zoom_settings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/zoom_settings/test_zoom_settings.py b/lms/lms/doctype/zoom_settings/test_zoom_settings.py new file mode 100644 index 00000000..3162d3dc --- /dev/null +++ b/lms/lms/doctype/zoom_settings/test_zoom_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestZoomSettings(FrappeTestCase): + pass diff --git a/lms/lms/doctype/zoom_settings/zoom_settings.js b/lms/lms/doctype/zoom_settings/zoom_settings.js new file mode 100644 index 00000000..70710042 --- /dev/null +++ b/lms/lms/doctype/zoom_settings/zoom_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Zoom Settings", { +// refresh(frm) { + +// }, +// }); diff --git a/lms/lms/doctype/zoom_settings/zoom_settings.json b/lms/lms/doctype/zoom_settings/zoom_settings.json new file mode 100644 index 00000000..a20a6b5e --- /dev/null +++ b/lms/lms/doctype/zoom_settings/zoom_settings.json @@ -0,0 +1,71 @@ +{ + "actions": [], + "creation": "2023-02-27 14:30:28.696814", + "default_view": "List", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "enable", + "sb_00", + "account_id", + "client_id", + "client_secret" + ], + "fields": [ + { + "default": "0", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" + }, + { + "depends_on": "enable", + "fieldname": "sb_00", + "fieldtype": "Section Break", + "label": "OAuth Client ID" + }, + { + "description": "The Client ID obtained from the Google Cloud Console under \n\"APIs & Services\" > \"Credentials\"\n", + "fieldname": "client_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Client ID", + "mandatory_depends_on": "google_drive_picker_enabled" + }, + { + "fieldname": "client_secret", + "fieldtype": "Password", + "in_list_view": 1, + "label": "Client Secret" + }, + { + "fieldname": "account_id", + "fieldtype": "Data", + "label": "Account ID" + } + ], + "issingle": 1, + "links": [], + "modified": "2023-03-01 17:15:59.722497", + "modified_by": "Administrator", + "module": "LMS", + "name": "Zoom Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "ASC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/lms/lms/doctype/zoom_settings/zoom_settings.py b/lms/lms/doctype/zoom_settings/zoom_settings.py new file mode 100644 index 00000000..d7ed9efb --- /dev/null +++ b/lms/lms/doctype/zoom_settings/zoom_settings.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 ZoomSettings(Document): + pass diff --git a/lms/public/css/style.css b/lms/public/css/style.css index 94a244cb..d8e6b3aa 100644 --- a/lms/public/css/style.css +++ b/lms/public/css/style.css @@ -1776,7 +1776,7 @@ li { } .modal-content { - font-size: var(--text-base) !important; + font-size: var(--text-sm) !important; } .modal-header, .modal-body { @@ -1790,11 +1790,14 @@ li { .modal-footer { padding: 0.75rem 1.5rem !important; border-top: none !important; - background-color: var(--gray-200) !important; + background-color: var(--gray-50) !important; + justify-content: flex-end !important; } .modal-header .modal-title { color: var(--gray-900); + line-height: 1.5rem; + margin-bottom: 0.5rem; } .frappe-chart .title { @@ -1977,3 +1980,50 @@ select { .common-page-style .tooltip-content { display: none; } + +.resize-none { + resize: none; +} + +.lms-page-style { + background-color: var(--fg-color); + font-size: var(--text-base); +} + +.lms-card { + display: flex; + flex-direction: column; + border-radius: 0.75rem; + /* border: 1px solid var(--gray-200); */ + box-shadow: var(--shadow-sm); + padding: 0.5rem; + height: 100%; + position: relative; +} + +.live-class-panel { + margin-top: auto; +} + +.lms-card .live-class-panel .btn { + visibility: hidden; +} + +.lms-card:hover .live-class-panel .btn { + visibility: visible; +} + +.add-students ul li:nth-last-child(-n+2) { + display: none; +} + +.lms-card-title { + color: var(--gray-900); + font-weight: 500; +} + +.lms-card-parent { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + grid-gap: 1.5rem; +} diff --git a/lms/public/icons/symbol-defs.svg b/lms/public/icons/symbol-defs.svg index 167397e1..37a0ec86 100644 --- a/lms/public/icons/symbol-defs.svg +++ b/lms/public/icons/symbol-defs.svg @@ -70,4 +70,8 @@ + + + + diff --git a/lms/www/classes/class.html b/lms/www/classes/class.html index 87a73f2d..50270274 100644 --- a/lms/www/classes/class.html +++ b/lms/www/classes/class.html @@ -5,10 +5,10 @@ {% block page_content %} -
+
{{ BreadCrumb(class_info) }} -
+
{{ ClassDetails(class_info) }} {{ ClassSections(class_info, class_courses, class_students, published_courses) }}
@@ -30,23 +30,23 @@ {% macro ClassDetails(class_info) %}
-
- {% if class_info.start_date %} - - {{ frappe.utils.format_date(class_info.start_date, "medium") }} - - - {% endif %} - {% if class_info.end_date %} - - {{ frappe.utils.format_date(class_info.end_date, "medium") }} - - {% endif %} -
+
{{ class_info.title }}
+
+ + + + + {{ frappe.utils.format_date(class_info.start_date, "long") }} - + + + {{ frappe.utils.format_date(class_info.end_date, "long") }} + +
{% if class_info.description %} -
+
{{ class_info.description }}
{% endif %} @@ -74,6 +74,14 @@ + {% if class_students | length and (is_moderator or is_student) %} + + {% endif %} +
@@ -87,6 +95,12 @@ {{ StudentsSection(class_info, class_students) }}
+ {% if class_students | length and (is_moderator or is_student) %} +
+ {{ LiveClassSection(class_info, live_classes) }} +
+ {% endif %} +
{% endmacro %} @@ -109,7 +123,7 @@ {% macro StudentsSection(class_info, class_students) %} -
+
{% if is_moderator %} {{ AddStudents() }} {% endif %} @@ -151,7 +165,7 @@ {% else %} -

{{ _("No Students are added to this class.") }}

+

{{ _("No Students are added to this class.") }}

{% endif %}
@@ -160,19 +174,127 @@ {% macro AddStudents() %}
-
- {{ _("Add Student") }} -
-
-
-
- -
-
- -
+
+
{% endmacro %} + + +{% macro LiveClassSection(class_info, live_classes) %} +
+ {{ CreateLiveClass(class_info) }} + {{ LiveClassList(class_info, live_classes) }} +
+{% endmacro %} + + +{% macro CreateLiveClass(class_info) %} + +{% if is_moderator %} + + + +{% endif %} + +{% endmacro %} + + +{% macro LiveClassList(class_info, live_classes) %} +
+ {% for class in live_classes %} +
+ +
+ +
+ {{ class.title }} +
+
+
+ + + + {{ frappe.utils.format_date(class.date, "full") }} +
+
+ + + + {{ frappe.utils.format_time(class.time, "hh:mm a") }} +
+ +
+ {{ class.description }} +
+ +
+ {% endfor %} +
+{% endmacro %} + +{%- block script %} + {{ super() }} + + {% if is_moderator %} + + {% endif %} + + {{ include_script('controls.bundle.js') }} +{% endblock %} diff --git a/lms/www/classes/class.js b/lms/www/classes/class.js index 23b9c3ca..ac266856 100644 --- a/lms/www/classes/class.js +++ b/lms/www/classes/class.js @@ -10,27 +10,46 @@ frappe.ready(() => { $(".class-course").click((e) => { update_course(e); }); + + if ($("#live-class-form").length) { + make_live_class_form(); + } + + if ($(".add-students").length) { + make_add_students_section(); + } + + $("#open-class-modal").click((e) => { + e.preventDefault(); + $("#live-class-modal").modal("show"); + }); + + $("#create-live-class").click((e) => { + create_live_class(e); + }); }); const submit_student = (e) => { e.preventDefault(); - frappe.call({ - method: "lms.lms.doctype.lms_class.lms_class.add_student", - args: { - email: $("#student-email").val(), - class_name: $(".class-details").data("class"), - }, - callback: (data) => { - frappe.show_alert( - { - message: __("Student added successfully"), - indicator: "green", - }, - 3 - ); - window.location.reload(); - }, - }); + if ($('input[data-fieldname="student_input"]').val()) { + frappe.call({ + method: "lms.lms.doctype.lms_class.lms_class.add_student", + args: { + email: $('input[data-fieldname="student_input"]').val(), + class_name: $(".class-details").data("class"), + }, + callback: (data) => { + frappe.show_alert( + { + message: __("Student added successfully"), + indicator: "green", + }, + 3 + ); + window.location.reload(); + }, + }); + } }; const remove_student = (e) => { @@ -68,3 +87,269 @@ const update_course = (e) => { }, }); }; + +const create_live_class = (e) => { + let class_name = $(".class-details").data("class"); + frappe.call({ + method: "lms.lms.doctype.lms_class.lms_class.create_live_class", + args: { + class_name: class_name, + title: $("input[data-fieldname='meeting_title']").val(), + duration: $("input[data-fieldname='meeting_duration']").val(), + date: $("input[data-fieldname='meeting_date']").val(), + time: $("input[data-fieldname='meeting_time']").val(), + timezone: $('select[data-fieldname="meeting_timezone"]').val(), + auto_recording: $( + 'select[data-fieldname="meeting_recording"]' + ).val(), + description: $( + "textarea[data-fieldname='meeting_description']" + ).val(), + }, + callback: (data) => { + $("#live-class-modal").modal("hide"); + frappe.show_alert( + { + message: __("Live Class added successfully"), + indicator: "green", + }, + 3 + ); + setTimeout(function () { + window.location.reload(); + }, 1000); + }, + }); +}; + +const make_live_class_form = (e) => { + this.field_group = new frappe.ui.FieldGroup({ + fields: [ + { + fieldname: "meeting_title", + fieldtype: "Data", + options: "", + label: "Title", + reqd: 1, + }, + { + fieldname: "meeting_time", + fieldtype: "Time", + options: "", + label: "Time", + reqd: 1, + }, + { + fieldname: "meeting_timezone", + label: __("Time Zone"), + fieldtype: "Select", + options: get_timezones().join("\n"), + reqd: 1, + }, + { + fieldname: "meeting_col", + fieldtype: "Column Break", + options: "", + }, + { + fieldname: "meeting_date", + fieldtype: "Date", + options: "", + label: "Date", + reqd: 1, + }, + { + fieldname: "meeting_duration", + fieldtype: "Int", + options: "", + label: "Duration (in Minutes)", + reqd: 1, + }, + { + fieldname: "meeting_recording", + fieldtype: "Select", + options: "No Recording\nLocal\nCloud", + label: "Auto Recording", + default: "No Recording", + }, + { + fieldname: "meeting_sec", + fieldtype: "Section Break", + options: "", + }, + { + fieldname: "meeting_description", + fieldtype: "Small Text", + options: "", + max_height: 100, + min_lines: 5, + label: "Description", + }, + ], + body: $("#live-class-form").get(0), + }); + + this.field_group.make(); + $("#live-class-form .form-section:last").removeClass("empty-section"); + $("#live-class-form .frappe-control").removeClass("hide-control"); +}; + +const get_timezones = () => { + return [ + "Pacific/Midway", + "Pacific/Pago_Pago", + "Pacific/Honolulu", + "America/Anchorage", + "America/Vancouver", + "America/Los_Angeles", + "America/Tijuana", + "America/Edmonton", + "America/Denver", + "America/Phoenix", + "America/Mazatlan", + "America/Winnipeg", + "America/Regina", + "America/Chicago", + "America/Mexico_City", + "America/Guatemala", + "America/El_Salvador", + "America/Managua", + "America/Costa_Rica", + "America/Montreal", + "America/New_York", + "America/Indianapolis", + "America/Panama", + "America/Bogota", + "America/Lima", + "America/Halifax", + "America/Puerto_Rico", + "America/Caracas", + "America/Santiago", + "America/St_Johns", + "America/Montevideo", + "America/Araguaina", + "America/Argentina/Buenos_Aires", + "America/Godthab", + "America/Sao_Paulo", + "Atlantic/Azores", + "Canada/Atlantic", + "Atlantic/Cape_Verde", + "UTC", + "Etc/Greenwich", + "Europe/Belgrade", + "CET", + "Atlantic/Reykjavik", + "Europe/Dublin", + "Europe/London", + "Europe/Lisbon", + "Africa/Casablanca", + "Africa/Nouakchott", + "Europe/Oslo", + "Europe/Copenhagen", + "Europe/Brussels", + "Europe/Berlin", + "Europe/Helsinki", + "Europe/Amsterdam", + "Europe/Rome", + "Europe/Stockholm", + "Europe/Vienna", + "Europe/Luxembourg", + "Europe/Paris", + "Europe/Zurich", + "Europe/Madrid", + "Africa/Bangui", + "Africa/Algiers", + "Africa/Tunis", + "Africa/Harare", + "Africa/Nairobi", + "Europe/Warsaw", + "Europe/Prague", + "Europe/Budapest", + "Europe/Sofia", + "Europe/Istanbul", + "Europe/Athens", + "Europe/Bucharest", + "Asia/Nicosia", + "Asia/Beirut", + "Asia/Damascus", + "Asia/Jerusalem", + "Asia/Amman", + "Africa/Tripoli", + "Africa/Cairo", + "Africa/Johannesburg", + "Europe/Moscow", + "Asia/Baghdad", + "Asia/Kuwait", + "Asia/Riyadh", + "Asia/Bahrain", + "Asia/Qatar", + "Asia/Aden", + "Asia/Tehran", + "Africa/Khartoum", + "Africa/Djibouti", + "Africa/Mogadishu", + "Asia/Dubai", + "Asia/Muscat", + "Asia/Baku", + "Asia/Kabul", + "Asia/Yekaterinburg", + "Asia/Tashkent", + "Asia/Calcutta", + "Asia/Kathmandu", + "Asia/Novosibirsk", + "Asia/Almaty", + "Asia/Dacca", + "Asia/Krasnoyarsk", + "Asia/Dhaka", + "Asia/Bangkok", + "Asia/Saigon", + "Asia/Jakarta", + "Asia/Irkutsk", + "Asia/Shanghai", + "Asia/Hong_Kong", + "Asia/Taipei", + "Asia/Kuala_Lumpur", + "Asia/Singapore", + "Australia/Perth", + "Asia/Yakutsk", + "Asia/Seoul", + "Asia/Tokyo", + "Australia/Darwin", + "Australia/Adelaide", + "Asia/Vladivostok", + "Pacific/Port_Moresby", + "Australia/Brisbane", + "Australia/Sydney", + "Australia/Hobart", + "Asia/Magadan", + "SST", + "Pacific/Noumea", + "Asia/Kamchatka", + "Pacific/Fiji", + "Pacific/Auckland", + "Asia/Kolkata", + "Europe/Kiev", + "America/Tegucigalpa", + "Pacific/Apia", + ]; +}; + +const make_add_students_section = () => { + this.field_group = new frappe.ui.FieldGroup({ + fields: [ + { + fieldname: "student_input", + fieldtype: "Link", + options: "User", + label: "Add Student", + filters: { + ignore_user_type: 1, + }, + }, + ], + body: $(".add-students").get(0), + }); + this.field_group.make(); + $(".add-students .form-section:last").removeClass("empty-section"); + $(".add-students .frappe-control").removeClass("hide-control"); +}; diff --git a/lms/www/classes/class.py b/lms/www/classes/class.py index b872685c..09a98a9c 100644 --- a/lms/www/classes/class.py +++ b/lms/www/classes/class.py @@ -1,6 +1,7 @@ import frappe from lms.lms.utils import has_course_moderator_role from frappe import _ +from frappe.utils import getdate def get_context(context): @@ -40,3 +41,13 @@ def get_context(context): context.class_students = class_students context.is_moderator = has_course_moderator_role() + + students = [student.student for student in class_students] + context.is_student = frappe.session.user in students + + context.live_classes = frappe.get_all( + "LMS Live Class", + {"class_name": class_name, "date": [">=", getdate()]}, + ["title", "description", "time", "date", "start_url", "join_url", "owner"], + order_by="date", + ) diff --git a/lms/www/classes/index.html b/lms/www/classes/index.html index 22684ef4..ad18f817 100644 --- a/lms/www/classes/index.html +++ b/lms/www/classes/index.html @@ -4,10 +4,10 @@ {% endblock %} {% block page_content %} -
+
{% if has_course_moderator_role() %} - + {{ _("Create Class") }} {% endif %} @@ -29,36 +29,40 @@ {% macro ClassCards(classes) %} -
+
{% for class in classes %} {% set course_count = frappe.db.count("Class Course", {"parent": class.name}) %} {% set student_count = frappe.db.count("Class Student", {"parent": class.name}) %} -
-
+
+ +
+ {{ class.title }} +
+ +
{% if course_count %} - + {{ course_count }} {{ _("Courses") }} {% endif %} {% if student_count %} - + {{ student_count }} {{ _("Students") }} {% endif %}
-
- {{ class.title }} -
-
+ + + - {{ frappe.utils.format_date(class.start_date, "medium") }} - + {{ frappe.utils.format_date(class.start_date, "long") }} - - {{ frappe.utils.format_date(class.end_date, "medium") }} + {{ frappe.utils.format_date(class.end_date, "long") }}
diff --git a/lms/www/classes/index.py b/lms/www/classes/index.py index 483b54e9..cb28b46d 100644 --- a/lms/www/classes/index.py +++ b/lms/www/classes/index.py @@ -1,10 +1,12 @@ import frappe -from frappe import _ +from frappe.utils import getdate def get_context(context): context.no_cache = 1 context.classes = frappe.get_all( - "LMS Class", fields=["name", "title", "start_date", "end_date"] + "LMS Class", + {"end_date": [">=", getdate()]}, + ["name", "title", "start_date", "end_date"], )