diff --git a/client/src/pages/Dashboard.tsx b/client/src/pages/Dashboard.tsx index bc2756e..6fc890f 100644 --- a/client/src/pages/Dashboard.tsx +++ b/client/src/pages/Dashboard.tsx @@ -1,117 +1,262 @@ import DashboardLayout from "@/components/DashboardLayout"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { trpc } from "@/lib/trpc"; -import { FileText, CheckCircle, Clock, AlertCircle } from "lucide-react"; +import { FileText, DollarSign, TrendingUp, Clock } from "lucide-react"; +import { BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts"; +import { Button } from "@/components/ui/button"; +import { useState } from "react"; + +const COLORS = [ + '#3b82f6', // blue + '#8b5cf6', // purple + '#ec4899', // pink + '#f59e0b', // amber + '#10b981', // emerald + '#06b6d4', // cyan + '#f97316', // orange + '#6366f1', // indigo + '#14b8a6', // teal + '#a855f7', // violet +]; export default function Dashboard() { const { data: stats, isLoading } = trpc.invoices.getStats.useQuery(); - const { data: invoices } = trpc.invoices.list.useQuery(); + const [showDetailedStats, setShowDetailedStats] = useState(false); - const recentInvoices = invoices?.slice(0, 5) || []; + if (isLoading) { + return ( + +
+
Chargement des statistiques...
+
+
+ ); + } + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('fr-FR', { + style: 'currency', + currency: 'EUR', + }).format(amount); + }; + + const formatDate = (date: Date | string | null) => { + if (!date) return 'N/A'; + return new Date(date).toLocaleDateString('fr-FR', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); + }; + + const getScoreLabel = (score: number) => { + if (score >= 90) return 'Excellent'; + if (score >= 75) return 'Bon'; + if (score >= 60) return 'Moyen'; + return 'Faible'; + }; + + const getScoreColor = (score: number) => { + if (score >= 90) return 'text-green-600'; + if (score >= 75) return 'text-blue-600'; + if (score >= 60) return 'text-yellow-600'; + return 'text-red-600'; + }; return (
-
-

Tableau de bord

-

Vue d'ensemble de vos factures

+ {/* Header */} +
+
+

Tableau de bord

+

Activité des 30 derniers jours

+
+
- {/* Stats cards */} -
+ {/* Stats Cards */} +
- - Total - + + Factures traitées + -
{stats?.total || 0}
-

Factures au total

+
{stats?.completed || 0}
+

+ {stats?.completed || 0} complétées, {stats?.error || 0} en erreur +

- - Complétées - + + Montant total + -
{stats?.completed || 0}
-

Extraction réussie

+
{formatCurrency(stats?.totalAmount || 0)}
+

+ Moyenne : {stats?.completed ? formatCurrency((stats.totalAmount || 0) / stats.completed) : '0 €'} / facture +

- - En cours - + + Score moyen + -
{stats?.processing || 0}
-

En traitement

-
-
- - - - Erreurs - - - -
{stats?.error || 0}
-

Échecs d'extraction

-
-
-
- - {/* Recent invoices */} - - - Factures récentes - Les 5 dernières factures importées - - - {recentInvoices.length === 0 ? ( -
- -

Aucune facture pour le moment

-

Commencez par importer un fichier PDF

+
+
+ {stats?.averageScore || 0} +
+
+ {getScoreLabel(stats?.averageScore || 0)} +
- ) : ( -
- {recentInvoices.map((invoice) => ( -
-
-
{invoice.supplierName || "Fournisseur inconnu"}
-
- {invoice.invoiceNumber || "N° inconnu"} • {invoice.invoiceDate ? new Date(invoice.invoiceDate).toLocaleDateString("fr-FR") : "Date inconnue"} -
+

Qualité d'extraction

+ + + + + + Dernière activité + + + +
+ {stats?.lastActivity + ? new Date(stats.lastActivity).toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit' }) + : 'N/A'} +
+

+ {stats?.lastActivity + ? new Date(stats.lastActivity).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }) + : ''} +

+
+
+
+ + {/* Detailed Stats */} + {showDetailedStats && ( + <> + {/* Charts Row */} +
+ {/* Weekly Chart */} + + + Factures par semaine + + Nombre de factures traitées chaque semaine (30 derniers jours) + + + + {stats?.weeklyData && stats.weeklyData.length > 0 ? ( + + + + + + + + + + ) : ( +
+ Aucune donnée disponible
-
-
- {invoice.totalAmount ? `${parseFloat(invoice.totalAmount).toFixed(2)} €` : "-"} -
-
- {invoice.status === "completed" && ( - Complété - )} - {invoice.status === "processing" && ( - En cours - )} - {invoice.status === "error" && ( - Erreur - )} -
+ )} + + + + {/* Top Suppliers Pie Chart */} + + + Top 10 fournisseurs + + Répartition des factures par fournisseur (30 derniers jours) + + + + {stats?.topSuppliers && stats.topSuppliers.length > 0 ? ( + + + `${entry.name}: ${entry.percentage.toFixed(0)}%`} + outerRadius={80} + fill="#8884d8" + dataKey="count" + > + {stats.topSuppliers.map((entry, index) => ( + + ))} + + + + + ) : ( +
+ Aucune donnée disponible
+ )} +
+
+
+ + {/* Suppliers List */} + + + Détail par fournisseur + Liste des fournisseurs (30 derniers jours) + + + {stats?.suppliersList && stats.suppliersList.length > 0 ? ( +
+ {stats.suppliersList.map((supplier, index) => ( +
+
+
+ {supplier.name} +
+
+
{supplier.count} factures
+
+ {formatCurrency(supplier.amount)} +
+
+
+ ))}
- ))} -
- )} -
-
+ ) : ( +
+ Aucun fournisseur trouvé +
+ )} +
+
+ + )}
); diff --git a/server/db.ts b/server/db.ts index 81be172..1ab2bfb 100644 --- a/server/db.ts +++ b/server/db.ts @@ -252,14 +252,97 @@ export async function searchInvoices(userId: number, query: string): Promise i.status === "completed"); + const totalAmount = completed.reduce((sum, inv) => sum + (Number(inv.totalAmount) || 0), 0); + const averageScore = completed.length > 0 + ? completed.reduce((sum, inv) => sum + (inv.qualityScore || 0), 0) / completed.length + : 0; + + // Get last activity + const sortedByDate = [...allInvoices].sort((a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + ); + const lastActivity = sortedByDate[0]?.createdAt || null; + + // Calculate weekly data (last 30 days) + const now = new Date(); + const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + const weeklyMap = new Map(); + + allInvoices.forEach(inv => { + const date = new Date(inv.createdAt); + if (date >= thirtyDaysAgo) { + const weekStart = new Date(date); + weekStart.setDate(date.getDate() - date.getDay()); // Start of week + const weekKey = `${weekStart.getDate().toString().padStart(2, '0')}/${(weekStart.getMonth() + 1).toString().padStart(2, '0')}`; + weeklyMap.set(weekKey, (weeklyMap.get(weekKey) || 0) + 1); + } + }); + + const weeklyData = Array.from(weeklyMap.entries()) + .map(([week, count]) => ({ week, count })) + .sort((a, b) => { + const [dayA, monthA] = a.week.split('/').map(Number); + const [dayB, monthB] = b.week.split('/').map(Number); + return (monthA! * 100 + dayA!) - (monthB! * 100 + dayB!); + }); + + // Calculate top suppliers + const supplierMap = new Map(); + completed.forEach(inv => { + const supplier = inv.supplierName || 'Inconnu'; + const current = supplierMap.get(supplier) || { count: 0, amount: 0 }; + supplierMap.set(supplier, { + count: current.count + 1, + amount: current.amount + (Number(inv.totalAmount) || 0) + }); + }); + + const topSuppliers = Array.from(supplierMap.entries()) + .map(([name, data]) => ({ + name, + count: data.count, + amount: data.amount, + percentage: completed.length > 0 ? (data.count / completed.length * 100) : 0 + })) + .sort((a, b) => b.count - a.count) + .slice(0, 10); + + const suppliersList = Array.from(supplierMap.entries()) + .map(([name, data]) => ({ + name, + count: data.count, + amount: data.amount + })) + .sort((a, b) => b.count - a.count); + return { total: allInvoices.length, - completed: allInvoices.filter(i => i.status === "completed").length, + completed: completed.length, processing: allInvoices.filter(i => i.status === "processing").length, error: allInvoices.filter(i => i.status === "error").length, + totalAmount, + averageScore: Math.round(averageScore), + lastActivity, + weeklyData, + topSuppliers, + suppliersList }; } diff --git a/todo.md b/todo.md index 223d492..bfb5aa1 100644 --- a/todo.md +++ b/todo.md @@ -66,3 +66,11 @@ - [x] Diagnostiquer le problème de connexion locale avec o.pareige@itinova.org - [x] Corriger l'authentification locale (ajout support cookie auth_token) + +## Amélioration du Dashboard +- [x] Créer route tRPC pour statistiques détaillées (factures par semaine, top fournisseurs) +- [x] Créer cartes de statistiques (Factures traitées, Montant total, Score moyen, Dernière activité) +- [x] Créer graphique en barres des factures par semaine (Recharts) +- [x] Créer graphique circulaire du top 10 fournisseurs (Recharts) +- [x] Créer liste détaillée des fournisseurs avec nombre de factures +- [x] Ajouter bouton "Voir les statistiques détaillées"