Luyện Tập Vật Lý - Hệ Thống Học Tập
Thiết Lập Thời Gian Làm Bài
Nộp Bài
Xem Đáp Án
Câu Tiếp
Hướng dẫn làm bài
1. Bài tập cơ bản:
Gồm các câu hỏi trắc nghiệm, đúng/sai và tự luận
Bạn phải trả lời hết tất cả câu hỏi mới được nộp bài
Tối đa 7 điểm
2. Bài tập nâng cao:
Chỉ hiển thị sau khi hoàn thành bài tập cơ bản
Bạn có thể nộp bài bất kỳ lúc nào
Tối đa 3 điểm
Tổng điểm: Điểm bài cơ bản + điểm bài nâng cao (tối đa 10 điểm)
Đã hiểu
Nhập Số Điện Thoại
Vui lòng nhập số điện thoại bạn đã sử dụng khi mua khóa học để tiếp tục làm bài tập làm thêm.
Hủy
Kiểm Tra
✕
Luyện Tập Vật Lý - Hệ Thống Học Tập
Thiết Lập Thời Gian Làm Bài
Nộp Bài
Xem Đáp Án
Câu Tiếp
Hướng dẫn làm bài
1. Bài tập cơ bản:
Gồm các câu hỏi trắc nghiệm, đúng/sai và tự luận
Bạn phải trả lời hết tất cả câu hỏi mới được nộp bài
Tối đa 7 điểm
2. Bài tập nâng cao:
Chỉ hiển thị sau khi hoàn thành bài tập cơ bản
Bạn có thể nộp bài bất kỳ lúc nào
Tối đa 3 điểm
Tổng điểm: Điểm bài cơ bản + điểm bài nâng cao (tối đa 10 điểm)
Đã hiểu
Nhập Số Điện Thoại
Vui lòng nhập số điện thoại bạn đã sử dụng khi mua khóa học để tiếp tục làm bài tập làm thêm.
Hủy
Kiểm Tra
✕
/* ============================================
* PHẦN 2: TRẠNG THÁI ỨNG DỤNG VÀ DOM ELEMENTS
* ========================================== */
const state = {
currentQuestionIndex: 0,
userAnswers: {},
startTime: null,
timerInterval: null,
questionKeys: [],
totalQuestions: 0,
extraKeys: [],
currentExtraIndex: 0,
userExtraAnswers: {},
userRating: 0,
basicQuizSubmitted: false,
advancedQuizSubmitted: false,
examCode: null,
// Thêm các trạng thái mới cho tính năng thời gian
totalTimeAllowed: 0, // Tổng thời gian cho phép (giây)
timeRemaining: 0, // Thời gian còn lại (giây)
questionTimeRemaining: 0, // Thời gian còn lại cho câu hỏi hiện tại
questionTimeLimit: 0, // Thời gian tối đa cho câu hỏi hiện tại
selectedQuestions: [] // Danh sách câu hỏi được chọn
};
const elements = {
// Các phần tử hiện có
timer: document.getElementById('timer'),
progressBar: document.getElementById('progress-bar'),
questionContainer: document.getElementById('question-container'),
navigation: document.getElementById('navigation'),
submitBtn: document.getElementById('submit-btn'),
answerBtn: document.getElementById('answer-btn'),
nextBtn: document.getElementById('next-btn'),
extraExerciseBtn: document.getElementById('extra-exercise-btn'),
extraExerciseContainer: document.getElementById('extra-exercise-container'),
phoneModal: document.getElementById('phone-modal'),
phoneInput: document.getElementById('phone-input'),
phoneSubmit: document.getElementById('phone-submit'),
phoneCancel: document.getElementById('phone-cancel'),
checkResult: document.getElementById('check-result'),
resultModal: document.getElementById('result-modal'),
resultTime: document.getElementById('result-time'),
resultMessage: document.getElementById('result-message'),
commentBox: document.getElementById('comment-box'),
ratingText: document.getElementById('rating-text'),
stars: document.querySelectorAll('.star'),
finalSubmit: document.getElementById('final-submit'),
finalCancel: document.getElementById('final-cancel'),
notification: document.getElementById('notification'),
notificationText: document.getElementById('notification-text'),
notificationClose: document.getElementById('notification-close'),
submitForm: document.getElementById('main-submit-form'),
guideModal: document.getElementById('guide-modal'),
guideOkButton: document.getElementById('guide-ok'),
// Thêm các phần tử mới
setupContainer: document.getElementById('setup-container'),
quizHeader: document.getElementById('quiz-header'),
mainContent: document.getElementById('main-content'),
totalTimeInput: document.getElementById('total-time'),
questionEstimate: document.getElementById('question-estimate'),
startBtn: document.getElementById('start-btn'),
totalAllowedTime: document.getElementById('total-allowed-time')
};
/* ============================================
* PHẦN 3: CÁC MODULE CHỨC NĂNG
* ========================================== */
const saveCurrentQuestionState = (answerStateObject) => {
const container = elements.questionContainer.querySelector('.question-container');
if (!container) return;
const questionId = container.dataset.id;
if (!questionId) return;
container.querySelectorAll('.math-answer-field').forEach(mf => {
const id = mf.dataset.id;
const part = mf.dataset.part;
const key = part ? `${id}_${part}` : id;
if (mf.value.trim()) {
answerStateObject[key] = [mf.value];
} else {
delete answerStateObject[key];
}
});
};
// Module mới: Quản lý thời gian và chọn câu hỏi
const timeManager = {
// Tính toán số câu hỏi dựa trên thời gian
calculateQuestionCount(totalMinutes) {
const totalSeconds = totalMinutes * 60;
let remainingTime = totalSeconds;
// Lấy tất cả câu hỏi có sẵn
const availableQuestions = Object.values(quizData);
// Sắp xếp câu hỏi theo độ ưu tiên (trắc nghiệm trước, rồi đến đúng/sai, cuối cùng là tự luận)
const sortedQuestions = availableQuestions.sort((a, b) => {
const priority = { multipleChoice: 1, trueFalse: 2, essay: 3 };
return priority[a.type] - priority[b.type];
});
// Chọn câu hỏi cho đến khi hết thời gian
const selectedQuestions = [];
for (const question of sortedQuestions) {
const timeNeeded = config.TIME_PER_QUESTION[question.type];
if (remainingTime >= timeNeeded) {
selectedQuestions.push(question);
remainingTime -= timeNeeded;
}
}
// Xáo trộn thứ tự câu hỏi
return utils.shuffleArray(selectedQuestions);
},
// Ước tính số câu hỏi dựa trên thời gian
estimateQuestionCount(totalMinutes) {
const totalSeconds = totalMinutes * 60;
// Đếm số lượng từng loại câu hỏi có sẵn
const availableCounts = {
multipleChoice: Object.values(quizData).filter(q => q.type === 'multipleChoice').length,
trueFalse: Object.values(quizData).filter(q => q.type === 'trueFalse').length,
essay: Object.values(quizData).filter(q => q.type === 'essay').length
};
// Tính số câu hỏi tối đa có thể làm cho mỗi loại
const maxPossible = {
multipleChoice: Math.min(availableCounts.multipleChoice, Math.floor(totalSeconds * 0.7 / config.TIME_PER_QUESTION.multipleChoice)),
trueFalse: Math.min(availableCounts.trueFalse, Math.floor(totalSeconds * 0.2 / config.TIME_PER_QUESTION.trueFalse)),
essay: Math.min(availableCounts.essay, Math.floor(totalSeconds * 0.1 / config.TIME_PER_QUESTION.essay))
};
return maxPossible.multipleChoice + maxPossible.trueFalse + maxPossible.essay;
},
// Định dạng thời gian (giây -> MM:SS)
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
},
// Cập nhật ước tính số câu hỏi
updateQuestionEstimate() {
const totalMinutes = parseInt(elements.totalTimeInput.value) || 20;
const estimatedCount = this.estimateQuestionCount(totalMinutes);
elements.questionEstimate.textContent = `Số câu ước tính: ${estimatedCount} câu`;
}
};
// Module hệ thống tính điểm
const scoringSystem = {
// Cải tiến normalizeExpression trong scoringSystem
normalizeExpression(expr) {
if (typeof expr !== 'string') expr = String(expr);
if (!expr.trim()) return '';
return expr
.toLowerCase()
.replace(/\s+/g, '')
.replace(/,/g, '.')
// Xử lý các ký tự đặc biệt toán học
.replace(/\\times/g, '*')
.replace(/\\div/g, '/')
.replace(/\\cdot/g, '*')
.replace(/\\pi/g, 'pi')
.replace(/\\infty/g, 'inf')
.replace(/\\theta/g, 'theta')
.replace(/\\alpha/g, 'alpha')
.replace(/\\beta/g, 'beta')
.replace(/\\gamma/g, 'gamma')
// Loại bỏ đơn vị và ký tự thừa
.replace(/(km\/h|cm\/s2|cm\/s|m\/s|rad\/s|cm|kg|hz|rad|gam|%)/g, '')
// Chuẩn hóa phân số
.replace(/(\d+)\/(\d+)/g, '($1)/($2)');
},
compareMathExpressions(userAnswer, correctAnswer) {
if (userAnswer === undefined || userAnswer === null || correctAnswer === undefined || correctAnswer === null) {
return false;
}
const userNorm = this.normalizeExpression(userAnswer);
const correctNorm = this.normalizeExpression(correctAnswer);
if (userNorm === correctNorm) return true;
if (userNorm.includes(';') || correctNorm.includes(';')) {
const userParts = userNorm.split(';').map(p => p.trim()).filter(p => p).sort();
const correctParts = correctNorm.split(';').map(p => p.trim()).filter(p => p).sort();
if (userParts.length !== correctParts.length) return false;
return userParts.every((part, index) => this.compareMathExpressions(part, correctParts[index]));
}
try {
const userValue = math.evaluate(userNorm);
const correctValue = math.evaluate(correctNorm);
const tolerance = Math.max(config.TOLERANCE, Math.abs(correctValue) * 0.01);
return Math.abs(userValue - correctValue) < tolerance;
} catch (e) {
return userNorm === correctNorm;
}
},
checkEssayAnswer(questionPart, userAnswerArray) {
if (!userAnswerArray || !userAnswerArray[0]) return false;
const userAnswer = userAnswerArray[0];
return questionPart.answer.some(correctAns => this.compareMathExpressions(userAnswer, correctAns));
},
calculateQuestionScore(question, userAnswers) {
if (!question || !userAnswers) return 0;
switch (question.type) {
case 'multipleChoice':
const userAnswer = userAnswers[question.ID];
if (!userAnswer) return 0;
const userChoiceIndex = userAnswer[0];
const correctChoiceIndex = question.choices.findIndex(c => c.answer === "true");
return (userChoiceIndex === correctChoiceIndex && correctChoiceIndex !== -1) ? (question.choices[correctChoiceIndex].score ?? 0.25) : 0;
case 'essay':
const userEssayAnswer = userAnswers[question.ID];
return this.checkEssayAnswer(question, userEssayAnswer) ? (question.score || 1) : 0;
case 'essay-multi':
let partScore = 0;
Object.entries(question.parts).forEach(([partId, partData]) => {
const userPartAnswer = userAnswers[`${question.ID}_${partId}`];
if (this.checkEssayAnswer(partData, userPartAnswer)) {
partScore += partData.score || 1;
}
});
return partScore;
case 'trueFalse':
let tfScore = 0;
const userSubAnswers = userAnswers[question.ID];
if (!userSubAnswers) return 0;
question.subQuestions.forEach((subQ, index) => {
const correctAnswer = subQ.answer === 'true';
const userSubAnswer = userSubAnswers[index];
if (userSubAnswer === correctAnswer) {
tfScore += subQ.score ?? 0.1;
}
});
return tfScore;
default:
return 0;
}
},
calculateScore(questionKeys, questionData, userAnswers, maxScore) {
let totalPossiblePoints = 0;
let earnedPoints = 0;
questionKeys.forEach(key => {
const question = questionData[key];
if (!question) return;
let questionMaxScore = 0;
switch (question.type) {
case 'multipleChoice':
const correctChoice = question.choices.find(c => c.answer === "true");
questionMaxScore = correctChoice ? (correctChoice.score ?? 0.25) : 0.25;
break;
case 'essay':
questionMaxScore = question.score || 1;
break;
case 'essay-multi':
questionMaxScore = Object.values(question.parts).reduce((sum, part) => sum + (part.score || 1), 0);
break;
case 'trueFalse':
questionMaxScore = question.subQuestions.reduce((sum, sub) => sum + (sub.score ?? 0.1), 0);
break;
}
totalPossiblePoints += questionMaxScore;
earnedPoints += this.calculateQuestionScore(question, userAnswers);
});
if (totalPossiblePoints === 0) return 0;
const normalizedScore = (earnedPoints / totalPossiblePoints) * maxScore;
return Math.min(normalizedScore, maxScore);
},
calculateBasicScore() {
const questionKeys = state.selectedQuestions.map(q => q.ID);
return this.calculateScore(questionKeys, quizData, state.userAnswers, 7);
},
calculateAdvancedScore() {
return this.calculateScore(state.extraKeys, extraPracticeData, state.userExtraAnswers, 3);
}
};
// Module tiện ích
const utils = {
playClickSound() {
try {
const audio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU9vT19XgS0BAgECAQIDAwMDBQQEBQYGBgcIBwcICAkKCgoLCwwMDAwNDg4ODw8PEA8PERERERISEhITExMTFBQUFBYWFhYXFxgYGCIXFRYVFBMTDxAODQwMCQgHBgUEAwIB');
audio.volume = 0.3;
audio.play();
} catch(e) {}
},
showNotification(message, isError = false) {
elements.notificationText.textContent = message;
elements.notification.className = 'notification';
if (isError) elements.notification.classList.add('error');
elements.notification.classList.add('show');
setTimeout(() => { elements.notification.classList.remove('show'); }, 5000);
},
hideModal(modalElement) { if (modalElement) modalElement.style.display = 'none'; },
handleImageError(img) {
img.onerror = null;
const placeholder = document.createElement('div');
placeholder.className = 'image-placeholder';
placeholder.textContent = 'Lỗi khi tải hình ảnh.';
if (img.parentNode) img.parentNode.replaceChild(placeholder, img);
},
shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
},
generateExamCode(length) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
},
preventCopyEvents() {
document.addEventListener('contextmenu', (e) => {
e.preventDefault();
this.showNotification('Tính năng này đã bị vô hiệu hóa trên bài làm', true);
});
document.addEventListener('copy', (e) => {
e.preventDefault();
this.showNotification('Không cho phép sao chép nội dung bài làm', true);
});
document.addEventListener('cut', (e) => {
e.preventDefault();
this.showNotification('Không cho phép cắt nội dung bài làm', true);
});
}
};
// ==========================================================================
// * MODULE RENDER LATEX HOÀN CHỈNH (ĐÃ SỬA LỖI CACHE)
// ========================================================================
const latexRenderer = {
// THÊM: Cache để tối ưu hiệu suất
cache: new Map(),
// GIỮ NGUYÊN: Hàm render chính từ mã gốc (đã tích hợp cache)
renderLatex(content) {
if (!content || typeof content !== 'string') return content;
// KIỂM TRA CACHE TRƯỚC
if (this.cache.has(content)) {
return this.cache.get(content);
}
try {
// THÊM: Xử lý các ký tự đặc biệt trước
let processed = content
.replace(/\\\(/g, '(') // Chuyển \( thành (
.replace(/\\\)/g, ')') // Chuyển \) thành )
.replace(/\\\[/g, '[') // Chuyển \[ thành [
.replace(/\\\]/g, ']') // Chuyển \] thành ]
// GIỮ NGUYÊN: Xử lý display math
.replace(/\$\$([^$]+)\$\$/g, '
\\[$1\\]
')
// GIỮ NGUYÊN: Xử lý inline math
.replace(/\$([^$]+)\$/g, '
\\($1\\) ');
// LƯU VÀO CACHE
this.cache.set(content, processed);
// Giới hạn cache size (tránh memory leak)
if (this.cache.size > 100) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
return processed;
} catch (error) {
console.error('Lỗi render LaTeX:', error);
return content;
}
},
// GIỮ NGUYÊN: Hàm render an toàn cho các phần tử cụ thể
safeRenderElement(element) {
if (!element) return;
const original = element.innerHTML;
const rendered = this.renderLatex(original);
if (rendered !== original) {
element.innerHTML = rendered;
this.triggerMathJax();
}
},
// GIỮ NGUYÊN: Kích hoạt MathJax render - PHIÊN BẢN CẢI TIẾN
triggerMathJax() {
if (window.MathJax && window.MathJax.typesetPromise) {
// Đảm bảo MathJax hoạt động đúng
return MathJax.startup.promise.then(() => {
return MathJax.typesetPromise().catch(err => {
console.warn('MathJax typeset warning:', err);
// Thử lại sau 100ms nếu có lỗi
return new Promise(resolve => setTimeout(resolve, 100))
.then(() => MathJax.typesetPromise());
});
}).catch(err => {
console.error('MathJax initialization error:', err);
});
}
return Promise.resolve();
},
// CẢI TIẾN: Kích hoạt MathJax với loading indicator
triggerMathJaxWithLoading() {
if (!window.MathJax) {
console.warn('MathJax not loaded');
return Promise.resolve();
}
// Thêm indicator loading
const containers = document.querySelectorAll('.question-container, .choice-text');
containers.forEach(container => {
container.classList.add('math-rendering');
});
return MathJax.startup.promise.then(() => {
return MathJax.typesetPromise()
.then(() => {
containers.forEach(container => {
container.classList.remove('math-rendering');
container.classList.add('math-rendered');
});
})
.catch(err => {
console.error('MathJax typeset failed:', err);
containers.forEach(container => {
container.classList.remove('math-rendering');
container.classList.add('math-error');
});
throw err;
});
}).catch(err => {
console.error('MathJax initialization error:', err);
containers.forEach(container => {
container.classList.remove('math-rendering');
container.classList.add('math-error');
});
});
},
// Render container và kích hoạt MathJax - PHIÊN BẢN CẢI TIẾN
safeRenderContainer(container) {
if (!container) return Promise.resolve();
const originalContent = container.innerHTML;
const renderedContent = this.renderLatex(originalContent);
if (renderedContent !== originalContent) {
container.innerHTML = renderedContent;
// Kích hoạt MathJax sau khi render
return this.triggerMathJax();
}
return Promise.resolve();
},
// Render container (tương thích ngược)
renderContainer(container) {
if (!container) return;
const originalContent = container.innerHTML;
const renderedContent = this.renderLatex(originalContent);
if (renderedContent !== originalContent) {
container.innerHTML = renderedContent;
// Kích hoạt MathJax sau khi render
setTimeout(() => this.triggerMathJax(), 100);
}
},
// THÊM: Retry mechanism cho MathJax
triggerMathJaxWithRetry(retries = 3) {
return this.triggerMathJax()
.catch(err => {
if (retries > 0) {
console.warn(`MathJax retry ${4-retries}/3`);
return new Promise(resolve => setTimeout(resolve, 500))
.then(() => this.triggerMathJaxWithRetry(retries - 1));
}
throw err;
});
},
// THÊM: Hàm render với fallback
safeRenderWithFallback(content) {
try {
return this.renderLatex(content);
} catch (error) {
console.warn('LaTeX render error, using fallback:', error);
// Giữ nguyên nội dung gốc, chỉ xử lý các ký tự đặc biệt cơ bản
return content
.replace(/\$\$([^$]+)\$\$/g, '
$1
')
.replace(/\$([^$]+)\$/g, '
$1 ');
}
},
// GIỮ NGUYÊN: Khởi tạo MathLive cho math-field
initMathFields(container) {
if (!container || !window.MathLive) return;
const mathFields = container.querySelectorAll('math-field');
mathFields.forEach(mf => {
if (!mf.hasAttribute('data-mathlive-initialized')) {
try {
MathLive.makeMathField(mf, {
readOnly: mf.hasAttribute('readonly'),
virtualKeyboardMode: 'manual',
virtualKeyboards: 'numeric symbols'
});
mf.setAttribute('data-mathlive-initialized', 'true');
} catch (error) {
console.error('Lỗi khởi tạo MathLive:', error);
}
}
});
},
// THÊM: Hàm clear cache (tùy chọn)
clearCache() {
this.cache.clear();
console.log('LaTeX cache cleared');
},
// THÊM: Hàm xem cache size (debug)
getCacheSize() {
return this.cache.size;
}
// XÓA: Hàm renderLatexWithCache vì đã tích hợp cache trong renderLatex
};
// Module đồng hồ đếm ngược
const timer = {
startCountdown() {
state.startTime = new Date();
state.timeRemaining = state.totalTimeAllowed;
state.timerInterval = setInterval(() => {
this.updateCountdown();
this.updateQuestionTimer();
}, 1000);
},
updateCountdown() {
if (state.timeRemaining <= 0) {
this.stop();
if (!state.basicQuizSubmitted) {
this.autoSubmit();
}
return;
}
state.timeRemaining--;
elements.timer.textContent = timeManager.formatTime(state.timeRemaining);
// Cảnh báo khi thời gian sắp hết
if (state.timeRemaining <= 60) {
elements.timer.parentElement.style.background = 'rgba(220, 53, 69, 0.3)';
}
},
updateQuestionTimer() {
if (state.questionTimeRemaining > 0) {
state.questionTimeRemaining--;
const questionTimer = document.getElementById('question-timer');
if (questionTimer) {
questionTimer.textContent = timeManager.formatTime(state.questionTimeRemaining);
// Cảnh báo khi thời gian cho câu hỏi sắp hết
if (state.questionTimeRemaining <= 30) {
questionTimer.classList.add('warning');
} else {
questionTimer.classList.remove('warning');
}
}
// Tự động chuyển câu khi hết thời gian
if (state.questionTimeRemaining <= 0 && !state.basicQuizSubmitted) {
questions.next();
}
}
},
stop() {
clearInterval(state.timerInterval);
state.timerInterval = null;
},
getElapsedTime() {
if (!state.startTime) return "00:00";
const elapsed = Math.floor((new Date() - state.startTime) / 1000);
const totalAllowed = state.totalTimeAllowed;
const usedTime = Math.min(elapsed, totalAllowed);
const minutes = Math.floor(usedTime / 60).toString().padStart(2, '0');
const seconds = (usedTime % 60).toString().padStart(2, '0');
return `${minutes}:${seconds}`;
},
// Tự động nộp bài khi hết thời gian
autoSubmit() {
utils.showNotification("Đã hết thời gian làm bài. Hệ thống tự động nộp bài.", true);
quiz.finalizeBasicSubmission();
}
};
// Module quản lý câu hỏi
const questions = {
display(index) {
saveCurrentQuestionState(state.userAnswers);
if (!state.basicQuizSubmitted) {
this.checkAllAnswered();
}
state.currentQuestionIndex = index;
const question = state.selectedQuestions[index];
let questionHTML = '';
let questionText = question.mainQuestion;
// XỬ LÝ HÌNH ẢNH VÀ LATEX TỐT HƠN
const imgMatch = questionText.match(/
]+>/);
if (imgMatch) {
const imgTag = imgMatch[0];
const parts = questionText.split(imgTag);
// Xử lý LaTeX cho phần trước hình ảnh
if (parts[0].trim()) {
questionHTML += `
${latexRenderer.renderLatex(parts[0])}
`;
}
// Thêm hình ảnh
questionHTML += imgTag;
// Xử lý LaTeX cho phần sau hình ảnh
if (parts[1] && parts[1].trim()) {
questionHTML += `
${latexRenderer.renderLatex(parts[1])}
`;
}
} else {
// XỬ LÝ LATEX CHO TOÀN BỘ CÂU HỎI
questionHTML = `
${latexRenderer.renderLatex(questionText)}
`;
}
let html = `
Câu ${index + 1}/${state.totalQuestions}
${timeManager.formatTime(state.questionTimeLimit)}
${questionHTML}`;
// Thiết lập thời gian cho câu hỏi hiện tại
state.questionTimeRemaining = config.TIME_PER_QUESTION[question.type];
state.questionTimeLimit = config.TIME_PER_QUESTION[question.type];
switch(question.type) {
case "multipleChoice":
const mappedChoices = question.choices.map((choice, idx) => ({ choiceData: choice, originalIndex: idx }));
const shuffledChoices = utils.shuffleArray(mappedChoices);
html += `
`;
shuffledChoices.forEach((c, i) => {
const isSelected = (state.userAnswers[question.ID] || []).includes(c.originalIndex);
// XỬ LÝ LATEX CHO LỰA CHỌN - SỬ DỤNG PHƯƠNG PHÁP MỚI
const choiceContainer = document.createElement('div');
choiceContainer.innerHTML = c.choiceData.choice;
latexRenderer.safeRenderElement(choiceContainer);
html += `
${String.fromCharCode(65 + i)}
${choiceContainer.innerHTML}
`;
});
html += `
`;
break;
case "essay":
const userAnswerEssay = (state.userAnswers[question.ID] || [''])[0];
html += `
${userAnswerEssay} `;
break;
case "trueFalse":
html += `
`;
question.subQuestions.forEach((subQ, subIndex) => {
const userSubAnswer = state.userAnswers[question.ID] ? state.userAnswers[question.ID][subIndex] : null;
const trueSelected = userSubAnswer === true ? 'selected' : '';
const falseSelected = userSubAnswer === false ? 'selected' : '';
// XỬ LÝ LATEX CHO CÂU HỎI PHỤ - SỬ DỤNG PHƯƠNG PHÁP MỚI
const subQuestionContainer = document.createElement('div');
subQuestionContainer.innerHTML = subQ.question;
latexRenderer.safeRenderElement(subQuestionContainer);
html += `
${subQuestionContainer.innerHTML}
`;
});
html += `
`;
break;
}
html += `
`;
elements.questionContainer.innerHTML = html;
// KÍCH HOẠT RENDER LATEX VÀ MATHLIVE - CẢI TIẾN
setTimeout(() => {
// Render LaTeX cho toàn bộ container
latexRenderer.safeRenderContainer(elements.questionContainer);
// Khởi tạo MathLive cho các math-field
latexRenderer.initMathFields(elements.questionContainer);
// Đảm bảo MathJax render hoàn tất
latexRenderer.triggerMathJax();
}, 150);
this.updateNavigationDots();
if (state.basicQuizSubmitted) {
this.showAnswer();
} else {
this.addAnswerEventListeners();
}
},
addAnswerEventListeners() {
const container = elements.questionContainer;
const questionId = container.querySelector('.question-container')?.dataset.id;
const question = state.selectedQuestions.find(q => q.ID == questionId);
if (!questionId || !question) return;
container.querySelectorAll(".choice").forEach(choice => {
choice.addEventListener("click", e => {
utils.playClickSound();
const index = parseInt(e.currentTarget.dataset.index);
state.userAnswers[questionId] = [index];
this.updateNavigationAndProgress();
e.currentTarget.closest('.choices-container').querySelectorAll('.choice').forEach(c => c.classList.remove('selected'));
e.currentTarget.classList.add('selected');
});
});
container.querySelectorAll(".math-answer-field").forEach(mf => {
const handler = () => {
saveCurrentQuestionState(state.userAnswers);
this.updateNavigationAndProgress();
};
mf.addEventListener("input", handler);
mf.addEventListener("blur", handler);
});
if (question.type === 'trueFalse') {
container.querySelectorAll('.tf-btn').forEach(btn => {
btn.addEventListener('click', e => {
utils.playClickSound();
const subIndex = parseInt(e.currentTarget.closest('.sub-question').dataset.subIndex);
const value = e.currentTarget.dataset.value === 'true';
if (!state.userAnswers[questionId]) {
state.userAnswers[questionId] = Array(question.subQuestions.length).fill(null);
}
state.userAnswers[questionId][subIndex] = value;
e.currentTarget.parentElement.querySelectorAll('.tf-btn').forEach(b => b.classList.remove('selected'));
e.currentTarget.classList.add('selected');
this.updateNavigationAndProgress();
});
});
}
},
showAnswer() {
const question = state.selectedQuestions[state.currentQuestionIndex];
const container = elements.questionContainer;
const showAnswerForField = (field, isCorrect, correctAnswer) => {
if (!field) return;
field.readOnly = true;
field.style.borderColor = isCorrect ? '#28a745' : '#dc3545';
field.style.backgroundColor = isCorrect ? '#f0fff4' : '#f8d7da';
if (!isCorrect && correctAnswer) {
const correctAnsDiv = document.createElement('div');
correctAnsDiv.className = 'correct-answer-display';
// XỬ LÝ LATEX CHO ĐÁP ÁN ĐÚNG - PHƯƠNG PHÁP MỚI
const answerContainer = document.createElement('div');
answerContainer.innerHTML = `Đáp án đúng: ${correctAnswer}`;
latexRenderer.safeRenderElement(answerContainer);
correctAnsDiv.innerHTML = `
${answerContainer.innerHTML} `;
field.parentElement.appendChild(correctAnsDiv);
// Kích hoạt MathJax cho đáp án mới
setTimeout(() => latexRenderer.triggerMathJax(), 50);
}
};
container.querySelectorAll('.choice, .tf-btn, math-field').forEach(el => {
el.style.pointerEvents = 'none';
if (el.tagName === 'MATH-FIELD') el.readOnly = true;
});
switch (question.type) {
case 'multipleChoice':
const correctIndex = question.choices.findIndex(c => c.answer === "true");
const userChoiceIndex = (state.userAnswers[question.ID] || [])[0];
container.querySelectorAll('.choice').forEach(choice => {
const choiceOriginalIndex = parseInt(choice.dataset.index);
choice.style.boxShadow = 'none';
if (choiceOriginalIndex === correctIndex) {
choice.style.borderColor = '#28a745';
choice.style.backgroundColor = '#d4edda';
} else if (choiceOriginalIndex === userChoiceIndex) {
choice.style.borderColor = '#dc3545';
choice.style.backgroundColor = '#f8d7da';
}
});
break;
case 'essay':
const mf = container.querySelector(`math-field[data-id="${question.ID}"]`);
const isEssayCorrect = scoringSystem.checkEssayAnswer(question, state.userAnswers[question.ID]);
showAnswerForField(mf, isEssayCorrect, question.answer?.[0]);
break;
case 'trueFalse':
const userSubAnswers = state.userAnswers[question.ID] || [];
container.querySelectorAll('.sub-question').forEach((subEl, index) => {
const correctAnswer = question.subQuestions[index].answer === 'true';
const userAnswer = userSubAnswers[index];
const trueBtn = subEl.querySelector('.tf-btn[data-value="true"]');
const falseBtn = subEl.querySelector('.tf-btn[data-value="false"]');
if (correctAnswer) {
trueBtn.style.borderColor = '#28a745';
trueBtn.style.backgroundColor = '#d4edda';
} else {
falseBtn.style.borderColor = '#28a745';
falseBtn.style.backgroundColor = '#d4edda';
}
if (userAnswer !== undefined && userAnswer !== correctAnswer) {
const wrongBtn = userAnswer ? trueBtn : falseBtn;
wrongBtn.style.borderColor = '#dc3545';
wrongBtn.style.backgroundColor = '#f8d7da';
}
});
break;
}
// KÍCH HOẠT LẠI MATHJAX SAU KHI HIỂN THỊ ĐÁP ÁN
setTimeout(() => {
latexRenderer.triggerMathJax();
}, 200);
},
isBasicQuizFullyAnswered() {
return state.selectedQuestions.every(q => this.isQuestionFullyAnswered(q.ID));
},
isQuestionStarted(key) {
const question = state.selectedQuestions.find(q => q.ID == key);
const answer = state.userAnswers[key];
if (!answer) return false;
if (question.type === 'trueFalse') {
return answer.some(subAnswer => subAnswer !== null);
}
return answer && answer[0] !== undefined && answer[0] !== null && String(answer[0]).trim() !== '';
},
isQuestionFullyAnswered(key) {
const question = state.selectedQuestions.find(q => q.ID == key);
const answer = state.userAnswers[key];
if (question.type === 'trueFalse') {
return answer && answer.every(subAnswer => subAnswer !== null);
}
return answer && answer[0] !== undefined && answer[0] !== null && String(answer[0]).trim() !== '';
},
updateNavigationAndProgress() {
this.updateNavigationDots();
this.updateProgressBar();
this.checkAllAnswered();
},
next() {
if (state.currentQuestionIndex < state.totalQuestions - 1) {
this.display(state.currentQuestionIndex + 1);
}
},
createNavigationDots() {
elements.navigation.innerHTML = '';
state.selectedQuestions.forEach((_, i) => {
const dot = document.createElement('div');
dot.className = 'nav-dot';
dot.textContent = i + 1;
dot.dataset.index = i;
dot.addEventListener('click', () => this.display(i));
elements.navigation.appendChild(dot);
});
},
updateNavigationDots() {
document.querySelectorAll('.nav-dot').forEach((dot, i) => {
dot.classList.remove('active', 'answered');
if (i === state.currentQuestionIndex) dot.classList.add('active');
const question = state.selectedQuestions[i];
if (this.isQuestionFullyAnswered(question.ID)) {
dot.classList.add('answered');
}
});
},
updateProgressBar() {
const answeredCount = state.selectedQuestions.filter(q => this.isQuestionFullyAnswered(q.ID)).length;
const progress = (answeredCount / state.totalQuestions) * 100;
elements.progressBar.style.width = `${progress}%`;
},
checkAllAnswered() {
if (this.isBasicQuizFullyAnswered()) {
elements.submitBtn.disabled = false;
elements.extraExerciseContainer.style.display = 'block';
} else {
elements.submitBtn.disabled = true;
elements.extraExerciseContainer.style.display = 'none';
}
}
};
// Module quản lý bài tập nâng cao
const advancedExercises = {
display(index) {
saveCurrentQuestionState(state.userExtraAnswers);
state.currentExtraIndex = index;
const questionKey = state.extraKeys[index];
const question = extraPracticeData[questionKey];
// RENDER LATEX CHO CÂU HỎI CHÍNH
const renderedQuestion = latexRenderer.renderLatex(question.mainQuestion);
let html = `
`;
elements.questionContainer.innerHTML = html;
// KÍCH HOẠT RENDER LATEX VÀ MATHLIVE SAU KHI CHÈN HTML
setTimeout(() => {
latexRenderer.renderContainer(elements.questionContainer);
latexRenderer.initMathFields(elements.questionContainer);
}, 100);
this.addAnswerEventListeners();
if (state.advancedQuizSubmitted) {
this.showAnswer();
}
},
addAnswerEventListeners() {
const container = elements.questionContainer;
const questionId = container.querySelector('.question-container')?.dataset.id;
if (!questionId) return;
// Xử lý sự kiện cho lựa chọn trắc nghiệm
container.querySelectorAll(".choice").forEach(choice => {
choice.addEventListener("click", e => {
utils.playClickSound();
state.userExtraAnswers[questionId] = [parseInt(e.currentTarget.dataset.index)];
e.currentTarget.closest('.choices-container').querySelectorAll('.choice').forEach(c => c.classList.remove('selected'));
e.currentTarget.classList.add('selected');
});
});
// Xử lý sự kiện cho đúng/sai
container.querySelectorAll(".tf-btn").forEach(btn => {
btn.addEventListener("click", e => {
utils.playClickSound();
const stmtIndex = parseInt(e.currentTarget.dataset.index);
const value = e.currentTarget.dataset.value === 'true';
if (!state.userExtraAnswers[questionId]) {
state.userExtraAnswers[questionId] = [];
}
// Toggle selection
const options = e.currentTarget.parentElement.querySelectorAll('.tf-btn');
options.forEach(opt => opt.classList.remove('selected'));
e.currentTarget.classList.add('selected');
state.userExtraAnswers[questionId][stmtIndex] = value;
});
});
// Xử lý sự kiện cho math-field (tự luận)
container.querySelectorAll('math-field').forEach(mf => {
if (!mf.hasAttribute('readonly')) {
mf.addEventListener('input', (e) => {
const fieldId = mf.id;
if (fieldId.startsWith('essay-part-')) {
// Xử lý essay_multi
const parts = fieldId.split('-');
const qId = parts[2];
const partId = parts[3];
const key = `${qId}_${partId}`;
state.userExtraAnswers[key] = [e.target.value];
} else if (fieldId.startsWith('essay-')) {
// Xử lý essay thường
const qId = fieldId.replace('essay-', '');
state.userExtraAnswers[qId] = [e.target.value];
}
});
}
});
},
showAnswer() {
const questionKey = state.extraKeys[state.currentExtraIndex];
const question = extraPracticeData[questionKey];
const container = elements.questionContainer;
container.querySelectorAll('.choice, math-field, .tf-btn').forEach(el => {
el.style.pointerEvents = 'none';
if (el.tagName === 'MATH-FIELD') el.readOnly = true;
});
switch (question.type) {
case 'multipleChoice':
const correctIndex = question.choices.findIndex(c => c.answer === "true");
const userChoiceIndex = (state.userExtraAnswers[question.ID] || [])[0];
container.querySelectorAll('.choice').forEach(choice => {
const choiceOriginalIndex = parseInt(choice.dataset.index);
choice.style.boxShadow = 'none';
if (choiceOriginalIndex === correctIndex) {
choice.style.borderColor = '#28a745';
choice.style.backgroundColor = '#f0fff4';
} else if (choiceOriginalIndex === userChoiceIndex && userChoiceIndex !== correctIndex) {
choice.style.borderColor = '#dc3545';
choice.style.backgroundColor = '#f8d7da';
}
});
break;
case 'trueFalse':
container.querySelectorAll('.sub-question').forEach((subQ, index) => {
const correctAnswer = question.subQuestions[index].answer === 'true';
const userAnswer = (state.userExtraAnswers[question.ID] || [])[index];
const options = subQ.querySelectorAll('.tf-btn');
options.forEach(option => {
const optionValue = option.dataset.value === 'true';
if (optionValue === correctAnswer) {
option.style.borderColor = '#28a745';
option.style.backgroundColor = '#f0fff4';
} else if (optionValue === userAnswer && userAnswer !== correctAnswer) {
option.style.borderColor = '#dc3545';
option.style.backgroundColor = '#f8d7da';
}
});
});
break;
case 'essay':
const userEssayAnswer = (state.userExtraAnswers[question.ID] || [])[0] || '';
const correctEssayAnswer = question.answer ? question.answer[0] : '';
if (correctEssayAnswer) {
const answerDisplay = document.createElement('div');
answerDisplay.className = 'correct-answer-display';
answerDisplay.innerHTML = `
✓ Đáp án:
${correctEssayAnswer}
`;
const essayContainer = container.querySelector('.essay-answer-container');
if (essayContainer) {
essayContainer.appendChild(answerDisplay);
// Khởi tạo MathLive cho đáp án
setTimeout(() => {
const answerField = answerDisplay.querySelector('math-field');
if (answerField) {
MathLive.makeMathField(answerField, { readOnly: true });
}
}, 50);
}
}
break;
case 'essay-multi':
for (const [partId, partData] of Object.entries(question.parts)) {
const correctPartAnswer = partData.answer ? partData.answer[0] : '';
if (correctPartAnswer) {
const partContainer = container.querySelector(`#essay-part-${question.ID}-${partId}`)?.parentElement;
if (partContainer) {
const answerDisplay = document.createElement('div');
answerDisplay.className = 'correct-answer-display';
answerDisplay.innerHTML = `
✓ Đáp án phần ${partId}:
${correctPartAnswer}
`;
partContainer.appendChild(answerDisplay);
// Khởi tạo MathLive cho đáp án
setTimeout(() => {
const answerField = answerDisplay.querySelector('math-field');
if (answerField) {
MathLive.makeMathField(answerField, { readOnly: true });
}
}, 50);
}
}
}
break;
}
// Đảm bảo tất cả công thức toán được render lại
setTimeout(() => {
latexRenderer.triggerMathJax();
}, 200);
},
next() {
if (state.currentExtraIndex < state.extraKeys.length - 1) {
this.display(state.currentExtraIndex + 1);
}
},
prev() {
if (state.currentExtraIndex > 0) {
this.display(state.currentExtraIndex - 1);
}
}
};
// Module quản lý bài kiểm tra
const quiz = {
submitBasicOnly() {
try {
if (state.basicQuizSubmitted) return;
saveCurrentQuestionState(state.userAnswers);
if (!questions.isBasicQuizFullyAnswered()) {
utils.showNotification("Vui lòng trả lời hết các câu hỏi.", true);
return;
}
if (confirm("Bạn có chắc chắn muốn nộp bài ngay và chỉ tính điểm phần cơ bản?")) {
this.finalizeBasicSubmission();
}
} catch (error) {
console.error("Lỗi khi nộp bài cơ bản:", error);
utils.showNotification("Có lỗi xảy ra khi xử lý. Vui lòng thử lại.", true);
}
},
finalizeBasicSubmission() {
try {
timer.stop();
state.basicQuizSubmitted = true;
const score = scoringSystem.calculateBasicScore();
this.toggleButtons(true);
elements.answerBtn.disabled = false;
elements.nextBtn.disabled = false;
this.showResult(score, false);
elements.extraExerciseContainer.style.display = 'block';
} catch (error) {
console.error("Lỗi khi hoàn tất bài cơ bản:", error);
utils.showNotification("Có lỗi khi xử lý kết quả", true);
}
},
showResult(score, isAdvanced) {
try {
if (!elements.resultModal || !elements.resultTime || !elements.resultMessage) {
throw new Error("Thiếu phần tử DOM để hiển thị kết quả");
}
this.setFormData(score, isAdvanced);
elements.resultTime.textContent = timer.getElapsedTime();
elements.totalAllowedTime.textContent = timeManager.formatTime(state.totalTimeAllowed);
if (isAdvanced) {
const basicScore = scoringSystem.calculateBasicScore();
const advancedScore = scoringSystem.calculateAdvancedScore();
const totalScore = Math.min(basicScore + advancedScore, 10);
elements.resultMessage.innerHTML = `
Mã đề: ${state.examCode}
Điểm phần cơ bản: ${basicScore.toFixed(2)}/7.00
Điểm phần nâng cao: ${advancedScore.toFixed(2)}/3.00
Tổng điểm: ${totalScore.toFixed(2)}/10.00
`;
} else {
elements.resultMessage.innerHTML = `
Mã đề: ${state.examCode}
Điểm phần cơ bản: ${score.toFixed(2)}/7.00
Bạn có thể làm thêm bài nâng cao để đạt tối đa 10 điểm
`;
}
elements.resultModal.style.display = 'flex';
questions.showAnswer();
} catch (error) {
console.error("Lỗi khi hiển thị kết quả:", error);
utils.showNotification("Không thể hiển thị kết quả", true);
}
},
toggleButtons(disable) {
try {
const buttons = ['submitBtn', 'advanced-submit-btn'];
buttons.forEach(btnId => {
const btn = document.getElementById(btnId) || elements[btnId];
if (btn) btn.disabled = disable;
});
} catch (error) {
console.error("Lỗi khi thay đổi trạng thái nút:", error);
}
},
setFormData(score, isAdvanced) {
try {
document.getElementById('hidden-result').value = `${score.toFixed(2)}/${isAdvanced ? '10' : '7'}`;
document.getElementById('hidden-lesson').value = config.LESSON_TITLE || '';
document.getElementById('hidden-time').value = timer.getElapsedTime() || '00:00';
document.getElementById('hidden-semester').value = 'Học kì 1';
document.getElementById('hidden-exam-code').value = state.examCode;
} catch (error) {
console.error("Lỗi khi điền form:", error);
}
},
// Khởi tạo bài kiểm tra với thời gian đã chọn
initializeQuiz() {
const totalMinutes = parseInt(elements.totalTimeInput.value) || 20;
if (totalMinutes < 5) {
utils.showNotification("Thời gian làm bài tối thiểu là 5 phút", true);
return;
}
// Chọn câu hỏi dựa trên thời gian
state.selectedQuestions = timeManager.calculateQuestionCount(totalMinutes);
state.totalQuestions = state.selectedQuestions.length;
if (state.totalQuestions === 0) {
utils.showNotification("Thời gian quá ngắn để làm bất kỳ câu hỏi nào", true);
return;
}
// Thiết lập thời gian
state.totalTimeAllowed = totalMinutes * 60;
// Ẩn phần thiết lập, hiển thị phần làm bài
elements.setupContainer.style.display = 'none';
elements.quizHeader.style.display = 'block';
elements.mainContent.style.display = 'block';
// Khởi tạo các thành phần
state.examCode = utils.generateExamCode(4);
timer.startCountdown();
questions.createNavigationDots();
questions.display(0);
utils.showNotification(`Bài kiểm tra đã bắt đầu với ${state.totalQuestions} câu hỏi trong ${totalMinutes} phút`);
}
};
// Module đánh giá sao
const rating = {
init() { elements.stars.forEach(star => { star.addEventListener('click', () => { this.setRating(parseInt(star.dataset.value)); }); }); },
setRating(value) { state.userRating = value; elements.stars.forEach((star, index) => { star.classList.toggle('active', index < value); }); elements.ratingText.textContent = `Đánh giá: ${value}/5`; document.getElementById('hidden-rating').value = value; }
};
/* ============================================
* PHẦN 4: KHỞI TẠO VÀ GẮN SỰ KIỆN
* ========================================== */
function setupEventListeners() {
// Sự kiện cho phần thiết lập thời gian
elements.totalTimeInput.addEventListener('input', () => {
timeManager.updateQuestionEstimate();
});
elements.startBtn.addEventListener('click', () => {
utils.playClickSound();
quiz.initializeQuiz();
});
// Các sự kiện hiện có
elements.submitBtn.addEventListener('click', () => quiz.submitBasicOnly());
elements.answerBtn.addEventListener('click', () => { if (state.basicQuizSubmitted) questions.showAnswer(); });
elements.nextBtn.addEventListener('click', () => questions.next());
elements.guideOkButton.addEventListener('click', () => { utils.playClickSound(); utils.hideModal(elements.guideModal); });
elements.finalCancel.addEventListener('click', () => utils.hideModal(elements.resultModal));
elements.submitForm.addEventListener('submit', handleFormSubmit);
elements.notificationClose.addEventListener('click', () => elements.notification.classList.remove('show'));
elements.extraExerciseBtn.addEventListener('click', () => { utils.playClickSound(); elements.phoneModal.style.display = 'flex'; });
elements.phoneCancel.addEventListener('click', () => { utils.playClickSound(); utils.hideModal(elements.phoneModal); });
elements.phoneSubmit.addEventListener('click', handlePhoneVerification);
rating.init();
utils.preventCopyEvents();
// Khởi tạo ước tính số câu hỏi
timeManager.updateQuestionEstimate();
}
async function handlePhoneVerification() {
utils.playClickSound();
const phone = elements.phoneInput.value.trim();
if (!phone || !/^\d{10}$/.test(phone)) {
utils.showNotification("Vui lòng nhập số điện thoại hợp lệ (10 chữ số)", true);
return;
}
elements.phoneSubmit.disabled = true;
elements.phoneSubmit.innerHTML = '
Đang kiểm tra...';
elements.checkResult.innerHTML = '';
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 20000);
const response = await fetch(`${config.PURCHASE_CHECK_URL}?phone=${phone}`, { signal: controller.signal });
clearTimeout(timeoutId);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const result = await response.json();
if (!result) throw new Error('Invalid response from server');
const isPaid = Array.isArray(result) ? result.some(item => item.paid === true) : result.valid === true;
if (isPaid) {
utils.hideModal(elements.phoneModal);
setupAdvancedQuizUI();
} else {
elements.checkResult.innerHTML = '
❌ SĐT không đúng. Vui lòng đăng ký . ';
}
} catch (error) {
handlePhoneVerificationError(error);
} finally {
elements.phoneSubmit.disabled = false;
elements.phoneSubmit.innerHTML = 'Kiểm Tra';
}
}
function setupAdvancedQuizUI() {
const container = document.querySelector('.practice-container');
const mainContent = container.querySelector('.main-content');
const advancedHeader = document.createElement('div');
advancedHeader.className = 'quiz-header extra-header';
advancedHeader.innerHTML = `
BÀI TẬP NÂNG CAO
Chương: Chuyển động thẳng - Bài tập nâng cao
`;
const oldHeader = container.querySelector('.quiz-header');
if(oldHeader) {
container.replaceChild(advancedHeader, oldHeader);
}
mainContent.innerHTML = `
< Câu Trước
Câu Tiếp >
Xem Đáp Án
Nộp Bài Nâng Cao
Làm lại từ đầu
`;
elements.questionContainer = document.getElementById('question-container');
// Khởi tạo câu hỏi nâng cao
state.extraKeys = utils.shuffleArray(Object.keys(extraPracticeData));
advancedExercises.display(0);
setupAdvancedControls();
}
function setupAdvancedControls() {
const backBtn = document.getElementById('advanced-back-btn');
const nextBtn = document.getElementById('advanced-next-btn');
const submitBtn = document.getElementById('advanced-submit-btn');
const answerBtn = document.getElementById('advanced-answer-btn');
const restartBtn = document.getElementById('back-to-basic-btn');
if (!backBtn || !nextBtn || !submitBtn || !answerBtn || !restartBtn) {
console.error('Không tìm thấy một hoặc nhiều nút điều khiển nâng cao');
return;
}
backBtn.addEventListener('click', () => { utils.playClickSound(); advancedExercises.prev(); });
nextBtn.addEventListener('click', () => { utils.playClickSound(); advancedExercises.next(); });
submitBtn.addEventListener('click', handleAdvancedSubmit);
answerBtn.addEventListener('click', () => { utils.playClickSound(); if(state.advancedQuizSubmitted) advancedExercises.showAnswer(); });
restartBtn.addEventListener('click', () => { if(confirm('Bạn có muốn làm lại bài từ đầu không?')) location.reload(); });
}
function handleAdvancedSubmit() {
utils.playClickSound();
const submitBtn = document.getElementById('advanced-submit-btn');
if (submitBtn.disabled) return;
if (!confirm("Bạn có chắc chắn muốn nộp bài tập nâng cao và kết thúc bài làm?")) {
return;
}
submitBtn.disabled = true;
submitBtn.innerHTML = '
Đang xử lý...';
saveCurrentQuestionState(state.userExtraAnswers);
if(state.timerInterval) timer.stop();
state.advancedQuizSubmitted = true;
const basicScore = scoringSystem.calculateBasicScore();
const advancedScore = scoringSystem.calculateAdvancedScore();
const totalScore = Math.min(basicScore + advancedScore, 10);
quiz.showResult(totalScore, true);
advancedExercises.showAnswer();
const answerBtn = document.getElementById('advanced-answer-btn');
if (answerBtn) answerBtn.disabled = false;
submitBtn.innerHTML = 'Đã Nộp';
}
function handlePhoneVerificationError(error) {
console.error('Phone verification error:', error);
let errorMsg = '❌ Lỗi kết nối. Vui lòng thử lại.';
if (error.name === 'AbortError') {
errorMsg = '❌ Yêu cầu hết thời gian. Vui lòng kiểm tra kết nối mạng.';
}
elements.checkResult.innerHTML = `
${errorMsg} `;
}
async function handleFormSubmit(event) {
event.preventDefault();
const form = event.target;
const submitBtn = elements.finalSubmit;
if (!form['user-name']?.value || !form['user-email']?.value || !form['user-phone']?.value || !form['user-school']?.value || !form['user-class']?.value) {
utils.showNotification('Vui lòng điền đầy đủ Họ và tên, Trường, Lớp, Email và Số điện thoại.', true);
return;
}
submitBtn.disabled = true;
submitBtn.innerHTML = '
Đang gửi...';
try {
await fetch(form.action, { method: 'POST', body: new FormData(form), mode: 'no-cors' });
utils.showNotification('Gửi kết quả thành công!');
setTimeout(() => {
utils.hideModal(elements.resultModal);
submitBtn.disabled = false;
submitBtn.innerHTML = 'Gửi kết quả';
}, 1500);
} catch (error) {
utils.showNotification('Đã xảy ra lỗi khi gửi kết quả. Vui lòng thử lại.', true);
submitBtn.disabled = false;
submitBtn.innerHTML = 'Gửi kết quả';
}
}
/* ============================================
* KHỞI CHẠY ỨNG DỤNG
* ========================================== */
return {
init: () => {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
setupEventListeners();
});
} else {
setupEventListeners();
}
},
utils: {
handleImageError: utils.handleImageError,
renderLatex: latexRenderer.renderLatex,
renderContainer: latexRenderer.renderContainer,
initMathFields: latexRenderer.initMathFields
}
};
})();
document.addEventListener('DOMContentLoaded', PhysicsQuizApp.init);