Files
LeDiscord/frontend/src/views/Home.vue
2025-12-23 19:12:30 +01:00

305 lines
11 KiB
Vue

<template>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Welcome Section -->
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-900 mb-2">
Salut {{ user?.full_name }} ! 👋
</h1>
<p class="text-gray-600">Voici ce qui se passe dans le groupe</p>
</div>
<!-- Quick Stats -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
<div class="card p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Prochain événement</p>
<p class="text-2xl font-bold text-gray-900">{{ nextEvent?.title || 'Aucun' }}</p>
<p v-if="nextEvent" class="text-sm text-gray-500 mt-1">
{{ formatDate(nextEvent.date) }}
</p>
</div>
<Calendar class="w-8 h-8 text-primary-600" />
</div>
</div>
<div class="card p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Taux de présence</p>
<p class="text-2xl font-bold text-gray-900">{{ Math.round(user?.attendance_rate || 0) }}%</p>
</div>
<TrendingUp class="w-8 h-8 text-green-600" />
</div>
</div>
<div class="card p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Nouveaux posts</p>
<p class="text-2xl font-bold text-gray-900">{{ recentPosts }}</p>
<p class="text-sm text-gray-500 mt-1">Cette semaine</p>
</div>
<MessageSquare class="w-8 h-8 text-blue-600" />
</div>
</div>
<div class="card p-6">
<div class="flex items-center justify-between">
<div>
<p class="text-sm text-gray-600">Membres actifs</p>
<p class="text-2xl font-bold text-gray-900">{{ activeMembers }}</p>
</div>
<Users class="w-8 h-8 text-purple-600" />
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Recent Posts -->
<div class="lg:col-span-2">
<div class="card">
<div class="p-6 border-b border-gray-100">
<h2 class="text-lg font-semibold text-gray-900">Publications récentes</h2>
</div>
<div class="divide-y divide-gray-100">
<div v-if="posts.length === 0" class="p-6 text-center text-gray-500">
Aucune publication récente
</div>
<div
v-for="post in posts"
:key="post.id"
class="p-6 hover:bg-gray-50 transition-colors"
>
<div class="flex items-start space-x-3">
<img
v-if="post.author_avatar"
:src="getMediaUrl(post.author_avatar)"
:alt="post.author_name"
class="w-10 h-10 rounded-full object-cover"
>
<div v-else class="w-10 h-10 rounded-full bg-primary-100 flex items-center justify-center">
<User class="w-5 h-5 text-primary-600" />
</div>
<div class="flex-1">
<div class="flex items-center space-x-2">
<p class="font-medium text-gray-900">{{ post.author_name }}</p>
<span class="text-xs text-gray-500">{{ formatRelativeDate(post.created_at) }}</span>
</div>
<div class="mt-1 text-gray-700">
<Mentions :content="post.content" :mentions="post.mentioned_users || []" />
</div>
<!-- Post Image -->
<img
v-if="post.image_url"
:src="getMediaUrl(post.image_url)"
:alt="post.content"
class="mt-3 rounded-lg max-w-full max-h-48 object-cover"
>
<!-- Post Actions -->
<div class="flex items-center space-x-4 mt-3 text-sm text-gray-500">
<button
@click="togglePostLike(post)"
class="flex items-center space-x-1 hover:text-primary-600 transition-colors"
:class="{ 'text-primary-600': post.is_liked }"
>
<Heart :class="post.is_liked ? 'fill-current' : ''" class="w-4 h-4" />
<span>{{ post.likes_count || 0 }}</span>
</button>
<button
@click="toggleComments(post.id)"
class="flex items-center space-x-1 hover:text-primary-600 transition-colors"
>
<MessageCircle class="w-4 h-4" />
<span>{{ post.comments_count || 0 }}</span>
</button>
</div>
</div>
</div>
</div>
</div>
<router-link
to="/posts"
class="block p-4 text-center text-primary-600 hover:bg-gray-50 font-medium"
>
Voir toutes les publications
</router-link>
</div>
</div>
<!-- Upcoming Events -->
<div>
<div class="card">
<div class="p-6 border-b border-gray-100">
<h2 class="text-lg font-semibold text-gray-900">Événements à venir</h2>
</div>
<div class="divide-y divide-gray-100">
<div v-if="upcomingEvents.length === 0" class="p-6 text-center text-gray-500">
Aucun événement prévu
</div>
<router-link
v-for="event in upcomingEvents"
:key="event.id"
:to="`/events/${event.id}`"
class="block p-4 hover:bg-gray-50 transition-colors"
>
<h3 class="font-medium text-gray-900">{{ event.title }}</h3>
<p class="text-sm text-gray-600 mt-1">{{ formatDate(event.date) }}</p>
<p v-if="event.location" class="text-sm text-gray-500 mt-1">
📍 {{ event.location }}
</p>
<div class="mt-3 flex items-center space-x-4 text-xs">
<span class="text-green-600">
{{ event.present_count }} présents
</span>
<span class="text-yellow-600">
? {{ event.maybe_count }} peut-être
</span>
</div>
</router-link>
</div>
<router-link
to="/events"
class="block p-4 text-center text-primary-600 hover:bg-gray-50 font-medium"
>
Voir tous les événements
</router-link>
</div>
<!-- Recent Vlogs -->
<div class="card mt-6">
<div class="p-6 border-b border-gray-100">
<h2 class="text-lg font-semibold text-gray-900">Derniers vlogs</h2>
</div>
<div class="divide-y divide-gray-100">
<div v-if="recentVlogs.length === 0" class="p-6 text-center text-gray-500">
Aucun vlog récent
</div>
<router-link
v-for="vlog in recentVlogs"
:key="vlog.id"
:to="`/vlogs/${vlog.id}`"
class="block p-4 hover:bg-gray-50 transition-colors"
>
<div class="aspect-video bg-gray-100 rounded-lg mb-3 relative overflow-hidden">
<img
v-if="vlog.thumbnail_url"
:src="getMediaUrl(vlog.thumbnail_url)"
:alt="vlog.title"
class="w-full h-full object-cover"
>
<div v-else class="w-full h-full flex items-center justify-center">
<Film class="w-8 h-8 text-gray-400" />
</div>
<div class="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center">
<Play class="w-12 h-12 text-white" />
</div>
</div>
<h3 class="font-medium text-gray-900">{{ vlog.title }}</h3>
<p class="text-sm text-gray-600 mt-1">Par {{ vlog.author_name }}</p>
<p class="text-xs text-gray-500 mt-1">{{ vlog.views_count }} vues</p>
</router-link>
</div>
<router-link
to="/vlogs"
class="block p-4 text-center text-primary-600 hover:bg-gray-50 font-medium"
>
Voir tous les vlogs
</router-link>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useAuthStore } from '@/stores/auth'
import axios from '@/utils/axios'
import { getMediaUrl } from '@/utils/axios'
import { format, formatDistanceToNow } from 'date-fns'
import { fr } from 'date-fns/locale'
import {
Calendar,
TrendingUp,
MessageSquare,
Users,
User,
Film,
Play,
Heart,
MessageCircle
} from 'lucide-vue-next'
import Mentions from '@/components/Mentions.vue'
const authStore = useAuthStore()
const posts = ref([])
const upcomingEvents = ref([])
const recentVlogs = ref([])
const stats = ref({})
const user = computed(() => authStore.user)
const nextEvent = computed(() => upcomingEvents.value[0])
const recentPosts = computed(() => stats.value.recent_posts || 0)
const activeMembers = computed(() => stats.value.total_users || 0)
function formatDate(date) {
return format(new Date(date), 'EEEE d MMMM à HH:mm', { locale: fr })
}
function formatRelativeDate(date) {
return formatDistanceToNow(new Date(date), { addSuffix: true, locale: fr })
}
async function fetchDashboardData() {
try {
// Fetch recent posts
const postsResponse = await axios.get('/api/posts?limit=5')
posts.value = postsResponse.data
// Fetch upcoming events
const eventsResponse = await axios.get('/api/events?upcoming=true')
upcomingEvents.value = eventsResponse.data.slice(0, 3)
// Fetch recent vlogs
const vlogsResponse = await axios.get('/api/vlogs?limit=2')
recentVlogs.value = vlogsResponse.data
// Fetch stats
const statsResponse = await axios.get('/api/stats/overview')
stats.value = statsResponse.data
} catch (error) {
console.error('Error fetching dashboard data:', error)
}
}
async function togglePostLike(post) {
try {
const response = await axios.post(`/api/posts/${post.id}/like`)
post.is_liked = response.data.is_liked
post.likes_count = response.data.likes_count
} catch (error) {
console.error('Error toggling like:', error)
}
}
function toggleComments(postId) {
const post = posts.value.find(p => p.id === postId)
if (post) {
post.showComments = !post.showComments
if (post.showComments && !post.comments) {
post.comments = []
post.newComment = ''
}
}
}
onMounted(() => {
fetchDashboardData()
})
</script>