Provide users a detailed sales report in an easily accessible file

Implements sales data export to .xlsx format using the xlsx library and updates related dependencies.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: e80fed01-7137-4997-b9af-dad57132d430
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/0715008c-7244-40f4-befc-26c014852236/e80fed01-7137-4997-b9af-dad57132d430/uQbgywo
This commit is contained in:
michaelschal committed 2025-07-19 11:11:15 +00:00
1 parent c3c1616e04
commit a586798a5d
4 files changed
+366 -10

No files matched your search

+32 -2
View File
@@ -12,6 +12,7 @@ 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';
import { formatDateTime } from '@/lib/export';
interface SalesHistoryProps {
canDelete?: boolean;
@@ -364,6 +365,35 @@ export default function SalesHistory({ canDelete = false }: SalesHistoryProps) {
setSearchQuery('');
};
const exportToExcel = async () => {
if (filteredSales.length === 0) {
toast({
title: "Aucune donnée",
description: "Aucune vente à exporter",
variant: "destructive",
});
return;
}
try {
const { exportToExcel } = await import('@/lib/export');
await exportToExcel(filteredSales);
toast({
title: "Export réussi",
description: "Le fichier Excel a été téléchargé",
variant: "success",
});
} catch (error) {
console.error('Erreur lors de l\'export Excel:', error);
toast({
title: "Erreur d'export",
description: "Impossible de générer le fichier Excel",
variant: "destructive",
});
}
};
if (isLoading) {
return (
<div className="elegant-card">
@@ -475,12 +505,12 @@ export default function SalesHistory({ canDelete = false }: SalesHistoryProps) {
Registre PDF Détaillé
</button>
<button
onClick={exportToCSV}
onClick={exportToExcel}
disabled={filteredSales.length === 0}
className="modern-button modern-button-secondary"
>
<Download className="h-4 w-4" />
Export CSV
Export Excel (.xlsx)
</button>
</div>
</div>
+132 -8
View File
@@ -1,5 +1,137 @@
import { Sale } from '@/types/sale';
export async function exportToExcel(sales: Sale[]): Promise<void> {
if (sales.length === 0) return;
try {
const XLSX = await import('xlsx');
const { utils, writeFile } = XLSX;
// Créer un nouveau classeur
const workbook = utils.book_new();
// Préparer les données pour la feuille principale
const salesData: any[] = [];
sales.forEach(sale => {
if (sale.products && sale.products.length > 0) {
// Pour chaque produit, créer une ligne avec les infos de la vente
sale.products.forEach((product, productIndex) => {
salesData.push({
'ID Vente': sale.id,
'Date et Heure': new Date(sale.timestamp).toLocaleString('fr-FR'),
'Vendeur': sale.vendeur,
'Produit N°': productIndex + 1,
'Type Article': product.typeArticle,
'Catégorie': product.categorie,
'Quantité': product.quantite,
'Code Générique': product.gencode || '',
'Nom Client': sale.nom,
'Prénom Client': sale.prenom,
'Date Naissance': sale.dateNaissance || '',
'Lieu Naissance': sale.lieuNaissance || '',
'Mode Paiement': sale.modePaiement || '',
'Type Identité': sale.typeIdentite || '',
'Numéro Identité': sale.numeroIdentite || '',
'Autorité Délivrance': sale.autoriteDelivrance || '',
'Date Délivrance': sale.dateDelivrance || '',
'Photo Recto': sale.photoRecto ? 'Oui' : 'Non',
'Photo Verso': sale.photoVerso ? 'Oui' : 'Non',
'Photo Ticket': sale.photoTicket ? 'Oui' : 'Non'
});
});
} else {
// Fallback pour les ventes sans produits
salesData.push({
'ID Vente': sale.id,
'Date et Heure': new Date(sale.timestamp).toLocaleString('fr-FR'),
'Vendeur': sale.vendeur,
'Produit N°': 'N/A',
'Type Article': 'N/A',
'Catégorie': 'N/A',
'Quantité': 0,
'Code Générique': '',
'Nom Client': sale.nom,
'Prénom Client': sale.prenom,
'Date Naissance': sale.dateNaissance || '',
'Lieu Naissance': sale.lieuNaissance || '',
'Mode Paiement': sale.modePaiement || '',
'Type Identité': sale.typeIdentite || '',
'Numéro Identité': sale.numeroIdentite || '',
'Autorité Délivrance': sale.autoriteDelivrance || '',
'Date Délivrance': sale.dateDelivrance || '',
'Photo Recto': sale.photoRecto ? 'Oui' : 'Non',
'Photo Verso': sale.photoVerso ? 'Oui' : 'Non',
'Photo Ticket': sale.photoTicket ? 'Oui' : 'Non'
});
}
});
// Créer la feuille des ventes
const salesWorksheet = utils.json_to_sheet(salesData);
// Ajuster la largeur des colonnes
const columnWidths = [
{ wch: 10 }, // ID Vente
{ wch: 18 }, // Date et Heure
{ wch: 15 }, // Vendeur
{ wch: 10 }, // Produit N°
{ wch: 25 }, // Type Article
{ wch: 10 }, // Catégorie
{ wch: 8 }, // Quantité
{ wch: 15 }, // Code Générique
{ wch: 15 }, // Nom Client
{ wch: 15 }, // Prénom Client
{ wch: 12 }, // Date Naissance
{ wch: 15 }, // Lieu Naissance
{ wch: 12 }, // Mode Paiement
{ wch: 12 }, // Type Identité
{ wch: 15 }, // Numéro Identité
{ wch: 18 }, // Autorité Délivrance
{ wch: 12 }, // Date Délivrance
{ wch: 10 }, // Photo Recto
{ wch: 10 }, // Photo Verso
{ wch: 10 } // Photo Ticket
];
salesWorksheet['!cols'] = columnWidths;
// Ajouter la feuille au classeur
utils.book_append_sheet(workbook, salesWorksheet, 'Ventes Détaillées');
// Créer une feuille de résumé
const summaryData = [
{ 'Statistique': 'Nombre total de ventes', 'Valeur': sales.length },
{ 'Statistique': 'Nombre total de produits vendus', 'Valeur': salesData.length },
{ 'Statistique': 'Date de génération', 'Valeur': new Date().toLocaleString('fr-FR') },
{ 'Statistique': 'Ventes avec photos recto', 'Valeur': sales.filter(s => s.photoRecto).length },
{ 'Statistique': 'Ventes avec photos verso', 'Valeur': sales.filter(s => s.photoVerso).length },
{ 'Statistique': 'Ventes avec photos ticket', 'Valeur': sales.filter(s => s.photoTicket).length }
];
const summaryWorksheet = utils.json_to_sheet(summaryData);
summaryWorksheet['!cols'] = [{ wch: 30 }, { wch: 20 }];
utils.book_append_sheet(workbook, summaryWorksheet, 'Résumé');
// Exporter le fichier
const fileName = `registre-feux-artifice-${new Date().toISOString().split('T')[0]}.xlsx`;
writeFile(workbook, fileName);
} catch (error) {
console.error('Erreur lors de l\'export Excel:', error);
throw error;
}
}
export function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString('fr-FR');
}
export function formatDateTime(dateString: string): string {
return new Date(dateString).toLocaleString('fr-FR');
}
// Fonction CSV basique maintenue pour compatibilité si nécessaire
export function exportToCSV(sales: Sale[]): void {
if (sales.length === 0) return;
@@ -85,11 +217,3 @@ export function exportToCSV(sales: Sale[]): void {
URL.revokeObjectURL(link.href);
}
export function formatDate(dateString: string): string {
return new Date(dateString).toLocaleDateString('fr-FR');
}
export function formatDateTime(dateString: string): string {
return new Date(dateString).toLocaleString('fr-FR');
}