From e1a78382c3ea055a480924c81db5635f18b08f07 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Mon, 18 Nov 2024 16:15:27 +0530 Subject: [PATCH] feat: learning paths --- frontend/src/components/AppSidebar.vue | 12 ++ frontend/src/components/Modals/Settings.vue | 37 +++- frontend/src/components/SettingDetails.vue | 10 +- frontend/src/components/SettingFields.vue | 3 +- frontend/src/pages/ProgramForm.vue | 88 ++++++++++ frontend/src/pages/Programs.vue | 164 ++++++++++++++++++ frontend/src/router.js | 11 ++ lms/lms/doctype/lms_program/__init__.py | 0 lms/lms/doctype/lms_program/lms_program.js | 8 + lms/lms/doctype/lms_program/lms_program.json | 60 +++++++ lms/lms/doctype/lms_program/lms_program.py | 9 + .../doctype/lms_program/test_lms_program.py | 21 +++ .../doctype/lms_program_course/__init__.py | 0 .../lms_program_course.json | 42 +++++ .../lms_program_course/lms_program_course.py | 9 + .../doctype/lms_program_member/__init__.py | 0 .../lms_program_member.json | 42 +++++ .../lms_program_member/lms_program_member.py | 9 + .../doctype/lms_settings/lms_settings.json | 19 +- 19 files changed, 535 insertions(+), 9 deletions(-) create mode 100644 frontend/src/pages/ProgramForm.vue create mode 100644 frontend/src/pages/Programs.vue create mode 100644 lms/lms/doctype/lms_program/__init__.py create mode 100644 lms/lms/doctype/lms_program/lms_program.js create mode 100644 lms/lms/doctype/lms_program/lms_program.json create mode 100644 lms/lms/doctype/lms_program/lms_program.py create mode 100644 lms/lms/doctype/lms_program/test_lms_program.py create mode 100644 lms/lms/doctype/lms_program_course/__init__.py create mode 100644 lms/lms/doctype/lms_program_course/lms_program_course.json create mode 100644 lms/lms/doctype/lms_program_course/lms_program_course.py create mode 100644 lms/lms/doctype/lms_program_member/__init__.py create mode 100644 lms/lms/doctype/lms_program_member/lms_program_member.json create mode 100644 lms/lms/doctype/lms_program_member/lms_program_member.py diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue index a0b94a83..2c0afea4 100644 --- a/frontend/src/components/AppSidebar.vue +++ b/frontend/src/components/AppSidebar.vue @@ -183,6 +183,17 @@ const addQuizzes = () => { } } +const addPrograms = () => { + if (isInstructor.value || isModerator.value) { + sidebarLinks.value.push({ + label: 'Programs', + icon: 'Route', + to: 'Programs', + activeFor: ['Programs', 'ProgramForm'], + }) + } +} + const openPageModal = (link) => { showPageModal.value = true pageToEdit.value = link @@ -215,6 +226,7 @@ watch(userResource, () => { isModerator.value = userResource.data.is_moderator isInstructor.value = userResource.data.is_instructor addQuizzes() + addPrograms() } }) diff --git a/frontend/src/components/Modals/Settings.vue b/frontend/src/components/Modals/Settings.vue index f8854df2..bf6801d4 100644 --- a/frontend/src/components/Modals/Settings.vue +++ b/frontend/src/components/Modals/Settings.vue @@ -108,9 +108,31 @@ const tabsStructure = computed(() => { hideLabel: true, items: [ { - label: 'Members', - description: 'Manage the members of your learning system', - icon: 'UserRoundPlus', + label: 'General', + icon: 'Wrench', + fields: [ + { + label: 'Enable Learning Paths', + name: 'enable_learning_paths', + description: + 'This will change the default flow of the system and enforce students to go through programs assigned to them in the correct order.', + type: 'checkbox', + }, + { + label: 'Send calendar invite for evaluations', + name: 'send_calendar_invite_for_evaluations', + description: + 'If enabled and Google Calendar of the evaluator is set in the system, students will receive calendar invites to remind them of their evaluations.', + type: 'checkbox', + }, + { + label: 'Unsplash Access Key', + name: 'unsplash_access_key', + description: + 'Optional. If this is set, students can pick a cover image from the unsplash library for their profile page. Refer the docs to know more https://unsplash.com/documentation#getting-started.', + type: 'text', + }, + ], }, ], }, @@ -156,9 +178,14 @@ const tabsStructure = computed(() => { ], }, { - label: 'Settings', - hideLabel: true, + label: 'Lists', + hideLabel: false, items: [ + { + label: 'Members', + description: 'Manage the members of your learning system', + icon: 'UserRoundPlus', + }, { label: 'Categories', description: 'Manage the members of your learning system', diff --git a/frontend/src/components/SettingDetails.vue b/frontend/src/components/SettingDetails.vue index 87a80f26..d90b1c4c 100644 --- a/frontend/src/components/SettingDetails.vue +++ b/frontend/src/components/SettingDetails.vue @@ -29,6 +29,7 @@ diff --git a/frontend/src/components/SettingFields.vue b/frontend/src/components/SettingFields.vue index 2cee55e9..cc74b3f7 100644 --- a/frontend/src/components/SettingFields.vue +++ b/frontend/src/components/SettingFields.vue @@ -90,6 +90,7 @@ :type="field.type" :rows="field.rows" :options="field.options" + :description="field.description" /> @@ -100,7 +101,7 @@ import { FormControl, FileUploader, Button, Switch } from 'frappe-ui' import { computed } from 'vue' import { getFileSize, validateFile } from '@/utils' -import { X, FileText } from 'lucide-vue-next' +import { X } from 'lucide-vue-next' import Link from '@/components/Controls/Link.vue' import CodeEditor from '@/components/Controls/CodeEditor.vue' diff --git a/frontend/src/pages/ProgramForm.vue b/frontend/src/pages/ProgramForm.vue new file mode 100644 index 00000000..e40a674b --- /dev/null +++ b/frontend/src/pages/ProgramForm.vue @@ -0,0 +1,88 @@ + + diff --git a/frontend/src/pages/Programs.vue b/frontend/src/pages/Programs.vue new file mode 100644 index 00000000..6ac89745 --- /dev/null +++ b/frontend/src/pages/Programs.vue @@ -0,0 +1,164 @@ + + diff --git a/frontend/src/router.js b/frontend/src/router.js index cff7e5f6..d69a1e46 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -182,6 +182,17 @@ const routes = [ component: () => import('@/pages/QuizSubmission.vue'), props: true, }, + { + path: '/programs', + name: 'Programs', + component: () => import('@/pages/Programs.vue'), + }, + { + path: '/programs/:programName', + name: 'ProgramForm', + component: () => import('@/pages/ProgramForm.vue'), + props: true, + }, ] let router = createRouter({ diff --git a/lms/lms/doctype/lms_program/__init__.py b/lms/lms/doctype/lms_program/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_program/lms_program.js b/lms/lms/doctype/lms_program/lms_program.js new file mode 100644 index 00000000..55710444 --- /dev/null +++ b/lms/lms/doctype/lms_program/lms_program.js @@ -0,0 +1,8 @@ +// Copyright (c) 2024, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("LMS Program", { +// refresh(frm) { + +// }, +// }); diff --git a/lms/lms/doctype/lms_program/lms_program.json b/lms/lms/doctype/lms_program/lms_program.json new file mode 100644 index 00000000..f2806e72 --- /dev/null +++ b/lms/lms/doctype/lms_program/lms_program.json @@ -0,0 +1,60 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:title", + "creation": "2024-11-18 12:27:13.283169", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "title", + "program_courses", + "program_members" + ], + "fields": [ + { + "fieldname": "program_courses", + "fieldtype": "Table", + "label": "Program Courses", + "options": "LMS Program Course" + }, + { + "fieldname": "program_members", + "fieldtype": "Table", + "label": "Program Members", + "options": "LMS Program Member" + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1, + "unique": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-11-18 14:08:26.958831", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Program", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/lms/lms/doctype/lms_program/lms_program.py b/lms/lms/doctype/lms_program/lms_program.py new file mode 100644 index 00000000..c07e3df5 --- /dev/null +++ b/lms/lms/doctype/lms_program/lms_program.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LMSProgram(Document): + pass diff --git a/lms/lms/doctype/lms_program/test_lms_program.py b/lms/lms/doctype/lms_program/test_lms_program.py new file mode 100644 index 00000000..e1599d98 --- /dev/null +++ b/lms/lms/doctype/lms_program/test_lms_program.py @@ -0,0 +1,21 @@ +# Copyright (c) 2024, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests import IntegrationTestCase, UnitTestCase + + +# On IntegrationTestCase, the doctype test records and all +# link-field test record depdendencies are recursively loaded +# Use these module variables to add/remove to/from that list +EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] +IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] + + +class TestLMSProgram(UnitTestCase): + """ + Unit tests for LMSProgram. + Use this class for testing individual functions and methods. + """ + + pass diff --git a/lms/lms/doctype/lms_program_course/__init__.py b/lms/lms/doctype/lms_program_course/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_program_course/lms_program_course.json b/lms/lms/doctype/lms_program_course/lms_program_course.json new file mode 100644 index 00000000..a7edc7ed --- /dev/null +++ b/lms/lms/doctype/lms_program_course/lms_program_course.json @@ -0,0 +1,42 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-11-18 12:27:37.030302", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "course", + "course_title" + ], + "fields": [ + { + "fieldname": "course", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Course", + "options": "LMS Course", + "reqd": 1 + }, + { + "fetch_from": "course.title", + "fieldname": "course_title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Course Title", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-11-18 12:43:46.800199", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Program Course", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/lms/lms/doctype/lms_program_course/lms_program_course.py b/lms/lms/doctype/lms_program_course/lms_program_course.py new file mode 100644 index 00000000..0d126937 --- /dev/null +++ b/lms/lms/doctype/lms_program_course/lms_program_course.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LMSProgramCourse(Document): + pass diff --git a/lms/lms/doctype/lms_program_member/__init__.py b/lms/lms/doctype/lms_program_member/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lms/lms/doctype/lms_program_member/lms_program_member.json b/lms/lms/doctype/lms_program_member/lms_program_member.json new file mode 100644 index 00000000..d8553936 --- /dev/null +++ b/lms/lms/doctype/lms_program_member/lms_program_member.json @@ -0,0 +1,42 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-11-18 12:29:13.615014", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "member", + "full_name" + ], + "fields": [ + { + "fieldname": "member", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Member", + "options": "User", + "reqd": 1 + }, + { + "fetch_from": "member.full_name", + "fieldname": "full_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Full Name", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-11-18 12:44:02.648786", + "modified_by": "Administrator", + "module": "LMS", + "name": "LMS Program Member", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/lms/lms/doctype/lms_program_member/lms_program_member.py b/lms/lms/doctype/lms_program_member/lms_program_member.py new file mode 100644 index 00000000..473cd1f9 --- /dev/null +++ b/lms/lms/doctype/lms_program_member/lms_program_member.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class LMSProgramMember(Document): + pass diff --git a/lms/lms/doctype/lms_settings/lms_settings.json b/lms/lms/doctype/lms_settings/lms_settings.json index d8bae686..87a595ed 100644 --- a/lms/lms/doctype/lms_settings/lms_settings.json +++ b/lms/lms/doctype/lms_settings/lms_settings.json @@ -5,13 +5,15 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ + "general_tab", "default_home", + "send_calendar_invite_for_evaluations", "is_onboarding_complete", "column_break_zdel", + "enable_learning_paths", "unsplash_access_key", "livecode_url", "section_break_szgq", - "send_calendar_invite_for_evaluations", "show_day_view", "column_break_2", "show_dashboard", @@ -80,6 +82,7 @@ { "fieldname": "mentor_request_section", "fieldtype": "Section Break", + "hidden": 1, "label": "Mentor Request" }, { @@ -127,6 +130,7 @@ { "fieldname": "section_break_szgq", "fieldtype": "Section Break", + "hidden": 1, "label": "Batch Settings" }, { @@ -336,12 +340,23 @@ "fieldname": "payments_app_is_not_installed", "fieldtype": "HTML", "label": "Payments app is not installed" + }, + { + "default": "0", + "fieldname": "enable_learning_paths", + "fieldtype": "Check", + "label": "Enable Learning Paths" + }, + { + "fieldname": "general_tab", + "fieldtype": "Tab Break", + "label": "General" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-10-01 12:15:49.800242", + "modified": "2024-11-18 12:52:41.236252", "modified_by": "Administrator", "module": "LMS", "name": "LMS Settings",