2026-01-21 10:02:37 +07:00
|
|
|
<script setup lang="ts">
|
|
|
|
|
/**
|
|
|
|
|
* @file [id].vue
|
|
|
|
|
* @description Dynamic Course Detail Page.
|
|
|
|
|
* Displays detailed information about a specific course based on the ID.
|
|
|
|
|
*/
|
|
|
|
|
|
2026-01-22 10:46:44 +07:00
|
|
|
definePageMeta({
|
|
|
|
|
layout: 'default',
|
|
|
|
|
middleware: 'auth'
|
|
|
|
|
})
|
|
|
|
|
|
2026-01-21 10:02:37 +07:00
|
|
|
const route = useRoute()
|
2026-01-23 09:47:32 +07:00
|
|
|
// ดึง courseId จาก URL params (แปลงเป็น integer)
|
2026-01-21 10:02:37 +07:00
|
|
|
const courseId = computed(() => parseInt(route.params.id as string))
|
2026-02-11 12:45:57 +07:00
|
|
|
const { currentUser } = useAuth()
|
2026-02-09 11:40:41 +07:00
|
|
|
const { fetchCourseById, enrollCourse, getLocalizedText } = useCourse()
|
2026-01-21 10:02:37 +07:00
|
|
|
|
2026-01-23 09:47:32 +07:00
|
|
|
// ใช้ useAsyncData ดึงข้อมูลคอร์ส Server-side rendering (SSR)
|
|
|
|
|
// Key: 'course-{id}' เพื่อให้ cache แยกกันตาม ID
|
2026-01-22 10:46:44 +07:00
|
|
|
const { data: courseData, error, refresh } = await useAsyncData(`course-${courseId.value}`, () => fetchCourseById(courseId.value))
|
2026-01-21 10:02:37 +07:00
|
|
|
|
|
|
|
|
const course = computed(() => {
|
|
|
|
|
return courseData.value?.success ? courseData.value.data : null
|
|
|
|
|
})
|
|
|
|
|
|
2026-01-22 10:46:44 +07:00
|
|
|
const isEnrolling = ref(false)
|
|
|
|
|
|
2026-01-23 09:47:32 +07:00
|
|
|
// ฟังก์ชันสำหรับกดปุ่ม "ลงทะเบียนเรียน"
|
2026-01-22 10:46:44 +07:00
|
|
|
const handleEnroll = async () => {
|
|
|
|
|
if (!course.value) return
|
|
|
|
|
if (isEnrolling.value) return
|
|
|
|
|
isEnrolling.value = true
|
|
|
|
|
|
2026-01-23 09:47:32 +07:00
|
|
|
// เรียก API ลงทะเบียนเรียน
|
2026-01-22 10:46:44 +07:00
|
|
|
const res = await enrollCourse(course.value.id)
|
|
|
|
|
|
|
|
|
|
if (res.success) {
|
2026-01-23 09:47:32 +07:00
|
|
|
// ถ้าสำเร็จ ให้เปลี่ยนหน้าไปที่ "คอร์สของฉัน" พร้อม params enrolled=true
|
2026-02-02 14:37:26 +07:00
|
|
|
// Use object syntax for robust query param handling
|
|
|
|
|
const targetId = route.params.id || course.value?.id
|
|
|
|
|
return navigateTo({
|
|
|
|
|
path: '/dashboard/my-courses',
|
|
|
|
|
query: {
|
|
|
|
|
enrolled: 'true',
|
|
|
|
|
course_id: String(targetId)
|
|
|
|
|
}
|
|
|
|
|
})
|
2026-01-22 10:46:44 +07:00
|
|
|
} else {
|
2026-01-23 09:47:32 +07:00
|
|
|
// กรณี error แสดง alert (อนาคตอาจเปลี่ยนเป็น Toast notification)
|
2026-01-22 10:46:44 +07:00
|
|
|
alert(res.error || 'Failed to enroll')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isEnrolling.value = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-01-21 10:02:37 +07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
useHead({
|
|
|
|
|
title: computed(() => course.value ? `${getLocalizedText(course.value.title)} - E-Learning` : 'Course Details')
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-02-09 10:37:42 +07:00
|
|
|
<div class="page-container">
|
2026-01-21 10:02:37 +07:00
|
|
|
|
2026-01-27 10:32:13 +07:00
|
|
|
|
2026-02-11 12:45:57 +07:00
|
|
|
<div v-if="course" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
|
|
|
<CourseDetailView
|
|
|
|
|
:course="course"
|
|
|
|
|
:user="currentUser"
|
|
|
|
|
@back="navigateTo('/browse/discovery')"
|
|
|
|
|
@enroll="handleEnroll"
|
|
|
|
|
/>
|
2026-01-21 10:02:37 +07:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Loading / Error State -->
|
2026-01-22 10:46:44 +07:00
|
|
|
<div v-else class="text-center py-20">
|
|
|
|
|
<div v-if="error" class="text-red-500 mb-4">
|
|
|
|
|
<p class="font-bold">เกิดข้อผิดพลาดในการโหลดข้อมูล</p>
|
|
|
|
|
<p class="text-sm opacity-80">{{ error.message }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-else class="flex flex-col items-center gap-4 text-slate-400">
|
|
|
|
|
<div class="w-10 h-10 border-4 border-blue-500/30 border-t-blue-500 rounded-full animate-spin"/>
|
|
|
|
|
<p>กำลังโหลด...</p>
|
|
|
|
|
</div>
|
2026-01-21 10:02:37 +07:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
@keyframes pulse-slow {
|
|
|
|
|
0%, 100% { opacity: 0.1; transform: scale(1); }
|
|
|
|
|
50% { opacity: 0.15; transform: scale(1.15); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.animate-pulse-slow {
|
|
|
|
|
animation: pulse-slow 10s linear infinite;
|
|
|
|
|
}
|
|
|
|
|
</style>
|