Add tests result
|
|
@ -343,10 +343,12 @@ const save = async () => {
|
||||||
saving.value = true;
|
saving.value = true;
|
||||||
try {
|
try {
|
||||||
// Convert local datetime to ISO string to preserve timezone
|
// Convert local datetime to ISO string to preserve timezone
|
||||||
const payload = { ...form.value };
|
const payload: any = { ...form.value };
|
||||||
if (payload.published_at) {
|
if (payload.published_at) {
|
||||||
const localDate = new Date(payload.published_at.replace(' ', 'T'));
|
const localDate = new Date(payload.published_at.replace(' ', 'T'));
|
||||||
payload.published_at = localDate.toISOString();
|
payload.published_at = localDate.toISOString();
|
||||||
|
} else {
|
||||||
|
delete payload.published_at;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editing.value) {
|
if (editing.value) {
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,11 @@ export default defineConfig({
|
||||||
use: {
|
use: {
|
||||||
baseURL: 'http://localhost:3000/',// ปรับเป็น URL ที่ทดสอบ
|
baseURL: 'http://localhost:3000/',// ปรับเป็น URL ที่ทดสอบ
|
||||||
headless: false, // false = เห็น browser ขณะรัน
|
headless: false, // false = เห็น browser ขณะรัน
|
||||||
screenshot: 'only-on-failure', // เก็บ screenshot เมื่อ fail
|
screenshot: 'on', // เก็บ screenshot
|
||||||
trace: 'retain-on-failure', // เก็บ trace เพื่อดีบักเมื่อ fail
|
trace: 'retain-on-failure', // เก็บ trace เพื่อดีบักเมื่อ fail
|
||||||
// launchOptions: {
|
launchOptions: {
|
||||||
// slowMo: 1000,
|
slowMo: 500,
|
||||||
// }, // ช้าลง 10 วินาที
|
}, // ช้าลง 10 วินาที
|
||||||
},
|
},
|
||||||
|
|
||||||
/* ──── Setup Projects: login ครั้งเดียว แล้ว save cookies ──── */
|
/* ──── Setup Projects: login ครั้งเดียว แล้ว save cookies ──── */
|
||||||
|
|
|
||||||
|
|
@ -610,6 +610,19 @@ export const instructorService = {
|
||||||
{ method: 'DELETE' }
|
{ method: 'DELETE' }
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
async getMyStudentsStats(): Promise<{ total_students: number; total_completed: number }> {
|
||||||
|
const response = await authRequest<{
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
total_students: number;
|
||||||
|
total_completed: number;
|
||||||
|
}>('/api/instructors/courses/my-students');
|
||||||
|
return {
|
||||||
|
total_students: response.total_students,
|
||||||
|
total_completed: response.total_completed
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
async getCourseApprovalHistory(courseId: number): Promise<ApprovalHistory[]> {
|
async getCourseApprovalHistory(courseId: number): Promise<ApprovalHistory[]> {
|
||||||
const response = await authRequest<{
|
const response = await authRequest<{
|
||||||
code: number;
|
code: number;
|
||||||
|
|
|
||||||
|
|
@ -51,56 +51,16 @@ export const useInstructorStore = defineStore('instructor', {
|
||||||
async fetchDashboardData() {
|
async fetchDashboardData() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
// Fetch real courses from API
|
// Fetch courses and student stats in parallel
|
||||||
const courses = await instructorService.getCourses();
|
const [courses, studentStats] = await Promise.all([
|
||||||
|
instructorService.getCourses(),
|
||||||
|
instructorService.getMyStudentsStats()
|
||||||
|
]);
|
||||||
|
|
||||||
// Fetch student counts for each course
|
// Update student stats from dedicated API
|
||||||
let totalStudents = 0;
|
|
||||||
let completedStudents = 0;
|
|
||||||
const courseDetails: Course[] = [];
|
|
||||||
|
|
||||||
for (const course of courses.slice(0, 5)) {
|
|
||||||
try {
|
|
||||||
// Get student counts
|
|
||||||
const studentsResponse = await instructorService.getEnrolledStudents(course.id, 1, 1);
|
|
||||||
const courseStudents = studentsResponse.total || 0;
|
|
||||||
totalStudents += courseStudents;
|
|
||||||
|
|
||||||
// Get completed count from full list (if small) or estimate
|
|
||||||
if (courseStudents > 0 && courseStudents <= 100) {
|
|
||||||
const allStudents = await instructorService.getEnrolledStudents(course.id, 1, 100);
|
|
||||||
completedStudents += allStudents.data.filter(s => s.status === 'COMPLETED').length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get lesson count from course detail
|
|
||||||
const courseDetail = await instructorService.getCourseById(course.id);
|
|
||||||
const lessonCount = courseDetail.chapters.reduce((sum, ch) => sum + ch.lessons.length, 0);
|
|
||||||
|
|
||||||
courseDetails.push({
|
|
||||||
id: course.id,
|
|
||||||
title: course.title.th,
|
|
||||||
students: courseStudents,
|
|
||||||
lessons: lessonCount,
|
|
||||||
icon: 'book',
|
|
||||||
thumbnail: course.thumbnail_url || null
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
// Course might not have students endpoint
|
|
||||||
courseDetails.push({
|
|
||||||
id: course.id,
|
|
||||||
title: course.title.th,
|
|
||||||
students: 0,
|
|
||||||
lessons: 0,
|
|
||||||
icon: 'book',
|
|
||||||
thumbnail: course.thumbnail_url || null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update stats
|
|
||||||
this.stats.totalCourses = courses.length;
|
this.stats.totalCourses = courses.length;
|
||||||
this.stats.totalStudents = totalStudents;
|
this.stats.totalStudents = studentStats.total_students;
|
||||||
this.stats.completedStudents = completedStudents;
|
this.stats.completedStudents = studentStats.total_completed;
|
||||||
|
|
||||||
// Update course status counts
|
// Update course status counts
|
||||||
this.courseStatusCounts = {
|
this.courseStatusCounts = {
|
||||||
|
|
@ -110,8 +70,15 @@ export const useInstructorStore = defineStore('instructor', {
|
||||||
rejected: courses.filter(c => c.status === 'REJECTED').length
|
rejected: courses.filter(c => c.status === 'REJECTED').length
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update recent courses (first 3)
|
// Build recent courses list (first 3) from existing data
|
||||||
this.recentCourses = courseDetails.slice(0, 3);
|
this.recentCourses = courses.slice(0, 3).map(course => ({
|
||||||
|
id: course.id,
|
||||||
|
title: course.title.th,
|
||||||
|
students: 0,
|
||||||
|
lessons: 0,
|
||||||
|
icon: 'book',
|
||||||
|
thumbnail: course.thumbnail_url || null
|
||||||
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch dashboard data:', error);
|
console.error('Failed to fetch dashboard data:', error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 145 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 213 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 198 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 150 KiB |