diff --git a/frontend_management/pages/instructor/courses/[id]/edit.vue b/frontend_management/pages/instructor/courses/[id]/edit.vue
new file mode 100644
index 00000000..847e9055
--- /dev/null
+++ b/frontend_management/pages/instructor/courses/[id]/edit.vue
@@ -0,0 +1,266 @@
+
+
+
+
+
+
+
แก้ไขหลักสูตร
+
แก้ไขข้อมูลหลักสูตรของคุณ
+
+
+
+
+
+
+
+
+
+
+
+
+ ข้อมูลพื้นฐาน
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ราคาและการตั้งค่า
+
+
+
+
+
+
+
+
+
+ รูปภาพปก
+
+
+
+
+
+
+
+
+
+
+
+
![Thumbnail preview]()
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend_management/pages/instructor/courses/[id]/index.vue b/frontend_management/pages/instructor/courses/[id]/index.vue
new file mode 100644
index 00000000..55994a30
--- /dev/null
+++ b/frontend_management/pages/instructor/courses/[id]/index.vue
@@ -0,0 +1,298 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
รูปหลักสูตร
+
+
+
+
+
+
+
{{ course.title.th }}
+
{{ course.description.th }}
+
+
+
+
+ เผยแพร่
+
+ {{ getStatusLabel(course.status) }}
+
+
+
+
+
+
+
+
+ {{ totalLessons }} บทเรียน
+
+
+
+ 0 ผู้เรียน
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
โครงสร้างบทเรียน
+
+
+
+
+
+ ยังไม่มีบทเรียน
+
+
+
+
+
+
+
+
+ Chapter {{ chapter.sort_order }}: {{ chapter.title.th }}
+
+
+ {{ chapter.lessons.length }} บทเรียน · {{ getChapterDuration(chapter) }} นาที
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Lesson {{ chapter.sort_order }}.{{ lesson.sort_order }}: {{ lesson.title.th }}
+
+
+
+ {{ lesson.duration_minutes }} นาที
+
+
+
+
+
+
+
+
+
+
+
+
ยังไม่มีผู้เรียนในหลักสูตรนี้
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend_management/pages/instructor/courses/create.vue b/frontend_management/pages/instructor/courses/create.vue
index 9a915c95..a4e0eafe 100644
--- a/frontend_management/pages/instructor/courses/create.vue
+++ b/frontend_management/pages/instructor/courses/create.vue
@@ -109,9 +109,9 @@
diff --git a/frontend_management/pages/instructor/courses/index.vue b/frontend_management/pages/instructor/courses/index.vue
index 384a9dea..2e689f77 100644
--- a/frontend_management/pages/instructor/courses/index.vue
+++ b/frontend_management/pages/instructor/courses/index.vue
@@ -236,7 +236,7 @@ const fetchCourses = async () => {
const getStatusColor = (status: string) => {
const colors: Record = {
APPROVED: 'green',
- PENDING: 'yellow',
+ PENDING: 'orange',
DRAFT: 'grey',
REJECTED: 'red'
};
@@ -275,12 +275,23 @@ const confirmDelete = (course: CourseResponse) => {
message: `คุณต้องการลบหลักสูตร "${course.title.th}" หรือไม่?`,
cancel: true,
persistent: true
- }).onOk(() => {
- $q.notify({
- type: 'positive',
- message: 'ลบหลักสูตรสำเร็จ',
- position: 'top'
- });
+ }).onOk(async () => {
+ try {
+ await instructorService.deleteCourse(course.id);
+ $q.notify({
+ type: 'positive',
+ message: 'ลบหลักสูตรสำเร็จ',
+ position: 'top'
+ });
+ // Refresh list
+ fetchCourses();
+ } catch (error) {
+ $q.notify({
+ type: 'negative',
+ message: 'ไม่สามารถลบหลักสูตรได้',
+ position: 'top'
+ });
+ }
});
};
diff --git a/frontend_management/services/instructor.service.ts b/frontend_management/services/instructor.service.ts
index 8138c081..512b0918 100644
--- a/frontend_management/services/instructor.service.ts
+++ b/frontend_management/services/instructor.service.ts
@@ -147,16 +147,119 @@ export const instructorService = {
}
const token = getAuthToken();
+
+ // Clean data - remove empty thumbnail_url
+ const cleanedData = { ...data };
+ if (!cleanedData.thumbnail_url) {
+ delete cleanedData.thumbnail_url;
+ }
+
+ console.log('=== CREATE COURSE DEBUG ===');
+ console.log('Body:', JSON.stringify({ data: cleanedData }, null, 2));
+ console.log('===========================');
+
const response = await $fetch<{ code: number; data: CourseResponse }>('/api/instructors/courses', {
method: 'POST',
baseURL: config.public.apiBaseUrl as string,
headers: {
Authorization: `Bearer ${token}`
},
+ 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 token = getAuthToken();
+ const response = await $fetch<{ code: number; data: CourseDetailResponse }>(`/api/instructors/courses/${courseId}`, {
+ baseURL: config.public.apiBaseUrl as string,
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ });
+
+ 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 token = getAuthToken();
+
+ // Debug log
+ console.log('=== UPDATE COURSE DEBUG ===');
+ console.log('URL:', `${config.public.apiBaseUrl}/api/instructors/courses/${courseId}`);
+ console.log('Body:', JSON.stringify({ data }, null, 2));
+ console.log('===========================');
+
+ const response = await $fetch<{ code: number; data: CourseResponse }>(`/api/instructors/courses/${courseId}`, {
+ method: 'PUT',
+ baseURL: config.public.apiBaseUrl as string,
+ headers: {
+ Authorization: `Bearer ${token}`
+ },
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;
+ }
+
+ const token = getAuthToken();
+ await $fetch(`/api/instructors/courses/${courseId}`, {
+ method: 'DELETE',
+ baseURL: config.public.apiBaseUrl as string,
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ });
+ },
+
+ 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;
+ }
+
+ const token = getAuthToken();
+ await $fetch(`/api/instructors/courses/send-review/${courseId}`, {
+ method: 'POST',
+ baseURL: config.public.apiBaseUrl as string,
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ });
}
};
@@ -177,3 +280,154 @@ export interface CreateCourseRequest {
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;
+ quiz: QuizResponse | null;
+}
+
+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;
+}
+
+// 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',
+ 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',
+ 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',
+ 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',
+ quiz: null
+ }
+ ]
+ }
+ ]
+};