elearning/frontend_management/pages/instructor/courses/index.vue

284 lines
9 KiB
Vue

<template>
<div>
<!-- Header -->
<div class="flex justify-between items-center mb-6">
<div>
<h1 class="text-2xl font-bold text-primary-600">หลกสตรของฉ</h1>
<p class="text-gray-600 mt-1">ดการหลกสตรทณสราง</p>
</div>
<q-btn
color="primary"
label="+ สร้างหลักสูตรใหม่"
@click="navigateTo('/instructor/courses/create')"
/>
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div class="bg-white rounded-xl shadow-sm p-5 text-center">
<div class="text-3xl font-bold text-primary-600">{{ stats.total }}</div>
<div class="text-gray-500 text-sm mt-1">หลกสตรทงหมด</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-5 text-center">
<div class="text-3xl font-bold text-green-600">{{ stats.approved }}</div>
<div class="text-gray-500 text-sm mt-1">เผยแพรแล</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-5 text-center">
<div class="text-3xl font-bold text-yellow-600">{{ stats.pending }}</div>
<div class="text-gray-500 text-sm mt-1">รอตรวจสอบ</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-5 text-center">
<div class="text-3xl font-bold text-gray-500">{{ stats.draft }}</div>
<div class="text-gray-500 text-sm mt-1">แบบราง</div>
</div>
</div>
<!-- Filter Bar -->
<div class="bg-white rounded-xl shadow-sm p-4 mb-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="md:col-span-2">
<q-input
v-model="searchQuery"
placeholder="ค้นหาหลักสูตร..."
outlined
dense
bg-color="grey-1"
>
<template v-slot:prepend>
<q-icon name="search" />
</template>
</q-input>
</div>
<q-select
v-model="filterStatus"
:options="statusOptions"
outlined
dense
emit-value
map-options
/>
</div>
</div>
<!-- Courses Grid -->
<div v-if="loading" class="flex justify-center py-10">
<q-spinner-dots size="50px" color="primary" />
</div>
<div v-else-if="filteredCourses.length === 0" class="bg-white rounded-xl shadow-sm p-10 text-center">
<q-icon name="school" size="60px" color="grey-5" class="mb-4" />
<p class="text-gray-500 text-lg">งไมหลกสตร</p>
<q-btn
color="primary"
label="สร้างหลักสูตรแรก"
class="mt-4"
@click="navigateTo('/instructor/courses/create')"
/>
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div
v-for="course in filteredCourses"
:key="course.id"
class="bg-white rounded-xl shadow-sm overflow-hidden hover:shadow-md transition-shadow"
>
<!-- Thumbnail -->
<div class="h-40 bg-gradient-to-br from-primary-400 to-primary-600 flex items-center justify-center">
<q-icon name="school" size="60px" color="white" />
</div>
<!-- Content -->
<div class="p-4">
<div class="flex justify-between items-start mb-2">
<h3 class="font-semibold text-gray-900 line-clamp-2">{{ course.title.th }}</h3>
<q-badge :color="getStatusColor(course.status)" class="ml-2">
{{ getStatusLabel(course.status) }}
</q-badge>
</div>
<p class="text-sm text-gray-500 line-clamp-2 mb-3">
{{ course.description.th }}
</p>
<div class="flex items-center justify-between text-sm">
<span class="font-semibold" :class="course.is_free ? 'text-green-600' : 'text-primary-600'">
{{ course.is_free ? 'ฟรี' : `฿${parseFloat(course.price).toLocaleString()}` }}
</span>
<span class="text-gray-400">
{{ formatDate(course.created_at) }}
</span>
</div>
</div>
<!-- Actions -->
<div class="border-t px-4 py-3 flex gap-2">
<q-btn
flat
dense
icon="visibility"
color="grey"
@click="navigateTo(`/instructor/courses/${course.id}`)"
>
<q-tooltip>รายละเอยด</q-tooltip>
</q-btn>
<q-btn
flat
dense
icon="edit"
color="primary"
@click="navigateTo(`/instructor/courses/${course.id}/edit`)"
>
<q-tooltip>แกไข</q-tooltip>
</q-btn>
<q-space />
<q-btn flat round dense icon="more_vert">
<q-menu>
<q-list style="min-width: 150px">
<q-item clickable v-close-popup @click="duplicateCourse(course)">
<q-item-section avatar>
<q-icon name="content_copy" />
</q-item-section>
<q-item-section>ทำสำเนา</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-close-popup @click="confirmDelete(course)">
<q-item-section avatar>
<q-icon name="delete" color="negative" />
</q-item-section>
<q-item-section class="text-negative">ลบ</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar';
import { instructorService, type CourseResponse } from '~/services/instructor.service';
definePageMeta({
layout: 'instructor',
middleware: ['auth']
});
const $q = useQuasar();
// Data
const courses = ref<CourseResponse[]>([]);
const loading = ref(true);
const searchQuery = ref('');
const filterStatus = ref<string | null>(null);
// Status options
const statusOptions = [
{ label: 'สถานะทั้งหมด', value: null },
{ label: 'เผยแพร่แล้ว', value: 'APPROVED' },
{ label: 'รอตรวจสอบ', value: 'PENDING' },
{ label: 'แบบร่าง', value: 'DRAFT' },
{ label: 'ถูกปฏิเสธ', value: 'REJECTED' }
];
// Stats
const stats = computed(() => ({
total: courses.value.length,
approved: courses.value.filter(c => c.status === 'APPROVED').length,
pending: courses.value.filter(c => c.status === 'PENDING').length,
draft: courses.value.filter(c => c.status === 'DRAFT').length
}));
// Filtered courses
const filteredCourses = computed(() => {
let result = courses.value;
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase();
result = result.filter(course =>
course.title.th.toLowerCase().includes(query) ||
course.title.en.toLowerCase().includes(query)
);
}
if (filterStatus.value) {
result = result.filter(course => course.status === filterStatus.value);
}
return result;
});
// Methods
const fetchCourses = async () => {
loading.value = true;
try {
courses.value = await instructorService.getCourses();
} catch (error) {
$q.notify({
type: 'negative',
message: 'ไม่สามารถโหลดข้อมูลหลักสูตรได้',
position: 'top'
});
} finally {
loading.value = false;
}
};
const getStatusColor = (status: string) => {
const colors: Record<string, string> = {
APPROVED: 'green',
PENDING: 'yellow',
DRAFT: 'grey',
REJECTED: 'red'
};
return colors[status] || 'grey';
};
const getStatusLabel = (status: string) => {
const labels: Record<string, string> = {
APPROVED: 'เผยแพร่แล้ว',
PENDING: 'รอตรวจสอบ',
DRAFT: 'แบบร่าง',
REJECTED: 'ถูกปฏิเสธ'
};
return labels[status] || status;
};
const formatDate = (date: string) => {
return new Date(date).toLocaleDateString('th-TH', {
day: 'numeric',
month: 'short',
year: '2-digit'
});
};
const duplicateCourse = (course: CourseResponse) => {
$q.notify({
type: 'info',
message: `กำลังทำสำเนา "${course.title.th}"...`,
position: 'top'
});
};
const confirmDelete = (course: CourseResponse) => {
$q.dialog({
title: 'ยืนยันการลบ',
message: `คุณต้องการลบหลักสูตร "${course.title.th}" หรือไม่?`,
cancel: true,
persistent: true
}).onOk(() => {
$q.notify({
type: 'positive',
message: 'ลบหลักสูตรสำเร็จ',
position: 'top'
});
});
};
// Lifecycle
onMounted(() => {
fetchCourses();
});
</script>