feat: enhance statistics page with new charts and data visualizations including savings trend, category trends, and year-over-year comparisons

This commit is contained in:
Julien Froidefond
2025-11-30 17:05:03 +01:00
parent f366ea02c5
commit 00dd8fc335
7 changed files with 902 additions and 27 deletions

View File

@@ -0,0 +1,116 @@
"use client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { TrendingUp, TrendingDown } from "lucide-react";
import {
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from "recharts";
interface SavingsTrendDataPoint {
month: string;
savings: number;
cumulative: number;
}
interface SavingsTrendChartProps {
data: SavingsTrendDataPoint[];
formatCurrency: (amount: number) => string;
}
export function SavingsTrendChart({
data,
formatCurrency,
}: SavingsTrendChartProps) {
const latestSavings = data.length > 0 ? data[data.length - 1].savings : 0;
const isPositive = latestSavings >= 0;
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle>Évolution des économies</CardTitle>
<div className="flex items-center gap-2">
{isPositive ? (
<TrendingUp className="w-4 h-4 text-emerald-600" />
) : (
<TrendingDown className="w-4 h-4 text-red-600" />
)}
<span
className={`text-sm font-semibold ${
isPositive ? "text-emerald-600" : "text-red-600"
}`}
>
{formatCurrency(latestSavings)}
</span>
</div>
</CardHeader>
<CardContent>
{data.length > 0 ? (
<div className="h-[250px]">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={data}>
<defs>
<linearGradient id="savingsGradient" x1="0" y1="0" x2="0" y2="1">
<stop
offset="5%"
stopColor={isPositive ? "#22c55e" : "#ef4444"}
stopOpacity={0.3}
/>
<stop
offset="95%"
stopColor={isPositive ? "#22c55e" : "#ef4444"}
stopOpacity={0}
/>
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis
dataKey="month"
className="text-xs"
interval="preserveStartEnd"
/>
<YAxis
className="text-xs"
width={80}
tickFormatter={(v) => {
if (Math.abs(v) >= 1000) {
return `${(v / 1000).toFixed(1)}k€`;
}
return `${Math.round(v)}`;
}}
tick={{ fill: "var(--muted-foreground)" }}
/>
<Tooltip
formatter={(value: number) => formatCurrency(value)}
contentStyle={{
backgroundColor: "var(--card)",
border: "1px solid var(--border)",
borderRadius: "8px",
}}
/>
<Area
type="monotone"
dataKey="savings"
name="Économies mensuelles"
stroke={isPositive ? "#22c55e" : "#ef4444"}
strokeWidth={2}
fill="url(#savingsGradient)"
/>
</AreaChart>
</ResponsiveContainer>
</div>
) : (
<div className="h-[250px] flex items-center justify-center text-muted-foreground">
Pas de données pour cette période
</div>
)}
</CardContent>
</Card>
);
}