feat: Implement core classroom functionality including video player, learning and quiz pages, course detail view, and i18n support.

This commit is contained in:
supalerk-ar66 2026-02-12 16:05:37 +07:00
parent 008f712480
commit 7f5119e5aa
9 changed files with 289 additions and 109 deletions

View file

@ -83,7 +83,7 @@ const jumpToQuestion = (targetIndex: number) => {
if (!isAnswered && !isSkippable) {
$q.notify({
type: 'warning',
message: t('quiz.pleaseSelectAnswer', 'กรุณาเลือกคำตอบ'),
message: t('quiz.pleaseSelectAnswer'),
position: 'top',
timeout: 2000
})
@ -106,6 +106,8 @@ const totalQuestions = computed(() => {
return quizData.value?.questions?.length || 0
})
const hasQuestions = computed(() => totalQuestions.value > 0)
const showQuestionMap = computed(() => $q.screen.gt.sm)
const timerDisplay = computed(() => {
@ -252,12 +254,37 @@ const submitQuiz = async (auto = false) => {
return
}
// Confirmation before submission
if (!confirm(t('quiz.submitConfirm', 'ยืนยันการส่งคำตอบ?'))) {
return
}
// Premium Confirmation before submission
$q.dialog({
title: `<div class="text-slate-900 dark:text-white font-black text-xl">${t('quiz.warningTitle')}</div>`,
message: `<div class="text-slate-600 dark:text-slate-300 text-base leading-relaxed mt-2">${t('quiz.submitConfirm')}</div>`,
html: true,
persistent: true,
class: 'rounded-[24px]',
ok: {
label: t('common.ok'),
color: 'primary',
unelevated: true,
rounded: true,
class: 'px-8 font-black'
},
cancel: {
label: t('common.cancel'),
color: 'grey-7',
flat: true,
rounded: true,
class: 'font-bold'
}
}).onOk(() => {
processSubmitQuiz(auto)
})
return
}
processSubmitQuiz(auto)
}
const processSubmitQuiz = async (auto = false) => {
// 2. Start Submission Process
if (timerInterval) clearInterval(timerInterval)
@ -285,12 +312,13 @@ const submitQuiz = async (auto = false) => {
}
} else {
// Fallback error handling
alert(res.error || 'Failed to submit quiz')
// Maybe go back to taking?
$q.notify({
type: 'negative',
message: res.error || 'Failed to submit quiz'
})
}
} catch (err) {
console.error('Submit quiz error:', err)
alert('An unexpected error occurred.')
} finally {
isSubmitting.value = false
}
@ -349,7 +377,7 @@ const getCorrectChoiceId = (questionId: number) => {
</button>
<div class="w-[1px] h-4 bg-slate-300 dark:bg-white/10 mx-4"/>
<h1 class="text-base font-bold text-slate-900 dark:text-white truncate max-w-[200px] md:max-w-md hidden md:block">
{{ currentScreen === 'review' ? $t('quiz.reviewAnswers', 'เฉลยคำตอบ') : (quizData ? getLocalizedText(quizData.title) : (courseData ? getLocalizedText(courseData.course.title) : $t('quiz.startTitle'))) }}
{{ currentScreen === 'review' ? $t('quiz.reviewAnswers') : (quizData ? getLocalizedText(quizData.title) : (courseData ? getLocalizedText(courseData.course.title) : $t('quiz.startTitle'))) }}
</h1>
</div>
@ -370,6 +398,22 @@ const getCorrectChoiceId = (questionId: number) => {
<p class="text-sm font-medium text-slate-500">{{ $t('classroom.loadingTitle') }}</p>
</div>
<div v-else-if="!quizData || !hasQuestions" class="w-full max-w-[640px] animate-fade-in py-12">
<div class="bg-white dark:!bg-[#1e293b] border border-slate-200 dark:border-white/5 rounded-[32px] p-8 md:p-14 shadow-lg text-center">
<div class="w-20 h-20 rounded-3xl bg-amber-50 dark:bg-amber-500/10 border border-amber-100 dark:border-amber-500/20 flex items-center justify-center mx-auto mb-6">
<q-icon name="warning" size="2.5rem" color="warning" />
</div>
<h2 class="text-2xl font-black text-slate-900 dark:text-white mb-2">{{ $t('quiz.noQuizData') }}</h2>
<p class="text-slate-500 dark:text-slate-400 mb-8">{{ $t('quiz.noQuizDesc') }}</p>
<button
class="px-8 py-3 bg-blue-600 text-white rounded-xl hover:bg-blue-500 transition-all font-black"
@click="confirmExit"
>
{{ $t('quiz.backToLesson') }}
</button>
</div>
</div>
<template v-else>
<!-- 1. START SCREEN -->
<div v-if="currentScreen === 'start'" class="w-full max-w-[640px] animate-fade-in py-12">
@ -476,7 +520,7 @@ const getCorrectChoiceId = (questionId: number) => {
:disabled="currentQuestionIndex === 0"
class="px-6 py-3 rounded-xl font-bold text-slate-600 dark:text-slate-300 bg-slate-100 dark:bg-white/5 hover:bg-slate-200 dark:hover:bg-white/10 disabled:opacity-30 disabled:cursor-not-allowed transition-all flex items-center gap-2"
>
<q-icon name="arrow_back" /> {{ $t('common.back', 'ย้อนกลับ') }}
<q-icon name="arrow_back" /> {{ $t('common.back') }}
</button>
<button
@ -484,7 +528,7 @@ const getCorrectChoiceId = (questionId: number) => {
@click="nextQuestion"
class="px-8 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-xl font-bold transition-all shadow-lg shadow-blue-500/20 flex items-center gap-2"
>
{{ $t('common.next', 'ถัดไป') }} <q-icon name="arrow_forward" />
{{ $t('common.next') }} <q-icon name="arrow_forward" />
</button>
<button
v-else
@ -556,7 +600,7 @@ const getCorrectChoiceId = (questionId: number) => {
@click="reviewQuiz"
class="w-full py-2 text-blue-500 hover:text-blue-700 dark:hover:text-blue-400 font-bold text-sm transition-colors mt-2"
>
{{ $t('quiz.reviewAnswers', 'ดูเฉลยคำตอบ') }}
{{ $t('quiz.reviewAnswers') }}
</button>
</div>
</div>