feat: add classroom learning and quiz pages for course content delivery and assessment.

This commit is contained in:
supalerk-ar66 2026-01-29 14:39:46 +07:00
parent b59eac1388
commit 6146d65949
2 changed files with 18 additions and 15 deletions

View file

@ -583,9 +583,9 @@ onBeforeUnmount(() => {
<div v-if="currentLesson.type === 'QUIZ'" class="mt-6 p-8 bg-[var(--bg-elevated)] rounded-xl border border-[var(--border-color)] text-center">
<q-icon name="quiz" size="4rem" color="primary" class="mb-4" />
<h2 class="text-xl font-bold mb-2 text-slate-900 dark:text-white">{{ $t('quiz.startTitle', 'แบบทดสอบ') }}</h2>
<p class="text-slate-500 mb-6">{{ getLocalizedText(currentLesson.quiz?.description || currentLesson.description) }}</p>
<p class="text-slate-500 dark:text-slate-400 mb-6">{{ getLocalizedText(currentLesson.quiz?.description || currentLesson.description) }}</p>
<div class="flex justify-center gap-4 text-sm text-slate-500 mb-8">
<div class="flex justify-center gap-4 text-sm text-slate-500 dark:text-slate-400 mb-8">
<span v-if="currentLesson.quiz?.questions?.length"><q-icon name="format_list_numbered" /> {{ currentLesson.quiz.questions.length }} </span>
<span v-if="currentLesson.quiz?.time_limit"><q-icon name="schedule" /> {{ currentLesson.quiz.time_limit }} นาท</span>
</div>
@ -615,7 +615,7 @@ onBeforeUnmount(() => {
:key="file.file_name"
:href="file.presigned_url"
target="_blank"
class="flex items-center gap-4 p-4 rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors group"
class="flex items-center gap-4 p-4 rounded-xl border border-slate-200 dark:!border-white/10 bg-white dark:!bg-slate-800 hover:bg-slate-50 dark:hover:bg-white/5 transition-colors group"
>
<div class="w-10 h-10 rounded-lg bg-red-100 dark:bg-red-900/30 text-red-600 flex items-center justify-center">
<q-icon name="picture_as_pdf" size="24px" />

View file

@ -220,17 +220,20 @@ onUnmounted(() => {
</script>
<template>
<div class="quiz-shell min-h-screen bg-slate-50 dark:bg-[#0b0f1a] text-slate-900 dark:text-slate-200 font-inter antialiased selection:bg-blue-500/20 transition-colors">
<div class="quiz-shell min-h-screen bg-slate-50 dark:bg-[#0b0f1a] text-slate-900 dark:text-slate-200 antialiased selection:bg-blue-500/20 transition-colors">
<!-- Header -->
<header class="h-14 bg-white dark:bg-[#161b22] fixed top-0 inset-x-0 z-[100] flex items-center px-6 border-b border-slate-200 dark:border-white/5 transition-colors">
<header class="h-14 bg-white dark:!bg-[var(--bg-surface)] fixed top-0 inset-x-0 z-[100] flex items-center px-6 border-b border-slate-200 dark:border-white/5 transition-colors">
<div class="flex items-center w-full justify-between">
<div class="flex items-center">
<button class="flex items-center gap-2 text-sm font-bold text-slate-500 hover:text-slate-800 dark:text-slate-400 dark:hover:text-white transition-colors" @click="confirmExit">
<q-icon name="arrow_back" />
<button
class="inline-flex items-center gap-2 text-slate-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-300 transition-all font-black text-sm md:text-base group mr-4"
@click="confirmExit"
>
<q-icon name="arrow_back" size="24px" class="transition-transform group-hover:-translate-x-1" />
<span>{{ $t('quiz.exitTitle') }}</span>
</button>
<div class="w-[1px] h-4 bg-slate-300 dark:bg-white/10 mx-4"/>
<h1 class="text-sm font-black text-slate-900 dark:text-white uppercase tracking-tight truncate max-w-[200px] md:max-w-md hidden md:block">
<h1 class="text-base font-bold text-slate-900 dark:text-white truncate max-w-[200px] md:max-w-md hidden md:block">
{{ quizData ? getLocalizedText(quizData.title) : (courseData ? getLocalizedText(courseData.course.title) : $t('quiz.startTitle')) }}
</h1>
</div>
@ -255,7 +258,7 @@ onUnmounted(() => {
<template v-else>
<!-- 1. START SCREEN -->
<div v-if="currentScreen === 'start'" 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 dark:shadow-2xl relative overflow-hidden transition-colors">
<div class="bg-white dark:!bg-[#1e293b] border border-slate-200 dark:border-white/5 rounded-[32px] p-8 md:p-14 shadow-lg dark:shadow-2xl relative overflow-hidden transition-colors">
<div class="flex justify-center mb-10">
<div class="w-20 h-20 rounded-3xl bg-blue-50 dark:bg-blue-500/10 border border-blue-100 dark:border-blue-500/20 flex items-center justify-center shadow-inner">
@ -267,7 +270,7 @@ onUnmounted(() => {
<h2 class="text-2xl font-black text-slate-900 dark:text-white mb-2 tracking-tight">
{{ quizData ? getLocalizedText(quizData.title) : $t('quiz.startTitle') }}
</h2>
<div class="flex justify-center gap-4 text-sm text-slate-500 mt-2">
<div class="flex justify-center gap-4 text-sm text-slate-500 dark:text-slate-400 mt-2">
<span v-if="quizData?.questions?.length"><q-icon name="format_list_numbered" /> {{ quizData.questions.length }} {{ $t('quiz.questions') }}</span>
<span v-if="quizData?.time_limit"><q-icon name="schedule" /> {{ quizData.time_limit }} {{ $t('quiz.minutes') }}</span>
</div>
@ -302,7 +305,7 @@ onUnmounted(() => {
<!-- 2. TAKING SCREEN -->
<div v-if="currentScreen === 'taking'" class="w-full max-w-[840px] animate-fade-in py-12">
<div v-if="currentQuestion" class="bg-white dark:bg-[#1e293b] border border-slate-200 dark:border-white/5 rounded-[32px] p-8 md:p-12 shadow-xl relative overflow-hidden">
<div v-if="currentQuestion" class="bg-white dark:!bg-[#1e293b] border border-slate-200 dark:border-white/5 rounded-[32px] p-8 md:p-12 shadow-xl relative overflow-hidden">
<!-- Progress Bar -->
<div class="absolute top-0 left-0 right-0 h-1.5 bg-slate-100 dark:bg-white/5">
<div class="h-full bg-blue-500 transition-all duration-300" :style="{ width: ((currentQuestionIndex + 1) / totalQuestions) * 100 + '%' }"></div>
@ -342,7 +345,7 @@ onUnmounted(() => {
<button
@click="prevQuestion"
:disabled="currentQuestionIndex === 0"
class="px-6 py-3 rounded-xl font-bold text-slate-500 hover:bg-slate-50 dark:hover:bg-white/5 disabled:opacity-30 disabled:cursor-not-allowed transition-colors flex items-center gap-2"
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', 'ย้อนกลับ') }}
</button>
@ -350,7 +353,7 @@ onUnmounted(() => {
<button
v-if="currentQuestionIndex < totalQuestions - 1"
@click="nextQuestion"
class="px-8 py-3 bg-slate-900 dark:bg-white text-white dark:text-slate-900 rounded-xl font-bold hover:opacity-90 transition-all flex items-center gap-2"
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" />
</button>
@ -367,7 +370,7 @@ onUnmounted(() => {
<!-- 3. RESULT SCREEN -->
<div v-if="currentScreen === 'result'" 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-[40px] p-10 shadow-2xl text-center relative overflow-hidden">
<div class="bg-white dark:!bg-[#1e293b] border border-slate-200 dark:border-white/5 rounded-[40px] p-10 shadow-2xl text-center relative overflow-hidden">
<div v-if="isSubmitting" class="absolute inset-0 bg-white/80 dark:bg-slate-900/80 z-20 flex flex-col items-center justify-center">
<q-spinner color="primary" size="3em" />
@ -404,7 +407,7 @@ onUnmounted(() => {
<div class="space-y-4 relative z-10">
<button
@click="confirmExit"
class="w-full py-4 bg-slate-900 dark:bg-white text-white dark:text-slate-900 rounded-[20px] font-black text-sm hover:opacity-90 transition-all"
class="w-full py-4 bg-blue-600 hover:bg-blue-500 text-white rounded-[20px] font-black text-sm shadow-xl shadow-blue-600/20 hover:scale-[1.02] active:scale-[0.98] transition-all"
>
{{ $t('quiz.backToLesson') }}
</button>