feat: Introduce core admin and instructor dashboards with dedicated services, pages, and layouts.

This commit is contained in:
Missez 2026-02-13 11:55:12 +07:00
parent af14610442
commit 5442f1beb6
7 changed files with 497 additions and 7 deletions

View file

@ -63,7 +63,7 @@
<div class="mb-6">
<q-input
v-model="form.description.th"
label="คำอธิบาย (ภาษาไทย)"
label="คำอธิบาย (ภาษาไทย) *"
type="textarea"
outlined
autogrow
@ -74,7 +74,7 @@
<div class="mb-6">
<q-input
v-model="form.description.en"
label="คำอธิบาย (English)"
label="คำอธิบาย (English) *"
type="textarea"
outlined
autogrow

View file

@ -14,7 +14,7 @@
</div>
<!-- Stats Cards -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div class="grid grid-cols-2 md:grid-cols-5 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>
@ -31,6 +31,10 @@
<div class="text-3xl font-bold text-gray-500">{{ stats.draft }}</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-red-600">{{ stats.rejected }}</div>
<div class="text-gray-500 text-sm mt-1">กปฏเสธ</div>
</div>
</div>
<!-- Filter Bar -->
@ -126,7 +130,7 @@
dense
icon="visibility"
color="grey"
@click="navigateTo(`/instructor/courses/${course.id}`)"
@click="handleViewDetails(course)"
>
<q-tooltip>รายละเอยด</q-tooltip>
</q-btn>
@ -162,6 +166,36 @@
</div>
</div>
</div>
<!-- Rejection Details Dialog -->
<q-dialog v-model="rejectionDialog">
<q-card style="min-width: 400px">
<q-card-section class="row items-center q-pb-none">
<div class="text-h6 text-red">หลกสตรถกปฏเสธ</div>
<q-space />
<q-btn icon="close" flat round dense v-close-popup />
</q-card-section>
<q-card-section>
<div class="text-subtitle1 font-bold mb-2">เหตผลการปฏเสธ:</div>
<div class="bg-red-50 p-4 rounded-lg text-red-800 border border-red-100">
{{ selectedRejectionCourse?.rejection_reason || 'ไม่ระบุเหตุผล' }}
</div>
<div class="text-gray-500 text-sm mt-4">
ณสามารถแกไขหลกสตรและสงขออนใหมได โดยการคนสถานะเปนแบบราง
</div>
</q-card-section>
<q-card-actions align="right" class="text-primary q-pt-none q-pb-md q-px-md">
<q-btn flat label="ยกเลิก" v-close-popup color="grey" />
<q-btn
label="คืนสถานะเป็นแบบร่าง"
color="primary"
@click="returnToDraft"
/>
</q-card-actions>
</q-card>
</q-dialog>
</div>
</template>
@ -196,7 +230,8 @@ 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
draft: courses.value.filter(c => c.status === 'DRAFT').length,
rejected: courses.value.filter(c => c.status === 'REJECTED').length
}));
// Filtered courses
@ -296,6 +331,41 @@ const confirmDelete = (course: CourseResponse) => {
});
};
// Rejection Dialog
const rejectionDialog = ref(false);
const selectedRejectionCourse = ref<CourseResponse | null>(null);
const handleViewDetails = (course: CourseResponse) => {
if (course.status === 'REJECTED') {
selectedRejectionCourse.value = course;
rejectionDialog.value = true;
} else {
navigateTo(`/instructor/courses/${course.id}`);
}
};
const returnToDraft = async () => {
if (!selectedRejectionCourse.value) return;
try {
const response = await instructorService.setCourseDraft(selectedRejectionCourse.value.id);
$q.notify({
type: 'positive',
message: response.message || 'คืนสถานะเป็นแบบร่างสำเร็จ',
position: 'top'
});
rejectionDialog.value = false;
selectedRejectionCourse.value = null;
fetchCourses(); // Refresh list
} catch (error: any) {
$q.notify({
type: 'negative',
message: error.data?.message || 'ไม่สามารถคืนสถานะได้',
position: 'top'
});
}
};
// Lifecycle
onMounted(() => {
fetchCourses();