// Course Response structure export interface CourseResponse { id: number; category_id: number; title: { en: string; th: string; }; slug: string; description: { en: string; th: string; }; thumbnail_url: string | null; price: string; is_free: boolean; have_certificate: boolean; status: 'DRAFT' | 'PENDING' | 'APPROVED' | 'REJECTED'; approved_by: number | null; approved_at: string | null; rejection_reason: string | null; created_at: string; created_by: number; updated_at: string; updated_by: number | null; } export interface CoursesListResponse { code: number; message: string; data: CourseResponse[]; total: number; } // Helper function to get auth token from cookie const getAuthToken = (): string => { const tokenCookie = useCookie('token'); return tokenCookie.value || ''; }; // Helper function for making authenticated requests with auto refresh const authRequest = async ( url: string, options: { method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; body?: any; } = {} ): Promise => { const config = useRuntimeConfig(); const makeRequest = async (token: string) => { return await $fetch(url, { baseURL: config.public.apiBaseUrl as string, method: options.method || 'GET', headers: { Authorization: `Bearer ${token}` }, body: options.body }); }; try { const token = getAuthToken(); return await makeRequest(token); } catch (error: any) { // If 401, try to refresh token if (error.response?.status === 401) { const { handleUnauthorized } = await import('~/utils/authFetch'); const newToken = await handleUnauthorized(); if (newToken) { return await makeRequest(newToken); } // Redirect to login navigateTo('/login'); } throw error; } }; // Mock courses data const MOCK_COURSES: CourseResponse[] = [ { id: 1, category_id: 1, title: { en: 'JavaScript Fundamentals', th: 'พื้นฐาน JavaScript' }, slug: 'javascript-fundamentals', description: { en: 'Learn JavaScript fundamentals from scratch', th: 'เรียนรู้พื้นฐาน JavaScript ตั้งแต่เริ่มต้น' }, thumbnail_url: null, price: '0', is_free: true, have_certificate: true, status: 'APPROVED', approved_by: 1, approved_at: '2024-01-15T00:00:00Z', rejection_reason: null, created_at: '2024-01-15T00:00:00Z', created_by: 2, updated_at: '2024-01-15T00:00:00Z', updated_by: null }, { id: 2, category_id: 2, title: { en: 'React for Beginners', th: 'React สำหรับผู้เริ่มต้น' }, slug: 'react-for-beginners', description: { en: 'Build modern web apps with React', th: 'สร้างเว็บแอปพลิเคชันด้วย React' }, thumbnail_url: null, price: '990', is_free: false, have_certificate: true, status: 'PENDING', approved_by: null, approved_at: null, rejection_reason: null, created_at: '2024-02-01T00:00:00Z', created_by: 2, updated_at: '2024-02-01T00:00:00Z', updated_by: null }, { id: 3, category_id: 1, title: { en: 'TypeScript Masterclass', th: 'TypeScript ขั้นสูง' }, slug: 'typescript-masterclass', description: { en: 'Master TypeScript for better JavaScript development', th: 'เรียนรู้ TypeScript เพื่อพัฒนา JavaScript ได้ดียิ่งขึ้น' }, thumbnail_url: null, price: '1290', is_free: false, have_certificate: true, status: 'DRAFT', approved_by: null, approved_at: null, rejection_reason: null, created_at: '2024-02-15T00:00:00Z', created_by: 2, updated_at: '2024-02-15T00:00:00Z', updated_by: null } ]; export const instructorService = { async getCourses(): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return MOCK_COURSES; } const response = await authRequest('/api/instructors/courses'); return response.data; }, async createCourse(data: CreateCourseRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { ...MOCK_COURSES[0], id: Date.now(), ...data, price: String(data.price), // Convert number to string to match CourseResponse type status: 'DRAFT', created_at: new Date().toISOString(), updated_at: new Date().toISOString() } as CourseResponse; } // Clean data - remove empty thumbnail_url const cleanedData = { ...data }; if (!cleanedData.thumbnail_url) { delete cleanedData.thumbnail_url; } const response = await authRequest<{ code: number; data: CourseResponse }>('/api/instructors/courses', { method: 'POST', body: { data: cleanedData } }); return response.data; }, async getCourseById(courseId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return MOCK_COURSE_DETAIL; } const response = await authRequest<{ code: number; data: CourseDetailResponse }>(`/api/instructors/courses/${courseId}`); return response.data; }, async updateCourse(courseId: number, data: CreateCourseRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { ...MOCK_COURSES[0], id: courseId, ...data, price: String(data.price) } as CourseResponse; } const response = await authRequest<{ code: number; data: CourseResponse }>(`/api/instructors/courses/${courseId}`, { method: 'PUT', body: { data } }); return response.data; }, async deleteCourse(courseId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return; } await authRequest(`/api/instructors/courses/${courseId}`, { method: 'DELETE' }); }, async sendForReview(courseId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return; } await authRequest(`/api/instructors/courses/send-review/${courseId}`, { method: 'POST' }); }, async getChapters(courseId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return MOCK_COURSE_DETAIL.chapters; } // Get chapters from course detail endpoint const response = await authRequest<{ code: number; data: { chapters: ChapterResponse[] } }>( `/api/instructors/courses/${courseId}` ); return response.data.chapters || []; }, async createChapter(courseId: number, data: CreateChapterRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { ...MOCK_COURSE_DETAIL.chapters[0], id: Date.now(), ...data, lessons: [] }; } const response = await authRequest<{ code: number; data: ChapterResponse }>( `/api/instructors/courses/${courseId}/chapters`, { method: 'POST', body: data } ); return response.data; }, async updateChapter(courseId: number, chapterId: number, data: CreateChapterRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { ...MOCK_COURSE_DETAIL.chapters[0], id: chapterId, ...data }; } const response = await authRequest<{ code: number; data: ChapterResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}`, { method: 'PUT', body: data } ); return response.data; }, async deleteChapter(courseId: number, chapterId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return; } await authRequest( `/api/instructors/courses/${courseId}/chapters/${chapterId}`, { method: 'DELETE' } ); }, async reorderChapter(courseId: number, chapterId: number, sortOrder: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 300)); return; } await authRequest( `/api/instructors/courses/${courseId}/chapters/${chapterId}/reorder`, { method: 'PUT', body: { sort_order: sortOrder } } ); }, // Lesson CRUD async getLesson(courseId: number, chapterId: number, lessonId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return MOCK_COURSE_DETAIL.chapters[0].lessons[0] as LessonDetailResponse; } const response = await authRequest<{ code: number; data: LessonDetailResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}` ); return response.data; }, async createLesson(courseId: number, chapterId: number, data: CreateLessonRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { ...MOCK_COURSE_DETAIL.chapters[0].lessons[0], id: Date.now(), ...data }; } const response = await authRequest<{ code: number; data: LessonResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons`, { method: 'POST', body: data } ); return response.data; }, async updateLesson(courseId: number, chapterId: number, lessonId: number, data: UpdateLessonRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { ...MOCK_COURSE_DETAIL.chapters[0].lessons[0], id: lessonId, ...data }; } const response = await authRequest<{ code: number; data: LessonResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`, { method: 'PUT', body: data } ); return response.data; }, async deleteLesson(courseId: number, chapterId: number, lessonId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return; } await authRequest( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`, { method: 'DELETE' } ); }, async reorderLesson(courseId: number, chapterId: number, lessonId: number, sortOrder: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 300)); return; } await authRequest( `/api/instructors/courses/${courseId}/chapters/${chapterId}/reorder-lessons`, { method: 'PUT', body: { lesson_id: lessonId, sort_order: sortOrder } } ); }, // Question CRUD async createQuestion(courseId: number, chapterId: number, lessonId: number, data: CreateQuestionRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { id: Date.now(), quiz_id: 1, question: data.question, explanation: data.explanation || null, question_type: data.question_type, score: data.score, sort_order: data.sort_order || 1, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), choices: data.choices.map((c, i) => ({ id: Date.now() + i, question_id: Date.now(), text: c.text, is_correct: c.is_correct, sort_order: c.sort_order || i + 1 })) }; } const response = await authRequest<{ code: number; data: QuizQuestionResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions`, { method: 'POST', body: data } ); return response.data; }, async updateQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number, data: CreateQuestionRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { id: questionId, quiz_id: 1, question: data.question, explanation: data.explanation || null, question_type: data.question_type, score: data.score, sort_order: data.sort_order || 1, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), choices: data.choices.map((c, i) => ({ id: Date.now() + i, question_id: questionId, text: c.text, is_correct: c.is_correct, sort_order: c.sort_order || i + 1 })) }; } const response = await authRequest<{ code: number; data: QuizQuestionResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}`, { method: 'PUT', body: data } ); return response.data; }, async deleteQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return; } await authRequest( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}`, { method: 'DELETE' } ); }, async reorderQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number, sortOrder: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 300)); return; } await authRequest( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}/reorder`, { method: 'PUT', body: { sort_order: sortOrder } } ); }, // Quiz Settings async updateQuizSettings(courseId: number, chapterId: number, lessonId: number, data: UpdateQuizSettingsRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { id: 1, lesson_id: lessonId, title: data.title, description: data.description, passing_score: data.passing_score, time_limit: data.time_limit, shuffle_questions: data.shuffle_questions, shuffle_choices: data.shuffle_choices, show_answers_after_completion: data.show_answers_after_completion }; } const response = await authRequest<{ code: number; data: QuizResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/quiz`, { method: 'PUT', body: data } ); return response.data; }, // Video Upload async uploadLessonVideo(courseId: number, chapterId: number, lessonId: number, video: File, attachments?: File[]): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 1000)); return MOCK_COURSE_DETAIL.chapters[0].lessons[0] as LessonResponse; } const formData = new FormData(); formData.append('video', video); if (attachments) { attachments.forEach(file => { formData.append('attachments', file); }); } const response = await authRequest<{ code: number; data: LessonResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/video`, { method: 'POST', body: formData } ); return response.data; }, async updateLessonVideo(courseId: number, chapterId: number, lessonId: number, video?: File, attachments?: File[]): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 1000)); return MOCK_COURSE_DETAIL.chapters[0].lessons[0] as LessonResponse; } const formData = new FormData(); if (video) { formData.append('video', video); } if (attachments) { attachments.forEach(file => { formData.append('attachments', file); }); } const response = await authRequest<{ code: number; data: LessonResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/video`, { method: 'PUT', body: formData } ); return response.data; }, // Attachments async addAttachments(courseId: number, chapterId: number, lessonId: number, files: File[]): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return MOCK_COURSE_DETAIL.chapters[0].lessons[0] as LessonResponse; } const formData = new FormData(); files.forEach(file => { formData.append('attachment', file); }); const response = await authRequest<{ code: number; data: LessonResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/attachments`, { method: 'POST', body: formData } ); return response.data; }, async deleteAttachment(courseId: number, chapterId: number, lessonId: number, attachmentId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return; } await authRequest( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/attachments/${attachmentId}`, { method: 'DELETE' } ); }, // Announcements async getAnnouncements(courseId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return MOCK_ANNOUNCEMENTS; } const response = await authRequest( `/api/instructors/courses/${courseId}/announcements` ); return response.data; }, async createAnnouncement(courseId: number, data: CreateAnnouncementRequest, files?: File[]): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { id: Date.now(), title: data.title, content: data.content, status: data.status || 'DRAFT', is_pinned: data.is_pinned || false, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), attachments: [] }; } const formData = new FormData(); formData.append('data', JSON.stringify(data)); if (files && files.length > 0) { files.forEach(file => { formData.append('files', file); }); } const response = await authRequest<{ code: number; data: AnnouncementResponse }>( `/api/instructors/courses/${courseId}/announcements`, { method: 'POST', body: formData } ); return response.data; }, async updateAnnouncement(courseId: number, announcementId: number, data: CreateAnnouncementRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { id: announcementId, title: data.title, content: data.content, status: data.status || 'DRAFT', is_pinned: data.is_pinned || false, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), attachments: [] }; } const response = await authRequest<{ code: number; data: AnnouncementResponse }>( `/api/instructors/courses/${courseId}/announcements/${announcementId}`, { method: 'PUT', body: data } ); return response.data; }, async deleteAnnouncement(courseId: number, announcementId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return; } await authRequest( `/api/instructors/courses/${courseId}/announcements/${announcementId}`, { method: 'DELETE' } ); }, async uploadAnnouncementAttachment(courseId: number, announcementId: number, file: File): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return MOCK_ANNOUNCEMENTS[0]; } const formData = new FormData(); formData.append('file', file); const response = await authRequest<{ code: number; data: AnnouncementResponse }>( `/api/instructors/courses/${courseId}/announcements/${announcementId}/attachments`, { method: 'POST', body: formData } ); return response.data; }, async deleteAnnouncementAttachment(courseId: number, announcementId: number, attachmentId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return; } await authRequest( `/api/instructors/courses/${courseId}/announcements/${announcementId}/attachments/${attachmentId}`, { method: 'DELETE' } ); } }; // Create course request export interface CreateCourseRequest { category_id: number; title: { en: string; th: string; }; slug: string; description: { en: string; th: string; }; thumbnail_url?: string | null; price: number; is_free: boolean; have_certificate: boolean; } // Course detail with chapters and lessons export interface CourseDetailResponse extends CourseResponse { chapters: ChapterResponse[]; } export interface ChapterResponse { id: number; course_id: number; title: { en: string; th: string }; description: { en: string; th: string }; sort_order: number; is_published: boolean; created_at: string; updated_at: string; lessons: LessonResponse[]; } export interface LessonResponse { id: number; chapter_id: number; title: { en: string; th: string }; content: { en: string; th: string } | null; type: 'VIDEO' | 'QUIZ' | 'DOCUMENT'; duration_minutes: number; sort_order: number; is_sequential: boolean; prerequisite_lesson_ids: number[] | null; require_pass_quiz: boolean; is_published: boolean; created_at: string; updated_at: string; video_url: string | null; attachments: AttachmentResponse[]; quiz: QuizResponse | null; } export interface QuizChoiceResponse { id: number; question_id: number; text: { en: string; th: string }; is_correct: boolean; sort_order: number; } export interface QuizQuestionResponse { id: number; quiz_id: number; question: { en: string; th: string }; explanation: { en: string; th: string } | null; question_type: 'MULTIPLE_CHOICE' | 'TRUE_FALSE'; score: number; sort_order: number; created_at: string; updated_at: string; choices: QuizChoiceResponse[]; } export interface QuizResponse { id: number; lesson_id: number; title: { en: string; th: string }; description: { en: string; th: string }; passing_score: number; time_limit: number; shuffle_questions: boolean; shuffle_choices: boolean; show_answers_after_completion: boolean; created_at?: string; updated_at?: string; questions?: QuizQuestionResponse[]; } export interface UpdateQuizSettingsRequest { title: { th: string; en: string }; description: { th: string; en: string }; passing_score: number; time_limit: number; shuffle_questions: boolean; shuffle_choices: boolean; show_answers_after_completion: boolean; } export interface CreateChapterRequest { title: { th: string; en: string }; description: { th: string; en: string }; sort_order?: number; is_published?: boolean; } export interface CreateChoiceRequest { text: { th: string; en: string }; is_correct: boolean; sort_order?: number; } export interface CreateQuestionRequest { question: { th: string; en: string }; explanation?: { th: string; en: string } | null; question_type: 'MULTIPLE_CHOICE' | 'TRUE_FALSE'; sort_order?: number; choices: CreateChoiceRequest[]; } export interface CreateLessonRequest { title: { th: string; en: string }; content?: { th: string; en: string } | null; type: 'VIDEO' | 'QUIZ'; sort_order?: number; } export interface UpdateLessonRequest { title: { th: string; en: string }; content?: { th: string; en: string } | null; } export interface AttachmentResponse { id: number; lesson_id: number; file_name: string; file_path: string; file_size: number; mime_type: string; description: { th: string; en: string }; sort_order: number; created_at: string; } export interface LessonDetailResponse extends LessonResponse { attachments: AttachmentResponse[]; } // Announcement interfaces export interface AnnouncementAttachment { id: number; file_name: string; file_path: string; file_size: number; mime_type: string; } export interface AnnouncementResponse { id: number; title: { th: string; en: string; }; content: { th: string; en: string; }; status: 'DRAFT' | 'PUBLISHED'; is_pinned: boolean; created_at: string; updated_at: string; attachments: AnnouncementAttachment[]; } export interface AnnouncementsListResponse { code: number; message: string; data: AnnouncementResponse[]; total: number; page: number; limit: number; } export interface CreateAnnouncementRequest { title: { th: string; en: string; }; content: { th: string; en: string; }; status?: 'DRAFT' | 'PUBLISHED'; is_pinned?: boolean; } // Mock announcements const MOCK_ANNOUNCEMENTS: AnnouncementResponse[] = [ { id: 1, title: { th: 'ยินดีต้อนรับสู่คอร์ส', en: 'Welcome to the Course' }, content: { th: 'ยินดีต้อนรับทุกคนสู่คอร์ส JavaScript Fundamentals! เราจะเริ่มเรียนในสัปดาห์หน้า', en: 'Welcome everyone to JavaScript Fundamentals! We will start next week' }, status: 'PUBLISHED', is_pinned: true, created_at: '2024-01-15T10:00:00Z', updated_at: '2024-01-15T10:00:00Z', attachments: [] }, { id: 2, title: { th: 'อัพเดทตารางเรียน', en: 'Schedule Update' }, content: { th: 'มีการเปลี่ยนแปลงตารางเรียนสำหรับบทที่ 3', en: 'There is a schedule change for Chapter 3' }, status: 'PUBLISHED', is_pinned: false, created_at: '2024-01-20T14:00:00Z', updated_at: '2024-01-20T14:00:00Z', attachments: [] } ]; // Mock course detail const MOCK_COURSE_DETAIL: CourseDetailResponse = { ...MOCK_COURSES[0], chapters: [ { id: 1, course_id: 1, title: { en: 'Chapter 1: Getting Started', th: 'บทที่ 1: เริ่มต้น' }, description: { en: 'Introduction to JavaScript', th: 'แนะนำ JavaScript' }, sort_order: 1, is_published: true, created_at: '2024-01-15T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', lessons: [ { id: 1, chapter_id: 1, title: { en: 'What is JavaScript', th: 'JavaScript คืออะไร' }, content: { en: 'Introduction', th: 'แนะนำ' }, type: 'VIDEO', duration_minutes: 15, sort_order: 1, is_sequential: true, prerequisite_lesson_ids: null, require_pass_quiz: false, is_published: true, created_at: '2024-01-15T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', video_url: null, attachments: [], quiz: null }, { id: 2, chapter_id: 1, title: { en: 'Variables', th: 'ตัวแปร' }, content: { en: 'Learn variables', th: 'เรียนรู้ตัวแปร' }, type: 'VIDEO', duration_minutes: 20, sort_order: 2, is_sequential: true, prerequisite_lesson_ids: null, require_pass_quiz: false, is_published: true, created_at: '2024-01-15T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', video_url: null, attachments: [], quiz: null }, { id: 3, chapter_id: 1, title: { en: 'Chapter 1 Quiz', th: 'แบบทดสอบบทที่ 1' }, content: null, type: 'QUIZ', duration_minutes: 10, sort_order: 3, is_sequential: true, prerequisite_lesson_ids: null, require_pass_quiz: true, is_published: true, created_at: '2024-01-15T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', video_url: null, attachments: [], quiz: { id: 1, lesson_id: 3, title: { en: 'Chapter 1 Quiz', th: 'แบบทดสอบบทที่ 1' }, description: { en: 'Test your knowledge', th: 'ทดสอบความรู้' }, passing_score: 70, time_limit: 10, shuffle_questions: true, shuffle_choices: true, show_answers_after_completion: true } } ] }, { id: 2, course_id: 1, title: { en: 'Chapter 2: Functions', th: 'บทที่ 2: ฟังก์ชัน' }, description: { en: 'Learn about functions', th: 'เรียนรู้เกี่ยวกับฟังก์ชัน' }, sort_order: 2, is_published: true, created_at: '2024-01-15T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', lessons: [ { id: 4, chapter_id: 2, title: { en: 'Creating Functions', th: 'การสร้างฟังก์ชัน' }, content: { en: 'How to create functions', th: 'วิธีสร้างฟังก์ชัน' }, type: 'VIDEO', duration_minutes: 25, sort_order: 1, is_sequential: true, prerequisite_lesson_ids: null, require_pass_quiz: false, is_published: true, created_at: '2024-01-15T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', video_url: null, attachments: [], quiz: null } ] } ] };