fix(users): include all workshop types in user stats
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user