feat: Add instructor course management pages for creating, listing, and viewing course details.
This commit is contained in:
parent
69eb60f901
commit
af7890cc8f
7 changed files with 229 additions and 28 deletions
|
|
@ -17,7 +17,7 @@
|
||||||
<div
|
<div
|
||||||
class="w-12 h-12 bg-primary-100 rounded-full flex items-center justify-center text-2xl cursor-pointer hover:bg-primary-200 transition"
|
class="w-12 h-12 bg-primary-100 rounded-full flex items-center justify-center text-2xl cursor-pointer hover:bg-primary-200 transition"
|
||||||
>
|
>
|
||||||
👨💼
|
<q-icon name="person" />
|
||||||
<q-menu>
|
<q-menu>
|
||||||
<q-list style="min-width: 200px">
|
<q-list style="min-width: 200px">
|
||||||
<!-- User Info Header -->
|
<!-- User Info Header -->
|
||||||
|
|
|
||||||
|
|
@ -230,7 +230,7 @@ const fetchCourse = async () => {
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
const colors: Record<string, string> = {
|
const colors: Record<string, string> = {
|
||||||
APPROVED: 'green',
|
APPROVED: 'green',
|
||||||
PENDING: 'yellow',
|
PENDING: 'orange',
|
||||||
DRAFT: 'grey',
|
DRAFT: 'grey',
|
||||||
REJECTED: 'red'
|
REJECTED: 'red'
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,13 @@
|
||||||
v-for="(lesson, lessonIndex) in chapter.lessons"
|
v-for="(lesson, lessonIndex) in chapter.lessons"
|
||||||
:key="lesson.id"
|
:key="lesson.id"
|
||||||
class="py-3"
|
class="py-3"
|
||||||
|
:class="{ 'opacity-50': draggedLesson?.id === lesson.id, 'bg-primary-50': dragOverLesson?.id === lesson.id }"
|
||||||
|
draggable="true"
|
||||||
|
@dragstart="onLessonDragStart(chapter, lesson, $event)"
|
||||||
|
@dragend="onLessonDragEnd"
|
||||||
|
@dragover.prevent="onLessonDragOver(chapter, lesson)"
|
||||||
|
@dragleave="onLessonDragLeave"
|
||||||
|
@drop.prevent="onLessonDrop(chapter, lesson)"
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-icon name="drag_indicator" class="cursor-move text-gray-300" />
|
<q-icon name="drag_indicator" class="cursor-move text-gray-300" />
|
||||||
|
|
@ -120,10 +127,10 @@
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>
|
<q-item-label>
|
||||||
{{ chapterIndex + 1 }}.{{ lessonIndex + 1 }} {{ lesson.title.th }}
|
{{ lesson.sort_order }}. {{ lesson.title.th }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label caption>
|
<q-item-label caption>
|
||||||
{{ getLessonTypeLabel(lesson.type) }} · {{ lesson.duration_minutes }} นาที
|
{{ getLessonTypeLabel(lesson.type) }}
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
|
|
@ -218,7 +225,6 @@
|
||||||
v-model="lessonForm.title.th"
|
v-model="lessonForm.title.th"
|
||||||
label="ชื่อบทเรียน (ภาษาไทย) *"
|
label="ชื่อบทเรียน (ภาษาไทย) *"
|
||||||
outlined
|
outlined
|
||||||
class="mb-4"
|
|
||||||
:rules="[val => !!val || 'กรุณากรอกชื่อบทเรียน']"
|
:rules="[val => !!val || 'กรุณากรอกชื่อบทเรียน']"
|
||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
|
|
@ -229,6 +235,7 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-select
|
<q-select
|
||||||
|
v-if="!editingLesson"
|
||||||
v-model="lessonForm.type"
|
v-model="lessonForm.type"
|
||||||
:options="lessonTypeOptions"
|
:options="lessonTypeOptions"
|
||||||
label="ประเภท *"
|
label="ประเภท *"
|
||||||
|
|
@ -238,18 +245,29 @@
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-input
|
<!-- <q-input
|
||||||
v-model.number="lessonForm.duration_minutes"
|
v-model.number="lessonForm.duration_minutes"
|
||||||
label="ระยะเวลา (นาที)"
|
label="ระยะเวลา (นาที)"
|
||||||
type="number"
|
type="number"
|
||||||
outlined
|
outlined
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
|
/> -->
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
v-if="lessonForm.type"
|
||||||
|
v-model="lessonForm.content.th"
|
||||||
|
label="เนื้อหา (ภาษาไทย)"
|
||||||
|
type="textarea"
|
||||||
|
outlined
|
||||||
|
autogrow
|
||||||
|
rows="3"
|
||||||
|
class="mb-4"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-if="lessonForm.type !== 'QUIZ'"
|
v-if="lessonForm.type"
|
||||||
v-model="lessonForm.content.th"
|
v-model="lessonForm.content.en"
|
||||||
label="เนื้อหา (ภาษาไทย)"
|
label="เนื้อหา (English)"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
outlined
|
outlined
|
||||||
autogrow
|
autogrow
|
||||||
|
|
@ -383,6 +401,57 @@ const onDrop = async (targetChapter: ChapterResponse) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Lesson Drag and Drop
|
||||||
|
const draggedLesson = ref<LessonResponse | null>(null);
|
||||||
|
const dragOverLesson = ref<LessonResponse | null>(null);
|
||||||
|
const draggedLessonChapter = ref<ChapterResponse | null>(null);
|
||||||
|
|
||||||
|
const onLessonDragStart = (chapter: ChapterResponse, lesson: LessonResponse, event: DragEvent) => {
|
||||||
|
draggedLesson.value = lesson;
|
||||||
|
draggedLessonChapter.value = chapter;
|
||||||
|
if (event.dataTransfer) {
|
||||||
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLessonDragEnd = () => {
|
||||||
|
draggedLesson.value = null;
|
||||||
|
dragOverLesson.value = null;
|
||||||
|
draggedLessonChapter.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLessonDragOver = (chapter: ChapterResponse, lesson: LessonResponse) => {
|
||||||
|
// Only allow drag within same chapter
|
||||||
|
if (draggedLesson.value && draggedLessonChapter.value?.id === chapter.id && draggedLesson.value.id !== lesson.id) {
|
||||||
|
dragOverLesson.value = lesson;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLessonDragLeave = () => {
|
||||||
|
dragOverLesson.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLessonDrop = async (chapter: ChapterResponse, targetLesson: LessonResponse) => {
|
||||||
|
if (!draggedLesson.value || !draggedLessonChapter.value || draggedLessonChapter.value.id !== chapter.id || draggedLesson.value.id === targetLesson.id) {
|
||||||
|
onLessonDragEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Insert at target position - backend will shift other lessons
|
||||||
|
const targetSortOrder = targetLesson.sort_order;
|
||||||
|
|
||||||
|
await instructorService.reorderLesson(courseId.value, chapter.id, draggedLesson.value.id, targetSortOrder);
|
||||||
|
|
||||||
|
$q.notify({ type: 'positive', message: 'เรียงลำดับบทเรียนสำเร็จ', position: 'top' });
|
||||||
|
fetchChapters();
|
||||||
|
} catch (error) {
|
||||||
|
$q.notify({ type: 'negative', message: 'ไม่สามารถเรียงลำดับได้', position: 'top' });
|
||||||
|
} finally {
|
||||||
|
onLessonDragEnd();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Forms
|
// Forms
|
||||||
const chapterForm = ref({
|
const chapterForm = ref({
|
||||||
title: { th: '', en: '' },
|
title: { th: '', en: '' },
|
||||||
|
|
@ -392,14 +461,12 @@ const chapterForm = ref({
|
||||||
const lessonForm = ref({
|
const lessonForm = ref({
|
||||||
title: { th: '', en: '' },
|
title: { th: '', en: '' },
|
||||||
content: { th: '', en: '' },
|
content: { th: '', en: '' },
|
||||||
type: 'VIDEO' as 'VIDEO' | 'QUIZ' | 'DOCUMENT',
|
type: 'VIDEO' as 'VIDEO' | 'QUIZ'
|
||||||
duration_minutes: 10
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const lessonTypeOptions = [
|
const lessonTypeOptions = [
|
||||||
{ label: 'วิดีโอ', value: 'VIDEO' },
|
{ label: 'วิดีโอ', value: 'VIDEO' },
|
||||||
{ label: 'แบบทดสอบ', value: 'QUIZ' },
|
{ label: 'แบบทดสอบ', value: 'QUIZ' },
|
||||||
{ label: 'เอกสาร', value: 'DOCUMENT' }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
|
|
@ -513,16 +580,14 @@ const openLessonDialog = (chapter: ChapterResponse, lesson?: LessonResponse) =>
|
||||||
lessonForm.value = {
|
lessonForm.value = {
|
||||||
title: { ...lesson.title },
|
title: { ...lesson.title },
|
||||||
content: lesson.content ? { ...lesson.content } : { th: '', en: '' },
|
content: lesson.content ? { ...lesson.content } : { th: '', en: '' },
|
||||||
type: lesson.type,
|
type: lesson.type as 'VIDEO' | 'QUIZ'
|
||||||
duration_minutes: lesson.duration_minutes
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
editingLesson.value = null;
|
editingLesson.value = null;
|
||||||
lessonForm.value = {
|
lessonForm.value = {
|
||||||
title: { th: '', en: '' },
|
title: { th: '', en: '' },
|
||||||
content: { th: '', en: '' },
|
content: { th: '', en: '' },
|
||||||
type: 'VIDEO',
|
type: 'VIDEO'
|
||||||
duration_minutes: 10
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
lessonDialog.value = true;
|
lessonDialog.value = true;
|
||||||
|
|
@ -531,11 +596,32 @@ const openLessonDialog = (chapter: ChapterResponse, lesson?: LessonResponse) =>
|
||||||
const saveLesson = async () => {
|
const saveLesson = async () => {
|
||||||
saving.value = true;
|
saving.value = true;
|
||||||
try {
|
try {
|
||||||
|
if (!selectedChapter.value) return;
|
||||||
|
|
||||||
if (editingLesson.value) {
|
if (editingLesson.value) {
|
||||||
// TODO: Call updateLesson API
|
// Update - don't send type
|
||||||
|
const updateData = {
|
||||||
|
title: lessonForm.value.title,
|
||||||
|
content: lessonForm.value.content
|
||||||
|
};
|
||||||
|
await instructorService.updateLesson(
|
||||||
|
courseId.value,
|
||||||
|
selectedChapter.value.id,
|
||||||
|
editingLesson.value.id,
|
||||||
|
updateData
|
||||||
|
);
|
||||||
$q.notify({ type: 'positive', message: 'แก้ไขบทเรียนสำเร็จ', position: 'top' });
|
$q.notify({ type: 'positive', message: 'แก้ไขบทเรียนสำเร็จ', position: 'top' });
|
||||||
} else {
|
} else {
|
||||||
// TODO: Call createLesson API
|
// Create - include type and sort_order
|
||||||
|
const createData = {
|
||||||
|
...lessonForm.value,
|
||||||
|
sort_order: (selectedChapter.value.lessons?.length || 0) + 1
|
||||||
|
};
|
||||||
|
await instructorService.createLesson(
|
||||||
|
courseId.value,
|
||||||
|
selectedChapter.value.id,
|
||||||
|
createData
|
||||||
|
);
|
||||||
$q.notify({ type: 'positive', message: 'เพิ่มบทเรียนสำเร็จ', position: 'top' });
|
$q.notify({ type: 'positive', message: 'เพิ่มบทเรียนสำเร็จ', position: 'top' });
|
||||||
}
|
}
|
||||||
lessonDialog.value = false;
|
lessonDialog.value = false;
|
||||||
|
|
@ -555,7 +641,7 @@ const confirmDeleteLesson = (chapter: ChapterResponse, lesson: LessonResponse) =
|
||||||
persistent: true
|
persistent: true
|
||||||
}).onOk(async () => {
|
}).onOk(async () => {
|
||||||
try {
|
try {
|
||||||
// TODO: Call deleteLesson API
|
await instructorService.deleteLesson(courseId.value, chapter.id, lesson.id);
|
||||||
$q.notify({ type: 'positive', message: 'ลบบทเรียนสำเร็จ', position: 'top' });
|
$q.notify({ type: 'positive', message: 'ลบบทเรียนสำเร็จ', position: 'top' });
|
||||||
fetchChapters();
|
fetchChapters();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ const categories = ref<CategoryResponse[]>([]);
|
||||||
|
|
||||||
// Form
|
// Form
|
||||||
const form = ref<CreateCourseRequest>({
|
const form = ref<CreateCourseRequest>({
|
||||||
category_id: 0,
|
category_id: 1,
|
||||||
title: { th: '', en: '' },
|
title: { th: '', en: '' },
|
||||||
slug: '',
|
slug: '',
|
||||||
description: { th: '', en: '' },
|
description: { th: '', en: '' },
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@
|
||||||
>
|
>
|
||||||
<q-tooltip>ดูรายละเอียด</q-tooltip>
|
<q-tooltip>ดูรายละเอียด</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-btn
|
<!-- <q-btn
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
icon="edit"
|
icon="edit"
|
||||||
|
|
@ -137,7 +137,7 @@
|
||||||
@click="navigateTo(`/instructor/courses/${course.id}/edit`)"
|
@click="navigateTo(`/instructor/courses/${course.id}/edit`)"
|
||||||
>
|
>
|
||||||
<q-tooltip>แก้ไข</q-tooltip>
|
<q-tooltip>แก้ไข</q-tooltip>
|
||||||
</q-btn>
|
</q-btn> -->
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-btn flat round dense icon="more_vert">
|
<q-btn flat round dense icon="more_vert">
|
||||||
<q-menu>
|
<q-menu>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<div
|
<div
|
||||||
class="w-12 h-12 bg-primary-100 rounded-full flex items-center justify-center text-2xl cursor-pointer hover:bg-primary-200 transition"
|
class="w-12 h-12 bg-primary-100 rounded-full flex items-center justify-center text-2xl cursor-pointer hover:bg-primary-200 transition"
|
||||||
>
|
>
|
||||||
👨🏫
|
<q-icon name="person" />
|
||||||
<q-menu>
|
<q-menu>
|
||||||
<q-list style="min-width: 200px">
|
<q-list style="min-width: 200px">
|
||||||
<!-- User Info Header -->
|
<!-- User Info Header -->
|
||||||
|
|
@ -83,7 +83,7 @@
|
||||||
<!-- Chart Placeholder -->
|
<!-- Chart Placeholder -->
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<h3 class="text-xl font-semibold mb-4">📊 สถิติผู้สมัครรวม (รายเดือน)</h3>
|
<h3 class="text-xl font-semibold mb-4"> สถิติผู้สมัครรวม (รายเดือน)</h3>
|
||||||
<div class="bg-gray-100 rounded-lg p-12 text-center text-gray-500">
|
<div class="bg-gray-100 rounded-lg p-12 text-center text-gray-500">
|
||||||
[กราฟแสดงสถิติผู้สมัครรวม (รายเดือน)]
|
[กราฟแสดงสถิติผู้สมัครรวม (รายเดือน)]
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -94,7 +94,7 @@
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<h3 class="text-xl font-semibold">📚 หลักสูตรล่าสุด</h3>
|
<h3 class="text-xl font-semibold"> หลักสูตร</h3>
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|
|
||||||
|
|
@ -258,10 +258,11 @@ export const instructorService = {
|
||||||
return MOCK_COURSE_DETAIL.chapters;
|
return MOCK_COURSE_DETAIL.chapters;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authRequest<{ code: number; data: ChapterResponse[]; total: number }>(
|
// Get chapters from course detail endpoint
|
||||||
`/api/instructors/courses/${courseId}/chapters`
|
const response = await authRequest<{ code: number; data: { chapters: ChapterResponse[] } }>(
|
||||||
|
`/api/instructors/courses/${courseId}`
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data.chapters || [];
|
||||||
},
|
},
|
||||||
|
|
||||||
async createChapter(courseId: number, data: CreateChapterRequest): Promise<ChapterResponse> {
|
async createChapter(courseId: number, data: CreateChapterRequest): Promise<ChapterResponse> {
|
||||||
|
|
@ -333,6 +334,92 @@ export const instructorService = {
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/reorder`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/reorder`,
|
||||||
{ method: 'PUT', body: { sort_order: sortOrder } }
|
{ method: 'PUT', body: { sort_order: sortOrder } }
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Lesson CRUD
|
||||||
|
async getLesson(courseId: number, chapterId: number, lessonId: number): Promise<LessonDetailResponse> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
|
if (useMockData) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
return MOCK_COURSE_DETAIL.chapters[0].lessons[0] as 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<LessonResponse> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
|
if (useMockData) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
return {
|
||||||
|
...MOCK_COURSE_DETAIL.chapters[0].lessons[0],
|
||||||
|
id: Date.now(),
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await authRequest<{ code: number; data: LessonResponse }>(
|
||||||
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons`,
|
||||||
|
{ method: 'POST', body: data }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateLesson(courseId: number, chapterId: number, lessonId: number, data: UpdateLessonRequest): Promise<LessonResponse> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const useMockData = config.public.useMockData as boolean;
|
||||||
|
|
||||||
|
if (useMockData) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
return {
|
||||||
|
...MOCK_COURSE_DETAIL.chapters[0].lessons[0],
|
||||||
|
id: lessonId,
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await authRequest<{ code: number; data: LessonResponse }>(
|
||||||
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`,
|
||||||
|
{ method: 'PUT', body: data }
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteLesson(courseId: number, chapterId: number, lessonId: 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}/chapters/${chapterId}/lessons/${lessonId}`,
|
||||||
|
{ method: 'DELETE' }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
async reorderLesson(courseId: number, chapterId: number, lessonId: 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/reorder`,
|
||||||
|
{ method: 'PUT', body: { lesson_id: lessonId, sort_order: sortOrder } }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -407,6 +494,34 @@ export interface CreateChapterRequest {
|
||||||
is_published?: boolean;
|
is_published?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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[];
|
||||||
|
}
|
||||||
|
|
||||||
// Mock course detail
|
// Mock course detail
|
||||||
const MOCK_COURSE_DETAIL: CourseDetailResponse = {
|
const MOCK_COURSE_DETAIL: CourseDetailResponse = {
|
||||||
...MOCK_COURSES[0],
|
...MOCK_COURSES[0],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue