From a0ca6f7e6ba6e86e89f3fbfa13698d64e18ac355 Mon Sep 17 00:00:00 2001 From: supalerk-ar66 Date: Thu, 12 Feb 2026 12:01:37 +0700 Subject: [PATCH] feat: Implement core e-learning platform features including quiz, dashboard, course discovery, and classroom learning with i18n support. --- Frontend-Learner/assets/css/main.css | 28 +-- .../classroom/CurriculumSidebar.vue | 89 +++++++-- .../components/classroom/VideoPlayer.vue | 2 + .../components/discovery/CategorySidebar.vue | 2 +- .../components/discovery/CourseDetailView.vue | 138 ++++++++------ Frontend-Learner/composables/useCourse.ts | 1 + Frontend-Learner/i18n/locales/en.json | 18 +- Frontend-Learner/i18n/locales/th.json | 18 +- Frontend-Learner/pages/browse/discovery.vue | 173 ++++++++++-------- Frontend-Learner/pages/classroom/learning.vue | 79 ++++---- Frontend-Learner/pages/classroom/quiz.vue | 5 +- Frontend-Learner/pages/dashboard/index.vue | 52 +++--- .../pages/dashboard/my-courses.vue | 70 +++++-- 13 files changed, 429 insertions(+), 246 deletions(-) diff --git a/Frontend-Learner/assets/css/main.css b/Frontend-Learner/assets/css/main.css index 5a4bf8c7..db18a4fb 100644 --- a/Frontend-Learner/assets/css/main.css +++ b/Frontend-Learner/assets/css/main.css @@ -10,9 +10,9 @@ --bg-body: #f8fafc; --bg-surface: #ffffff; --bg-elevated: #ffffff; - --text-main: #000000; /* Pure Black for absolute clarity in light mode */ - --text-secondary: #1f2937; /* text-slate-800: Strong contrast subtext */ - --primary: #3b82f6; /* Primary Blue */ + --text-main: #000000; /* Pure Black for absolute clarity in light mode */ + --text-secondary: #1f2937; /* text-slate-800: Strong contrast subtext */ + --primary: #3b82f6; /* Primary Blue */ /* Semantic mappings */ --border-color: #e2e8f0; @@ -60,17 +60,17 @@ /* Dark Mode (applied when `.dark` class is present on ) */ .dark { /* Oceanic Palette: Standardized for entire App */ - --bg-body: #020617; /* Slate-950: Main Background */ - --bg-surface: #0f172a; /* Slate-900: Sidebar & Header */ - --bg-elevated: #1e293b; /* Slate-800: Cards & Hover states */ - - --text-main: #f8fafc; /* Slate-50: Brightest for titles */ - --text-secondary: #94a3b8; /* Slate-400: Muted for subtext */ - + --bg-body: #020617; /* Slate-950: Main Background */ + --bg-surface: #0f172a; /* Slate-900: Sidebar & Header */ + --bg-elevated: #1e293b; /* Slate-800: Cards & Hover states */ + + --text-main: #f8fafc; /* Slate-50: Brightest for titles */ + --text-secondary: #94a3b8; /* Slate-400: Muted for subtext */ + --border-color: rgba(255, 255, 255, 0.06); - + --primary-light: rgba(59, 130, 246, 0.15); - + /* Neutral scale for dark mode utility usage */ --neutral-100: #1e293b; --neutral-200: #334155; @@ -658,12 +658,12 @@ ul { .page-container { max-width: 1280px; /* max-7xl equivalent roughly */ margin: 0 auto; - padding: 2rem 1.5rem; + padding: 1rem 1.5rem; } @media (min-width: 1024px) { .page-container { - padding: 3rem 2rem; + padding: 1.5rem 2rem; } } diff --git a/Frontend-Learner/components/classroom/CurriculumSidebar.vue b/Frontend-Learner/components/classroom/CurriculumSidebar.vue index ccd87df4..14d8d159 100644 --- a/Frontend-Learner/components/classroom/CurriculumSidebar.vue +++ b/Frontend-Learner/components/classroom/CurriculumSidebar.vue @@ -29,6 +29,20 @@ const getLocalizedText = (text: any) => { const currentLocale = locale.value as 'th' | 'en' return text[currentLocale] || text.th || text.en || '' } + +// Local Progress Calculation +const progressPercentage = computed(() => { + if (!props.courseData || !props.courseData.chapters) return 0 + let total = 0 + let completed = 0 + props.courseData.chapters.forEach((c: any) => { + c.lessons.forEach((l: any) => { + total++ + if (l.is_completed || l.progress?.is_completed) completed++ + }) + }) + return total > 0 ? Math.round((completed / total) * 100) : 0 +}) + + diff --git a/Frontend-Learner/components/classroom/VideoPlayer.vue b/Frontend-Learner/components/classroom/VideoPlayer.vue index 7dedea50..03349185 100644 --- a/Frontend-Learner/components/classroom/VideoPlayer.vue +++ b/Frontend-Learner/components/classroom/VideoPlayer.vue @@ -6,6 +6,7 @@ const props = defineProps<{ src: string; + poster?: string; initialSeekTime?: number; }>(); @@ -241,6 +242,7 @@ watch([volume, isMuted], () => {