feat: Implement initial e-learning platform frontend structure including dashboard, course management, authentication, and common UI components.

This commit is contained in:
supalerk-ar66 2026-02-27 10:05:33 +07:00
parent aceeb80d9a
commit ad11c6b7c5
44 changed files with 720 additions and 578 deletions

View file

@ -1,8 +1,8 @@
<script setup lang="ts">
/**
* @file CourseCard.vue
* @description Standardized Course Card Component.
* Usage: <CourseCard :id="1" title="..." ... />
* @description คอมโพเนนตการดแสดงคอรสเรยนมาตรฐาน (Standardized Course Card Component)
* ใชงาน: <CourseCard :id="1" title="..." ... />
*/
interface CourseCardProps {
@ -20,7 +20,7 @@ interface CourseCardProps {
image?: string
loading?: boolean
// Action Flags
// (Action Flags)
showViewDetails?: boolean
showContinue?: boolean
showCertificate?: boolean
@ -59,7 +59,7 @@ const displayCategory = computed(() => getLocalizedText(props.category))
<template>
<div class="group relative flex flex-col bg-white dark:!bg-slate-900 rounded-3xl overflow-hidden border border-slate-200 dark:border-white/5 shadow-sm hover:shadow-xl dark:shadow-none hover:-translate-y-1 transition-all duration-300 h-full">
<!-- Thumbnail Section -->
<!-- วนรปหนาปก (Thumbnail Section) -->
<div class="relative w-full aspect-video overflow-hidden">
<img
v-if="image"
@ -71,12 +71,12 @@ const displayCategory = computed(() => getLocalizedText(props.category))
<q-icon name="image" size="48px" class="text-slate-300 dark:text-slate-600" />
</div>
<!-- Overlays -->
<!-- เลเยอรลเตอรอนท (Overlays) -->
<div class="absolute inset-0 bg-gradient-to-t from-slate-900/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity"></div>
<!-- Completed Badge -->
<!-- ายแสดงสถานะเรยนจบ (Completed Badge) -->
<div v-if="completed" class="absolute inset-0 bg-emerald-900/40 backdrop-blur-[2px] flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<span class="bg-emerald-500 text-white w-12 h-12 rounded-full flex items-center justify-center shadow-lg transform scale-75 group-hover:scale-100 transition-transform">
<q-icon name="check" size="24px" />
@ -84,9 +84,9 @@ const displayCategory = computed(() => getLocalizedText(props.category))
</div>
</div>
<!-- Content Section -->
<!-- วนเนอหาขอม (Content Section) -->
<div class="p-6 flex flex-col flex-grow">
<!-- Meta Info (Lessons/Duration) -->
<!-- อมลประกอบยอย เช บทเรยน/ระยะเวลา (Meta Info - Lessons/Duration) -->
<div class="flex items-center gap-3 text-xs font-bold text-slate-500 dark:text-slate-400 mb-3 uppercase tracking-wider">
<span v-if="lessons" class="flex items-center gap-1">
<q-icon name="menu_book" size="14px" /> {{ lessons }} {{ $t('course.lessonsUnit') }}
@ -96,18 +96,18 @@ const displayCategory = computed(() => getLocalizedText(props.category))
</span>
</div>
<!-- Title -->
<!-- อคอร (Title) -->
<h3 class="text-lg font-black text-slate-900 dark:text-white mb-2 line-clamp-2 leading-tight group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
{{ displayTitle }}
</h3>
<!-- Description -->
<!-- รายละเอยดเพมเต (Description) -->
<p v-if="displayDescription" class="text-sm text-slate-500 dark:text-slate-400 line-clamp-2 mb-6">
{{ displayDescription }}
</p>
<div class="mt-auto pt-4">
<!-- Progress Bar -->
<!-- หลอดความคบหน (Progress Bar) -->
<div v-if="progress !== undefined && !completed && !hideProgress" class="mb-4">
<div class="flex justify-between text-[10px] font-bold uppercase mb-1">
<span class="text-slate-500 dark:text-slate-400">{{ $t('course.progress') }}</span>
@ -118,9 +118,9 @@ const displayCategory = computed(() => getLocalizedText(props.category))
</div>
</div>
<!-- Action Buttons -->
<!-- มปฏการตางๆ (Action Buttons) -->
<div v-if="!hideActions" class="flex flex-col gap-3">
<!-- View Details (Secondary Action) -->
<!-- มดรายละเอยด (มรอง) (View Details - Secondary Action) -->
<q-btn
v-if="showViewDetails && !completed && !progress"
flat
@ -130,7 +130,7 @@ const displayCategory = computed(() => getLocalizedText(props.category))
:to="`/course/${id}`"
/>
<!-- Continue Learning (Primary Action) -->
<!-- มเรยนต/เรมเรยน (มหล) (Continue Learning - Primary Action) -->
<q-btn
v-if="showContinue || (progress && !completed) || (progress === 0 && !completed)"
unelevated
@ -142,7 +142,7 @@ const displayCategory = computed(() => getLocalizedText(props.category))
</div>
<div v-if="completed" class="space-y-2">
<!-- Study Again -->
<!-- มเรยนอกคร (Study Again) -->
<q-btn
v-if="showStudyAgain"
unelevated
@ -152,7 +152,7 @@ const displayCategory = computed(() => getLocalizedText(props.category))
:to="`/classroom/learning?course_id=${id}`"
/>
<!-- Download Certificate -->
<!-- มดาวนโหลดใบรบรอง (Download Certificate) -->
<q-btn
v-if="showCertificate"
unelevated
@ -168,5 +168,5 @@ const displayCategory = computed(() => getLocalizedText(props.category))
</template>
<style scoped>
/* Scoped overrides if needed */
/* ใส่โค้ด CSS เพิ่มได้ถ้าต้องการครอบคลุมเฉพาะไฟล์นี้ (Scoped overrides if needed) */
</style>