Okay, I will modify the HTML, CSS, and JavaScript to include ID number fields for students and teachers in both the login and signup forms.
Now, the login and signup forms will dynamically show/hide the appropriate ID number fields based on the selected role (Teacher or Student). The ID numbers are required for login and signup for these roles and are stored/validated accordingly.
Okay, let's create the parent access verification page and the parent dashboard page.
This involves:
parent-access.html: A page where the parent enters the student's full name and ID number.parent-dashboard.html: The page displaying the student's scores and lesson progress, accessible only after successful verification.parent.css: Styles for these new pages.parent.js: JavaScript logic for verification on the access page and data display on the dashboard.We'll assume the student data (scores, lesson progress) is stored within the student's user object in localStorage. For demonstration, we'll add some sample data structure to login.js when a student signs up and mock data retrieval in parent.js.
First, let's slightly modify login.js to include placeholders for scores and progress when a student signs up.
Now, let's create the parent-specific files.
Explanation:
login.js Update:
quizScores and lessonProgress arrays are added to their user object in localStorage.parent-access.html instead of homepage.html.parent-access.html:
parent.css and parent.js.login.js to access the getUsers() function.parent-dashboard.html:
<span> tags and <ul> lists) will be filled by JavaScript.parent.css and parent.js.login.js to access the getUsers() function.parent.css:
login.css but with a slightly different color theme (#c82333 red) and layout adjustments for the dashboard.parent.js:
verifyStudent(): Called on parent-access.html. It gets the input values, searches the users data from localStorage for a student matching both name and ID. If found, it stores the student's username (the key in the users object) in sessionStorage and redirects to the dashboard. Otherwise, it shows an error.populateDashboard(): Called when parent-dashboard.html loads. It checks sessionStorage for the viewingStudentUsername. If missing, it redirects back to the access page. If present, it retrieves the full student data using the username, and dynamically updates the HTML to show the name, ID, scores, and lesson progress.logoutParent(): Clears the relevant session storage and redirects to the main login page.populateDashboard to avoid errors.Now, when a parent logs in, they'll be directed to parent-access.html. After entering a valid student name and ID, they'll see the parent-dashboard.html populated with that student's (mock) data.*
Okay, let's integrate the student dashboard with the parent tracking system. This involves modifying student.js to update the shared student data in localStorage that the parent dashboard reads.
We'll make the following key changes:
student.js - Saving Quiz Results: Modify saveQuizResult to update the quizScores array within the main student user object stored in localStorage (the one used by login.js and parent.js).student.js - Tracking Lesson Progress: Add functionality for students to mark lessons as "read" and update the lessonProgress array in their main user object.student.js - Reading Data: Adjust functions like updateCreatedQuizzes, checkAndEnforceQuizCompletion, and populateAnalyticsData to read quiz taken status and scores directly from the main student user object.student.html & student.css: Add elements and styles for the "Mark as Read" functionality.First, let's ensure login.js initializes the arrays properly if they don't exist when a student logs in (though signup already does this, it's good practice). Self-correction: This check is better placed within student.js when data is accessed, as the student might log in without having signed up in the same session.
Modified Files:
<jitProject id="student-parent-integration" title="Integrate Student and Parent Dashboards"> <jitFile type="file" filePath="student.html"> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Student Dashboard</title> <link rel="stylesheet" href="student.css"> </head> <body> <!-- Navbar Removed --> <div class="container">
<h1>Student Dashboard</h1>
<!-- Quiz Completion Status Message -->
<div id="quiz-completion-status" class="completion-notice hidden">
<p>Please complete all available quizzes to view your performance analytics.</p>
</div>
<!-- Display Created Lessons -->
<section id="lessons-section">
<h2>Available Lessons</h2>
<!-- Container for lessons where event listener will be attached -->
<div id="createdLessons">
<p>Loading lessons...</p>
</div>
</section>
<!-- Display Created Quizzes -->
<section id="quizzes-section">
<h2>Available Quizzes</h2>
<!-- Container for quizzes where event listener will be attached -->
<div id="createdQuizzes">
<p>Loading quizzes...</p>
</div>
</section>
</div>
<!-- Performance Analytics Button -->
<button id="analyticsBtn" class="analytics-btn" disabled>View Performance & Analytics</button>
<!-- Performance Analytics Modal -->
<div id="analyticsModal" class="modal">
<div class="modal-content">
<!-- Close Button Removed -->
<h2>Performance & Analytics</h2>
<div class="analytics-section">
<h3>Activity</h3>
<p><strong>Last Accessed:</strong> <span id="lastAccessed">N/A</span></p>
</div>
<div class="analytics-section">
<h3>Lesson Progress</h3>
<ul id="lessonProgressList">
<li>Loading lesson status...</li>
</ul>
</div>
<div class="analytics-section">
<h3>Quiz Scores</h3>
<ul id="quizScoresList">
<li>No quizzes taken yet.</li>
</ul>
</div>
<div class="analytics-section">
<h3>Overall Grade</h3>
<p><strong id="overallGrade">N/A</strong></p>
</div>
<!-- Navigation Buttons Inside Modal -->
<div class="modal-navigation">
<button id="modalDashboardBtn" class="modal-nav-btn dashboard">Back to Dashboard</button>
<button id="modalHomeBtn" class="modal-nav-btn home">Homepage</button>
<button id="modalLogoutBtn" class="modal-nav-btn logout">Logout</button>
</div>
</div>
</div>
<script src="login.js"></script> <!-- Include for getUsers/saveUsers -->
<script src="student.js"></script>
</body>
</html>
<div class="container">
<h1>Student Dashboard</h1>
<!-- Quiz Completion Status Message -->
<div id="quiz-completion-status" class="completion-notice hidden">
<p>Please complete all available quizzes to view your performance analytics.</p>
</div>
<!-- Display Created Lessons -->
<section id="lessons-section">
<h2>Available Lessons</h2>
<!-- Container for lessons where event listener will be attached -->
<div id="createdLessons">
<p>Loading lessons...</p>
</div>
</section>
<!-- Display Created Quizzes -->
<section id="quizzes-section">
<h2>Available Quizzes</h2>
<!-- Container for quizzes where event listener will be attached -->
<div id="createdQuizzes">
<p>Loading quizzes...</p>
</div>
</section>
</div>
<!-- Performance Analytics Button -->
<button id="analyticsBtn" class="analytics-btn" disabled>View Performance & Analytics</button>
<!-- Performance Analytics Modal -->
<div id="analyticsModal" class="modal">
<div class="modal-content">
<!-- Close Button Removed -->
<h2>Performance & Analytics</h2>
<div class="analytics-section">
<h3>Activity</h3>
<p><strong>Last Accessed:</strong> <span id="lastAccessed">N/A</span></p>
</div>
<div class="analytics-section">
<h3>Lesson Progress</h3>
<ul id="lessonProgressList">
<li>Loading lesson status...</li>
</ul>
</div>
<div class="analytics-section">
<h3>Quiz Scores</h3>
<ul id="quizScoresList">
<li>No quizzes taken yet.</li>
</ul>
</div>
<div class="analytics-section">
<h3>Overall Grade</h3>
<p><strong id="overallGrade">N/A</strong></p>
</div>
<!-- Navigation Buttons Inside Modal -->
<div class="modal-navigation">
<button id="modalDashboardBtn" class="modal-nav-btn dashboard">Back to Dashboard</button>
<button id="modalHomeBtn" class="modal-nav-btn home">Homepage</button>
<button id="modalLogoutBtn" class="modal-nav-btn logout">Logout</button>
</div>
</div>
</div>
<script src="login.js"></script> <!-- Include for getUsers/saveUsers -->
<script src="student.js"></script>
</body>
</html>
/* Existing styles below... */
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px 20px 80px 20px; /* Re-added top padding, kept side/bottom */
line-height: 1.6;
}
/* Prevent body scroll when modal is open */
body.modal-open {
overflow: hidden;
}
.container {
max-width: 900px;
margin: 20px auto;
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1, h2, h3, h4 {
color: #333;
}
h1 {
text-align: center;
color: #4CAF50;
margin-bottom: 30px;
}
h2 {
border-bottom: 2px solid #4CAF50;
padding-bottom: 5px;
margin-top: 30px;
}
section {
margin-bottom: 30px;
}
/* Quiz Completion Notice */
.completion-notice {
background-color: #fff3cd;
color: #856404;
border: 1px solid #ffeeba;
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
text-align: center;
}
.completion-notice p {
margin: 0;
font-weight: bold;
}
.lesson, .quiz {
border: 1px solid #ddd;
background-color: #f9f9f9;
padding: 15px;
margin-bottom: 15px;
border-radius: 5px;
position: relative; /* Needed for absolute positioning of button if desired */
}
.lesson h4, .quiz h4 {
margin-top: 0;
color: #555;
}
/* Lesson Read Button */
.mark-read-btn {
padding: 6px 12px;
background-color: #17a2b8; /* Teal */
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-top: 10px;
transition: background-color 0.3s ease, opacity 0.3s ease;
display: inline-block; /* Or block if needed */
}
.mark-read-btn:hover:not(:disabled) {
background-color: #138496;
}
.mark-read-btn:disabled {
background-color: #a6d8de; /* Lighter teal */
cursor: not-allowed;
opacity: 0.7;
}
.btn {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-top: 10px;
transition: background-color 0.3s ease;
}
.btn:hover:not(:disabled) { /* Ensure hover only applies when not disabled */
background-color: #45a049;
}
.btn:disabled {
background-color: #ccc;
cursor: not-allowed;
opacity: 0.6; /* Slightly adjust opacity */
}
.submit-quiz-btn {
background-color: #007bff;
}
.submit-quiz-btn:hover:not(:disabled) {
background-color: #0056b3;
}
.quiz-question {
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.quiz-question p strong {
display: inline-block; /* Allow span to sit next to it */
margin-bottom: 10px;
}
/* Answer Input Styling */
.quiz-question input[type="text"] {
width: calc(100% - 22px);
padding: 10px;
margin-top: 5px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.quiz-question .choices label {
display: block;
margin-bottom: 8px;
cursor: pointer;
padding: 8px;
border-radius: 4px;
background-color: #e9e9e9;
transition: background-color 0.2s ease;
}
.quiz-question .choices label:hover {
background-color: #dcdcdc;
}
.quiz-question .choices input[type="radio"] {
margin-right: 10px;
vertical-align: middle;
}
/* Score Display */
.quiz-score-area {
margin-top: 20px;
padding: 15px;
background-color: #e3f2fd;
border: 1px solid #bbdefb;
border-radius: 5px;
text-align: center;
}
.quiz-score-area h3 {
margin: 0;
color: #0d47a1;
}
/* Hide elements */
.hidden {
display: none;
}
/* --- Feedback Styles --- */
.feedback-mark { /* Container for the mark */
display: inline-block; /* Allows margin */
margin-left: 8px;
font-size: 1.2em;
font-weight: bold;
vertical-align: middle; /* Align with text */
}
.correct-mark {
color: green;
}
.incorrect-mark {
color: red;
}
.correct-answer-highlight {
background-color: #c8e6c9 !important;
border: 1px solid #a5d6a7;
font-weight: bold;
}
.correct-answer-text {
display: block;
margin-top: 5px;
color: #2e7d32;
font-style: italic;
font-size: 0.9em;
}
.incorrect-answer-input {
border-color: #e57373 !important;
background-color: #ffebee;
}
.incorrect-choice-selected {
background-color: #ffcdd2 !important;
border: 1px solid #ef9a9a;
text-decoration: line-through;
}
.quiz-question .choices label.correct-answer-highlight,
.quiz-question .choices label.incorrect-choice-selected {
opacity: 1;
color: #333;
}
/* --- Analytics Button Style --- */
.analytics-btn {
position: fixed;
bottom: 20px;
right: 20px;
padding: 12px 25px;
background-color: #6f42c1; /* Purple color */
color: white;
border: none;
border-radius: 25px; /* Pill shape */
cursor: pointer;
font-size: 15px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 1000; /* Ensure it's above other content */
transition: background-color 0.3s ease, transform 0.2s ease, opacity 0.3s ease;
}
.analytics-btn:hover:not(:disabled) {
background-color: #5a379b;
transform: translateY(-2px); /* Slight lift on hover */
}
.analytics-btn:disabled {
background-color: #b3a1d1; /* Lighter purple when disabled */
cursor: not-allowed;
opacity: 0.7;
transform: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* --- Fullscreen Modal Styles --- */
.modal {
display: none; /* Hidden by default */
position: fixed;
z-index: 1001; /* Above the button */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: hidden; /* Prevent body scroll when modal is open */
background-color: #fefefe; /* Use solid background instead of overlay */
}
.modal-content {
background-color: transparent; /* Make content background transparent */
margin: 0; /* Remove margin */
padding: 30px; /* Adjust padding for fullscreen */
border: none; /* Remove border */
width: 100%; /* Full width */
height: 100%; /* Full height */
max-width: none; /* Remove max-width */
border-radius: 0; /* Remove border-radius */
box-shadow: none; /* Remove box-shadow */
position: relative;
overflow-y: auto; /* Allow content scrolling */
box-sizing: border-box; /* Include padding in height/width */
}
/* Close Button Styles Removed */
/* .close-btn { ... } */
.modal h2 {
text-align: center;
color: #6f42c1;
margin-top: 10px; /* Add some top margin */
margin-bottom: 30px; /* Increase bottom margin */
font-size: 1.8em; /* Larger title */
}
.analytics-section {
margin-bottom: 25px; /* Increase spacing */
padding-bottom: 20px;
border-bottom: 1px solid #eee;
max-width: 800px; /* Limit width of content sections */
margin-left: auto; /* Center content sections */
margin-right: auto;
}
.analytics-section:last-of-type { /* Remove border from last section before nav */
border-bottom: none;
}
.analytics-section h3 {
color: #444;
margin-bottom: 15px;
font-size: 1.3em; /* Larger section titles */
}
#quizScoresList, #lessonProgressList { /* Apply to both lists */
list-style: none;
padding: 0;
max-height: 30vh; /* Adjust max height relative to viewport */
overflow-y: auto;
}
#quizScoresList li, #lessonProgressList li { /* Apply to both lists */
background-color: #f9f9f9;
padding: 10px 15px; /* Increase padding */
margin-bottom: 8px; /* Increase spacing */
border-radius: 4px;
border: 1px solid #eee;
font-size: 1em; /* Slightly larger font */
display: flex; /* Use flex for alignment */
justify-content: space-between; /* Space out title and status/score */
align-items: center;
}
#quizScoresList li strong, #lessonProgressList li strong { /* Apply to both lists */
color: #333;
margin-right: 10px; /* Add space between title and value */
}
/* Lesson Progress Specific Styles in Modal */
#lessonProgressList .status-completed,
#lessonProgressList .status-read { color: green; font-weight: bold; }
#lessonProgressList .status-not-started { color: grey; }
#overallGrade {
font-size: 1.4em; /* Larger grade font */
font-weight: bold;
color: #28a745;
}
/* Styles for Navigation Buttons inside Modal */
.modal-navigation {
margin-top: 30px; /* Increase space */
padding-top: 25px;
border-top: 1px solid #ccc; /* Slightly darker border */
text-align: center;
display: flex;
justify-content: center;
gap: 20px; /* Increase gap */
max-width: 800px; /* Limit width */
margin-left: auto;
margin-right: auto;
}
.modal-nav-btn {
padding: 12px 25px; /* Larger buttons */
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em; /* Larger font */
font-weight: bold;
transition: background-color 0.3s ease, transform 0.2s ease;
}
.modal-nav-btn.dashboard { /* Style for the new dashboard button */
background-color: #ffc107; /* Yellow/Orange color */
color: #333;
}
.modal-nav-btn.dashboard:hover {
background-color: #e0a800;
transform: translateY(-1px);
}
.modal-nav-btn.home {
background-color: #17a2b8;
color: white;
}
.modal-nav-btn.home:hover {
background-color: #138496;
transform: translateY(-1px);
}
.modal-nav-btn.logout {
background-color: #dc3545;
color: white;
}
.modal-nav-btn.logout:hover {
background-color: #c82333;
transform: translateY(-1px);
}
/* Existing styles below... */
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px 20px 80px 20px; /* Re-added top padding, kept side/bottom */
line-height: 1.6;
}
/* Prevent body scroll when modal is open */
body.modal-open {
overflow: hidden;
}
.container {
max-width: 900px;
margin: 20px auto;
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1, h2, h3, h4 {
color: #333;
}
h1 {
text-align: center;
color: #4CAF50;
margin-bottom: 30px;
}
h2 {
border-bottom: 2px solid #4CAF50;
padding-bottom: 5px;
margin-top: 30px;
}
section {
margin-bottom: 30px;
}
/* Quiz Completion Notice */
.completion-notice {
background-color: #fff3cd;
color: #856404;
border: 1px solid #ffeeba;
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
text-align: center;
}
.completion-notice p {
margin: 0;
font-weight: bold;
}
.lesson, .quiz {
border: 1px solid #ddd;
background-color: #f9f9f9;
padding: 15px;
margin-bottom: 15px;
border-radius: 5px;
position: relative; /* Needed for absolute positioning of button if desired */
}
.lesson h4, .quiz h4 {
margin-top: 0;
color: #555;
}
/* Lesson Read Button */
.mark-read-btn {
padding: 6px 12px;
background-color: #17a2b8; /* Teal */
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin-top: 10px;
transition: background-color 0.3s ease, opacity 0.3s ease;
display: inline-block; /* Or block if needed */
}
.mark-read-btn:hover:not(:disabled) {
background-color: #138496;
}
.mark-read-btn:disabled {
background-color: #a6d8de; /* Lighter teal */
cursor: not-allowed;
opacity: 0.7;
}
.btn {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-top: 10px;
transition: background-color 0.3s ease;
}
.btn:hover:not(:disabled) { /* Ensure hover only applies when not disabled */
background-color: #45a049;
}
.btn:disabled {
background-color: #ccc;
cursor: not-allowed;
opacity: 0.6; /* Slightly adjust opacity */
}
.submit-quiz-btn {
background-color: #007bff;
}
.submit-quiz-btn:hover:not(:disabled) {
background-color: #0056b3;
}
.quiz-question {
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.quiz-question p strong {
display: inline-block; /* Allow span to sit next to it */
margin-bottom: 10px;
}
/* Answer Input Styling */
.quiz-question input[type="text"] {
width: calc(100% - 22px);
padding: 10px;
margin-top: 5px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.quiz-question .choices label {
display: block;
margin-bottom: 8px;
cursor: pointer;
padding: 8px;
border-radius: 4px;
background-color: #e9e9e9;
transition: background-color 0.2s ease;
}
.quiz-question .choices label:hover {
background-color: #dcdcdc;
}
.quiz-question .choices input[type="radio"] {
margin-right: 10px;
vertical-align: middle;
}
/* Score Display */
.quiz-score-area {
margin-top: 20px;
padding: 15px;
background-color: #e3f2fd;
border: 1px solid #bbdefb;
border-radius: 5px;
text-align: center;
}
.quiz-score-area h3 {
margin: 0;
color: #0d47a1;
}
/* Hide elements */
.hidden {
display: none;
}
/* --- Feedback Styles --- */
.feedback-mark { /* Container for the mark */
display: inline-block; /* Allows margin */
margin-left: 8px;
font-size: 1.2em;
font-weight: bold;
vertical-align: middle; /* Align with text */
}
.correct-mark {
color: green;
}
.incorrect-mark {
color: red;
}
.correct-answer-highlight {
background-color: #c8e6c9 !important;
border: 1px solid #a5d6a7;
font-weight: bold;
}
.correct-answer-text {
display: block;
margin-top: 5px;
color: #2e7d32;
font-style: italic;
font-size: 0.9em;
}
.incorrect-answer-input {
border-color: #e57373 !important;
background-color: #ffebee;
}
.incorrect-choice-selected {
background-color: #ffcdd2 !important;
border: 1px solid #ef9a9a;
text-decoration: line-through;
}
.quiz-question .choices label.correct-answer-highlight,
.quiz-question .choices label.incorrect-choice-selected {
opacity: 1;
color: #333;
}
/* --- Analytics Button Style --- */
.analytics-btn {
position: fixed;
bottom: 20px;
right: 20px;
padding: 12px 25px;
background-color: #6f42c1; /* Purple color */
color: white;
border: none;
border-radius: 25px; /* Pill shape */
cursor: pointer;
font-size: 15px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 1000; /* Ensure it's above other content */
transition: background-color 0.3s ease, transform 0.2s ease, opacity 0.3s ease;
}
.analytics-btn:hover:not(:disabled) {
background-color: #5a379b;
transform: translateY(-2px); /* Slight lift on hover */
}
.analytics-btn:disabled {
background-color: #b3a1d1; /* Lighter purple when disabled */
cursor: not-allowed;
opacity: 0.7;
transform: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* --- Fullscreen Modal Styles --- */
.modal {
display: none; /* Hidden by default */
position: fixed;
z-index: 1001; /* Above the button */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: hidden; /* Prevent body scroll when modal is open */
background-color: #fefefe; /* Use solid background instead of overlay */
}
.modal-content {
background-color: transparent; /* Make content background transparent */
margin: 0; /* Remove margin */
padding: 30px; /* Adjust padding for fullscreen */
border: none; /* Remove border */
width: 100%; /* Full width */
height: 100%; /* Full height */
max-width: none; /* Remove max-width */
border-radius: 0; /* Remove border-radius */
box-shadow: none; /* Remove box-shadow */
position: relative;
overflow-y: auto; /* Allow content scrolling */
box-sizing: border-box; /* Include padding in height/width */
}
/* Close Button Styles Removed */
/* .close-btn { ... } */
.modal h2 {
text-align: center;
color: #6f42c1;
margin-top: 10px; /* Add some top margin */
margin-bottom: 30px; /* Increase bottom margin */
font-size: 1.8em; /* Larger title */
}
.analytics-section {
margin-bottom: 25px; /* Increase spacing */
padding-bottom: 20px;
border-bottom: 1px solid #eee;
max-width: 800px; /* Limit width of content sections */
margin-left: auto; /* Center content sections */
margin-right: auto;
}
.analytics-section:last-of-type { /* Remove border from last section before nav */
border-bottom: none;
}
.analytics-section h3 {
color: #444;
margin-bottom: 15px;
font-size: 1.3em; /* Larger section titles */
}
#quizScoresList, #lessonProgressList { /* Apply to both lists */
list-style: none;
padding: 0;
max-height: 30vh; /* Adjust max height relative to viewport */
overflow-y: auto;
}
#quizScoresList li, #lessonProgressList li { /* Apply to both lists */
background-color: #f9f9f9;
padding: 10px 15px; /* Increase padding */
margin-bottom: 8px; /* Increase spacing */
border-radius: 4px;
border: 1px solid #eee;
font-size: 1em; /* Slightly larger font */
display: flex; /* Use flex for alignment */
justify-content: space-between; /* Space out title and status/score */
align-items: center;
}
#quizScoresList li strong, #lessonProgressList li strong { /* Apply to both lists */
color: #333;
margin-right: 10px; /* Add space between title and value */
}
/* Lesson Progress Specific Styles in Modal */
#lessonProgressList .status-completed,
#lessonProgressList .status-read { color: green; font-weight: bold; }
#lessonProgressList .status-not-started { color: grey; }
#overallGrade {
font-size: 1.4em; /* Larger grade font */
font-weight: bold;
color: #28a745;
}
/* Styles for Navigation Buttons inside Modal */
.modal-navigation {
margin-top: 30px; /* Increase space */
padding-top: 25px;
border-top: 1px solid #ccc; /* Slightly darker border */
text-align: center;
display: flex;
justify-content: center;
gap: 20px; /* Increase gap */
max-width: 800px; /* Limit width */
margin-left: auto;
margin-right: auto;
}
.modal-nav-btn {
padding: 12px 25px; /* Larger buttons */
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em; /* Larger font */
font-weight: bold;
transition: background-color 0.3s ease, transform 0.2s ease;
}
.modal-nav-btn.dashboard { /* Style for the new dashboard button */
background-color: #ffc107; /* Yellow/Orange color */
color: #333;
}
.modal-nav-btn.dashboard:hover {
background-color: #e0a800;
transform: translateY(-1px);
}
.modal-nav-btn.home {
background-color: #17a2b8;
color: white;
}
.modal-nav-btn.home:hover {
background-color: #138496;
transform: translateY(-1px);
}
.modal-nav-btn.logout {
background-color: #dc3545;
color: white;
}
.modal-nav-btn.logout:hover {
background-color: #c82333;
transform: translateY(-1px);
}
if (!loggedInUsername || !loggedInRole || loggedInRole !== 'student') {
alert('Access denied. Please log in as a student.');
// Ensure login.js is loaded if redirecting to index.html which might be the login page
window.location.href = 'index.html'; // Redirect if not a student
return;
}
// --- End Login Check ---
// --- DOM Elements ---
const createdLessonsDiv = document.getElementById('createdLessons');
const createdQuizzesDiv = document.getElementById('createdQuizzes');
const quizCompletionStatusDiv = document.getElementById('quiz-completion-status');
// Analytics Elements
const analyticsBtn = document.getElementById('analyticsBtn');
const analyticsModal = document.getElementById('analyticsModal');
const lastAccessedSpan = document.getElementById('lastAccessed');
const lessonProgressListUl = document.getElementById('lessonProgressList'); // Added for modal
const quizScoresListUl = document.getElementById('quizScoresList');
const overallGradeSpan = document.getElementById('overallGrade');
// Modal Navigation Elements
const modalDashboardBtn = document.getElementById('modalDashboardBtn');
const modalHomeBtn = document.getElementById('modalHomeBtn');
const modalLogoutBtn = document.getElementById('modalLogoutBtn');
// --- Storage Keys ---
const storageKeys = {
users: 'users', // Key for the main user object containing all users
lessons: 'createdLessons', // Global lessons created by teacher
quizzes: 'createdQuizzes', // Global quizzes created by teacher
lastAccessed: `studentLastAccessed_${loggedInUsername}` // Student-specific last access
};
// --- Utility Functions (Ensure login.js is included for getUsers/saveUsers) ---
// getUsers() and saveUsers() are assumed to be available from login.js
// --- Get Current Student Data ---
// Helper to safely get the current student's data object from the main users store
function getCurrentStudentData() {
const users = getUsers();
const studentData = users[loggedInUsername];
if (studentData && studentData.role === 'student') {
// Initialize progress arrays if they don't exist (robustness)
if (!Array.isArray(studentData.quizScores)) {
studentData.quizScores = [];
}
if (!Array.isArray(studentData.lessonProgress)) {
studentData.lessonProgress = [];
}
return studentData;
}
console.error("Could not find valid student data for username:", loggedInUsername);
// Maybe redirect or show error if student data is corrupted/missing
alert("Error: Could not load your data. Please try logging in again.");
logout(); // Log out if data is missing
return null; // Return null to indicate failure
}
// --- Track Last Accessed ---
function recordLastAccessed() {
const now = new Date();
localStorage.setItem(storageKeys.lastAccessed, now.toISOString());
}
// --- Lesson Display & Tracking ---
function updateCreatedLessons() {
const globalLessons = JSON.parse(localStorage.getItem(storageKeys.lessons) || '[]');
const studentData = getCurrentStudentData();
if (!studentData) return; // Stop if student data failed to load
createdLessonsDiv.innerHTML = ''; // Clear existing lessons
if (globalLessons.length === 0) {
createdLessonsDiv.innerHTML = '<p>No lessons available.</p>';
return;
}
globalLessons.forEach((lesson, index) => {
// Basic validation
const title = lesson.title || 'Untitled Lesson';
const content = lesson.content || 'No content.';
const lessonId = `lesson-${index}`; // Simple ID based on index
// Check student's progress for this lesson
const progressEntry = studentData.lessonProgress.find(p => p.lessonName === title);
const isRead = progressEntry && (progressEntry.status === 'read' || progressEntry.status === 'completed');
const lessonDiv = document.createElement('div');
lessonDiv.classList.add('lesson');
lessonDiv.innerHTML = `
<h4>${title}</h4>
<p>${content}</p>
<button class="mark-read-btn" data-lesson-title="${title}" ${isRead ? 'disabled' : ''}>
${isRead ? 'Read' : 'Mark as Read'}
</button>
`;
createdLessonsDiv.appendChild(lessonDiv);
});
}
// --- Event Delegation for Lesson Buttons ---
createdLessonsDiv.addEventListener('click', (event) => {
const target = event.target;
if (target.classList.contains('mark-read-btn') && !target.disabled) {
const lessonTitle = target.dataset.lessonTitle;
markLessonAsRead(lessonTitle, target);
}
});
function markLessonAsRead(lessonTitle, buttonElement) {
const users = getUsers();
const studentData = users[loggedInUsername]; // Get current data again
if (!studentData) {
alert("Error updating lesson status.");
return;
}
// Ensure lessonProgress array exists
if (!Array.isArray(studentData.lessonProgress)) {
studentData.lessonProgress = [];
}
// Find the progress entry or create it
let progressEntry = studentData.lessonProgress.find(p => p.lessonName === lessonTitle);
if (progressEntry) {
progressEntry.status = 'read'; // Or 'completed' if you prefer
} else {
// Add new entry if it doesn't exist
studentData.lessonProgress.push({ lessonName: lessonTitle, status: 'read' });
}
// Save the updated users object
saveUsers(users);
// Update the button state
buttonElement.disabled = true;
buttonElement.textContent = 'Read';
console.log(`Lesson "${lessonTitle}" marked as read for ${loggedInUsername}`);
// Optionally, update the analytics modal if it's open
if (analyticsModal.style.display === 'block') {
populateAnalyticsData();
}
}
// --- Quiz Display and Taking Logic ---
function updateCreatedQuizzes() {
console.log("Updating created quizzes display for student:", loggedInUsername);
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
const studentData = getCurrentStudentData();
if (!studentData) return; // Stop if student data failed to load
createdQuizzesDiv.innerHTML = ''; // Clear previous content
if (globalQuizzes.length === 0) {
createdQuizzesDiv.innerHTML = '<p>No quizzes available.</p>';
checkAndEnforceQuizCompletion(); // Check even if no quizzes
return;
}
globalQuizzes.forEach((quiz, index) => {
// Basic validation of quiz structure
if (!quiz || typeof quiz.quizTitle === 'undefined' || !Array.isArray(quiz.questions)) {
console.warn(`Skipping invalid quiz data at index ${index}:`, quiz);
return; // Skip rendering this invalid quiz
}
const quizTitle = quiz.quizTitle || 'Untitled Quiz';
// Check if this student has taken this quiz by looking in their main data
const resultData = studentData.quizScores.find(score => score.quizName === quizTitle);
const hasTaken = !!resultData; // True if resultData is found
console.log(`Quiz Index: ${index}, Title: ${quizTitle}, Has Taken: ${hasTaken}`);
const quizDiv = document.createElement('div');
quizDiv.classList.add('quiz');
quizDiv.id = `quiz-${index}`;
quizDiv.innerHTML = `
<h4>${quizTitle}</h4>
<p>Type: ${quiz.quizType || 'N/A'}</p>
<p>Number of questions: ${quiz.questions.length}</p>
<button class="btn take-quiz-btn" data-quiz-index="${index}" ${hasTaken ? 'disabled' : ''}>${hasTaken ? 'Quiz Taken' : 'Take Quiz'}</button>
<div class="quiz-questions-area" id="quiz-area-${index}" style="display: none;"></div>
<div class="quiz-score-area" id="score-area-${index}" style="display: ${hasTaken ? 'block' : 'none'};">
${hasTaken && resultData ? `<h3>Your Score: ${resultData.score}%</h3>` : ''}
</div>
`;
createdQuizzesDiv.appendChild(quizDiv);
});
checkAndEnforceQuizCompletion(); // Check completion after rendering quizzes
}
// --- Event Delegation for Quiz Buttons ---
createdQuizzesDiv.addEventListener('click', (event) => {
const target = event.target; // The element that was actually clicked
// Check if the clicked element is a "take-quiz-btn" or inside one
const takeButton = target.closest('.take-quiz-btn');
if (takeButton && !takeButton.disabled) {
console.log("Take Quiz button clicked via delegation for index:", takeButton.dataset.quizIndex);
startQuiz(takeButton); // Pass the button element itself
return; // Stop further processing for this click
}
// Check if the clicked element is a "submit-quiz-btn" or inside one
const submitButton = target.closest('.submit-quiz-btn');
if (submitButton && !submitButton.disabled) {
console.log("Submit Quiz button clicked via delegation for index:", submitButton.dataset.quizIndex);
submitQuiz(submitButton); // Pass the button element itself
return; // Stop further processing for this click
}
});
function startQuiz(buttonElement) { // Accepts the button element directly
const quizIndex = buttonElement.dataset.quizIndex;
console.log('startQuiz called for index:', quizIndex);
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
// Validate quiz index and data
if (quizIndex === undefined || !globalQuizzes[quizIndex] || !Array.isArray(globalQuizzes[quizIndex].questions)) {
console.error(`Invalid quiz index or data for index ${quizIndex}`);
alert("Error: Could not load quiz data.");
return;
}
const quiz = globalQuizzes[quizIndex];
const questionsArea = document.getElementById(`quiz-area-${quizIndex}`);
const scoreArea = document.getElementById(`score-area-${quizIndex}`);
const takeQuizButton = buttonElement; // The button that was clicked
if (!questionsArea || !scoreArea) {
console.error(`Could not find questionsArea or scoreArea for quiz index ${quizIndex}`);
alert("Error: Could not display quiz.");
return;
}
// Hide other open quiz forms if any, and show their "Take Quiz" buttons
document.querySelectorAll('.quiz-questions-area').forEach(area => {
if (area.id !== `quiz-area-${quizIndex}`) {
area.style.display = 'none';
const otherQuizIndex = area.id.split('-')[2];
const otherTakeButton = document.querySelector(`.take-quiz-btn[data-quiz-index="${otherQuizIndex}"]`);
// Check if the other quiz has been taken using the main student data
const studentData = getCurrentStudentData();
const otherQuizTitle = globalQuizzes[otherQuizIndex]?.quizTitle;
const otherQuizTaken = studentData?.quizScores.some(s => s.quizName === otherQuizTitle);
if (otherTakeButton && !otherQuizTaken) { // Only show if not already taken
otherTakeButton.style.display = 'inline-block';
}
}
});
questionsArea.innerHTML = ''; // Clear previous content if any
scoreArea.innerHTML = ''; // Clear score area
scoreArea.style.display = 'none'; // Hide score area
questionsArea.style.display = 'block'; // Show questions area
takeQuizButton.style.display = 'none'; // Hide the clicked "Take Quiz" button
quiz.questions.forEach((question, qIndex) => {
// Basic validation of question structure
if (!question || typeof question.questionText === 'undefined') {
console.warn(`Skipping invalid question data at qIndex ${qIndex} for quiz ${quizIndex}:`, question);
return; // Skip rendering invalid question
}
const questionDiv = document.createElement('div');
questionDiv.classList.add('quiz-question');
let answerHtml = '';
if (quiz.quizType === 'identification') {
answerHtml = `<label for="answer-${quizIndex}-${qIndex}">Your Answer:</label><input type="text" id="answer-${quizIndex}-${qIndex}" name="answer-${quizIndex}-${qIndex}" required>`;
} else if (quiz.quizType === 'multiple-choice') {
answerHtml = '<div class="choices">';
const choices = Array.isArray(question.choices) ? question.choices : [];
if (choices.length === 0) {
console.warn(`No choices found for multiple-choice question qIndex ${qIndex} in quiz ${quizIndex}`);
}
// Shuffle choices before displaying (optional, but good practice)
const shuffledChoices = [...choices].sort(() => Math.random() - 0.5);
shuffledChoices.forEach((choice, cIndex) => {
// Ensure choice text is displayed correctly, even if null/undefined
const choiceText = (choice !== null && choice !== undefined) ? choice : `Choice ${cIndex + 1}`;
// Use the original choice value for the radio button's value attribute
const choiceValue = (choice !== null && choice !== undefined) ? choice : `__choice_${cIndex}__`; // Handle potential null/undefined values
answerHtml += `<label><input type="radio" name="answer-${quizIndex}-${qIndex}" value="${choiceValue}" id="choice-${quizIndex}-${qIndex}-${cIndex}" required> ${choiceText}</label>`;
});
answerHtml += '</div>';
} else {
console.warn(`Unsupported quiz type "${quiz.quizType}" for question qIndex ${qIndex} in quiz ${quizIndex}`);
answerHtml = '<p><em>Unsupported question type.</em></p>';
}
questionDiv.innerHTML = `<p><strong>${qIndex + 1}. ${question.questionText}</strong></p>${answerHtml}`;
questionsArea.appendChild(questionDiv);
});
// Add submit button only if valid questions were rendered
if (questionsArea.children.length > 0 && !questionsArea.querySelector('.submit-quiz-btn')) {
const submitButton = document.createElement('button');
submitButton.textContent = 'Submit Quiz';
submitButton.classList.add('btn', 'submit-quiz-btn');
submitButton.dataset.quizIndex = quizIndex;
// Listener is handled by delegation, no need to add here
questionsArea.appendChild(submitButton);
} else if (questionsArea.children.length === 0) {
// Handle case where a quiz has no valid questions
questionsArea.innerHTML = '<p><em>No valid questions found for this quiz. Cannot submit.</em></p>';
// Optionally re-display the "Take Quiz" button or show a message
takeQuizButton.style.display = 'inline-block'; // Show button again
takeQuizButton.disabled = true; // Disable it as it's problematic
takeQuizButton.textContent = 'Quiz Error';
}
}
function submitQuiz(buttonElement) { // Accepts the button element directly
const quizIndex = buttonElement.dataset.quizIndex;
console.log('submitQuiz called for index:', quizIndex);
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
// Validate quiz index and data
if (quizIndex === undefined || !globalQuizzes[quizIndex] || !Array.isArray(globalQuizzes[quizIndex].questions)) {
console.error(`Invalid quiz index or data for submission: ${quizIndex}`);
alert("Error: Could not submit quiz data.");
return;
}
const quiz = globalQuizzes[quizIndex];
const questionsArea = buttonElement.closest('.quiz-questions-area'); // Find parent question area
const scoreArea = document.getElementById(`score-area-${quizIndex}`);
if (!questionsArea || !scoreArea) {
console.error("Could not find questions or score area for quiz index:", quizIndex);
alert("Error submitting quiz. Please try again.");
return;
}
let score = 0;
let allAnswered = true;
// Filter out potentially invalid questions from the quiz data before counting/iterating
const validQuestions = quiz.questions.filter(q => q && typeof q.questionText !== 'undefined');
const totalValidQuestions = validQuestions.length;
// Clear previous feedback before evaluating
questionsArea.querySelectorAll('.feedback-mark, .correct-answer-text').forEach(el => el.remove());
questionsArea.querySelectorAll('.correct-answer-highlight, .incorrect-choice-selected, .incorrect-answer-input').forEach(el => {
el.classList.remove('correct-answer-highlight', 'incorrect-choice-selected', 'incorrect-answer-input');
if (el.tagName === 'LABEL') {
el.style.textDecoration = 'none';
}
});
// Re-enable inputs temporarily for validation, disable later
questionsArea.querySelectorAll('input[type="text"], input[type="radio"]').forEach(input => input.disabled = false);
// Iterate through each *valid* question defined in the quiz data
validQuestions.forEach((question, qIndex) => {
let studentAnswer = null;
const correctAnswer = question.correctAnswer;
// Find the corresponding question element in the DOM based on its position among valid questions
const questionElement = questionsArea.querySelector(`.quiz-question:nth-child(${qIndex + 1})`); // Assumes DOM order matches validQuestions order
const questionNumberElement = questionElement ? questionElement.querySelector('p strong') : null;
if (!questionElement || !questionNumberElement) {
console.warn(`Could not find question element in DOM for valid qIndex ${qIndex} during submission in quiz ${quizIndex}`);
allAnswered = false; // Consider it unanswered if the element is missing
return; // Skip this question
}
// --- Get Student Answer and Validate ---
if (quiz.quizType === 'identification') {
const inputElement = questionElement.querySelector(`input[type="text"]`);
if (!inputElement) {
console.warn(`Missing input element for identification qIndex ${qIndex}`);
allAnswered = false; return;
}
studentAnswer = inputElement.value.trim();
if (studentAnswer === "") allAnswered = false;
else if (correctAnswer !== undefined && correctAnswer !== null && studentAnswer.toLowerCase() === String(correctAnswer).toLowerCase()) score++;
} else if (quiz.quizType === 'multiple-choice') {
const checkedRadio = questionElement.querySelector(`input[type="radio"]:checked`);
if (!checkedRadio) allAnswered = false;
else {
studentAnswer = checkedRadio.value;
if (studentAnswer === String(correctAnswer)) score++;
}
}
}); // End of validQuestions.forEach for validation
// --- Check if all questions were answered ---
if (!allAnswered) {
alert('Please answer all questions before submitting.');
questionsArea.querySelectorAll('input[type="text"], input[type="radio"]').forEach(input => input.disabled = false); // Re-enable
return; // Stop submission
}
// --- If all answered, proceed to show feedback and save ---
// Disable all inputs now that submission is final
questionsArea.querySelectorAll('input[type="text"], input[type="radio"]').forEach(input => input.disabled = true);
// Iterate again through valid questions to apply feedback styles
validQuestions.forEach((question, qIndex) => {
const questionElement = questionsArea.querySelector(`.quiz-question:nth-child(${qIndex + 1})`);
if (!questionElement) return;
const questionNumberElement = questionElement.querySelector('p strong');
const correctAnswer = question.correctAnswer;
let studentAnswer = null;
let isCorrect = false;
if (quiz.quizType === 'identification') {
const inputElement = questionElement.querySelector(`input[type="text"]`);
studentAnswer = inputElement ? inputElement.value.trim() : '';
isCorrect = (correctAnswer !== undefined && correctAnswer !== null && studentAnswer.toLowerCase() === String(correctAnswer).toLowerCase());
if (!isCorrect && inputElement) {
inputElement.classList.add('incorrect-answer-input');
if (correctAnswer !== undefined && correctAnswer !== null) {
const correctAnswerSpan = document.createElement('span');
correctAnswerSpan.classList.add('correct-answer-text');
correctAnswerSpan.textContent = `Correct answer: ${correctAnswer}`;
inputElement.parentNode.insertBefore(correctAnswerSpan, inputElement.nextSibling);
}
}
} else if (quiz.quizType === 'multiple-choice') {
const radioButtons = questionElement.querySelectorAll(`input[type="radio"]`);
const checkedRadio = questionElement.querySelector(`input[type="radio"]:checked`);
studentAnswer = checkedRadio ? checkedRadio.value : null;
isCorrect = (studentAnswer === String(correctAnswer));
radioButtons.forEach(radio => {
const choiceLabel = radio.closest('label');
if (!choiceLabel) return;
if (radio.value === String(correctAnswer)) choiceLabel.classList.add('correct-answer-highlight');
if (radio.checked && !isCorrect) {
if (!loggedInUsername || !loggedInRole || loggedInRole !== 'student') {
alert('Access denied. Please log in as a student.');
// Ensure login.js is loaded if redirecting to index.html which might be the login page
window.location.href = 'index.html'; // Redirect if not a student
return;
}
// --- End Login Check ---
// --- DOM Elements ---
const createdLessonsDiv = document.getElementById('createdLessons');
const createdQuizzesDiv = document.getElementById('createdQuizzes');
const quizCompletionStatusDiv = document.getElementById('quiz-completion-status');
// Analytics Elements
const analyticsBtn = document.getElementById('analyticsBtn');
const analyticsModal = document.getElementById('analyticsModal');
const lastAccessedSpan = document.getElementById('lastAccessed');
const lessonProgressListUl = document.getElementById('lessonProgressList'); // Added for modal
const quizScoresListUl = document.getElementById('quizScoresList');
const overallGradeSpan = document.getElementById('overallGrade');
// Modal Navigation Elements
const modalDashboardBtn = document.getElementById('modalDashboardBtn');
const modalHomeBtn = document.getElementById('modalHomeBtn');
const modalLogoutBtn = document.getElementById('modalLogoutBtn');
// --- Storage Keys ---
const storageKeys = {
users: 'users', // Key for the main user object containing all users
lessons: 'createdLessons', // Global lessons created by teacher
quizzes: 'createdQuizzes', // Global quizzes created by teacher
lastAccessed: `studentLastAccessed_${loggedInUsername}` // Student-specific last access
};
// --- Utility Functions (Ensure login.js is included for getUsers/saveUsers) ---
// getUsers() and saveUsers() are assumed to be available from login.js
// --- Get Current Student Data ---
// Helper to safely get the current student's data object from the main users store
function getCurrentStudentData() {
const users = getUsers();
const studentData = users[loggedInUsername];
if (studentData && studentData.role === 'student') {
// Initialize progress arrays if they don't exist (robustness)
if (!Array.isArray(studentData.quizScores)) {
studentData.quizScores = [];
}
if (!Array.isArray(studentData.lessonProgress)) {
studentData.lessonProgress = [];
}
return studentData;
}
console.error("Could not find valid student data for username:", loggedInUsername);
// Maybe redirect or show error if student data is corrupted/missing
alert("Error: Could not load your data. Please try logging in again.");
logout(); // Log out if data is missing
return null; // Return null to indicate failure
}
// --- Track Last Accessed ---
function recordLastAccessed() {
const now = new Date();
localStorage.setItem(storageKeys.lastAccessed, now.toISOString());
}
// --- Lesson Display & Tracking ---
function updateCreatedLessons() {
const globalLessons = JSON.parse(localStorage.getItem(storageKeys.lessons) || '[]');
const studentData = getCurrentStudentData();
if (!studentData) return; // Stop if student data failed to load
createdLessonsDiv.innerHTML = ''; // Clear existing lessons
if (globalLessons.length === 0) {
createdLessonsDiv.innerHTML = '<p>No lessons available.</p>';
return;
}
globalLessons.forEach((lesson, index) => {
// Basic validation
const title = lesson.title || 'Untitled Lesson';
const content = lesson.content || 'No content.';
const lessonId = `lesson-${index}`; // Simple ID based on index
// Check student's progress for this lesson
const progressEntry = studentData.lessonProgress.find(p => p.lessonName === title);
const isRead = progressEntry && (progressEntry.status === 'read' || progressEntry.status === 'completed');
const lessonDiv = document.createElement('div');
lessonDiv.classList.add('lesson');
lessonDiv.innerHTML = `
<h4>${title}</h4>
<p>${content}</p>
<button class="mark-read-btn" data-lesson-title="${title}" ${isRead ? 'disabled' : ''}>
${isRead ? 'Read' : 'Mark as Read'}
</button>
`;
createdLessonsDiv.appendChild(lessonDiv);
});
}
// --- Event Delegation for Lesson Buttons ---
createdLessonsDiv.addEventListener('click', (event) => {
const target = event.target;
if (target.classList.contains('mark-read-btn') && !target.disabled) {
const lessonTitle = target.dataset.lessonTitle;
markLessonAsRead(lessonTitle, target);
}
});
function markLessonAsRead(lessonTitle, buttonElement) {
const users = getUsers();
const studentData = users[loggedInUsername]; // Get current data again
if (!studentData) {
alert("Error updating lesson status.");
return;
}
// Ensure lessonProgress array exists
if (!Array.isArray(studentData.lessonProgress)) {
studentData.lessonProgress = [];
}
// Find the progress entry or create it
let progressEntry = studentData.lessonProgress.find(p => p.lessonName === lessonTitle);
if (progressEntry) {
progressEntry.status = 'read'; // Or 'completed' if you prefer
} else {
// Add new entry if it doesn't exist
studentData.lessonProgress.push({ lessonName: lessonTitle, status: 'read' });
}
// Save the updated users object
saveUsers(users);
// Update the button state
buttonElement.disabled = true;
buttonElement.textContent = 'Read';
console.log(`Lesson "${lessonTitle}" marked as read for ${loggedInUsername}`);
// Optionally, update the analytics modal if it's open
if (analyticsModal.style.display === 'block') {
populateAnalyticsData();
}
}
// --- Quiz Display and Taking Logic ---
function updateCreatedQuizzes() {
console.log("Updating created quizzes display for student:", loggedInUsername);
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
const studentData = getCurrentStudentData();
if (!studentData) return; // Stop if student data failed to load
createdQuizzesDiv.innerHTML = ''; // Clear previous content
if (globalQuizzes.length === 0) {
createdQuizzesDiv.innerHTML = '<p>No quizzes available.</p>';
checkAndEnforceQuizCompletion(); // Check even if no quizzes
return;
}
globalQuizzes.forEach((quiz, index) => {
// Basic validation of quiz structure
if (!quiz || typeof quiz.quizTitle === 'undefined' || !Array.isArray(quiz.questions)) {
console.warn(`Skipping invalid quiz data at index ${index}:`, quiz);
return; // Skip rendering this invalid quiz
}
const quizTitle = quiz.quizTitle || 'Untitled Quiz';
// Check if this student has taken this quiz by looking in their main data
const resultData = studentData.quizScores.find(score => score.quizName === quizTitle);
const hasTaken = !!resultData; // True if resultData is found
console.log(`Quiz Index: ${index}, Title: ${quizTitle}, Has Taken: ${hasTaken}`);
const quizDiv = document.createElement('div');
quizDiv.classList.add('quiz');
quizDiv.id = `quiz-${index}`;
quizDiv.innerHTML = `
<h4>${quizTitle}</h4>
<p>Type: ${quiz.quizType || 'N/A'}</p>
<p>Number of questions: ${quiz.questions.length}</p>
<button class="btn take-quiz-btn" data-quiz-index="${index}" ${hasTaken ? 'disabled' : ''}>${hasTaken ? 'Quiz Taken' : 'Take Quiz'}</button>
<div class="quiz-questions-area" id="quiz-area-${index}" style="display: none;"></div>
<div class="quiz-score-area" id="score-area-${index}" style="display: ${hasTaken ? 'block' : 'none'};">
${hasTaken && resultData ? `<h3>Your Score: ${resultData.score}%</h3>` : ''}
</div>
`;
createdQuizzesDiv.appendChild(quizDiv);
});
checkAndEnforceQuizCompletion(); // Check completion after rendering quizzes
}
// --- Event Delegation for Quiz Buttons ---
createdQuizzesDiv.addEventListener('click', (event) => {
const target = event.target; // The element that was actually clicked
// Check if the clicked element is a "take-quiz-btn" or inside one
const takeButton = target.closest('.take-quiz-btn');
if (takeButton && !takeButton.disabled) {
console.log("Take Quiz button clicked via delegation for index:", takeButton.dataset.quizIndex);
startQuiz(takeButton); // Pass the button element itself
return; // Stop further processing for this click
}
// Check if the clicked element is a "submit-quiz-btn" or inside one
const submitButton = target.closest('.submit-quiz-btn');
if (submitButton && !submitButton.disabled) {
console.log("Submit Quiz button clicked via delegation for index:", submitButton.dataset.quizIndex);
submitQuiz(submitButton); // Pass the button element itself
return; // Stop further processing for this click
}
});
function startQuiz(buttonElement) { // Accepts the button element directly
const quizIndex = buttonElement.dataset.quizIndex;
console.log('startQuiz called for index:', quizIndex);
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
// Validate quiz index and data
if (quizIndex === undefined || !globalQuizzes[quizIndex] || !Array.isArray(globalQuizzes[quizIndex].questions)) {
console.error(`Invalid quiz index or data for index ${quizIndex}`);
alert("Error: Could not load quiz data.");
return;
}
const quiz = globalQuizzes[quizIndex];
const questionsArea = document.getElementById(`quiz-area-${quizIndex}`);
const scoreArea = document.getElementById(`score-area-${quizIndex}`);
const takeQuizButton = buttonElement; // The button that was clicked
if (!questionsArea || !scoreArea) {
console.error(`Could not find questionsArea or scoreArea for quiz index ${quizIndex}`);
alert("Error: Could not display quiz.");
return;
}
// Hide other open quiz forms if any, and show their "Take Quiz" buttons
document.querySelectorAll('.quiz-questions-area').forEach(area => {
if (area.id !== `quiz-area-${quizIndex}`) {
area.style.display = 'none';
const otherQuizIndex = area.id.split('-')[2];
const otherTakeButton = document.querySelector(`.take-quiz-btn[data-quiz-index="${otherQuizIndex}"]`);
// Check if the other quiz has been taken using the main student data
const studentData = getCurrentStudentData();
const otherQuizTitle = globalQuizzes[otherQuizIndex]?.quizTitle;
const otherQuizTaken = studentData?.quizScores.some(s => s.quizName === otherQuizTitle);
if (otherTakeButton && !otherQuizTaken) { // Only show if not already taken
otherTakeButton.style.display = 'inline-block';
}
}
});
questionsArea.innerHTML = ''; // Clear previous content if any
scoreArea.innerHTML = ''; // Clear score area
scoreArea.style.display = 'none'; // Hide score area
questionsArea.style.display = 'block'; // Show questions area
takeQuizButton.style.display = 'none'; // Hide the clicked "Take Quiz" button
quiz.questions.forEach((question, qIndex) => {
// Basic validation of question structure
if (!question || typeof question.questionText === 'undefined') {
console.warn(`Skipping invalid question data at qIndex ${qIndex} for quiz ${quizIndex}:`, question);
return; // Skip rendering invalid question
}
const questionDiv = document.createElement('div');
questionDiv.classList.add('quiz-question');
let answerHtml = '';
if (quiz.quizType === 'identification') {
answerHtml = `<label for="answer-${quizIndex}-${qIndex}">Your Answer:</label><input type="text" id="answer-${quizIndex}-${qIndex}" name="answer-${quizIndex}-${qIndex}" required>`;
} else if (quiz.quizType === 'multiple-choice') {
answerHtml = '<div class="choices">';
const choices = Array.isArray(question.choices) ? question.choices : [];
if (choices.length === 0) {
console.warn(`No choices found for multiple-choice question qIndex ${qIndex} in quiz ${quizIndex}`);
}
// Shuffle choices before displaying (optional, but good practice)
const shuffledChoices = [...choices].sort(() => Math.random() - 0.5);
shuffledChoices.forEach((choice, cIndex) => {
// Ensure choice text is displayed correctly, even if null/undefined
const choiceText = (choice !== null && choice !== undefined) ? choice : `Choice ${cIndex + 1}`;
// Use the original choice value for the radio button's value attribute
const choiceValue = (choice !== null && choice !== undefined) ? choice : `__choice_${cIndex}__`; // Handle potential null/undefined values
answerHtml += `<label><input type="radio" name="answer-${quizIndex}-${qIndex}" value="${choiceValue}" id="choice-${quizIndex}-${qIndex}-${cIndex}" required> ${choiceText}</label>`;
});
answerHtml += '</div>';
} else {
console.warn(`Unsupported quiz type "${quiz.quizType}" for question qIndex ${qIndex} in quiz ${quizIndex}`);
answerHtml = '<p><em>Unsupported question type.</em></p>';
}
questionDiv.innerHTML = `<p><strong>${qIndex + 1}. ${question.questionText}</strong></p>${answerHtml}`;
questionsArea.appendChild(questionDiv);
});
// Add submit button only if valid questions were rendered
if (questionsArea.children.length > 0 && !questionsArea.querySelector('.submit-quiz-btn')) {
const submitButton = document.createElement('button');
submitButton.textContent = 'Submit Quiz';
submitButton.classList.add('btn', 'submit-quiz-btn');
submitButton.dataset.quizIndex = quizIndex;
// Listener is handled by delegation, no need to add here
questionsArea.appendChild(submitButton);
} else if (questionsArea.children.length === 0) {
// Handle case where a quiz has no valid questions
questionsArea.innerHTML = '<p><em>No valid questions found for this quiz. Cannot submit.</em></p>';
// Optionally re-display the "Take Quiz" button or show a message
takeQuizButton.style.display = 'inline-block'; // Show button again
takeQuizButton.disabled = true; // Disable it as it's problematic
takeQuizButton.textContent = 'Quiz Error';
}
}
function submitQuiz(buttonElement) { // Accepts the button element directly
const quizIndex = buttonElement.dataset.quizIndex;
console.log('submitQuiz called for index:', quizIndex);
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
// Validate quiz index and data
if (quizIndex === undefined || !globalQuizzes[quizIndex] || !Array.isArray(globalQuizzes[quizIndex].questions)) {
console.error(`Invalid quiz index or data for submission: ${quizIndex}`);
alert("Error: Could not submit quiz data.");
return;
}
const quiz = globalQuizzes[quizIndex];
const questionsArea = buttonElement.closest('.quiz-questions-area'); // Find parent question area
const scoreArea = document.getElementById(`score-area-${quizIndex}`);
if (!questionsArea || !scoreArea) {
console.error("Could not find questions or score area for quiz index:", quizIndex);
alert("Error submitting quiz. Please try again.");
return;
}
let score = 0;
let allAnswered = true;
// Filter out potentially invalid questions from the quiz data before counting/iterating
const validQuestions = quiz.questions.filter(q => q && typeof q.questionText !== 'undefined');
const totalValidQuestions = validQuestions.length;
// Clear previous feedback before evaluating
questionsArea.querySelectorAll('.feedback-mark, .correct-answer-text').forEach(el => el.remove());
questionsArea.querySelectorAll('.correct-answer-highlight, .incorrect-choice-selected, .incorrect-answer-input').forEach(el => {
el.classList.remove('correct-answer-highlight', 'incorrect-choice-selected', 'incorrect-answer-input');
if (el.tagName === 'LABEL') {
el.style.textDecoration = 'none';
}
});
// Re-enable inputs temporarily for validation, disable later
questionsArea.querySelectorAll('input[type="text"], input[type="radio"]').forEach(input => input.disabled = false);
// Iterate through each *valid* question defined in the quiz data
validQuestions.forEach((question, qIndex) => {
let studentAnswer = null;
const correctAnswer = question.correctAnswer;
// Find the corresponding question element in the DOM based on its position among valid questions
const questionElement = questionsArea.querySelector(`.quiz-question:nth-child(${qIndex + 1})`); // Assumes DOM order matches validQuestions order
const questionNumberElement = questionElement ? questionElement.querySelector('p strong') : null;
if (!questionElement || !questionNumberElement) {
console.warn(`Could not find question element in DOM for valid qIndex ${qIndex} during submission in quiz ${quizIndex}`);
allAnswered = false; // Consider it unanswered if the element is missing
return; // Skip this question
}
// --- Get Student Answer and Validate ---
if (quiz.quizType === 'identification') {
const inputElement = questionElement.querySelector(`input[type="text"]`);
if (!inputElement) {
console.warn(`Missing input element for identification qIndex ${qIndex}`);
allAnswered = false; return;
}
studentAnswer = inputElement.value.trim();
if (studentAnswer === "") allAnswered = false;
else if (correctAnswer !== undefined && correctAnswer !== null && studentAnswer.toLowerCase() === String(correctAnswer).toLowerCase()) score++;
} else if (quiz.quizType === 'multiple-choice') {
const checkedRadio = questionElement.querySelector(`input[type="radio"]:checked`);
if (!checkedRadio) allAnswered = false;
else {
studentAnswer = checkedRadio.value;
if (studentAnswer === String(correctAnswer)) score++;
}
}
}); // End of validQuestions.forEach for validation
// --- Check if all questions were answered ---
if (!allAnswered) {
alert('Please answer all questions before submitting.');
questionsArea.querySelectorAll('input[type="text"], input[type="radio"]').forEach(input => input.disabled = false); // Re-enable
return; // Stop submission
}
// --- If all answered, proceed to show feedback and save ---
// Disable all inputs now that submission is final
questionsArea.querySelectorAll('input[type="text"], input[type="radio"]').forEach(input => input.disabled = true);
// Iterate again through valid questions to apply feedback styles
validQuestions.forEach((question, qIndex) => {
const questionElement = questionsArea.querySelector(`.quiz-question:nth-child(${qIndex + 1})`);
if (!questionElement) return;
const questionNumberElement = questionElement.querySelector('p strong');
const correctAnswer = question.correctAnswer;
let studentAnswer = null;
let isCorrect = false;
if (quiz.quizType === 'identification') {
const inputElement = questionElement.querySelector(`input[type="text"]`);
studentAnswer = inputElement ? inputElement.value.trim() : '';
isCorrect = (correctAnswer !== undefined && correctAnswer !== null && studentAnswer.toLowerCase() === String(correctAnswer).toLowerCase());
if (!isCorrect && inputElement) {
inputElement.classList.add('incorrect-answer-input');
if (correctAnswer !== undefined && correctAnswer !== null) {
const correctAnswerSpan = document.createElement('span');
correctAnswerSpan.classList.add('correct-answer-text');
correctAnswerSpan.textContent = `Correct answer: ${correctAnswer}`;
inputElement.parentNode.insertBefore(correctAnswerSpan, inputElement.nextSibling);
}
}
} else if (quiz.quizType === 'multiple-choice') {
const radioButtons = questionElement.querySelectorAll(`input[type="radio"]`);
const checkedRadio = questionElement.querySelector(`input[type="radio"]:checked`);
studentAnswer = checkedRadio ? checkedRadio.value : null;
isCorrect = (studentAnswer === String(correctAnswer));
radioButtons.forEach(radio => {
const choiceLabel = radio.closest('label');
if (!choiceLabel) return;
if (radio.value === String(correctAnswer)) choiceLabel.classList.add('correct-answer-highlight');
if (radio.checked && !isCorrect) {
Okay, here is the complete student.js code, incorporating the integration with the parent dashboard data stored in the main user object.
javascript document.addEventListener('DOMContentLoaded', () => { // --- Check if user is logged in --- const loggedInUsername = sessionStorage.getItem('loggedInUsername'); const loggedInRole = sessionStorage.getItem('loggedInRole');
if (!loggedInUsername || !loggedInRole || loggedInRole !== 'student') {
alert('Access denied. Please log in as a student.');
// Ensure login.js is loaded if redirecting to index.html which might be the login page
window.location.href = 'index.html'; // Redirect if not a student
return;
}
// --- End Login Check ---
// --- DOM Elements ---
const createdLessonsDiv = document.getElementById('createdLessons');
const createdQuizzesDiv = document.getElementById('createdQuizzes');
const quizCompletionStatusDiv = document.getElementById('quiz-completion-status');
// Analytics Elements
const analyticsBtn = document.getElementById('analyticsBtn');
const analyticsModal = document.getElementById('analyticsModal');
const lastAccessedSpan = document.getElementById('lastAccessed');
const lessonProgressListUl = document.getElementById('lessonProgressList'); // Added for modal
const quizScoresListUl = document.getElementById('quizScoresList');
const overallGradeSpan = document.getElementById('overallGrade');
// Modal Navigation Elements
const modalDashboardBtn = document.getElementById('modalDashboardBtn');
const modalHomeBtn = document.getElementById('modalHomeBtn');
const modalLogoutBtn = document.getElementById('modalLogoutBtn');
// --- Storage Keys ---
const storageKeys = {
users: 'users', // Key for the main user object containing all users
lessons: 'createdLessons', // Global lessons created by teacher
quizzes: 'createdQuizzes', // Global quizzes created by teacher
lastAccessed: `studentLastAccessed_${loggedInUsername}` // Student-specific last access
};
// --- Utility Functions (Ensure login.js is included for getUsers/saveUsers) ---
// getUsers() and saveUsers() are assumed to be available from login.js
if (typeof getUsers !== 'function' || typeof saveUsers !== 'function') {
console.error("Error: getUsers or saveUsers function not found. Make sure login.js is included before student.js");
alert("A critical error occurred. Please try refreshing the page or logging in again.");
// Optionally redirect or disable functionality
return;
}
// --- Get Current Student Data ---
// Helper to safely get the current student's data object from the main users store
function getCurrentStudentData() {
const users = getUsers();
const studentData = users[loggedInUsername];
if (studentData && studentData.role === 'student') {
// Initialize progress arrays if they don't exist (robustness)
if (!Array.isArray(studentData.quizScores)) {
studentData.quizScores = [];
}
if (!Array.isArray(studentData.lessonProgress)) {
studentData.lessonProgress = [];
}
return studentData;
}
console.error("Could not find valid student data for username:", loggedInUsername);
// Maybe redirect or show error if student data is corrupted/missing
alert("Error: Could not load your data. Please try logging in again.");
logout(); // Log out if data is missing
return null; // Return null to indicate failure
}
// --- Track Last Accessed ---
function recordLastAccessed() {
const now = new Date();
localStorage.setItem(storageKeys.lastAccessed, now.toISOString());
}
// --- Lesson Display & Tracking ---
function updateCreatedLessons() {
const globalLessons = JSON.parse(localStorage.getItem(storageKeys.lessons) || '[]');
const studentData = getCurrentStudentData();
if (!studentData) return; // Stop if student data failed to load
createdLessonsDiv.innerHTML = ''; // Clear existing lessons
if (globalLessons.length === 0) {
createdLessonsDiv.innerHTML = '<p>No lessons available.</p>';
return;
}
globalLessons.forEach((lesson, index) => {
// Basic validation
const title = lesson.title || 'Untitled Lesson';
const content = lesson.content || 'No content.';
const lessonId = `lesson-${index}`; // Simple ID based on index
// Check student's progress for this lesson
const progressEntry = studentData.lessonProgress.find(p => p.lessonName === title);
const isRead = progressEntry && (progressEntry.status === 'read' || progressEntry.status === 'completed'); // Consider both 'read' and 'completed' as read
const lessonDiv = document.createElement('div');
lessonDiv.classList.add('lesson');
lessonDiv.innerHTML = `
<h4>${title}</h4>
<p>${content}</p>
<button class="mark-read-btn" data-lesson-title="${title}" ${isRead ? 'disabled' : ''}>
${isRead ? 'Read' : 'Mark as Read'}
</button>
`;
createdLessonsDiv.appendChild(lessonDiv);
});
}
// --- Event Delegation for Lesson Buttons ---
createdLessonsDiv.addEventListener('click', (event) => {
const target = event.target;
if (target.classList.contains('mark-read-btn') && !target.disabled) {
const lessonTitle = target.dataset.lessonTitle;
markLessonAsRead(lessonTitle, target);
}
});
function markLessonAsRead(lessonTitle, buttonElement) {
const users = getUsers();
const studentData = users[loggedInUsername]; // Get current data again
if (!studentData) {
alert("Error updating lesson status.");
return;
}
// Ensure lessonProgress array exists
if (!Array.isArray(studentData.lessonProgress)) {
studentData.lessonProgress = [];
}
// Find the progress entry or create it
let progressEntry = studentData.lessonProgress.find(p => p.lessonName === lessonTitle);
if (progressEntry) {
progressEntry.status = 'read'; // Update status to 'read'
} else {
// Add new entry if it doesn't exist
studentData.lessonProgress.push({ lessonName: lessonTitle, status: 'read' });
}
// Save the updated users object
saveUsers(users);
// Update the button state
buttonElement.disabled = true;
buttonElement.textContent = 'Read';
console.log(`Lesson "${lessonTitle}" marked as read for ${loggedInUsername}`);
// Optionally, update the analytics modal if it's open
if (analyticsModal.style.display === 'block') {
populateAnalyticsData();
}
}
// --- Quiz Display and Taking Logic ---
function updateCreatedQuizzes() {
console.log("Updating created quizzes display for student:", loggedInUsername);
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
const studentData = getCurrentStudentData();
if (!studentData) return; // Stop if student data failed to load
createdQuizzesDiv.innerHTML = ''; // Clear previous content
if (globalQuizzes.length === 0) {
createdQuizzesDiv.innerHTML = '<p>No quizzes available.</p>';
checkAndEnforceQuizCompletion(); // Check even if no quizzes
return;
}
globalQuizzes.forEach((quiz, index) => {
// Basic validation of quiz structure
if (!quiz || typeof quiz.quizTitle === 'undefined' || !Array.isArray(quiz.questions)) {
console.warn(`Skipping invalid quiz data at index ${index}:`, quiz);
return; // Skip rendering this invalid quiz
}
const quizTitle = quiz.quizTitle || 'Untitled Quiz';
// Check if this student has taken this quiz by looking in their main data
const resultData = studentData.quizScores.find(score => score.quizName === quizTitle);
const hasTaken = !!resultData; // True if resultData is found
console.log(`Quiz Index: ${index}, Title: ${quizTitle}, Has Taken: ${hasTaken}`);
const quizDiv = document.createElement('div');
quizDiv.classList.add('quiz');
quizDiv.id = `quiz-${index}`;
quizDiv.innerHTML = `
<h4>${quizTitle}</h4>
<p>Type: ${quiz.quizType || 'N/A'}</p>
<p>Number of questions: ${quiz.questions.length}</p>
<button class="btn take-quiz-btn" data-quiz-index="${index}" ${hasTaken ? 'disabled' : ''}>${hasTaken ? 'Quiz Taken' : 'Take Quiz'}</button>
<div class="quiz-questions-area" id="quiz-area-${index}" style="display: none;"></div>
<div class="quiz-score-area" id="score-area-${index}" style="display: ${hasTaken ? 'block' : 'none'};">
${hasTaken && resultData ? `<h3>Your Score: ${resultData.score}%</h3>` : ''}
</div>
`;
createdQuizzesDiv.appendChild(quizDiv);
});
checkAndEnforceQuizCompletion(); // Check completion after rendering quizzes
}
// --- Event Delegation for Quiz Buttons ---
createdQuizzesDiv.addEventListener('click', (event) => {
const target = event.target; // The element that was actually clicked
// Check if the clicked element is a "take-quiz-btn" or inside one
const takeButton = target.closest('.take-quiz-btn');
if (takeButton && !takeButton.disabled) {
console.log("Take Quiz button clicked via delegation for index:", takeButton.dataset.quizIndex);
startQuiz(takeButton); // Pass the button element itself
return; // Stop further processing for this click
}
// Check if the clicked element is a "submit-quiz-btn" or inside one
const submitButton = target.closest('.submit-quiz-btn');
if (submitButton && !submitButton.disabled) {
console.log("Submit Quiz button clicked via delegation for index:", submitButton.dataset.quizIndex);
submitQuiz(submitButton); // Pass the button element itself
return; // Stop further processing for this click
}
});
function startQuiz(buttonElement) { // Accepts the button element directly
const quizIndex = buttonElement.dataset.quizIndex;
console.log('startQuiz called for index:', quizIndex);
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
// Validate quiz index and data
if (quizIndex === undefined || !globalQuizzes[quizIndex] || !Array.isArray(globalQuizzes[quizIndex].questions)) {
console.error(`Invalid quiz index or data for index ${quizIndex}`);
alert("Error: Could not load quiz data.");
return;
}
const quiz = globalQuizzes[quizIndex];
const questionsArea = document.getElementById(`quiz-area-${quizIndex}`);
const scoreArea = document.getElementById(`score-area-${quizIndex}`);
const takeQuizButton = buttonElement; // The button that was clicked
if (!questionsArea || !scoreArea) {
console.error(`Could not find questionsArea or scoreArea for quiz index ${quizIndex}`);
alert("Error: Could not display quiz.");
return;
}
// Hide other open quiz forms if any, and show their "Take Quiz" buttons
document.querySelectorAll('.quiz-questions-area').forEach(area => {
if (area.id !== `quiz-area-${quizIndex}`) {
area.style.display = 'none';
const otherQuizIndex = area.id.split('-')[2];
const otherTakeButton = document.querySelector(`.take-quiz-btn[data-quiz-index="${otherQuizIndex}"]`);
// Check if the other quiz has been taken using the main student data
const studentData = getCurrentStudentData();
const otherQuizTitle = globalQuizzes[otherQuizIndex]?.quizTitle;
const otherQuizTaken = studentData?.quizScores.some(s => s.quizName === otherQuizTitle);
if (otherTakeButton && !otherQuizTaken) { // Only show if not already taken
otherTakeButton.style.display = 'inline-block';
}
}
});
questionsArea.innerHTML = ''; // Clear previous content if any
scoreArea.innerHTML = ''; // Clear score area
scoreArea.style.display = 'none'; // Hide score area
questionsArea.style.display = 'block'; // Show questions area
takeQuizButton.style.display = 'none'; // Hide the clicked "Take Quiz" button
quiz.questions.forEach((question, qIndex) => {
// Basic validation of question structure
if (!question || typeof question.questionText === 'undefined') {
console.warn(`Skipping invalid question data at qIndex ${qIndex} for quiz ${quizIndex}:`, question);
return; // Skip rendering invalid question
}
const questionDiv = document.createElement('div');
questionDiv.classList.add('quiz-question');
let answerHtml = '';
if (quiz.quizType === 'identification') {
answerHtml = `<label for="answer-${quizIndex}-${qIndex}">Your Answer:</label><input type="text" id="answer-${quizIndex}-${qIndex}" name="answer-${quizIndex}-${qIndex}" required>`;
} else if (quiz.quizType === 'multiple-choice') {
answerHtml = '<div class="choices">';
const choices = Array.isArray(question.choices) ? question.choices : [];
if (choices.length === 0) {
console.warn(`No choices found for multiple-choice question qIndex ${qIndex} in quiz ${quizIndex}`);
}
// Shuffle choices before displaying (optional, but good practice)
const shuffledChoices = [...choices].sort(() => Math.random() - 0.5);
shuffledChoices.forEach((choice, cIndex) => {
// Ensure choice text is displayed correctly, even if null/undefined
const choiceText = (choice !== null && choice !== undefined) ? choice : `Choice ${cIndex + 1}`;
// Use the original choice value for the radio button's value attribute
const choiceValue = (choice !== null && choice !== undefined) ? choice : `__choice_${cIndex}__`; // Handle potential null/undefined values
answerHtml += `<label><input type="radio" name="answer-${quizIndex}-${qIndex}" value="${choiceValue}" id="choice-${quizIndex}-${qIndex}-${cIndex}" required> ${choiceText}</label>`;
});
answerHtml += '</div>';
} else {
console.warn(`Unsupported quiz type "${quiz.quizType}" for question qIndex ${qIndex} in quiz ${quizIndex}`);
answerHtml = '<p><em>Unsupported question type.</em></p>';
}
questionDiv.innerHTML = `<p><strong>${qIndex + 1}. ${question.questionText}</strong></p>${answerHtml}`;
questionsArea.appendChild(questionDiv);
});
// Add submit button only if valid questions were rendered
if (questionsArea.children.length > 0 && !questionsArea.querySelector('.submit-quiz-btn')) {
const submitButton = document.createElement('button');
submitButton.textContent = 'Submit Quiz';
submitButton.classList.add('btn', 'submit-quiz-btn');
submitButton.dataset.quizIndex = quizIndex;
// Listener is handled by delegation, no need to add here
questionsArea.appendChild(submitButton);
} else if (questionsArea.children.length === 0) {
// Handle case where a quiz has no valid questions
questionsArea.innerHTML = '<p><em>No valid questions found for this quiz. Cannot submit.</em></p>';
// Optionally re-display the "Take Quiz" button or show a message
takeQuizButton.style.display = 'inline-block'; // Show button again
takeQuizButton.disabled = true; // Disable it as it's problematic
takeQuizButton.textContent = 'Quiz Error';
}
}
function submitQuiz(buttonElement) { // Accepts the button element directly
const quizIndex = buttonElement.dataset.quizIndex;
console.log('submitQuiz called for index:', quizIndex);
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
// Validate quiz index and data
if (quizIndex === undefined || !globalQuizzes[quizIndex] || !Array.isArray(globalQuizzes[quizIndex].questions)) {
console.error(`Invalid quiz index or data for submission: ${quizIndex}`);
alert("Error: Could not submit quiz data.");
return;
}
const quiz = globalQuizzes[quizIndex];
const questionsArea = buttonElement.closest('.quiz-questions-area'); // Find parent question area
const scoreArea = document.getElementById(`score-area-${quizIndex}`);
if (!questionsArea || !scoreArea) {
console.error("Could not find questions or score area for quiz index:", quizIndex);
alert("Error submitting quiz. Please try again.");
return;
}
let score = 0;
let allAnswered = true;
// Filter out potentially invalid questions from the quiz data before counting/iterating
const validQuestions = quiz.questions.filter(q => q && typeof q.questionText !== 'undefined');
const totalValidQuestions = validQuestions.length;
// Clear previous feedback before evaluating
questionsArea.querySelectorAll('.feedback-mark, .correct-answer-text').forEach(el => el.remove());
questionsArea.querySelectorAll('.correct-answer-highlight, .incorrect-choice-selected, .incorrect-answer-input').forEach(el => {
el.classList.remove('correct-answer-highlight', 'incorrect-choice-selected', 'incorrect-answer-input');
if (el.tagName === 'LABEL') {
el.style.textDecoration = 'none';
}
});
// Re-enable inputs temporarily for validation, disable later
questionsArea.querySelectorAll('input[type="text"], input[type="radio"]').forEach(input => input.disabled = false);
// Iterate through each *valid* question defined in the quiz data
validQuestions.forEach((question, qIndex) => {
let studentAnswer = null;
const correctAnswer = question.correctAnswer;
// Find the corresponding question element in the DOM based on its position among valid questions
const questionElement = questionsArea.querySelector(`.quiz-question:nth-child(${qIndex + 1})`); // Assumes DOM order matches validQuestions order
const questionNumberElement = questionElement ? questionElement.querySelector('p strong') : null;
if (!questionElement || !questionNumberElement) {
console.warn(`Could not find question element in DOM for valid qIndex ${qIndex} during submission in quiz ${quizIndex}`);
allAnswered = false; // Consider it unanswered if the element is missing
return; // Skip this question
}
// --- Get Student Answer and Validate ---
if (quiz.quizType === 'identification') {
const inputElement = questionElement.querySelector(`input[type="text"]`);
if (!inputElement) {
console.warn(`Missing input element for identification qIndex ${qIndex}`);
allAnswered = false; return;
}
studentAnswer = inputElement.value.trim();
if (studentAnswer === "") allAnswered = false;
else if (correctAnswer !== undefined && correctAnswer !== null && studentAnswer.toLowerCase() === String(correctAnswer).toLowerCase()) score++;
} else if (quiz.quizType === 'multiple-choice') {
const checkedRadio = questionElement.querySelector(`input[type="radio"]:checked`);
if (!checkedRadio) allAnswered = false;
else {
studentAnswer = checkedRadio.value;
if (studentAnswer === String(correctAnswer)) score++;
}
}
}); // End of validQuestions.forEach for validation
// --- Check if all questions were answered ---
if (!allAnswered) {
alert('Please answer all questions before submitting.');
questionsArea.querySelectorAll('input[type="text"], input[type="radio"]').forEach(input => input.disabled = false); // Re-enable
return; // Stop submission
}
// --- If all answered, proceed to show feedback and save ---
// Disable all inputs now that submission is final
questionsArea.querySelectorAll('input[type="text"], input[type="radio"]').forEach(input => input.disabled = true);
// Iterate again through valid questions to apply feedback styles
validQuestions.forEach((question, qIndex) => {
const questionElement = questionsArea.querySelector(`.quiz-question:nth-child(${qIndex + 1})`);
if (!questionElement) return;
const questionNumberElement = questionElement.querySelector('p strong');
const correctAnswer = question.correctAnswer;
let studentAnswer = null;
let isCorrect = false; // Re-evaluate for feedback
if (quiz.quizType === 'identification') {
const inputElement = questionElement.querySelector(`input[type="text"]`);
studentAnswer = inputElement ? inputElement.value.trim() : '';
isCorrect = (correctAnswer !== undefined && correctAnswer !== null && studentAnswer.toLowerCase() === String(correctAnswer).toLowerCase());
if (!isCorrect && inputElement) {
inputElement.classList.add('incorrect-answer-input');
if (correctAnswer !== undefined && correctAnswer !== null) {
const correctAnswerSpan = document.createElement('span');
correctAnswerSpan.classList.add('correct-answer-text');
correctAnswerSpan.textContent = `Correct answer: ${correctAnswer}`;
inputElement.parentNode.insertBefore(correctAnswerSpan, inputElement.nextSibling);
}
}
} else if (quiz.quizType === 'multiple-choice') {
const radioButtons = questionElement.querySelectorAll(`input[type="radio"]`);
const checkedRadio = questionElement.querySelector(`input[type="radio"]:checked`);
studentAnswer = checkedRadio ? checkedRadio.value : null;
isCorrect = (studentAnswer === String(correctAnswer));
radioButtons.forEach(radio => {
const choiceLabel = radio.closest('label');
if (!choiceLabel) return;
// Highlight the correct answer's label
if (radio.value === String(correctAnswer)) {
choiceLabel.classList.add('correct-answer-highlight');
}
// If this radio was checked and it's incorrect, style it
if (radio.checked && !isCorrect) {
choiceLabel.classList.add('incorrect-choice-selected');
choiceLabel.style.textDecoration = 'line-through'; // Add line-through
}
});
}
// Add checkmark or cross
if (questionNumberElement) {
const feedbackMark = document.createElement('span');
feedbackMark.classList.add('feedback-mark');
feedbackMark.textContent = isCorrect ? '✓' : '✗';
feedbackMark.classList.add(isCorrect ? 'correct-mark' : 'incorrect-mark');
// Ensure no duplicate marks
const existingMark = questionNumberElement.querySelector('.feedback-mark');
if (existingMark) existingMark.remove();
questionNumberElement.appendChild(feedbackMark);
}
}); // End of feedback loop
// --- Display Final Score and Save ---
const percentageScore = totalValidQuestions > 0 ? Math.round((score / totalValidQuestions) * 100) : 0;
scoreArea.innerHTML = `<h3>Your Score: ${percentageScore}%</h3>`; // Show percentage
scoreArea.style.display = 'block';
buttonElement.disabled = true; // Disable the submit button permanently
buttonElement.textContent = 'Submitted';
// Find the original "Take Quiz" button again and ensure it's disabled and text updated
const originalTakeButton = document.querySelector(`.take-quiz-btn[data-quiz-index="${quizIndex}"]`);
if (originalTakeButton) {
originalTakeButton.disabled = true;
originalTakeButton.textContent = 'Quiz Taken';
originalTakeButton.style.display = 'inline-block'; // Ensure it's visible again
}
// Save the result (percentage score) to the main student data
saveQuizResult(quiz.quizTitle || 'Untitled Quiz', percentageScore);
checkAndEnforceQuizCompletion(); // Update analytics button status
}
function saveQuizResult(quizTitle, percentageScore) {
const users = getUsers();
const studentData = users[loggedInUsername];
if (!studentData) {
console.error("Cannot save quiz result: Student data not found.");
return;
}
// Ensure quizScores array exists
if (!Array.isArray(studentData.quizScores)) {
studentData.quizScores = [];
}
// Find if a result for this quiz already exists
const existingResultIndex = studentData.quizScores.findIndex(q => q.quizName === quizTitle);
const newResult = {
quizName: quizTitle,
score: percentageScore, // Store the percentage score
timestamp: new Date().toISOString()
};
if (existingResultIndex > -1) {
// Update existing result
studentData.quizScores[existingResultIndex] = newResult;
console.log("Quiz result updated for:", quizTitle, "for student:", loggedInUsername);
} else {
// Add new result
studentData.quizScores.push(newResult);
console.log("Quiz result saved for:", quizTitle, "for student:", loggedInUsername);
}
// Save the entire updated users object back to localStorage
saveUsers(users);
}
// --- Quiz Completion Enforcement ---
function checkAndEnforceQuizCompletion() {
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
const studentData = getCurrentStudentData();
if (!studentData) return; // Can't check if data is missing
let allValidQuizzesTaken = true;
// Filter global quizzes to only include valid ones
const validGlobalQuizzes = globalQuizzes.filter(quiz => quiz && typeof quiz.quizTitle !== 'undefined' && Array.isArray(quiz.questions));
if (validGlobalQuizzes.length === 0) {
allValidQuizzesTaken = true; // No valid quizzes to take
} else {
for (const quiz of validGlobalQuizzes) {
const quizTitle = quiz.quizTitle;
// Check if THIS student has taken this VALID quiz by looking in their data
const hasTaken = studentData.quizScores.some(score => score.quizName === quizTitle);
if (!hasTaken) {
allValidQuizzesTaken = false;
break; // Found an untaken, valid quiz
}
}
}
console.log("All valid quizzes taken by", loggedInUsername, ":", allValidQuizzesTaken);
if (allValidQuizzesTaken) {
quizCompletionStatusDiv.classList.add('hidden');
analyticsBtn.disabled = false; // Enable button
console.log("Analytics button enabled for", loggedInUsername);
} else {
quizCompletionStatusDiv.classList.remove('hidden');
analyticsBtn.disabled = true; // Disable button
console.log("Analytics button disabled for", loggedInUsername);
}
}
// --- Analytics Modal Logic ---
function openAnalyticsModal() {
checkAndEnforceQuizCompletion(); // Re-check before opening
if (analyticsBtn.disabled) {
alert("Please complete all available quizzes before viewing analytics.");
return;
}
populateAnalyticsData();
analyticsModal.style.display = 'block';
document.body.classList.add('modal-open'); // Prevent body scroll
}
function closeAnalyticsModal() {
analyticsModal.style.display = 'none';
document.body.classList.remove('modal-open'); // Allow body scroll again
}
function populateAnalyticsData() {
const studentData = getCurrentStudentData();
if (!studentData) return; // Stop if data is missing
// Get last accessed time for this student
const lastAccessedISO = localStorage.getItem(storageKeys.lastAccessed);
lastAccessedSpan.textContent = lastAccessedISO ? new Date(lastAccessedISO).toLocaleString() : 'Not recorded yet.';
// --- Populate Lesson Progress ---
lessonProgressListUl.innerHTML = ''; // Clear previous list
const globalLessons = JSON.parse(localStorage.getItem(storageKeys.lessons) || '[]');
if (globalLessons.length > 0) {
// Ensure studentData.lessonProgress exists
const lessonProgress = Array.isArray(studentData.lessonProgress) ? studentData.lessonProgress : [];
globalLessons.forEach(lesson => {
const title = lesson.title || 'Untitled Lesson';
const progressEntry = lessonProgress.find(p => p.lessonName === title);
const status = progressEntry ? progressEntry.status : 'not started';
const statusClass = `status-${status.toLowerCase().replace(' ', '-')}`;
const listItem = document.createElement('li');
listItem.innerHTML = `<strong>${title}:</strong> <span class="${statusClass}">${status}</span>`;
lessonProgressListUl.appendChild(listItem);
});
} else {
lessonProgressListUl.innerHTML = '<li>No lessons available.</li>';
}
// --- Populate Quiz Scores ---
quizScoresListUl.innerHTML = ''; // Clear previous list
const quizScores = studentData.quizScores; // Get scores directly from student data
if (quizScores.length === 0) {
quizScoresListUl.innerHTML = '<li>No quizzes taken yet.</li>';
overallGradeSpan.textContent = 'N/A';
return; // Exit early if no scores
}
let totalPercentage = 0;
let numberOfQuizzesTaken = 0;
// Sort scores alphabetically by quiz name for consistent display
quizScores.sort((a, b) => a.quizName.localeCompare(b.quizName));
quizScores.forEach(result => {
// Ensure result has expected properties before calculating
if (result && typeof result.quizName !== 'undefined' && typeof result.score === 'number') {
const listItem = document.createElement('li');
listItem.innerHTML = `<strong>${result.quizName}:</strong> ${result.score}%`;
quizScoresListUl.appendChild(listItem);
totalPercentage += result.score;
numberOfQuizzesTaken++;
} else {
console.warn(`Invalid score data found in student results:`, result);
}
});
// Calculate overall grade as the average of the percentage scores of quizzes taken
overallGradeSpan.textContent = numberOfQuizzesTaken > 0 ? `${(totalPercentage / numberOfQuizzesTaken).toFixed(1)}%` : 'N/A';
}
// --- Navigation Logic ---
function goToHomepage() {
// Determine homepage based on role if necessary, or assume a general one
window.location.href = 'homepage.html'; // Assuming homepage.html exists
}
function logout() {
sessionStorage.removeItem('loggedInUsername');
sessionStorage.removeItem('loggedInRole');
// Also clear student-specific items if desired
localStorage.removeItem(storageKeys.lastAccessed);
alert('You have been logged out.');
window.location.href = 'index.html'; // Redirect to login page (index.html)
}
// --- Event Listeners ---
if (analyticsBtn) analyticsBtn.addEventListener('click', openAnalyticsModal);
if (modalDashboardBtn) modalDashboardBtn.addEventListener('click', closeAnalyticsModal);
if (modalHomeBtn) modalHomeBtn.addEventListener('click', goToHomepage);
if (modalLogoutBtn) modalLogoutBtn.addEventListener('click', logout);
// --- Initial Load ---
recordLastAccessed(); // Record access time on load
updateCreatedLessons();
updateCreatedQuizzes(); // Initial population and completion check for this student
if (!loggedInUsername || !loggedInRole || loggedInRole !== 'student') {
alert('Access denied. Please log in as a student.');
// Ensure login.js is loaded if redirecting to index.html which might be the login page
window.location.href = 'index.html'; // Redirect if not a student
return;
}
// --- End Login Check ---
// --- DOM Elements ---
const createdLessonsDiv = document.getElementById('createdLessons');
const createdQuizzesDiv = document.getElementById('createdQuizzes');
const quizCompletionStatusDiv = document.getElementById('quiz-completion-status');
// Analytics Elements
const analyticsBtn = document.getElementById('analyticsBtn');
const analyticsModal = document.getElementById('analyticsModal');
const lastAccessedSpan = document.getElementById('lastAccessed');
const lessonProgressListUl = document.getElementById('lessonProgressList'); // Added for modal
const quizScoresListUl = document.getElementById('quizScoresList');
const overallGradeSpan = document.getElementById('overallGrade');
// Modal Navigation Elements
const modalDashboardBtn = document.getElementById('modalDashboardBtn');
const modalHomeBtn = document.getElementById('modalHomeBtn');
const modalLogoutBtn = document.getElementById('modalLogoutBtn');
// --- Storage Keys ---
const storageKeys = {
users: 'users', // Key for the main user object containing all users
lessons: 'createdLessons', // Global lessons created by teacher
quizzes: 'createdQuizzes', // Global quizzes created by teacher
lastAccessed: `studentLastAccessed_${loggedInUsername}` // Student-specific last access
};
// --- Utility Functions (Ensure login.js is included for getUsers/saveUsers) ---
// getUsers() and saveUsers() are assumed to be available from login.js
if (typeof getUsers !== 'function' || typeof saveUsers !== 'function') {
console.error("Error: getUsers or saveUsers function not found. Make sure login.js is included before student.js");
alert("A critical error occurred. Please try refreshing the page or logging in again.");
// Optionally redirect or disable functionality
return;
}
// --- Get Current Student Data ---
// Helper to safely get the current student's data object from the main users store
function getCurrentStudentData() {
const users = getUsers();
const studentData = users[loggedInUsername];
if (studentData && studentData.role === 'student') {
// Initialize progress arrays if they don't exist (robustness)
if (!Array.isArray(studentData.quizScores)) {
studentData.quizScores = [];
}
if (!Array.isArray(studentData.lessonProgress)) {
studentData.lessonProgress = [];
}
return studentData;
}
console.error("Could not find valid student data for username:", loggedInUsername);
// Maybe redirect or show error if student data is corrupted/missing
alert("Error: Could not load your data. Please try logging in again.");
logout(); // Log out if data is missing
return null; // Return null to indicate failure
}
// --- Track Last Accessed ---
function recordLastAccessed() {
const now = new Date();
localStorage.setItem(storageKeys.lastAccessed, now.toISOString());
}
// --- Lesson Display & Tracking ---
function updateCreatedLessons() {
const globalLessons = JSON.parse(localStorage.getItem(storageKeys.lessons) || '[]');
const studentData = getCurrentStudentData();
if (!studentData) return; // Stop if student data failed to load
createdLessonsDiv.innerHTML = ''; // Clear existing lessons
if (globalLessons.length === 0) {
createdLessonsDiv.innerHTML = '<p>No lessons available.</p>';
return;
}
globalLessons.forEach((lesson, index) => {
// Basic validation
const title = lesson.title || 'Untitled Lesson';
const content = lesson.content || 'No content.';
const lessonId = `lesson-${index}`; // Simple ID based on index
// Check student's progress for this lesson
const progressEntry = studentData.lessonProgress.find(p => p.lessonName === title);
const isRead = progressEntry && (progressEntry.status === 'read' || progressEntry.status === 'completed'); // Consider both 'read' and 'completed' as read
const lessonDiv = document.createElement('div');
lessonDiv.classList.add('lesson');
lessonDiv.innerHTML = `
<h4>${title}</h4>
<p>${content}</p>
<button class="mark-read-btn" data-lesson-title="${title}" ${isRead ? 'disabled' : ''}>
${isRead ? 'Read' : 'Mark as Read'}
</button>
`;
createdLessonsDiv.appendChild(lessonDiv);
});
}
// --- Event Delegation for Lesson Buttons ---
createdLessonsDiv.addEventListener('click', (event) => {
const target = event.target;
if (target.classList.contains('mark-read-btn') && !target.disabled) {
const lessonTitle = target.dataset.lessonTitle;
markLessonAsRead(lessonTitle, target);
}
});
function markLessonAsRead(lessonTitle, buttonElement) {
const users = getUsers();
const studentData = users[loggedInUsername]; // Get current data again
if (!studentData) {
alert("Error updating lesson status.");
return;
}
// Ensure lessonProgress array exists
if (!Array.isArray(studentData.lessonProgress)) {
studentData.lessonProgress = [];
}
// Find the progress entry or create it
let progressEntry = studentData.lessonProgress.find(p => p.lessonName === lessonTitle);
if (progressEntry) {
progressEntry.status = 'read'; // Update status to 'read'
} else {
// Add new entry if it doesn't exist
studentData.lessonProgress.push({ lessonName: lessonTitle, status: 'read' });
}
// Save the updated users object
saveUsers(users);
// Update the button state
buttonElement.disabled = true;
buttonElement.textContent = 'Read';
console.log(`Lesson "${lessonTitle}" marked as read for ${loggedInUsername}`);
// Optionally, update the analytics modal if it's open
if (analyticsModal.style.display === 'block') {
populateAnalyticsData();
}
}
// --- Quiz Display and Taking Logic ---
function updateCreatedQuizzes() {
console.log("Updating created quizzes display for student:", loggedInUsername);
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
const studentData = getCurrentStudentData();
if (!studentData) return; // Stop if student data failed to load
createdQuizzesDiv.innerHTML = ''; // Clear previous content
if (globalQuizzes.length === 0) {
createdQuizzesDiv.innerHTML = '<p>No quizzes available.</p>';
checkAndEnforceQuizCompletion(); // Check even if no quizzes
return;
}
globalQuizzes.forEach((quiz, index) => {
// Basic validation of quiz structure
if (!quiz || typeof quiz.quizTitle === 'undefined' || !Array.isArray(quiz.questions)) {
console.warn(`Skipping invalid quiz data at index ${index}:`, quiz);
return; // Skip rendering this invalid quiz
}
const quizTitle = quiz.quizTitle || 'Untitled Quiz';
// Check if this student has taken this quiz by looking in their main data
const resultData = studentData.quizScores.find(score => score.quizName === quizTitle);
const hasTaken = !!resultData; // True if resultData is found
console.log(`Quiz Index: ${index}, Title: ${quizTitle}, Has Taken: ${hasTaken}`);
const quizDiv = document.createElement('div');
quizDiv.classList.add('quiz');
quizDiv.id = `quiz-${index}`;
quizDiv.innerHTML = `
<h4>${quizTitle}</h4>
<p>Type: ${quiz.quizType || 'N/A'}</p>
<p>Number of questions: ${quiz.questions.length}</p>
<button class="btn take-quiz-btn" data-quiz-index="${index}" ${hasTaken ? 'disabled' : ''}>${hasTaken ? 'Quiz Taken' : 'Take Quiz'}</button>
<div class="quiz-questions-area" id="quiz-area-${index}" style="display: none;"></div>
<div class="quiz-score-area" id="score-area-${index}" style="display: ${hasTaken ? 'block' : 'none'};">
${hasTaken && resultData ? `<h3>Your Score: ${resultData.score}%</h3>` : ''}
</div>
`;
createdQuizzesDiv.appendChild(quizDiv);
});
checkAndEnforceQuizCompletion(); // Check completion after rendering quizzes
}
// --- Event Delegation for Quiz Buttons ---
createdQuizzesDiv.addEventListener('click', (event) => {
const target = event.target; // The element that was actually clicked
// Check if the clicked element is a "take-quiz-btn" or inside one
const takeButton = target.closest('.take-quiz-btn');
if (takeButton && !takeButton.disabled) {
console.log("Take Quiz button clicked via delegation for index:", takeButton.dataset.quizIndex);
startQuiz(takeButton); // Pass the button element itself
return; // Stop further processing for this click
}
// Check if the clicked element is a "submit-quiz-btn" or inside one
const submitButton = target.closest('.submit-quiz-btn');
if (submitButton && !submitButton.disabled) {
console.log("Submit Quiz button clicked via delegation for index:", submitButton.dataset.quizIndex);
submitQuiz(submitButton); // Pass the button element itself
return; // Stop further processing for this click
}
});
function startQuiz(buttonElement) { // Accepts the button element directly
const quizIndex = buttonElement.dataset.quizIndex;
console.log('startQuiz called for index:', quizIndex);
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
// Validate quiz index and data
if (quizIndex === undefined || !globalQuizzes[quizIndex] || !Array.isArray(globalQuizzes[quizIndex].questions)) {
console.error(`Invalid quiz index or data for index ${quizIndex}`);
alert("Error: Could not load quiz data.");
return;
}
const quiz = globalQuizzes[quizIndex];
const questionsArea = document.getElementById(`quiz-area-${quizIndex}`);
const scoreArea = document.getElementById(`score-area-${quizIndex}`);
const takeQuizButton = buttonElement; // The button that was clicked
if (!questionsArea || !scoreArea) {
console.error(`Could not find questionsArea or scoreArea for quiz index ${quizIndex}`);
alert("Error: Could not display quiz.");
return;
}
// Hide other open quiz forms if any, and show their "Take Quiz" buttons
document.querySelectorAll('.quiz-questions-area').forEach(area => {
if (area.id !== `quiz-area-${quizIndex}`) {
area.style.display = 'none';
const otherQuizIndex = area.id.split('-')[2];
const otherTakeButton = document.querySelector(`.take-quiz-btn[data-quiz-index="${otherQuizIndex}"]`);
// Check if the other quiz has been taken using the main student data
const studentData = getCurrentStudentData();
const otherQuizTitle = globalQuizzes[otherQuizIndex]?.quizTitle;
const otherQuizTaken = studentData?.quizScores.some(s => s.quizName === otherQuizTitle);
if (otherTakeButton && !otherQuizTaken) { // Only show if not already taken
otherTakeButton.style.display = 'inline-block';
}
}
});
questionsArea.innerHTML = ''; // Clear previous content if any
scoreArea.innerHTML = ''; // Clear score area
scoreArea.style.display = 'none'; // Hide score area
questionsArea.style.display = 'block'; // Show questions area
takeQuizButton.style.display = 'none'; // Hide the clicked "Take Quiz" button
quiz.questions.forEach((question, qIndex) => {
// Basic validation of question structure
if (!question || typeof question.questionText === 'undefined') {
console.warn(`Skipping invalid question data at qIndex ${qIndex} for quiz ${quizIndex}:`, question);
return; // Skip rendering invalid question
}
const questionDiv = document.createElement('div');
questionDiv.classList.add('quiz-question');
let answerHtml = '';
if (quiz.quizType === 'identification') {
answerHtml = `<label for="answer-${quizIndex}-${qIndex}">Your Answer:</label><input type="text" id="answer-${quizIndex}-${qIndex}" name="answer-${quizIndex}-${qIndex}" required>`;
} else if (quiz.quizType === 'multiple-choice') {
answerHtml = '<div class="choices">';
const choices = Array.isArray(question.choices) ? question.choices : [];
if (choices.length === 0) {
console.warn(`No choices found for multiple-choice question qIndex ${qIndex} in quiz ${quizIndex}`);
}
// Shuffle choices before displaying (optional, but good practice)
const shuffledChoices = [...choices].sort(() => Math.random() - 0.5);
shuffledChoices.forEach((choice, cIndex) => {
// Ensure choice text is displayed correctly, even if null/undefined
const choiceText = (choice !== null && choice !== undefined) ? choice : `Choice ${cIndex + 1}`;
// Use the original choice value for the radio button's value attribute
const choiceValue = (choice !== null && choice !== undefined) ? choice : `__choice_${cIndex}__`; // Handle potential null/undefined values
answerHtml += `<label><input type="radio" name="answer-${quizIndex}-${qIndex}" value="${choiceValue}" id="choice-${quizIndex}-${qIndex}-${cIndex}" required> ${choiceText}</label>`;
});
answerHtml += '</div>';
} else {
console.warn(`Unsupported quiz type "${quiz.quizType}" for question qIndex ${qIndex} in quiz ${quizIndex}`);
answerHtml = '<p><em>Unsupported question type.</em></p>';
}
questionDiv.innerHTML = `<p><strong>${qIndex + 1}. ${question.questionText}</strong></p>${answerHtml}`;
questionsArea.appendChild(questionDiv);
});
// Add submit button only if valid questions were rendered
if (questionsArea.children.length > 0 && !questionsArea.querySelector('.submit-quiz-btn')) {
const submitButton = document.createElement('button');
submitButton.textContent = 'Submit Quiz';
submitButton.classList.add('btn', 'submit-quiz-btn');
submitButton.dataset.quizIndex = quizIndex;
// Listener is handled by delegation, no need to add here
questionsArea.appendChild(submitButton);
} else if (questionsArea.children.length === 0) {
// Handle case where a quiz has no valid questions
questionsArea.innerHTML = '<p><em>No valid questions found for this quiz. Cannot submit.</em></p>';
// Optionally re-display the "Take Quiz" button or show a message
takeQuizButton.style.display = 'inline-block'; // Show button again
takeQuizButton.disabled = true; // Disable it as it's problematic
takeQuizButton.textContent = 'Quiz Error';
}
}
function submitQuiz(buttonElement) { // Accepts the button element directly
const quizIndex = buttonElement.dataset.quizIndex;
console.log('submitQuiz called for index:', quizIndex);
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
// Validate quiz index and data
if (quizIndex === undefined || !globalQuizzes[quizIndex] || !Array.isArray(globalQuizzes[quizIndex].questions)) {
console.error(`Invalid quiz index or data for submission: ${quizIndex}`);
alert("Error: Could not submit quiz data.");
return;
}
const quiz = globalQuizzes[quizIndex];
const questionsArea = buttonElement.closest('.quiz-questions-area'); // Find parent question area
const scoreArea = document.getElementById(`score-area-${quizIndex}`);
if (!questionsArea || !scoreArea) {
console.error("Could not find questions or score area for quiz index:", quizIndex);
alert("Error submitting quiz. Please try again.");
return;
}
let score = 0;
let allAnswered = true;
// Filter out potentially invalid questions from the quiz data before counting/iterating
const validQuestions = quiz.questions.filter(q => q && typeof q.questionText !== 'undefined');
const totalValidQuestions = validQuestions.length;
// Clear previous feedback before evaluating
questionsArea.querySelectorAll('.feedback-mark, .correct-answer-text').forEach(el => el.remove());
questionsArea.querySelectorAll('.correct-answer-highlight, .incorrect-choice-selected, .incorrect-answer-input').forEach(el => {
el.classList.remove('correct-answer-highlight', 'incorrect-choice-selected', 'incorrect-answer-input');
if (el.tagName === 'LABEL') {
el.style.textDecoration = 'none';
}
});
// Re-enable inputs temporarily for validation, disable later
questionsArea.querySelectorAll('input[type="text"], input[type="radio"]').forEach(input => input.disabled = false);
// Iterate through each *valid* question defined in the quiz data
validQuestions.forEach((question, qIndex) => {
let studentAnswer = null;
const correctAnswer = question.correctAnswer;
// Find the corresponding question element in the DOM based on its position among valid questions
const questionElement = questionsArea.querySelector(`.quiz-question:nth-child(${qIndex + 1})`); // Assumes DOM order matches validQuestions order
const questionNumberElement = questionElement ? questionElement.querySelector('p strong') : null;
if (!questionElement || !questionNumberElement) {
console.warn(`Could not find question element in DOM for valid qIndex ${qIndex} during submission in quiz ${quizIndex}`);
allAnswered = false; // Consider it unanswered if the element is missing
return; // Skip this question
}
// --- Get Student Answer and Validate ---
if (quiz.quizType === 'identification') {
const inputElement = questionElement.querySelector(`input[type="text"]`);
if (!inputElement) {
console.warn(`Missing input element for identification qIndex ${qIndex}`);
allAnswered = false; return;
}
studentAnswer = inputElement.value.trim();
if (studentAnswer === "") allAnswered = false;
else if (correctAnswer !== undefined && correctAnswer !== null && studentAnswer.toLowerCase() === String(correctAnswer).toLowerCase()) score++;
} else if (quiz.quizType === 'multiple-choice') {
const checkedRadio = questionElement.querySelector(`input[type="radio"]:checked`);
if (!checkedRadio) allAnswered = false;
else {
studentAnswer = checkedRadio.value;
if (studentAnswer === String(correctAnswer)) score++;
}
}
}); // End of validQuestions.forEach for validation
// --- Check if all questions were answered ---
if (!allAnswered) {
alert('Please answer all questions before submitting.');
questionsArea.querySelectorAll('input[type="text"], input[type="radio"]').forEach(input => input.disabled = false); // Re-enable
return; // Stop submission
}
// --- If all answered, proceed to show feedback and save ---
// Disable all inputs now that submission is final
questionsArea.querySelectorAll('input[type="text"], input[type="radio"]').forEach(input => input.disabled = true);
// Iterate again through valid questions to apply feedback styles
validQuestions.forEach((question, qIndex) => {
const questionElement = questionsArea.querySelector(`.quiz-question:nth-child(${qIndex + 1})`);
if (!questionElement) return;
const questionNumberElement = questionElement.querySelector('p strong');
const correctAnswer = question.correctAnswer;
let studentAnswer = null;
let isCorrect = false; // Re-evaluate for feedback
if (quiz.quizType === 'identification') {
const inputElement = questionElement.querySelector(`input[type="text"]`);
studentAnswer = inputElement ? inputElement.value.trim() : '';
isCorrect = (correctAnswer !== undefined && correctAnswer !== null && studentAnswer.toLowerCase() === String(correctAnswer).toLowerCase());
if (!isCorrect && inputElement) {
inputElement.classList.add('incorrect-answer-input');
if (correctAnswer !== undefined && correctAnswer !== null) {
const correctAnswerSpan = document.createElement('span');
correctAnswerSpan.classList.add('correct-answer-text');
correctAnswerSpan.textContent = `Correct answer: ${correctAnswer}`;
inputElement.parentNode.insertBefore(correctAnswerSpan, inputElement.nextSibling);
}
}
} else if (quiz.quizType === 'multiple-choice') {
const radioButtons = questionElement.querySelectorAll(`input[type="radio"]`);
const checkedRadio = questionElement.querySelector(`input[type="radio"]:checked`);
studentAnswer = checkedRadio ? checkedRadio.value : null;
isCorrect = (studentAnswer === String(correctAnswer));
radioButtons.forEach(radio => {
const choiceLabel = radio.closest('label');
if (!choiceLabel) return;
// Highlight the correct answer's label
if (radio.value === String(correctAnswer)) {
choiceLabel.classList.add('correct-answer-highlight');
}
// If this radio was checked and it's incorrect, style it
if (radio.checked && !isCorrect) {
choiceLabel.classList.add('incorrect-choice-selected');
choiceLabel.style.textDecoration = 'line-through'; // Add line-through
}
});
}
// Add checkmark or cross
if (questionNumberElement) {
const feedbackMark = document.createElement('span');
feedbackMark.classList.add('feedback-mark');
feedbackMark.textContent = isCorrect ? '✓' : '✗';
feedbackMark.classList.add(isCorrect ? 'correct-mark' : 'incorrect-mark');
// Ensure no duplicate marks
const existingMark = questionNumberElement.querySelector('.feedback-mark');
if (existingMark) existingMark.remove();
questionNumberElement.appendChild(feedbackMark);
}
}); // End of feedback loop
// --- Display Final Score and Save ---
const percentageScore = totalValidQuestions > 0 ? Math.round((score / totalValidQuestions) * 100) : 0;
scoreArea.innerHTML = `<h3>Your Score: ${percentageScore}%</h3>`; // Show percentage
scoreArea.style.display = 'block';
buttonElement.disabled = true; // Disable the submit button permanently
buttonElement.textContent = 'Submitted';
// Find the original "Take Quiz" button again and ensure it's disabled and text updated
const originalTakeButton = document.querySelector(`.take-quiz-btn[data-quiz-index="${quizIndex}"]`);
if (originalTakeButton) {
originalTakeButton.disabled = true;
originalTakeButton.textContent = 'Quiz Taken';
originalTakeButton.style.display = 'inline-block'; // Ensure it's visible again
}
// Save the result (percentage score) to the main student data
saveQuizResult(quiz.quizTitle || 'Untitled Quiz', percentageScore);
checkAndEnforceQuizCompletion(); // Update analytics button status
}
function saveQuizResult(quizTitle, percentageScore) {
const users = getUsers();
const studentData = users[loggedInUsername];
if (!studentData) {
console.error("Cannot save quiz result: Student data not found.");
return;
}
// Ensure quizScores array exists
if (!Array.isArray(studentData.quizScores)) {
studentData.quizScores = [];
}
// Find if a result for this quiz already exists
const existingResultIndex = studentData.quizScores.findIndex(q => q.quizName === quizTitle);
const newResult = {
quizName: quizTitle,
score: percentageScore, // Store the percentage score
timestamp: new Date().toISOString()
};
if (existingResultIndex > -1) {
// Update existing result
studentData.quizScores[existingResultIndex] = newResult;
console.log("Quiz result updated for:", quizTitle, "for student:", loggedInUsername);
} else {
// Add new result
studentData.quizScores.push(newResult);
console.log("Quiz result saved for:", quizTitle, "for student:", loggedInUsername);
}
// Save the entire updated users object back to localStorage
saveUsers(users);
}
// --- Quiz Completion Enforcement ---
function checkAndEnforceQuizCompletion() {
const globalQuizzes = JSON.parse(localStorage.getItem(storageKeys.quizzes) || '[]');
const studentData = getCurrentStudentData();
if (!studentData) return; // Can't check if data is missing
let allValidQuizzesTaken = true;
// Filter global quizzes to only include valid ones
const validGlobalQuizzes = globalQuizzes.filter(quiz => quiz && typeof quiz.quizTitle !== 'undefined' && Array.isArray(quiz.questions));
if (validGlobalQuizzes.length === 0) {
allValidQuizzesTaken = true; // No valid quizzes to take
} else {
for (const quiz of validGlobalQuizzes) {
const quizTitle = quiz.quizTitle;
// Check if THIS student has taken this VALID quiz by looking in their data
const hasTaken = studentData.quizScores.some(score => score.quizName === quizTitle);
if (!hasTaken) {
allValidQuizzesTaken = false;
break; // Found an untaken, valid quiz
}
}
}
console.log("All valid quizzes taken by", loggedInUsername, ":", allValidQuizzesTaken);
if (allValidQuizzesTaken) {
quizCompletionStatusDiv.classList.add('hidden');
analyticsBtn.disabled = false; // Enable button
console.log("Analytics button enabled for", loggedInUsername);
} else {
quizCompletionStatusDiv.classList.remove('hidden');
analyticsBtn.disabled = true; // Disable button
console.log("Analytics button disabled for", loggedInUsername);
}
}
// --- Analytics Modal Logic ---
function openAnalyticsModal() {
checkAndEnforceQuizCompletion(); // Re-check before opening
if (analyticsBtn.disabled) {
alert("Please complete all available quizzes before viewing analytics.");
return;
}
populateAnalyticsData();
analyticsModal.style.display = 'block';
document.body.classList.add('modal-open'); // Prevent body scroll
}
function closeAnalyticsModal() {
analyticsModal.style.display = 'none';
document.body.classList.remove('modal-open'); // Allow body scroll again
}
function populateAnalyticsData() {
const studentData = getCurrentStudentData();
if (!studentData) return; // Stop if data is missing
// Get last accessed time for this student
const lastAccessedISO = localStorage.getItem(storageKeys.lastAccessed);
lastAccessedSpan.textContent = lastAccessedISO ? new Date(lastAccessedISO).toLocaleString() : 'Not recorded yet.';
// --- Populate Lesson Progress ---
lessonProgressListUl.innerHTML = ''; // Clear previous list
const globalLessons = JSON.parse(localStorage.getItem(storageKeys.lessons) || '[]');
if (globalLessons.length > 0) {
// Ensure studentData.lessonProgress exists
const lessonProgress = Array.isArray(studentData.lessonProgress) ? studentData.lessonProgress : [];
globalLessons.forEach(lesson => {
const title = lesson.title || 'Untitled Lesson';
const progressEntry = lessonProgress.find(p => p.lessonName === title);
const status = progressEntry ? progressEntry.status : 'not started';
const statusClass = `status-${status.toLowerCase().replace(' ', '-')}`;
const listItem = document.createElement('li');
listItem.innerHTML = `<strong>${title}:</strong> <span class="${statusClass}">${status}</span>`;
lessonProgressListUl.appendChild(listItem);
});
} else {
lessonProgressListUl.innerHTML = '<li>No lessons available.</li>';
}
// --- Populate Quiz Scores ---
quizScoresListUl.innerHTML = ''; // Clear previous list
const quizScores = studentData.quizScores; // Get scores directly from student data
if (quizScores.length === 0) {
quizScoresListUl.innerHTML = '<li>No quizzes taken yet.</li>';
overallGradeSpan.textContent = 'N/A';
return; // Exit early if no scores
}
let totalPercentage = 0;
let numberOfQuizzesTaken = 0;
// Sort scores alphabetically by quiz name for consistent display
quizScores.sort((a, b) => a.quizName.localeCompare(b.quizName));
quizScores.forEach(result => {
// Ensure result has expected properties before calculating
if (result && typeof result.quizName !== 'undefined' && typeof result.score === 'number') {
const listItem = document.createElement('li');
listItem.innerHTML = `<strong>${result.quizName}:</strong> ${result.score}%`;
quizScoresListUl.appendChild(listItem);
totalPercentage += result.score;
numberOfQuizzesTaken++;
} else {
console.warn(`Invalid score data found in student results:`, result);
}
});
// Calculate overall grade as the average of the percentage scores of quizzes taken
overallGradeSpan.textContent = numberOfQuizzesTaken > 0 ? `${(totalPercentage / numberOfQuizzesTaken).toFixed(1)}%` : 'N/A';
}
// --- Navigation Logic ---
function goToHomepage() {
// Determine homepage based on role if necessary, or assume a general one
window.location.href = 'homepage.html'; // Assuming homepage.html exists
}
function logout() {
sessionStorage.removeItem('loggedInUsername');
sessionStorage.removeItem('loggedInRole');
// Also clear student-specific items if desired
localStorage.removeItem(storageKeys.lastAccessed);
alert('You have been logged out.');
window.location.href = 'index.html'; // Redirect to login page (index.html)
}
// --- Event Listeners ---
if (analyticsBtn) analyticsBtn.addEventListener('click', openAnalyticsModal);
if (modalDashboardBtn) modalDashboardBtn.addEventListener('click', closeAnalyticsModal);
if (modalHomeBtn) modalHomeBtn.addEventListener('click', goToHomepage);
if (modalLogoutBtn) modalLogoutBtn.addEventListener('click', logout);
// --- Initial Load ---
recordLastAccessed(); // Record access time on load
updateCreatedLessons();
updateCreatedQuizzes(); // Initial population and completion check for this student
});
Key Changes Implemented:
updateCreatedQuizzes, submitQuiz, saveQuizResult, checkAndEnforceQuizCompletion, populateAnalyticsData) now read from and write to the student's specific data object within the main users object in localStorage (accessed via getCurrentStudentData, getUsers, saveUsers).getCurrentStudentData: Added a helper function to reliably get the current student's data and initialize quizScores and lessonProgress arrays if they are missing. Includes error handling and logout if data is corrupt.markLessonAsRead function and event listener. updateCreatedLessons now checks the student's lessonProgress array to set the button state. populateAnalyticsData now displays lesson progress.saveQuizResult now saves the quizName and score (as a percentage) into the studentData.quizScores array. It updates if the quiz was taken before, otherwise adds a new entry.updateCreatedQuizzes and populateAnalyticsData display the percentage score stored in studentData.quizScores.checkAndEnforceQuizCompletion now iterates through the valid global quizzes and checks against the student's quizScores array (by quizName) to determine if all have been taken.populateAnalyticsData as the average of the percentage scores of all quizzes taken by the student.getUsers and saveUsers (from login.js) are available. Remember to include <script src="login.js"></script> before <script src="student.js"></script> in your student.html.*