2026-01-16 10:03:04 +07:00
|
|
|
import type { H3Event } from 'h3'
|
|
|
|
|
|
|
|
|
|
// Types based on API responses
|
|
|
|
|
export interface Course {
|
|
|
|
|
id: number
|
2026-01-16 10:26:33 +07:00
|
|
|
title: string | { th: string; en: string }
|
2026-01-16 10:03:04 +07:00
|
|
|
slug: string
|
2026-01-16 10:26:33 +07:00
|
|
|
description: string | { th: string; en: string }
|
2026-01-16 10:03:04 +07:00
|
|
|
thumbnail_url: string
|
|
|
|
|
price: string
|
|
|
|
|
is_free: boolean
|
|
|
|
|
have_certificate: boolean
|
|
|
|
|
status: string // 'DRAFT' | 'PUBLISHED' | ...
|
|
|
|
|
category_id: number
|
|
|
|
|
created_at?: string
|
|
|
|
|
updated_at?: string
|
|
|
|
|
created_by?: number
|
|
|
|
|
updated_by?: number
|
|
|
|
|
approved_at?: string
|
|
|
|
|
approved_by?: number
|
|
|
|
|
rejection_reason?: string
|
|
|
|
|
|
|
|
|
|
// Helper properties for UI (may be computed or mapped)
|
|
|
|
|
rating?: string
|
|
|
|
|
lessons?: number | string
|
|
|
|
|
levelType?: 'neutral' | 'warning' | 'success' // For UI badging
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface CourseResponse {
|
|
|
|
|
code: number
|
|
|
|
|
message: string
|
|
|
|
|
data: Course[]
|
|
|
|
|
total: number
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 15:13:02 +07:00
|
|
|
export interface EnrolledCourse {
|
|
|
|
|
id: number // enrollment_id
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface EnrolledCourseResponse {
|
|
|
|
|
code: number
|
|
|
|
|
message: string
|
|
|
|
|
data: EnrolledCourse[]
|
|
|
|
|
total: number
|
|
|
|
|
page: number
|
|
|
|
|
limit: number
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 10:03:04 +07:00
|
|
|
export const useCourse = () => {
|
|
|
|
|
const config = useRuntimeConfig()
|
|
|
|
|
const API_BASE_URL = config.public.apiBase as string
|
|
|
|
|
const { token } = useAuth()
|
|
|
|
|
|
|
|
|
|
const fetchCourses = async () => {
|
|
|
|
|
try {
|
2026-01-16 10:26:33 +07:00
|
|
|
const data = await $fetch<CourseResponse>(`${API_BASE_URL}/courses`, {
|
2026-01-16 10:03:04 +07:00
|
|
|
method: 'GET',
|
|
|
|
|
headers: token.value ? {
|
|
|
|
|
Authorization: `Bearer ${token.value}`
|
|
|
|
|
} : {}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
2026-01-16 10:26:33 +07:00
|
|
|
data: data.data || [],
|
|
|
|
|
total: data.total || 0
|
2026-01-16 10:03:04 +07:00
|
|
|
}
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
console.error('Fetch courses failed:', err)
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: err.data?.message || err.message || 'Error fetching courses'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchCourseById = async (id: number) => {
|
|
|
|
|
try {
|
2026-01-16 10:26:33 +07:00
|
|
|
const data = await $fetch<CourseResponse>(`${API_BASE_URL}/courses/${id}`, {
|
2026-01-16 10:03:04 +07:00
|
|
|
method: 'GET',
|
|
|
|
|
headers: token.value ? {
|
|
|
|
|
Authorization: `Bearer ${token.value}`
|
|
|
|
|
} : {}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// API returns data array even for single item based on schema
|
2026-01-16 10:26:33 +07:00
|
|
|
const courseData = data.data?.[0]
|
2026-01-16 10:03:04 +07:00
|
|
|
|
|
|
|
|
if (!courseData) throw new Error('Course not found')
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: courseData
|
|
|
|
|
}
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
console.error('Fetch course details failed:', err)
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: err.data?.message || err.message || 'Error fetching course details'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 15:01:01 +07:00
|
|
|
const enrollCourse = async (courseId: number) => {
|
|
|
|
|
try {
|
|
|
|
|
const data = await $fetch<{ code: number; message: string; data: any }>(`${API_BASE_URL}/students/courses/${courseId}/enroll`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: token.value ? {
|
|
|
|
|
Authorization: `Bearer ${token.value}`
|
|
|
|
|
} : {}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: data.data,
|
|
|
|
|
message: data.message
|
|
|
|
|
}
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
console.error('Enroll course failed:', err)
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: err.data?.message || err.message || 'Error enrolling in course',
|
|
|
|
|
code: err.data?.code
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 15:13:02 +07:00
|
|
|
const fetchEnrolledCourses = async (params: { page?: number; limit?: number; status?: string } = {}) => {
|
|
|
|
|
try {
|
|
|
|
|
const queryParams = new URLSearchParams()
|
|
|
|
|
if (params.page) queryParams.append('page', params.page.toString())
|
|
|
|
|
if (params.limit) queryParams.append('limit', params.limit.toString())
|
|
|
|
|
if (params.status && params.status !== 'ALL') queryParams.append('status', params.status)
|
|
|
|
|
|
|
|
|
|
const data = await $fetch<EnrolledCourseResponse>(`${API_BASE_URL}/students/courses?${queryParams.toString()}`, {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
headers: token.value ? {
|
|
|
|
|
Authorization: `Bearer ${token.value}`
|
|
|
|
|
} : {}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: data.data || [],
|
|
|
|
|
total: data.total || 0,
|
|
|
|
|
page: data.page,
|
|
|
|
|
limit: data.limit
|
|
|
|
|
}
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
console.error('Fetch enrolled courses failed:', err)
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: err.data?.message || err.message || 'Error fetching enrolled courses'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 15:25:18 +07:00
|
|
|
const fetchCourseLearningInfo = async (courseId: number) => {
|
|
|
|
|
try {
|
|
|
|
|
const data = await $fetch<{ code: number; message: string; data: any }>(`${API_BASE_URL}/students/courses/${courseId}/learn`, {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
headers: token.value ? {
|
|
|
|
|
Authorization: `Bearer ${token.value}`
|
|
|
|
|
} : {}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: data.data
|
|
|
|
|
}
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
console.error('Fetch course learning info failed:', err)
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: err.data?.message || err.message || 'Error fetching course learning info',
|
|
|
|
|
code: err.data?.code
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fetchLessonContent = async (courseId: number, lessonId: number) => {
|
|
|
|
|
try {
|
|
|
|
|
const data = await $fetch<{ code: number; message: string; data: any; progress?: any }>(`${API_BASE_URL}/students/courses/${courseId}/lessons/${lessonId}`, {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
headers: token.value ? {
|
|
|
|
|
Authorization: `Bearer ${token.value}`
|
|
|
|
|
} : {}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: data.data,
|
|
|
|
|
progress: data.progress
|
|
|
|
|
}
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
console.error('Fetch lesson content failed:', err)
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: err.data?.message || err.message || 'Error fetching lesson content',
|
|
|
|
|
code: err.data?.code,
|
|
|
|
|
status: err.status
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 15:31:28 +07:00
|
|
|
const checkLessonAccess = async (courseId: number, lessonId: number) => {
|
|
|
|
|
try {
|
|
|
|
|
const data = await $fetch<{ code: number; message: string; data: any }>(`${API_BASE_URL}/students/courses/${courseId}/lessons/${lessonId}/access-check`, {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
headers: token.value ? {
|
|
|
|
|
Authorization: `Bearer ${token.value}`
|
|
|
|
|
} : {}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
data: data.data
|
|
|
|
|
}
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
console.error('Check lesson access failed:', err)
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
error: err.data?.message || err.message || 'Error checking lesson access',
|
|
|
|
|
code: err.data?.code,
|
|
|
|
|
status: err.status
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-16 10:03:04 +07:00
|
|
|
return {
|
|
|
|
|
fetchCourses,
|
2026-01-20 15:01:01 +07:00
|
|
|
fetchCourseById,
|
2026-01-20 15:13:02 +07:00
|
|
|
enrollCourse,
|
2026-01-20 15:25:18 +07:00
|
|
|
fetchEnrolledCourses,
|
|
|
|
|
fetchCourseLearningInfo,
|
2026-01-20 15:31:28 +07:00
|
|
|
fetchLessonContent,
|
|
|
|
|
checkLessonAccess
|
2026-01-16 10:03:04 +07:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-16 10:26:33 +07:00
|
|
|
|