diff --git a/frontend_management/layouts/admin.vue b/frontend_management/layouts/admin.vue index e3d9546e..82872eba 100644 --- a/frontend_management/layouts/admin.vue +++ b/frontend_management/layouts/admin.vue @@ -53,6 +53,15 @@ หมวดหมู่ + + + คอร์สแนะนำ + + { const getActionColor = (action: string) => { if (!action) return 'grey'; - if (action.includes('DELETE') || action.includes('REJECT') || action.includes('DEACTIVATE')) return 'negative'; + if (action.includes('DELETE') || action.includes('REJECT') || action.includes('DEACTIVATE') || action.includes('ERROR')) return 'negative'; if (action.includes('UPDATE') || action.includes('CHANGE')) return 'warning'; if (action.includes('CREATE') || action.includes('APPROVE') || action.includes('ACTIVATE')) return 'positive'; if (action.includes('LOGIN')) return 'info'; diff --git a/frontend_management/pages/admin/recommended-courses/index.vue b/frontend_management/pages/admin/recommended-courses/index.vue new file mode 100644 index 00000000..8eeef6fb --- /dev/null +++ b/frontend_management/pages/admin/recommended-courses/index.vue @@ -0,0 +1,314 @@ + + + diff --git a/frontend_management/pages/instructor/courses/create.vue b/frontend_management/pages/instructor/courses/create.vue index 3f8cbf58..091b953e 100644 --- a/frontend_management/pages/instructor/courses/create.vue +++ b/frontend_management/pages/instructor/courses/create.vue @@ -63,7 +63,7 @@
-
+
{{ stats.total }}
หลักสูตรทั้งหมด
@@ -31,6 +31,10 @@
{{ stats.draft }}
แบบร่าง
+
+
{{ stats.rejected }}
+
ถูกปฏิเสธ
+
@@ -126,7 +130,7 @@ dense icon="visibility" color="grey" - @click="navigateTo(`/instructor/courses/${course.id}`)" + @click="handleViewDetails(course)" > ดูรายละเอียด @@ -162,6 +166,36 @@
+ + + + + +
หลักสูตรถูกปฏิเสธ
+ + +
+ + +
เหตุผลการปฏิเสธ:
+
+ {{ selectedRejectionCourse?.rejection_reason || 'ไม่ระบุเหตุผล' }} +
+
+ คุณสามารถแก้ไขหลักสูตรและส่งขออนุมัติใหม่ได้ โดยการคืนสถานะเป็นแบบร่าง +
+
+ + + + + +
+
@@ -196,7 +230,8 @@ const stats = computed(() => ({ total: courses.value.length, approved: courses.value.filter(c => c.status === 'APPROVED').length, pending: courses.value.filter(c => c.status === 'PENDING').length, - draft: courses.value.filter(c => c.status === 'DRAFT').length + draft: courses.value.filter(c => c.status === 'DRAFT').length, + rejected: courses.value.filter(c => c.status === 'REJECTED').length })); // Filtered courses @@ -296,6 +331,41 @@ const confirmDelete = (course: CourseResponse) => { }); }; +// Rejection Dialog +const rejectionDialog = ref(false); +const selectedRejectionCourse = ref(null); + +const handleViewDetails = (course: CourseResponse) => { + if (course.status === 'REJECTED') { + selectedRejectionCourse.value = course; + rejectionDialog.value = true; + } else { + navigateTo(`/instructor/courses/${course.id}`); + } +}; + +const returnToDraft = async () => { + if (!selectedRejectionCourse.value) return; + + try { + const response = await instructorService.setCourseDraft(selectedRejectionCourse.value.id); + $q.notify({ + type: 'positive', + message: response.message || 'คืนสถานะเป็นแบบร่างสำเร็จ', + position: 'top' + }); + rejectionDialog.value = false; + selectedRejectionCourse.value = null; + fetchCourses(); // Refresh list + } catch (error: any) { + $q.notify({ + type: 'negative', + message: error.data?.message || 'ไม่สามารถคืนสถานะได้', + position: 'top' + }); + } +}; + // Lifecycle onMounted(() => { fetchCourses(); diff --git a/frontend_management/services/admin.service.ts b/frontend_management/services/admin.service.ts index 507f17c1..473c6ed9 100644 --- a/frontend_management/services/admin.service.ts +++ b/frontend_management/services/admin.service.ts @@ -254,6 +254,57 @@ export interface AuditLogStats { recentActivity: AuditLog[]; } +export interface RecommendedCourse { + id: number; + title: { + th: string; + en: string; + }; + slug: string; + description: { + th: string; + en: string; + }; + thumbnail_url: string | null; + price: number; + is_free: boolean; + have_certificate: boolean; + is_recommended: boolean; + status: string; + created_at: string; + updated_at: string; + category: { + id: number; + name: { + th: string; + en: string; + }; + }; + instructors: { + user_id: number; + is_primary: boolean; + user: { + id: number; + username: string; + email: string; + }; + }[]; + creator: { + id: number; + username: string; + email: string; + }; + chapters_count: number; + lessons_count: number; +} + +export interface RecommendedCoursesListResponse { + code: number; + message: string; + data: RecommendedCourse[]; + total: number; +} + // Helper function to get auth token from cookie const getAuthToken = (): string => { const tokenCookie = useCookie('token'); @@ -517,6 +568,48 @@ export const adminService = { headers: { Authorization: `Bearer ${token}` }, query: { days } }); + return response; + }, + + // ============ Recommended Courses ============ + async getRecommendedCourses(): Promise { + const config = useRuntimeConfig(); + const token = getAuthToken(); + const response = await $fetch('/api/admin/recommended-courses', { + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + } + }); + + return response.data; + }, + + async getRecommendedCourseById(id: number): Promise { + const config = useRuntimeConfig(); + const token = getAuthToken(); + const response = await $fetch>(`/api/admin/recommended-courses/${id}`, { + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + } + }); + + return response.data; + }, + + async toggleCourseRecommendation(courseId: number, isRecommended: boolean): Promise> { + const config = useRuntimeConfig(); + const token = getAuthToken(); + const response = await $fetch>(`/api/admin/recommended-courses/${courseId}/toggle`, { + method: 'PUT', + baseURL: config.public.apiBaseUrl as string, + headers: { + Authorization: `Bearer ${token}` + }, + query: { is_recommended: isRecommended } + }); + return response; } }; diff --git a/frontend_management/services/instructor.service.ts b/frontend_management/services/instructor.service.ts index be75eb83..1109816e 100644 --- a/frontend_management/services/instructor.service.ts +++ b/frontend_management/services/instructor.service.ts @@ -301,6 +301,10 @@ export const instructorService = { return await authRequest>(`/api/instructors/courses/send-review/${courseId}`, { method: 'POST' }); }, + async setCourseDraft(courseId: number): Promise> { + return await authRequest>(`/api/instructors/courses/set-draft/${courseId}`, { method: 'POST' }); + }, + async getEnrolledStudents( courseId: number, page: number = 1,