create another page that parent can see the score of the the specific student on this code:
<!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>
/* Navbar Styles Removed */
/* 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
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
(parent)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Parent Access - Find Student</title>
<link rel="stylesheet" href="parent.css"> <!-- Use new CSS -->
</head>
<body>
<div class="access-container">
<h2>Find Student Record</h2>
<p>Please enter the student's details to view their progress.</p>
<input type="text" id="studentFullName" placeholder="Enter Student's Full Name">
<input type="text" id="studentIdNumber" placeholder="Enter Student's ID Number">
<button onclick="verifyStudent()">View Dashboard</button>
<p id="accessErrorMessage" class="error-message"></p>
<p><a href="login.html">Back to Login</a></p>
</div>
<script src="parent.js"></script> <!-- Use new JS -->
<script src="login.js"></script> <!-- Include login.js for getUsers function -->
</body>
</html>
/* Reusing and adapting styles from login.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #e2a2a2, #f4f4f4); /* Slightly different gradient for parent section */
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.access-container,
.dashboard-container {
background: #fff;
width: 100%;
max-width: 600px; /* Wider for dashboard */
padding: 30px;
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
text-align: left; /* Align text left for dashboard content */
}
.access-container {
max-width: 450px; /* Keep access container similar to login */
text-align: center; /* Center align access form */
}
h2, h3 {
color: #333;
margin-bottom: 20px;
font-weight: bold;
}
h2 {
font-size: 24px;
text-align: center; /* Center main headings */
}
h3 {
font-size: 20px;
margin-top: 25px; /* Add space above subheadings */
border-bottom: 1px solid #eee; /* Separator line */
padding-bottom: 10px;
}
input, button { /* Styles for access page */
width: 100%;
padding: 15px;
margin: 10px 0;
border-radius: 8px;
border: 1px solid #ddd;
font-size: 16px;
outline: none;
transition: all 0.3s ease;
}
input:focus, button:focus {
border-color: #c82333; /* Parent theme color */
box-shadow: 0 0 8px rgba(200, 35, 51, 0.3);
}
button {
background: #c82333; /* Parent theme color */
color: white;
border: none;
cursor: pointer;
font-weight: bold;
transition: background 0.3s ease;
}
button:hover {
background: #a81d2a;
}
.error-message {
color: red;
font-size: 14px;
margin-top: 10px;
min-height: 1.2em; /* Reserve space */
text-align: center;
}
p {
font-size: 14px;
color: #666;
margin-top: 15px;
line-height: 1.6;
}
.access-container p {
text-align: center; /* Center paragraph text in access container */
}
p a {
color: #c82333; /* Parent theme color */
text-decoration: none;
}
p a:hover {
text-decoration: underline;
}
/* Dashboard specific styles */
.dashboard-container div {
margin-bottom: 20px;
}
.dashboard-container ul {
list-style: none;
padding-left: 0;
}
.dashboard-container li {
background-color: #f9f9f9;
border: 1px solid #eee;
padding: 10px 15px;
margin-bottom: 8px;
border-radius: 4px;
display: flex;
justify-content: space-between;
align-items: center;
}
.dashboard-container li span {
font-weight: bold;
}
.lesson-progress .status-completed { color: green; }
.lesson-progress .status-read { color: orange; }
.lesson-progress .status-not-started { color: grey; }
.student-info p {
margin-top: 5px;
}
// ========== Parent Access Page (parent-access.html) ==========
function verifyStudent() {
const studentFullNameInput = document.getElementById("studentFullName");
const studentIdNumberInput = document.getElementById("studentIdNumber");
const errorMessage = document.getElementById("accessErrorMessage");
const studentFullName = studentFullNameInput.value.trim();
const studentIdNumber = studentIdNumberInput.value.trim();
errorMessage.textContent = ''; // Clear previous errors
if (!studentFullName || !studentIdNumber) {
errorMessage.textContent = "Please enter both the student's full name and ID number.";
return;
}
const users = getUsers(); // Assumes getUsers() is available (from login.js)
let foundStudentUsername = null;
// Iterate through all users to find a matching student
for (const username in users) {
const userData = users[username];
if (userData.role === 'student' &&
userData.fullName.toLowerCase() === studentFullName.toLowerCase() && // Case-insensitive name check
userData.studentId === studentIdNumber)
{
foundStudentUsername = username;
break; // Stop searching once found
}
}
if (foundStudentUsername) {
// Student found, store the username (key) for the dashboard
sessionStorage.setItem('viewingStudentUsername', foundStudentUsername);
// Redirect to the dashboard
window.location.href = "parent-dashboard.html";
} else {
// No matching student found
errorMessage.textContent = "No student record found matching the provided name and ID. Please check the details and try again.";
studentFullNameInput.value = ''; // Clear fields on error
studentIdNumberInput.value = '';
}
}
// ========== Parent Dashboard Page (parent-dashboard.html) ==========
function populateDashboard() {
const viewingStudentUsername = sessionStorage.getItem('viewingStudentUsername');
const dashboardTitle = document.getElementById('dashboardTitle');
// Security check: If no student username is in session storage, redirect back
if (!viewingStudentUsername) {
alert("No student selected. Redirecting to access page.");
window.location.href = "parent-access.html";
return; // Stop execution
}
const users = getUsers(); // Assumes getUsers() is available
const studentData = users[viewingStudentUsername];
if (!studentData || studentData.role !== 'student') {
alert("Error retrieving student data. Redirecting.");
sessionStorage.removeItem('viewingStudentUsername'); // Clear invalid session item
window.location.href = "parent-access.html";
return; // Stop execution
}
// Populate Student Info
document.getElementById('studentName').textContent = studentData.fullName || 'N/A';
document.getElementById('studentId').textContent = studentData.studentId || 'N/A';
dashboardTitle.textContent = `${studentData.fullName}'s Progress Dashboard`;
// Populate Quiz Scores
const quizList = document.getElementById('quizList');
quizList.innerHTML = ''; // Clear loading message
if (studentData.quizScores && studentData.quizScores.length > 0) {
studentData.quizScores.forEach(quiz => {
const listItem = document.createElement('li');
listItem.innerHTML = `${quiz.quizName} <span>${quiz.score}%</span>`;
quizList.appendChild(listItem);
});
} else {
quizList.innerHTML = '<li>No quiz scores available.</li>';
}
// Populate Lesson Progress
const lessonList = document.getElementById('lessonList');
lessonList.innerHTML = ''; // Clear loading message
if (studentData.lessonProgress && studentData.lessonProgress.length > 0) {
studentData.lessonProgress.forEach(lesson => {
const listItem = document.createElement('li');
// Add class for styling based on status
const statusClass = `status-${lesson.status.toLowerCase().replace(' ', '-')}`;
listItem.innerHTML = `${lesson.lessonName} <span class="${statusClass}">${lesson.status}</span>`;
lessonList.appendChild(listItem);
});
} else {
lessonList.innerHTML = '<li>No lesson progress available.</li>';
}
}
function logoutParent() {
// Clear specific session storage items related to parent view
sessionStorage.removeItem('viewingStudentUsername');
// Optionally clear parent login info if you stored it, e.g., sessionStorage.removeItem('loggedInUsername');
// Redirect to main login page
window.location.href = 'index.html';
}
// Run dashboard population logic only if we are on the dashboard page
// Check for an element unique to the dashboard
if (document.getElementById('dashboardTitle')) {
document.addEventListener('DOMContentLoaded', populateDashboard);
}