feat: Implement course discovery page with English and Thai internationalization.
This commit is contained in:
parent
6d7e78fb33
commit
78a26fc2e1
3 changed files with 64 additions and 118 deletions
|
|
@ -23,6 +23,14 @@
|
||||||
"introduction": "Introduction",
|
"introduction": "Introduction",
|
||||||
"lessons": "Lessons",
|
"lessons": "Lessons",
|
||||||
"lessonsUnit": "Lessons",
|
"lessonsUnit": "Lessons",
|
||||||
|
"chapter": "Chapter",
|
||||||
|
"price": "Price",
|
||||||
|
"coursePrice": "Course Price",
|
||||||
|
"instructor": "Instructor",
|
||||||
|
"level": "Level",
|
||||||
|
"allLevels": "All Levels",
|
||||||
|
"haveCertificate": "Certificate Available",
|
||||||
|
"noCertificate": "No Certificate",
|
||||||
"enrollNow": "Enroll Now",
|
"enrollNow": "Enroll Now",
|
||||||
"free": "Free",
|
"free": "Free",
|
||||||
"certificate": "Certificate",
|
"certificate": "Certificate",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,14 @@
|
||||||
"introduction": "บทนำ",
|
"introduction": "บทนำ",
|
||||||
"lessons": "บทเรียน",
|
"lessons": "บทเรียน",
|
||||||
"lessonsUnit": "บทเรียน",
|
"lessonsUnit": "บทเรียน",
|
||||||
|
"chapter": "บทที่",
|
||||||
|
"price": "ราคา",
|
||||||
|
"coursePrice": "ราคาคอร์ส",
|
||||||
|
"instructor": "ผู้สอน",
|
||||||
|
"level": "ระดับ",
|
||||||
|
"allLevels": "ทุกระดับ",
|
||||||
|
"haveCertificate": "มีใบประกาศฯ",
|
||||||
|
"noCertificate": "ไม่มีใบประกาศฯ",
|
||||||
"enrollNow": "ลงทะเบียนเรียนทันที",
|
"enrollNow": "ลงทะเบียนเรียนทันที",
|
||||||
"free": "ฟรี",
|
"free": "ฟรี",
|
||||||
"certificate": "ใบประกาศ",
|
"certificate": "ใบประกาศ",
|
||||||
|
|
|
||||||
|
|
@ -16,87 +16,64 @@ useHead({
|
||||||
});
|
});
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 1. ตัวแปร State (สถานะของ UI)
|
// 1. State Management
|
||||||
// ==========================================
|
|
||||||
// showDetail: ควบคุมการแสดงผลหน้ารายละเอียดคอร์ส (true = แสดง, false = แสดงรายการ)
|
|
||||||
const showDetail = ref(false);
|
const showDetail = ref(false);
|
||||||
// searchQuery: เก็บคำค้นหาที่ผู้ใช้พิมพ์
|
|
||||||
const searchQuery = ref("");
|
const searchQuery = ref("");
|
||||||
const sortOption = ref('เรียงตาม: ล่าสุด')
|
const selectedCategoryIds = ref<number[]>([]);
|
||||||
const sortOptions = ['เรียงตาม: ล่าสุด']
|
|
||||||
// isCategoryOpen: ควบคุมการเปิด/ปิดเมนูหมวดหมู่ด้านข้าง
|
|
||||||
const isCategoryOpen = ref(true);
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 2. ฟังก์ชันช่วยเหลือ (Helpers)
|
|
||||||
// ==========================================
|
|
||||||
// getLocalizedText: เลือกแสดงภาษาไทยหรืออังกฤษตามข้อมูลที่มี
|
|
||||||
// ถ้าเป็น object {th, en} จะพยายามหา th ก่อน, ถ้าไม่มีให้ใช้ en
|
|
||||||
const getLocalizedText = (text: string | { th: string; en: string } | undefined) => {
|
|
||||||
if (!text) return ''
|
|
||||||
if (typeof text === 'string') return text
|
|
||||||
return text.th || text.en || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 3. จัดการข้อมูลหมวดหมู่ (Categories)
|
|
||||||
// ==========================================
|
|
||||||
// ใช้ useCategory composable เพื่อดึงข้อมูลหมวดหมู่จาก API
|
|
||||||
const { fetchCategories } = useCategory();
|
|
||||||
const categories = ref<any[]>([]);
|
const categories = ref<any[]>([]);
|
||||||
const showAllCategories = ref(false); // ควบคุมการแสดงหมวดหมู่ทั้งหมด (Show More/Less)
|
const courses = ref<any[]>([]);
|
||||||
|
const selectedCourse = ref<any>(null);
|
||||||
|
|
||||||
// ฟังก์ชันโหลดข้อมูลหมวดหมู่
|
const isLoading = ref(false);
|
||||||
|
const isLoadingDetail = ref(false);
|
||||||
|
const isEnrolling = ref(false);
|
||||||
|
const showAllCategories = ref(false);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { fetchCategories } = useCategory();
|
||||||
|
const { fetchCourses, fetchCourseById, enrollCourse } = useCourse();
|
||||||
|
const { currentUser } = useAuth();
|
||||||
|
|
||||||
|
// 2. Computed Properties
|
||||||
|
const sortOption = ref(computed(() => t('discovery.sortRecent')));
|
||||||
|
const sortOptions = computed(() => [t('discovery.sortRecent')]);
|
||||||
|
|
||||||
|
const filteredCourses = computed(() => {
|
||||||
|
let result = courses.value;
|
||||||
|
if (selectedCategoryIds.value.length > 0) {
|
||||||
|
result = result.filter(c => selectedCategoryIds.value.includes(c.category_id));
|
||||||
|
}
|
||||||
|
if (searchQuery.value) {
|
||||||
|
const query = searchQuery.value.toLowerCase();
|
||||||
|
result = result.filter(c => {
|
||||||
|
const title = getLocalizedText(c.title).toLowerCase();
|
||||||
|
const desc = getLocalizedText(c.description).toLowerCase();
|
||||||
|
return title.includes(query) || (desc && desc.includes(query));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Helper Functions
|
||||||
|
const getLocalizedText = (text: string | { th: string; en: string } | undefined) => {
|
||||||
|
if (!text) return '';
|
||||||
|
if (typeof text === 'string') return text;
|
||||||
|
return text.th || text.en || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 4. API Actions
|
||||||
const loadCategories = async () => {
|
const loadCategories = async () => {
|
||||||
const res = await fetchCategories();
|
const res = await fetchCategories();
|
||||||
if (res.success) {
|
if (res.success) categories.value = res.data || [];
|
||||||
categories.value = res.data || [];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// คำนวณหมวดหมู่ที่จะแสดงผล (ถ้ากด Show More จะแสดงทั้งหมด, ปกติแสดงแค่ 8 รายการ)
|
|
||||||
const visibleCategories = computed(() => {
|
|
||||||
return showAllCategories.value ? categories.value : categories.value.slice(0, 8);
|
|
||||||
});
|
|
||||||
|
|
||||||
// เตรียม Options สำหรับ QSelect Dropdown
|
|
||||||
const categoryOptions = computed(() => {
|
|
||||||
return categories.value.map(c => ({
|
|
||||||
id: c.id,
|
|
||||||
label: getLocalizedText(c.name),
|
|
||||||
value: c.id // q-select uses value by default if not constrained
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
// ฟังก์ชัน Toggle Category สำหรับ Chip Remove
|
|
||||||
const toggleCategory = (id: number) => {
|
|
||||||
if (selectedCategoryIds.value.includes(id)) {
|
|
||||||
selectedCategoryIds.value = selectedCategoryIds.value.filter(cid => cid !== id);
|
|
||||||
} else {
|
|
||||||
selectedCategoryIds.value.push(id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 4. จัดการข้อมูลคอร์ส (Courses)
|
|
||||||
// ==========================================
|
|
||||||
// ใช้ useCourse composable สำหรับจัดการคอร์สเรียน (ดึงข้อมูล, ลงทะเบียน)
|
|
||||||
const { fetchCourses, fetchCourseById, enrollCourse } = useCourse();
|
|
||||||
const courses = ref<any[]>([]);
|
|
||||||
const isLoading = ref(false); // สถานะกำลังโหลดรายการคอร์ส
|
|
||||||
const selectedCourse = ref<any>(null); // คอร์สที่ถูกเลือกดูรายละเอียด
|
|
||||||
const isLoadingDetail = ref(false); // สถานะกำลังโหลดรายละเอียดคอร์ส
|
|
||||||
const isEnrolling = ref(false); // สถานะกำลังกดลงทะเบียน
|
|
||||||
|
|
||||||
// ฟังก์ชันโหลดข้อมูลคอร์สทั้งหมด
|
|
||||||
const loadCourses = async () => {
|
const loadCourses = async () => {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
const res = await fetchCourses();
|
const res = await fetchCourses();
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
// แปลงข้อมูลเบื้องต้นสำหรับแสดงผลในการ์ด
|
|
||||||
courses.value = (res.data || []).map((c: any) => ({
|
courses.value = (res.data || []).map((c: any) => ({
|
||||||
...c,
|
...c,
|
||||||
rating: "0.0",
|
rating: "0.0",
|
||||||
lessons: "0",
|
lessons: "0",
|
||||||
levelType: c.levelType || "neutral"
|
levelType: c.levelType || "neutral"
|
||||||
}));
|
}));
|
||||||
|
|
@ -104,78 +81,31 @@ const loadCourses = async () => {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ฟังก์ชันเลือกคอร์สเพื่อดูรายละเอียด
|
|
||||||
const selectCourse = async (id: number) => {
|
const selectCourse = async (id: number) => {
|
||||||
isLoadingDetail.value = true;
|
isLoadingDetail.value = true;
|
||||||
selectedCourse.value = null;
|
selectedCourse.value = null;
|
||||||
showDetail.value = true; // เปลี่ยนหน้าเป็นแบบ Detail View
|
showDetail.value = true;
|
||||||
|
|
||||||
// ดึงข้อมูลรายละเอียดคอร์สจาก API โดยใช้ ID
|
|
||||||
const res = await fetchCourseById(id);
|
const res = await fetchCourseById(id);
|
||||||
if (res.success) {
|
if (res.success) selectedCourse.value = res.data;
|
||||||
selectedCourse.value = res.data;
|
|
||||||
}
|
|
||||||
isLoadingDetail.value = false;
|
isLoadingDetail.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ฟังก์ชันลงทะเบียนเรียน (Enroll)
|
|
||||||
const handleEnroll = async (id: number) => {
|
const handleEnroll = async (id: number) => {
|
||||||
if (isEnrolling.value) return; // ป้องกันการกดรัว
|
if (isEnrolling.value) return;
|
||||||
isEnrolling.value = true;
|
isEnrolling.value = true;
|
||||||
|
|
||||||
const res = await enrollCourse(id);
|
const res = await enrollCourse(id);
|
||||||
|
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
// ถ้าสำเร็จ ให้เปลี่ยนหน้าไปที่ "คอร์สของฉัน" พร้อมส่ง parameter enrolled=true เพื่อแสดง popup
|
|
||||||
return navigateTo('/dashboard/my-courses?enrolled=true');
|
return navigateTo('/dashboard/my-courses?enrolled=true');
|
||||||
} else {
|
} else {
|
||||||
alert(res.error || 'Failed to enroll');
|
alert(res.error || 'Failed to enroll');
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnrolling.value = false;
|
isEnrolling.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// โหลดข้อมูลเมื่อหน้าเว็บเริ่มทำงาน
|
|
||||||
loadCategories();
|
loadCategories();
|
||||||
loadCourses();
|
loadCourses();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filter Logic based on search query
|
|
||||||
// ==========================================
|
|
||||||
// 5. ระบบกรองและค้นหา (Filter & Search)
|
|
||||||
// ==========================================
|
|
||||||
// selectedCategoryIds: เก็บ ID ของหมวดหมู่ที่ถูกติ๊กเลือก
|
|
||||||
const selectedCategoryIds = ref<number[]>([]);
|
|
||||||
|
|
||||||
// คำนวณคอร์สที่จะแสดงผลจริง (Filter Logic)
|
|
||||||
const filteredCourses = computed(() => {
|
|
||||||
let result = courses.value;
|
|
||||||
|
|
||||||
// 1. กรองตามหมวดหมู่ (Category Filter)
|
|
||||||
if (selectedCategoryIds.value.length > 0) {
|
|
||||||
result = result.filter(c => selectedCategoryIds.value.includes(c.category_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. กรองตามคำค้นหา (Search Query) - ค้นหาจากชื่อหรือคำอธิบาย
|
|
||||||
if (searchQuery.value) {
|
|
||||||
const query = searchQuery.value.toLowerCase();
|
|
||||||
result = result.filter(
|
|
||||||
(c) => {
|
|
||||||
const title = getLocalizedText(c.title).toLowerCase();
|
|
||||||
const desc = getLocalizedText(c.description).toLowerCase();
|
|
||||||
return title.includes(query) || (desc && desc.includes(query));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 6. User & Helpers
|
|
||||||
// ==========================================
|
|
||||||
const { currentUser } = useAuth();
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -200,7 +130,7 @@ const { currentUser } = useAuth();
|
||||||
dense
|
dense
|
||||||
outlined
|
outlined
|
||||||
rounded
|
rounded
|
||||||
placeholder="ค้นหาคอร์ส..."
|
:placeholder="$t('discovery.searchPlaceholder')"
|
||||||
class="bg-white dark:bg-slate-800 w-full md:w-64"
|
class="bg-white dark:bg-slate-800 w-full md:w-64"
|
||||||
bg-color="white"
|
bg-color="white"
|
||||||
color="slate-900"
|
color="slate-900"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue