diff --git a/app/api/stats/route.ts b/app/api/stats/route.ts index 16ffb09..159b333 100644 --- a/app/api/stats/route.ts +++ b/app/api/stats/route.ts @@ -8,6 +8,12 @@ export async function GET() { // Récupérer toutes les transactions const transactions = await db.collection("transactions").find({}).toArray(); + // Récupérer les conversations pour analyser les connexions utilisateurs + const conversations = await db.collection("conversations").find({}).toArray(); + + // Récupérer les messages pour une analyse plus précise de l'activité + const messages = await db.collection("messages").find({}).toArray(); + console.log(`Total transactions trouvées: ${transactions.length}`); // Vérifier les champs de date disponibles dans les transactions @@ -106,14 +112,75 @@ export async function GET() { .map(([name, value]) => ({ name, value })) .sort((a, b) => b.value - a.value); + // Calculer les connexions utilisateurs par jour (7 derniers jours) + const dailyConnections = []; + + for (let i = 6; i >= 0; i--) { + const date = new Date(today); + date.setDate(date.getDate() - i); + date.setHours(0, 0, 0, 0); + + const nextDate = new Date(date); + nextDate.setDate(nextDate.getDate() + 1); + + // Analyser l'activité des utilisateurs via les messages + const activeUsers = new Set(); + + messages.forEach(message => { + let messageDate = null; + + if (message.createdAt) { + messageDate = new Date(message.createdAt); + } else if (message.updatedAt) { + messageDate = new Date(message.updatedAt); + } else if (message._id && message._id.getTimestamp) { + messageDate = message._id.getTimestamp(); + } + + if (messageDate && messageDate >= date && messageDate < nextDate) { + if (message.user && message.isCreatedByUser) { + activeUsers.add(message.user); + } + } + }); + + // Aussi analyser via les conversations créées ce jour-là + conversations.forEach(conversation => { + let convDate = null; + + if (conversation.createdAt) { + convDate = new Date(conversation.createdAt); + } else if (conversation.updatedAt) { + convDate = new Date(conversation.updatedAt); + } else if (conversation._id && conversation._id.getTimestamp) { + convDate = conversation._id.getTimestamp(); + } + + if (convDate && convDate >= date && convDate < nextDate) { + if (conversation.user) { + activeUsers.add(conversation.user); + } + } + }); + + console.log(`${dayNames[date.getDay()]} (${date.toISOString().split('T')[0]}): ${activeUsers.size} utilisateurs actifs`); + + dailyConnections.push({ + name: dayNames[date.getDay()], + value: activeUsers.size + }); + } + console.log("Statistiques calculées:", { dailyStats, + dailyConnections, totalModels: modelData.length, topModel: modelData[0] }); return NextResponse.json({ dailyTokens: dailyStats, + dailyConnections: dailyConnections, modelDistribution: modelData }); } catch (error) { diff --git a/components/dashboard/add-credits.tsx b/components/dashboard/add-credits.tsx index 5ad7fc2..7b68510 100644 --- a/components/dashboard/add-credits.tsx +++ b/components/dashboard/add-credits.tsx @@ -158,7 +158,7 @@ export default function AddCredits() { ⚠️ Action Importante

- Cette action va ajouter 5,000,000 crédits à + Cette action va ajouter 3,000,000 crédits à chacun des {stats.totalUsers} utilisateurs.
Total de crédits qui seront ajoutés:{" "} @@ -173,7 +173,7 @@ export default function AddCredits() { > {loading ? "Ajout en cours..." - : `Ajouter 5M crédits à ${stats.totalUsers} utilisateurs`} + : `Ajouter 3M crédits à ${stats.totalUsers} utilisateurs`} )} diff --git a/components/dashboard/charts/user-connections-chart.tsx b/components/dashboard/charts/user-connections-chart.tsx new file mode 100644 index 0000000..39dc1c7 --- /dev/null +++ b/components/dashboard/charts/user-connections-chart.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer +} from "recharts"; + +interface UserConnectionsChartProps { + title: string; + data: Array<{ + name: string; + value: number; + }>; + color?: string; +} + +export function UserConnectionsChart({ title, data, color = "hsl(var(--chart-2))" }: UserConnectionsChartProps) { + console.log("UserConnectionsChart - data:", data); + console.log("UserConnectionsChart - title:", title); + + return ( + + + {title} + + + + + + + + + + + + + { + return value.toString(); + }} + /> + [ + `${value} utilisateur${value > 1 ? 's' : ''}`, + 'Connexions' + ]} + /> + + + + + + ); +} \ No newline at end of file diff --git a/components/dashboard/dashboard-users-list.tsx b/components/dashboard/dashboard-users-list.tsx index e4892cb..443fc3a 100644 --- a/components/dashboard/dashboard-users-list.tsx +++ b/components/dashboard/dashboard-users-list.tsx @@ -52,17 +52,17 @@ export function DashboardUsersList() { // Récupérer toutes les données nécessaires const { data: users, loading: usersLoading } = - useCollection("users"); + useCollection("users", { limit: 1000 }); const { data: conversations, loading: conversationsLoading } = - useCollection("conversations"); + useCollection("conversations", { limit: 1000 }); const { data: balances, loading: balancesLoading } = - useCollection("balances"); + useCollection("balances", { limit: 1000 }); const { data: messages, loading: messagesLoading } = - useCollection("messages"); + useCollection("messages", { limit: 1000 }); const { data: tokens, loading: tokensLoading } = - useCollection("tokens"); + useCollection("tokens", { limit: 1000 }); const { data: toolcalls, loading: toolcallsLoading } = - useCollection("toolcalls"); + useCollection("toolcalls", { limit: 1000 }); const processUsers = useCallback(() => { if ( @@ -174,20 +174,23 @@ export function DashboardUsersList() { const latestBalance = sortedBalances[0]; const credits = latestBalance ? latestBalance.tokenCredits || 0 : 0; - // Calculer les tokens consommés depuis les crédits - const INITIAL_CREDITS = 3000000; - const creditsUsed = INITIAL_CREDITS - credits; - const tokensFromCredits = creditsUsed > 0 ? creditsUsed : 0; - - // Prendre la valeur la plus élevée (plus précise) + // Calculer les tokens réellement consommés depuis les messages (approche principale) const totalTokens = Math.max( totalTokensFromMessages, totalTokensFromConversations, tokensFromTokensCollection, - tokensFromToolcalls, - tokensFromCredits + tokensFromToolcalls ); + // Calculer les tokens depuis les crédits seulement si on n'a pas de données de messages + const INITIAL_CREDITS = 3000000; + const creditsUsed = INITIAL_CREDITS - credits; + const tokensFromCredits = creditsUsed > 0 ? creditsUsed : 0; + + // Si on n'a pas de tokens depuis les messages mais qu'on a une consommation de crédits significative + const finalTokens = totalTokens > 0 ? totalTokens : + (tokensFromCredits > 0 && tokensFromCredits < INITIAL_CREDITS) ? tokensFromCredits : 0; + // Log de débogage très détaillé console.log(`👤 User ${user.name || user.email}:`, { conversations: userConversations.length, @@ -200,7 +203,8 @@ export function DashboardUsersList() { currentCredits: credits, creditsUsed: creditsUsed, tokensFromCredits: tokensFromCredits, - finalTokens: totalTokens, + finalTokens: finalTokens, + willBeIncluded: finalTokens > 0, messagesSample: userMessages.slice(0, 2).map((m) => ({ tokenCount: m.tokenCount, model: m.model, @@ -213,14 +217,14 @@ export function DashboardUsersList() { })), }); - // Ajouter l'utilisateur seulement s'il a des données significatives - if (userConversations.length > 0 || totalTokens > 0 || credits > 0) { + // Ajouter l'utilisateur s'il a consommé des tokens (éviter les faux positifs de 3M tokens) + if (finalTokens > 0 && finalTokens < INITIAL_CREDITS) { processedUsers.push({ userId: user._id, userName: user.name || user.username || user.email || "Utilisateur inconnu", conversations: userConversations.length, - tokens: totalTokens, + tokens: finalTokens, credits: credits, }); } @@ -231,6 +235,17 @@ export function DashboardUsersList() { .sort((a, b) => b.tokens - a.tokens) .slice(0, 5); + console.log("📊 Processing summary:", { + totalUsers: users.length, + usersWithActivity: processedUsers.length, + top5Users: sortedUsers.length, + allProcessedUsers: processedUsers.map(u => ({ + name: u.userName, + conversations: u.conversations, + tokens: u.tokens, + credits: u.credits + })) + }); console.log("✅ Top 5 users processed:", sortedUsers); setTopUsers(sortedUsers); setIsLoading(false); diff --git a/components/dashboard/overview-metrics.tsx b/components/dashboard/overview-metrics.tsx index 77c79fa..1c9e620 100644 --- a/components/dashboard/overview-metrics.tsx +++ b/components/dashboard/overview-metrics.tsx @@ -2,7 +2,15 @@ import { useMetrics } from "@/hooks/useMetrics"; import { MetricCard } from "@/components/ui/metric-card"; -import { Users, UserCheck, Shield, Coins, MessageSquare, FileText, Euro, Activity } from "lucide-react"; +import { + Users, + UserCheck, + Coins, + MessageSquare, + FileText, + Euro, + Activity, +} from "lucide-react"; import { convertCreditsToEuros } from "@/lib/utils/currency"; export function OverviewMetrics() { @@ -30,62 +38,63 @@ export function OverviewMetrics() { const creditsInEuros = convertCreditsToEuros(metrics.totalCredits); return ( -

- - - - - {/* Nouvelle carte pour les tokens consommés */} - - -
-
-

Crédits totaux

-
- - +
+ {/* Ligne 1: Utilisateurs actifs, Conversations actives, Tokens consommés */} +
+ + + +
+ + {/* Ligne 2: Utilisateurs totaux, Messages totaux, Crédits totaux */} +
+ + +
+
+

Crédits totaux

+
+ + +
-
-
- {metrics.totalCredits.toLocaleString()} -
-
crédits disponibles
-
-
- {creditsInEuros.formatted.eur} +
+ {metrics.totalCredits.toLocaleString()}
-
- {creditsInEuros.formatted.usd} USD +
crédits disponibles
+
+
+ {creditsInEuros.formatted.eur} +
+
+ {creditsInEuros.formatted.usd} USD +
- -
); -} \ No newline at end of file +} diff --git a/components/dashboard/real-time-stats.tsx b/components/dashboard/real-time-stats.tsx index 51bf1bb..4db0d23 100644 --- a/components/dashboard/real-time-stats.tsx +++ b/components/dashboard/real-time-stats.tsx @@ -1,13 +1,19 @@ "use client"; import { Card, CardContent } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useStats } from "@/hooks/useStats"; import { SimpleStatsChart } from "./charts/simple-stats-chart"; +import { UserConnectionsChart } from "./charts/user-connections-chart"; import { ModelDistributionChart } from "./charts/model-distribution-chart"; import { AlertCircle } from "lucide-react"; export function RealTimeStats() { const { stats, loading, error } = useStats(); + + console.log("RealTimeStats - stats:", stats); + console.log("RealTimeStats - loading:", loading); + console.log("RealTimeStats - error:", error); if (loading) { return ( @@ -67,16 +73,35 @@ export function RealTimeStats() { } return ( -
- - -
+ + + Nombre de connexions par utilisateur/par jour + Tokens consommés par jour + + + + + + + + + + + + ); } diff --git a/hooks/useStats.ts b/hooks/useStats.ts index 671044e..afa2199 100644 --- a/hooks/useStats.ts +++ b/hooks/useStats.ts @@ -7,6 +7,11 @@ interface DailyToken { value: number; } +interface DailyConnection { + name: string; + value: number; +} + interface ModelDistribution { name: string; value: number; @@ -14,6 +19,7 @@ interface ModelDistribution { interface StatsData { dailyTokens: DailyToken[]; + dailyConnections: DailyConnection[]; modelDistribution: ModelDistribution[]; }