elearning/frontend_management/pages/admin/courses/pending.vue

218 lines
7.1 KiB
Vue

<template>
<div>
<!-- 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>
<!-- Stats Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div class="bg-white rounded-xl shadow-sm p-6 text-center">
<div class="text-4xl font-bold text-orange-500">{{ courses.length }}</div>
<div class="text-gray-500 mt-1">รอตรวจสอบ</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-6 text-center">
<div class="text-4xl font-bold text-gray-700">{{ totalChapters }}</div>
<div class="text-gray-500 mt-1">บททงหมด</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-6 text-center">
<div class="text-4xl font-bold text-gray-700">{{ totalLessons }}</div>
<div class="text-gray-500 mt-1">บทเรยนทงหมด</div>
</div>
</div>
<!-- Search -->
<div class="bg-white rounded-xl shadow-sm p-4 mb-6">
<q-input
v-model="searchQuery"
placeholder="ค้นหาชื่อคอร์ส, ผู้สอน..."
outlined
dense
bg-color="grey-1"
>
<template v-slot:prepend>
<q-icon name="search" />
</template>
<template v-slot:append v-if="searchQuery">
<q-icon name="close" class="cursor-pointer" @click="searchQuery = ''" />
</template>
</q-input>
</div>
<!-- Pending Courses List -->
<div v-if="loading" class="flex justify-center py-12">
<q-spinner color="primary" size="48px" />
</div>
<div v-else-if="filteredCourses.length === 0" class="bg-white rounded-xl shadow-sm p-12 text-center">
<q-icon name="pending_actions" size="64px" color="grey-4" />
<p class="text-gray-500 mt-4">ไมคอรสทรอการอน</p>
</div>
<div v-else class="space-y-4">
<div
v-for="course in filteredCourses"
:key="course.id"
class="bg-white rounded-xl shadow-sm overflow-hidden"
>
<div class="flex flex-col md:flex-row">
<!-- Thumbnail -->
<div class="md:w-48 h-32 md:h-auto bg-gray-100 flex-shrink-0">
<img
v-if="course.thumbnail_url"
:src="course.thumbnail_url"
:alt="course.title.th"
class="w-full h-full object-cover"
/>
<div v-else class="w-full h-full flex items-center justify-center">
<q-icon name="school" size="48px" color="grey-4" />
</div>
</div>
<!-- Course Info -->
<div class="flex-1 p-4">
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900">{{ course.title.th }}</h3>
<p class="text-sm text-gray-500 mt-1">{{ course.title.en }}</p>
<p class="text-gray-600 mt-2 line-clamp-2">{{ course.description.th }}</p>
<!-- Meta Info -->
<div class="flex flex-wrap items-center gap-4 mt-3">
<div class="flex items-center gap-1 text-sm text-gray-500">
<q-icon name="person" size="18px" />
<span>{{ getPrimaryInstructor(course) }}</span>
</div>
<div class="flex items-center gap-1 text-sm text-gray-500">
<q-icon name="folder" size="18px" />
<span>{{ course.chapters_count }} บท</span>
</div>
<div class="flex items-center gap-1 text-sm text-gray-500">
<q-icon name="play_circle" size="18px" />
<span>{{ course.lessons_count }} บทเรยน</span>
</div>
</div>
<!-- Submission Info -->
<div v-if="course.latest_submission" class="mt-3 text-sm text-gray-500">
<q-icon name="send" size="16px" class="mr-1" />
งโดย {{ course.latest_submission.submitter.username }}
เม {{ formatDate(course.latest_submission.created_at) }}
</div>
</div>
<!-- Actions -->
<div class="flex md:flex-col gap-2">
<q-btn
color="primary"
label="ดูรายละเอียด"
icon="visibility"
class="flex-1 md:flex-none"
@click="viewCourse(course)"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar';
import { adminService, type PendingCourse } from '~/services/admin.service';
definePageMeta({
layout: 'admin',
middleware: ['auth', 'admin']
});
const $q = useQuasar();
const router = useRouter();
// Data
const courses = ref<PendingCourse[]>([]);
const loading = ref(true);
const searchQuery = ref('');
// Computed
const totalChapters = computed(() =>
courses.value.reduce((sum, c) => sum + c.chapters_count, 0)
);
const totalLessons = computed(() =>
courses.value.reduce((sum, c) => sum + c.lessons_count, 0)
);
const filteredCourses = computed(() => {
if (!searchQuery.value) return courses.value;
const query = searchQuery.value.toLowerCase();
return courses.value.filter(course =>
course.title.th.toLowerCase().includes(query) ||
course.title.en.toLowerCase().includes(query) ||
course.creator.username.toLowerCase().includes(query) ||
course.creator.email.toLowerCase().includes(query)
);
});
// Methods
const fetchPendingCourses = async () => {
loading.value = true;
try {
courses.value = await adminService.getPendingCourses();
} catch (error) {
$q.notify({
type: 'negative',
message: 'ไม่สามารถโหลดข้อมูลคอร์สได้',
position: 'top'
});
} finally {
loading.value = false;
}
};
const getPrimaryInstructor = (course: PendingCourse) => {
const primary = course.instructors.find(i => i.is_primary);
return primary?.user.username || course.creator.username;
};
const formatDate = (date: string) => {
return new Date(date).toLocaleDateString('th-TH', {
day: 'numeric',
month: 'short',
year: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
};
const viewCourse = (course: PendingCourse) => {
router.push(`/admin/courses/${course.id}`);
};
// Lifecycle
onMounted(() => {
fetchPendingCourses();
});
</script>
<style scoped>
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>