Replace UI components to fix critical production errors

Refactor `BLReconciliation.tsx` and multiple Shadcn/ui components by replacing them with custom, robust alternatives to resolve React #310 errors in production.

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/rmaxwaw
This commit is contained in:
michaelschal committed 2025-08-12 11:05:49 +00:00
1 parent 553c07a2cb
commit c0e1b26599
80 files changed
+5691 -377

No files matched your search

+210 -121
View File
@@ -1,10 +1,6 @@
import { useState, useEffect } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { safeFormat } from "@/lib/dateUtils";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
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";
@@ -13,6 +9,117 @@ import { usePermissions } from "@shared/permissions";
import { Search, Edit, FileText, Settings, Eye, AlertTriangle, X, Check, Trash2, Ban, ChevronLeft, ChevronRight } from "lucide-react";
import type { Supplier } from "@shared/schema";
// Composants UI robustes sans dépendances shadcn/ui problématiques
const RobustButton = ({ children, onClick, disabled = false, variant = "default", size = "default", className = "", title = "" }: {
children: React.ReactNode,
onClick?: () => void,
disabled?: boolean,
variant?: "default" | "ghost" | "outline" | "destructive",
size?: "default" | "sm" | "lg",
className?: string,
title?: string
}) => {
const variantStyles = {
default: "bg-blue-600 text-white hover:bg-blue-700 border-blue-600",
ghost: "text-gray-600 hover:text-gray-900 hover:bg-gray-100 border-transparent",
outline: "border-gray-300 bg-white text-gray-700 hover:bg-gray-50",
destructive: "bg-red-600 text-white hover:bg-red-700 border-red-600"
};
const sizeStyles = {
default: "px-4 py-2 text-sm",
sm: "px-3 py-1.5 text-xs",
lg: "px-6 py-3 text-base"
};
return (
<button
onClick={onClick}
disabled={disabled}
title={title}
className={`inline-flex items-center justify-center font-medium rounded-md border transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${variantStyles[variant]} ${sizeStyles[size]} ${disabled ? 'opacity-50 cursor-not-allowed' : ''} ${className}`}
>
{children}
</button>
);
};
const RobustInput = ({ placeholder, value, onChange, className = "" }: {
placeholder?: string,
value: string,
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void,
className?: string
}) => {
return (
<input
type="text"
placeholder={placeholder}
value={value}
onChange={onChange}
className={`w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 ${className}`}
/>
);
};
const RobustBadge = ({ children, variant = "default", className = "" }: {
children: React.ReactNode,
variant?: "default" | "secondary" | "destructive" | "outline",
className?: string
}) => {
const variantStyles = {
default: "bg-blue-100 text-blue-800 border-blue-200",
secondary: "bg-gray-100 text-gray-800 border-gray-200",
destructive: "bg-red-100 text-red-800 border-red-200",
outline: "bg-white text-gray-700 border-gray-300"
};
return (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border ${variantStyles[variant]} ${className}`}>
{children}
</span>
);
};
const RobustTabs = ({ value, onValueChange, children }: {
value: string,
onValueChange: (value: string) => void,
children: React.ReactNode
}) => {
return (
<div data-tab-value={value} onChange={() => onValueChange}>
{children}
</div>
);
};
const RobustTabsList = ({ children }: { children: React.ReactNode }) => {
return (
<div className="inline-flex h-10 items-center justify-center rounded-md bg-gray-100 p-1">
{children}
</div>
);
};
const RobustTabsTrigger = ({ value, children, isActive, onClick }: {
value: string,
children: React.ReactNode,
isActive: boolean,
onClick: () => void
}) => {
return (
<button
onClick={onClick}
className={`inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all focus:outline-none ${
isActive
? 'bg-white text-gray-900 shadow-sm'
: 'text-gray-600 hover:text-gray-900'
}`}
>
{children}
</button>
);
};
export default function BLReconciliation() {
const { user } = useAuthUnified();
const { selectedStoreId } = useStore();
@@ -86,7 +193,7 @@ export default function BLReconciliation() {
return supplier?.automaticReconciliation;
});
// Mutation pour valider un rapprochement manuel
// Mutations pour les actions
const validateManualMutation = useMutation({
mutationFn: async (deliveryId: number) => {
return await apiRequest(`/api/deliveries/${deliveryId}`, "PUT", {
@@ -110,7 +217,6 @@ export default function BLReconciliation() {
}
});
// Mutation pour dévalider un rapprochement (admins uniquement)
const devalidateMutation = useMutation({
mutationFn: async (deliveryId: number) => {
return await apiRequest(`/api/deliveries/${deliveryId}`, "PUT", {
@@ -134,7 +240,6 @@ export default function BLReconciliation() {
}
});
// Mutation pour supprimer une ligne (admins uniquement)
const deleteMutation = useMutation({
mutationFn: async (deliveryId: number) => {
return await apiRequest(`/api/deliveries/${deliveryId}`, "DELETE");
@@ -238,8 +343,8 @@ export default function BLReconciliation() {
return delivery.reconciled === false && delivery.validatedAt === null;
};
// Composant de pagination simplifiée
const SimplePagination = ({ currentPage, totalPages, onPageChange, totalItems, itemsPerPage }: {
// Composant de pagination robuste
const RobustPagination = ({ currentPage, totalPages, onPageChange, totalItems, itemsPerPage }: {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
@@ -259,7 +364,7 @@ export default function BLReconciliation() {
</span>
</div>
<div className="flex items-center space-x-2">
<Button
<RobustButton
variant="outline"
size="sm"
onClick={() => onPageChange(currentPage - 1)}
@@ -268,13 +373,13 @@ export default function BLReconciliation() {
>
<ChevronLeft className="w-4 h-4" />
<span>Précédent</span>
</Button>
</RobustButton>
<span className="px-3 py-1 text-sm bg-gray-100 rounded">
Page {currentPage} sur {totalPages}
</span>
<Button
<RobustButton
variant="outline"
size="sm"
onClick={() => onPageChange(currentPage + 1)}
@@ -283,7 +388,7 @@ export default function BLReconciliation() {
>
<span>Suivant</span>
<ChevronRight className="w-4 h-4" />
</Button>
</RobustButton>
</div>
</div>
);
@@ -315,52 +420,64 @@ export default function BLReconciliation() {
</p>
</div>
<div className="flex items-center space-x-4">
<Badge variant="outline" className="text-sm border border-gray-300">
<RobustBadge variant="outline" className="text-sm border border-gray-300">
{manualReconciliationDeliveries.length} rapprochements manuels
</Badge>
<Badge variant="outline" className="text-sm border border-gray-300 bg-blue-50">
</RobustBadge>
<RobustBadge variant="outline" className="text-sm border border-gray-300 bg-blue-50">
{automaticReconciliationDeliveries.length} rapprochements automatiques
</Badge>
</RobustBadge>
</div>
</div>
{/* Onglets */}
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="manual" className="flex items-center space-x-2">
<Edit className="w-4 h-4" />
<span>Rapprochement Manuel</span>
<Badge variant="secondary" className="ml-2">
{manualReconciliationDeliveries.length}
</Badge>
</TabsTrigger>
<TabsTrigger value="automatic" className="flex items-center space-x-2">
<Settings className="w-4 h-4" />
<span>Rapprochement Automatique</span>
<Badge variant="secondary" className="ml-2">
{automaticReconciliationDeliveries.length}
</Badge>
</TabsTrigger>
</TabsList>
</Tabs>
{/* Onglets robustes */}
<div className="border-b border-gray-200">
<nav className="-mb-px flex space-x-8">
<RobustTabsTrigger
value="manual"
isActive={activeTab === "manual"}
onClick={() => setActiveTab("manual")}
>
<div className="flex items-center space-x-2">
<Edit className="w-4 h-4" />
<span>Rapprochement Manuel</span>
<RobustBadge variant="secondary" className="ml-2">
{manualReconciliationDeliveries.length}
</RobustBadge>
</div>
</RobustTabsTrigger>
<RobustTabsTrigger
value="automatic"
isActive={activeTab === "automatic"}
onClick={() => setActiveTab("automatic")}
>
<div className="flex items-center space-x-2">
<Settings className="w-4 h-4" />
<span>Rapprochement Automatique</span>
<RobustBadge variant="secondary" className="ml-2">
{automaticReconciliationDeliveries.length}
</RobustBadge>
</div>
</RobustTabsTrigger>
</nav>
</div>
</div>
{/* Filtre de recherche */}
<div className="bg-gray-50 border border-gray-200 p-4 rounded-lg">
<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" />
<Input
<RobustInput
placeholder="Rechercher par fournisseur, BL ou facture..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10 border border-gray-300 shadow-sm"
className="pl-10"
/>
</div>
</div>
{/* Contenu des onglets */}
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
<TabsContent value="manual" className="space-y-6">
{activeTab === "manual" && (
<div className="space-y-6">
{filteredManualDeliveries.length === 0 ? (
<div className="text-center py-12">
<FileText className="w-16 h-16 text-gray-300 mx-auto mb-4" />
@@ -374,7 +491,7 @@ export default function BLReconciliation() {
) : (
<div className="bg-white border border-gray-200 shadow-lg overflow-hidden rounded-lg">
{/* Pagination du haut */}
<SimplePagination
<RobustPagination
currentPage={currentPage}
totalPages={totalManualPages}
onPageChange={setCurrentPage}
@@ -386,30 +503,14 @@ export default function BLReconciliation() {
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50 border-b border-gray-200">
<tr>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Fournisseur
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
N° BL
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Date Livr.
</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">
Statut
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Fournisseur</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">N° BL</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date Livr.</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">Statut</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
@@ -463,34 +564,34 @@ export default function BLReconciliation() {
</td>
<td className="px-3 py-2 text-sm">
{delivery.reconciled ? (
<Badge variant="default" className="bg-green-600 text-white">
<RobustBadge variant="default" className="bg-green-600 text-white border-green-600">
Validé
</Badge>
</RobustBadge>
) : isDevalidated(delivery) ? (
<Badge variant="destructive">
<RobustBadge variant="destructive">
Dévalidé
</Badge>
</RobustBadge>
) : (
<Badge variant="outline">
<RobustBadge variant="outline">
En attente
</Badge>
</RobustBadge>
)}
</td>
<td className="px-3 py-2 text-sm">
<div className="flex items-center space-x-2">
{permissions.canEdit('reconciliation') && (
<Button
<RobustButton
variant="ghost"
size="sm"
className="text-gray-600 hover:text-blue-600 p-1"
title="Modifier"
>
<Edit className="w-4 h-4" />
</Button>
</RobustButton>
)}
{!delivery.reconciled && permissions.canValidate('reconciliation') && (
<Button
<RobustButton
variant="ghost"
size="sm"
onClick={() => handleValidateManual(delivery.id)}
@@ -499,11 +600,11 @@ export default function BLReconciliation() {
title="Valider"
>
<Check className="w-4 h-4" />
</Button>
</RobustButton>
)}
{delivery.reconciled && permissions.canEdit('reconciliation') && user?.role === 'admin' && (
<Button
<RobustButton
variant="ghost"
size="sm"
onClick={() => handleDevalidate(delivery.id)}
@@ -512,11 +613,11 @@ export default function BLReconciliation() {
title="Dévalider"
>
<Ban className="w-4 h-4" />
</Button>
</RobustButton>
)}
{permissions.canDelete('reconciliation') && (
<Button
<RobustButton
variant="ghost"
size="sm"
onClick={() => handleDelete(delivery.id)}
@@ -525,7 +626,7 @@ export default function BLReconciliation() {
title="Supprimer"
>
<Trash2 className="w-4 h-4" />
</Button>
</RobustButton>
)}
</div>
</td>
@@ -536,7 +637,7 @@ export default function BLReconciliation() {
</div>
{/* Pagination du bas */}
<SimplePagination
<RobustPagination
currentPage={currentPage}
totalPages={totalManualPages}
onPageChange={setCurrentPage}
@@ -545,9 +646,11 @@ export default function BLReconciliation() {
/>
</div>
)}
</TabsContent>
<TabsContent value="automatic" className="space-y-6">
</div>
)}
{activeTab === "automatic" && (
<div className="space-y-6">
{/* Message d'information */}
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div className="flex items-start space-x-3">
@@ -579,7 +682,7 @@ export default function BLReconciliation() {
) : (
<div className="bg-white border border-gray-200 shadow-lg overflow-hidden rounded-lg">
{/* Pagination du haut */}
<SimplePagination
<RobustPagination
currentPage={currentPage}
totalPages={totalAutomaticPages}
onPageChange={setCurrentPage}
@@ -593,29 +696,15 @@ export default function BLReconciliation() {
<tr>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Fournisseur
<Badge variant="secondary" className="ml-2 text-xs">AUTO</Badge>
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
N° BL
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Date Livr.
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Date Valid.
</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">
Statut
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
<RobustBadge variant="secondary" className="ml-2 text-xs">AUTO</RobustBadge>
</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">N° BL</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date Livr.</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date Valid.</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">Statut</th>
<th className="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
@@ -667,28 +756,28 @@ export default function BLReconciliation() {
</td>
<td className="px-3 py-2 text-sm">
{isDevalidated(delivery) ? (
<Badge variant="destructive">
<RobustBadge variant="destructive">
Dévalidé
</Badge>
</RobustBadge>
) : (
<Badge variant="default" className="bg-green-600 text-white">
<RobustBadge variant="default" className="bg-green-600 text-white border-green-600">
Auto-validé
</Badge>
</RobustBadge>
)}
</td>
<td className="px-3 py-2 text-sm">
<div className="flex items-center space-x-2">
{isDevalidated(delivery) && permissions.canEdit('reconciliation') && (
<>
<Button
<RobustButton
variant="ghost"
size="sm"
className="text-gray-600 hover:text-blue-600 p-1"
title="Modifier"
>
<Edit className="w-4 h-4" />
</Button>
<Button
</RobustButton>
<RobustButton
variant="ghost"
size="sm"
onClick={() => handleValidateManual(delivery.id)}
@@ -697,12 +786,12 @@ export default function BLReconciliation() {
title="Valider"
>
<Check className="w-4 h-4" />
</Button>
</RobustButton>
</>
)}
{!isDevalidated(delivery) && permissions.canEdit('reconciliation') && (
<Button
<RobustButton
variant="ghost"
size="sm"
onClick={() => handleDevalidate(delivery.id)}
@@ -711,11 +800,11 @@ export default function BLReconciliation() {
title="Dévalider"
>
<Ban className="w-4 h-4" />
</Button>
</RobustButton>
)}
{permissions.canDelete('reconciliation') && (
<Button
<RobustButton
variant="ghost"
size="sm"
onClick={() => handleDelete(delivery.id)}
@@ -724,7 +813,7 @@ export default function BLReconciliation() {
title="Supprimer"
>
<Trash2 className="w-4 h-4" />
</Button>
</RobustButton>
)}
</div>
</td>
@@ -735,7 +824,7 @@ export default function BLReconciliation() {
</div>
{/* Pagination du bas */}
<SimplePagination
<RobustPagination
currentPage={currentPage}
totalPages={totalAutomaticPages}
onPageChange={setCurrentPage}
@@ -744,8 +833,8 @@ export default function BLReconciliation() {
/>
</div>
)}
</TabsContent>
</Tabs>
</div>
)}
</div>
);
}