elearning/frontend_management/services/instructor.service.ts
Missez 031ca5c984
All checks were successful
Build and Deploy Frontend Management to Dev Server / Build Frontend Management Docker Image (push) Successful in 46s
Build and Deploy Frontend Management to Dev Server / Deploy E-learning Frontend Management to Dev Server (push) Successful in 6s
Build and Deploy Frontend Management to Dev Server / Notify Deployment Status (push) Successful in 1s
feat: Add initial e-learning frontend setup including admin and instructor services, layouts, and pages.
2026-02-24 09:25:02 +07:00

939 lines
29 KiB
TypeScript

// Course Response structure
export interface CourseResponse {
id: number;
category_id: number;
title: {
en: string;
th: string;
};
slug: string;
description: {
en: string;
th: string;
};
thumbnail_url: string | null;
price: string;
is_free: boolean;
have_certificate: boolean;
status: 'DRAFT' | 'PENDING' | 'APPROVED' | 'REJECTED';
approved_by: number | null;
approved_at: string | null;
rejection_reason: string | null;
created_at: string;
created_by: number;
updated_at: string;
updated_by: number | null;
}
export interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
export interface CoursesListResponse {
code: number;
message: string;
data: CourseResponse[];
total: number;
}
export interface CourseInstructorResponse {
id: number;
course_id: number;
user_id: number;
is_primary: boolean;
joined_at: string;
user: {
id: number;
username: string;
email: string;
role_id: number;
email_verified_at: string | null;
is_deactivated: boolean;
created_at: string;
updated_at: string;
avatar_url?: string;
};
}
export interface InstructorsListResponse {
code: number;
message: string;
data: CourseInstructorResponse[];
}
export interface SearchInstructorResult {
id: number;
username: string;
email: string;
profile: {
first_name: string;
last_name: string;
avatar_url: string | null;
};
}
export interface SearchInstructorsResponse {
code: number;
message: string;
data: SearchInstructorResult[];
}
export interface EnrolledStudentResponse {
user_id: number;
username: string;
email: string;
first_name: string;
last_name: string;
avatar_url: string | null;
enrolled_at: string;
progress_percentage: number;
status: 'ENROLLED' | 'COMPLETED' | 'DROPPED';
}
export interface EnrolledStudentsListResponse {
code: number;
message: string;
data: EnrolledStudentResponse[];
total: number;
page: number;
limit: number;
}
export interface StudentSearchResult {
user_id: number;
first_name: string;
last_name: string;
email: string;
}
export interface StudentSearchResponse {
code: number;
message: string;
data: StudentSearchResult[];
}
// Student Detail with Progress interfaces
export interface StudentDetailLesson {
lesson_id: number;
lesson_title: { th: string; en: string };
lesson_type: string;
sort_order: number;
is_completed: boolean;
completed_at: string | null;
video_progress_seconds: number;
video_duration_seconds: number;
video_progress_percentage: number;
last_watched_at: string | null;
}
export interface StudentDetailChapter {
chapter_id: number;
chapter_title: { th: string; en: string };
sort_order: number;
lessons: StudentDetailLesson[];
completed_lessons: number;
total_lessons: number;
}
export interface StudentDetailData {
student: {
user_id: number;
username: string;
email: string;
first_name: string;
last_name: string;
avatar_url: string | null;
};
enrollment: {
status: string;
progress_percentage: number;
enrolled_at: string;
};
chapters: StudentDetailChapter[];
total_completed_lessons: number;
total_lessons: number;
}
export interface StudentDetailResponse {
code: number;
message: string;
data: StudentDetailData;
}
// Helper function to get auth token from cookie
const getAuthToken = (): string => {
const tokenCookie = useCookie('token');
return tokenCookie.value || '';
};
// Helper function for making authenticated requests with auto refresh
const authRequest = async <T>(
url: string,
options: {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
body?: any;
} = {}
): Promise<T> => {
const config = useRuntimeConfig();
const makeRequest = async (token: string) => {
return await $fetch<T>(url, {
baseURL: config.public.apiBaseUrl as string,
method: options.method || 'GET',
headers: {
Authorization: `Bearer ${token}`
},
body: options.body
});
};
try {
const token = getAuthToken();
return await makeRequest(token);
} catch (error: any) {
// If 401, try to refresh token
if (error.response?.status === 401) {
const { handleUnauthorized } = await import('~/utils/authFetch');
const newToken = await handleUnauthorized();
if (newToken) {
return await makeRequest(newToken);
}
// Redirect to login
navigateTo('/login');
}
throw error;
}
};
export const instructorService = {
async getCourses(status?: string): Promise<CourseResponse[]> {
let url = '/api/instructors/courses';
if (status) {
url += `?status=${status}`;
}
const response = await authRequest<CoursesListResponse>(url);
return response.data;
},
async createCourse(data: CreateCourseRequest): Promise<ApiResponse<CourseResponse>> {
// Clean data - remove empty thumbnail_url
const cleanedData = { ...data };
if (!cleanedData.thumbnail_url) {
delete cleanedData.thumbnail_url;
}
const formData = new FormData();
formData.append('data', JSON.stringify(cleanedData));
return await authRequest<ApiResponse<CourseResponse>>('/api/instructors/courses', {
method: 'POST',
body: formData
});
},
async getCourseById(courseId: number): Promise<CourseDetailResponse> {
const response = await authRequest<{ code: number; data: CourseDetailResponse }>(`/api/instructors/courses/${courseId}`);
return response.data;
},
async updateCourse(courseId: number, data: CreateCourseRequest): Promise<ApiResponse<CourseResponse>> {
return await authRequest<ApiResponse<CourseResponse>>(`/api/instructors/courses/${courseId}`, {
method: 'PUT',
body: { data }
});
},
async uploadCourseThumbnail(courseId: number, file: File): Promise<ApiResponse<{ thumbnail_url: string }>> {
const formData = new FormData();
formData.append('file', file);
return await authRequest<ApiResponse<{ thumbnail_url: string }>>(
`/api/instructors/courses/${courseId}/thumbnail`,
{ method: 'POST', body: formData }
);
},
async submitCourseForApproval(courseId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/send-review/${courseId}`,
{ method: 'POST' }
);
},
async getCourseInstructors(courseId: number): Promise<CourseInstructorResponse[]> {
const response = await authRequest<InstructorsListResponse>(
`/api/instructors/courses/listinstructor/${courseId}`
);
return response.data;
},
async addInstructor(courseId: number, emailOrUsername: string): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/add-instructor/${courseId}/${encodeURIComponent(emailOrUsername)}`,
{ method: 'POST' }
);
},
async removeInstructor(courseId: number, userId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/remove-instructor/${courseId}/${userId}`,
{ method: 'DELETE' }
);
},
async setPrimaryInstructor(courseId: number, userId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/set-primary-instructor/${courseId}/${userId}`,
{ method: 'PUT' }
);
},
async searchInstructors(query: string, courseId: number): Promise<SearchInstructorResult[]> {
const response = await authRequest<SearchInstructorsResponse>(
`/api/instructors/courses/${courseId}/search-instructors?query=${encodeURIComponent(query)}`
);
return response.data;
},
async deleteCourse(courseId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(`/api/instructors/courses/${courseId}`, { method: 'DELETE' });
},
async sendForReview(courseId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(`/api/instructors/courses/send-review/${courseId}`, { method: 'POST' });
},
async setCourseDraft(courseId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(`/api/instructors/courses/set-draft/${courseId}`, { method: 'POST' });
},
async cloneCourse(courseId: number, titleTh: string, titleEn: string): Promise<ApiResponse<CourseResponse>> {
return await authRequest<ApiResponse<CourseResponse>>(`/api/instructors/courses/${courseId}/clone`, {
method: 'POST',
body: {
title: {
en: titleEn,
th: titleTh
}
}
});
},
async getEnrolledStudents(
courseId: number,
page: number = 1,
limit: number = 10,
search?: string,
status?: string
): Promise<EnrolledStudentsListResponse> {
let url = `/api/instructors/courses/${courseId}/students?page=${page}&limit=${limit}`;
if (search) {
url += `&search=${encodeURIComponent(search)}`;
}
if (status && status !== 'all') {
url += `&status=${status}`;
}
return await authRequest<EnrolledStudentsListResponse>(url);
},
async searchStudentsInCourse(courseId: number, query: string, limit: number = 5): Promise<StudentSearchResult[]> {
const response = await authRequest<StudentSearchResponse>(
`/api/instructors/courses/${courseId}/students/search?query=${encodeURIComponent(query)}&limit=${limit}`
);
return response.data;
},
async getStudentDetail(courseId: number, studentId: number): Promise<StudentDetailData> {
const response = await authRequest<StudentDetailResponse>(
`/api/instructors/courses/${courseId}/students/${studentId}`
);
return response.data;
},
async getChapters(courseId: number): Promise<ChapterResponse[]> {
// Get chapters from course detail endpoint
const response = await authRequest<{ code: number; data: { chapters: ChapterResponse[] } }>(
`/api/instructors/courses/${courseId}`
);
return response.data.chapters || [];
},
async createChapter(courseId: number, data: CreateChapterRequest): Promise<ApiResponse<ChapterResponse>> {
return await authRequest<ApiResponse<ChapterResponse>>(
`/api/instructors/courses/${courseId}/chapters`,
{ method: 'POST', body: data }
);
},
async updateChapter(courseId: number, chapterId: number, data: CreateChapterRequest): Promise<ApiResponse<ChapterResponse>> {
return await authRequest<ApiResponse<ChapterResponse>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}`,
{ method: 'PUT', body: data }
);
},
async deleteChapter(courseId: number, chapterId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}`,
{ method: 'DELETE' }
);
},
async reorderChapter(courseId: number, chapterId: number, sortOrder: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/reorder`,
{ method: 'PUT', body: { sort_order: sortOrder } }
);
},
// Lesson CRUD
async getLesson(courseId: number, chapterId: number, lessonId: number): Promise<LessonDetailResponse> {
const response = await authRequest<{ code: number; data: LessonDetailResponse }>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`
);
return response.data;
},
async createLesson(courseId: number, chapterId: number, data: CreateLessonRequest): Promise<ApiResponse<LessonResponse>> {
return await authRequest<ApiResponse<LessonResponse>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons`,
{ method: 'POST', body: data }
);
},
async updateLesson(courseId: number, chapterId: number, lessonId: number, data: UpdateLessonRequest): Promise<ApiResponse<LessonResponse>> {
return await authRequest<ApiResponse<LessonResponse>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`,
{ method: 'PUT', body: data }
);
},
async deleteLesson(courseId: number, chapterId: number, lessonId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`,
{ method: 'DELETE' }
);
},
async reorderLesson(courseId: number, chapterId: number, lessonId: number, sortOrder: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/reorder-lessons`,
{ method: 'PUT', body: { lesson_id: lessonId, sort_order: sortOrder } }
);
},
// Question CRUD
async createQuestion(courseId: number, chapterId: number, lessonId: number, data: CreateQuestionRequest): Promise<ApiResponse<QuizQuestionResponse>> {
return await authRequest<ApiResponse<QuizQuestionResponse>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions`,
{ method: 'POST', body: data }
);
},
async updateQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number, data: CreateQuestionRequest): Promise<ApiResponse<QuizQuestionResponse>> {
return await authRequest<ApiResponse<QuizQuestionResponse>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}`,
{ method: 'PUT', body: data }
);
},
async deleteQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}`,
{ method: 'DELETE' }
);
},
async reorderQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number, sortOrder: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}/reorder`,
{ method: 'PUT', body: { sort_order: sortOrder } }
);
},
// Quiz Settings
async updateQuizSettings(courseId: number, chapterId: number, lessonId: number, data: UpdateQuizSettingsRequest): Promise<ApiResponse<QuizResponse>> {
return await authRequest<ApiResponse<QuizResponse>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/quiz`,
{ method: 'PUT', body: data }
);
},
// 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();
formData.append('video', video);
if (attachments) {
attachments.forEach(file => {
formData.append('attachments', file);
});
}
return await authRequest<ApiResponse<LessonResponse>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/video`,
{ method: 'POST', body: formData }
);
},
async updateLessonVideo(courseId: number, chapterId: number, lessonId: number, video?: File, attachments?: File[]): Promise<ApiResponse<LessonResponse>> {
const formData = new FormData();
if (video) {
formData.append('video', video);
}
if (attachments) {
attachments.forEach(file => {
formData.append('attachments', file);
});
}
return await authRequest<ApiResponse<LessonResponse>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/video`,
{ method: 'PUT', body: formData }
);
},
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();
files.forEach(file => {
formData.append('attachment', file);
});
return await authRequest<ApiResponse<LessonResponse>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/attachments`,
{ method: 'POST', body: formData }
);
},
async deleteAttachment(courseId: number, chapterId: number, lessonId: number, attachmentId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/attachments/${attachmentId}`,
{ method: 'DELETE' }
);
},
// Announcements
async getAnnouncements(courseId: number): Promise<AnnouncementResponse[]> {
const response = await authRequest<AnnouncementsListResponse>(
`/api/instructors/courses/${courseId}/announcements`
);
return response.data;
},
async createAnnouncement(courseId: number, data: CreateAnnouncementRequest, files?: File[]): Promise<ApiResponse<AnnouncementResponse>> {
const formData = new FormData();
formData.append('data', JSON.stringify(data));
if (files && files.length > 0) {
files.forEach(file => {
formData.append('files', file);
});
}
return await authRequest<ApiResponse<AnnouncementResponse>>(
`/api/instructors/courses/${courseId}/announcements`,
{ method: 'POST', body: formData }
);
},
async updateAnnouncement(courseId: number, announcementId: number, data: CreateAnnouncementRequest): Promise<ApiResponse<AnnouncementResponse>> {
return await authRequest<ApiResponse<AnnouncementResponse>>(
`/api/instructors/courses/${courseId}/announcements/${announcementId}`,
{ method: 'PUT', body: data }
);
},
async deleteAnnouncement(courseId: number, announcementId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/${courseId}/announcements/${announcementId}`,
{ method: 'DELETE' }
);
},
async uploadAnnouncementAttachment(courseId: number, announcementId: number, file: File): Promise<ApiResponse<AnnouncementResponse>> {
const formData = new FormData();
formData.append('file', file);
return await authRequest<ApiResponse<AnnouncementResponse>>(
`/api/instructors/courses/${courseId}/announcements/${announcementId}/attachments`,
{ method: 'POST', body: formData }
);
},
async deleteAnnouncementAttachment(courseId: number, announcementId: number, attachmentId: number): Promise<ApiResponse<void>> {
return await authRequest<ApiResponse<void>>(
`/api/instructors/courses/${courseId}/announcements/${announcementId}/attachments/${attachmentId}`,
{ method: 'DELETE' }
);
},
async getCourseApprovalHistory(courseId: number): Promise<ApprovalHistory[]> {
const response = await authRequest<{
code: number;
message: string;
data: {
approval_history: ApprovalHistory[];
current_status: string;
course_title: { en: string; th: string };
course_id: number;
}
}>(`/api/instructors/courses/${courseId}/approval-history`);
return response.data.approval_history;
}
};
// Approval History Interface
export interface ApprovalHistory {
id: number;
action: string;
comment: string | null;
created_at: string;
submitter: {
id: number;
username: string;
email: string;
};
reviewer: {
id: number;
username: string;
email: string;
} | null;
}
// Create course request
export interface CreateCourseRequest {
category_id: number;
title: {
en: string;
th: string;
};
slug: string;
description: {
en: string;
th: string;
};
thumbnail_url?: string | null;
price: number;
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;
video_url: string | null;
attachments: AttachmentResponse[];
quiz: QuizResponse | null;
}
export interface QuizChoiceResponse {
id: number;
question_id: number;
text: { en: string; th: string };
is_correct: boolean;
sort_order: number;
}
export interface QuizQuestionResponse {
id: number;
quiz_id: number;
question: { en: string; th: string };
explanation: { en: string; th: string } | null;
question_type: 'MULTIPLE_CHOICE' | 'TRUE_FALSE';
score: number;
sort_order: number;
created_at: string;
updated_at: string;
choices: QuizChoiceResponse[];
}
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;
is_skippable: boolean;
allow_multiple_attempts: boolean;
created_at?: string;
updated_at?: string;
questions?: QuizQuestionResponse[];
}
export interface UpdateQuizSettingsRequest {
title: { th: string; en: string };
description: { th: string; en: string };
passing_score: number;
time_limit: number;
shuffle_questions: boolean;
shuffle_choices: boolean;
show_answers_after_completion: boolean;
is_skippable: boolean;
allow_multiple_attempts: boolean;
}
export interface CreateChapterRequest {
title: { th: string; en: string };
description: { th: string; en: string };
sort_order?: number;
is_published?: boolean;
}
export interface CreateChoiceRequest {
text: { th: string; en: string };
is_correct: boolean;
sort_order?: number;
}
export interface CreateQuestionRequest {
question: { th: string; en: string };
explanation?: { th: string; en: string } | null;
question_type: 'MULTIPLE_CHOICE' | 'TRUE_FALSE';
score?: number;
sort_order?: number;
choices: CreateChoiceRequest[];
}
export interface CreateLessonRequest {
title: { th: string; en: string };
content?: { th: string; en: string } | null;
type: 'VIDEO' | 'QUIZ';
sort_order?: number;
}
export interface UpdateLessonRequest {
title: { th: string; en: string };
content?: { th: string; en: string } | null;
prerequisite_lesson_ids?: number[] | null;
}
export interface AttachmentResponse {
id: number;
lesson_id: number;
file_name: string;
file_path: string;
file_size: number;
mime_type: string;
description: { th: string; en: string };
sort_order: number;
created_at: string;
}
export interface LessonDetailResponse extends LessonResponse {
attachments: AttachmentResponse[];
}
// Announcement interfaces
export interface AnnouncementAttachment {
id: number;
file_name: string;
file_path: string;
file_size: number;
mime_type: string;
}
export interface AnnouncementResponse {
id: number;
title: {
th: string;
en: string;
};
content: {
th: string;
en: string;
};
status: 'DRAFT' | 'PUBLISHED';
is_pinned: boolean;
created_at: string;
updated_at: string;
published_at?: string;
attachments: AnnouncementAttachment[];
}
export interface AnnouncementsListResponse {
code: number;
message: string;
data: AnnouncementResponse[];
total: number;
page: number;
limit: number;
}
export interface CreateAnnouncementRequest {
title: {
th: string;
en: string;
};
content: {
th: string;
en: string;
};
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;
}