From edf7cc704d608e568dfa83578be90fc22280b90b Mon Sep 17 00:00:00 2001 From: Manus Date: Sun, 12 Apr 2026 05:20:55 -0400 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Ajout=20du=20filtre=20destinatair?= =?UTF-8?q?e=20dans=20la=20liste=20des=20factures=20(dropdown=20+=20recher?= =?UTF-8?q?che=20textuelle),=20graphique=20"Top=2010=20destinataires"=20et?= =?UTF-8?q?=20liste=20d=C3=A9taill=C3=A9e=20dans=20le=20dashboard,=20filtr?= =?UTF-8?q?e=20destinataire=20dans=20les=20param=C3=A8tres=20SFTP=20(sftpR?= =?UTF-8?q?ecipientFilter)=20avec=20application=20lors=20de=20l'export?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Dashboard.tsx | 137 ++- client/src/pages/Invoices.tsx | 46 +- client/src/pages/Settings.tsx | 19 + drizzle/0016_tricky_quasimodo.sql | 1 + drizzle/meta/0016_snapshot.json | 1471 +++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + drizzle/schema.ts | 1 + server/db.ts | 40 +- server/routers.ts | 17 +- todo.md | 8 + 10 files changed, 1707 insertions(+), 40 deletions(-) create mode 100644 drizzle/0016_tricky_quasimodo.sql create mode 100644 drizzle/meta/0016_snapshot.json diff --git a/client/src/pages/Dashboard.tsx b/client/src/pages/Dashboard.tsx index 6fc890f..115dd0f 100644 --- a/client/src/pages/Dashboard.tsx +++ b/client/src/pages/Dashboard.tsx @@ -218,39 +218,116 @@ export default function Dashboard() { - {/* 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)} + {/* Suppliers List + Recipients List side by side */} +
+ + + 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é +
+ )} + + + + {/* Recipients List */} + + + Détail par destinataire + Liste des destinataires (30 derniers jours) + + + {stats?.recipientsList && stats.recipientsList.length > 0 ? ( +
+ {stats.recipientsList.map((recipient, index) => ( +
+
+
+ {recipient.name} +
+
+
{recipient.count} factures
+
+ {formatCurrency(recipient.amount)} +
+
+
+ ))} +
+ ) : ( +
+ Aucun destinataire trouvé +
+ )} + + +
+ + {/* Top Recipients Pie Chart */} + + + Top 10 destinataires + + Répartition des factures par destinataire (30 derniers jours) + + + + {stats?.topRecipients && stats.topRecipients.length > 0 ? ( + + + `${entry.name}: ${entry.percentage.toFixed(0)}%`} + outerRadius={80} + fill="#8884d8" + dataKey="count" + > + {stats.topRecipients.map((entry, index) => ( + + ))} + + + + ) : ( -
- Aucun fournisseur trouvé +
+ Aucun destinataire trouvé — les factures doivent avoir un champ Destinataire rempli
)} diff --git a/client/src/pages/Invoices.tsx b/client/src/pages/Invoices.tsx index df2c169..c16deb2 100644 --- a/client/src/pages/Invoices.tsx +++ b/client/src/pages/Invoices.tsx @@ -32,7 +32,8 @@ import { TableRow, } from "@/components/ui/table"; import { trpc } from "@/lib/trpc"; -import { Search, FileText, Download, FileSpreadsheet, Trash2, Edit, Trash } from "lucide-react"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Search, FileText, Download, FileSpreadsheet, Trash2, Edit, Trash, Filter } from "lucide-react"; import * as XLSX from 'xlsx'; import { toast } from "sonner"; import { useLocation } from "wouter"; @@ -42,6 +43,7 @@ export default function Invoices() { const [searchQuery, setSearchQuery] = useState(""); const [selectedIds, setSelectedIds] = useState([]); const [statusFilter, setStatusFilter] = useState("all"); + const [recipientFilter, setRecipientFilter] = useState("all"); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [invoiceToDelete, setInvoiceToDelete] = useState(null); const [addDialogOpen, setAddDialogOpen] = useState(false); @@ -175,12 +177,20 @@ export default function Invoices() { } }; + // Collect unique recipients for the filter dropdown + const uniqueRecipients = Array.from( + new Set( + (invoices || []).map(inv => (inv as any).recipientName).filter(Boolean) + ) + ).sort(); + const filteredInvoices = invoices?.filter((inv) => { // Filter by search query if (searchQuery) { const query = searchQuery.toLowerCase(); const matchesSearch = inv.supplierName?.toLowerCase().includes(query) || - inv.invoiceNumber?.toLowerCase().includes(query); + inv.invoiceNumber?.toLowerCase().includes(query) || + (inv as any).recipientName?.toLowerCase().includes(query); if (!matchesSearch) return false; } @@ -190,6 +200,15 @@ export default function Invoices() { if (statusFilter === "not_exported" && inv.exportStatus !== "not_exported") return false; if (statusFilter === "export_error" && inv.exportStatus !== "export_error") return false; } + + // Filter by recipient + if (recipientFilter !== "all") { + if (recipientFilter === "__empty__") { + if ((inv as any).recipientName) return false; + } else { + if ((inv as any).recipientName !== recipientFilter) return false; + } + } return true; }); @@ -336,17 +355,32 @@ export default function Invoices() { - {/* Search */} -
-
+ {/* Search + Recipient Filter */} +
+
setSearchQuery(e.target.value)} className="pl-10" />
+
+ + +
{/* Status Filters */} diff --git a/client/src/pages/Settings.tsx b/client/src/pages/Settings.tsx index fc06bcb..105a20d 100644 --- a/client/src/pages/Settings.tsx +++ b/client/src/pages/Settings.tsx @@ -671,6 +671,7 @@ export default function Settings() { const [sftpPassword, setSftpPassword] = useState(""); const [sftpRemotePath, setSftpRemotePath] = useState("/"); const [sftpAutoExport, setSftpAutoExport] = useState(false); + const [sftpRecipientFilter, setSftpRecipientFilter] = useState(""); const [llmLogsRetentionMonths, setLlmLogsRetentionMonths] = useState(3); useEffect(() => { @@ -689,6 +690,7 @@ export default function Settings() { setSftpPassword(settings.sftpPassword || ""); setSftpRemotePath(settings.sftpRemotePath || "/"); setSftpAutoExport(settings.sftpAutoExport === 1); + setSftpRecipientFilter((settings as any).sftpRecipientFilter || ""); setLlmLogsRetentionMonths(settings.llmLogsRetentionMonths || 3); } }, [settings]); @@ -732,6 +734,7 @@ export default function Settings() { sftpPassword, sftpRemotePath, sftpAutoExport: sftpAutoExport ? 1 : 0, + sftpRecipientFilter, llmLogsRetentionMonths, }); }; @@ -1093,6 +1096,22 @@ export default function Settings() {

+
+ + setSftpRecipientFilter(e.target.value)} + placeholder="Ex: DSI, Service Travaux, Itinova... (vide = exporter toutes)" + className="h-11" + /> +

+ + Seules les factures dont le destinataire contient cette valeur seront exportées automatiquement. Laisser vide pour exporter toutes les factures. +

+