diff --git a/client/src/components/stats-cards.tsx b/client/src/components/stats-cards.tsx index 0051ce6..ad7fbee 100644 --- a/client/src/components/stats-cards.tsx +++ b/client/src/components/stats-cards.tsx @@ -1,8 +1,19 @@ import { CalendarCheck, Users, Bot, Images, TrendingUp, TrendingDown } from "lucide-react"; import { useQuery } from "@tanstack/react-query"; +interface StatsResponse { + scheduledPosts: number; + scheduledPostsChange?: string; + scheduledPostsTrending?: "up" | "down"; + connectedPages: number; + aiTextsGenerated: number; + aiTextsChange?: string; + aiTextsTrending?: "up" | "down"; + mediaStored: number; +} + export default function StatsCards() { - const { data: stats, isLoading } = useQuery({ + const { data: stats, isLoading } = useQuery({ queryKey: ["/api/stats"], }); @@ -10,32 +21,32 @@ export default function StatsCards() { { icon: CalendarCheck, iconColor: "from-blue-500 to-blue-600", - title: (stats as any)?.scheduledPosts ?? 0, + title: stats?.scheduledPosts ?? 0, subtitle: "Publications planifiées", - change: "+12%", - trending: "up", + change: stats?.scheduledPostsChange, + trending: stats?.scheduledPostsTrending, changeLabel: "vs mois dernier", }, { icon: Users, iconColor: "from-purple-500 to-purple-600", - title: (stats as any)?.connectedPages ?? 0, + title: stats?.connectedPages ?? 0, subtitle: "Pages connectées", info: "Facebook • Instagram", }, { icon: Bot, iconColor: "from-green-500 to-green-600", - title: (stats as any)?.aiTextsGenerated ?? 0, + title: stats?.aiTextsGenerated ?? 0, subtitle: "Textes générés par IA", - change: "+8%", - trending: "up", + change: stats?.aiTextsChange, + trending: stats?.aiTextsTrending, changeLabel: "vs hier", }, { icon: Images, iconColor: "from-cyan-500 to-cyan-600", - title: (stats as any)?.mediaStored ?? 0, + title: stats?.mediaStored ?? 0, subtitle: "Médias stockés", info: "Images • Vidéos", }, diff --git a/server/routes.ts b/server/routes.ts index 1e18e45..f92ba5a 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -369,12 +369,52 @@ export async function registerRoutes(app: Express): Promise { const media = await storage.getMedia(userId); const aiGenerations = await storage.getAiGenerations(userId); + // Statistiques actuelles const scheduledPosts = posts.filter(p => p.status === "scheduled").length; + const currentAiTexts = aiGenerations.length; + const currentMedia = media.length; + + // Calculer les statistiques de la période précédente (hier pour les textes IA, mois dernier pour les posts) + const now = new Date(); + const yesterday = new Date(now); + yesterday.setDate(yesterday.getDate() - 1); + yesterday.setHours(0, 0, 0, 0); + + const lastMonth = new Date(now); + lastMonth.setMonth(lastMonth.getMonth() - 1); + + // Textes IA générés hier + const aiTextsYesterday = aiGenerations.filter(gen => { + if (!gen.createdAt) return false; + const genDate = new Date(gen.createdAt); + genDate.setHours(0, 0, 0, 0); + return genDate.getTime() === yesterday.getTime(); + }).length; + + // Posts planifiés le mois dernier + const scheduledPostsLastMonth = posts.filter(p => { + if (!p.createdAt) return false; + const createdDate = new Date(p.createdAt); + return p.status === "scheduled" && createdDate < lastMonth; + }).length; + + // Calculer les variations en pourcentage + const aiTextChange = aiTextsYesterday > 0 + ? Math.round(((currentAiTexts - aiTextsYesterday) / aiTextsYesterday) * 100) + : currentAiTexts > 0 ? 100 : 0; + + const scheduledPostsChange = scheduledPostsLastMonth > 0 + ? Math.round(((scheduledPosts - scheduledPostsLastMonth) / scheduledPostsLastMonth) * 100) + : scheduledPosts > 0 ? 100 : 0; res.json({ scheduledPosts, + scheduledPostsChange: scheduledPostsChange > 0 ? `+${scheduledPostsChange}%` : `${scheduledPostsChange}%`, + scheduledPostsTrending: scheduledPostsChange >= 0 ? "up" : "down", connectedPages: pages.length, - aiTextsGenerated: aiGenerations.length, + aiTextsGenerated: currentAiTexts, + aiTextsChange: aiTextChange > 0 ? `+${aiTextChange}%` : `${aiTextChange}%`, + aiTextsTrending: aiTextChange >= 0 ? "up" : "down", mediaStored: media.length, }); } catch (error) {