import { useState, useMemo } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter, DialogDescription } from '@/components/ui/dialog'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from '@/components/ui/alert-dialog'; import { useToast } from '@/hooks/use-toast'; import { useStoreContext } from '@/hooks/useStoreContext'; import { useAuth } from '@/hooks/useAuth'; import { apiRequest } from '@/lib/queryClient'; import type { Sale } from '@shared/schema'; import { History, Download, Trash2, Eye, Filter, FileText, Search, X, Calendar, Users } from 'lucide-react'; import SaleDetailsModal from '@/components/SaleDetailsModal'; interface SalesHistoryProps { canDelete?: boolean; } export default function SalesHistory({ canDelete = false }: SalesHistoryProps) { const { toast } = useToast(); const queryClient = useQueryClient(); const { user } = useAuth(); const { selectedStoreId } = useStoreContext(); const [selectedSale, setSelectedSale] = useState(null); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [searchQuery, setSearchQuery] = useState(''); // Query for sales const { data: salesData, isLoading } = useQuery({ queryKey: ['/api/sales', startDate, endDate, selectedStoreId], queryFn: async () => { const params = new URLSearchParams(); if (startDate) params.append('startDate', startDate); if (endDate) params.append('endDate', endDate); if (selectedStoreId) { params.append('storeId', selectedStoreId.toString()); } const queryString = params.toString(); const response = await apiRequest(`/api/sales${queryString ? `?${queryString}` : ''}`); return response.json(); }, enabled: !!selectedStoreId && !!user, refetchOnMount: true, refetchOnWindowFocus: false, }); const sales = Array.isArray(salesData) ? salesData : []; // Filtrer les ventes selon la recherche const filteredSales = useMemo(() => { if (!searchQuery.trim()) return sales; const query = searchQuery.toLowerCase().trim(); return sales.filter(sale => sale.nom.toLowerCase().includes(query) || sale.prenom.toLowerCase().includes(query) || sale.vendeur.toLowerCase().includes(query) || sale.typeArticle.toLowerCase().includes(query) || sale.gencode.includes(query) || sale.numeroIdentite.toLowerCase().includes(query) || (sale.lieuNaissance && sale.lieuNaissance.toLowerCase().includes(query)) || sale.modePaiement.toLowerCase().includes(query) ); }, [sales, searchQuery]); // Delete mutation const deleteSaleMutation = useMutation({ mutationFn: (saleId: number) => apiRequest(`/api/sales/${saleId}`, { method: 'DELETE' }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/sales'], exact: false }); toast({ title: "Vente supprimée", description: "La vente a été supprimée avec succès", variant: "success", }); }, onError: () => { toast({ title: "Erreur", description: "Impossible de supprimer la vente", variant: "destructive", }); }, }); const handleDeleteSale = (sale: Sale) => { deleteSaleMutation.mutate(sale.id); }; const exportToPDF = async () => { if (filteredSales.length === 0) { toast({ title: "Aucune donnée", description: "Aucune vente à exporter", variant: "destructive", }); return; } try { const { default: jsPDF } = await import('jspdf'); const { default: autoTable } = await import('jspdf-autotable'); const doc = new jsPDF(); doc.setFont('helvetica'); doc.setFontSize(16); doc.text('Registre Détaillé des Ventes de Feux d\'Artifice', 20, 20); doc.setFontSize(10); doc.text(`Généré le ${new Date().toLocaleDateString('fr-FR')} à ${new Date().toLocaleTimeString('fr-FR')}`, 20, 30); if (startDate || endDate) { const period = `Période: ${startDate ? new Date(startDate).toLocaleDateString('fr-FR') : 'début'} - ${endDate ? new Date(endDate).toLocaleDateString('fr-FR') : 'fin'}`; doc.text(period, 20, 35); } let currentY = startDate || endDate ? 50 : 45; // Pour chaque vente, créer une section complète filteredSales.forEach((sale, saleIndex) => { // Vérifier si on a assez de place, sinon nouvelle page if (currentY > 250) { doc.addPage(); currentY = 20; } // Titre de la vente doc.setFontSize(12); doc.setFont('helvetica', 'bold'); doc.text(`VENTE #${sale.id} - ${new Date(sale.timestamp).toLocaleDateString('fr-FR')} ${new Date(sale.timestamp).toLocaleTimeString('fr-FR')}`, 20, currentY); currentY += 10; // Informations vendeur doc.setFontSize(10); doc.setFont('helvetica', 'normal'); doc.text(`Vendeur: ${sale.vendeur}`, 20, currentY); currentY += 8; // Informations client doc.setFont('helvetica', 'bold'); doc.text('INFORMATIONS CLIENT:', 20, currentY); currentY += 6; doc.setFont('helvetica', 'normal'); doc.text(`Nom: ${sale.nom} ${sale.prenom}`, 25, currentY); currentY += 5; doc.text(`Date de naissance: ${sale.dateNaissance || 'Non spécifiée'}`, 25, currentY); currentY += 5; doc.text(`Lieu de naissance: ${sale.lieuNaissance || 'Non spécifié'}`, 25, currentY); currentY += 5; doc.text(`Mode de paiement: ${sale.modePaiement || 'Non spécifié'}`, 25, currentY); currentY += 8; // Informations identité doc.setFont('helvetica', 'bold'); doc.text('PIÈCE D\'IDENTITÉ:', 20, currentY); currentY += 6; doc.setFont('helvetica', 'normal'); doc.text(`Type: ${sale.typeIdentite || 'Non spécifié'}`, 25, currentY); currentY += 5; doc.text(`Numéro: ${sale.numeroIdentite || 'Non spécifié'}`, 25, currentY); currentY += 5; doc.text(`Autorité de délivrance: ${sale.autoriteDelivrance || 'Non spécifiée'}`, 25, currentY); currentY += 5; doc.text(`Date de délivrance: ${sale.dateDelivrance || 'Non spécifiée'}`, 25, currentY); currentY += 8; // Produits doc.setFont('helvetica', 'bold'); doc.text('PRODUITS VENDUS:', 20, currentY); currentY += 6; if (sale.products && sale.products.length > 0) { sale.products.forEach((product, productIndex) => { doc.setFont('helvetica', 'normal'); doc.text(`Produit ${productIndex + 1}:`, 25, currentY); currentY += 5; doc.text(` • Type: ${product.typeArticle}`, 30, currentY); currentY += 4; doc.text(` • Catégorie: ${product.categorie}`, 30, currentY); currentY += 4; doc.text(` • Quantité: ${product.quantite}`, 30, currentY); currentY += 4; doc.text(` • Code: ${product.gencode || 'Non spécifié'}`, 30, currentY); currentY += 6; }); } else { doc.setFont('helvetica', 'normal'); doc.text('Aucun produit enregistré', 25, currentY); currentY += 6; } // Photos if (sale.photoRecto || sale.photoVerso || sale.photoTicket) { doc.setFont('helvetica', 'bold'); doc.text('DOCUMENTS PHOTOGRAPHIÉS:', 20, currentY); currentY += 6; doc.setFont('helvetica', 'normal'); if (sale.photoRecto) { doc.text('✓ Photo recto pièce d\'identité', 25, currentY); currentY += 5; } if (sale.photoVerso) { doc.text('✓ Photo verso pièce d\'identité', 25, currentY); currentY += 5; } if (sale.photoTicket) { doc.text('✓ Photo ticket/reçu', 25, currentY); currentY += 5; } currentY += 5; } // Ligne de séparation si pas la dernière vente if (saleIndex < filteredSales.length - 1) { doc.setLineWidth(0.5); doc.line(20, currentY, 190, currentY); currentY += 15; } }); doc.save(`registre-detaille-feux-artifice-${new Date().toISOString().split('T')[0]}.pdf`); toast({ title: "Export réussi", description: "Le registre détaillé PDF a été téléchargé", variant: "success", }); } catch (error) { console.error('Erreur lors de l\'export PDF:', error); toast({ title: "Erreur d'export", description: "Impossible de générer le fichier PDF", variant: "destructive", }); } }; const exportToCSV = () => { if (filteredSales.length === 0) { toast({ title: "Aucune donnée", description: "Aucune vente à exporter", variant: "destructive", }); return; } try { const headers = [ 'Date', 'Vendeur', 'Nom', 'Prénom', 'Date de naissance', 'Lieu de naissance', 'Type article', 'Catégorie', 'Quantité', 'Gencode', 'Mode de paiement', 'Type identité', 'Numéro identité', 'Autorité délivrance', 'Date délivrance' ]; const csvData = filteredSales.map(sale => [ new Date(sale.timestamp).toLocaleDateString('fr-FR'), sale.vendeur, sale.nom, sale.prenom, new Date(sale.dateNaissance).toLocaleDateString('fr-FR'), sale.lieuNaissance || '', sale.typeArticle, sale.categorie, sale.quantite, sale.gencode, sale.modePaiement, sale.typeIdentite, sale.numeroIdentite, sale.autoriteDelivrance || '', new Date(sale.dateDelivrance).toLocaleDateString('fr-FR') ]); const csvContent = [headers, ...csvData] .map(row => row.map(field => `"${field}"`).join(';')) .join('\n'); const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', `ventes-feux-artifice-${new Date().toISOString().split('T')[0]}.csv`); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); toast({ title: "Export réussi", description: "Le fichier CSV a été téléchargé", variant: "success", }); } catch (error) { console.error('Erreur lors de l\'export CSV:', error); toast({ title: "Erreur d'export", description: "Impossible de générer le fichier CSV", variant: "destructive", }); } }; const clearFilters = () => { setStartDate(''); setEndDate(''); setSearchQuery(''); }; if (isLoading) { return (
); } return (
{/* Module de recherche */}
Rechercher dans l'historique
setSearchQuery(e.target.value)} className="search-input pr-10" /> {searchQuery && ( )}
{searchQuery && (
{filteredSales.length} résultat{filteredSales.length > 1 ? 's' : ''} trouvé{filteredSales.length > 1 ? 's' : ''} sur {sales.length} vente{sales.length > 1 ? 's' : ''}
)}
{/* Filtres */}
Filtres par date
setStartDate(e.target.value)} className="modern-input" />
setEndDate(e.target.value)} className="modern-input" />
{/* Actions d'export */}
{filteredSales.length} vente{filteredSales.length > 1 ? 's' : ''}
{searchQuery && (
Recherche active
)}
{/* Table des ventes */}
{filteredSales.length === 0 ? ( ) : ( filteredSales.map((sale) => ( )) )}
Date et Heure Vendeur Client Article Cat. Qté Paiement Actions
{searchQuery ? 'Aucun résultat pour cette recherche' : 'Aucune vente enregistrée'}
{new Date(sale.timestamp).toLocaleDateString('fr-FR')}
{new Date(sale.timestamp).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}
{sale.vendeur}
{sale.nom} {sale.prenom}
{sale.typeArticle} {sale.categorie} {sale.quantite} {sale.modePaiement}
{canDelete && ( Supprimer la vente Êtes-vous sûr de vouloir supprimer cette vente ?

Client : {sale.nom} {sale.prenom}

Article : {sale.typeArticle}

Date : {new Date(sale.timestamp).toLocaleDateString('fr-FR')}

Cette action est irréversible.
Annuler handleDeleteSale(sale)} className="bg-red-600 hover:bg-red-700" > Supprimer
)}
{/* Modal de détails de la vente */} setSelectedSale(null)} />
); }