624 lines
26 KiB
JavaScript
624 lines
26 KiB
JavaScript
// =======================================================================
|
||
// Murphy Grammar Engine & Handwritten Journaling Module (Native ES Module)
|
||
// Essential Grammar in Use (A1-A2) & English Grammar in Use (B1-B2)
|
||
// Evening Deep Work Mode, Contrast Method & Paper Notebook Journaling
|
||
// =======================================================================
|
||
|
||
import { api } from './api.js';
|
||
import { showToast, escapeHtml, renderMarkdownTable, renderMarkdownNotes } from './ui.js';
|
||
|
||
let allUnits = [];
|
||
let currentFilter = 'all';
|
||
let currentUnit = null;
|
||
let currentExerciseIdx = 0;
|
||
let isExerciseAnswered = false;
|
||
let sessionMistakes = 0;
|
||
let sessionCorrect = 0;
|
||
let sessionIngestedCards = [];
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Initializer
|
||
// -----------------------------------------------------------------------
|
||
export function initGrammar() {
|
||
const backBtn = document.getElementById('grammar-back-btn');
|
||
const tabTheoryBtn = document.getElementById('grammar-tab-theory-btn');
|
||
const tabPracticeBtn = document.getElementById('grammar-tab-practice-btn');
|
||
const toPracticeBtn = document.getElementById('grammar-to-practice-btn');
|
||
const exNextBtn = document.getElementById('grammar-ex-next-btn');
|
||
const sumRepeatBtn = document.getElementById('grammar-sum-repeat-btn');
|
||
const sumUnitsBtn = document.getElementById('grammar-sum-units-btn');
|
||
const sumSrsBtn = document.getElementById('grammar-sum-srs-btn');
|
||
const handwrittenToggle = document.getElementById('grammar-handwritten-toggle');
|
||
const cheatsheetToggle = document.getElementById('grammar-practice-cheatsheet-toggle');
|
||
const cheatsheetDrawer = document.getElementById('grammar-practice-cheatsheet-drawer');
|
||
|
||
if (cheatsheetToggle && cheatsheetDrawer) {
|
||
cheatsheetToggle.addEventListener('click', () => {
|
||
const isNowHidden = !cheatsheetDrawer.classList.contains('hidden');
|
||
cheatsheetDrawer.classList.toggle('hidden', isNowHidden);
|
||
cheatsheetToggle.innerHTML = isNowHidden
|
||
? '<span>📋 Шпаргалка по теме</span><i class="ph-caret-down"></i>'
|
||
: '<span>📋 Скрыть шпаргалку</span><i class="ph-caret-up"></i>';
|
||
});
|
||
}
|
||
|
||
if (backBtn) {
|
||
backBtn.addEventListener('click', () => {
|
||
showGrammarScreen('directory');
|
||
window.location.hash = '#grammar';
|
||
loadGrammarUnits();
|
||
});
|
||
}
|
||
|
||
if (tabTheoryBtn && tabPracticeBtn) {
|
||
tabTheoryBtn.addEventListener('click', () => switchLessonTab('theory'));
|
||
tabPracticeBtn.addEventListener('click', () => switchLessonTab('practice'));
|
||
}
|
||
|
||
if (toPracticeBtn) {
|
||
toPracticeBtn.addEventListener('click', () => switchLessonTab('practice'));
|
||
}
|
||
|
||
if (exNextBtn) {
|
||
exNextBtn.addEventListener('click', () => advanceExercise());
|
||
}
|
||
|
||
if (sumRepeatBtn) {
|
||
sumRepeatBtn.addEventListener('click', () => {
|
||
if (currentUnit) {
|
||
startUnitPractice();
|
||
}
|
||
});
|
||
}
|
||
|
||
if (sumUnitsBtn) {
|
||
sumUnitsBtn.addEventListener('click', () => {
|
||
showGrammarScreen('directory');
|
||
window.location.hash = '#grammar';
|
||
loadGrammarUnits();
|
||
});
|
||
}
|
||
|
||
if (sumSrsBtn) {
|
||
sumSrsBtn.addEventListener('click', () => {
|
||
window.location.hash = '#srs/grammar';
|
||
});
|
||
}
|
||
|
||
if (handwrittenToggle) {
|
||
handwrittenToggle.addEventListener('change', async (e) => {
|
||
if (!currentUnit) return;
|
||
const isChecked = e.target.checked;
|
||
try {
|
||
await api.grammar.toggleHandwritten(currentUnit.id, isChecked);
|
||
currentUnit.handwritten_logged = isChecked;
|
||
if (isChecked) {
|
||
showToast('✍️ Отметка сохранена: записано от руки в тетрадь!', 'success');
|
||
} else {
|
||
showToast('Отметка тетради снята', 'info');
|
||
}
|
||
} catch (err) {
|
||
console.error(err);
|
||
showToast('Ошибка сохранения статуса', 'error');
|
||
e.target.checked = !isChecked;
|
||
}
|
||
});
|
||
}
|
||
|
||
// Filter pills in directory
|
||
const filterPills = document.querySelectorAll('.grammar-filter-pill');
|
||
filterPills.forEach(pill => {
|
||
pill.addEventListener('click', () => {
|
||
filterPills.forEach(p => p.classList.remove('active'));
|
||
pill.classList.add('active');
|
||
currentFilter = pill.dataset.filter || 'all';
|
||
renderUnitsGrid();
|
||
});
|
||
});
|
||
|
||
// Keyboard shortcuts (1-4 for exercise choices, Enter/Space for next)
|
||
window.addEventListener('keydown', (e) => {
|
||
const grammarView = document.getElementById('grammar-view');
|
||
const lessonScreen = document.getElementById('grammar-lesson-screen');
|
||
const practiceTab = document.getElementById('grammar-tab-practice-content');
|
||
if (!grammarView || grammarView.classList.contains('hidden')) return;
|
||
if (!lessonScreen || lessonScreen.classList.contains('hidden')) return;
|
||
if (!practiceTab || practiceTab.classList.contains('hidden')) return;
|
||
|
||
if (['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName)) return;
|
||
|
||
if (!isExerciseAnswered) {
|
||
if (['1', '2', '3', '4'].includes(e.key)) {
|
||
e.preventDefault();
|
||
const idx = parseInt(e.key, 10) - 1;
|
||
const btns = document.querySelectorAll('.grammar-opt-btn');
|
||
if (btns[idx]) btns[idx].click();
|
||
}
|
||
} else {
|
||
if (e.key === ' ' || e.key === 'Enter') {
|
||
e.preventDefault();
|
||
advanceExercise();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Screen Switcher inside #grammar-view
|
||
// -----------------------------------------------------------------------
|
||
export function showGrammarScreen(screenName) {
|
||
const dir = document.getElementById('grammar-directory-screen');
|
||
const lesson = document.getElementById('grammar-lesson-screen');
|
||
|
||
if (dir) dir.classList.toggle('hidden', screenName !== 'directory');
|
||
if (lesson) lesson.classList.toggle('hidden', screenName !== 'lesson');
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Units Directory Loader
|
||
// -----------------------------------------------------------------------
|
||
export async function loadGrammarUnits() {
|
||
showGrammarScreen('directory');
|
||
const grid = document.getElementById('grammar-units-grid');
|
||
if (grid) {
|
||
grid.innerHTML = '<div class="grammar-loading"><div class="spinner"></div><p>Загрузка каталога грамматики Мерфи...</p></div>';
|
||
}
|
||
|
||
try {
|
||
const res = await api.grammar.getUnits();
|
||
allUnits = res.units || [];
|
||
updateDirectoryStats();
|
||
renderUnitsGrid();
|
||
} catch (err) {
|
||
console.error('Error loading grammar units:', err);
|
||
if (grid) {
|
||
grid.innerHTML = `<div class="empty-state"><p>Ошибка загрузки юнитов: ${escapeHtml(err.message)}</p></div>`;
|
||
}
|
||
}
|
||
}
|
||
|
||
function updateDirectoryStats() {
|
||
const totalCount = allUnits.length;
|
||
const completedCount = allUnits.filter(u => u.is_completed).length;
|
||
const handwrittenCount = allUnits.filter(u => u.handwritten_logged).length;
|
||
|
||
const totalEl = document.getElementById('grammar-stat-total');
|
||
const completedEl = document.getElementById('grammar-stat-completed');
|
||
const handwrittenEl = document.getElementById('grammar-stat-handwritten');
|
||
|
||
if (totalEl) totalEl.textContent = totalCount;
|
||
if (completedEl) completedEl.textContent = completedCount;
|
||
if (handwrittenEl) handwrittenEl.textContent = handwrittenCount;
|
||
}
|
||
|
||
function renderUnitsGrid() {
|
||
const grid = document.getElementById('grammar-units-grid');
|
||
if (!grid) return;
|
||
|
||
let filtered = allUnits;
|
||
if (currentFilter === 'A1-A2') {
|
||
filtered = allUnits.filter(u => u.level === 'A1-A2');
|
||
} else if (currentFilter === 'B1-B2') {
|
||
filtered = allUnits.filter(u => u.level === 'B1-B2');
|
||
} else if (currentFilter === 'completed') {
|
||
filtered = allUnits.filter(u => u.is_completed);
|
||
} else if (currentFilter === 'handwritten') {
|
||
filtered = allUnits.filter(u => u.handwritten_logged);
|
||
} else if (currentFilter !== 'all') {
|
||
filtered = allUnits.filter(u => u.category.toLowerCase().includes(currentFilter.toLowerCase()));
|
||
}
|
||
|
||
if (filtered.length === 0) {
|
||
grid.innerHTML = '<div class="empty-state"><p>Нет юнитов в этой категории</p></div>';
|
||
return;
|
||
}
|
||
|
||
grid.innerHTML = filtered.map(unit => {
|
||
const levelBadgeClass = unit.level === 'A1-A2' ? 'badge-a1' : 'badge-b1';
|
||
const isComp = unit.is_completed;
|
||
const isHw = unit.handwritten_logged;
|
||
|
||
return `
|
||
<div class="grammar-unit-card ${isComp ? 'is-completed' : ''}" data-unit-id="${unit.id}">
|
||
<div class="grammar-card-header">
|
||
<span class="grammar-unit-number">Юнит ${unit.unit_num}</span>
|
||
<span class="grammar-level-pill ${levelBadgeClass}">${escapeHtml(unit.level)}</span>
|
||
</div>
|
||
<h3 class="grammar-card-title">${escapeHtml(unit.title)}</h3>
|
||
<p class="grammar-card-sub">${escapeHtml(unit.subtitle)}</p>
|
||
<div class="grammar-card-meta">
|
||
<span class="grammar-card-cat"><i class="ph-folder-simple"></i> ${escapeHtml(unit.category)}</span>
|
||
</div>
|
||
<div class="grammar-card-footer">
|
||
<div class="grammar-badges">
|
||
${isComp ? `<span class="badge-status-completed"><i class="ph-check-circle-fill"></i> ${unit.score || 100}%</span>` : '<span class="badge-status-new">Новый</span>'}
|
||
${isHw ? '<span class="badge-status-handwritten" title="Записано от руки в тетрадь"><i class="ph-pen-nib-fill"></i> В тетради</span>' : ''}
|
||
</div>
|
||
<button class="btn btn-sm btn-primary grammar-open-btn" data-unit-id="${unit.id}">
|
||
${isComp ? 'Повторить' : 'Начать'} <i class="ph-arrow-right"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
|
||
// Attach click handlers to cards
|
||
grid.querySelectorAll('.grammar-unit-card').forEach(card => {
|
||
card.addEventListener('click', (e) => {
|
||
const unitId = parseInt(card.dataset.unitId, 10);
|
||
window.location.hash = `#grammar/unit/${unitId}`;
|
||
});
|
||
});
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Unit Lesson Loader
|
||
// -----------------------------------------------------------------------
|
||
export async function openGrammarUnit(unitId) {
|
||
showGrammarScreen('lesson');
|
||
const lessonScreen = document.getElementById('grammar-lesson-screen');
|
||
if (!lessonScreen) return;
|
||
|
||
const titleEl = document.getElementById('grammar-lesson-title');
|
||
if (titleEl) titleEl.textContent = 'Загрузка юнита...';
|
||
|
||
try {
|
||
currentUnit = await api.grammar.getUnit(unitId);
|
||
renderUnitTheory();
|
||
switchLessonTab('theory');
|
||
} catch (err) {
|
||
console.error('Error loading grammar unit:', err);
|
||
showToast('Не удалось загрузить урок: ' + err.message, 'error');
|
||
window.location.hash = '#grammar';
|
||
}
|
||
}
|
||
|
||
function switchLessonTab(tab) {
|
||
const tabTheoryBtn = document.getElementById('grammar-tab-theory-btn');
|
||
const tabPracticeBtn = document.getElementById('grammar-tab-practice-btn');
|
||
const tabTheoryContent = document.getElementById('grammar-tab-theory-content');
|
||
const tabPracticeContent = document.getElementById('grammar-tab-practice-content');
|
||
const summaryCard = document.getElementById('grammar-summary-card');
|
||
|
||
if (tab === 'theory') {
|
||
if (tabTheoryBtn) tabTheoryBtn.classList.add('active');
|
||
if (tabPracticeBtn) tabPracticeBtn.classList.remove('active');
|
||
if (tabTheoryContent) tabTheoryContent.classList.remove('hidden');
|
||
if (tabPracticeContent) tabPracticeContent.classList.add('hidden');
|
||
if (summaryCard) summaryCard.classList.add('hidden');
|
||
} else if (tab === 'practice') {
|
||
if (tabTheoryBtn) tabTheoryBtn.classList.remove('active');
|
||
if (tabPracticeBtn) tabPracticeBtn.classList.add('active');
|
||
if (tabTheoryContent) tabTheoryContent.classList.add('hidden');
|
||
if (summaryCard) summaryCard.classList.add('hidden');
|
||
if (tabPracticeContent) tabPracticeContent.classList.remove('hidden');
|
||
startUnitPractice();
|
||
} else if (tab === 'summary') {
|
||
if (tabTheoryBtn) tabTheoryBtn.classList.remove('active');
|
||
if (tabPracticeBtn) tabPracticeBtn.classList.remove('active');
|
||
if (tabTheoryContent) tabTheoryContent.classList.add('hidden');
|
||
if (tabPracticeContent) tabPracticeContent.classList.add('hidden');
|
||
if (summaryCard) summaryCard.classList.remove('hidden');
|
||
}
|
||
}
|
||
|
||
function renderUnitTheory() {
|
||
if (!currentUnit) return;
|
||
|
||
// Header info
|
||
const numEl = document.getElementById('grammar-lesson-num');
|
||
const levelEl = document.getElementById('grammar-lesson-level');
|
||
const titleEl = document.getElementById('grammar-lesson-title');
|
||
const subEl = document.getElementById('grammar-lesson-sub');
|
||
const catEl = document.getElementById('grammar-lesson-cat');
|
||
const handwrittenToggle = document.getElementById('grammar-handwritten-toggle');
|
||
|
||
if (numEl) numEl.textContent = `Юнит ${currentUnit.unit_num}`;
|
||
if (levelEl) {
|
||
levelEl.textContent = currentUnit.level;
|
||
levelEl.className = `grammar-level-pill ${currentUnit.level === 'A1-A2' ? 'badge-a1' : 'badge-b1'}`;
|
||
}
|
||
if (titleEl) titleEl.textContent = currentUnit.title;
|
||
if (subEl) subEl.textContent = currentUnit.subtitle || '';
|
||
if (catEl) catEl.textContent = currentUnit.category;
|
||
|
||
if (handwrittenToggle) {
|
||
handwrittenToggle.checked = Boolean(currentUnit.handwritten_logged);
|
||
}
|
||
|
||
// Cheatsheet Markdown Table
|
||
const cheatsheetSec = document.getElementById('grammar-cheatsheet-section');
|
||
const cheatsheetBox = document.getElementById('grammar-cheatsheet-content');
|
||
if (cheatsheetSec && cheatsheetBox) {
|
||
if (currentUnit.cheatsheet_table && currentUnit.cheatsheet_table.trim()) {
|
||
cheatsheetBox.innerHTML = renderMarkdownTable(currentUnit.cheatsheet_table);
|
||
cheatsheetSec.classList.remove('hidden');
|
||
} else {
|
||
cheatsheetBox.innerHTML = '';
|
||
cheatsheetSec.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
// Detailed Murphy Notes
|
||
const notesSec = document.getElementById('grammar-detailed-notes-section');
|
||
const notesBox = document.getElementById('grammar-detailed-notes-content');
|
||
if (notesSec && notesBox) {
|
||
if (currentUnit.detailed_notes && currentUnit.detailed_notes.trim()) {
|
||
notesBox.innerHTML = renderMarkdownNotes(currentUnit.detailed_notes);
|
||
notesSec.classList.remove('hidden');
|
||
} else {
|
||
notesBox.innerHTML = '';
|
||
notesSec.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
// Practice Cheatsheet Drawer content
|
||
const practiceDrawer = document.getElementById('grammar-practice-cheatsheet-drawer');
|
||
const practiceToggleWrap = document.querySelector('.practice-cheatsheet-toggle-wrap');
|
||
if (practiceDrawer && practiceToggleWrap) {
|
||
if (currentUnit.cheatsheet_table && currentUnit.cheatsheet_table.trim()) {
|
||
practiceDrawer.innerHTML = renderMarkdownTable(currentUnit.cheatsheet_table);
|
||
practiceToggleWrap.classList.remove('hidden');
|
||
} else {
|
||
practiceDrawer.innerHTML = '';
|
||
practiceToggleWrap.classList.add('hidden');
|
||
}
|
||
practiceDrawer.classList.add('hidden');
|
||
const practiceToggle = document.getElementById('grammar-practice-cheatsheet-toggle');
|
||
if (practiceToggle) {
|
||
practiceToggle.innerHTML = '<span>📋 Шпаргалка по теме</span><i class="ph-caret-down"></i>';
|
||
}
|
||
}
|
||
|
||
// Golden formula
|
||
const formulaEl = document.getElementById('grammar-golden-formula-text');
|
||
if (formulaEl) {
|
||
formulaEl.textContent = currentUnit.golden_formula;
|
||
}
|
||
|
||
// Journaling prompt
|
||
const promptEl = document.getElementById('grammar-journaling-prompt-text');
|
||
if (promptEl) {
|
||
promptEl.textContent = currentUnit.journaling_prompt;
|
||
}
|
||
|
||
// Contrasts
|
||
const contrastsContainer = document.getElementById('grammar-contrasts-list');
|
||
if (contrastsContainer) {
|
||
const contrasts = currentUnit.contrasts || [];
|
||
if (contrasts.length === 0) {
|
||
contrastsContainer.innerHTML = '';
|
||
} else {
|
||
contrastsContainer.innerHTML = contrasts.map(c => `
|
||
<div class="grammar-contrast-card">
|
||
<div class="contrast-col contrast-a">
|
||
<div class="contrast-header"><span class="contrast-pill a">Ситуация А</span> ${escapeHtml(c.situation_a)}</div>
|
||
<div class="contrast-example">${escapeHtml(c.example_a)}</div>
|
||
<div class="contrast-trans">${escapeHtml(c.trans_a)}</div>
|
||
</div>
|
||
<div class="contrast-divider">
|
||
<span class="vs-badge">VS</span>
|
||
</div>
|
||
<div class="contrast-col contrast-b">
|
||
<div class="contrast-header"><span class="contrast-pill b">Ситуация Б</span> ${escapeHtml(c.situation_b)}</div>
|
||
<div class="contrast-example">${escapeHtml(c.example_b)}</div>
|
||
<div class="contrast-trans">${escapeHtml(c.trans_b)}</div>
|
||
</div>
|
||
<div class="contrast-key-diff">
|
||
<strong>💡 Ключевая разница:</strong> ${escapeHtml(c.key_difference)}
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
}
|
||
}
|
||
}
|
||
|
||
// -----------------------------------------------------------------------
|
||
// Cloze Tap Practice Engine (5 micro-exercises)
|
||
// -----------------------------------------------------------------------
|
||
function startUnitPractice() {
|
||
if (!currentUnit) return;
|
||
currentExerciseIdx = 0;
|
||
isExerciseAnswered = false;
|
||
sessionMistakes = 0;
|
||
sessionCorrect = 0;
|
||
sessionIngestedCards = [];
|
||
|
||
// Ensure cheatsheet drawer is collapsed at practice start
|
||
const practiceDrawer = document.getElementById('grammar-practice-cheatsheet-drawer');
|
||
const practiceToggle = document.getElementById('grammar-practice-cheatsheet-toggle');
|
||
if (practiceDrawer) practiceDrawer.classList.add('hidden');
|
||
if (practiceToggle) practiceToggle.innerHTML = '<span>📋 Шпаргалка по теме</span><i class="ph-caret-down"></i>';
|
||
|
||
renderExercise();
|
||
}
|
||
|
||
function renderExercise() {
|
||
if (!currentUnit || !currentUnit.exercises) return;
|
||
const exercises = currentUnit.exercises;
|
||
|
||
if (currentExerciseIdx >= exercises.length) {
|
||
finishUnitPractice();
|
||
return;
|
||
}
|
||
|
||
const ex = exercises[currentExerciseIdx];
|
||
isExerciseAnswered = false;
|
||
|
||
// Progress bar & dots
|
||
const progressText = document.getElementById('grammar-practice-progress');
|
||
const dotsContainer = document.getElementById('grammar-practice-dots');
|
||
if (progressText) {
|
||
progressText.textContent = `Задание ${currentExerciseIdx + 1} из ${exercises.length}`;
|
||
}
|
||
if (dotsContainer) {
|
||
dotsContainer.innerHTML = exercises.map((_, i) => {
|
||
let dotClass = 'dot';
|
||
if (i < currentExerciseIdx) dotClass += ' dot-done';
|
||
if (i === currentExerciseIdx) dotClass += ' dot-active';
|
||
return `<span class="${dotClass}"></span>`;
|
||
}).join('');
|
||
}
|
||
|
||
// Sentence with cloze blank
|
||
const sentenceEl = document.getElementById('grammar-cloze-sentence');
|
||
const clueEl = document.getElementById('grammar-cloze-clue');
|
||
const feedbackBox = document.getElementById('grammar-exercise-feedback');
|
||
const nextBtn = document.getElementById('grammar-ex-next-btn');
|
||
|
||
if (feedbackBox) feedbackBox.classList.add('hidden');
|
||
if (nextBtn) nextBtn.classList.add('hidden');
|
||
|
||
if (sentenceEl) {
|
||
const parts = ex.sentence.split('___');
|
||
sentenceEl.innerHTML = `${escapeHtml(parts[0] || '')}<span class="cloze-blank" id="current-cloze-blank">______</span>${escapeHtml(parts[1] || '')}`;
|
||
}
|
||
|
||
if (clueEl) {
|
||
clueEl.textContent = ex.clue ? `💡 Подсказка: ${ex.clue}` : '';
|
||
}
|
||
|
||
// Tap options
|
||
const optionsContainer = document.getElementById('grammar-options-container');
|
||
if (optionsContainer) {
|
||
optionsContainer.innerHTML = ex.options.map((opt, i) => `
|
||
<button class="btn btn-outline grammar-opt-btn" data-option="${escapeHtml(opt)}">
|
||
<span class="opt-num">${i + 1}</span>
|
||
<span class="opt-label">${escapeHtml(opt)}</span>
|
||
</button>
|
||
`).join('');
|
||
|
||
optionsContainer.querySelectorAll('.grammar-opt-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
if (isExerciseAnswered) return;
|
||
handleOptionSelect(btn.dataset.option);
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
async function handleOptionSelect(selectedOption) {
|
||
if (isExerciseAnswered || !currentUnit) return;
|
||
isExerciseAnswered = true;
|
||
|
||
const blank = document.getElementById('current-cloze-blank');
|
||
if (blank) blank.textContent = selectedOption;
|
||
|
||
const allBtns = document.querySelectorAll('.grammar-opt-btn');
|
||
allBtns.forEach(b => b.disabled = true);
|
||
|
||
try {
|
||
const result = await api.grammar.submitAnswer(currentUnit.id, currentExerciseIdx, selectedOption);
|
||
|
||
const feedbackBox = document.getElementById('grammar-exercise-feedback');
|
||
const nextBtn = document.getElementById('grammar-ex-next-btn');
|
||
|
||
allBtns.forEach(btn => {
|
||
if (btn.dataset.option === result.correct_option) {
|
||
btn.classList.add('btn-correct');
|
||
} else if (btn.dataset.option === selectedOption && !result.correct) {
|
||
btn.classList.add('btn-wrong');
|
||
}
|
||
});
|
||
|
||
if (blank) {
|
||
blank.textContent = result.correct_option;
|
||
blank.classList.add(result.correct ? 'blank-correct' : 'blank-wrong');
|
||
}
|
||
|
||
if (result.correct) {
|
||
sessionCorrect++;
|
||
if (feedbackBox) {
|
||
feedbackBox.className = 'grammar-feedback-box feedback-correct';
|
||
feedbackBox.innerHTML = `
|
||
<div class="feedback-title"><i class="ph-check-circle-fill"></i> Верно!</div>
|
||
<div class="feedback-text">${escapeHtml(result.explanation)}</div>
|
||
`;
|
||
feedbackBox.classList.remove('hidden');
|
||
}
|
||
} else {
|
||
sessionMistakes++;
|
||
if (result.card_created || result.card_updated) {
|
||
sessionIngestedCards.push(result.card_id);
|
||
}
|
||
if (feedbackBox) {
|
||
feedbackBox.className = 'grammar-feedback-box feedback-wrong';
|
||
feedbackBox.innerHTML = `
|
||
<div class="feedback-title"><i class="ph-x-circle-fill"></i> Неверно (правильно: <b>${escapeHtml(result.correct_option)}</b>)</div>
|
||
<div class="feedback-text">${escapeHtml(result.explanation)}</div>
|
||
<div class="feedback-srs-ingested">
|
||
<i class="ph-lightning-fill"></i> Грамматический паттерн сохранён в ваши карточки для утреннего интервального повторения (SRS).
|
||
</div>
|
||
`;
|
||
feedbackBox.classList.remove('hidden');
|
||
}
|
||
}
|
||
|
||
if (nextBtn) {
|
||
nextBtn.classList.remove('hidden');
|
||
nextBtn.focus();
|
||
}
|
||
} catch (err) {
|
||
console.error('Error checking grammar answer:', err);
|
||
showToast('Ошибка проверки ответа', 'error');
|
||
allBtns.forEach(b => b.disabled = false);
|
||
isExerciseAnswered = false;
|
||
}
|
||
}
|
||
|
||
function advanceExercise() {
|
||
currentExerciseIdx++;
|
||
renderExercise();
|
||
}
|
||
|
||
async function finishUnitPractice() {
|
||
if (!currentUnit) return;
|
||
const total = currentUnit.exercises.length;
|
||
const score = Math.round((sessionCorrect / total) * 100);
|
||
|
||
// Save completion
|
||
try {
|
||
await api.grammar.completeUnit(currentUnit.id, score, Boolean(currentUnit.handwritten_logged));
|
||
currentUnit.is_completed = true;
|
||
currentUnit.score = Math.max(currentUnit.score || 0, score);
|
||
} catch (err) {
|
||
console.error('Error saving unit completion:', err);
|
||
}
|
||
|
||
// Switch to summary tab
|
||
switchLessonTab('summary');
|
||
|
||
const scoreValEl = document.getElementById('grammar-sum-score-val');
|
||
const scoreTextEl = document.getElementById('grammar-sum-score-text');
|
||
const ingestedEl = document.getElementById('grammar-sum-ingested-count');
|
||
const srsBtn = document.getElementById('grammar-sum-srs-btn');
|
||
const hwCard = document.getElementById('grammar-sum-hw-reminder');
|
||
|
||
if (scoreValEl) scoreValEl.textContent = `${score}%`;
|
||
if (scoreTextEl) {
|
||
if (score === 100) {
|
||
scoreTextEl.textContent = '🎉 Идеально! Все 5 конструкций освоены без ошибок.';
|
||
} else if (score >= 80) {
|
||
scoreTextEl.textContent = '👍 Отличный результат! Конструкция хорошо понята.';
|
||
} else {
|
||
scoreTextEl.textContent = '💪 Хорошая практика! Ошибки зафиксированы для повторения.';
|
||
}
|
||
}
|
||
|
||
if (ingestedEl) {
|
||
ingestedEl.textContent = sessionIngestedCards.length;
|
||
}
|
||
|
||
if (srsBtn) {
|
||
srsBtn.classList.toggle('hidden', sessionIngestedCards.length === 0);
|
||
}
|
||
|
||
if (hwCard) {
|
||
if (!currentUnit.handwritten_logged) {
|
||
hwCard.classList.remove('hidden');
|
||
} else {
|
||
hwCard.classList.add('hidden');
|
||
}
|
||
}
|
||
}
|