|
|
class ExerciseUI { |
|
|
constructor() { |
|
|
this.currentExercise = null; |
|
|
this.answerSubmitted = false; |
|
|
} |
|
|
|
|
|
async render(exerciseData) { |
|
|
this.currentExercise = exerciseData; |
|
|
this.answerSubmitted = false; |
|
|
|
|
|
const exerciseContent = document.getElementById('exerciseContent'); |
|
|
const exerciseFeedback = document.getElementById('exerciseFeedback'); |
|
|
|
|
|
if (!exerciseContent) return; |
|
|
|
|
|
|
|
|
if (exerciseFeedback) { |
|
|
exerciseFeedback.innerHTML = ''; |
|
|
} |
|
|
|
|
|
let html = ` |
|
|
<div class="exercise-header"> |
|
|
<h4>${this.escapeHtml(exerciseData.exercise.question)}</h4> |
|
|
${exerciseData.exercise.description ? |
|
|
`<p class="exercise-description">${this.escapeHtml(exerciseData.exercise.description)}</p>` : ''} |
|
|
</div> |
|
|
|
|
|
<div class="exercise-body"> |
|
|
<div class="answer-section"> |
|
|
<label for="exerciseAnswer" class="answer-label">پاسخ خود را بنویسید:</label> |
|
|
<textarea |
|
|
id="exerciseAnswer" |
|
|
rows="6" |
|
|
placeholder="پاسخ خود را اینجا وارد کنید..." |
|
|
${this.answerSubmitted ? 'disabled' : ''} |
|
|
></textarea> |
|
|
|
|
|
${exerciseData.exercise.hint ? |
|
|
`<div class="exercise-hint"> |
|
|
<strong>💡 نکته:</strong> ${this.escapeHtml(exerciseData.exercise.hint)} |
|
|
</div>` : ''} |
|
|
|
|
|
<div class="exercise-actions"> |
|
|
<button |
|
|
id="submitExercise" |
|
|
class="btn btn-primary" |
|
|
onclick="exerciseUI.submitAnswer()" |
|
|
${this.answerSubmitted ? 'disabled' : ''} |
|
|
> |
|
|
ارسال پاسخ |
|
|
</button> |
|
|
<button |
|
|
class="btn btn-secondary" |
|
|
onclick="exerciseUI.showSolution()" |
|
|
> |
|
|
مشاهده راهحل |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="exercise-info"> |
|
|
<div class="info-card"> |
|
|
<h5>📋 انتظارات ما</h5> |
|
|
<ul class="expected-keywords"> |
|
|
${exerciseData.exercise.expected_keywords.map(keyword => |
|
|
`<li><code>${this.escapeHtml(keyword)}</code></li>` |
|
|
).join('')} |
|
|
</ul> |
|
|
</div> |
|
|
|
|
|
<div class="info-card"> |
|
|
<h5>🎯 معیارهای ارزیابی</h5> |
|
|
<p>پاسخ شما بر اساس تطابق با کلمات کلیدی بالا ارزیابی میشود.</p> |
|
|
<p>حداقل ${Math.round((exerciseData.exercise.match_threshold || 0.6) * 100)}% تطابق مورد نیاز است.</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
exerciseContent.innerHTML = html; |
|
|
|
|
|
|
|
|
const textarea = document.getElementById('exerciseAnswer'); |
|
|
if (textarea && !this.answerSubmitted) { |
|
|
textarea.addEventListener('input', this.debounce(this.autoSaveAnswer.bind(this), 1000)); |
|
|
this.loadSavedAnswer(); |
|
|
} |
|
|
} |
|
|
|
|
|
async submitAnswer() { |
|
|
const answerTextarea = document.getElementById('exerciseAnswer'); |
|
|
const submitButton = document.getElementById('submitExercise'); |
|
|
|
|
|
if (!answerTextarea || !submitButton) return; |
|
|
|
|
|
const answer = answerTextarea.value.trim(); |
|
|
|
|
|
if (!answer) { |
|
|
Utils.showNotification('لطفاً پاسخ خود را وارد کنید', 'error'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
submitButton.disabled = true; |
|
|
submitButton.innerHTML = '⏳ در حال بررسی...'; |
|
|
|
|
|
try { |
|
|
const result = await learningLogic.checkExerciseAnswer( |
|
|
this.currentExercise.day, |
|
|
answer |
|
|
); |
|
|
|
|
|
this.answerSubmitted = true; |
|
|
this.showResult(result); |
|
|
this.clearSavedAnswer(); |
|
|
|
|
|
|
|
|
document.dispatchEvent(new CustomEvent('exerciseSubmitted', { |
|
|
detail: { |
|
|
exercise: this.currentExercise, |
|
|
result: result |
|
|
} |
|
|
})); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Error submitting exercise:', error); |
|
|
Utils.showNotification('خطا در ارسال پاسخ', 'error'); |
|
|
submitButton.disabled = false; |
|
|
submitButton.innerHTML = 'ارسال پاسخ'; |
|
|
} |
|
|
} |
|
|
|
|
|
showResult(result) { |
|
|
const exerciseFeedback = document.getElementById('exerciseFeedback'); |
|
|
if (!exerciseFeedback) return; |
|
|
|
|
|
const answerTextarea = document.getElementById('exerciseAnswer'); |
|
|
const submitButton = document.getElementById('submitExercise'); |
|
|
|
|
|
if (answerTextarea) { |
|
|
answerTextarea.disabled = true; |
|
|
} |
|
|
|
|
|
if (submitButton) { |
|
|
submitButton.disabled = true; |
|
|
submitButton.innerHTML = '✅ پاسخ ارسال شده'; |
|
|
} |
|
|
|
|
|
let html = ` |
|
|
<div class="exercise-result ${result.isCorrect ? 'success' : 'warning'}"> |
|
|
<div class="result-header"> |
|
|
<h4>${result.isCorrect ? '✅ پاسخ صحیح' : '❌ نیاز به بهبود'}</h4> |
|
|
<div class="reward-badge">${result.reward} امتیاز</div> |
|
|
</div> |
|
|
|
|
|
<div class="result-body"> |
|
|
<p><strong>پیام:</strong> ${result.feedback.message}</p> |
|
|
|
|
|
<div class="keywords-analysis"> |
|
|
<h5>📊 تحلیل کلمات کلیدی:</h5> |
|
|
<div class="keywords-grid"> |
|
|
<div class="matched-keywords"> |
|
|
<h6>✅ کلمات یافت شده:</h6> |
|
|
${result.matchedKeywords.length > 0 ? |
|
|
`<ul>${result.matchedKeywords.map(kw => `<li><code>${this.escapeHtml(kw)}</code></li>`).join('')}</ul>` : |
|
|
'<p>هیچ کلمهای یافت نشد</p>' |
|
|
} |
|
|
</div> |
|
|
<div class="missing-keywords"> |
|
|
<h6>❌ کلمات مفقوده:</h6> |
|
|
${result.expectedKeywords.filter(kw => !result.matchedKeywords.includes(kw)).length > 0 ? |
|
|
`<ul>${result.expectedKeywords.filter(kw => !result.matchedKeywords.includes(kw)) |
|
|
.map(kw => `<li><code>${this.escapeHtml(kw)}</code></li>`).join('')}</ul>` : |
|
|
'<p>همه کلمات یافت شدند</p>' |
|
|
} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
${!result.isCorrect && result.hint ? |
|
|
`<div class="improvement-hint"> |
|
|
<strong>💡 راهنمایی بهبود:</strong> ${this.escapeHtml(result.hint)} |
|
|
</div>` : '' |
|
|
} |
|
|
</div> |
|
|
|
|
|
<div class="result-actions"> |
|
|
<button class="btn btn-outline" onclick="exerciseUI.tryAgain()"> |
|
|
🔄 تلاش مجدد |
|
|
</button> |
|
|
<button class="btn btn-primary" onclick="exerciseUI.nextExercise()"> |
|
|
➡️ تمرین بعدی |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
exerciseFeedback.innerHTML = html; |
|
|
} |
|
|
|
|
|
tryAgain() { |
|
|
this.answerSubmitted = false; |
|
|
this.render(this.currentExercise); |
|
|
} |
|
|
|
|
|
nextExercise() { |
|
|
|
|
|
|
|
|
const nextDay = this.currentExercise.day + 1; |
|
|
document.dispatchEvent(new CustomEvent('dayChanged', { |
|
|
detail: { day: nextDay } |
|
|
})); |
|
|
} |
|
|
|
|
|
showSolution() { |
|
|
if (!this.currentExercise) return; |
|
|
|
|
|
|
|
|
|
|
|
const keywords = this.currentExercise.exercise.expected_keywords; |
|
|
Utils.showNotification( |
|
|
`کلمات کلیدی مورد انتظار: ${keywords.join('، ')}`, |
|
|
'info' |
|
|
); |
|
|
} |
|
|
|
|
|
autoSaveAnswer() { |
|
|
const answerTextarea = document.getElementById('exerciseAnswer'); |
|
|
if (!answerTextarea || this.answerSubmitted) return; |
|
|
|
|
|
const answer = answerTextarea.value; |
|
|
const key = `exercise_${this.currentExercise.day}_answer`; |
|
|
Utils.saveToLocalStorage(key, answer); |
|
|
} |
|
|
|
|
|
loadSavedAnswer() { |
|
|
const answerTextarea = document.getElementById('exerciseAnswer'); |
|
|
if (!answerTextarea) return; |
|
|
|
|
|
const key = `exercise_${this.currentExercise.day}_answer`; |
|
|
const savedAnswer = Utils.loadFromLocalStorage(key); |
|
|
|
|
|
if (savedAnswer) { |
|
|
answerTextarea.value = savedAnswer; |
|
|
} |
|
|
} |
|
|
|
|
|
clearSavedAnswer() { |
|
|
const key = `exercise_${this.currentExercise.day}_answer`; |
|
|
localStorage.removeItem(key); |
|
|
} |
|
|
|
|
|
escapeHtml(unsafe) { |
|
|
return unsafe |
|
|
.replace(/&/g, "&") |
|
|
.replace(/</g, "<") |
|
|
.replace(/>/g, ">") |
|
|
.replace(/"/g, """) |
|
|
.replace(/'/g, "'"); |
|
|
} |
|
|
|
|
|
debounce(func, wait) { |
|
|
let timeout; |
|
|
return function executedFunction(...args) { |
|
|
const later = () => { |
|
|
clearTimeout(timeout); |
|
|
func(...args); |
|
|
}; |
|
|
clearTimeout(timeout); |
|
|
timeout = setTimeout(later, wait); |
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const exerciseUI = new ExerciseUI(); |