ยังไม่มีบัญชีสมาชิก?
diff --git a/Frontend-Learner/pages/browse/discovery.vue b/Frontend-Learner/pages/browse/discovery.vue
index 01b5369d..e8f15f77 100644
--- a/Frontend-Learner/pages/browse/discovery.vue
+++ b/Frontend-Learner/pages/browse/discovery.vue
@@ -1,9 +1,7 @@
-
-
-
-
-
-
-
-
-
- {{ $t("discovery.title") }}
-
-
-
- {{ $t("discovery.subtitle") }}
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
ค้นหา
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- {{ $t("discovery.foundTotal") }} {{ filteredCourses.length }} {{ $t("discovery.items") }}
-
-
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
![]()
+
+ {{ course.category_name }}
+
+
+
+
+
+
{{ getLocalizedText(course.title) }}
+
+
+
+
+
+ {{ course.formatted_price }}
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+ {{ course.category_name }}
+
+
+
+
+
{{ getLocalizedText(course.title) }}
+
+
+
+
+ {{ course.formatted_price }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t("discovery.emptyTitle") }}
+
{{ $t("discovery.emptyDesc") }}
+
+
-
-
-
-
-
-
-
-
- {{ $t("discovery.emptyTitle") }}
-
-
- {{ $t("discovery.emptyDesc") }}
-
-
-
-
-
-
-
-
- {{ $t("discovery.backToCatalog") }}
-
-
-
-
-
-
-
diff --git a/Frontend-Learner/pages/browse/index.vue b/Frontend-Learner/pages/browse/index.vue
index 415ee060..669fa5d5 100644
--- a/Frontend-Learner/pages/browse/index.vue
+++ b/Frontend-Learner/pages/browse/index.vue
@@ -1,26 +1,22 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- คอร์สเรียนทั้งหมด
-
-
-
- เริ่มต้นอัปสกิลของคุณวันนี้ด้วยหลักสูตรคุณภาพที่ออกแบบโดยผู้เชี่ยวชาญในอุตสาหกรรม
-
-
-
-
-
-
-
-
-
-
-
-
-
-
คอร์สเรียนทั้งหมด
-
พัฒนาทักษะใหม่ๆ กับผู้เชี่ยวชาญจากทั่วโลก
+
+
+
+
-
-
-
-
-
-
-
-
- ค้นหา
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- พร้อมจะเริ่มต้นแล้วหรือยัง?
-
-
- ลงทะเบียนฟรีวันนี้เพื่อเข้าถึงบทเรียนพื้นฐาน และติดตามความคืบหน้าการเรียนของคุณได้ทันที ไม่มีค่าใช้จ่ายแอบแฝง
-
-
-
- สมัครสมาชิกฟรี
-
-
-
+
+
+
+
{{ searchQuery ? 'ไม่พบคอร์สที่คุณค้นหา' : 'ไม่มีคอร์สในหมวดหมู่นี้' }}
+
ลองใช้คำค้นหาอื่น หรือเลือกหมวดหมู่อื่นเพื่อดูคอร์สที่เรามีให้บริการ
+
+ แสดงคอร์สทั้งหมด
+
+
+
+
diff --git a/Frontend-Learner/pages/dashboard/index.vue b/Frontend-Learner/pages/dashboard/index.vue
index 9e84868c..5e934d24 100644
--- a/Frontend-Learner/pages/dashboard/index.vue
+++ b/Frontend-Learner/pages/dashboard/index.vue
@@ -1,44 +1,46 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
- {{ $t("dashboard.heroTitle") }}
- {{
- $t("dashboard.heroSubtitle")
- }}
-
-
-
- {{ $t("dashboard.heroDesc") }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t("dashboard.continueLearningTitle") }}
-
-
- {{ $t("dashboard.myCourses") }}
-
-
+
+
{{ $t('dashboard.welcomeTitle') }} {{ currentUser?.firstName || 'User' }} !
+
+ {{ $t('dashboard.welcomeSubtitle') }}
+
+
+ {{ $t('dashboard.moreCourses') }}
+
+
-
-
-
-
![]()
-
-
-
- {{ getLocalizedText(heroCourse.title) }}
-
-
-
-
-
- {{ heroCourse.progress }}%
-
-
-
-
- {{
- heroCourse.progress === 100
- ? $t("dashboard.studyAgain")
- : $t("dashboard.continue")
- }}
-
-
-
+
+
+
+
+
+
+
+
+
{{ $t('discovery.design') }}
+
-
+
+
+
+
+
+
+
{{ $t('discovery.programming') }}
+
+
+
+
+
+
+
+
+
{{ $t('discovery.business') }}
+
+
+
-
-
-
-
-
![]()
+
+
+
{{ $t('dashboard.continueLearningTitle') }}
+
+ {{ $t('dashboard.viewAll') }}
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+ {{ getLocalizedText(heroCourse.category) }}
+
+
+
+ {{ $t('common.latest') }} {{ new Date(heroCourse.last_accessed).toLocaleDateString('th-TH', { day: 'numeric', month: 'short' }) }}
+
+
+
+
+ {{ getLocalizedText(heroCourse.title) || 'Advanced UI/UX Design มาสเตอร์คลาส' }}
+
+
+
+
+
+ {{ $t('course.progress') }}: {{ heroCourse.progress || 0 }}%
+
+
+
+
+
+
+
+
![]()
+
+
+
+ {{ heroCourse.instructor.firstName || heroCourse.instructor.first_name ? `${heroCourse.instructor.firstName || heroCourse.instructor.first_name} ${heroCourse.instructor.lastName || heroCourse.instructor.last_name || ''}` : heroCourse.instructor.username || 'ผู้สอน' }}
+
+
{{ heroCourse.instructor.bio || heroCourse.instructor.role?.name?.th || 'Instructor' }}
+
+
+
+
+ {{ $t('course.continueLearning') }}
+
+
+
+
+
+
+ ไม่มีคอร์สเรียนปัจจุบัน
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- {{ getLocalizedText(course.title) }}
-
-
-
-
-
- {{
- course.progress === 100
- ? $t("dashboard.studyAgain")
- : $t("dashboard.continue")
- }}
-
-
+
+
+
+ {{ currentUser?.firstName ? `${currentUser.firstName} ${currentUser.lastName || ''}` : 'ผู้ใช้งาน' }}
+
+
{{ $t('common.student') }}
+
+
+
+ {{ String(enrolledCourses.length || 0).padStart(2, '0') }}
+ {{ $t('myCourses.filterProgress') }}
+
+
+ {{ String(enrolledCourses.filter(c => c.progress >= 100).length || 0).padStart(2, '0') }}
+ {{ $t('myCourses.filterCompleted') }}
-
-
-
- {{ $t("dashboard.startNewCourse") }}
+
+
+
+
+
+ {{ $t('dashboard.recommendedCourses') }}
+
+
+
+
+
+
![]()
+
+
+
+
{{ getLocalizedText(course.title) }}
+
+ {{ getLocalizedText(course.category) || 'อื่นๆ' }}
+ ฟรี
+ ฿{{ Number(course.price).toLocaleString() }}
+
+
+
-
-
-
-
-
-
-
-
- {{ $t("dashboard.knowledgeLibrary") }}
-
-
- {{ $t("dashboard.libraryDesc") }}
-
-
-
-
-
-
-
-
-
-
-
- {{ $t("dashboard.chooseLibrary") }}
-
-
- {{ $t("dashboard.viewAll") }}
-
-
-
-
-
-
-
-
-
-
-
- {{ $t("dashboard.emptyLibraryTitle") }}
-
-
- {{ $t("dashboard.emptyLibraryDesc") }}
-
-
- {{ $t("dashboard.viewAllCourses") }}
-
-
-
-
-
-
-
-
- {{ $t("dashboard.recommendedCourses") }}
-
-
-
-
-
-
-
-
-
-
-
{{ $t("dashboard.noRecommended") }}
-
-
+
+
+ {{ $t('dashboard.viewAllCourses') }}
+
+
+
diff --git a/Frontend-Learner/pages/dashboard/my-courses.vue b/Frontend-Learner/pages/dashboard/my-courses.vue
index 9b410f8f..00b0a210 100644
--- a/Frontend-Learner/pages/dashboard/my-courses.vue
+++ b/Frontend-Learner/pages/dashboard/my-courses.vue
@@ -1,303 +1,369 @@
-
-
-
-
-
-
-
คอร์สของฉัน
-
ติดตามความคืบหน้าและเรียนรู้ต่อจากจุดที่ค้างไว้
+
+
-
-
-
-
-
-
-
+
+
+
+
+
{{ $t('dashboard.continueLearningTitle') }}
-
-
-
-
-
ค้นหา
+
+
+
+
+
![]()
+
+
+
+
+
+
+ {{ course.category_name }}
+
+
{{ getLocalizedText(course.title) }}
+
+
+
+
+ {{ $t('course.progress') }}: {{ course.progress }}%
+
+
+
+
{{ $t('dashboard.continue') }}
+
+
-
+
-
-
+
+
+
+
+
{{ $t('myCourses.title') }}
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ {{ $t('myCourses.filterAll') }}
+
+
+
+
+ {{ getLocalizedText(cat.name) }}
+
+
+
-
-
-
-
- {{ searchQuery ? $t('discovery.emptyTitle') : $t('myCourses.emptyTitle') }}
-
-
- {{ searchQuery ? $t('discovery.emptyDesc') : $t('myCourses.emptyDesc') }}
-
-
{{ $t('myCourses.goToDiscovery') }}
-
- {{ $t('discovery.showAll') }}
-
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+ {{ course.category_name }}
+
+
+
+
+
+
{{ getLocalizedText(course.title) }}
+
+
+
+
+
+
{{ $t('course.progress') }}: {{ course.progress }}%
+
+
+
+
+
+ {{ $t('course.certificate') }}
+
+
+
+ {{ course.completed ? $t('course.studyAgain') : $t('dashboard.continue') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+ {{ course.category_name }}
+
+
+
+
+
+
+
+
{{ getLocalizedText(course.title) }}
+
+
+
+
+
+
+
+ {{ $t('course.progress') }}:
+ {{ course.progress }}%
+
+
+
+
+
+
+
+ {{ $t('course.downloadCertificate') }}
+
+
+
+ {{ course.completed ? $t('course.studyAgain') : $t('dashboard.continue') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t('myCourses.searchNoResult') }}
+
{{ $t('myCourses.searchNoResultDesc') }}
+
+
+
-
- ✓
-
+ ✓
{{ $t('enrollment.successTitle') }}
{{ $t('enrollment.successDesc') }}
-
-
-
+
+
+
-
-
-
diff --git a/Frontend-Learner/pages/dashboard/profile.vue b/Frontend-Learner/pages/dashboard/profile.vue
index be58fc39..74b4c141 100644
--- a/Frontend-Learner/pages/dashboard/profile.vue
+++ b/Frontend-Learner/pages/dashboard/profile.vue
@@ -4,13 +4,15 @@ definePageMeta({
middleware: 'auth'
})
-useHead({
- title: 'ตั้งค่าบัญชี - e-Learning'
-})
-
+const { locale, t } = useI18n()
const { currentUser, updateUserProfile, changePassword, uploadAvatar, sendVerifyEmail, fetchUserProfile } = useAuth()
const { getLocalizedText } = useCourse()
-const { locale, t } = useI18n()
+import { useQuasar } from 'quasar'
+const $q = useQuasar()
+
+useHead({
+ title: `${t('userMenu.settings')} - e-Learning`
+})
@@ -57,6 +59,13 @@ const passwordForm = reactive({
confirmPassword: ''
})
+const showPasswordModal = ref(false)
+const showPassword = reactive({
+ current: false,
+ new: false,
+ confirm: false
+})
+
// Rules have been moved to components
@@ -95,9 +104,10 @@ const handleFileUpload = async (fileOrEvent: File | Event) => {
if (result.success && result.data?.avatar_url) {
userData.value.photoURL = result.data.avatar_url
+ $q.notify({ type: 'positive', message: 'อัปเดตรูปโปรไฟล์สำเร็จ', position: 'top' })
} else {
console.error('Upload failed:', result.error)
- alert(result.error || t('profile.updateError'))
+ $q.notify({ type: 'negative', message: result.error || t('profile.updateError') || 'อัปเดตรูปโปรไฟล์ไม่สำเร็จ', position: 'top' })
}
}
}
@@ -131,9 +141,9 @@ const handleUpdateProfile = async () => {
const result = await updateUserProfile(payload)
if (result?.success) {
- // success logic
+ $q.notify({ type: 'positive', message: t('profile.updateSuccess'), position: 'top' })
} else {
- alert(result?.error || t('profile.updateError'))
+ $q.notify({ type: 'negative', message: result?.error || t('profile.updateError'), position: 'top' })
}
isProfileSaving.value = false
@@ -145,19 +155,19 @@ const handleSendVerifyEmail = async () => {
isSendingVerify.value = false
if (result.success) {
- alert(result.message || t('profile.verifyEmailSuccess') || 'ส่งอีเมลยืนยันสำเร็จ')
+ $q.notify({ type: 'positive', message: result.message || t('profile.verifyEmailSuccess') || 'ส่งอีเมลยืนยันสำเร็จ', position: 'top' })
} else {
if (result.code === 400) {
- alert(t('profile.emailAlreadyVerified') || 'อีเมลของคุณได้รับการยืนยันแล้ว')
+ $q.notify({ type: 'warning', message: t('profile.emailAlreadyVerified') || 'อีเมลของคุณได้รับการยืนยันแล้ว', position: 'top' })
} else {
- alert(result.error || t('profile.verifyEmailError') || 'ส่งอีเมลไม่สำเร็จ')
+ $q.notify({ type: 'negative', message: result.error || t('profile.verifyEmailError') || 'ส่งอีเมลไม่สำเร็จ', position: 'top' })
}
}
}
const handleUpdatePassword = async () => {
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
- alert('รหัสผ่านใหม่ไม่ตรงกัน')
+ $q.notify({ type: 'negative', message: 'รหัสผ่านใหม่ไม่ตรงกัน', position: 'top' })
return
}
@@ -169,12 +179,13 @@ const handleUpdatePassword = async () => {
})
if (result.success) {
- alert(t('profile.passwordSuccess'))
+ $q.notify({ type: 'positive', message: t('profile.passwordSuccess') || 'เปลี่ยนรหัสผ่านสำเร็จ', position: 'top' })
passwordForm.currentPassword = ''
passwordForm.newPassword = ''
passwordForm.confirmPassword = ''
+ showPasswordModal.value = false
} else {
- alert(result.error || t('profile.passwordError'))
+ $q.notify({ type: 'negative', message: result.error || t('profile.passwordError') || 'เปลี่ยนรหัสผ่านไม่สำเร็จ', position: 'top' })
}
isPasswordSaving.value = false
@@ -203,170 +214,216 @@ onMounted(async () => {
-
+
-
-
-
-
-
-
-
- {{ (isHydrated && isEditing) ? $t('profile.editProfile') : $t('profile.myProfile') }}
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ userData.firstName }} {{ userData.lastName }}
-
-
-
-
- {{ userData.email }}
-
-
-
- {{ userData.emailVerifiedAt ? $t('profile.emailVerified') : $t('profile.verifyEmail') }}
-
-
-
-
+
+
+
+
{{ $t('profile.myProfile') }}
+
{{ $t('profile.publicInfo') }}
-
-
-
-
-
- {{ $t('profile.accountDetails') }}
-
-
-
-
-
-
-
-
{{ $t('profile.phone') }}
-
{{ userData.phone || '-' }}
-
-
-
-
-
-
-
-
{{ $t('profile.joinedAt') }}
-
{{ formatDate(userData.createdAt) }}
-
-
+
+
+
+
+
+
+
+ {{ $t('profile.uploading') }}
+ {{ $t('profile.changeAvatar') }}
+
+
{{ $t('profile.avatarHint') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('profile.emailVerified') }}
+
+
+ {{ $t('profile.verifying') }}{{ $t('profile.verifyNow') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('common.cancel') }}
+
+ {{ $t('profile.saving') }}
+ {{ $t('common.saveChanges') }}
+
-
-
-
-
-
-
- {{ $t('profile.generalInfo') }}
-
-
- {{ $t('profile.security') }}
-
+
+
+
+
{{ $t('profile.security') }}
+
{{ $t('profile.securitySubtitle') }}
+
+
+
+
+
+
+
+
+
+
{{ $t('profile.password') }}
+
{{ $t('profile.securitySubtitle') }}
+
+
+ {{ $t('profile.changePasswordBtn') }}
+
-
-
-
+
+
+
+
+
+
+ {{ $t('profile.changePasswordBtn') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -375,7 +432,13 @@ onMounted(async () => {
color: white;
}
-/* Removed card-premium and dark mode overrides as we used utility classes */
+.custom-pwd-input :deep(.q-field__control) {
+ border-radius: 8px;
+ background-color: #f8fafc;
+}
+.dark .custom-pwd-input :deep(.q-field__control) {
+ background-color: #1e293b;
+}
.fade-in {
animation: fadeIn 0.4s ease-out forwards;
diff --git a/Frontend-Learner/pages/index.vue b/Frontend-Learner/pages/index.vue
index de7ea2e6..1b03a4ef 100644
--- a/Frontend-Learner/pages/index.vue
+++ b/Frontend-Learner/pages/index.vue
@@ -13,47 +13,29 @@ useHead({
title: 'E-Learning System - ระบบการเรียนการสอนออนไลน์'
})
+import { CATEGORY_CARDS, WHY_CHOOSE_US } from '@/constants/landing'
+
const { fetchCategories } = useCategory()
const { fetchCourses, getLocalizedText } = useCourse()
const { user } = useAuth()
-const stepOneCards = [
- { title: 'AI Foundations', desc: 'เข้าใจพื้นฐาน AI ใช้งานจริงได้ทุกสายงาน', bgClass: 'bg-slate-900', textClass: 'text-white', arrowClass: 'text-white/60 border-white/20 group-hover:text-slate-900', categorySlug: 'programming' },
- { title: 'Data Analyst', desc: 'เรียนจนทำ Dashboard วิเคราะห์ Data ได้เลย', bgClass: 'bg-amber-500', textClass: 'text-slate-900', arrowClass: 'text-slate-900/40 border-slate-900/10 group-hover:text-amber-500', categorySlug: 'business' },
- { title: 'Front-End Web Developer', desc: 'เขียนเว็บสวย ใช้งานได้จริงตั้งแต่หน้าแรก', bgClass: 'bg-orange-500', textClass: 'text-white', arrowClass: 'text-white/60 border-white/20 group-hover:text-orange-500', categorySlug: 'programming' },
- { title: 'UX/UI Designer', desc: 'ต่อยอดทำ Portfolio ไม่มีประสบการณ์ก็เรียนได้', bgClass: 'bg-pink-600', textClass: 'text-white', arrowClass: 'text-white/60 border-white/20 group-hover:text-pink-600', categorySlug: 'design' },
- { title: 'Product Manager', desc: 'เก็บทุกทักษะ ปั้น Product วางแผนแบบมือโปร', bgClass: 'bg-teal-500', textClass: 'text-white', arrowClass: 'text-white/60 border-white/20 group-hover:text-teal-500', categorySlug: 'business' },
- { title: 'Back-End Developer', desc: 'เข้าใจโครงสร้างระบบและฐานข้อมูลหลังบ้าน', bgClass: 'bg-blue-600', textClass: 'text-white', arrowClass: 'text-white/60 border-white/20 group-hover:text-blue-600', categorySlug: 'programming' },
- { title: 'Supply Chain & Logistics', desc: 'ใช้ Data วางแผนโลจิสติกส์ได้อย่างมีประสิทธิภาพ', bgClass: 'bg-slate-700', textClass: 'text-white', arrowClass: 'text-white/60 border-white/20 group-hover:text-slate-700', categorySlug: 'business' }
-]
+const categoryCards = CATEGORY_CARDS
+const whyChooseUs = WHY_CHOOSE_US
-const learningStyles = [
- {
- title: 'คอร์สออนไลน์', icon: 'desktop_windows', type: 'ONLINE',
- subtitle: 'เรียนได้ทุกที่ ทุกเวลา', desc: 'คัดสรรเนื้อหาคุณภาพจากผู้เชี่ยวชาญ\nพร้อมให้คุณเริ่มต้นเรียนรู้ได้ทันที',
- time: 'เข้าถึงได้ตลอดชีพ',
- features: ['เนื้อหาครบทุกประเด็นสำคัญ', 'โจทย์ตัวอย่างและแบบฝึกหัด', 'เรียนซ้ำได้ไม่จำกัด', 'ใบเซอร์ทิฟิเคตหลังเรียนจบ'],
- iconBg: 'bg-blue-50', iconColor: 'text-blue-600', titleClass: 'text-blue-700',
- btnClass: 'bg-indigo-900 text-white hover:bg-indigo-800'
- }
-]
-
-const promoCategories = [
- { title: 'Data', desc: 'เรียนรู้และฝึกฝนกระบวนการคิดสร้างมูลค่าให้ธุรกิจด้วยข้อมูล', icon: 'analytics' },
- { title: 'Design', desc: 'ออกแบบ Digital Product เพื่อให้ผู้ใช้งานได้รับประสบการณ์ที่ดีที่สุด', icon: 'palette' },
- { title: 'Tech', desc: 'พร้อมเป็นที่ต้องการของตลาดแรงงานด้วยทักษะการเขียนโปรแกรม', icon: 'code' },
- { title: 'Business', desc: 'พลิกโฉมธุรกิจในยุคดิจิทัลด้วยการเข้าถึงลูกค้าในช่องทางและเวลาที่เหมาะสม', icon: 'trending_up' }
-]
+//ระดับความยาก
+const levelModel = ref('ระดับทั้งหมด')
+const levelOptions = ['ระดับทั้งหมด','ระดับเริ่มต้น', 'ระดับกลาง', 'ระดับสูง']
const categories = ref
([])
const topCourses = ref([])
const selectedCategory = ref('all')
const isLoading = ref(false)
const currentSlide = ref(0)
+
const courseChunks = computed(() => {
const chunkSize = 4
const chunks = []
- if (!topCourses.value) return []
+ if (!topCourses.value || topCourses.value.length === 0) return []
for (let i = 0; i < topCourses.value.length; i += chunkSize) {
chunks.push(topCourses.value.slice(i, i + chunkSize))
}
@@ -65,7 +47,7 @@ const loadData = async () => {
try {
const [catRes, courseRes] = await Promise.all([
fetchCategories(),
- fetchCourses({ limit: 8, forceRefresh: true })
+ fetchCourses({ limit: 12, forceRefresh: true })
])
if (catRes.success) categories.value = catRes.data || []
@@ -84,7 +66,7 @@ const goBrowse = (slug: string) => {
watch(selectedCategory, async (newVal) => {
isLoading.value = true
try {
- const params: any = { limit: 8 }
+ const params: any = { limit: 12 }
if (newVal !== 'all') {
const category = categories.value.find(c => c.slug === newVal)
if (category) {
@@ -110,41 +92,41 @@ onMounted(() => {
-
-
-
-
-
-
-
-
-
- E-Learning Platform
-
-
- คอร์สเรียนออนไลน์
เพิ่มทักษะยุคดิจิทัล
+
+
+
+
+
+
+
+ ขยายขอบเขตความรู้ของคุณ
+ ด้วยการเรียนรู้ออนไลน์
-
- แหล่งรวมคอร์สออนไลน์คุณภาพสูงที่จะช่วยอัปสกิลให้คุณทำงานเก่งขึ้น พัฒนาทักษะที่ตลาดต้องการ พร้อมให้คุณก้าวไปข้างหน้าได้อย่างมั่นใจ!
+
+
+
+ จุดประกายความรู้ของคุณ และเริ่มต้นอัปสกิลกับผู้เชี่ยวชาญ
+ ในอุตสาหกรรมที่มีความรู้รอบด้านหลากหลายในหลายสาขา
+ เรียนได้ทุกที่ ทุกเวลา
-
-
-
-
-
-
-
-

-
+
+
+
+

+
-
-
-
-
-
-
-
-
-
-
-
-
-
- เพราะ “ก้าวแรก” ของการพัฒนาตัวเอง ท้าทายเสมอ
-
-
- เราจึงตั้งใจออกแบบบทเรียนให้ ‘เข้าใจง่าย’ และ ‘นำไปใช้ได้จริง’ เพื่อให้ทุกก้าวของคุณ มั่นคงและไปถึงเป้าหมายได้สำเร็จ
-
-
-
-
-
-
-
-
-
-
-
- ก้าวแรกของ
-
{{ card.title }}
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
- คอร์สออนไลน์
- ที่ออกแบบมาสำหรับคุณ
-
-
- เรียนรู้ทักษะใหม่จากผู้เชี่ยวชาญตัวจริง พร้อมเนื้อหาที่เข้มข้นและใช้งานได้จริง
-
-
-
-
-
-
-
-
-
-
-
-
Access Status
-
เข้าถึงได้ตลอดชีพ
-
-
-
-
+
+
+
+ ทำไมต้องเลือกแพลตฟอร์มของเรา?
+
+
+ เรามีเครื่องมือและความเชี่ยวชาญที่จะช่วยให้คุณประสบความสำเร็จในการเปลี่ยนสายอาชีพและการสร้างทักษะระดับมืออาชีพ
+
+
-
-
-
-
-
- ก้าวข้ามทุกขีดจำกัด
- ด้วยการเรียนรู้ที่ “อิสระ”
-
-
- เราคัดสรรและคราฟต์ทุกคอร์สเรียนเพื่อให้มั่นใจว่าคุณจะได้รับประสบการณ์การเรียนรู้ที่ดีที่สุด ไม่ว่าจะอยู่ที่ไหนหรือเวลาใดก็ตาม
+
+
+
+
+
+
+
+ {{ item.title }}
+
+
+ {{ item.desc }}
+
+
+
+
+
+
+
+
+
+
+
+
+ เลือกเรียนตามเรื่องที่คุณสนใจ
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ card.title }}
+
+
+ {{ card.desc }}
-
-
-
-
+
-
+
+
-
คอร์สออนไลน์
-
เริ่มต้นเรียนรู้ทักษะใหม่ด้วยคอร์สคุณภาพจากผู้เชี่ยวชาญ
+
คอร์สออนไลน์
-
- คอร์สออนไลน์ทั้งหมด
+
+ ดูคอร์สทั้งหมด
+
-
-
+
+
+
+
+
+
+
+
+
+ พร้อมเริ่มต้นการเรียนรู้แล้วหรือยัง?
+
+
+ อัปสกิลและรับทักษะที่คุณต้องการเพื่อก้าวหน้าในระดับมืออาชีพ
+ เปิดประสบการณ์การเรียนรู้รูปแบบใหม่ สมัครเลยวันนี้เพื่อเริ่มต้นเข้าสู่บทเรียน
+
+
+
+ สำรวจคอร์สเรียน
+
+
+
+
+
+
+
-
-
diff --git a/Frontend-Learner/pages/verify-email.vue b/Frontend-Learner/pages/verify-email.vue
index 31b513a1..169e8195 100644
--- a/Frontend-Learner/pages/verify-email.vue
+++ b/Frontend-Learner/pages/verify-email.vue
@@ -6,7 +6,7 @@
*/
definePageMeta({
- layout: 'default'
+ layout: 'auth'
})
const route = useRoute()
@@ -68,8 +68,8 @@ const navigateToHome = () => {
-
-
+
+
diff --git a/Frontend-Learner/types/auth.ts b/Frontend-Learner/types/auth.ts
new file mode 100644
index 00000000..22f07190
--- /dev/null
+++ b/Frontend-Learner/types/auth.ts
@@ -0,0 +1,41 @@
+/**
+ * @file auth.ts
+ * @description Type definitions for authentication and user profiles.
+ */
+
+export interface User {
+ id: number
+ username: string
+ email: string
+ email_verified_at?: string | null
+ created_at?: string
+ updated_at?: string
+ role: {
+ code: string // เช่น 'STUDENT', 'INSTRUCTOR', 'ADMIN'
+ name: { th: string; en: string }
+ }
+ profile?: {
+ prefix: { th: string; en: string }
+ first_name: string
+ last_name: string
+ phone: string | null
+ avatar_url: string | null
+ }
+}
+
+export interface LoginResponse {
+ token: string
+ refreshToken: string
+ user: User
+ profile: User['profile']
+}
+
+export interface RegisterPayload {
+ username: string
+ email: string
+ password: string
+ first_name: string
+ last_name: string
+ prefix: { th: string; en: string }
+ phone: string
+}
diff --git a/Frontend-Learner/types/course.ts b/Frontend-Learner/types/course.ts
new file mode 100644
index 00000000..44e4d0a5
--- /dev/null
+++ b/Frontend-Learner/types/course.ts
@@ -0,0 +1,142 @@
+/**
+ * @file course.ts
+ * @description Type definitions for courses, enrollments, quizzes, and certificates.
+ */
+
+export interface Course {
+ id: number
+ title: string | { th: string; en: string }
+ slug: string
+ description: string | { th: string; en: string }
+ thumbnail_url: string
+ price: string
+ is_free: boolean
+ original_price?: string
+ have_certificate: boolean
+ status: string
+ category_id: number
+ created_at?: string
+ updated_at?: string
+ created_by?: number
+ updated_by?: number
+ approved_at?: string
+ approved_by?: number
+ rejection_reason?: string
+ enrolled?: boolean
+ total_lessons?: number
+ rating?: string
+ lessons?: number | string
+ levelType?: 'neutral' | 'warning' | 'success'
+ chapters?: {
+ id: number
+ title: string | { th: string; en: string }
+ lessons: {
+ id: number
+ title: string | { th: string; en: string }
+ duration_minutes: number
+ video_url?: string
+ }[]
+ }[]
+ creator?: {
+ id: number
+ username: string
+ email: string
+ profile: {
+ first_name: string
+ last_name: string
+ avatar_url: string
+ }
+ }
+ instructors?: {
+ user_id: number
+ is_primary: boolean
+ user: {
+ id: number
+ username: string
+ email: string
+ profile: {
+ first_name: string
+ last_name: string
+ avatar_url: string
+ }
+ }
+ }[]
+}
+
+export interface CourseResponse {
+ code: number
+ message: string
+ data: Course[]
+ total: number
+ page?: number
+ limit?: number
+ totalPages?: number
+}
+
+export interface SingleCourseResponse {
+ code: number
+ message: string
+ data: Course
+}
+
+export interface EnrolledCourse {
+ id: number
+ course_id: number
+ course: Course
+ status: 'ENROLLED' | 'IN_PROGRESS' | 'COMPLETED' | 'DROPPED'
+ progress_percentage: number
+ enrolled_at: string
+ started_at?: string
+ completed_at?: string
+ last_accessed_at?: string
+}
+
+export interface EnrolledCourseResponse {
+ code: number
+ message: string
+ data: EnrolledCourse[]
+ total: number
+ page: number
+ limit: number
+}
+
+export interface QuizAnswerSubmission {
+ question_id: number
+ choice_id: number
+}
+
+export interface QuizSubmitRequest {
+ answers: QuizAnswerSubmission[]
+}
+
+export interface QuizResult {
+ answers_review: {
+ score: number
+ is_correct: boolean
+ correct_choice_id: number
+ selected_choice_id: number
+ question_id: number
+ }[]
+ completed_at: string
+ started_at: string
+ attempt_number: number
+ passing_score: number
+ is_passed: boolean
+ correct_answers: number
+ total_questions: number
+ total_score: number
+ score: number
+ quiz_id: number
+ attempt_id: number
+}
+
+export interface Certificate {
+ certificate_id: number
+ course_id: number
+ course_title: {
+ en: string
+ th: string
+ }
+ issued_at: string
+ download_url: string
+}
diff --git a/Frontend-Learner/types/index.ts b/Frontend-Learner/types/index.ts
new file mode 100644
index 00000000..0f2701d3
--- /dev/null
+++ b/Frontend-Learner/types/index.ts
@@ -0,0 +1,2 @@
+export * from './auth'
+export * from './course'
diff --git a/Frontend-Learner/คู่มืออธิบาย/ED.md b/Frontend-Learner/คู่มืออธิบาย/ED.md
deleted file mode 100644
index 5727dde2..00000000
--- a/Frontend-Learner/คู่มืออธิบาย/ED.md
+++ /dev/null
@@ -1,71 +0,0 @@
-วันที่บันทึกปฏิบัติงาน \*
-18/02/2026
-
-องค์ความรู้ที่ได้รับ \*
-
-- การใช้งาน Nuxt 3 Routing (useRoute, useRouter) ในการจัดการ Query Parameters
-- การเชื่อมโยง State กับ URL เพื่อสร้าง Deep Linking ให้แชร์ลิงก์หมวดหมู่ได้
-- การใช้งาน Vue 3 Composition API (Script Setup) แทน Options API เพื่อความเป็นระเบียบและลดความซับซ้อน
-- การตกแต่ง UI ด้วย Tailwind CSS ขั้นสูง (Gradients, Glow Effects, Backdrop Filters) เพื่อให้ได้ธีม Premium/Clean
-- การจัดการ Event Listener ใน Vue Component (onMounted, onUnmounted) เพื่อป้องกัน Memory Leak
-
-รายละเอียด \*
-
-- พัฒนาระบบ Filter หมวดหมู่คอร์สเรียน เชื่อมโยงหน้าแรก (Home) กับหน้าค้นหา (Browse)
-- ปรับแก้โครงสร้าง Code ในหน้า `index.vue` ให้ใช้ Script Setup ทั้งหมดเพื่อลดความซับซ้อนและแก้ปัญหาการเรียกใช้ตัวแปร
-- ปรับดีไซน์ส่วน Call to Action (CTA) ในหน้า `browse/index.vue` และ `browse/recommended.vue` ให้ดูสว่างและทันสมัยขึ้นโดยใช้แสงและเงา (Blue Glow)
-
-ปัญหาและอุปสรรค \*
-
-- พบปัญหาการทำงานร่วมกันระหว่าง Options API และ Script Setup ในไฟล์ `index.vue` ทำให้ฟังก์ชันบางตัวเรียกใช้ไม่ได้
-- ระบบ Filter เดิมไม่ทำงานเมื่อกดย้อนกลับ (Back Button) เนื่องจากไม่ได้ Watch การเปลี่ยนแปลงของ URL Query
-- การดึงข้อมูลคอร์สเริ่มต้นได้ไม่ครบเนื่องจาก API มีการกำหนด Limit ไว้
-
-รายละเอียด \*
-
-- แก้ไขโดยการยุบรวม Code ทั้งหมดให้เป็นรูปแบบ Script Setup มาตรฐานเดียว
-- เพิ่ม `watch(() => route.query.category)` ในหน้า Browse เพื่อให้อัปเดตข้อมูลทุกครั้งที่ URL เปลี่ยน
-- เพิ่มพารามิเตอร์ `limit: 1000` ในการเรียก API เพื่อดึงข้อมูลคอร์สทั้งหมดมาแสดง
-
-หลักฐานการปฏิบัติงาน (เฉพาะไฟล์ JPG, JPEG, PNG, PDF.)
-
-- แก้ไขไฟล์ `pages/index.vue` (เพิ่ม goBrowse, ย้าย Logic)
-- แก้ไขไฟล์ `pages/browse/index.vue` (เพิ่ม Query Watcher, ปรับ UI)
-- แก้ไขไฟล์ `pages/browse/recommended.vue` (ปรับ UI ส่วน CTA)
-- แก้ไขไฟล์ `components/layout/LandingHeader.vue` (แก้ Memory Leak)
-
----
-
-วันที่บันทึกปฏิบัติงาน \*
-19/02/2026
-
-องค์ความรู้ที่ได้รับ \*
-
-- การออกแบบ Dashboard Layout แบบ Modern Grid (SkillLane Style/Reference Style)
-- การใช้ `q-carousel` (Quasar) ทำ Image Slider แบนเนอร์ประชาสัมพันธ์
-- การจัดการแสดงผลข้อมูล Course Card แยกตามสถานะ (Enrolled/Recommended/Free)
-- การปรับแต่ง Menu Navigation (`useNavItems`) เพื่อลดความซ้ำซ้อนใน Sidebar
-
-รายละเอียด \*
-
-- ออกแบบและพัฒนาหน้า Dashboard ใหม่ (`pages/dashboard/index.vue`)
- 1. เพิ่ม Banner Slide ขนาดใหญ่ด้านบน
- 2. แสดงรายการ "คอร์สที่คุณกำลังเรียน" (In Progress Courses) พร้อม Progress Bar
- 3. แสดงรายการ "คอร์สแนะนำ" (Recommended Courses)
- 4. เพิ่มหมวด "คอร์สฟรี" (Free Courses)
-- ปรับแต่ง Sidebar Navigation (`composables/useNavItems.ts`) ลบเมนูที่ไม่จำเป็นออก (Online Courses, Recommended, Announcements) ให้เหลือเฉพาะเมนูหลัก
-
-ปัญหาและอุปสรรค \*
-
-- การแสดงผล Carousel มีปุ่ม Arrows เกะกะสายตาบนแบนเนอร์
-- ต้องการแยกข้อมูลคอร์สฟรีออกจากคอร์สทั่วไป แต่ API ยังไม่มี Filter โดยตรง
-
-รายละเอียด \*
-
-- แก้ไข Property ของ `q-carousel` โดยลบ `arrows` ออก เพื่อให้เหลือเฉพาะ Navigation Dots ด้านล่าง
-- เขียน Logic กรองข้อมูลฝั่ง Client (Client-side filtering) สำหรับคอร์สฟรี โดยตรวจสอบจาก `price === 0` หรือ `is_free: true`
-
-หลักฐานการปฏิบัติงาน (เฉพาะไฟล์ JPG, JPEG, PNG, PDF.)
-
-- แก้ไขไฟล์ `pages/dashboard/index.vue` (New Dashboard UI)
-- แก้ไขไฟล์ `composables/useNavItems.ts` (Clean Sidebar)
diff --git a/Frontend-Learner/คู่มืออธิบาย/web-dev-details.md b/Frontend-Learner/คู่มืออธิบาย/web-dev-details.md
index fba0cc4e..cc52444c 100644
--- a/Frontend-Learner/คู่มืออธิบาย/web-dev-details.md
+++ b/Frontend-Learner/คู่มืออธิบาย/web-dev-details.md
@@ -1,187 +1,138 @@
-# 🛠️ Web Development Documentation: e-Learning Platform (Frontend)
+# Frontend-Learner (Web) — Technical Documentation
-เอกสารฉบับนี้สรุปรายละเอียดทางเทคนิค โครงสร้างโค้ด และการทำงานของระบบ **Frontend-Learner** (อัปเดตล่าสุด: กุมภาพันธ์ 2026)
+เอกสารฉบับนี้สรุปรายละเอียดทางเทคนิค โครงสร้างโค้ด และกลไกการทำงานของระบบ **Frontend-Learner (ฝั่งผู้เรียน)**
+ใช้เป็นคู่มือสำหรับการพัฒนา บำรุงรักษา และขยายระบบต่อไป
+
+> อัปเดตล่าสุด: ปลายเดือนกุมภาพันธ์ 2026
---
-## z️ 1. Technical Foundation (รากฐานทางเทคนิค)
+## Table of Contents
-รวมข้อมูลเครื่องมือ, ระบบความปลอดภัย และประสิทธิภาพการทำงานไว้ด้วยกัน
+- [1. Technical Foundation](#1-technical-foundation)
+ - [1.1 Tech Stack](#11-tech-stack)
+ - [1.2 Security & Authentication](#12-security--authentication)
+- [2. Project Architecture](#2-project-architecture)
+ - [2.1 Directory Structure](#21-directory-structure)
+ - [2.2 Shared Infrastructure](#22-shared-infrastructure)
+- [3. Logic & Data Layer (Composables)](#3-logic--data-layer-composables)
+- [4. Branding & UI Policy](#4-branding--ui-policy)
+ - [4.1 Theme Strategy](#41-theme-strategy)
+ - [4.2 UI Elements](#42-ui-elements)
+- [5. Core Feature Highlights](#5-core-feature-highlights)
+- [6. Maintenance & Performance Guidelines](#6-maintenance--performance-guidelines)
+
+---
+
+## 1. Technical Foundation
+
+รากฐานทางเทคนิคที่ขับเคลื่อนระบบ เพื่อให้ได้ประสิทธิภาพและความเสถียรสูงสุด
### 1.1 Tech Stack
-- **Core:** [Nuxt 3](https://nuxt.com) (v`^3.11.2`), TypeScript `^5.4.5`
-- **UI Framework:** Quasar Framework `^2.15.2` (via `nuxt-quasar-ui ^3.0.0`)
-- **Styling:** Tailwind CSS `^6.12.0` (Utility) + Vanilla CSS Variables (Theming/Dark Mode)
-- **State Management:** `ref`/`reactive` (Local) + `useState` (Global/Shared State)
-- **Localization:** `@nuxtjs/i18n` (Supports JSON locales in `i18n/locales/`)
-- **Media Control:** `useMediaPrefs` (Command Pattern for global volume/mute state)
+- **Framework:** Nuxt 3 (Vue 3, Vite, SSR/SPA Hybrid)
+- **UI System:** Quasar Framework + Tailwind CSS (Utility-first)
+- **Typography:** Google Fonts (**Prompt** เป็น Font หลักเพื่อความทันสมัยและอ่านง่าย)
+- **Multilingual:** `@nuxtjs/i18n` (รองรับ JSON-based locales ภาษาไทยและอังกฤษ)
+- **Programming:** TypeScript (Strict Type Checking)
-### 1.2 Core Systems & Security
+### 1.2 Security & Authentication
-- **Authentication:**
- - ใช้ **JWT** (Access Token 1 วัน, Refresh Token 7 วัน)
- - เก็บ Token ใน `useCookie` (Secure, SameSite)
- - Middleware (`middleware/auth.ts`) ป้องกัน Route ตามสถานะ
- - **Remember Me:** ระบบจดจำอีเมลลงใน `localStorage` (จำแยกจาก session, ไม่ถูกลบเมื่อ Logout)
-- **API Handling:**
- - ใช้ `runtimeConfig.public.apiBase` เชื่องโยง Backend
- - Auto-attach Bearer Token ใน `useAuth` และ `useCourse`
-- **Performance:**
- - **Hybrid Progress Saving:** บันทึกเวลาเรียนลง LocalStorage (ถี่) และ Server (Throttle 15s) เพื่อความแม่นยำสูงสุด
- - **Caching:** ใช้ `useState` จำข้อมูล Profile และ Categories ลด request
- - **Code Quality:** ลบ Log, Dead logic และ Redundant comments ทั่วทั้งโปรเจกต์ (Clean Code Phase)
+- **Token Management:** ใช้ JWT (Access & Refresh Tokens) จัดเก็บผ่าน `useCookie`
+ โดยตั้งค่าความปลอดภัยระดับ **HTTP-only** และ **SameSite**
+- **Middleware:** `auth.ts` ตรวจสอบสิทธิ์การเข้าถึงหน้า Dashboard และ Classroom แบบ Real-time
+- **Persistence:** ระบบ Remember Me (จดจำอีเมล) ใช้ `localStorage` แยกส่วนจาก Session
+ เพื่อความปลอดภัยและสะดวกสำหรับผู้ใช้
---
-## 📂 2. Frontend Structure (โครงสร้างหน้าเว็บและ UI)
+## 2. Project Architecture
-### 2.1 Application Routes (`pages/`)
+โครงสร้างโฟลเดอร์ที่จัดระเบียบตามหลัก Clean Architecture เพื่อความคล่องตัวในการขยายระบบ
-| Module | ไฟล์ | Path | หน้าที่ |
-| :---------- | :------------------------- | :---------------------- | :-------------------------------------------- |
-| **Public** | `index.vue` | `/` | หน้าแรก Landing Page (**Forced Light Mode**) |
-| | `browse/discovery.vue` | `/browse/discovery` | **ระบบค้นหาและ Filter คอร์ส** (Catalog) |
-| | `course/[id].vue` | `/course/:id` | **หน้ารายละเอียดคอร์ส** (Course Detail) |
-| **Auth** | `auth/login.vue` | `/auth/login` | เข้าสู่ระบบ (**Remember Me**, **Light Mode**) |
-| | `auth/register.vue` | `/auth/register` | สมัครสมาชิกผู้เรียน (**Light Mode**) |
-| | `auth/forgot-password.vue` | `/auth/forgot-password` | กู้คืนรหัสผ่าน (**Light Mode**) |
-| **Student** | `dashboard/index.vue` | `/dashboard` | แดชบอร์ดภาพรวมผู้เรียน |
-| | `dashboard/my-courses.vue` | `/dashboard/my-courses` | **คอร์สของฉัน** และดาวน์โหลดใบประกาศฯ |
-| | `dashboard/profile.vue` | `/dashboard/profile` | จัดการโปรไฟล์, รูปภาพ, เปลี่ยนรหัสผ่าน |
-| | `classroom/learning.vue` | `/classroom/learning` | **ห้องเรียน (Video Player)** & Announcements |
-| | `classroom/quiz.vue` | `/classroom/quiz` | การสอบวัดผล (**API-Driven Logic**) |
+### 2.1 Directory Structure
-### 2.2 Key Components (`components/`)
+- `pages/` : ระบบ Routing ทั้งหมด (Landing, Auth, Dashboard, Classroom)
+- `components/` : UI Components แยกตามความรับผิดชอบ (Common, Layout, Course, Classroom, Profile)
+- `composables/` : Business Logic ทั้งหมด (Auth, Course, Theme, Quiz, Navigation)
+- `types/` : ศูนย์รวม Interface และ Type definitions ของทั้งระบบ
+- `constants/` : แหล่งเก็บข้อมูล Static (เช่น Category cards, Why choose us) เพื่อลดความซ้อนในไฟล์ Vue
+- `assets/css/` : `main.css` ที่เป็น Single Source of Truth สำหรับสไตล์และ CSS Variables
+- `layouts/` : Master templates (Default, Auth, Dashboard)
+- `middleware/` : ตัวกรองความปลอดภัยก่อนเข้าถึงแต่ละหน้า
-- **Common (`components/common/`):**
- - `GlobalLoader.vue`: Loading indicator ทั่วทั้งแอป
- - `LanguageSwitcher.vue`: ปุ่มเปลี่ยนภาษา (TH/EN)
- - `AppHeader.vue`, `MobileNav.vue`: Navigation หลัก (ใช้ร่วมกับ AppSidebar)
- - `FormInput.vue`: Input field มาตรฐาน
-- **Layout (`components/layout/`):**
- - `AppSidebar.vue`: Sidebar หลักสำหรับ Dashboard (Collapsible)
- - `LandingHeader.vue`: Header เฉพาะสำหรับหน้า Landing Page
-- **Course (`components/course/`):**
- - `CourseCard.vue`: การ์ดแสดงผลคอร์ส รองรับ Progress และ **Glassmorphism** ในโหมดมืด
-- **Discovery (`components/discovery/`):**
- - `CategorySidebar.vue`: Sidebar ตัวกรองหมวดหมู่แบบย่อ/ขยายได้
- - `CourseDetailView.vue`: หน้ารายละเอียดคอร์สขนาดใหญ่ (Video Preview + Syllabus)
-- **Classroom (`components/classroom/`):**
- - `CurriculumSidebar.vue`: Sidebar บทเรียนและสถานะการเรียน
- - `AnnouncementModal.vue`: Modal แสดงประกาศของคอร์ส
- - `VideoPlayer.vue`: Video Player พร้อม Custom Controls และ YouTube Support
-- **User / Profile (`components/user/`, `components/profile/`):**
- - `UserAvatar.vue`: แสดงรูปโปรไฟล์ (รองรับ Fallback)
- - `ProfileEditForm.vue`: ฟอร์มแก้ไขข้อมูลส่วนตัว
- - `PasswordChangeForm.vue`: ฟอร์มเปลี่ยนรหัสผ่าน
+### 2.2 Shared Infrastructure
+
+- **Types Architecture:** การสกัด Types จาก Composable ออกมาไว้ที่ `@/types`
+ ช่วยลดความซ้ำซ้อนและป้องกัน Error จากการเปลี่ยนโครงสร้างข้อมูล API
+- **Constants System:** การใช้ `@/constants` ช่วยให้การแก้ไขคำโฆษณาหรือข้อมูลหน้าแรกทำได้จากจุดเดียว
+ โดยไม่ต้องแก้โค้ด HTML
---
-## 🧠 3. Logic & Data Layer (Composables)
+## 3. Logic & Data Layer (Composables)
-รวบรวม Logic หลักแยกส่วนตามหน้าที่ (Separation of Concerns)
+การแยก Logic ออกจาก UI เพื่อความสะอาดและ Testable
-### 3.1 `useAuth.ts` (Authentication & User)
+- `useAuth`
+ จัดการสถานะ Login, การดึงโปรไฟล์ล่วงหน้า (Pre-fetching), และระบบ Token Refresh
-จัดการสถานะผู้ใช้, ล็อกอิน, และความปลอดภัย
+- `useCourse`
+ หัวใจของระบบ จัดการตั้งแต่ Catalog, การสมัครเรียน (Enroll), ไปจนถึงการส่งผลการเรียน (Progress)
-- **Key Functions:** `login`, `register`, `fetchUserProfile`, `uploadAvatar`, `sendVerifyEmail`
-- **Features:** Refresh Token อัตโนมัติ, ตรวจสอบ Role, **Logout Logic ที่ไม่ลบข้อมูลจดจำผู้ใช้**
+- `useThemeMode`
+ ระบบจัดการธีมกลางที่เชื่อมต่อกับ `localStorage` และ CSS Variables อย่างเป็นระบบ
-### 3.2 `useCourse.ts` (Course & Classroom)
+- `useQuizRunner`
+ จัดการสถานะการสอบ เปลี่ยนข้อสอบ และส่งคะแนนไปยัง Backend โดยตรง
-หัวใจหลักของการเรียนการสอน
-
-- **Catalog:** `fetchCourses`, `fetchCourseById`, `enrollCourse`
-- **Classroom:**
- - `fetchCourseLearningInfo`: โครงสร้างบทเรียน (Chapters/Lessons)
- - `fetchLessonContent`: เนื้อหาวิดีโอ/Quiz/Attachments
- - `fetchCourseAnnouncements`: ดึงข้อมูลประกาศของคอร์ส
- - `saveVideoProgress`: บันทึกเวลาเรียน (Sync Server)
-- **i18n Support:** `getLocalizedText` ตัวช่วยในการเลือกแสดงผลภาษา (TH/EN) ตาม Locale ปัจจุบันที่ผู้ใช้เลือก อัตโนมัติทั่วทั้งแอป
-
-### 3.3 `useQuizRunner.ts` (Quiz System)
-
-จัดการ Logic การทำข้อสอบ (Production-Ready)
-
-- **Logic:** ควบคุมการเปลี่ยนข้อ, การส่งคำตอบ, และการรับผลลัพธ์จาก API
-- **Cleanup:** ลบ Mock delays และ Simulation logic ออกทั้งหมดเพื่อให้ทำงานร่วมกับ API จริงได้ทันที
+- `useNavItems`
+ Single Source of Truth สำหรับเมนูทั้งหมด (Sidebar, Mobile Drawer, User Menu)
---
-## 🎨 4. Design System & Theming
+## 4. Branding & UI Policy
+
+มาตรฐานการออกแบบที่เน้นความ Premium และ Consistent
### 4.1 Theme Strategy
-- **Framework:** Tailwind CSS + Quasar UI
-- **Light/Dark Mode Policy:**
- - **Public Pages:** บังคับ **Light Mode** (Landing, Course Detail, Auth) เพื่อภาพลักษณ์แบรนด์ที่สะอาดตา
- - **Dashboard/Learning:** รองรับ **Dark Mode** เต็มรูปแบบ (Oceanic Theme)
- - **Aesthetics:** ปรับปรุงความชัดเจนของ Badge, Icon และสถานะต่างๆ ในหน้าสอบ (Quiz) สำหรับโหมดมืดโดยเฉพาะ ให้มี Contrast สูงและดู Premium
-- **Visual Fixes:** แก้ไขปัญหา "Dark Frame" ในหน้า Auth โดยการบังคับสไตล์ระดับ HTML/Body
+- **Public Pages (Landing, Auth, Detail):** บังคับ **Forced Light Mode**
+ เพื่อภาพลักษณ์แบรนด์ที่สะอาดและน่าเชื่อถือ
+- **Internal Pages (Dashboard, Learning):** รองรับ **Dark Mode (Oceanic Theme)**
+ ลดการเมื่อยล้าของสายตาขณะเรียนเป็นเวลานาน
+- **Transitions:** ใช้ GlobalLoader และ Smooth transitions ทั่วทั้งแอปเพื่อประสบการณ์ที่ลื่นไหล
+
+### 4.2 UI Elements
+
+- **Image 2 Style Categories:** การ์ดหมวดหมู่แบบแนวนอนที่เป็นระเบียบ (Minimalist)
+- **Glassmorphism:** พื้นผิวโปร่งแสงใน Dashboard และ Classroom ช่วยให้แอปดูมีมิติ
+- **Standardized Icons:** ใช้ Material Icons ผ่าน Quasar ระบบเดียวทั้งหมด
---
-## 📊 5. Dependency Map (ความสัมพันธ์ไฟล์)
+## 5. Core Feature Highlights
-| หน้าเว็บ (Page) | Components หลัก | Composables หลัก |
-| :----------------------- | :--------------------------- | :----------------------------------------------------- |
-| **Login / Register** | `FormInput` | `useAuth` (Remember Me), `useFormValidation` |
-| **Discovery (Browse)** | `CourseCard` | `useCourse` (Search/Filter), `useCategory` |
-| **My Courses** | `CourseCard` (with Progress) | `useCourse` (Certificates) |
-| **Classroom (Learning)** | Video Player, Sidebar | `useCourse` (Progress, Announcements), `useMediaPrefs` |
-| **Quiz** | `QuizHeader`, `QuizContent` | `useQuizRunner` (Real API Integration) |
-| **Profile** | `UserAvatar`, `FormInput` | `useAuth` (Upload Avatar, Verify Email) |
+ฟีเจอร์เด่นที่ถูกพัฒนาขึ้นเพื่อผู้เรียนโดยเฉพาะ
+
+- **SPA Learning Journey:** การสลับบทเรียนในห้องเรียนเป็นแบบ Single Page App (ไม่มีการ Re-load หน้า)
+ ทำให้การเรียนต่อเนื่อง
+- **Hybrid Progress Tracking:** บันทึกเวลาเรียนลง `localStorage` แบบ Real-time และ Sync ขึ้น Server เป็นระยะ
+ เพื่อป้องกันข้อมูลหาย
+- **Announcement System:** ระบบแจ้งเตือนในคอร์สพร้อมตัวระบุ "ยังไม่ได้อ่าน" (Unread Badge)
+ ที่จำสถานะตามผู้ใช้งาน
+- **Interactive Quizzes:** ระบบสอบที่สลับคำถามอัตโนมัติ พร้อมโหมดเฉลย (Answer Review) ที่ชัดเจน
+- **Certificate Automation:** ระบบตรวจสอบสิทธิ์ความสำเร็จและออกใบประกาศนียบัตรได้ทันที
---
-## ✅ 6. Project Status (สถานะล่าสุด)
+## 6. Maintenance & Performance Guidelines
-### ✨ Recent Updates (กุมภาพันธ์ 2026)
+แนวทางสำหรับการพัฒนาต่อยอด
-1. **System-Wide Code Cleanup (Phase Final):**
- - **Refactoring:** ปัดกวาดโค้ดในหน้า `learning`, `quiz`, `discovery`, `dashboard` และ `profile`
- - **Logging:** ลบ `console.log` มหาศาล และ logic ซ้ำซ้อนที่ตกค้างจากการพัฒนา
- - **Structure:** จัดกลุ่มสไตล์และฟังก์ชันให้เป็นระเบียบ อ่านง่ายขึ้นตามมาตรฐาน Clean Code
+- **Clean Code:** หลีกเลี่ยงการใช้ `console.log` ในโค้ด Final และลบ Dead Logic ทิ้งทันที
+- **Standard Fonts:** ใช้ชุด Font Prompt ผ่านตัวแปร `--font-main` เสมอ
+- **API Integrity:** ตรวจสอบข้อมูลผ่าน Interface ใน `@/types` ก่อนการใช้งานทุกครั้ง
+- **Mobile First:** ทุก Component ต้องรองรับระบบ Master Drawer บนมือถืออย่างสมบูรณ์
-2. **Authentication & Security Polish:**
- - **Remember Me:** พัฒนาระบบจดจำอีเมลในหน้า Login ให้เสถียร (ใช้ `localStorage`)
- - **Smart Logout:** ปรับปรุง `useAuth.logout` ให้ลบข้อมูล Session แต่เก็บข้อมูลที่ผู้ใช้สั่งจำไว้ (อีเมล)
-
-3. **UI & Aesthetics (Premium Fixes):**
- - **Theme Enforcement:** บังคับหน้าสาธารณะ (Landing/Auth) ให้เป็น Light Mode 100% พร้อมแก้ปัญหากรอบมืด (Dark Frame) ตกค้าง
- - **Dark Mode Optimization:** ปรับปรุงสีและ Contrast ในหน้า Dashboard และ Profile ให้สวยงามและอ่านง่ายขึ้นในโหมมืด
-
-4. **Quiz System Productionization:**
- - **useQuizRunner:** แปลงร่างจาก Mock system เป็น API-Ready system (ลบ simulation logic ทั้งหมด)
- - **Quiz UI:** ปรับปรุงการนำทางและสถานะการทำข้อสอบให้ลื่นไหล
-
-5. **Smooth Navigation & Quiz Experience:**
- - **SPA Navigation:** เปลี่ยนการสไลด์บทเรียนจาก Hard Reload เป็น SPA Navigation (`router.push`) ทำให้เรียนได้ต่อเนื่อง ไม่ต้องรอโหลดหน้าใหม่
- - **Smart Lesson Loading:** ปรับปรุง Error ที่หน้าเว็บชอบเด้งกลับไปบทเรียนที่ 1 เสมอ โดยเปลี่ยนให้ความสำคัญกับ `lesson_id` จาก URL ก่อน
- - **UI Simplification:** ลบทิ้ง "Legend/คำอธิบายสถานะ" ในหน้าสอบเพื่อความสะอาดตา (Minimal UI)
- - **Sidebar visibility:** ช่วยให้ผู้ใช้เปิด-ปิด Sidebar บน Desktop ได้อย่างอิสระผ่านปุ่ม Hamburger
-
-6. **Internationalization (i18n) Improvements:**
- - **Localized Text Logic:** แก้ไขฟังก์ชัน `getLocalizedText` ให้แสดงภาษาตามที่ผู้ใช้สลับจริง (แก้ปัญหาหน้าเว็บเป็นอังกฤษแต่ชื่อวิชาเป็นไทย)
- - **Hardcoded Removal:** ทยอยลบข้อความภาษาไทยที่พิมพ์ค้างไว้ในโค้ด (เช่น ใน Sidebar หมวดหมู่) และแทนที่ด้วย i18n keys
- - **Boot Sequence Fix:** แก้ไขปัญหาเว็บค้าง (Error 500) ที่เกิดจากการเรียกใช้ภาษาเร็วเกินไปก่อนที่ระบบจะพร้อม (`initialization error`)
-
-7. **Classroom & UX Optimization (Mid-February 2026):**
- - **SPA Navigation for Learning:** เปลี่ยนระบบเลือกบทเรียนจากการ Reload หน้าเป็น SPA Navigation ทำให้เปลี่ยนวิดีโอ/บทเรียนได้ทันทีโดยไม่ต้อง Refresh หน้าจอ
- - **Announcement Persistence:** เพิ่มระบบเช็กสถานะการอ่านประกาศ (Unread Badge) โดยบันทึกสถานะล่าสุดลง LocalStorage แยกตามผู้ใช้และคอร์ส
- - **YouTube Resume:** รองรับการเรียนต่อจากจุดเดิมสำหรับวิดีโอ YouTube (Time Seeking via URL parameter)
-
-8. **Quiz System Enhancements:**
- - **Answer Review Mode:** เพิ่มโหมดเฉลยข้อสอบหลังทำเสร็จ พร้อมการไฮไลท์สีที่ชัดเจน (เขียว = ถูก, แดง = ตอบผิด)
- - **Shuffle Logic:** เพิ่มการสลับคำถามและตัวเลือก (Shuffle) เพื่อความโปร่งใสในการสอบ
- - **Enhanced Feedback:** ปรับปรุง UI ผลลัพธ์การสอบให้มีความ Premium และเข้าใจง่ายขึ้น
-
-9. **Security & Registration Polish:**
- - **Phone Validation:** เพิ่มระบบตรวจสอบเบอร์โทรศัพท์ในหน้าสมัครสมาชิก (ต้องเป็นตัวเลขและยาวไม่เกิน 10 หลัก)
- - **Enrollment Alert Logic:** ปรับปรุง Logic การสมัครเรียนให้ตรวจสอบสถานะ Enrollment เดิมก่อน เพื่อป้องกัน API Error และการเรียก request ซ้ำซ้อน
-
-10. **Profile & Certificates:**
- - **Verification Badge:** เพิ่มการแสดงผลสถานะการยืนยันอีเมลในหน้าโปรไฟล์ พร้อมปุ่มส่งอีเมลยืนยันหากยังไม่ได้ทำ
- - **Certificate Flow:** ปรับปรุงระบบดาวน์โหลดใบประกาศนียบัตรให้รองรับทั้งการดึงไฟล์เดิมและสั่ง Generate ใหม่หากยังไม่มี
+---
diff --git a/frontend_management/pages/admin/courses/pending.vue b/frontend_management/pages/admin/courses/pending.vue
index 800855c8..e5871eb9 100644
--- a/frontend_management/pages/admin/courses/pending.vue
+++ b/frontend_management/pages/admin/courses/pending.vue
@@ -31,33 +31,48 @@
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
+ dense
+ rounded
+ unelevated
+ class="border"
+ >
+
+
+ มุมมองการ์ด
+
+
+
+ มุมมองตาราง
+
+
+
diff --git a/frontend_management/pages/admin/users/index.vue b/frontend_management/pages/admin/users/index.vue
index b5570d7e..2b220138 100644
--- a/frontend_management/pages/admin/users/index.vue
+++ b/frontend_management/pages/admin/users/index.vue
@@ -216,7 +216,7 @@
diff --git a/frontend_management/pages/instructor/courses/index.vue b/frontend_management/pages/instructor/courses/index.vue
index cdda8b84..4764b770 100644
--- a/frontend_management/pages/instructor/courses/index.vue
+++ b/frontend_management/pages/instructor/courses/index.vue
@@ -39,8 +39,8 @@
-
-
+
+
+
+
+
+
+ มุมมองการ์ด
+
+
+
+ มุมมองตาราง
+
+
-
+
+
ยังไม่มีหลักสูตร
@@ -82,7 +106,8 @@
/>
-
+
+
ดูรายละเอียด
-
@@ -167,6 +183,94 @@
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
{{ props.row.title.th }}
+
{{ props.row.title.en }}
+
+
+
+
+
+
+
+
+
+ {{ getStatusLabel(props.row.status) }}
+
+
+
+
+
+
+
+
+ {{ props.row.is_free ? 'ฟรี' : `฿${parseFloat(props.row.price).toLocaleString()}` }}
+
+
+
+
+
+
+
+ {{ formatDate(props.row.created_at) }}
+
+
+
+
+
+
+
+ ดูรายละเอียด
+
+
+
+
+
+
+
+
+ ทำสำเนา
+
+
+
+
+
+
+ ลบ
+
+
+
+
+
+
+
+
+
@@ -256,6 +360,17 @@ const courses = ref([]);
const loading = ref(true);
const searchQuery = ref('');
const filterStatus = ref(null);
+const viewMode = ref<'card' | 'table'>('card');
+
+// Table config
+const tablePagination = ref({ page: 1, rowsPerPage: 10 });
+const tableColumns = [
+ { name: 'title', label: 'หลักสูตร', field: 'title', align: 'left' as const, sortable: true },
+ { name: 'status', label: 'สถานะ', field: 'status', align: 'center' as const, sortable: true },
+ { name: 'price', label: 'ราคา', field: 'price', align: 'center' as const, sortable: true },
+ { name: 'created_at', label: 'วันที่สร้าง', field: 'created_at', align: 'center' as const, sortable: true },
+ { name: 'actions', label: 'จัดการ', field: 'actions', align: 'center' as const }
+];
// Status options
const statusOptions = [
diff --git a/frontend_management/services/admin.service.ts b/frontend_management/services/admin.service.ts
index cdea0ea4..8a90c27f 100644
--- a/frontend_management/services/admin.service.ts
+++ b/frontend_management/services/admin.service.ts
@@ -320,7 +320,25 @@ const getAuthToken = (): string => {
return tokenCookie.value || '';
};
+// Role interface
+export interface RoleResponse {
+ id: number;
+ code: string;
+}
+
export const adminService = {
+ async getRoles(): Promise {
+ const config = useRuntimeConfig();
+ const token = getAuthToken();
+ const response = await $fetch<{ roles: RoleResponse[] }>('/api/user/roles', {
+ baseURL: config.public.apiBaseUrl as string,
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ });
+ return response.roles;
+ },
+
async getUsers(): Promise {
const config = useRuntimeConfig();
const token = getAuthToken();