Files
Socialflow/client/src/components/stats-cards.tsx
T
michaelschal 48138c2166 Update dashboard to show accurate statistics and improve sidebar functionality
Refactor the API endpoint for statistics to accurately calculate and display scheduled posts, AI text generations, and media stored, including percentage changes and trending indicators.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ae4037a0-2a6f-4530-9bac-79b543286bda
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/397bca8c-984f-43ff-841a-10897aeb8140/ae4037a0-2a6f-4530-9bac-79b543286bda/ycYcQjF
2025-10-08 15:35:49 +00:00

115 lines
3.9 KiB
TypeScript

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<StatsResponse>({
queryKey: ["/api/stats"],
});
const statCards = [
{
icon: CalendarCheck,
iconColor: "from-blue-500 to-blue-600",
title: stats?.scheduledPosts ?? 0,
subtitle: "Publications planifiées",
change: stats?.scheduledPostsChange,
trending: stats?.scheduledPostsTrending,
changeLabel: "vs mois dernier",
},
{
icon: Users,
iconColor: "from-purple-500 to-purple-600",
title: stats?.connectedPages ?? 0,
subtitle: "Pages connectées",
info: "Facebook • Instagram",
},
{
icon: Bot,
iconColor: "from-green-500 to-green-600",
title: stats?.aiTextsGenerated ?? 0,
subtitle: "Textes générés par IA",
change: stats?.aiTextsChange,
trending: stats?.aiTextsTrending,
changeLabel: "vs hier",
},
{
icon: Images,
iconColor: "from-cyan-500 to-cyan-600",
title: stats?.mediaStored ?? 0,
subtitle: "Médias stockés",
info: "Images • Vidéos",
},
];
if (isLoading) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{[1, 2, 3, 4].map((i) => (
<div key={i} className="bg-card rounded-2xl border border-border p-6 shadow-sm">
<div className="skeleton h-12 w-12 rounded-xl mb-4" />
<div className="skeleton h-10 w-20 mb-2" />
<div className="skeleton h-4 w-36" />
</div>
))}
</div>
);
}
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{statCards.map((stat, index) => {
const Icon = stat.icon;
const TrendIcon = stat.trending === "up" ? TrendingUp : TrendingDown;
return (
<div
key={index}
className="group bg-card rounded-2xl border border-border p-6 hover:border-primary/30 transition-all cursor-pointer card-hover shadow-sm"
data-testid={`stat-card-${index}`}
>
<div className="flex items-start justify-between mb-6">
<div className={`w-14 h-14 rounded-xl bg-gradient-to-br ${stat.iconColor} flex items-center justify-center shadow-lg`}>
<Icon className="text-white w-7 h-7" />
</div>
</div>
<div className="space-y-1">
<h3 className="text-4xl font-bold text-foreground" data-testid={`stat-value-${index}`}>
{stat.title}
</h3>
<p className="text-sm text-muted-foreground font-medium">{stat.subtitle}</p>
</div>
{stat.change && (
<div className="mt-4 flex items-center gap-2">
<div className={`flex items-center gap-1 px-2 py-1 rounded-lg ${stat.trending === "up" ? "bg-success/10 text-success" : "bg-destructive/10 text-destructive"}`}>
<TrendIcon className="w-3 h-3" />
<span className="text-xs font-semibold">{stat.change}</span>
</div>
<span className="text-xs text-muted-foreground">{stat.changeLabel}</span>
</div>
)}
{stat.info && (
<div className="mt-4">
<span className="text-xs text-muted-foreground font-medium">{stat.info}</span>
</div>
)}
</div>
);
})}
</div>
);
}