feat: Implement initial e-learning platform frontend structure including dashboard, course management, authentication, and common UI components.
This commit is contained in:
parent
aceeb80d9a
commit
ad11c6b7c5
44 changed files with 720 additions and 578 deletions
|
|
@ -1,17 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
/**
|
||||
* @file announcements.vue
|
||||
* @description Page displaying system and course-related announcements.
|
||||
* Uses the default layout and requires authentication.
|
||||
* @description หน้าแสดงประกาศระบบและข่าวสารเกี่ยวกับคอร์สเรียน (Page displaying system and course-related announcements.)
|
||||
* ใช้เลย์เอาต์เริ่มต้นและต้องตรวจสอบสิทธิ์ (Uses the default layout and requires authentication.)
|
||||
*/
|
||||
|
||||
// Define page metadata: usage of 'default' layout and 'auth' middleware
|
||||
// ข้อมูล metadata ของหน้า: ใช้เลย์เอาต์ 'default' และระบบตรวจสอบสิทธิ์ 'auth' (Define page metadata: usage of 'default' layout and 'auth' middleware)
|
||||
definePageMeta({
|
||||
layout: 'default',
|
||||
middleware: 'auth'
|
||||
})
|
||||
|
||||
// Set page title for SEO
|
||||
// กำหนดชื่อหัวหน้าเว็บสำหรับ SEO (Set page title for SEO)
|
||||
useHead({
|
||||
title: 'ประกาศ - e-Learning'
|
||||
})
|
||||
|
|
@ -19,22 +19,22 @@ useHead({
|
|||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- Page Header -->
|
||||
<!-- ส่วนหัวหน้าเว็บ (Page Header) -->
|
||||
<h1 class="text-3xl font-black mb-10 text-slate-900 dark:text-white">ประกาศ</h1>
|
||||
|
||||
<!--
|
||||
Main Layout: 12-column Grid
|
||||
- Left Column (span-8): Main announcements content
|
||||
- Right Column (span-4): Categories/Filter sidebar
|
||||
โครงสร้างหลัก: กริด 12 คอลัมน์ (Main Layout: 12-column Grid)
|
||||
- คอลัมน์ซ้าย (span-8): เนื้อหาประกาศหลัก (Left Column: Main announcements content)
|
||||
- คอลัมน์ขวา (span-4): แถบหมวดหมู่/ตัวกรอง (Right Column: Categories/Filter sidebar)
|
||||
-->
|
||||
<div class="grid-12">
|
||||
|
||||
<!-- ==========================================
|
||||
MAIN CONTENT AREA (Left)
|
||||
พื้นที่เนื้อหาหลัก (ด้านซ้าย) (MAIN CONTENT AREA (Left))
|
||||
========================================== -->
|
||||
<div class="col-span-8">
|
||||
|
||||
<!-- Feature 1: Critical System Announcement -->
|
||||
<!-- หน้าที่ 1: ประกาศระบบสำคัญ (Feature 1: Critical System Announcement) -->
|
||||
<div class="card mb-6">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="status-pill status-warning">สำคัญ</span>
|
||||
|
|
@ -42,13 +42,13 @@ useHead({
|
|||
</div>
|
||||
<h2 class="text-xl font-bold mb-4 text-slate-900 dark:text-white">แจ้งปิดปรับปรุงระบบ</h2>
|
||||
<p class="mb-4">เราจะทำการปิดปรับปรุงระบบในวันที่ 25 ธ.ค. เวลา 02:00 - 04:00 น. ขออภัยในความไม่สะดวก</p>
|
||||
<!-- Attachment Block -->
|
||||
<!-- ส่วนแนบไฟล์ (Attachment Block) -->
|
||||
<div class="flex items-center gap-2 p-3 rounded" style="background: var(--neutral-50); border: 1px solid var(--border-color); width: fit-content;">
|
||||
<span>📎</span> <span class="text-sm font-bold">ตารางการปิดปรับปรุง.pdf</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Announcement: UX/UI Course Update -->
|
||||
<!-- ประกาศ: อัปเดตคอร์ส UX/UI (Announcement: UX/UI Course Update) -->
|
||||
<div class="card mb-4" style="border-left: 4px solid var(--primary);">
|
||||
<div class="flex justify-between items-start mb-2" style="flex-wrap: wrap; gap: 8px;">
|
||||
<div>
|
||||
|
|
@ -61,7 +61,7 @@ useHead({
|
|||
<NuxtLink to="/browse/discovery" class="text-sm" style="color: var(--primary);">ดูรายละเอียดคอร์ส</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- Announcement: Accessibility (WCAG) Material -->
|
||||
<!-- ประกาศ: เอกสารประกอบการเรียนด้านการเข้าถึงเว็บ (WCAG) (Announcement: Accessibility (WCAG) Material) -->
|
||||
<div class="card mb-4" style="border-left: 4px solid var(--success);">
|
||||
<div class="flex justify-between items-start mb-2" style="flex-wrap: wrap; gap: 8px;">
|
||||
<div>
|
||||
|
|
@ -71,13 +71,13 @@ useHead({
|
|||
<span class="text-sm text-slate-600 dark:text-slate-400">22 ธ.ค. 2024</span>
|
||||
</div>
|
||||
<p class="text-slate-700 dark:text-slate-300 mb-2">เราได้เพิ่มไฟล์ PDF สรุปเกณฑ์ WCAG 2.2 ในส่วนของเอกสารประกอบการเรียนแล้ว...</p>
|
||||
<!-- Small Attachment -->
|
||||
<!-- ไฟล์แนบแบบเล็ก (Small Attachment) -->
|
||||
<div class="flex items-center gap-2 p-2 rounded mt-2" style="background: var(--neutral-50); border: 1px dotted var(--border-color); width: fit-content;">
|
||||
<span>📄</span> <span class="text-xs">WCAG_2.2_Summary.pdf</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Announcement: React Course Update -->
|
||||
<!-- ประกาศ: อัปเดตคอร์ส React (Announcement: React Course Update) -->
|
||||
<div class="card mb-4" style="border-left: 4px solid var(--warning);">
|
||||
<div class="flex justify-between items-start mb-2" style="flex-wrap: wrap; gap: 8px;">
|
||||
<div>
|
||||
|
|
@ -90,7 +90,7 @@ useHead({
|
|||
<NuxtLink to="/classroom/learning" class="btn btn-secondary text-sm" style="width: fit-content;">เข้าสู่บทเรียน</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- Announcement: General New Course -->
|
||||
<!-- ประกาศ: คอร์สใหม่ทั่วไป (Announcement: General New Course) -->
|
||||
<div class="card mb-4">
|
||||
<div class="flex justify-between items-start mb-2" style="flex-wrap: wrap; gap: 8px;">
|
||||
<h3 class="font-bold text-slate-900 dark:text-white">คอร์สใหม่: Advanced Python</h3>
|
||||
|
|
@ -100,7 +100,7 @@ useHead({
|
|||
<a href="#" class="text-sm" style="color: var(--primary);">อ่านเพิ่มเติม</a>
|
||||
</div>
|
||||
|
||||
<!-- Announcement: Platform Update -->
|
||||
<!-- ประกาศ: อัปเดตแพลตฟอร์ม (Announcement: Platform Update) -->
|
||||
<div class="card mb-4">
|
||||
<div class="flex justify-between items-start mb-2" style="flex-wrap: wrap; gap: 8px;">
|
||||
<h3 class="font-bold text-slate-900 dark:text-white">ยินดีต้อนรับสู่ดีไซน์ใหม่!</h3>
|
||||
|
|
@ -112,24 +112,24 @@ useHead({
|
|||
</div>
|
||||
|
||||
<!-- ==========================================
|
||||
SIDEBAR (Right)
|
||||
Category Filters
|
||||
แถบด้านข้าง (ด้านขวา) (SIDEBAR (Right))
|
||||
ตัวกรองหมวดหมู่ (Category Filters)
|
||||
========================================== -->
|
||||
<div class="col-span-4">
|
||||
<div class="card">
|
||||
<h3 class="font-bold mb-4 text-slate-900 dark:text-white">หมวดหมู่</h3>
|
||||
<ul class="flex flex-col gap-2">
|
||||
<!-- Filter Option: All -->
|
||||
<!-- ตัวเลือกตัวกรอง: ทั้งหมด (Filter Option: All) -->
|
||||
<li class="flex justify-between items-center p-2 rounded cursor-pointer" style="background: var(--neutral-50);">
|
||||
<span>ทั้งหมด</span>
|
||||
<span class="text-muted">15</span>
|
||||
</li>
|
||||
<!-- Filter Option: System Updates -->
|
||||
<!-- ตัวเลือกตัวกรอง: อัปเดตระบบ (Filter Option: System Updates) -->
|
||||
<li class="flex justify-between items-center p-2 rounded cursor-pointer">
|
||||
<span>อัปเดตระบบ</span>
|
||||
<span class="text-muted">3</span>
|
||||
</li>
|
||||
<!-- Filter Option: Course News -->
|
||||
<!-- ตัวเลือกตัวกรอง: ข่าวสารคอร์ส (Filter Option: Course News) -->
|
||||
<li class="flex justify-between items-center p-2 rounded cursor-pointer">
|
||||
<span>ข่าวสารคอร์ส</span>
|
||||
<span class="text-muted">11</span>
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ const navigateToCategory = (catName: string) => {
|
|||
<div class="bg-[#F8F9FA] dark:bg-[#020617] min-h-screen font-inter p-4 md:p-8 transition-colors duration-300">
|
||||
<div class="max-w-[1400px] mx-auto grid grid-cols-1 xl:grid-cols-3 gap-8">
|
||||
|
||||
<!-- Left Column (Main Content) -->
|
||||
<!-- คอลัมน์ซ้าย (เนื้อหาหลัก) -->
|
||||
<div class="xl:col-span-2 space-y-6">
|
||||
|
||||
<!-- ป้ายต้อนรับ (Welcome Banner) -->
|
||||
|
|
@ -179,7 +179,7 @@ const navigateToCategory = (catName: string) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Continue Learning (เรียนต่อจากครั้งก่อน) -->
|
||||
<!-- ส่วนเรียนต่อจากครั้งก่อน (Continue Learning) -->
|
||||
<div class="bg-white dark:!bg-slate-900 rounded-[2rem] p-6 md:p-8 shadow-sm border border-slate-100 dark:border-slate-800 transition-colors">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-[1.35rem] font-bold text-slate-900 dark:text-white tracking-tight">{{ $t('dashboard.continueLearningTitle') }}</h2>
|
||||
|
|
@ -195,7 +195,7 @@ const navigateToCategory = (catName: string) => {
|
|||
|
||||
<div class="flex-1 w-full flex flex-col">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<!-- Category Badge -->
|
||||
<!-- ป้ายบอกหมวดหมู่ (Category Badge) -->
|
||||
<div v-if="heroCourse.category">
|
||||
<span class="bg-[#E9EFFD] dark:bg-blue-900/40 text-[#3B6BE8] dark:text-blue-400 px-3 py-1 rounded-full text-[11px] font-bold tracking-wide">{{ getLocalizedText(heroCourse.category) }}</span>
|
||||
</div>
|
||||
|
|
@ -208,7 +208,7 @@ const navigateToCategory = (catName: string) => {
|
|||
<h3 class="text-2xl font-bold text-slate-900 dark:text-white mb-2 leading-snug line-clamp-2">
|
||||
{{ getLocalizedText(heroCourse.title) || 'Advanced UI/UX Design มาสเตอร์คลาส' }}
|
||||
</h3>
|
||||
<!-- Removed Lesson Title/Number as per request -->
|
||||
<!-- ไม่แสดงเลขบทเรียนตามที่ได้รับคำขอมา (Removed Lesson Title/Number as per request) -->
|
||||
|
||||
<div class="mb-6 mt-4">
|
||||
<div class="flex justify-between text-[13px] font-bold mb-2">
|
||||
|
|
@ -245,11 +245,11 @@ const navigateToCategory = (catName: string) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column (Sidebar/Profile Content) -->
|
||||
<!-- คอลัมน์ขวา (แถบข้อมูลโปรไฟล์และคอร์สแนะนำ) -->
|
||||
<div class="xl:col-span-1 space-y-6">
|
||||
<!-- Profile Widget -->
|
||||
<!-- วิดเจ็ตโปรไฟล์ผู้ใช้ -->
|
||||
<div class="bg-white dark:!bg-slate-900 rounded-[2rem] p-8 shadow-sm border border-slate-100 dark:border-slate-800 text-center flex flex-col items-center relative overflow-hidden transition-colors">
|
||||
<!-- decorative bg -->
|
||||
<!-- พื้นหลังตกแต่ง (decorative bg) -->
|
||||
<div class="absolute top-0 left-0 right-0 h-24 bg-gradient-to-b from-[#F8FAFC] to-white dark:from-slate-800 dark:to-slate-900"></div>
|
||||
|
||||
<div class="relative z-10 w-24 h-24 rounded-full bg-white dark:bg-slate-800 mb-4 shadow-md flex items-center justify-center">
|
||||
|
|
@ -280,18 +280,18 @@ const navigateToCategory = (catName: string) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recommended Courses Widget -->
|
||||
<!-- วิดเจ็ตคอร์สแนะนำ -->
|
||||
<div v-if="recommendedCourses.length > 0" class="bg-white dark:!bg-slate-900 rounded-[2rem] p-6 shadow-sm border border-slate-100 dark:border-slate-800 transition-colors">
|
||||
<h2 class="text-[1.1rem] font-bold text-slate-900 dark:text-white mb-5 tracking-tight flex items-center justify-between">
|
||||
{{ $t('dashboard.recommendedCourses') }}
|
||||
</h2>
|
||||
<div class="flex flex-col gap-5">
|
||||
<div v-for="course in recommendedCourses.slice(0, 3)" :key="course.id" class="flex gap-4 group cursor-pointer transition-all" @click="navigateTo(`/browse/discovery?course_id=${course.id}`)">
|
||||
<!-- Thumbnail -->
|
||||
<!-- รูปหน้าปก (Thumbnail) -->
|
||||
<div class="w-24 h-[68px] rounded-xl overflow-hidden bg-slate-100 shrink-0 relative shadow-sm">
|
||||
<img :src="course.image" class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" />
|
||||
</div>
|
||||
<!-- Info -->
|
||||
<!-- ข้อมูลคอร์ส (Info) -->
|
||||
<div class="flex-1 flex flex-col justify-center min-w-0">
|
||||
<h3 class="font-bold text-[13px] text-slate-900 dark:text-white leading-snug line-clamp-2 mb-1.5 group-hover:text-[#3B6BE8] transition-colors pr-1">{{ getLocalizedText(course.title) }}</h3>
|
||||
<div class="flex items-center justify-between mt-auto">
|
||||
|
|
|
|||
|
|
@ -67,22 +67,22 @@ const showPassword = reactive({
|
|||
})
|
||||
|
||||
|
||||
// Rules have been moved to components
|
||||
// กฎต่างๆ ถูกย้ายไปที่คอมโพเนนต์แล้ว (Rules have been moved to components)
|
||||
|
||||
const fileInput = ref<HTMLInputElement | null>(null) // Used in view mode (outside component)
|
||||
const fileInput = ref<HTMLInputElement | null>(null) // ใช้ในโหมดมุมมอง (อยู่นอกคอมโพเนนต์) (Used in view mode (outside component))
|
||||
|
||||
const toggleEdit = (edit: boolean) => {
|
||||
isEditing.value = edit
|
||||
}
|
||||
|
||||
// Updated to accept File object directly (or Event for view mode compatibility if needed)
|
||||
// อัปเดตให้รับออบเจ็กต์ File ได้โดยตรง (หรือ Event สำหรับความเข้ากันได้ของโหมดมุมมองหากจำเป็น) (Updated to accept File object directly (or Event for view mode compatibility if needed))
|
||||
const handleFileUpload = async (fileOrEvent: File | Event) => {
|
||||
let file: File | null = null
|
||||
|
||||
if (fileOrEvent instanceof File) {
|
||||
file = fileOrEvent
|
||||
} else {
|
||||
// Fallback for native input change event
|
||||
// การทำงานสำรองสำหรับอีเวนต์ input change แบบเนทีฟ (Fallback for native input change event)
|
||||
const target = (fileOrEvent as Event).target as HTMLInputElement
|
||||
if (target.files && target.files[0]) {
|
||||
file = target.files[0]
|
||||
|
|
@ -112,7 +112,7 @@ const handleFileUpload = async (fileOrEvent: File | Event) => {
|
|||
}
|
||||
}
|
||||
|
||||
// Trigger upload for VIEW mode avatar click
|
||||
// เรียกการอัปโหลดเมื่อคลิกที่รูปโปรไฟล์ในโหมด View (Trigger upload for VIEW mode avatar click)
|
||||
const triggerUpload = () => {
|
||||
fileInput.value?.click()
|
||||
}
|
||||
|
|
@ -191,7 +191,7 @@ const handleUpdatePassword = async () => {
|
|||
isPasswordSaving.value = false
|
||||
}
|
||||
|
||||
// Watch for changes in global user state (e.g. after avatar upload or profile update)
|
||||
// เฝ้าดูการเปลี่ยนแปลงในสถานะผู้ใช้ส่วนกลาง (เช่น หลังจากอัปโหลดรูปโปรไฟล์หรืออัปเดตข้อมูลส่วนตัว) (Watch for changes in global user state (e.g. after avatar upload or profile update))
|
||||
watch(() => currentUser.value, (newUser) => {
|
||||
if (newUser) {
|
||||
userData.value.photoURL = newUser.photoURL || ''
|
||||
|
|
@ -220,7 +220,7 @@ onMounted(async () => {
|
|||
<q-spinner size="3rem" color="primary" />
|
||||
</div>
|
||||
|
||||
<!-- MAIN PROFILE SETTINGS -->
|
||||
<!-- การตั้งค่าโปรไฟล์หลัก (MAIN PROFILE SETTINGS) -->
|
||||
<div v-else class="max-w-5xl mx-auto pb-20 fade-in pt-4">
|
||||
|
||||
<!-- บัตรข้อมูลโปรไฟล์ (Profile Card) -->
|
||||
|
|
@ -255,7 +255,7 @@ onMounted(async () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Inputs (2 Column Grid) -->
|
||||
<!-- ฟิลด์ข้อมูลฟอร์ม (แบ่งเป็น 2 คอลัมน์) (Form Inputs (2 Column Grid)) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-4">
|
||||
<div class="md:col-span-2 relative">
|
||||
<label class="block text-[13px] font-bold text-slate-700 dark:text-slate-300 mb-2">{{ $t('profile.prefix') }}</label>
|
||||
|
|
@ -300,7 +300,7 @@ onMounted(async () => {
|
|||
|
||||
</div>
|
||||
|
||||
<!-- Footer Buttons -->
|
||||
<!-- ปุ่มกดยืนยันต่างๆ (Footer Buttons) -->
|
||||
<div class="px-6 sm:px-8 py-5 border-t border-slate-200 dark:border-slate-800 flex flex-col sm:flex-row justify-center sm:justify-end gap-3 items-center bg-white dark:!bg-slate-900">
|
||||
<button class="w-full sm:w-auto text-[13px] font-bold text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white px-4 py-2 transition order-2 sm:order-1" @click="fetchUserProfile(true)">{{ $t('common.cancel') }}</button>
|
||||
<button @click="handleUpdateProfile" :disabled="isProfileSaving" class="w-full sm:w-auto bg-[#3B6BE8] hover:bg-blue-700 text-white px-6 py-2.5 rounded-lg text-[13px] font-bold transition shadow-sm disabled:opacity-50 order-1 sm:order-2">
|
||||
|
|
@ -310,7 +310,7 @@ onMounted(async () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security Card -->
|
||||
<!-- การ์ดความปลอดภัย (Security Card) -->
|
||||
<div class="bg-white dark:!bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-2xl shadow-sm overflow-hidden">
|
||||
<div class="p-8 border-b border-slate-200 dark:border-slate-800">
|
||||
<h2 class="text-xl font-bold text-slate-900 dark:text-white">{{ $t('profile.security') }}</h2>
|
||||
|
|
@ -337,7 +337,7 @@ onMounted(async () => {
|
|||
|
||||
</div>
|
||||
|
||||
<!-- Password Modal -->
|
||||
<!-- โมดอลเปลี่ยนรหัสผ่าน (Password Modal) -->
|
||||
<q-dialog v-model="showPasswordModal">
|
||||
<q-card class="w-full max-w-md rounded-2xl p-2 dark:bg-slate-900 shadow-xl">
|
||||
<q-form @submit="handleUpdatePassword">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue