diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..e82154a1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "frappe-ui"] + path = frappe-ui + url = https://github.com/pateljannat/frappe-ui diff --git a/frontend/src/components/Controls/Autocomplete.vue b/frontend/src/components/Controls/Autocomplete.vue index eb9a5b08..81f5358b 100644 --- a/frontend/src/components/Controls/Autocomplete.vue +++ b/frontend/src/components/Controls/Autocomplete.vue @@ -75,7 +75,7 @@ >
  • @@ -87,7 +87,16 @@ name="item-label" v-bind="{ active, selected, option }" > - {{ option.label }} +
    +
    + {{ option.label }} +
    +
    +
  • diff --git a/frontend/src/components/Controls/Link.vue b/frontend/src/components/Controls/Link.vue index 8ce30846..f7730380 100644 --- a/frontend/src/components/Controls/Link.vue +++ b/frontend/src/components/Controls/Link.vue @@ -77,6 +77,7 @@ const valuePropPassed = computed(() => 'value' in attrs) const value = computed({ get: () => (valuePropPassed.value ? attrs.value : props.modelValue), set: (val) => { + console.log(valuePropPassed.value) return ( val?.value && emit(valuePropPassed.value ? 'change' : 'update:modelValue', val?.value) @@ -118,6 +119,7 @@ const options = createResource({ return { label: option.value, value: option.value, + description: option.description, } }) }, diff --git a/frontend/src/components/LessonPlugins.vue b/frontend/src/components/LessonPlugins.vue index ccaa17cf..89af799d 100644 --- a/frontend/src/components/LessonPlugins.vue +++ b/frontend/src/components/LessonPlugins.vue @@ -2,7 +2,7 @@
    {{ __('Components') }}
    -
    +
    -
    +
    - + + +
    -
    +
    {{ __('Add an image, video, pdf or audio.') }}
    @@ -68,7 +79,7 @@
    -
    +
    {{ __( @@ -112,11 +123,13 @@ const props = defineProps({ }, }) -const addQuiz = () => { +const addQuiz = (value) => { + console.log('here') + console.log(value) getCurrentEditor().caret.setToLastBlock('end', 0) - if (quiz.value) { + if (value) { getCurrentEditor().blocks.insert('quiz', { - quiz: quiz.value, + quiz: value, }) quiz.value = null } diff --git a/frontend/src/components/Modals/ChapterModal.vue b/frontend/src/components/Modals/ChapterModal.vue index 0ec46085..59794507 100644 --- a/frontend/src/components/Modals/ChapterModal.vue +++ b/frontend/src/components/Modals/ChapterModal.vue @@ -21,8 +21,8 @@ + diff --git a/frontend/src/pages/LessonForm.vue b/frontend/src/pages/LessonForm.vue index b92ea7d9..f112d2e5 100644 --- a/frontend/src/pages/LessonForm.vue +++ b/frontend/src/pages/LessonForm.vue @@ -70,7 +70,14 @@ diff --git a/frontend/src/pages/Quizzes.vue b/frontend/src/pages/Quizzes.vue new file mode 100644 index 00000000..3a678e91 --- /dev/null +++ b/frontend/src/pages/Quizzes.vue @@ -0,0 +1,126 @@ + + diff --git a/frontend/src/router.js b/frontend/src/router.js index e8e96180..46b830b4 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -141,6 +141,17 @@ const routes = [ component: () => import('@/pages/Badge.vue'), props: true, }, + { + path: '/quizzes', + name: 'Quizzes', + component: () => import('@/pages/Quizzes.vue'), + }, + { + path: '/quizzes/:quizID', + name: 'QuizCreation', + component: () => import('@/pages/QuizCreation.vue'), + props: true, + }, ] let router = createRouter({ diff --git a/frontend/src/translation.js b/frontend/src/translation.js index 0f9911dc..f6b9050b 100644 --- a/frontend/src/translation.js +++ b/frontend/src/translation.js @@ -2,6 +2,7 @@ import { createResource } from 'frappe-ui' export default function translationPlugin(app) { app.config.globalProperties.__ = translate + window.__ = translate if (!window.translatedMessages) fetchTranslations() } diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js index 05372373..e732e09f 100644 --- a/frontend/src/utils/index.js +++ b/frontend/src/utils/index.js @@ -236,7 +236,7 @@ export function getEditorTools() { regex: /https:\/\/docs\.google\.com\/presentation\/d\/e\/([A-Za-z0-9_-]+)\/pub/, embedUrl: 'https://docs.google.com/presentation/d/e/<%= remote_id %>/embed', - html: "", + html: "", }, drive: { regex: /https:\/\/drive\.google\.com\/file\/d\/([A-Za-z0-9_-]+)\/view(\?.+)?/, @@ -260,7 +260,7 @@ export function getEditorTools() { regex: /https:\/\/docs\.google\.com\/presentation\/d\/([A-Za-z0-9_-]+)\/edit(\?.+)?/, embedUrl: 'https://docs.google.com/presentation/d/<%= remote_id %>/embed', - html: "", + html: "", }, codesandbox: { regex: /^https:\/\/codesandbox\.io\/(?:embed\/)?([A-Za-z0-9_-]+)(?:\?[^\/]*)?$/, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 30adce7c..7833087e 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -68,13 +68,6 @@ dependencies: "@codexteam/icons" "^0.0.5" -"@editorjs/image@^2.9.2": - version "2.9.2" - resolved "https://registry.yarnpkg.com/@editorjs/image/-/image-2.9.2.tgz#c8bea65a578fab65a1a75df1223b4fd8f06b57d5" - integrity sha512-n09sMieGW8cksoeflpplzvbmFH2bdVzVTWbnidPWAHaeU467HRteoXU9yfGBB7+eeHZLnmCulQ2dr6ae+G2niw== - dependencies: - "@codexteam/icons" "^0.3.0" - "@editorjs/inline-code@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@editorjs/inline-code/-/inline-code-1.5.0.tgz#ad5849bac3396b9dad22dceeda76198dd991e426" @@ -1882,8 +1875,16 @@ source-map-js@^1.2.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: - name string-width-cjs +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -1901,8 +1902,14 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: - name strip-ansi-cjs +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== diff --git a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py index 22e01439..4a39f39f 100644 --- a/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py +++ b/lms/lms/doctype/lms_certificate_request/lms_certificate_request.py @@ -26,6 +26,9 @@ class LMSCertificateRequest(Document): self.validate_if_existing_requests() self.validate_evaluation_end_date() + def after_insert(self): + self.send_notification() + def set_evaluator(self): if not self.evaluator: self.evaluator = get_evaluator(self.course, self.batch_name) @@ -108,6 +111,35 @@ class LMSCertificateRequest(Document): ) ) + def send_notification(self): + outgoing_email_account = frappe.get_cached_value( + "Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name" + ) + if outgoing_email_account or frappe.conf.get("mail_login"): + subject = _("Your evaluation slot has been booked") + template = "certificate_request_notification" + + args = { + "course": frappe.db.get_value("LMS Course", self.course, "title"), + "timezone": frappe.db.get_value("LMS Batch", self.batch_name, "timezone") + if self.batch_name + else "", + "date": format_date(self.date, "medium"), + "member_name": self.member_name, + "start_time": format_time(self.start_time, "short"), + "evaluator": frappe.db.get_value("User", self.evaluator, "full_name"), + } + + frappe.sendmail( + recipients=[self.member], + cc=[self.evaluator], + subject=subject, + template=template, + args=args, + header=[subject, "green"], + retry=3, + ) + def schedule_evals(): if frappe.db.get_single_value("LMS Settings", "send_calendar_invite_for_evaluations"): diff --git a/lms/lms/doctype/lms_question/lms_question.json b/lms/lms/doctype/lms_question/lms_question.json index 6118ff6d..2a8b45f6 100644 --- a/lms/lms/doctype/lms_question/lms_question.json +++ b/lms/lms/doctype/lms_question/lms_question.json @@ -196,7 +196,7 @@ "index_web_pages_for_search": 1, "links": [], "make_attachments_public": 1, - "modified": "2024-08-01 13:01:55.000072", + "modified": "2024-08-01 12:53:22.540990", "modified_by": "Administrator", "module": "LMS", "name": "LMS Question", diff --git a/lms/lms/doctype/lms_question/lms_question.py b/lms/lms/doctype/lms_question/lms_question.py index 0e6f87bd..5f18e0dd 100644 --- a/lms/lms/doctype/lms_question/lms_question.py +++ b/lms/lms/doctype/lms_question/lms_question.py @@ -10,6 +10,7 @@ from lms.lms.utils import has_course_instructor_role, has_course_moderator_role class LMSQuestion(Document): def validate(self): validate_correct_answers(self) + update_question_title(self) def validate_correct_answers(question): @@ -62,6 +63,16 @@ def validate_possible_answer(question): ) +def update_question_title(question): + if not question.is_new(): + question_rows = frappe.get_all( + "LMS Quiz Question", {"question": question.name}, pluck="name" + ) + + for row in question_rows: + frappe.db.set_value("LMS Quiz Question", row, "question_detail", question.question) + + def get_correct_options(question): correct_options = [] correct_option_fields = [ diff --git a/lms/lms/doctype/lms_quiz/lms_quiz.json b/lms/lms/doctype/lms_quiz/lms_quiz.json index 9c1b8e95..dd5de78d 100644 --- a/lms/lms/doctype/lms_quiz/lms_quiz.json +++ b/lms/lms/doctype/lms_quiz/lms_quiz.json @@ -9,16 +9,15 @@ "field_order": [ "title", "max_attempts", - "limit_questions_to", + "show_answers", "column_break_gaac", "total_marks", "passing_percentage", - "section_break_hsiv", - "show_answers", - "column_break_rocd", "show_submission_history", - "column_break_dsup", + "section_break_tzbu", "shuffle_questions", + "column_break_clsh", + "limit_questions_to", "section_break_sbjx", "questions", "section_break_3", @@ -74,6 +73,7 @@ "default": "1", "fieldname": "show_answers", "fieldtype": "Check", + "in_standard_filter": 1, "label": "Show Answers" }, { @@ -90,35 +90,25 @@ "fieldtype": "Check", "label": "Show Submission History" }, - { - "fieldname": "section_break_hsiv", - "fieldtype": "Section Break", - "label": "Settings" - }, { "fieldname": "passing_percentage", "fieldtype": "Int", + "in_list_view": 1, + "in_standard_filter": 1, "label": "Passing Percentage", "non_negative": 1, "reqd": 1 }, - { - "fieldname": "column_break_rocd", - "fieldtype": "Column Break" - }, { "default": "0", "fieldname": "total_marks", "fieldtype": "Int", + "in_list_view": 1, "label": "Total Marks", "non_negative": 1, "read_only": 1, "reqd": 1 }, - { - "fieldname": "column_break_dsup", - "fieldtype": "Column Break" - }, { "default": "0", "fieldname": "shuffle_questions", @@ -126,14 +116,23 @@ "label": "Shuffle Questions" }, { + "depends_on": "shuffle_questions", "fieldname": "limit_questions_to", "fieldtype": "Int", "label": "Limit Questions To" + }, + { + "fieldname": "section_break_tzbu", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_clsh", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-06-27 22:03:48.576489", + "modified": "2024-08-09 12:21:36.256522", "modified_by": "Administrator", "module": "LMS", "name": "LMS Quiz", diff --git a/lms/lms/doctype/lms_quiz/lms_quiz.py b/lms/lms/doctype/lms_quiz/lms_quiz.py index ef7f6243..33abac89 100644 --- a/lms/lms/doctype/lms_quiz/lms_quiz.py +++ b/lms/lms/doctype/lms_quiz/lms_quiz.py @@ -5,7 +5,7 @@ import json import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cstr, comma_and +from frappe.utils import cstr, comma_and, cint from fuzzywuzzy import fuzz from lms.lms.doctype.course_lesson.course_lesson import save_progress from lms.lms.utils import ( @@ -30,12 +30,12 @@ class LMSQuiz(Document): ) def validate_limit(self): - if self.limit_questions_to and self.limit_questions_to >= len(self.questions): + if self.limit_questions_to and cint(self.limit_questions_to) >= len(self.questions): frappe.throw( _("Limit cannot be greater than or equal to the number of questions in the quiz.") ) - if self.limit_questions_to and self.limit_questions_to < len(self.questions): + if self.limit_questions_to and cint(self.limit_questions_to) < len(self.questions): marks = [question.marks for question in self.questions] if len(set(marks)) > 1: frappe.throw(_("All questions should have the same marks if the limit is set.")) @@ -43,10 +43,10 @@ class LMSQuiz(Document): def calculate_total_marks(self): if self.limit_questions_to: self.total_marks = sum( - question.marks for question in self.questions[: self.limit_questions_to] + question.marks for question in self.questions[: cint(self.limit_questions_to)] ) else: - self.total_marks = sum(question.marks for question in self.questions) + self.total_marks = sum(cint(question.marks) for question in self.questions) def autoname(self): if not self.name: diff --git a/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json b/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json index e14a8d48..5570559f 100644 --- a/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json +++ b/lms/lms/doctype/lms_quiz_question/lms_quiz_question.json @@ -6,7 +6,10 @@ "engine": "InnoDB", "field_order": [ "question", - "marks" + "column_break_qcpo", + "marks", + "section_break_huup", + "question_detail" ], "fields": [ { @@ -25,12 +28,28 @@ "label": "Marks", "non_negative": 1, "reqd": 1 + }, + { + "fetch_from": "question.question", + "fieldname": "question_detail", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Question Detail", + "read_only": 1 + }, + { + "fieldname": "column_break_qcpo", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_huup", + "fieldtype": "Section Break" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2023-10-16 19:51:03.893144", + "modified": "2024-07-29 15:10:09.662715", "modified_by": "Administrator", "module": "LMS", "name": "LMS Quiz Question", diff --git a/lms/lms/notification/certificate_request_creation/certificate_request_creation.json b/lms/lms/notification/certificate_request_creation/certificate_request_creation.json index 15c58963..ca9c3826 100644 --- a/lms/lms/notification/certificate_request_creation/certificate_request_creation.json +++ b/lms/lms/notification/certificate_request_creation/certificate_request_creation.json @@ -6,14 +6,14 @@ "docstatus": 0, "doctype": "Notification", "document_type": "LMS Certificate Request", - "enabled": 1, + "enabled": 0, "event": "New", "idx": 0, "is_standard": 1, - "message": "{% set title = frappe.db.get_value(\"LMS Course\", doc.course, \"title\") %}\n{% set timezone = frappe.db.get_value(\"LMS Batch\", doc.batch, \"timezone\") %}\n{% set timezone = timezone if timezone else '' %}\n{% set evaluator_name = frappe.db.get_value(\"User\", doc.evaluator, \"full_name\") %}\n\n

    {{ _(\"Hey {0}\").format(doc.member_name) }}

    \n

    {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(title, frappe.utils.format_date(doc.date, \"medium\"), frappe.utils.format_time(doc.start_time, \"short\"), timezone) }}

    \n

    {{ _(\"Your evaluator is {0}\").format(evaluator_name) }}\n

    {{ _(\"Please prepare well and be on time for the evaluations.\") }}

    \n", + "message": "{% set title = frappe.db.get_value(\"LMS Course\", doc.course, \"title\") %}\n{% set timezone = frappe.db.get_value(\"LMS Batch\", doc.batch, \"timezone\") %}\n{% set timezone = timezone if timezone else '' %}\n{% set evaluator_name = frappe.db.get_value(\"User\", doc.evaluator, \"full_name\") %}\n\n

    {{ _(\"Hey {0}\").format(doc.member_name) }}

    \n

    {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(title, frappe.utils.format_date(doc.date, \"medium\"), frappe.utils.format_time(doc.start_time, \"short\"), timezone) }}

    \n

    {{ _(\"Your evaluator is {0}\").format(evaluator_name) }}

    \n

    {{ _(\"Please prepare well and be on time for the evaluations.\") }}

    \n", "message_type": "HTML", - "modified": "2024-07-10 15:51:03.429317", - "modified_by": "sayali@erpnext.com", + "modified": "2024-08-01 12:17:40.647724", + "modified_by": "jannat@frappe.io", "module": "LMS", "name": "Certificate Request Creation", "owner": "Administrator", diff --git a/lms/templates/emails/certificate_request_notification.html b/lms/templates/emails/certificate_request_notification.html new file mode 100644 index 00000000..6e4309bc --- /dev/null +++ b/lms/templates/emails/certificate_request_notification.html @@ -0,0 +1,4 @@ +

    {{ _("Hey {0}").format(member_name) }}

    +

    {{ _('Your evaluation for the course {0} has been scheduled on {1} at {2} {3}.').format(title, date, start_time, timezone) }}

    +

    {{ _("Your evaluator is {0}").format(evaluator) }}

    +

    {{ _("Please prepare well and be on time for the evaluations.") }}