import { useState, useEffect } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Pagination, usePagination } from "@/components/ui/pagination"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Textarea } from "@/components/ui/textarea"; import { AlertTriangle, Plus, Eye, Edit, Trash2, CheckCircle, Package, Clock, AlertCircle, Filter, Download, FileText } from "lucide-react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { format } from "date-fns"; import { fr } from "date-fns/locale"; import { useToast } from "@/hooks/use-toast"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { useStore } from "@/components/Layout"; import { apiRequest } from "@/lib/queryClient"; import type { DlcProductWithRelations, InsertDlcProduct } from "@shared/schema"; const dlcFormSchema = z.object({ productName: z.string().min(1, "Le nom du produit est obligatoire"), gencode: z.string().optional(), dlcDate: z.string().min(1, "La date d'expiration est obligatoire"), dateType: z.enum(["dlc", "ddm", "dluo"], { required_error: "Le type de date est obligatoire" }), supplierId: z.number().min(1, "Le fournisseur est obligatoire"), status: z.enum(["en_cours", "expires_soon", "expires", "valides"]).default("en_cours"), notes: z.string().optional(), }); type DlcFormData = z.infer; export default function DlcPage() { const { user, isLoading: authLoading } = useAuthUnified(); const { toast } = useToast(); const queryClient = useQueryClient(); const { selectedStoreId } = useStore(); const [statusFilter, setStatusFilter] = useState("all"); const [supplierFilter, setSupplierFilter] = useState("all"); const [searchTerm, setSearchTerm] = useState(""); const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingProduct, setEditingProduct] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [productToDelete, setProductToDelete] = useState(null); // Fetch stores/groups const { data: stores = [] } = useQuery({ queryKey: ["/api/groups"], enabled: !authLoading, }); // Fetch suppliers with DLC enabled only const { data: suppliers = [] } = useQuery({ queryKey: ["/api/suppliers", "dlc"], queryFn: () => apiRequest("/api/suppliers?dlc=true"), enabled: !authLoading, }); // Fetch DLC products const { data: dlcProducts = [], isLoading: productsLoading } = useQuery({ queryKey: ["/api/dlc-products", selectedStoreId, statusFilter, supplierFilter], queryFn: () => { const params = new URLSearchParams(); if (selectedStoreId && user?.role === 'admin') params.append("storeId", selectedStoreId.toString()); if (statusFilter && statusFilter !== "all") params.append("status", statusFilter); if (supplierFilter && supplierFilter !== "all") params.append("supplierId", supplierFilter); console.log("🔍 Requête DLC products:", { url: `/api/dlc-products?${params.toString()}`, user: user?.role, selectedStoreId, userGroups: user?.userGroups?.map(ug => ({ groupId: ug.groupId, groupName: ug.group?.name })) }); return apiRequest(`/api/dlc-products?${params.toString()}`); }, enabled: !authLoading, onSuccess: (data) => { console.log("📋 DLC products reçus:", { total: data.length, sample: data.slice(0, 3).map(d => ({ id: d.id, productName: d.productName, groupId: d.groupId })) }); } }); // Fetch DLC stats const { data: stats = { active: 0, expiringSoon: 0, expired: 0 } } = useQuery({ queryKey: ["/api/dlc-products/stats", selectedStoreId], queryFn: () => { const params = new URLSearchParams(); if (selectedStoreId && user?.role === 'admin') params.append("storeId", selectedStoreId.toString()); return apiRequest(`/api/dlc-products/stats?${params.toString()}`); }, enabled: !authLoading, }); // Form setup const form = useForm({ resolver: zodResolver(dlcFormSchema), defaultValues: { productName: "", gencode: "", dateType: "dlc", status: "en_cours", notes: "", }, }); // Create mutation const createMutation = useMutation({ mutationFn: (data: InsertDlcProduct) => apiRequest("/api/dlc-products", "POST", data), onSuccess: () => { // Invalidate all DLC queries with any combination of parameters queryClient.invalidateQueries({ queryKey: ["/api/dlc-products"], exact: false }); queryClient.invalidateQueries({ queryKey: ["/api/dlc-products/stats"], exact: false }); console.log("🔄 Cache DLC invalidé après création"); toast({ title: "Produit DLC créé avec succès" }); setIsDialogOpen(false); form.reset(); setEditingProduct(null); }, onError: (error: any) => { toast({ title: "Erreur lors de la création", description: error.message || "Une erreur est survenue", variant: "destructive", }); }, }); // Update mutation const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: number; data: Partial }) => apiRequest(`/api/dlc-products/${id}`, "PUT", data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/dlc-products"], exact: false }); queryClient.invalidateQueries({ queryKey: ["/api/dlc-products/stats"], exact: false }); console.log("🔄 Cache DLC invalidé après mise à jour"); toast({ title: "Produit DLC mis à jour avec succès" }); setIsDialogOpen(false); form.reset(); setEditingProduct(null); }, onError: (error: any) => { toast({ title: "Erreur lors de la mise à jour", description: error.message || "Une erreur est survenue", variant: "destructive", }); }, }); // Validate mutation const validateMutation = useMutation({ mutationFn: (id: number) => apiRequest(`/api/dlc-products/${id}/validate`, "PUT"), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/dlc-products"], exact: false }); queryClient.invalidateQueries({ queryKey: ["/api/dlc-products/stats"], exact: false }); console.log("🔄 Cache DLC invalidé après validation"); toast({ title: "Produit validé avec succès" }); }, onError: (error: any) => { toast({ title: "Erreur lors de la validation", description: error.message || "Une erreur est survenue", variant: "destructive", }); }, }); // Delete mutation const deleteMutation = useMutation({ mutationFn: (id: number) => apiRequest(`/api/dlc-products/${id}`, "DELETE"), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/dlc-products"], exact: false }); queryClient.invalidateQueries({ queryKey: ["/api/dlc-products/stats"], exact: false }); console.log("🔄 Cache DLC invalidé après suppression"); toast({ title: "Produit supprimé avec succès" }); }, onError: (error: any) => { toast({ title: "Erreur lors de la suppression", description: error.message || "Une erreur est survenue", variant: "destructive", }); }, }); const onSubmit = (data: DlcFormData) => { // Calculer la date d'expiration et le seuil d'alerte (15 jours avant) const dlcDate = new Date(data.dlcDate); // Déterminer le groupId correctement selon le rôle utilisateur let groupId; // FORCE L'UTILISATION DU GROUPE ASSIGNÉ EN PREMIER if (user?.userGroups?.[0]?.groupId) { // Utilisateur avec groupe assigné : TOUJOURS utiliser ce groupe groupId = user.userGroups[0].groupId; console.log("🎯 DLC Frontend: Using user's assigned group (PRIORITY):", groupId); } else if (user?.role === 'admin' && selectedStoreId) { // Admin avec magasin sélectionné ET pas de groupe assigné groupId = selectedStoreId; console.log("🎯 DLC Frontend: Using admin selected store:", groupId); } else if (user?.role === 'admin' && stores?.[0]?.id) { // Admin sans sélection : premier magasin disponible groupId = stores[0].id; console.log("🎯 DLC Frontend: Using first available store for admin:", groupId); } else { // Fallback d'urgence groupId = 1; console.log("🚨 DLC Frontend: Using emergency fallback groupId:", groupId); } console.log("🏪 DLC GroupId Selection DEBUG:", { userRole: user?.role, selectedStoreId, userGroups: user?.userGroups?.map(ug => ({groupId: ug.groupId, groupName: ug.group?.name})), availableStores: stores.map(s => ({id: s.id, name: s.name})), userGroupsRaw: user?.userGroups, firstUserGroup: user?.userGroups?.[0], finalGroupId: groupId, logicPath: !groupId ? 'need-fallback' : 'already-set' }); const dlcData: InsertDlcProduct = { ...data, dlcDate, quantity: 1, // Valeur par défaut unit: "unité", // Valeur par défaut location: "Magasin", // Valeur par défaut alertThreshold: 15, // Toujours 15 jours groupId, }; if (editingProduct) { updateMutation.mutate({ id: editingProduct.id, data: dlcData }); } else { createMutation.mutate(dlcData); } }; const handleEdit = (product: DlcProductWithRelations) => { setEditingProduct(product); form.reset({ productName: product.productName, gencode: product.gencode || "", dlcDate: product.dlcDate ? format(new Date(product.dlcDate), "yyyy-MM-dd") : "", dateType: product.dateType as "dlc" | "ddm" | "dluo", supplierId: product.supplierId, status: product.status as "en_cours" | "expires_soon" | "expires" | "valides", notes: product.notes || "", }); setIsDialogOpen(true); }; const handleDelete = (id: number) => { setProductToDelete(id); setDeleteDialogOpen(true); }; const confirmDelete = () => { if (productToDelete) { deleteMutation.mutate(productToDelete); setDeleteDialogOpen(false); setProductToDelete(null); } }; const handleValidate = (id: number) => { validateMutation.mutate(id); }; // Fonction pour déterminer si un produit doit afficher le bouton de validation const shouldShowValidateButton = (product: DlcProductWithRelations) => { const today = new Date(); const expiry = new Date(product.dlcDate || new Date()); const daysUntilExpiry = Math.ceil((expiry.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); // Afficher le bouton si le produit expire dans 15 jours ou moins, ou est déjà expiré return (daysUntilExpiry <= 15 && product.status !== "valide"); }; const getStatusBadge = (status: string, dlcDate: string | null) => { if (!dlcDate) return Non défini; // LOGIQUE SIMPLIFIÉE ET COHÉRENTE : // 1. Si le statut en base est "valides", afficher "Validé" (peu importe la date) // 2. Sinon, calculer selon la date d'expiration if (status === "valides") { return Validé; } // Pour tous les autres statuts, calculer selon la date const today = new Date(); const expiry = new Date(dlcDate); const daysUntilExpiry = Math.ceil((expiry.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); if (daysUntilExpiry < 0) { return Expiré; } else if (daysUntilExpiry <= 15) { return Expire bientôt; } else { return Actif; } }; const printExpiringSoon = () => { const expiringSoon = filteredProducts.filter(product => { const today = new Date(); const expiry = new Date(product.dlcDate || new Date()); const diffTime = expiry.getTime() - today.getTime(); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); return diffDays > 0 && diffDays <= 15; }); const printContent = ` Produits expirant bientôt

Produits expirant dans les 15 prochains jours

Date d'impression: ${format(new Date(), "dd/MM/yyyy HH:mm")}

${expiringSoon.map(product => { const today = new Date(); const expiry = new Date(product.dlcDate || new Date()); const diffDays = Math.ceil((expiry.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); return ` `; }).join('')}
Produit Code EAN13 Date d'expiration Type Fournisseur Jours restants
${product.productName} ${product.gencode || '-'} ${product.dlcDate ? format(new Date(product.dlcDate), "dd/MM/yyyy") : 'N/A'} ${product.dateType.toUpperCase()} ${product.supplier?.name || 'N/A'} ${diffDays}
`; const printWindow = window.open('', '_blank'); if (printWindow) { printWindow.document.write(printContent); printWindow.document.close(); printWindow.print(); } }; const printExpired = () => { const expired = filteredProducts.filter(product => { const today = new Date(); const expiry = new Date(product.dlcDate || new Date()); return expiry < today; }); const printContent = ` Produits expirés

Produits expirés

Date d'impression: ${format(new Date(), "dd/MM/yyyy HH:mm")}

${expired.map(product => { const today = new Date(); const expiry = new Date(product.dlcDate || new Date()); const diffDays = Math.ceil((today.getTime() - expiry.getTime()) / (1000 * 60 * 60 * 24)); return ` `; }).join('')}
Produit Code EAN13 Date d'expiration Type Fournisseur Jours dépassés
${product.productName} ${product.gencode || '-'} ${product.dlcDate ? format(new Date(product.dlcDate), "dd/MM/yyyy") : 'N/A'} ${product.dateType.toUpperCase()} ${product.supplier?.name || 'N/A'} ${diffDays}
`; const printWindow = window.open('', '_blank'); if (printWindow) { printWindow.document.write(printContent); printWindow.document.close(); printWindow.print(); } }; const filteredProducts = (dlcProducts || []).filter(product => { if (searchTerm) { return product.productName.toLowerCase().includes(searchTerm.toLowerCase()) || (product.supplier?.name && product.supplier.name.toLowerCase().includes(searchTerm.toLowerCase())); } return true; }); // Pagination const { currentPage, setCurrentPage, itemsPerPage, setItemsPerPage, totalPages, paginatedData: paginatedProducts, totalItems } = usePagination(filteredProducts, 10); if (authLoading) { return
Chargement...
; } return (
{/* Header */}

Gestion DLC

Gestion des dates limites de consommation

{editingProduct ? "Modifier le produit DLC" : "Nouveau produit DLC"}
( Nom du produit )} /> ( Fournisseur )} />
( Code EAN13 (optionnel) )} />
( Date d'expiration )} /> ( Type de date )} />
( Notes (optionnel)