From 941151553f076098cb6b1cce41c8967c31c91841 Mon Sep 17 00:00:00 2001 From: Julien Froidefond Date: Fri, 28 Nov 2025 10:55:49 +0100 Subject: [PATCH] feat: add 'Utilisateurs' link to Header component and implement user statistics retrieval in auth service --- src/app/users/page.tsx | 231 +++++++++++++++++++++++++++++++ src/components/layout/Header.tsx | 7 + src/services/auth.ts | 58 ++++++++ 3 files changed, 296 insertions(+) create mode 100644 src/app/users/page.tsx diff --git a/src/app/users/page.tsx b/src/app/users/page.tsx new file mode 100644 index 0000000..3a8a4cf --- /dev/null +++ b/src/app/users/page.tsx @@ -0,0 +1,231 @@ +import { auth } from '@/lib/auth'; +import { redirect } from 'next/navigation'; +import { getAllUsersWithStats } from '@/services/auth'; +import { getGravatarUrl } from '@/lib/gravatar'; + +function formatRelativeTime(date: Date): string { + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) return "Aujourd'hui"; + if (diffDays === 1) return 'Hier'; + if (diffDays < 7) return `Il y a ${diffDays} jours`; + if (diffDays < 30) return `Il y a ${Math.floor(diffDays / 7)} sem.`; + if (diffDays < 365) return `Il y a ${Math.floor(diffDays / 30)} mois`; + return `Il y a ${Math.floor(diffDays / 365)} an(s)`; +} + +export default async function UsersPage() { + const session = await auth(); + + if (!session?.user?.id) { + redirect('/login'); + } + + const users = await getAllUsersWithStats(); + + // Calculate some global stats + const totalSessions = users.reduce( + (acc, u) => acc + u._count.sessions + u._count.motivatorSessions, + 0 + ); + const avgSessionsPerUser = users.length > 0 ? totalSessions / users.length : 0; + + return ( +
+ {/* Header */} +
+

Utilisateurs

+

+ {users.length} utilisateur{users.length > 1 ? 's' : ''} inscrit + {users.length > 1 ? 's' : ''} +

+
+ + {/* Global Stats */} +
+
+
{users.length}
+
Utilisateurs
+
+
+
{totalSessions}
+
Sessions totales
+
+
+
+ {avgSessionsPerUser.toFixed(1)} +
+
Moy. par user
+
+
+
+ {users.reduce( + (acc, u) => + acc + u._count.sharedSessions + u._count.sharedMotivatorSessions, + 0 + )} +
+
Partages actifs
+
+
+ + {/* Users List */} +
+ {users.map((user) => { + const totalUserSessions = + user._count.sessions + user._count.motivatorSessions; + const totalShares = + user._count.sharedSessions + user._count.sharedMotivatorSessions; + const isCurrentUser = user.id === session.user?.id; + + return ( +
+ {/* Avatar */} + {/* eslint-disable-next-line @next/next/no-img-element */} + {user.name + + {/* User Info */} +
+
+ + {user.name || 'Sans nom'} + + {isCurrentUser && ( + + Vous + + )} +
+
{user.email}
+
+ + {/* Stats Pills */} +
+
+ + + + {user._count.sessions} +
+
+ + + + {user._count.motivatorSessions} +
+ {totalShares > 0 && ( +
+ + + + {totalShares} +
+ )} +
+ + {/* Mobile Stats */} +
+
+ {totalUserSessions} session{totalUserSessions !== 1 ? 's' : ''} +
+
+ {formatRelativeTime(user.createdAt)} +
+
+ + {/* Date Info */} +
+
+ {formatRelativeTime(user.createdAt)} +
+
+ {new Date(user.createdAt).toLocaleDateString('fr-FR')} +
+
+
+ ); + })} +
+ + {/* Empty state */} + {users.length === 0 && ( +
+
👥
+
+ Aucun utilisateur +
+
+ Les utilisateurs apparaîtront ici une fois inscrits +
+
+ )} +
+ ); +} + diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 958be8b..db7cebc 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -155,6 +155,13 @@ export function Header() { > 👤 Mon Profil + setMenuOpen(false)} + className="block w-full px-4 py-2 text-left text-sm text-foreground hover:bg-card-hover" + > + 👥 Utilisateurs +