initial commit - LeDiscord plateforme des copains
This commit is contained in:
304
frontend/src/views/Home.vue
Normal file
304
frontend/src/views/Home.vue
Normal file
@@ -0,0 +1,304 @@
|
||||
<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.active_members || 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>
|
||||
Reference in New Issue
Block a user