/* eslint-env browser */ // Polished single-question card quiz renderer (choice / true-false only) const QuizEngine = (function() { // Top-level storage key; inside we'll store per-quiz results by quizId const ROOT_KEY = 'weblesson_quiz_v1'; function makeStorage() { const raw = localStorage.getItem(ROOT_KEY) || '{}'; try {return JSON.parse(raw);} catch (e) {return {};} } function saveStorage(obj) {localStorage.setItem(ROOT_KEY, JSON.stringify(obj));} // Render a quiz as a card UI. `quizMeta` may contain id/title; questions array contains {id, question, options[], answer, type} function renderQuiz(container, questions, quizMeta = {}) { if (!Array.isArray(questions) || questions.length === 0) { container.innerHTML = '
ไม่มีแบบทดสอบ
'; return; } const quizId = quizMeta.id || quizMeta.src || ('quiz_' + (questions[0].id || Date.now())); // ensure only supported types const filtered = questions.map(q => ({ id: q.id || String(Math.random()).slice(2, 8), question: q.question || '', options: Array.isArray(q.options) ? q.options : [], answer: typeof q.answer !== 'undefined' ? q.answer : 0, type: q.type === 'true-false' ? 'true-false' : 'choice', explanation: q.explanation || '' })); const total = filtered.length; let idx = 0; function renderCard() { container.innerHTML = ''; const card = document.createElement('div'); card.className = 'quiz-card'; const header = document.createElement('div'); header.className = 'quiz-header'; const title = document.createElement('div'); title.className = 'quiz-title'; title.textContent = quizMeta.title || 'แบบทดสอบ'; const progress = document.createElement('div'); progress.className = 'quiz-progress'; progress.textContent = `คำถาม ${idx + 1} / ${total}`; header.appendChild(title); header.appendChild(progress); const body = document.createElement('div'); body.className = 'quiz-body'; const qobj = filtered[idx]; const qEl = document.createElement('div'); qEl.className = 'quiz-question'; qEl.textContent = qobj.question; const optionsWrap = document.createElement('div'); optionsWrap.className = 'quiz-options'; const opts = qobj.type === 'true-false' ? ['True', 'False'] : qobj.options; opts.forEach((opt, i) => { const row = document.createElement('label'); row.className = 'quiz-option'; row.setAttribute('data-choice', String(i)); const radio = document.createElement('input'); radio.type = 'radio'; radio.name = 'quiz_opt_' + qobj.id; radio.value = String(i); // visually hidden, we use the whole label as button const span = document.createElement('div'); span.className = 'label-text'; span.textContent = opt; row.appendChild(radio); row.appendChild(span); row.addEventListener('click', () => handleSelect(qobj, i, row)); optionsWrap.appendChild(row); }); body.appendChild(qEl); body.appendChild(optionsWrap); const actions = document.createElement('div'); actions.className = 'quiz-actions'; const skip = document.createElement('button'); skip.className = 'quiz-btn ghost'; skip.textContent = idx < total - 1 ? 'ข้าม' : 'ดูผล'; skip.addEventListener('click', () => {if (idx < total - 1) {idx++; renderCard();} else {showSummary();} }); const next = document.createElement('button'); next.className = 'quiz-btn'; next.textContent = idx < total - 1 ? 'ถัดไป' : 'ส่งคำตอบ'; next.addEventListener('click', () => {if (idx < total - 1) {idx++; renderCard();} else {showSummary();} }); actions.appendChild(skip); actions.appendChild(next); card.appendChild(header); card.appendChild(body); card.appendChild(actions); container.appendChild(card); // restore previous answer if present const stored = makeStorage(); const quizData = stored[quizId] || {}; const saved = quizData[qobj.id]; if (typeof saved !== 'undefined') { const chosen = String(saved.choice); const label = card.querySelector('.quiz-option[data-choice="' + chosen + '"]'); if (label) label.classList.add(saved.correct ? 'correct' : 'wrong'); } } function handleSelect(qobj, choiceIndex, rowEl) { // disable options for this question const card = rowEl.closest('.quiz-card'); card.querySelectorAll('.quiz-option').forEach(r => r.style.pointerEvents = 'none'); const correct = Number(qobj.answer) === Number(choiceIndex); rowEl.classList.add(correct ? 'correct' : 'wrong'); // save per-quiz/per-question const store = makeStorage(); store[quizId] = store[quizId] || {}; store[quizId][qobj.id] = {choice: choiceIndex, correct: !!correct, ts: Date.now()}; saveStorage(store); // show explanation if any if (qobj.explanation) { const fb = document.createElement('div'); fb.className = 'quiz-feedback'; fb.textContent = qobj.explanation; const body = card.querySelector('.quiz-body'); body.appendChild(fb); } } function showSummary() { const store = makeStorage(); const data = (store[quizId]) || {}; let correctCount = 0; filtered.forEach(q => {if (data[q.id] && data[q.id].correct) correctCount++;}); container.innerHTML = ''; const card = document.createElement('div'); card.className = 'quiz-card'; const header = document.createElement('div'); header.className = 'quiz-header'; const title = document.createElement('div'); title.className = 'quiz-title'; title.textContent = quizMeta.title || 'ผลการทดสอบ'; const progress = document.createElement('div'); progress.className = 'quiz-progress'; progress.textContent = `ได้ ${correctCount} / ${total}`; header.appendChild(title); header.appendChild(progress); const score = document.createElement('div'); score.className = 'quiz-score'; score.innerHTML = `
${Math.round((correctCount / total) * 100)}%
คำตอบถูก ${correctCount} จาก ${total}
`; const actions = document.createElement('div'); actions.className = 'quiz-actions'; const retry = document.createElement('button'); retry.className = 'quiz-btn'; retry.textContent = 'ทำใหม่'; retry.addEventListener('click', () => { // clear quiz results const s = makeStorage(); if (s[quizId]) delete s[quizId]; saveStorage(s); idx = 0; renderCard(); }); const close = document.createElement('button'); close.className = 'quiz-btn ghost'; close.textContent = 'ปิด'; close.addEventListener('click', () => {container.innerHTML = '';}); actions.appendChild(close); actions.appendChild(retry); card.appendChild(header); card.appendChild(score); card.appendChild(actions); container.appendChild(card); } renderCard(); } // convenience: load/save helpers exposed for tests return {renderQuiz, _makeStorage: makeStorage}; })(); // Expose globally window.QuizEngine = QuizEngine;