feat: Implement instructor course management, including student progress tracking and course content views, alongside admin user management.

This commit is contained in:
Missez 2026-02-05 09:31:24 +07:00
parent c9381b9385
commit be5b9756be
11 changed files with 2116 additions and 1386 deletions

View file

@ -12,6 +12,7 @@ export interface AdminUserResponse {
th: string;
};
};
avatar_url?: string;
profile: {
prefix: {
en: string;

View file

@ -251,6 +251,13 @@ export const instructorService = {
);
},
async submitCourseForApproval(courseId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/${courseId}/submit`,
{ method: 'POST' }
);
},
async getCourseInstructors(courseId: number): Promise<CourseInstructorResponse[]> {
const response = await authRequest<InstructorsListResponse>(
`/api/instructors/courses/listinstructor/${courseId}`
@ -434,6 +441,35 @@ export const instructorService = {
);
},
// Quiz Scores
async getLessonQuizScores(
courseId: number,
lessonId: number,
page: number = 1,
limit: number = 10,
search?: string,
isPassed?: boolean
): Promise<QuizScoresResponse> {
let url = `/api/instructors/courses/${courseId}/lessons/${lessonId}/quiz/scores?page=${page}&limit=${limit}`;
if (search) {
url += `&search=${encodeURIComponent(search)}`;
}
if (isPassed !== undefined) {
url += `&isPassed=${isPassed}`;
}
return await authRequest<QuizScoresResponse>(url);
},
async getStudentQuizAttemptDetail(
courseId: number,
lessonId: number,
studentId: number
): Promise<QuizAttemptDetailResponse> {
return await authRequest<QuizAttemptDetailResponse>(
`/api/instructors/courses/${courseId}/lessons/${lessonId}/quiz/students/${studentId}`
);
},
// Video Upload
async uploadLessonVideo(courseId: number, chapterId: number, lessonId: number, video: File, attachments?: File[]): Promise<ApiResponse<LessonResponse>> {
const formData = new FormData();
@ -467,6 +503,19 @@ export const instructorService = {
);
},
async setLessonYoutubeVideo(courseId: number, chapterId: number, lessonId: number, youtubeVideoId: string, videoTitle: string): Promise<ApiResponse<LessonResponse>> {
return await authRequest<ApiResponse<LessonResponse>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/youtube-video`,
{
method: 'POST',
body: {
youtube_video_id: youtubeVideoId,
video_title: videoTitle
}
}
);
},
// Attachments
async addAttachments(courseId: number, chapterId: number, lessonId: number, files: File[]): Promise<ApiResponse<LessonResponse>> {
const formData = new FormData();
@ -719,6 +768,7 @@ export interface AnnouncementResponse {
is_pinned: boolean;
created_at: string;
updated_at: string;
published_at?: string;
attachments: AnnouncementAttachment[];
}
@ -740,6 +790,97 @@ export interface CreateAnnouncementRequest {
th: string;
en: string;
};
status?: 'DRAFT' | 'PUBLISHED';
is_pinned?: boolean;
status?: 'DRAFT' | 'PUBLISHED';
published_at?: string;
}
export interface QuizScoreStudentResponse {
user_id: number;
username: string;
email: string;
first_name: string;
last_name: string;
avatar_url: string | null;
latest_attempt: {
completed_at: string;
attempt_number: number;
is_passed: boolean;
total_score: number;
score: number;
id: number;
} | null;
best_score: number;
total_attempts: number;
is_passed: boolean;
}
export interface QuizScoresData {
students: QuizScoreStudentResponse[];
total_score: number;
passing_score: number;
quiz_title: {
th: string;
en: string;
};
quiz_id: number;
lesson_title: {
th: string;
en: string;
};
lesson_id: number;
}
export interface QuizScoresResponse {
code: number;
message: string;
data: QuizScoresData;
total: number;
page: number;
limit: number;
}
export interface QuizAnswerReview {
question_id: number;
question_text: {
th: string;
en: string;
};
sort_order: number;
selected_choice_id: number;
selected_choice_text: {
th: string;
en: string;
};
is_correct: boolean;
score: number;
question_score: number;
}
export interface QuizAttemptDetailData {
attempt_id: number;
quiz_id: number;
student: {
user_id: number;
username: string;
email: string;
first_name: string;
last_name: string;
};
score: number;
total_score: number;
total_questions: number;
correct_answers: number;
is_passed: boolean;
passing_score: number;
attempt_number: number;
started_at: string;
completed_at: string;
answers_review: QuizAnswerReview[];
}
export interface QuizAttemptDetailResponse {
code: number;
message: string;
data: QuizAttemptDetailData;
}