feat: Timer in LMS Quiz (#24246)

* feat: new fields in quiz doctypes

* feat: timer in lms quiz

* fix: variable initialisation

* fix: context, exception fix

* fix:sider

* fix:sider

* fix: indentation

* fix: timer

* fix: sider

* fix: return value and format

* fix: show time taken only after all attempts are over

* fix: sider

Co-authored-by: pateljannat <jannatpatel@MacBook-Air.local>
Co-authored-by: Marica <maricadsouza221197@gmail.com>
diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py
index f7aa6e9..2b3acf1 100644
--- a/erpnext/education/doctype/course_enrollment/course_enrollment.py
+++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py
@@ -41,7 +41,7 @@
 			frappe.throw(_("Student is already enrolled via Course Enrollment {0}").format(
 				get_link_to_form("Course Enrollment", enrollment)), title=_('Duplicate Entry'))
 
-	def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status):
+	def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status, time_taken):
 		result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
 		result_data = []
 		for key in answers:
@@ -66,7 +66,8 @@
 			"activity_date": frappe.utils.datetime.datetime.now(),
 			"result": result_data,
 			"score": score,
-			"status": status
+			"status": status,
+			"time_taken": time_taken
 			}).insert(ignore_permissions = True)
 
 	def add_activity(self, content_type, content):
diff --git a/erpnext/education/doctype/quiz/quiz.json b/erpnext/education/doctype/quiz/quiz.json
index 569c281..16d7d7e 100644
--- a/erpnext/education/doctype/quiz/quiz.json
+++ b/erpnext/education/doctype/quiz/quiz.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "field:title",
@@ -12,7 +13,10 @@
   "quiz_configuration_section",
   "passing_score",
   "max_attempts",
-  "grading_basis"
+  "grading_basis",
+  "column_break_7",
+  "is_time_bound",
+  "duration"
  ],
  "fields": [
   {
@@ -58,9 +62,26 @@
    "fieldtype": "Select",
    "label": "Grading Basis",
    "options": "Latest Highest Score\nLatest Attempt"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_time_bound",
+   "fieldtype": "Check",
+   "label": "Is Time-Bound"
+  },
+  {
+   "depends_on": "is_time_bound",
+   "fieldname": "duration",
+   "fieldtype": "Duration",
+   "label": "Duration"
+  },
+  {
+   "fieldname": "column_break_7",
+   "fieldtype": "Column Break"
   }
  ],
- "modified": "2019-06-12 12:23:57.020508",
+ "links": [],
+ "modified": "2020-12-24 15:41:35.043262",
  "modified_by": "Administrator",
  "module": "Education",
  "name": "Quiz",
