From aee6f7cf5ca22eda030ce76d9348967c5bacd8b8 Mon Sep 17 00:00:00 2001 From: michaelschal <35957947-michaelschal@users.noreply.replit.com> Date: Tue, 12 Aug 2025 12:10:40 +0000 Subject: [PATCH] Add pagination and card design to the reconciliation page Update the BLReconciliation page to include pagination controls at the top and bottom of the table, limit to 20 items per page, and refactor the UI to use a card-based layout. Replit-Commit-Author: Agent Replit-Commit-Session-Id: b163d4c0-de5e-4f4e-a9c0-aed4c7049718 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/b163d4c0-de5e-4f4e-a9c0-aed4c7049718/pf3Qeu9 --- client/src/pages/BLReconciliation.tsx | 829 +++++++++++++------------- 1 file changed, 408 insertions(+), 421 deletions(-) diff --git a/client/src/pages/BLReconciliation.tsx b/client/src/pages/BLReconciliation.tsx index c2243a9..5b1d3be 100644 --- a/client/src/pages/BLReconciliation.tsx +++ b/client/src/pages/BLReconciliation.tsx @@ -4,13 +4,15 @@ import { safeFormat } from "@/lib/dateUtils"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { useToast } from "@/hooks/use-toast"; import { apiRequest } from "@/lib/queryClient"; import { useStore } from "@/components/Layout"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { usePermissions } from "@shared/permissions"; -import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X, Check, Trash2, Ban } from "lucide-react"; +import { Pagination, usePagination } from "@/components/ui/pagination"; +import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X, Check, Trash2, Ban, Filter } from "lucide-react"; import ReconciliationModal from "@/components/modals/ReconciliationModal"; export default function BLReconciliation() { @@ -73,50 +75,52 @@ export default function BLReconciliation() { return filtered.sort((a: any, b: any) => new Date(b.deliveredDate || b.updatedAt).getTime() - new Date(a.deliveredDate || a.updatedAt).getTime()); }, + enabled: !!user }); - // Séparer les livraisons selon le mode de rapprochement du fournisseur + // Séparer les livraisons par mode de rapprochement const manualReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => { - const supplier = suppliers.find((s: any) => s.id === delivery.supplierId); - return !supplier?.automaticReconciliation; + const supplier = suppliers.find(s => s.id === delivery.supplierId); + return !supplier?.isAutoReconciliation; }); const automaticReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => { - const supplier = suppliers.find((s: any) => s.id === delivery.supplierId); - return supplier?.automaticReconciliation; + const supplier = suppliers.find(s => s.id === delivery.supplierId); + return supplier?.isAutoReconciliation; }); - // Fonction pour ouvrir le modal d'édition + // Fonctions de gestion const handleOpenModal = (delivery: any) => { setSelectedDelivery(delivery); setIsModalOpen(true); }; - // Fonction pour valider un rapprochement rapidement (avec validation des données) + const handleCloseModal = () => { + setSelectedDelivery(null); + setIsModalOpen(false); + }; + + const handleSaveReconciliation = async (data: any) => { + try { + await apiRequest(`/api/deliveries/${selectedDelivery.id}`, "PUT", data); + + toast({ + title: "Succès", + description: "Données de rapprochement mises à jour", + }); + + queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); + handleCloseModal(); + } catch (error) { + toast({ + title: "Erreur", + description: "Impossible de mettre à jour les données", + variant: "destructive", + }); + } + }; + const handleQuickValidate = async (delivery: any) => { - if (!permissions.canValidate('reconciliation')) { - toast({ - title: "Accès refusé", - description: "Vous n'avez pas les permissions pour valider les rapprochements", - variant: "destructive", - }); - return; - } - - // Vérifier que les données minimales sont présentes - if (!delivery.blNumber || delivery.blNumber.trim() === '') { - toast({ - title: "Données manquantes", - description: "Le numéro de BL est requis. Cliquez sur 'Voir les détails' pour le renseigner.", - variant: "destructive", - }); - return; - } - - if (!window.confirm("Êtes-vous sûr de vouloir valider ce rapprochement ?")) { - return; - } - try { await apiRequest(`/api/deliveries/${delivery.id}`, "PUT", { reconciled: true, @@ -138,7 +142,6 @@ export default function BLReconciliation() { } }; - // Fonction pour dévalider un rapprochement (admins uniquement) const handleDevalidateReconciliation = async (deliveryId: number) => { if (user?.role !== 'admin') { toast({ @@ -174,7 +177,6 @@ export default function BLReconciliation() { } }; - // Fonction pour supprimer une livraison (admins uniquement) const handleDeleteDelivery = async (deliveryId: number) => { if (!permissions.canDelete('reconciliation')) { toast({ @@ -207,42 +209,6 @@ export default function BLReconciliation() { } }; - // Fonction pour dévalider un rapprochement automatique (admins uniquement) - const handleDevalidateAutoReconciliation = async (deliveryId: number) => { - if (user?.role !== 'admin') { - toast({ - title: "Accès refusé", - description: "Seuls les administrateurs peuvent dévalider les rapprochements automatiques", - variant: "destructive", - }); - return; - } - - if (!window.confirm("Êtes-vous sûr de vouloir dévalider ce rapprochement automatique ?")) { - return; - } - - try { - await apiRequest(`/api/deliveries/${deliveryId}`, "PUT", { - reconciled: false, - validatedAt: null - }); - - toast({ - title: "Succès", - description: "Rapprochement automatique dévalidé avec succès", - }); - - queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); - } catch (error) { - toast({ - title: "Erreur", - description: "Impossible de dévalider le rapprochement", - variant: "destructive", - }); - } - }; - // Filtrage des livraisons par recherche const filterDeliveries = (deliveries: any[]) => { return deliveries.filter((delivery: any) => { @@ -258,11 +224,37 @@ export default function BLReconciliation() { const filteredManualDeliveries = filterDeliveries(manualReconciliationDeliveries); const filteredAutomaticDeliveries = filterDeliveries(automaticReconciliationDeliveries); + // Pagination pour les rapprochements manuels + const { + currentPage: manualCurrentPage, + setCurrentPage: setManualCurrentPage, + itemsPerPage: manualItemsPerPage, + setItemsPerPage: setManualItemsPerPage, + totalPages: manualTotalPages, + paginatedData: paginatedManualDeliveries, + totalItems: manualTotalItems + } = usePagination(filteredManualDeliveries, 20); + + // Pagination pour les rapprochements automatiques + const { + currentPage: autoCurrentPage, + setCurrentPage: setAutoCurrentPage, + itemsPerPage: autoItemsPerPage, + setItemsPerPage: setAutoItemsPerPage, + totalPages: autoTotalPages, + paginatedData: paginatedAutoDeliveries, + totalItems: autoTotalItems + } = usePagination(filteredAutomaticDeliveries, 20); + const canModify = user?.role === 'directeur' || user?.role === 'admin'; + if (isLoading) { + return
Chargement...
; + } + return (
- {/* Header avec onglets */} + {/* Header */}
@@ -306,17 +298,19 @@ export default function BLReconciliation() {
{/* Filtre de recherche */} -
-
- - setSearchTerm(e.target.value)} - className="pl-10 border border-gray-300 shadow-sm" - /> -
-
+ + +
+ + setSearchTerm(e.target.value)} + className="pl-10 border border-gray-300 shadow-sm" + /> +
+
+
{/* Contenu des onglets */} @@ -332,182 +326,210 @@ export default function BLReconciliation() {

) : ( -
-
- - - - - - - - - - - - - - - - {filteredManualDeliveries.map((delivery: any) => ( - - - - - - - - - - + + ))} + +
- Fournisseur - - N° BL - - Date Livr. - - Montant BL - - Ref. Facture - - Montant Fact. - - Écart - - Magasin - - Actions -
-
- {delivery.supplier?.name} -
-
-
- {delivery.blNumber || ( - Non renseigné - )} -
-
-
- {safeFormat(delivery.scheduledDate, 'dd/MM/yy')} -
-
-
- {delivery.blAmount ? - `${parseFloat(delivery.blAmount).toFixed(2)}€` : - Non renseigné - } -
-
-
- {delivery.invoiceReference || ( - Non renseigné - )} -
-
-
- {delivery.invoiceAmount ? - `${parseFloat(delivery.invoiceAmount).toFixed(2)}€` : - Non renseigné - } -
-
- {(() => { - const blAmount = delivery.blAmount ? parseFloat(delivery.blAmount) : 0; - const invoiceAmount = delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0; - if (blAmount && invoiceAmount) { - const diff = blAmount - invoiceAmount; - const diffAbs = Math.abs(diff); - return ( -
10 ? 'text-red-600' : 'text-orange-600' - }`}> - {diff > 0 ? '+' : ''}{diff.toFixed(2)}€ -
- ); - } - return -; - })()} -
-
- {delivery.group?.name} -
-
-
- {/* Actions selon le statut de rapprochement et les permissions */} - {!delivery.reconciled ? ( - // Livraison non rapprochée : Valider + Supprimer - <> - {permissions.canValidate('reconciliation') && ( + + + + + Rapprochements Manuels + + + + {/* Pagination du haut */} + + +
+
+ + + + + + + + + + + + + + + + {paginatedManualDeliveries.map((delivery: any) => ( + + + + + + + + + + - - ))} - -
+ Fournisseur + + N° BL + + Date Livr. + + Montant BL + + Ref. Facture + + Montant Fact. + + Écart + + Magasin + + Actions +
+
+ {delivery.supplier?.name} +
+
+
+ {delivery.blNumber || ( + Non renseigné + )} +
+
+
+ {safeFormat(delivery.scheduledDate, 'dd/MM/yy')} +
+
+
+ {delivery.blAmount ? + `${parseFloat(delivery.blAmount).toFixed(2)}€` : + Non renseigné + } +
+
+
+ {delivery.invoiceReference || ( + Non renseigné + )} +
+
+
+ {delivery.invoiceAmount ? + `${parseFloat(delivery.invoiceAmount).toFixed(2)}€` : + Non renseigné + } +
+
+ {(() => { + const blAmount = delivery.blAmount ? parseFloat(delivery.blAmount) : 0; + const invoiceAmount = delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0; + if (blAmount && invoiceAmount) { + const diff = blAmount - invoiceAmount; + const diffAbs = Math.abs(diff); + return ( +
10 ? 'text-red-600' : 'text-orange-600' + }`}> + {diff > 0 ? '+' : ''}{diff.toFixed(2)}€ +
+ ); + } + return -; + })()} +
+
+ {delivery.group?.name} +
+
+
+ {!delivery.reconciled ? ( + <> + {permissions.canValidate('reconciliation') && ( + + )} + {permissions.canDelete('reconciliation') && ( + + )} + + ) : ( + <> + {user?.role === 'admin' && ( + + )} + {permissions.canDelete('reconciliation') && ( + + )} + + )} + {!delivery.reconciled && ( )} - {permissions.canDelete('reconciliation') && ( - - )} - - ) : ( - // Livraison rapprochée : Dévalider (admin seulement) + Supprimer - <> - {user?.role === 'admin' && ( - - )} - {permissions.canDelete('reconciliation') && ( - - )} - - )} - {/* Bouton Modifier : disponible seulement si pas encore validé */} - {!delivery.reconciled && ( - - )} -
-
-
-
+
+
+
+
+ + {/* Pagination du bas */} + + + )} @@ -541,179 +563,144 @@ export default function BLReconciliation() {

) : ( -
-
- - - - - - - - - - - - - - - - - {filteredAutomaticDeliveries.map((delivery: any) => ( - - - - - - - - - - - - - ))} - -
- Fournisseur - AUTO - - N° BL - - Date Livr. - - Date Valid. - - Montant BL - - Ref. Facture - - Montant Fact. - - Écart - - Magasin - - Actions -
-
-
- {delivery.supplier?.name} -
- -
-
-
- {delivery.blNumber || '-'} -
-
-
- {safeFormat(delivery.scheduledDate, 'dd/MM/yy')} -
-
-
- {delivery.reconciled && delivery.updatedAt ? - safeFormat(delivery.updatedAt, 'dd/MM/yy') : - '-' - } -
-
-
- {delivery.blAmount ? - `${parseFloat(delivery.blAmount).toFixed(2)}€` : - '-' - } -
-
-
- {delivery.invoiceReference || '-'} -
-
-
- {delivery.invoiceAmount ? - `${parseFloat(delivery.invoiceAmount).toFixed(2)}€` : - '-' - } -
-
- {(() => { - const blAmount = delivery.blAmount ? parseFloat(delivery.blAmount) : 0; - const invoiceAmount = delivery.invoiceAmount ? parseFloat(delivery.invoiceAmount) : 0; - if (blAmount && invoiceAmount) { - const diff = blAmount - invoiceAmount; - const diffAbs = Math.abs(diff); - return ( -
10 ? 'text-red-600' : 'text-orange-600' - }`}> - {diff > 0 ? '+' : ''}{diff.toFixed(2)}€ -
- ); - } - return -; - })()} -
-
- {delivery.group?.name} -
-
-
- {user?.role === 'admin' && delivery.reconciled && ( - - )} - {user?.role === 'admin' && ( - - )} - {/* Bouton Modifier : disponible seulement si pas encore validé */} - {!delivery.reconciled && ( - - )} -
-
-
-
+ + + + + Rapprochements Automatiques + + + + {/* Pagination du haut */} + + +
+
+ + + + + + + + + + + + + {paginatedAutoDeliveries.map((delivery: any) => ( + + + + + + + + + ))} + +
+ Fournisseur + AUTO + + N° BL + + Date Livr. + + Date Valid. + + Magasin + + Actions +
+
+ {delivery.supplier?.name} +
+
+
+ {delivery.blNumber || ( + Non renseigné + )} +
+
+
+ {safeFormat(delivery.scheduledDate, 'dd/MM/yy')} +
+
+
+ {delivery.validatedAt ? + safeFormat(delivery.validatedAt, 'dd/MM/yy HH:mm') : + Non validé + } +
+
+
+ {delivery.group?.name} +
+
+
+ {user?.role === 'admin' && ( + + )} + {permissions.canDelete('reconciliation') && ( + + )} + +
+
+
+
+ + {/* Pagination du bas */} + +
+
)} {/* Modal de rapprochement */} - setIsModalOpen(false)} - delivery={selectedDelivery} - onSave={() => { - // Rafraîchir les données après sauvegarde - queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] }); - }} - /> + {isModalOpen && selectedDelivery && ( + + )}
); } \ No newline at end of file