/** * BreakPilot Studio - Q&A Leitner Module * * Frage-Antwort Lernkarten mit Leitner-System: * - Generieren von Q&A aus analysierten Arbeitsblättern * - Leitner-Box-System (Neu, Gelernt, Gefestigt) * - Lern-Session mit Selbstbewertung * - Fortschrittsspeicherung * * Refactored: 2026-01-19 */ import { t } from './i18n.js'; import { setStatus, setStatusWorking, setStatusError, setStatusSuccess, fetchJSON } from './api-helpers.js'; // State let currentQaData = null; let currentQaIndex = 0; let qaSessionStats = { correct: 0, incorrect: 0, total: 0 }; // DOM References let qaPreview = null; let qaBadge = null; let qaModal = null; let qaModalBody = null; let qaModalClose = null; let btnQaGenerate = null; let btnQaLearn = null; let btnQaPrint = null; // Callback für aktuelle Datei let getCurrentFileCallback = null; let getFilesCallback = null; let getCurrentIndexCallback = null; /** * Initialisiert das Q&A Leitner-Modul * @param {Object} options - Konfiguration */ export function initQaModule(options = {}) { getCurrentFileCallback = options.getCurrentFile || (() => null); getFilesCallback = options.getFiles || (() => []); getCurrentIndexCallback = options.getCurrentIndex || (() => 0); qaPreview = document.getElementById('qa-preview') || options.previewEl; qaBadge = document.getElementById('qa-badge') || options.badgeEl; qaModal = document.getElementById('qa-modal') || options.modalEl; qaModalBody = document.getElementById('qa-modal-body') || options.modalBodyEl; qaModalClose = document.getElementById('qa-modal-close') || options.closeBtn; btnQaGenerate = document.getElementById('btn-qa-generate') || options.generateBtn; btnQaLearn = document.getElementById('btn-qa-learn') || options.learnBtn; btnQaPrint = document.getElementById('btn-qa-print') || options.printBtn; // Event-Listener if (btnQaGenerate) { btnQaGenerate.addEventListener('click', generateQaQuestions); } if (btnQaLearn) { btnQaLearn.addEventListener('click', openQaModal); } if (btnQaPrint) { btnQaPrint.addEventListener('click', openQaPrintDialog); } if (qaModalClose) { qaModalClose.addEventListener('click', closeQaModal); } if (qaModal) { qaModal.addEventListener('click', (ev) => { if (ev.target === qaModal) { closeQaModal(); } }); } // Event für Datei-Wechsel window.addEventListener('fileSelected', () => { loadQaPreviewForCurrent(); }); } /** * Generiert Q&A für alle Dateien */ export async function generateQaQuestions() { try { setStatusWorking(t('status_generating_qa') || 'Generiere Q&A ...'); if (qaBadge) qaBadge.textContent = t('mc_generating') || 'Generiert...'; const resp = await fetch('/api/generate-qa', { method: 'POST' }); if (!resp.ok) { throw new Error('HTTP ' + resp.status); } const result = await resp.json(); if (result.status === 'OK' && result.generated.length > 0) { setStatusSuccess( t('status_qa_generated') || 'Q&A generiert', result.generated.length + ' ' + (t('status_files_created') || 'Dateien erstellt') ); if (qaBadge) qaBadge.textContent = t('mc_done') || 'Fertig'; if (btnQaLearn) btnQaLearn.style.display = 'inline-block'; if (btnQaPrint) btnQaPrint.style.display = 'inline-block'; await loadQaPreviewForCurrent(); } else if (result.errors && result.errors.length > 0) { setStatusError(t('error') || 'Fehler', result.errors[0].error); if (qaBadge) qaBadge.textContent = t('mc_error') || 'Fehler'; } else { setStatusError(t('error') || 'Fehler', 'Keine Q&A generiert.'); if (qaBadge) qaBadge.textContent = t('mc_ready') || 'Bereit'; } } catch (e) { console.error('Q&A-Generierung fehlgeschlagen:', e); setStatusError(t('error') || 'Fehler', String(e)); if (qaBadge) qaBadge.textContent = t('mc_error') || 'Fehler'; } } /** * Lädt die Q&A-Vorschau für die aktuelle Datei */ export async function loadQaPreviewForCurrent() { const files = getFilesCallback(); if (!files.length) { if (qaPreview) { qaPreview.innerHTML = `
${t('qa_no_questions') || 'Noch keine Q&A vorhanden.'}
`; } return; } const currentFile = getCurrentFileCallback(); if (!currentFile) return; try { const resp = await fetch('/api/qa-data/' + encodeURIComponent(currentFile)); const result = await resp.json(); if (result.status === 'OK' && result.data) { currentQaData = result.data; renderQaPreview(result.data); if (btnQaLearn) btnQaLearn.style.display = 'inline-block'; if (btnQaPrint) btnQaPrint.style.display = 'inline-block'; } else { if (qaPreview) { qaPreview.innerHTML = `
${t('qa_no_questions') || 'Noch keine Q&A für dieses Arbeitsblatt generiert.'}
`; } currentQaData = null; if (btnQaLearn) btnQaLearn.style.display = 'none'; if (btnQaPrint) btnQaPrint.style.display = 'none'; } } catch (e) { console.error('Fehler beim Laden der Q&A-Daten:', e); if (qaPreview) qaPreview.innerHTML = ''; } } /** * Rendert die Q&A-Vorschau */ function renderQaPreview(qaData) { if (!qaPreview) return; if (!qaData || !qaData.qa_items || qaData.qa_items.length === 0) { qaPreview.innerHTML = `
${t('qa_no_questions') || 'Keine Fragen vorhanden.'}
`; return; } const items = qaData.qa_items; // Zähle Fragen nach Box let box0 = 0, box1 = 0, box2 = 0; items.forEach(item => { const box = item.leitner ? item.leitner.box : 0; if (box === 0) box0++; else if (box === 1) box1++; else box2++; }); let html = `
${t('qa_box_new') || 'Neu'}: ${box0}
${t('qa_box_learning') || 'Lernt'}: ${box1}
${t('qa_box_mastered') || 'Gefestigt'}: ${box2}
`; // Zeige erste 2 Fragen als Vorschau const previewItems = items.slice(0, 2); previewItems.forEach((item, idx) => { html += `
${idx + 1}. ${escapeHtml(item.question)}
→ ${escapeHtml(item.answer.substring(0, 60))}${item.answer.length > 60 ? '...' : ''}
`; }); if (items.length > 2) { html += `
+ ${items.length - 2} ${t('questions') || 'weitere Fragen'}
`; } qaPreview.innerHTML = html; } /** * Öffnet das Lern-Modal */ export function openQaModal() { if (!currentQaData || !currentQaData.qa_items) { alert(t('qa_no_questions') || 'Keine Q&A vorhanden. Bitte zuerst generieren.'); return; } currentQaIndex = 0; qaSessionStats = { correct: 0, incorrect: 0, total: 0 }; renderQaLearningCard(); if (qaModal) qaModal.classList.remove('hidden'); } /** * Schließt das Lern-Modal */ export function closeQaModal() { if (qaModal) qaModal.classList.add('hidden'); } /** * Rendert die aktuelle Lernkarte */ function renderQaLearningCard() { const items = currentQaData.qa_items; if (currentQaIndex >= items.length) { renderQaSessionSummary(); return; } const item = items[currentQaIndex]; const leitner = item.leitner || { box: 0 }; const boxNames = [t('qa_box_new') || 'Neu', t('qa_box_learning') || 'Gelernt', t('qa_box_mastered') || 'Gefestigt']; const boxColors = ['#ef4444', '#f59e0b', '#22c55e']; let html = `
${t('question') || 'Frage'} ${currentQaIndex + 1} / ${items.length}
${boxNames[leitner.box]}
${t('question') || 'Frage'}:
${escapeHtml(item.question)}
${t('qa_your_answer') || 'Deine Antwort'}:
${t('qa_session_correct') || 'Richtig'}: ${qaSessionStats.correct}
${t('qa_session_incorrect') || 'Falsch'}: ${qaSessionStats.incorrect}
`; if (qaModalBody) { qaModalBody.innerHTML = html; // Event Listener document.getElementById('btn-qa-check-answer')?.addEventListener('click', () => { const userAnswer = document.getElementById('qa-user-answer')?.value.trim() || ''; document.getElementById('qa-user-answer-text').textContent = userAnswer || (t('qa_no_answer') || '(keine Antwort eingegeben)'); document.getElementById('qa-input-container').style.display = 'none'; document.getElementById('qa-check-btn-container').style.display = 'none'; document.getElementById('qa-comparison-container').style.display = 'block'; }); // Enter zum Prüfen document.getElementById('qa-user-answer')?.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); document.getElementById('btn-qa-check-answer')?.click(); } }); // Fokus setTimeout(() => { document.getElementById('qa-user-answer')?.focus(); }, 100); document.getElementById('btn-qa-correct')?.addEventListener('click', () => handleQaAnswer(true)); document.getElementById('btn-qa-incorrect')?.addEventListener('click', () => handleQaAnswer(false)); } } /** * Verarbeitet die Antwort */ async function handleQaAnswer(correct) { const item = currentQaData.qa_items[currentQaIndex]; qaSessionStats.total++; if (correct) qaSessionStats.correct++; else qaSessionStats.incorrect++; // Speichere Fortschritt try { const currentFile = getCurrentFileCallback(); if (currentFile) { await fetch(`/api/qa-progress?filename=${encodeURIComponent(currentFile)}&item_id=${encodeURIComponent(item.id)}&correct=${correct}`, { method: 'POST' }); } } catch (e) { console.error('Fehler beim Speichern des Fortschritts:', e); } currentQaIndex++; renderQaLearningCard(); } /** * Rendert die Session-Zusammenfassung */ function renderQaSessionSummary() { const percent = qaSessionStats.total > 0 ? Math.round(qaSessionStats.correct / qaSessionStats.total * 100) : 0; const emoji = percent >= 80 ? '🎉' : percent >= 50 ? '👍' : '💪'; let html = `
${emoji}
${t('qa_session_complete') || 'Lernrunde abgeschlossen!'}
${qaSessionStats.correct} / ${qaSessionStats.total} ${t('qa_result_correct') || 'richtig'} (${percent}%)
${qaSessionStats.correct}
${t('qa_correct') || 'Richtig'}
${qaSessionStats.incorrect}
${t('qa_incorrect') || 'Falsch'}
`; if (qaModalBody) { qaModalBody.innerHTML = html; document.getElementById('btn-qa-restart')?.addEventListener('click', () => { currentQaIndex = 0; qaSessionStats = { correct: 0, incorrect: 0, total: 0 }; renderQaLearningCard(); }); document.getElementById('btn-qa-close-summary')?.addEventListener('click', closeQaModal); // Aktualisiere Preview loadQaPreviewForCurrent(); } } /** * Öffnet den Druck-Dialog */ function openQaPrintDialog() { if (!currentQaData) { alert(t('qa_no_questions') || 'Keine Q&A vorhanden.'); return; } const currentFile = getCurrentFileCallback(); if (!currentFile) return; const choice = confirm((t('qa_print_with_answers') || 'Mit Lösungen drucken?') + '\n\nOK = Mit Lösungen\nAbbrechen = Nur Fragen'); const url = '/api/print-qa/' + encodeURIComponent(currentFile) + '?show_answers=' + choice; window.open(url, '_blank'); } /** * Gibt die aktuellen Q&A-Daten zurück */ export function getQaData() { return currentQaData; } /** * Helper: HTML-Escape */ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text || ''; return div.innerHTML; }