diff --git a/erpnext/education/doctype/quiz_activity/quiz_activity.json b/erpnext/education/doctype/quiz_activity/quiz_activity.json
index e78db42..742c887 100644
--- a/erpnext/education/doctype/quiz_activity/quiz_activity.json
+++ b/erpnext/education/doctype/quiz_activity/quiz_activity.json
@@ -1,490 +1,163 @@
 {
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
+ "actions": [],
  "autoname": "format:EDU-QA-{YYYY}-{#####}",
  "beta": 1,
  "creation": "2018-10-15 15:48:40.482821",
- "custom": 0,
- "docstatus": 0,
  "doctype": "DocType",
- "document_type": "",
  "editable_grid": 1,
  "engine": "InnoDB",
+ "field_order": [
+  "enrollment",
+  "student",
+  "column_break_3",
+  "course",
+  "section_break_5",
+  "quiz",
+  "column_break_7",
+  "status",
+  "section_break_9",
+  "result",
+  "section_break_11",
+  "activity_date",
+  "score",
+  "column_break_14",
+  "time_taken"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "enrollment",
    "fieldtype": "Link",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Enrollment",
-   "length": 0,
-   "no_copy": 0,
    "options": "Course Enrollment",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 1,
-   "translatable": 0,
-   "unique": 0
+   "set_only_once": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fetch_from": "enrollment.student",
    "fieldname": "student",
    "fieldtype": "Link",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
    "label": "Student",
-   "length": 0,
-   "no_copy": 0,
    "options": "Student",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 1,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "read_only": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "column_break_3",
-   "fieldtype": "Column Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Column Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fetch_from": "enrollment.course",
    "fieldname": "course",
    "fieldtype": "Link",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Course",
-   "length": 0,
-   "no_copy": 0,
    "options": "Course",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
    "read_only": 1,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 1,
-   "translatable": 0,
-   "unique": 0
+   "set_only_once": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "section_break_5",
-   "fieldtype": "Section Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Section Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "quiz",
    "fieldtype": "Link",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
    "label": "Quiz",
-   "length": 0,
-   "no_copy": 0,
    "options": "Quiz",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 1,
-   "translatable": 0,
-   "unique": 0
+   "set_only_once": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "column_break_7",
-   "fieldtype": "Column Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Column Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "status",
    "fieldtype": "Select",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Status",
-   "length": 0,
-   "no_copy": 0,
    "options": "\nPass\nFail",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 1,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "read_only": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "section_break_9",
-   "fieldtype": "Section Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Section Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "result",
    "fieldtype": "Table",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Result",
-   "length": 0,
-   "no_copy": 0,
    "options": "Quiz Result",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 1,
-   "translatable": 0,
-   "unique": 0
+   "set_only_once": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "activity_date",
    "fieldtype": "Data",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Activity Date",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 1,
-   "translatable": 0,
-   "unique": 0
+   "set_only_once": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "score",
    "fieldtype": "Data",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
    "label": "Score",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 1,
-   "translatable": 0,
-   "unique": 0
+   "set_only_once": 1
+  },
+  {
+   "fieldname": "time_taken",
+   "fieldtype": "Duration",
+   "label": "Time Taken",
+   "set_only_once": 1
+  },
+  {
+   "fieldname": "section_break_11",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_14",
+   "fieldtype": "Column Break"
   }
  ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-11-25 19:05:52.434437",
+ "links": [],
+ "modified": "2020-12-24 15:41:20.085380",
  "modified_by": "Administrator",
  "module": "Education",
  "name": "Quiz Activity",
- "name_case": "",
  "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "Academics User",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   },
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "LMS User",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   },
   {
-   "amend": 0,
-   "cancel": 0,
-   "create": 0,
-   "delete": 0,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "Instructor",
-   "set_user_permissions": 0,
-   "share": 1,
-   "submit": 0,
-   "write": 0
+   "share": 1
   }
  ],
  "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
  "sort_field": "modified",
  "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py
index 81626f1..2dc0f63 100644
--- a/erpnext/education/doctype/student/student.py
+++ b/erpnext/education/doctype/student/student.py
@@ -114,7 +114,7 @@
 					status = check_content_completion(content.name, content.doctype, course_enrollment_name)
 					progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status})
 				elif content.doctype == 'Quiz':
-					status, score, result = check_quiz_completion(content, course_enrollment_name)
+					status, score, result, time_taken = check_quiz_completion(content, course_enrollment_name)
 					progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result})
 		return progress
 
diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py
index cffc396..8f51fef 100644
--- a/erpnext/education/utils.py
+++ b/erpnext/education/utils.py
@@ -194,7 +194,7 @@
 		return enrollment.add_activity(content_type, content)
 
 @frappe.whitelist()
-def evaluate_quiz(quiz_response, quiz_name, course, program):
+def evaluate_quiz(quiz_response, quiz_name, course, program, time_taken):
 	import json
 
 	student = get_current_student()
@@ -209,7 +209,7 @@
 	if student:
 		enrollment = get_or_create_course_enrollment(course, program)
 		if quiz.allowed_attempt(enrollment, quiz_name):
