feat: Implement core classroom functionality including video player, learning and quiz pages, course detail view, and i18n support.
This commit is contained in:
parent
008f712480
commit
7f5119e5aa
9 changed files with 289 additions and 109 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue