feat: Add initial frontend setup including authentication, instructor, and admin course management modules.

This commit is contained in:
Missez 2026-01-28 13:38:54 +07:00
parent 9fd217e1db
commit 19844f343b
16 changed files with 2065 additions and 293 deletions

View file

@ -504,6 +504,48 @@ export const instructorService = {
);
},
async reorderQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number, sortOrder: number): Promise<void> {
const config = useRuntimeConfig();
const useMockData = config.public.useMockData as boolean;
if (useMockData) {
await new Promise(resolve => setTimeout(resolve, 300));
return;
}
await authRequest(
`/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<QuizResponse> {
const config = useRuntimeConfig();
const useMockData = config.public.useMockData as boolean;
if (useMockData) {
await new Promise(resolve => setTimeout(resolve, 500));
return {
id: 1,
lesson_id: lessonId,
title: data.title,
description: data.description,
passing_score: data.passing_score,
time_limit: data.time_limit,
shuffle_questions: data.shuffle_questions,
shuffle_choices: data.shuffle_choices,
show_answers_after_completion: data.show_answers_after_completion
};
}
const response = await authRequest<{ code: number; data: QuizResponse }>(
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/quiz`,
{ method: 'PUT', body: data }
);
return response.data;
},
// Video Upload
async uploadLessonVideo(courseId: number, chapterId: number, lessonId: number, video: File, attachments?: File[]): Promise<LessonResponse> {
const config = useRuntimeConfig();
@ -567,7 +609,7 @@ export const instructorService = {
const formData = new FormData();
files.forEach(file => {
formData.append('attachments', file);
formData.append('attachment', file);
});
const response = await authRequest<{ code: number; data: LessonResponse }>(
@ -590,6 +632,130 @@ export const instructorService = {
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/attachments/${attachmentId}`,
{ method: 'DELETE' }
);
},
// Announcements
async getAnnouncements(courseId: number): Promise<AnnouncementResponse[]> {
const config = useRuntimeConfig();
const useMockData = config.public.useMockData as boolean;
if (useMockData) {
await new Promise(resolve => setTimeout(resolve, 500));
return MOCK_ANNOUNCEMENTS;
}
const response = await authRequest<AnnouncementsListResponse>(
`/api/instructors/courses/${courseId}/announcements`
);
return response.data;
},
async createAnnouncement(courseId: number, data: CreateAnnouncementRequest, files?: File[]): Promise<AnnouncementResponse> {
const config = useRuntimeConfig();
const useMockData = config.public.useMockData as boolean;
if (useMockData) {
await new Promise(resolve => setTimeout(resolve, 500));
return {
id: Date.now(),
title: data.title,
content: data.content,
status: data.status || 'DRAFT',
is_pinned: data.is_pinned || false,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
attachments: []
};
}
const formData = new FormData();
formData.append('data', JSON.stringify(data));
if (files && files.length > 0) {
files.forEach(file => {
formData.append('files', file);
});
}
const response = await authRequest<{ code: number; data: AnnouncementResponse }>(
`/api/instructors/courses/${courseId}/announcements`,
{ method: 'POST', body: formData }
);
return response.data;
},
async updateAnnouncement(courseId: number, announcementId: number, data: CreateAnnouncementRequest): Promise<AnnouncementResponse> {
const config = useRuntimeConfig();
const useMockData = config.public.useMockData as boolean;
if (useMockData) {
await new Promise(resolve => setTimeout(resolve, 500));
return {
id: announcementId,
title: data.title,
content: data.content,
status: data.status || 'DRAFT',
is_pinned: data.is_pinned || false,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
attachments: []
};
}
const response = await authRequest<{ code: number; data: AnnouncementResponse }>(
`/api/instructors/courses/${courseId}/announcements/${announcementId}`,
{ method: 'PUT', body: data }
);
return response.data;
},
async deleteAnnouncement(courseId: number, announcementId: number): Promise<void> {
const config = useRuntimeConfig();
const useMockData = config.public.useMockData as boolean;
if (useMockData) {
await new Promise(resolve => setTimeout(resolve, 500));
return;
}
await authRequest(
`/api/instructors/courses/${courseId}/announcements/${announcementId}`,
{ method: 'DELETE' }
);
},
async uploadAnnouncementAttachment(courseId: number, announcementId: number, file: File): Promise<AnnouncementResponse> {
const config = useRuntimeConfig();
const useMockData = config.public.useMockData as boolean;
if (useMockData) {
await new Promise(resolve => setTimeout(resolve, 500));
return MOCK_ANNOUNCEMENTS[0];
}
const formData = new FormData();
formData.append('file', file);
const response = await authRequest<{ code: number; data: AnnouncementResponse }>(
`/api/instructors/courses/${courseId}/announcements/${announcementId}/attachments`,
{ method: 'POST', body: formData }
);
return response.data;
},
async deleteAnnouncementAttachment(courseId: number, announcementId: number, attachmentId: number): Promise<void> {
const config = useRuntimeConfig();
const useMockData = config.public.useMockData as boolean;
if (useMockData) {
await new Promise(resolve => setTimeout(resolve, 500));
return;
}
await authRequest(
`/api/instructors/courses/${courseId}/announcements/${announcementId}/attachments/${attachmentId}`,
{ method: 'DELETE' }
);
}
};
@ -683,6 +849,16 @@ export interface QuizResponse {
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;
}
export interface CreateChapterRequest {
title: { th: string; en: string };
description: { th: string; en: string };
@ -732,6 +908,84 @@ 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;
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;
};
status?: 'DRAFT' | 'PUBLISHED';
is_pinned?: boolean;
}
// Mock announcements
const MOCK_ANNOUNCEMENTS: AnnouncementResponse[] = [
{
id: 1,
title: { th: 'ยินดีต้อนรับสู่คอร์ส', en: 'Welcome to the Course' },
content: {
th: 'ยินดีต้อนรับทุกคนสู่คอร์ส JavaScript Fundamentals! เราจะเริ่มเรียนในสัปดาห์หน้า',
en: 'Welcome everyone to JavaScript Fundamentals! We will start next week'
},
status: 'PUBLISHED',
is_pinned: true,
created_at: '2024-01-15T10:00:00Z',
updated_at: '2024-01-15T10:00:00Z',
attachments: []
},
{
id: 2,
title: { th: 'อัพเดทตารางเรียน', en: 'Schedule Update' },
content: {
th: 'มีการเปลี่ยนแปลงตารางเรียนสำหรับบทที่ 3',
en: 'There is a schedule change for Chapter 3'
},
status: 'PUBLISHED',
is_pinned: false,
created_at: '2024-01-20T14:00:00Z',
updated_at: '2024-01-20T14:00:00Z',
attachments: []
}
];
// Mock course detail
const MOCK_COURSE_DETAIL: CourseDetailResponse = {
...MOCK_COURSES[0],