feat: Implement initial frontend for admin and instructor roles, including dashboards, course management, authentication, and core services.

This commit is contained in:
Missez 2026-02-06 14:58:41 +07:00
parent 832a8f5067
commit 127b63de49
16 changed files with 1505 additions and 102 deletions

View file

@ -1,5 +1,6 @@
import { defineStore } from 'pinia';
import { authService } from '~/services/auth.service';
import { userService } from '~/services/user.service';
interface User {
id: string;
@ -123,6 +124,28 @@ export const useAuthStore = defineStore('auth', {
this.logout();
}
}
},
async fetchUserProfile() {
try {
const response = await userService.getProfile();
// Update local user state
this.user = {
id: response.id.toString(),
email: response.email,
firstName: response.profile.first_name,
lastName: response.profile.last_name,
role: response.role.code as 'INSTRUCTOR' | 'ADMIN' | 'STUDENT',
avatarUrl: response.profile.avatar_url
};
// Update user cookie to keep it in sync
const userCookie = useCookie('user');
userCookie.value = JSON.stringify(this.user);
} catch (error) {
console.error('Failed to fetch user profile:', error);
}
}
}
});

View file

@ -16,6 +16,13 @@ interface DashboardStats {
completedStudents: number;
}
interface CourseStatusCounts {
approved: number;
pending: number;
draft: number;
rejected: number;
}
export const useInstructorStore = defineStore('instructor', {
state: () => ({
stats: {
@ -24,6 +31,13 @@ export const useInstructorStore = defineStore('instructor', {
completedStudents: 0
} as DashboardStats,
courseStatusCounts: {
approved: 0,
pending: 0,
draft: 0,
rejected: 0
} as CourseStatusCounts,
recentCourses: [] as Course[],
loading: false
}),
@ -40,21 +54,64 @@ export const useInstructorStore = defineStore('instructor', {
// Fetch real courses from API
const courses = await instructorService.getCourses();
// Fetch student counts for each course
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;
// TODO: Get real student counts from API when available
this.stats.totalStudents = 0;
this.stats.completedStudents = 0;
this.stats.totalStudents = totalStudents;
this.stats.completedStudents = completedStudents;
// Map to recent courses format (take first 5)
this.recentCourses = courses.slice(0, 3).map((course, index) => ({
id: course.id,
title: course.title.th,
students: 0, // TODO: Get from API
lessons: 0, // TODO: Get from course detail API
icon: 'book',
thumbnail: course.thumbnail_url || null
}));
// Update course status counts
this.courseStatusCounts = {
approved: courses.filter(c => c.status === 'APPROVED').length,
pending: courses.filter(c => c.status === 'PENDING').length,
draft: courses.filter(c => c.status === 'DRAFT').length,
rejected: courses.filter(c => c.status === 'REJECTED').length
};
// Update recent courses (first 3)
this.recentCourses = courseDetails.slice(0, 3);
} catch (error) {
console.error('Failed to fetch dashboard data:', error);
} finally {