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
| - 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') && (
+
+
+
+
+
-
|
+
|
- Fournisseur
- |
- - 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 && (
-
- )}
-
- |
-
|
+ Fournisseur
+ |
+ + 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') && (
+
+ )}
+
+
+ |
+