-			enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status)
+			enrollment.add_quiz_activity(quiz_name, quiz_response, result, score, status, time_taken)
 			return {'result': result, 'score': score, 'status': status}
 		else:
 			return None
@@ -219,8 +219,9 @@
 	try:
 		quiz = frappe.get_doc("Quiz", quiz_name)
 		questions = quiz.get_questions()
+		duration = quiz.duration
 	except:
-		frappe.throw(_("Quiz {0} does not exist").format(quiz_name))
+		frappe.throw(_("Quiz {0} does not exist").format(quiz_name), frappe.DoesNotExistError)
 		return None
 
 	questions = [{
@@ -232,12 +233,20 @@
 		} for question in questions]
 
 	if has_super_access():
-		return {'questions': questions, 'activity': None}
+		return {
+			'questions': questions,
+			'activity': None,
+			'duration':duration
+		}
 
 	student = get_current_student()
 	course_enrollment = get_enrollment("course", course, student.name)
-	status, score, result = check_quiz_completion(quiz, course_enrollment)
-	return {'questions': questions, 'activity': {'is_complete': status, 'score': score, 'result': result}}
+	status, score, result, time_taken = check_quiz_completion(quiz, course_enrollment)
+	return {
+		'questions': questions, 
+		'activity': {'is_complete': status, 'score': score, 'result': result, 'time_taken': time_taken},
+		'duration': quiz.duration
+	}
 
 def get_topic_progress(topic, course_name, program):
 	"""
@@ -361,15 +370,23 @@
 		return False
 
 def check_quiz_completion(quiz, enrollment_name):
-	attempts = frappe.get_all("Quiz Activity", filters={'enrollment': enrollment_name, 'quiz': quiz.name}, fields=["name", "activity_date", "score", "status"])
+	attempts = frappe.get_all("Quiz Activity",
+		filters={
+			'enrollment': enrollment_name, 
+			'quiz': quiz.name
+		}, 
+		fields=["name", "activity_date", "score", "status", "time_taken"]
+	)
 	status = False if quiz.max_attempts == 0 else bool(len(attempts) >= quiz.max_attempts)
 	score = None
 	result = None
+	time_taken = None
 	if attempts:
 		if quiz.grading_basis == 'Last Highest Score':
 			attempts = sorted(attempts, key = lambda i: int(i.score), reverse=True)
 		score = attempts[0]['score']
 		result = attempts[0]['status']
+		time_taken = attempts[0]['time_taken']
 		if result == 'Pass':
 			status = True
-	return status, score, result
\ No newline at end of file
+	return status, score, result, time_taken
\ No newline at end of file
diff --git a/erpnext/public/js/education/lms/quiz.js b/erpnext/public/js/education/lms/quiz.js
index 4a9d1e3..32fa4ab 100644
--- a/erpnext/public/js/education/lms/quiz.js
+++ b/erpnext/public/js/education/lms/quiz.js
@@ -20,6 +20,16 @@
 	}
 
 	make(data) {
+		if (data.duration) {
+			const timer_display = document.createElement("div");
+			timer_display.classList.add("lms-timer", "float-right", "font-weight-bold");
+			document.getElementsByClassName("lms-title")[0].appendChild(timer_display);
+			if (!data.activity || (data.activity && !data.activity.is_complete)) {
+				this.initialiseTimer(data.duration);
+				this.is_time_bound = true;
+				this.time_taken = 0;
+			}
+		}
 		data.questions.forEach(question_data => {
 			let question_wrapper = document.createElement('div');
 			let question = new Question({
@@ -37,12 +47,51 @@
 				indicator = 'green'
 				message = 'You have already cleared the quiz.'
 			}
-
+			if (data.activity.time_taken) {
+				this.calculate_and_display_time(data.activity.time_taken, "Time Taken - ");
+			}
 			this.set_quiz_footer(message, indicator, data.activity.score)
 		}
 		else {
 			this.make_actions();
 		}
+		window.addEventListener('beforeunload', (event) => {
+			event.preventDefault();
+			event.returnValue = '';
+		});
+	}
+
+	initialiseTimer(duration) {
+		this.time_left = duration;
+		var self = this;
+		var old_diff;
+		this.calculate_and_display_time(this.time_left, "Time Left - ");
+		this.start_time = new Date().getTime();
+		this.timer = setInterval(function () {
+			var diff = (new Date().getTime() - self.start_time)/1000;
+			var variation = old_diff ? diff - old_diff : diff;
+			old_diff = diff;
+			self.time_left -= variation;
+			self.time_taken += variation;
+			self.calculate_and_display_time(self.time_left, "Time Left - ");
+			if (self.time_left <= 0) {
+				clearInterval(self.timer);
+				self.time_taken -= 1;
+				self.submit();
+			}
+		}, 1000);
+	}
+
+	calculate_and_display_time(second, text) {
+		var timer_display = document.getElementsByClassName("lms-timer")[0];
+		var hours = this.append_zero(Math.floor(second / 3600));
+		var minutes = this.append_zero(Math.floor(second % 3600 / 60));
+		var seconds = this.append_zero(Math.ceil(second % 3600 % 60));
+		timer_display.innerText = text + hours + ":" + minutes + ":" + seconds;
+	}
+
+	append_zero(time) {
+		return time > 9 ? time : "0" + time;
 	}
 
 	make_actions() {
@@ -57,6 +106,10 @@
 	}
 
 	submit() {
+		if (this.is_time_bound) {
+			clearInterval(this.timer);
+			$(".lms-timer").text("");
+		}
 		this.submit_btn.innerText = 'Evaluating..'
 		this.submit_btn.disabled = true
 		this.disable()
@@ -64,7 +117,8 @@
 			quiz_name: this.name,
 			quiz_response: this.get_selected(),
 			course: this.course,
-			program: this.program
+			program: this.program,
+			time_taken: this.is_time_bound ? this.time_taken : ""
 		}).then(res => {
 			this.submit_btn.remove()
 			if (!res.message) {
@@ -157,7 +211,7 @@
 			return input;
 		}
 
-		let make_label = function(name, value) {
+		let make_label = function (name, value) {
 			let label = document.createElement('label');
 			label.classList.add('form-check-label');
 			label.htmlFor = name;
@@ -166,14 +220,14 @@
 		}
 
 		let make_option = function (wrapper, option) {
-			let option_div = document.createElement('div')
-			option_div.classList.add('form-check', 'pb-1')
+			let option_div = document.createElement('div');
+			option_div.classList.add('form-check', 'pb-1');
 			let input = make_input(option.name, option.option);
 			let label = make_label(option.name, option.option);
-			option_div.appendChild(input)
-			option_div.appendChild(label)
-			wrapper.appendChild(option_div)
-			return {input: input, ...option}
+			option_div.appendChild(input);
+			option_div.appendChild(label);
+			wrapper.appendChild(option_div);
+			return { input: input, ...option };
 		}
 
 		let options_wrapper = document.createElement('div')
diff --git a/erpnext/www/lms/content.html b/erpnext/www/lms/content.html
index dc9b6d8..15afb09 100644
--- a/erpnext/www/lms/content.html
+++ b/erpnext/www/lms/content.html
@@ -62,7 +62,7 @@
 			{{_('Back to Course')}}
 		</a>
 	</div>
-	<div>
+	<div class="lms-title">
 		<h2>{{ content.name }} <span class="small text-muted">({{ position + 1 }}/{{length}})</span></h2>
 	</div>
 {% endmacro %}
@@ -169,14 +169,51 @@
 			const next_url = '/lms/course?name={{ course }}&program={{ program }}'
 			{% endif %}
 			frappe.ready(() => {
-				const quiz = new Quiz(document.getElementById('quiz-wrapper'), {
-					name: '{{ content.name }}',
-					course: '{{ course }}',
-					program: '{{ program }}',
-					quiz_exit_button: quiz_exit_button,
-					next_url: next_url
-				})
-				window.quiz = quiz;
+				{% if content.is_time_bound %}
+					var duration = get_duration("{{content.duration}}")
+					var d = frappe.msgprint({
+						title: __('Important Notice'),
+						indicator: "red",
+						message: __(`This is a Time-Bound Quiz. <br><br>
+						A timer for <b>${duration}</b> will start, once you click on <b>Proceed</b>. <br><br>
+						If you fail to submit before the time is up, the Quiz will be submitted automatically.`),
+						primary_action: {
+							label: __("Proceed"),
+							action: () => {
+								create_quiz();
+								d.hide();
+        					}
+						},
+						secondary_action: {
+							action: () => {
+								d.hide();
+								window.location.href = "/lms/course?name={{ course }}&program={{ program }}";
+							},
+							label: __("Go Back"),
+						}
+					});
+				{% else %}
+					create_quiz();
+				{% endif %}
+				function create_quiz() {
+					const quiz = new Quiz(document.getElementById('quiz-wrapper'), {
+						name: '{{ content.name }}',
+						course: '{{ course }}',
+						program: '{{ program }}',
+						quiz_exit_button: quiz_exit_button,
+						next_url: next_url
+					})
+					window.quiz = quiz;
+				}
+				function get_duration(seconds){
+					var hours = append_zero(Math.floor(seconds / 3600));
+					var minutes = append_zero(Math.floor(seconds % 3600 / 60));
+					var seconds = append_zero(Math.floor(seconds % 3600 % 60));
+					return `${hours}:${minutes}:${seconds}`;
+				}
+				function append_zero(time) {
+					return time > 9 ? time : "0" + time;
+				}
 			})
 		{% endif  %}
 
diff --git a/erpnext/www/lms/index.html b/erpnext/www/lms/index.html
index 7b239ac..c1e9620 100644
--- a/erpnext/www/lms/index.html
+++ b/erpnext/www/lms/index.html
@@ -42,7 +42,9 @@
 <section class="top-section" style="padding: 6rem 0rem;">
 	<div class='container pb-5'>
 		<h1>{{ education_settings.portal_title }}</h1>
-		<p class='lead'>{{ education_settings.description }}</p>
+		{% if education_settings.description %}
+			<p class='lead'>{{ education_settings.description }}</p>
+		{% endif %}
 		<p class="mt-4">
 			{% if frappe.session.user == 'Guest' %}
 				<a class="btn btn-primary btn-lg" href="/login#signup">{{_('Sign Up')}}</a>
@@ -51,13 +53,15 @@
 	</div>
 	<div class='container'>
 		<div class="row mt-5">
-			{% for program in featured_programs %}
-				{{ program_card(program.program, program.has_access) }}
-			{% endfor %}
 			{% if featured_programs %}
+				{% for program in featured_programs %}
+					{{ program_card(program.program, program.has_access) }}
+				{% endfor %}
 				{% for n in range( (3 - (featured_programs|length)) %3) %}
 					{{ null_card() }}
 				{% endfor %}
+			{% else %}
+				<p class="lead">You have not enrolled in any program. Contact your Instructor.</p>
 			{% endif %}
 		</div>
 	</div>
diff --git a/erpnext/www/lms/topic.py b/erpnext/www/lms/topic.py
index f75ae8e..8abbc72 100644
--- a/erpnext/www/lms/topic.py
+++ b/erpnext/www/lms/topic.py
@@ -35,7 +35,7 @@
 				progress.append({'content': content, 'content_type': content.doctype, 'completed': status})
 			elif content.doctype == 'Quiz':
 				if student:
-					status, score, result = utils.check_quiz_completion(content, course_enrollment.name)
+					status, score, result, time_taken = utils.check_quiz_completion(content, course_enrollment.name)
 				else:
 					status = False
 					score = None