feat: Implement initial frontend for admin and instructor roles, including dashboards, course management, authentication, and core services.
This commit is contained in:
parent
832a8f5067
commit
127b63de49
16 changed files with 1505 additions and 102 deletions
|
|
@ -11,7 +11,8 @@
|
|||
<div class="flex flex-col md:flex-row gap-6">
|
||||
<!-- Thumbnail -->
|
||||
<div
|
||||
class="w-full md:w-48 h-32 bg-gray-100 rounded-lg flex items-center justify-center flex-shrink-0 relative group cursor-pointer overflow-hidden border border-gray-200"
|
||||
class="bg-gray-100 rounded-lg flex items-center justify-center flex-shrink-0 relative group cursor-pointer overflow-hidden border border-gray-200"
|
||||
style="width: 192px; height: 128px;"
|
||||
@click="triggerThumbnailUpload"
|
||||
>
|
||||
<img
|
||||
|
|
@ -73,7 +74,7 @@
|
|||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<q-icon name="people" size="20px" />
|
||||
<span>0 ผู้เรียน</span>
|
||||
<span>{{ totalStudentsCount }} ผู้เรียน</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -166,6 +167,7 @@ const course = ref<CourseDetailResponse | null>(null);
|
|||
const activeTab = ref('structure');
|
||||
const uploadingThumbnail = ref(false);
|
||||
const thumbnailInputRef = ref<HTMLInputElement | null>(null);
|
||||
const totalStudentsCount = ref(0);
|
||||
|
||||
// Computed
|
||||
const totalLessons = computed(() => {
|
||||
|
|
@ -178,7 +180,13 @@ const fetchCourse = async () => {
|
|||
loading.value = true;
|
||||
try {
|
||||
const courseId = parseInt(route.params.id as string);
|
||||
course.value = await instructorService.getCourseById(courseId);
|
||||
const [courseData, studentsData] = await Promise.all([
|
||||
instructorService.getCourseById(courseId),
|
||||
instructorService.getEnrolledStudents(courseId, 1, 1)
|
||||
]);
|
||||
|
||||
course.value = courseData;
|
||||
totalStudentsCount.value = studentsData.total;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch course:', error);
|
||||
$q.notify({ type: 'negative', message: 'ไม่สามารถโหลดข้อมูลหลักสูตรได้', position: 'top' });
|
||||
|
|
@ -187,6 +195,7 @@ const fetchCourse = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
const colors: Record<string, string> = {
|
||||
APPROVED: 'green',
|
||||
|
|
@ -238,8 +247,8 @@ const handleThumbnailUpload = async (event: Event) => {
|
|||
const requestApproval = async () => {
|
||||
if (!course.value) return;
|
||||
try {
|
||||
await instructorService.submitCourseForApproval(course.value.id);
|
||||
$q.notify({ type: 'positive', message: 'ส่งขออนุมัติสำเร็จ', position: 'top' });
|
||||
const response = await instructorService.submitCourseForApproval(course.value.id);
|
||||
$q.notify({ type: 'positive', message: response.message || 'ส่งขออนุมัติสำเร็จ', position: 'top' });
|
||||
fetchCourse();
|
||||
} catch (error: any) {
|
||||
$q.notify({
|
||||
|
|
|
|||
|
|
@ -86,12 +86,39 @@
|
|||
|
||||
<!-- Chart and Recent Courses -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Chart Placeholder -->
|
||||
<!-- Course Status Breakdown -->
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<h3 class="text-xl font-semibold mb-4"> สถิติผู้สมัครรวม (รายเดือน)</h3>
|
||||
<div class="bg-gray-100 rounded-lg p-12 text-center text-gray-500">
|
||||
[กราฟแสดงสถิติผู้สมัครรวม (รายเดือน)]
|
||||
<h3 class="text-xl font-semibold mb-4">สถานะหลักสูตร</h3>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between p-3 bg-green-50 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<q-icon name="check_circle" color="green" size="24px" />
|
||||
<span class="font-medium text-gray-700">เผยแพร่แล้ว</span>
|
||||
</div>
|
||||
<span class="text-2xl font-bold text-green-600">{{ instructorStore.courseStatusCounts.approved }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-3 bg-orange-50 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<q-icon name="hourglass_empty" color="orange" size="24px" />
|
||||
<span class="font-medium text-gray-700">รอตรวจสอบ</span>
|
||||
</div>
|
||||
<span class="text-2xl font-bold text-orange-600">{{ instructorStore.courseStatusCounts.pending }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<q-icon name="edit_note" color="grey" size="24px" />
|
||||
<span class="font-medium text-gray-700">แบบร่าง</span>
|
||||
</div>
|
||||
<span class="text-2xl font-bold text-gray-600">{{ instructorStore.courseStatusCounts.draft }}</span>
|
||||
</div>
|
||||
<div v-if="instructorStore.courseStatusCounts.rejected > 0" class="flex items-center justify-between p-3 bg-red-50 rounded-lg">
|
||||
<div class="flex items-center gap-3">
|
||||
<q-icon name="cancel" color="red" size="24px" />
|
||||
<span class="font-medium text-gray-700">ถูกปฏิเสธ</span>
|
||||
</div>
|
||||
<span class="text-2xl font-bold text-red-600">{{ instructorStore.courseStatusCounts.rejected }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
|
@ -170,5 +197,6 @@ const handleLogout = () => {
|
|||
// Fetch dashboard data on mount
|
||||
onMounted(() => {
|
||||
instructorStore.fetchDashboardData();
|
||||
authStore.fetchUserProfile();
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue