-
-
- {{ formatPrice(course.price) }}
-
-
- {{ $t('course.free') }}
-
+
+
+
+
+
+
+
{{ $t('course.price', 'ราคาคอร์ส') }}
+
+
+ {{ formatPrice(course.price) }}
+
+
{
rounded
size="lg"
color="primary"
- class="w-full mb-4 shadow-lg shadow-blue-600/30 font-bold"
+ class="w-full h-14 shadow-xl shadow-blue-600/30 font-black tracking-tight"
:label="user ? (course.enrolled ? $t('course.startLearning') : (course.price > 0 ? $t('course.buyNow') : $t('course.enrollFree'))) : $t('course.loginToEnroll')"
:loading="enrollmentLoading"
@click="handleEnroll"
- />
+ >
+
+
+
+
+
+
{{ $t('course.includes', 'คอร์สนี้รวมอะไรบ้าง') }}
+
+
+
+
+
+ {{ $t('course.fullLifetimeAccess', 'เข้าเรียนได้ตลอดชีพ') }}
+
-
-
-
-
-
- {{ $t('course.certificate') }} ({{ $t('course.available') }})
+
+
+
+
+ {{ $t('course.certificate') }}
+
+
+
+
+
+
+ {{ $t('course.accessOnMobile', 'เข้าเรียนได้ทุกอุปกรณ์') }}
-
+
diff --git a/Frontend-Learner/composables/useCourse.ts b/Frontend-Learner/composables/useCourse.ts
index 879c5083..66ab5ade 100644
--- a/Frontend-Learner/composables/useCourse.ts
+++ b/Frontend-Learner/composables/useCourse.ts
@@ -19,6 +19,7 @@ export interface Course {
approved_by?: number
rejection_reason?: string
enrolled?: boolean
+ total_lessons?: number
rating?: string
diff --git a/Frontend-Learner/i18n/locales/en.json b/Frontend-Learner/i18n/locales/en.json
index 0a810b22..768ef6ca 100644
--- a/Frontend-Learner/i18n/locales/en.json
+++ b/Frontend-Learner/i18n/locales/en.json
@@ -48,7 +48,8 @@
"enrollFree": "Enroll for free",
"loginToEnroll": "Log in to enroll",
"minutes": "Minutes",
- "noVideoPreview": "Video preview not available"
+ "noVideoPreview": "Video preview not available",
+ "videoNotSupported": "Your browser does not support the video tag"
},
"sidebar": {
"overview": "Home",
@@ -69,7 +70,8 @@
"emptyDesc": "Try different keywords or check spelling",
"showAll": "Show All",
"loadMore": "Load More",
- "backToCatalog": "Back to Catalog"
+ "backToCatalog": "Back to Catalog",
+ "selectable": "Selected"
},
"myCourses": {
"filterAll": "All",
@@ -84,7 +86,8 @@
"successDesc": "You have successfully enrolled in this course.",
"startNow": "Start Learning",
"later": "Later",
- "alreadyEnrolledHint": "You have already enrolled in the course {course}."
+ "alreadyEnrolledHint": "You have already enrolled in the course {course}.",
+ "error": "Failed to enroll"
},
"certificate": {
"title": "Certificate of Completion",
@@ -195,7 +198,11 @@
"attachments": "Attachments",
"announcements": "Course Announcements",
"posts": "Posts",
- "noAnnouncements": "No announcements yet"
+ "noAnnouncements": "No announcements yet",
+ "quizRequired": "Please pass the quiz \"{title}\" first",
+ "lessonRequired": "Please complete the lesson \"{title}\" first",
+ "notEnrolled": "You are not yet enrolled in this course",
+ "curriculum": "Course Content"
},
"quiz": {
"exitTitle": "Exit Quiz",
@@ -235,6 +242,7 @@
"statusCompleted": "Completed",
"statusSkipped": "Skipped",
"statusNotStarted": "Not Started",
- "alertIncomplete": "Please answer all questions"
+ "alertIncomplete": "Please answer all questions",
+ "yourAnswer": "Your Answer"
}
}
diff --git a/Frontend-Learner/i18n/locales/th.json b/Frontend-Learner/i18n/locales/th.json
index 056b3b44..5461bfd9 100644
--- a/Frontend-Learner/i18n/locales/th.json
+++ b/Frontend-Learner/i18n/locales/th.json
@@ -48,7 +48,8 @@
"enrollFree": "ลงทะเบียนเรียนฟรี",
"loginToEnroll": "เข้าสู่ระบบเพื่อลงทะเบียน",
"minutes": "นาที",
- "noVideoPreview": "วิดีโอตัวอย่างยังไม่พร้อมใช้งาน"
+ "noVideoPreview": "วิดีโอตัวอย่างยังไม่พร้อมใช้งาน",
+ "videoNotSupported": "เบราว์เซอร์ของคุณไม่รองรับการเล่นวิดีโอ"
},
"sidebar": {
"overview": "หน้าหลัก",
@@ -69,7 +70,8 @@
"emptyDesc": "ลองใช้คำค้นหาอื่น หรือตรวจดูความถูกต้องของตัวอักษรอีกครั้ง",
"showAll": "แสดงทั้งหมด",
"loadMore": "โหลดเพิ่มเติม",
- "backToCatalog": "กลับหน้ารายการคอร์ส"
+ "backToCatalog": "กลับหน้ารายการคอร์ส",
+ "selectable": "รายการที่เลือก"
},
"myCourses": {
"filterAll": "ทั้งหมด",
@@ -84,7 +86,8 @@
"successDesc": "คุณได้ลงทะเบียนคอร์สนี้เรียบร้อยแล้ว",
"startNow": "เริ่มเรียนทันที",
"later": "ไว้ทีหลัง",
- "alreadyEnrolledHint": "ท่านเคยลงทะเบียนคอร์ส {course} นี้ไปเรียบร้อยแล้ว"
+ "alreadyEnrolledHint": "ท่านเคยลงทะเบียนคอร์ส {course} นี้ไปเรียบร้อยแล้ว",
+ "error": "ไม่สามารถลงทะเบียนได้"
},
"certificate": {
"title": "ใบประกาศนียบัตรจบหลักสูตร",
@@ -195,7 +198,11 @@
"attachments": "เอกสารประกอบ",
"announcements": "ประกาศในคอร์ส",
"posts": "โพสต์",
- "noAnnouncements": "ยังไม่มีประกาศในขณะนี้"
+ "noAnnouncements": "ยังไม่มีประกาศในขณะนี้",
+ "quizRequired": "กรุณาทำแบบทดสอบ \"{title}\" ให้ผ่านก่อน",
+ "lessonRequired": "กรุณาเรียนบทเรียน \"{title}\" ให้จบก่อน",
+ "notEnrolled": "คุณยังไม่ได้ลงทะเบียนในคอร์สนี้",
+ "curriculum": "เนื้อหาหลักสูตร"
},
"quiz": {
"startTitle": "แบบทดสอบ",
@@ -235,6 +242,7 @@
"statusCompleted": "ทำแล้ว",
"statusSkipped": "ข้าม",
"statusNotStarted": "ยังไม่ทำ",
- "alertIncomplete": "กรุณาเลือกคำตอบให้ครบทุกข้อ"
+ "alertIncomplete": "กรุณาเลือกคำตอบให้ครบทุกข้อ",
+ "yourAnswer": "คำตอบของคุณ"
}
}
diff --git a/Frontend-Learner/pages/browse/discovery.vue b/Frontend-Learner/pages/browse/discovery.vue
index b75b933a..51401e36 100644
--- a/Frontend-Learner/pages/browse/discovery.vue
+++ b/Frontend-Learner/pages/browse/discovery.vue
@@ -112,7 +112,7 @@ const handleEnroll = async (id: number) => {
} else {
$q.notify({
type: 'negative',
- message: res.error || 'Failed to enroll',
+ message: res.error || t('enrollment.error'),
position: 'top',
timeout: 3000,
actions: [{ icon: 'close', color: 'white' }]
@@ -127,6 +127,15 @@ watch(selectedCategoryIds, () => {
loadCourses(1);
}, { deep: true });
+const toggleCategory = (id: number) => {
+ const index = selectedCategoryIds.value.indexOf(id)
+ if (index === -1) {
+ selectedCategoryIds.value.push(id)
+ } else {
+ selectedCategoryIds.value.splice(index, 1)
+ }
+}
+
onMounted(() => {
loadCategories();
loadCourses();
@@ -140,91 +149,101 @@ onMounted(() => {
-
-
-
-
- {{ $t('discovery.title') }}
-
+
+
+
+
+
+ {{ $t('discovery.title') }}
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
{{ $t('discovery.emptyTitle') }}
-
- {{ $t('discovery.emptyDesc') }}
-
-
+
+
+
+
+
+
+
{{ $t('discovery.emptyTitle') }}
+
+ {{ $t('discovery.emptyDesc') }}
+
+
+
diff --git a/Frontend-Learner/pages/classroom/learning.vue b/Frontend-Learner/pages/classroom/learning.vue
index 9999f491..2d944ea9 100644
--- a/Frontend-Learner/pages/classroom/learning.vue
+++ b/Frontend-Learner/pages/classroom/learning.vue
@@ -219,14 +219,14 @@ const loadLesson = async (lessonId: number) => {
msg = accessRes.data.lock_reason
} else if (accessRes.data.required_quiz_pass && !accessRes.data.required_quiz_pass.is_passed) {
const quizTitle = getLocalizedText(accessRes.data.required_quiz_pass.title)
- msg = `กรุณาทำแบบทดสอบ "${quizTitle}" ให้ผ่านก่อน`
+ msg = t('classroom.quizRequired', { title: quizTitle })
} else if (accessRes.data.required_lessons && accessRes.data.required_lessons.length > 0) {
const reqLesson = accessRes.data.required_lessons.find((l: any) => !l.is_completed)
if (reqLesson) {
- msg = `กรุณาเรียนบทเรียน "${getLocalizedText(reqLesson.title)}" ให้จบก่อน`
+ msg = t('classroom.lessonRequired', { title: getLocalizedText(reqLesson.title) })
}
} else if (accessRes.data.is_enrolled === false) {
- msg = 'คุณยังไม่ได้ลงทะเบียนในคอร์สนี้'
+ msg = t('classroom.notEnrolled')
}
alert(msg)
@@ -516,27 +516,31 @@ onBeforeUnmount(() => {
-
-
-
-
-
- {{ $t('classroom.backToDashboard') }}
-
+
+
+
+ {{ $t('common.close') }}
+ {{ $t('classroom.backToDashboard') }}
+
-
-
+
+
+
+ {{ $t('classroom.curriculum', 'เนื้อหาหลักสูตร') }}
+
{{ courseData ? getLocalizedText(courseData.course.title) : $t('classroom.loadingTitle') }}
@@ -581,6 +585,7 @@ onBeforeUnmount(() => {
v-if="currentLesson && videoSrc && !isLessonLoading"
ref="videoPlayerComp"
:src="videoSrc"
+ :poster="courseData?.course?.thumbnail_url"
:initialSeekTime="initialSeekTime"
@timeupdate="handleVideoTimeUpdate"
@ended="onVideoEnded"
@@ -588,11 +593,21 @@ onBeforeUnmount(() => {
/>
-
-
+
+
+
![]()
+
+
-
-
{{ $t('common.loading') }}...
+
+
+
+
+
{{ $t('common.loading') }}
@@ -600,7 +615,7 @@ onBeforeUnmount(() => {
-
{{ getLocalizedText(currentLesson.title) }}
+ {{ getLocalizedText(currentLesson.title) }}
{{ getLocalizedText(currentLesson.description) }}
@@ -615,10 +630,10 @@ onBeforeUnmount(() => {
- {{ currentLesson.quiz.questions.length }} ข้อ
+ {{ currentLesson.quiz.questions.length }} {{ $t('quiz.questions') }}
- {{ currentLesson.quiz.time_limit }} นาที
+ {{ currentLesson.quiz.time_limit }} {{ $t('quiz.minutes') }}
@@ -627,7 +642,7 @@ onBeforeUnmount(() => {
size="lg"
rounded
no-caps
- :label="$t('quiz.startBtn', 'เริ่มทำแบบทดสอบ')"
+ :label="$t('quiz.startBtn')"
icon="play_arrow"
@click="$router.push(`/classroom/quiz?course_id=${courseId}&lesson_id=${currentLesson.id}`)"
/>
@@ -642,7 +657,7 @@ onBeforeUnmount(() => {
- {{ $t('classroom.attachments') || 'เอกสารประกอบการเรียน' }}
+ {{ $t('classroom.attachments') }}
{
const getLocalizedText = (text: any) => {
if (!text) return ''
if (typeof text === 'string') return text
- return text.th || text.en || ''
+ const currentLocale = locale.value as 'th' | 'en'
+ return text[currentLocale] || text.th || text.en || ''
}
const lessonProgress = ref(null)
diff --git a/Frontend-Learner/pages/dashboard/index.vue b/Frontend-Learner/pages/dashboard/index.vue
index b8b69554..41a47f55 100644
--- a/Frontend-Learner/pages/dashboard/index.vue
+++ b/Frontend-Learner/pages/dashboard/index.vue
@@ -53,27 +53,28 @@ onMounted(async () => {
-
-
-
-
-
-
-
- {{ $t('dashboard.welcomeTitle') }}, {{ currentUser?.firstName }}!
-
-
-
- {{ $t('dashboard.welcomeSubtitle') }}
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('dashboard.welcomeTitle') }}, {{ currentUser?.firstName }}!
+
+
+ {{ $t('dashboard.welcomeSubtitle') }}
+
-
-
-
-
-
@@ -104,13 +105,12 @@ onMounted(async () => {
diff --git a/Frontend-Learner/pages/dashboard/my-courses.vue b/Frontend-Learner/pages/dashboard/my-courses.vue
index 5b80a822..22cdced5 100644
--- a/Frontend-Learner/pages/dashboard/my-courses.vue
+++ b/Frontend-Learner/pages/dashboard/my-courses.vue
@@ -18,6 +18,7 @@ useHead({
const route = useRoute()
const showEnrollModal = ref(false)
const activeFilter = ref<'all' | 'progress' | 'completed'>('all')
+const searchQuery = ref('')
// Check URL query parameters to show 'Enrollment Success' modal
@@ -71,6 +72,7 @@ const loadEnrolledCourses = async () => {
enrollment_id: item.id,
title: item.course.title,
progress: item.progress_percentage || 0,
+ lessons: item.course.total_lessons || 0,
completed: item.status === 'COMPLETED',
thumbnail_url: item.course.thumbnail_url
}))
@@ -83,6 +85,16 @@ watch(activeFilter, () => {
loadEnrolledCourses()
})
+// Client-side Search Filtering
+const filteredEnrolledCourses = computed(() => {
+ if (!searchQuery.value) return enrolledCourses.value
+ const query = searchQuery.value.toLowerCase()
+ return enrolledCourses.value.filter(c => {
+ const title = getLocalizedText(c.title).toLowerCase()
+ return title.includes(query)
+ })
+})
+
onMounted(() => {
if (route.query.enrolled) {
showEnrollModal.value = true
@@ -138,21 +150,43 @@ const validCourseId = computed(() => {
-
-
-
{{ $t('sidebar.myCourses') }}
+
+
+
+
+
+ {{ $t('sidebar.myCourses') }}
+
-
-
+
+
+
@@ -163,7 +197,7 @@ const validCourseId = computed(() => {
-
+
{
-
-
{{ $t('myCourses.emptyTitle') }}
-
{{ $t('myCourses.emptyDesc') }}
-
{{ $t('myCourses.goToDiscovery') }}
+
+
+
+ {{ searchQuery ? $t('discovery.emptyTitle') : $t('myCourses.emptyTitle') }}
+
+
+ {{ searchQuery ? $t('discovery.emptyDesc') : $t('myCourses.emptyDesc') }}
+
+
{{ $t('myCourses.goToDiscovery') }}
+