fix(users): include all workshop types in user stats

This commit is contained in:
2026-03-04 08:44:07 +01:00
parent 313ad53e2e
commit dcc769a930
2 changed files with 54 additions and 57 deletions

View File

@@ -4,6 +4,31 @@ import { getAllUsersWithStats } from '@/services/auth';
import { getGravatarUrl } from '@/lib/gravatar';
import { PageHeader } from '@/components/ui';
const OWNED_WORKSHOP_COUNT_KEYS = [
'sessions',
'motivatorSessions',
'yearReviewSessions',
'weeklyCheckInSessions',
'weatherSessions',
'gifMoodSessions',
] as const;
const SHARED_WORKSHOP_COUNT_KEYS = [
'sharedSessions',
'sharedMotivatorSessions',
'sharedYearReviewSessions',
'sharedWeeklyCheckInSessions',
'sharedWeatherSessions',
'sharedGifMoodSessions',
] as const;
function sumCountKeys(
counts: Record<string, number>,
keys: readonly string[]
): number {
return keys.reduce((acc, key) => acc + (counts[key] ?? 0), 0);
}
function formatRelativeTime(date: Date): string {
const now = new Date();
const diffMs = now.getTime() - date.getTime();
@@ -28,7 +53,11 @@ export default async function UsersPage() {
// Calculate some global stats
const totalSessions = users.reduce(
(acc, u) => acc + u._count.sessions + u._count.motivatorSessions,
(acc, u) => acc + sumCountKeys(u._count, OWNED_WORKSHOP_COUNT_KEYS),
0
);
const totalSharedSessions = users.reduce(
(acc, u) => acc + sumCountKeys(u._count, SHARED_WORKSHOP_COUNT_KEYS),
0
);
const avgSessionsPerUser = users.length > 0 ? totalSessions / users.length : 0;
@@ -56,12 +85,7 @@ export default async function UsersPage() {
<div className="text-sm text-muted">Moy. par user</div>
</div>
<div className="rounded-xl border border-border bg-card p-4">
<div className="text-2xl font-bold text-accent">
{users.reduce(
(acc, u) => acc + u._count.sharedSessions + u._count.sharedMotivatorSessions,
0
)}
</div>
<div className="text-2xl font-bold text-accent">{totalSharedSessions}</div>
<div className="text-sm text-muted">Partages actifs</div>
</div>
</div>
@@ -69,8 +93,8 @@ export default async function UsersPage() {
{/* Users List */}
<div className="space-y-3">
{users.map((user) => {
const totalUserSessions = user._count.sessions + user._count.motivatorSessions;
const totalShares = user._count.sharedSessions + user._count.sharedMotivatorSessions;
const totalUserSessions = sumCountKeys(user._count, OWNED_WORKSHOP_COUNT_KEYS);
const totalShares = sumCountKeys(user._count, SHARED_WORKSHOP_COUNT_KEYS);
const isCurrentUser = user.id === session.user?.id;
return (
@@ -115,7 +139,7 @@ export default async function UsersPage() {
backgroundColor: 'color-mix(in srgb, var(--strength) 15%, transparent)',
color: 'var(--strength)',
}}
title="Sessions SWOT"
title="Ateliers créés (tous types)"
>
<svg
className="h-3.5 w-3.5"
@@ -130,30 +154,7 @@ export default async function UsersPage() {
d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"
/>
</svg>
{user._count.sessions}
</div>
<div
className="flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium"
style={{
backgroundColor: 'color-mix(in srgb, var(--opportunity) 15%, transparent)',
color: 'var(--opportunity)',
}}
title="Sessions Moving Motivators"
>
<svg
className="h-3.5 w-3.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
/>
</svg>
{user._count.motivatorSessions}
{totalUserSessions}
</div>
{totalShares > 0 && (
<div

View File

@@ -194,8 +194,16 @@ export async function updateUserPassword(
export interface UserStats {
sessions: number;
motivatorSessions: number;
yearReviewSessions: number;
weeklyCheckInSessions: number;
weatherSessions: number;
gifMoodSessions: number;
sharedSessions: number;
sharedMotivatorSessions: number;
sharedYearReviewSessions: number;
sharedWeeklyCheckInSessions: number;
sharedWeatherSessions: number;
sharedGifMoodSessions: number;
}
export interface UserWithStats {
@@ -208,7 +216,7 @@ export interface UserWithStats {
}
export async function getAllUsersWithStats(): Promise<UserWithStats[]> {
const users = await prisma.user.findMany({
return prisma.user.findMany({
select: {
id: true,
email: true,
@@ -218,32 +226,20 @@ export async function getAllUsersWithStats(): Promise<UserWithStats[]> {
_count: {
select: {
sessions: true,
motivatorSessions: true,
yearReviewSessions: true,
weeklyCheckInSessions: true,
weatherSessions: true,
gifMoodSessions: true,
sharedSessions: true,
sharedMotivatorSessions: true,
sharedYearReviewSessions: true,
sharedWeeklyCheckInSessions: true,
sharedWeatherSessions: true,
sharedGifMoodSessions: true,
},
},
},
orderBy: { createdAt: 'desc' },
});
// Get motivator counts in bulk (2 queries instead of 2*N)
const motivatorCounts = await prisma.movingMotivatorsSession.groupBy({
by: ['userId'],
_count: { id: true },
});
const sharedMotivatorCounts = await prisma.mMSessionShare.groupBy({
by: ['userId'],
_count: { id: true },
});
const motivatorMap = new Map(motivatorCounts.map((m) => [m.userId, m._count.id]));
const sharedMotivatorMap = new Map(sharedMotivatorCounts.map((m) => [m.userId, m._count.id]));
return users.map((user) => ({
...user,
_count: {
...user._count,
motivatorSessions: motivatorMap.get(user.id) ?? 0,
sharedMotivatorSessions: sharedMotivatorMap.get(user.id) ?? 0,
},
}));
}