/** * BreakPilot Studio - Multiple Choice Module * * Multiple Choice Quiz-Funktionalität: * - Generierung von MC-Fragen aus Arbeitsblättern * - Interaktives Quiz mit Auswertung * - Druckfunktion (mit/ohne Lösungen) * * Refactored: 2026-01-19 */ import { t } from './i18n.js'; import { setStatus } from './api-helpers.js'; // State let currentMcData = null; let mcAnswers = {}; // DOM References let mcPreview = null; let mcBadge = null; let mcModal = null; let mcModalBody = null; let mcModalClose = null; let btnMcGenerate = null; let btnMcShow = null; let btnMcPrint = null; // Callback für aktuelle Datei let getCurrentFileCallback = null; let getEingangFilesCallback = null; let getCurrentIndexCallback = null; /** * Initialisiert das Multiple Choice Modul * @param {Object} options - Konfiguration */ export function initMcModule(options = {}) { getCurrentFileCallback = options.getCurrentFile || (() => null); getEingangFilesCallback = options.getEingangFiles || (() => []); getCurrentIndexCallback = options.getCurrentIndex || (() => 0); // DOM References mcPreview = document.getElementById('mc-preview') || options.previewEl; mcBadge = document.getElementById('mc-badge') || options.badgeEl; mcModal = document.getElementById('mc-modal') || options.modalEl; mcModalBody = document.getElementById('mc-modal-body') || options.modalBodyEl; mcModalClose = document.getElementById('mc-modal-close') || options.modalCloseEl; btnMcGenerate = document.getElementById('btn-mc-generate') || options.generateBtn; btnMcShow = document.getElementById('btn-mc-show') || options.showBtn; btnMcPrint = document.getElementById('btn-mc-print') || options.printBtn; // Event-Listener if (btnMcGenerate) { btnMcGenerate.addEventListener('click', generateMcQuestions); } if (btnMcShow) { btnMcShow.addEventListener('click', openMcModal); } if (btnMcPrint) { btnMcPrint.addEventListener('click', openMcPrintDialog); } if (mcModalClose) { mcModalClose.addEventListener('click', closeMcModal); } if (mcModal) { mcModal.addEventListener('click', (ev) => { if (ev.target === mcModal) { closeMcModal(); } }); } // Event für Datei-Wechsel window.addEventListener('fileSelected', () => { loadMcPreviewForCurrent(); }); } /** * Generiert MC-Fragen für alle Arbeitsblätter */ export async function generateMcQuestions() { try { setStatus(t('mc_generating') || 'Generiere MC-Fragen …', t('ai_working') || 'Bitte warten, KI arbeitet.', 'busy'); if (mcBadge) mcBadge.textContent = t('generating') || 'Generiert...'; const resp = await fetch('/api/generate-mc', { method: 'POST' }); if (!resp.ok) { throw new Error('HTTP ' + resp.status); } const result = await resp.json(); if (result.status === 'OK' && result.generated.length > 0) { setStatus(t('mc_generated') || 'MC-Fragen generiert', result.generated.length + ' ' + (t('files_created') || 'Dateien erstellt')); if (mcBadge) mcBadge.textContent = t('ready') || 'Fertig'; if (btnMcShow) btnMcShow.style.display = 'inline-block'; if (btnMcPrint) btnMcPrint.style.display = 'inline-block'; // Lade die erste MC-Datei für Vorschau await loadMcPreviewForCurrent(); } else if (result.errors && result.errors.length > 0) { setStatus(t('mc_error') || 'Fehler bei MC-Generierung', result.errors[0].error, 'error'); if (mcBadge) mcBadge.textContent = t('error') || 'Fehler'; } else { setStatus(t('no_mc_generated') || 'Keine MC-Fragen generiert', t('analysis_missing') || 'Möglicherweise fehlen Analyse-Daten.', 'error'); if (mcBadge) mcBadge.textContent = t('ready') || 'Bereit'; } } catch (e) { console.error('MC-Generierung fehlgeschlagen:', e); setStatus(t('mc_error') || 'Fehler bei MC-Generierung', String(e), 'error'); if (mcBadge) mcBadge.textContent = t('error') || 'Fehler'; } } /** * Lädt MC-Vorschau für die aktuelle Datei */ export async function loadMcPreviewForCurrent() { const eingangFiles = getEingangFilesCallback(); const currentIndex = getCurrentIndexCallback(); if (!eingangFiles.length) { if (mcPreview) mcPreview.innerHTML = '
' + (t('no_worksheets') || 'Keine Arbeitsblätter vorhanden.') + '
'; return; } const currentFile = eingangFiles[currentIndex]; if (!currentFile) return; try { const resp = await fetch('/api/mc-data/' + encodeURIComponent(currentFile)); const result = await resp.json(); if (result.status === 'OK' && result.data) { currentMcData = result.data; renderMcPreview(result.data); if (btnMcShow) btnMcShow.style.display = 'inline-block'; if (btnMcPrint) btnMcPrint.style.display = 'inline-block'; } else { if (mcPreview) mcPreview.innerHTML = '
' + (t('no_mc_for_worksheet') || 'Noch keine MC-Fragen für dieses Arbeitsblatt generiert.') + '
'; currentMcData = null; if (btnMcPrint) btnMcPrint.style.display = 'none'; } } catch (e) { console.error('Fehler beim Laden der MC-Daten:', e); if (mcPreview) mcPreview.innerHTML = ''; } } /** * Rendert die MC-Vorschau * @param {Object} mcData - MC-Daten */ function renderMcPreview(mcData) { if (!mcPreview) return; if (!mcData || !mcData.questions || mcData.questions.length === 0) { mcPreview.innerHTML = '
' + (t('no_questions') || 'Keine Fragen vorhanden.') + '
'; return; } const questions = mcData.questions; const metadata = mcData.metadata || {}; let html = ''; // Zeige Metadaten if (metadata.grade_level || metadata.subject) { html += '
'; if (metadata.subject) { html += '
' + (t('subject') || 'Fach') + ': ' + escapeHtml(metadata.subject) + '
'; } if (metadata.grade_level) { html += '
' + (t('grade') || 'Stufe') + ': ' + escapeHtml(metadata.grade_level) + '
'; } html += '
' + (t('questions') || 'Fragen') + ': ' + questions.length + '
'; html += '
'; } // Zeige erste 2 Fragen als Vorschau const previewQuestions = questions.slice(0, 2); previewQuestions.forEach((q, idx) => { html += '
'; html += '
' + (idx + 1) + '. ' + escapeHtml(q.question) + '
'; html += '
'; q.options.forEach(opt => { html += '
'; html += '' + opt.id + ') ' + escapeHtml(opt.text); html += '
'; }); html += '
'; html += '
'; }); if (questions.length > 2) { html += '
+ ' + (questions.length - 2) + ' ' + (t('more_questions') || 'weitere Fragen') + '
'; } mcPreview.innerHTML = html; // Event-Listener für Antwort-Auswahl mcPreview.querySelectorAll('.mc-option').forEach(optEl => { optEl.addEventListener('click', () => handleMcOptionClick(optEl)); }); } /** * Behandelt Klick auf eine MC-Option * @param {Element} optEl - Angeklicktes Option-Element */ function handleMcOptionClick(optEl) { const qid = optEl.getAttribute('data-qid'); const optId = optEl.getAttribute('data-opt'); if (!currentMcData) return; // Finde die Frage const question = currentMcData.questions.find(q => q.id === qid); if (!question) return; // Markiere alle Optionen dieser Frage const questionEl = optEl.closest('.mc-question'); const allOptions = questionEl.querySelectorAll('.mc-option'); allOptions.forEach(opt => { opt.classList.remove('selected', 'correct', 'incorrect'); const thisOptId = opt.getAttribute('data-opt'); if (thisOptId === question.correct_answer) { opt.classList.add('correct'); } else if (thisOptId === optId) { opt.classList.add('incorrect'); } }); // Speichere Antwort mcAnswers[qid] = optId; // Zeige Feedback const isCorrect = optId === question.correct_answer; let feedbackEl = questionEl.querySelector('.mc-feedback'); if (!feedbackEl) { feedbackEl = document.createElement('div'); feedbackEl.className = 'mc-feedback'; questionEl.appendChild(feedbackEl); } if (isCorrect) { feedbackEl.style.background = 'rgba(34,197,94,0.1)'; feedbackEl.style.borderColor = 'rgba(34,197,94,0.3)'; feedbackEl.style.color = 'var(--bp-accent)'; feedbackEl.textContent = (t('correct') || 'Richtig!') + ' ' + (question.explanation || ''); } else { feedbackEl.style.background = 'rgba(239,68,68,0.1)'; feedbackEl.style.borderColor = 'rgba(239,68,68,0.3)'; feedbackEl.style.color = '#ef4444'; feedbackEl.textContent = (t('incorrect') || 'Leider falsch.') + ' ' + (question.explanation || ''); } } /** * Öffnet das MC-Quiz-Modal */ export function openMcModal() { if (!currentMcData || !currentMcData.questions) { alert(t('no_mc_questions') || 'Keine MC-Fragen vorhanden. Bitte zuerst generieren.'); return; } mcAnswers = {}; // Reset Antworten renderMcModal(currentMcData); if (mcModal) mcModal.classList.remove('hidden'); } /** * Schließt das MC-Modal */ export function closeMcModal() { if (mcModal) mcModal.classList.add('hidden'); } /** * Rendert den Modal-Inhalt * @param {Object} mcData - MC-Daten */ function renderMcModal(mcData) { if (!mcModalBody) return; const questions = mcData.questions; const metadata = mcData.metadata || {}; let html = ''; // Header mit Metadaten html += '
'; if (metadata.source_title) { html += '
' + (t('worksheet') || 'Arbeitsblatt') + ': ' + escapeHtml(metadata.source_title) + '
'; } if (metadata.subject) { html += '
' + (t('subject') || 'Fach') + ': ' + escapeHtml(metadata.subject) + '
'; } if (metadata.grade_level) { html += '
' + (t('grade') || 'Stufe') + ': ' + escapeHtml(metadata.grade_level) + '
'; } html += '
'; // Alle Fragen questions.forEach((q, idx) => { html += '
'; html += '
' + (idx + 1) + '. ' + escapeHtml(q.question) + '
'; html += '
'; q.options.forEach(opt => { html += '
'; html += '' + opt.id + ') ' + escapeHtml(opt.text); html += '
'; }); html += '
'; html += '
'; }); // Auswertungs-Button html += '
'; html += ''; html += '
'; mcModalBody.innerHTML = html; // Event-Listener mcModalBody.querySelectorAll('.mc-option').forEach(optEl => { optEl.addEventListener('click', () => { const qid = optEl.getAttribute('data-qid'); const optId = optEl.getAttribute('data-opt'); // Deselektiere andere Optionen der gleichen Frage const questionEl = optEl.closest('.mc-question'); questionEl.querySelectorAll('.mc-option').forEach(o => o.classList.remove('selected')); optEl.classList.add('selected'); mcAnswers[qid] = optId; }); }); const btnEvaluate = document.getElementById('btn-mc-evaluate'); if (btnEvaluate) { btnEvaluate.addEventListener('click', evaluateMcQuiz); } } /** * Wertet das Quiz aus */ function evaluateMcQuiz() { if (!currentMcData || !mcModalBody) return; let correct = 0; let total = currentMcData.questions.length; currentMcData.questions.forEach(q => { const questionEl = mcModalBody.querySelector('.mc-question[data-qid="' + q.id + '"]'); if (!questionEl) return; const userAnswer = mcAnswers[q.id]; const allOptions = questionEl.querySelectorAll('.mc-option'); allOptions.forEach(opt => { opt.classList.remove('correct', 'incorrect'); const optId = opt.getAttribute('data-opt'); if (optId === q.correct_answer) { opt.classList.add('correct'); } else if (optId === userAnswer && userAnswer !== q.correct_answer) { opt.classList.add('incorrect'); } }); // Zeige Erklärung let feedbackEl = questionEl.querySelector('.mc-feedback'); if (!feedbackEl) { feedbackEl = document.createElement('div'); feedbackEl.className = 'mc-feedback'; questionEl.appendChild(feedbackEl); } if (userAnswer === q.correct_answer) { correct++; feedbackEl.style.background = 'rgba(34,197,94,0.1)'; feedbackEl.style.borderColor = 'rgba(34,197,94,0.3)'; feedbackEl.style.color = 'var(--bp-accent)'; feedbackEl.textContent = (t('correct') || 'Richtig!') + ' ' + (q.explanation || ''); } else if (userAnswer) { feedbackEl.style.background = 'rgba(239,68,68,0.1)'; feedbackEl.style.borderColor = 'rgba(239,68,68,0.3)'; feedbackEl.style.color = '#ef4444'; feedbackEl.textContent = (t('incorrect') || 'Falsch.') + ' ' + (q.explanation || ''); } else { feedbackEl.style.background = 'rgba(148,163,184,0.1)'; feedbackEl.style.borderColor = 'rgba(148,163,184,0.3)'; feedbackEl.style.color = 'var(--bp-text-muted)'; feedbackEl.textContent = (t('not_answered') || 'Nicht beantwortet.') + ' ' + (t('correct_was') || 'Richtig wäre:') + ' ' + q.correct_answer.toUpperCase(); } }); // Zeige Gesamtergebnis const percentage = Math.round(correct / total * 100); const resultHtml = '
' + '
' + correct + ' ' + (t('of') || 'von') + ' ' + total + ' ' + (t('correct_answers') || 'richtig') + '
' + '
' + percentage + '% ' + (t('percent_correct') || 'korrekt') + '
' + '
'; const existingResult = mcModalBody.querySelector('.mc-result'); if (existingResult) { existingResult.remove(); } const resultDiv = document.createElement('div'); resultDiv.className = 'mc-result'; resultDiv.innerHTML = resultHtml; mcModalBody.appendChild(resultDiv); } /** * Öffnet den Druck-Dialog */ export function openMcPrintDialog() { if (!currentMcData) { alert(t('no_mc_questions') || 'Keine MC-Fragen vorhanden.'); return; } const eingangFiles = getEingangFilesCallback(); const currentIndex = getCurrentIndexCallback(); const currentFile = eingangFiles[currentIndex]; const confirmMsg = (t('mc_print_with_answers') || 'Mit Lösungen drucken?') + '\n\nOK = ' + (t('solution_sheet') || 'Lösungsblatt mit markierten Antworten') + '\n' + (t('cancel') || 'Abbrechen') + ' = ' + (t('exercise_sheet') || 'Übungsblatt ohne Lösungen'); const choice = confirm(confirmMsg); const url = '/api/print-mc/' + encodeURIComponent(currentFile) + '?show_answers=' + choice; window.open(url, '_blank'); } // === Getter und Setter === /** * Gibt die aktuellen MC-Daten zurück * @returns {Object|null} */ export function getMcData() { return currentMcData; } /** * Setzt die MC-Daten * @param {Object} data */ export function setMcData(data) { currentMcData = data; if (data) { renderMcPreview(data); } } /** * Helper: HTML-Escape */ function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }