feat: Introduce admin pages for pending course review and course details, and instructor pages for course management and lesson quizzes.
All checks were successful
Build and Deploy Frontend Management to Dev Server / Build Frontend Management Docker Image (push) Successful in 33s
Build and Deploy Frontend Management to Dev Server / Deploy E-learning Frontend Management to Dev Server (push) Successful in 2s
Build and Deploy Frontend Management to Dev Server / Notify Deployment Status (push) Successful in 1s

This commit is contained in:
Missez 2026-02-10 15:24:19 +07:00
parent ff91df2bd6
commit 941b195813
6 changed files with 388 additions and 27 deletions

View file

@ -323,7 +323,7 @@ const getLessonIcon = (type: string) => {
const getLessonTypeColor = (type: string) => {
const colors: Record<string, string> = {
VIDEO: 'blue',
QUIZ: 'purple',
QUIZ: 'orange',
DOCUMENT: 'teal'
};
return colors[type] || 'grey';

View file

@ -3,14 +3,16 @@
<!-- Header -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-primary-600">คอรสรออน</h1>
<q-btn
outline
color="primary"
label="รีเฟรช"
icon="refresh"
:loading="loading"
@click="fetchPendingCourses"
/>
<div class="flex gap-4">
<q-btn
outline
color="primary"
label="รีเฟรช"
icon="refresh"
:loading="loading"
@click="fetchPendingCourses"
/>
</div>
</div>
<!-- Stats Cards -->
@ -47,6 +49,17 @@
</q-input>
</div>
<div class="flex justify-end mb-6">
<q-btn-toggle
v-model="viewMode"
toggle-color="primary"
:options="[
{ label: 'การ์ด', value: 'card' },
{ label: 'ตาราง', value: 'table' }
]"
/>
</div>
<!-- Pending Courses List -->
<div v-if="loading" class="flex justify-center py-12">
<q-spinner color="primary" size="48px" />
@ -57,7 +70,8 @@
<p class="text-gray-500 mt-4">ไมคอรสทรอการอน</p>
</div>
<div v-else class="space-y-4">
<!-- Card View -->
<div v-else-if="viewMode === 'card'" class="space-y-4">
<div
v-for="course in filteredCourses"
:key="course.id"
@ -125,6 +139,78 @@
</div>
</div>
</div>
<!-- Table View -->
<div v-else class="bg-white rounded-xl shadow-sm overflow-hidden">
<q-table
:rows="filteredCourses"
:columns="columns"
row-key="id"
flat
bordered
:pagination="{ rowsPerPage: 10 }"
>
<!-- Thumbnail Slot -->
<template v-slot:body-cell-thumbnail="props">
<q-td :props="props">
<div class="w-16 h-10 rounded overflow-hidden bg-gray-100">
<img
v-if="props.row.thumbnail_url"
:src="props.row.thumbnail_url"
class="w-full h-full object-cover"
/>
<div v-else class="w-full h-full flex items-center justify-center">
<q-icon name="school" color="grey" />
</div>
</div>
</q-td>
</template>
<!-- Title Slot -->
<template v-slot:body-cell-title="props">
<q-td :props="props">
<div class="font-semibold">{{ props.row.title.th }}</div>
<div class="text-xs text-gray-500">{{ props.row.title.en }}</div>
</q-td>
</template>
<!-- Stats Slot -->
<template v-slot:body-cell-stats="props">
<q-td :props="props">
<div class="text-xs">
<div>{{ props.row.chapters_count }} บท</div>
<div>{{ props.row.lessons_count }} บทเรยน</div>
</div>
</q-td>
</template>
<!-- Submission Slot -->
<template v-slot:body-cell-submitted_at="props">
<q-td :props="props">
<div v-if="props.row.latest_submission">
<div class="text-xs">{{ formatDate(props.row.latest_submission.created_at) }}</div>
<div class="text-xs text-gray-500">โดย {{ props.row.latest_submission.submitter.username }}</div>
</div>
<span v-else>-</span>
</q-td>
</template>
<!-- Actions Slot -->
<template v-slot:body-cell-actions="props">
<q-td :props="props">
<q-btn
flat
round
color="primary"
icon="visibility"
@click="viewCourse(props.row)"
>
<q-tooltip>รายละเอยด</q-tooltip>
</q-btn>
</q-td>
</template>
</q-table>
</div>
</div>
</template>
@ -144,6 +230,16 @@ const router = useRouter();
const courses = ref<PendingCourse[]>([]);
const loading = ref(true);
const searchQuery = ref('');
const viewMode = ref('table');
const columns = [
{ name: 'thumbnail', label: 'รูปปก', field: 'thumbnail', align: 'left' },
{ name: 'title', label: 'ชื่อคอร์ส', field: (row: PendingCourse) => row.title.th, align: 'left', sortable: true },
{ name: 'instructor', label: 'ผู้สอน', field: (row: PendingCourse) => getPrimaryInstructor(row), align: 'left', sortable: true },
{ name: 'stats', label: 'จำนวนบท', field: 'stats', align: 'center' },
{ name: 'submitted_at', label: 'วันที่ส่ง', field: (row: PendingCourse) => row.latest_submission?.created_at, align: 'left', sortable: true },
{ name: 'actions', label: '', field: 'actions', align: 'center' }
];
// Computed
const totalChapters = computed(() =>