feat: Create dashboard page displaying welcome message, continue learning progress, and recommended courses.
This commit is contained in:
parent
bdebf2224e
commit
6b1ff7196a
1 changed files with 47 additions and 22 deletions
|
|
@ -15,6 +15,15 @@ useHead({
|
||||||
})
|
})
|
||||||
|
|
||||||
const { currentUser } = useAuth()
|
const { currentUser } = useAuth()
|
||||||
|
const { fetchCourses } = useCourse() // Import useCourse
|
||||||
|
const { fetchCategories } = useCategory() // Import useCategory
|
||||||
|
|
||||||
|
// Helper to get localized text
|
||||||
|
const getLocalizedText = (text: string | { th: string; en: string } | undefined) => {
|
||||||
|
if (!text) return ''
|
||||||
|
if (typeof text === 'string') return text
|
||||||
|
return text.th || text.en || ''
|
||||||
|
}
|
||||||
|
|
||||||
// Mock data: Recent Course Progress
|
// Mock data: Recent Course Progress
|
||||||
const recentCourse = {
|
const recentCourse = {
|
||||||
|
|
@ -24,25 +33,35 @@ const recentCourse = {
|
||||||
image: 'https://images.unsplash.com/photo-1586717791821-3f44a563de4c?w=400&auto=format&fit=crop&q=60'
|
image: 'https://images.unsplash.com/photo-1586717791821-3f44a563de4c?w=400&auto=format&fit=crop&q=60'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock data: Recommended Courses
|
// Recommended Courses State
|
||||||
const recommendedCourses = [
|
const recommendedCourses = ref<any[]>([])
|
||||||
{
|
|
||||||
title: 'Advanced React Patterns',
|
onMounted(async () => {
|
||||||
category: 'Development',
|
// 1. Fetch Categories for mapping
|
||||||
duration: '4h 30m',
|
const catRes = await fetchCategories()
|
||||||
image: 'https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=400&auto=format&fit=crop&q=60',
|
const catMap = new Map()
|
||||||
badge: 'New',
|
if (catRes.success) {
|
||||||
badgeType: 'success'
|
catRes.data.forEach((c: any) => catMap.set(c.id, c.name))
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Data Science Fundamentals',
|
|
||||||
category: 'Data Science',
|
|
||||||
duration: '12h 0m',
|
|
||||||
image: 'https://images.unsplash.com/photo-1551288049-bbda38a0617f?w=400&auto=format&fit=crop&q=60',
|
|
||||||
badge: 'Popular',
|
|
||||||
badgeType: 'warning'
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
|
// 2. Fetch All Courses and Randomize
|
||||||
|
const res = await fetchCourses()
|
||||||
|
if (res.success && res.data?.length) {
|
||||||
|
// Shuffle array
|
||||||
|
const shuffled = [...res.data].sort(() => 0.5 - Math.random())
|
||||||
|
|
||||||
|
// Pick first 3
|
||||||
|
recommendedCourses.value = shuffled.slice(0, 3).map((c: any) => ({
|
||||||
|
id: c.id,
|
||||||
|
title: getLocalizedText(c.title),
|
||||||
|
category: getLocalizedText(catMap.get(c.category_id)) || 'General', // Map Category ID to Name
|
||||||
|
duration: c.lessons ? `${c.lessons} บทเรียน` : 'พร้อมเรียน', // Use lesson count or default
|
||||||
|
image: c.thumbnail_url || 'https://images.unsplash.com/photo-1498050108023-c5249f4df085?w=400&q=80',
|
||||||
|
badge: 'Recommended',
|
||||||
|
badgeType: 'warning'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -111,7 +130,7 @@ const recommendedCourses = [
|
||||||
|
|
||||||
<!-- Recommended Grid -->
|
<!-- Recommended Grid -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
<div v-for="(course, idx) in recommendedCourses" :key="idx" class="p-0 overflow-hidden group border border-slate-200 dark:border-white/5 rounded-3xl shadow-sm dark:shadow-xl transition-all hover:-translate-y-1 dark:hover:-translate-y-1" style="background-color: var(--bg-surface);">
|
<NuxtLink v-for="(course, idx) in recommendedCourses" :key="course.id" to="/browse/discovery" class="p-0 overflow-hidden group border border-slate-200 dark:border-white/5 rounded-3xl shadow-sm dark:shadow-xl transition-all hover:-translate-y-1 dark:hover:-translate-y-1 block" style="background-color: var(--bg-surface);">
|
||||||
<div class="h-48 overflow-hidden relative rounded-t-3xl">
|
<div class="h-48 overflow-hidden relative rounded-t-3xl">
|
||||||
<img :src="course.image" :alt="course.title" class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700" >
|
<img :src="course.image" :alt="course.title" class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700" >
|
||||||
<span v-if="course.badge" :class="`absolute top-5 left-5 status-pill status-${course.badgeType} shadow-lg font-black text-[9px]`">{{ course.badge }}</span>
|
<span v-if="course.badge" :class="`absolute top-5 left-5 status-pill status-${course.badgeType} shadow-lg font-black text-[9px]`">{{ course.badge }}</span>
|
||||||
|
|
@ -119,17 +138,23 @@ const recommendedCourses = [
|
||||||
</div>
|
</div>
|
||||||
<div class="p-7" style="background-color: var(--bg-surface);">
|
<div class="p-7" style="background-color: var(--bg-surface);">
|
||||||
<div class="text-[10px] font-black text-slate-700 dark:text-slate-500 uppercase tracking-[0.2em] mb-2">{{ course.category }}</div>
|
<div class="text-[10px] font-black text-slate-700 dark:text-slate-500 uppercase tracking-[0.2em] mb-2">{{ course.category }}</div>
|
||||||
<h4 class="font-black text-xl mb-6 text-slate-900 dark:text-white group-hover:text-blue-700 dark:group-hover:text-blue-400 transition-colors">{{ course.title }}</h4>
|
<h4 class="font-black text-xl mb-6 text-slate-900 dark:text-white group-hover:text-blue-700 dark:group-hover:text-blue-400 transition-colors line-clamp-2">{{ course.title }}</h4>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-xs font-bold text-slate-700 dark:text-slate-400 flex items-center gap-2">
|
<span class="text-xs font-bold text-slate-700 dark:text-slate-400 flex items-center gap-2">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-slate-600 dark:text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-slate-600 dark:text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||||
{{ course.duration }}
|
{{ course.duration }}
|
||||||
</span>
|
</span>
|
||||||
<button class="text-[11px] font-black text-blue-700 dark:text-blue-500 uppercase tracking-widest hover:text-blue-800 dark:hover:text-blue-400 transition-colors">{{ $t('menu.viewDetails') }}</button>
|
<span class="text-[11px] font-black text-blue-700 dark:text-blue-500 uppercase tracking-widest hover:text-blue-800 dark:hover:text-blue-400 transition-colors">{{ $t('menu.viewDetails') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading State for Recommended -->
|
||||||
|
<div v-if="recommendedCourses.length === 0" class="flex justify-center py-10 opacity-50">
|
||||||
|
<div class="animate-pulse">Loading recommendations...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue