mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-23 06:58:19 +02:00
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
This commit is contained in:
1 parent
0fa48697a1
commit
aee6f7cf5c
1 file changed
+153
-166
@@ -4,13 +4,15 @@ import { safeFormat } from "@/lib/dateUtils";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Badge } from "@/components/ui/badge";
|
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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { apiRequest } from "@/lib/queryClient";
|
import { apiRequest } from "@/lib/queryClient";
|
||||||
import { useStore } from "@/components/Layout";
|
import { useStore } from "@/components/Layout";
|
||||||
import { useAuthUnified } from "@/hooks/useAuthUnified";
|
import { useAuthUnified } from "@/hooks/useAuthUnified";
|
||||||
import { usePermissions } from "@shared/permissions";
|
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";
|
import ReconciliationModal from "@/components/modals/ReconciliationModal";
|
||||||
|
|
||||||
export default function BLReconciliation() {
|
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());
|
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 manualReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => {
|
||||||
const supplier = suppliers.find((s: any) => s.id === delivery.supplierId);
|
const supplier = suppliers.find(s => s.id === delivery.supplierId);
|
||||||
return !supplier?.automaticReconciliation;
|
return !supplier?.isAutoReconciliation;
|
||||||
});
|
});
|
||||||
|
|
||||||
const automaticReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => {
|
const automaticReconciliationDeliveries = deliveriesWithBL.filter((delivery: any) => {
|
||||||
const supplier = suppliers.find((s: any) => s.id === delivery.supplierId);
|
const supplier = suppliers.find(s => s.id === delivery.supplierId);
|
||||||
return supplier?.automaticReconciliation;
|
return supplier?.isAutoReconciliation;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fonction pour ouvrir le modal d'édition
|
// Fonctions de gestion
|
||||||
const handleOpenModal = (delivery: any) => {
|
const handleOpenModal = (delivery: any) => {
|
||||||
setSelectedDelivery(delivery);
|
setSelectedDelivery(delivery);
|
||||||
setIsModalOpen(true);
|
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) => {
|
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 {
|
try {
|
||||||
await apiRequest(`/api/deliveries/${delivery.id}`, "PUT", {
|
await apiRequest(`/api/deliveries/${delivery.id}`, "PUT", {
|
||||||
reconciled: true,
|
reconciled: true,
|
||||||
@@ -138,7 +142,6 @@ export default function BLReconciliation() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fonction pour dévalider un rapprochement (admins uniquement)
|
|
||||||
const handleDevalidateReconciliation = async (deliveryId: number) => {
|
const handleDevalidateReconciliation = async (deliveryId: number) => {
|
||||||
if (user?.role !== 'admin') {
|
if (user?.role !== 'admin') {
|
||||||
toast({
|
toast({
|
||||||
@@ -174,7 +177,6 @@ export default function BLReconciliation() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fonction pour supprimer une livraison (admins uniquement)
|
|
||||||
const handleDeleteDelivery = async (deliveryId: number) => {
|
const handleDeleteDelivery = async (deliveryId: number) => {
|
||||||
if (!permissions.canDelete('reconciliation')) {
|
if (!permissions.canDelete('reconciliation')) {
|
||||||
toast({
|
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
|
// Filtrage des livraisons par recherche
|
||||||
const filterDeliveries = (deliveries: any[]) => {
|
const filterDeliveries = (deliveries: any[]) => {
|
||||||
return deliveries.filter((delivery: any) => {
|
return deliveries.filter((delivery: any) => {
|
||||||
@@ -258,11 +224,37 @@ export default function BLReconciliation() {
|
|||||||
const filteredManualDeliveries = filterDeliveries(manualReconciliationDeliveries);
|
const filteredManualDeliveries = filterDeliveries(manualReconciliationDeliveries);
|
||||||
const filteredAutomaticDeliveries = filterDeliveries(automaticReconciliationDeliveries);
|
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';
|
const canModify = user?.role === 'directeur' || user?.role === 'admin';
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <div className="flex justify-center items-center h-64">Chargement...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-6 space-y-6">
|
<div className="p-6 space-y-6">
|
||||||
{/* Header avec onglets */}
|
{/* Header */}
|
||||||
<div className="bg-white border-b border-gray-200 p-6 shadow-sm -m-6 mb-6">
|
<div className="bg-white border-b border-gray-200 p-6 shadow-sm -m-6 mb-6">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<div>
|
<div>
|
||||||
@@ -306,7 +298,8 @@ export default function BLReconciliation() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filtre de recherche */}
|
{/* Filtre de recherche */}
|
||||||
<div className="bg-gray-50 border border-gray-200 p-4 rounded-lg">
|
<Card>
|
||||||
|
<CardContent className="p-4">
|
||||||
<div className="relative max-w-md">
|
<div className="relative max-w-md">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||||
<Input
|
<Input
|
||||||
@@ -316,7 +309,8 @@ export default function BLReconciliation() {
|
|||||||
className="pl-10 border border-gray-300 shadow-sm"
|
className="pl-10 border border-gray-300 shadow-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Contenu des onglets */}
|
{/* Contenu des onglets */}
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||||
@@ -332,10 +326,29 @@ export default function BLReconciliation() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-white border border-gray-200 shadow-lg overflow-hidden rounded-lg">
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Edit className="w-5 h-5" />
|
||||||
|
Rapprochements Manuels
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{/* Pagination du haut */}
|
||||||
|
<Pagination
|
||||||
|
currentPage={manualCurrentPage}
|
||||||
|
totalPages={manualTotalPages}
|
||||||
|
totalItems={manualTotalItems}
|
||||||
|
itemsPerPage={manualItemsPerPage}
|
||||||
|
onPageChange={setManualCurrentPage}
|
||||||
|
onItemsPerPageChange={setManualItemsPerPage}
|
||||||
|
className="mb-4"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="border border-gray-200 rounded-lg overflow-hidden">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50 border-b border-gray-200">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Fournisseur
|
Fournisseur
|
||||||
@@ -367,7 +380,7 @@ export default function BLReconciliation() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{filteredManualDeliveries.map((delivery: any) => (
|
{paginatedManualDeliveries.map((delivery: any) => (
|
||||||
<tr
|
<tr
|
||||||
key={delivery.id}
|
key={delivery.id}
|
||||||
className={`hover:bg-gray-50 ${
|
className={`hover:bg-gray-50 ${
|
||||||
@@ -444,9 +457,7 @@ export default function BLReconciliation() {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-sm">
|
<td className="px-3 py-2 text-sm">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
{/* Actions selon le statut de rapprochement et les permissions */}
|
|
||||||
{!delivery.reconciled ? (
|
{!delivery.reconciled ? (
|
||||||
// Livraison non rapprochée : Valider + Supprimer
|
|
||||||
<>
|
<>
|
||||||
{permissions.canValidate('reconciliation') && (
|
{permissions.canValidate('reconciliation') && (
|
||||||
<button
|
<button
|
||||||
@@ -468,7 +479,6 @@ export default function BLReconciliation() {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
// Livraison rapprochée : Dévalider (admin seulement) + Supprimer
|
|
||||||
<>
|
<>
|
||||||
{user?.role === 'admin' && (
|
{user?.role === 'admin' && (
|
||||||
<button
|
<button
|
||||||
@@ -490,7 +500,6 @@ export default function BLReconciliation() {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{/* Bouton Modifier : disponible seulement si pas encore validé */}
|
|
||||||
{!delivery.reconciled && (
|
{!delivery.reconciled && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleOpenModal(delivery)}
|
onClick={() => handleOpenModal(delivery)}
|
||||||
@@ -508,6 +517,19 @@ export default function BLReconciliation() {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Pagination du bas */}
|
||||||
|
<Pagination
|
||||||
|
currentPage={manualCurrentPage}
|
||||||
|
totalPages={manualTotalPages}
|
||||||
|
totalItems={manualTotalItems}
|
||||||
|
itemsPerPage={manualItemsPerPage}
|
||||||
|
onPageChange={setManualCurrentPage}
|
||||||
|
onItemsPerPageChange={setManualItemsPerPage}
|
||||||
|
className="mt-4"
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
@@ -541,10 +563,29 @@ export default function BLReconciliation() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-white border border-gray-200 shadow-lg overflow-hidden rounded-lg">
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Settings className="w-5 h-5" />
|
||||||
|
Rapprochements Automatiques
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{/* Pagination du haut */}
|
||||||
|
<Pagination
|
||||||
|
currentPage={autoCurrentPage}
|
||||||
|
totalPages={autoTotalPages}
|
||||||
|
totalItems={autoTotalItems}
|
||||||
|
itemsPerPage={autoItemsPerPage}
|
||||||
|
onPageChange={setAutoCurrentPage}
|
||||||
|
onItemsPerPageChange={setAutoItemsPerPage}
|
||||||
|
className="mb-4"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="border border-gray-200 rounded-lg overflow-hidden">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50 border-b border-gray-200">
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Fournisseur
|
Fournisseur
|
||||||
@@ -559,18 +600,6 @@ export default function BLReconciliation() {
|
|||||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Date Valid.
|
Date Valid.
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Montant BL
|
|
||||||
</th>
|
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Ref. Facture
|
|
||||||
</th>
|
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Montant Fact.
|
|
||||||
</th>
|
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
||||||
Écart
|
|
||||||
</th>
|
|
||||||
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||||
Magasin
|
Magasin
|
||||||
</th>
|
</th>
|
||||||
@@ -580,28 +609,18 @@ export default function BLReconciliation() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{filteredAutomaticDeliveries.map((delivery: any) => (
|
{paginatedAutoDeliveries.map((delivery: any) => (
|
||||||
<tr
|
<tr key={delivery.id} className="hover:bg-gray-50 bg-green-50">
|
||||||
key={delivery.id}
|
|
||||||
className={`hover:bg-gray-50 ${
|
|
||||||
delivery.reconciled === true
|
|
||||||
? 'bg-gray-100 opacity-60 text-gray-500'
|
|
||||||
: delivery.reconciled === false
|
|
||||||
? 'bg-red-50 border-l-4 border-red-400'
|
|
||||||
: 'bg-green-50'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<td className="px-3 py-2 text-sm">
|
<td className="px-3 py-2 text-sm">
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<div className="font-medium text-gray-900 truncate max-w-32">
|
<div className="font-medium text-gray-900 truncate max-w-32">
|
||||||
{delivery.supplier?.name}
|
{delivery.supplier?.name}
|
||||||
</div>
|
</div>
|
||||||
<Settings className="w-3 h-3 text-blue-500" />
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-sm">
|
<td className="px-3 py-2 text-sm">
|
||||||
<div className="text-gray-900 font-medium">
|
<div className="text-gray-900">
|
||||||
{delivery.blNumber || '-'}
|
{delivery.blNumber || (
|
||||||
|
<span className="text-gray-400 italic text-xs">Non renseigné</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-sm">
|
<td className="px-3 py-2 text-sm">
|
||||||
@@ -611,52 +630,12 @@ export default function BLReconciliation() {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-sm">
|
<td className="px-3 py-2 text-sm">
|
||||||
<div className="text-gray-900">
|
<div className="text-gray-900">
|
||||||
{delivery.reconciled && delivery.updatedAt ?
|
{delivery.validatedAt ?
|
||||||
safeFormat(delivery.updatedAt, 'dd/MM/yy') :
|
safeFormat(delivery.validatedAt, 'dd/MM/yy HH:mm') :
|
||||||
'-'
|
<span className="text-gray-400 italic text-xs">Non validé</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-sm">
|
|
||||||
<div className="font-medium text-gray-900">
|
|
||||||
{delivery.blAmount ?
|
|
||||||
`${parseFloat(delivery.blAmount).toFixed(2)}€` :
|
|
||||||
'-'
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-3 py-2 text-sm">
|
|
||||||
<div className="text-gray-900">
|
|
||||||
{delivery.invoiceReference || '-'}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-3 py-2 text-sm">
|
|
||||||
<div className="text-gray-900">
|
|
||||||
{delivery.invoiceAmount ?
|
|
||||||
`${parseFloat(delivery.invoiceAmount).toFixed(2)}€` :
|
|
||||||
'-'
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="px-3 py-2 text-sm">
|
|
||||||
{(() => {
|
|
||||||
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 (
|
|
||||||
<div className={`font-medium ${
|
|
||||||
diff === 0 ? 'text-green-600' :
|
|
||||||
diffAbs > 10 ? 'text-red-600' : 'text-orange-600'
|
|
||||||
}`}>
|
|
||||||
{diff > 0 ? '+' : ''}{diff.toFixed(2)}€
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <span className="text-gray-400 italic text-xs">-</span>;
|
|
||||||
})()}
|
|
||||||
</td>
|
|
||||||
<td className="px-3 py-2 text-sm">
|
<td className="px-3 py-2 text-sm">
|
||||||
<div className="text-gray-900">
|
<div className="text-gray-900">
|
||||||
{delivery.group?.name}
|
{delivery.group?.name}
|
||||||
@@ -664,16 +643,16 @@ export default function BLReconciliation() {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 text-sm">
|
<td className="px-3 py-2 text-sm">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
{user?.role === 'admin' && delivery.reconciled && (
|
{user?.role === 'admin' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDevalidateAutoReconciliation(delivery.id)}
|
onClick={() => handleDevalidateReconciliation(delivery.id)}
|
||||||
className="text-gray-600 hover:text-orange-600 transition-colors duration-200 p-1 hover:bg-orange-50 rounded"
|
className="text-gray-600 hover:text-orange-600 transition-colors duration-200 p-1 hover:bg-orange-50 rounded"
|
||||||
title="Dévalider le rapprochement automatique"
|
title="Dévalider le rapprochement automatique"
|
||||||
>
|
>
|
||||||
<Ban className="w-4 h-4" />
|
<Ban className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{user?.role === 'admin' && (
|
{permissions.canDelete('reconciliation') && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDeleteDelivery(delivery.id)}
|
onClick={() => handleDeleteDelivery(delivery.id)}
|
||||||
className="text-gray-600 hover:text-red-600 transition-colors duration-200 p-1 hover:bg-red-50 rounded"
|
className="text-gray-600 hover:text-red-600 transition-colors duration-200 p-1 hover:bg-red-50 rounded"
|
||||||
@@ -682,16 +661,13 @@ export default function BLReconciliation() {
|
|||||||
<Trash2 className="w-4 h-4" />
|
<Trash2 className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{/* Bouton Modifier : disponible seulement si pas encore validé */}
|
|
||||||
{!delivery.reconciled && (
|
|
||||||
<button
|
<button
|
||||||
onClick={() => handleOpenModal(delivery)}
|
onClick={() => handleOpenModal(delivery)}
|
||||||
className="text-gray-600 hover:text-blue-600 transition-colors duration-200 p-1 hover:bg-blue-50 rounded"
|
className="text-gray-600 hover:text-blue-600 transition-colors duration-200 p-1 hover:bg-blue-50 rounded"
|
||||||
title="Modifier les données de rapprochement"
|
title="Voir les détails"
|
||||||
>
|
>
|
||||||
<Settings className="w-4 h-4" />
|
<Eye className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -700,20 +676,31 @@ export default function BLReconciliation() {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Pagination du bas */}
|
||||||
|
<Pagination
|
||||||
|
currentPage={autoCurrentPage}
|
||||||
|
totalPages={autoTotalPages}
|
||||||
|
totalItems={autoTotalItems}
|
||||||
|
itemsPerPage={autoItemsPerPage}
|
||||||
|
onPageChange={setAutoCurrentPage}
|
||||||
|
onItemsPerPageChange={setAutoItemsPerPage}
|
||||||
|
className="mt-4"
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
)}
|
)}
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
{/* Modal de rapprochement */}
|
{/* Modal de rapprochement */}
|
||||||
|
{isModalOpen && selectedDelivery && (
|
||||||
<ReconciliationModal
|
<ReconciliationModal
|
||||||
isOpen={isModalOpen}
|
|
||||||
onClose={() => setIsModalOpen(false)}
|
|
||||||
delivery={selectedDelivery}
|
delivery={selectedDelivery}
|
||||||
onSave={() => {
|
onSave={handleSaveReconciliation}
|
||||||
// Rafraîchir les données après sauvegarde
|
onClose={handleCloseModal}
|
||||||
queryClient.invalidateQueries({ queryKey: ['/api/deliveries/bl'] });
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user