mirror of
https://github.com/R0m1k3/Regisflow.git
synced 2026-10-11 17:29:48 +02:00
Switch to server-side data fetching and improve user interface elements
Refactors sales data fetching to use API calls instead of IndexedDB, introduces react-query, updates UI icons, and removes obsolete IndexedDB code. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 91318273-c764-4fd4-be04-bdc12c38af32 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/0715008c-7244-40f4-befc-26c014852236/6e755801-08e8-4492-805e-ac3610de5a2a.jpg
This commit is contained in:
1 parent
ec38451a81
commit
4ad5b97932
3 files changed
+93
-214
No files matched your search
@@ -1,13 +1,34 @@
|
||||
import { useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Shield, Flame } from 'lucide-react';
|
||||
import { Shield, Flame, Plus, History } from 'lucide-react';
|
||||
import NewSaleForm from './NewSaleForm';
|
||||
import SalesHistory from './SalesHistory';
|
||||
import { useIndexedDB } from '@/hooks/useIndexedDB';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
import { useStoreContext } from '@/hooks/useStoreContext';
|
||||
import { apiRequest } from '@/lib/queryClient';
|
||||
import type { Sale } from '@shared/schema';
|
||||
|
||||
export default function FireworksApp() {
|
||||
const [activeTab, setActiveTab] = useState('nouvelle-vente');
|
||||
const { sales, isLoading, error, saveSale, deleteSale } = useIndexedDB();
|
||||
const { user } = useAuth();
|
||||
const { selectedStoreId } = useStoreContext();
|
||||
|
||||
// Query for sales count
|
||||
const { data: salesData } = useQuery<Sale[]>({
|
||||
queryKey: ['/api/sales', selectedStoreId],
|
||||
queryFn: async () => {
|
||||
if (!selectedStoreId) return [];
|
||||
const params = new URLSearchParams();
|
||||
params.append('storeId', selectedStoreId.toString());
|
||||
const response = await apiRequest(`/api/sales?${params.toString()}`);
|
||||
return response.json();
|
||||
},
|
||||
enabled: !!selectedStoreId && !!user,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
const sales = Array.isArray(salesData) ? salesData : [];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
@@ -18,8 +39,11 @@ export default function FireworksApp() {
|
||||
<div className="flex items-center space-x-3">
|
||||
<Flame className="h-8 w-8 text-primary" />
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
Registre des Ventes de Pétards
|
||||
RegisFlow
|
||||
</h1>
|
||||
<span className="text-sm text-gray-500">
|
||||
Registre des ventes de Feux d'artifice
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 flex items-center">
|
||||
<Shield className="h-4 w-4 mr-1" />
|
||||
@@ -34,30 +58,21 @@ export default function FireworksApp() {
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2 mb-6">
|
||||
<TabsTrigger value="nouvelle-vente" className="flex items-center gap-2">
|
||||
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
<Plus className="h-4 w-4" />
|
||||
Nouvelle Vente
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="historique" className="flex items-center gap-2">
|
||||
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<History className="h-4 w-4" />
|
||||
Historique ({sales.length} vente{sales.length !== 1 ? 's' : ''})
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="nouvelle-vente">
|
||||
<NewSaleForm onSaveSale={saveSale} />
|
||||
<NewSaleForm />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="historique">
|
||||
<SalesHistory
|
||||
sales={sales}
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
onDeleteSale={deleteSale}
|
||||
/>
|
||||
<SalesHistory canDelete={user?.role === 'admin'} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</main>
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Sale } from '@/types/sale';
|
||||
import { FireworksDB, createAutoBackup } from '@/lib/storage';
|
||||
|
||||
export function useIndexedDB() {
|
||||
const [db] = useState(() => new FireworksDB());
|
||||
const [sales, setSales] = useState<Sale[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const loadSales = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const allSales = await db.getAllSales();
|
||||
setSales(allSales.sort((a, b) => new Date(b.dateVente).getTime() - new Date(a.dateVente).getTime()));
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error('Error loading sales:', err);
|
||||
setError('Erreur lors du chargement des ventes');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [db]);
|
||||
|
||||
const saveSale = useCallback(async (saleData: Omit<Sale, 'id' | 'timestamp'>) => {
|
||||
try {
|
||||
const savedSale = await db.saveSale(saleData);
|
||||
setSales(prev => [savedSale, ...prev]);
|
||||
|
||||
// Create auto backup
|
||||
const updatedSales = await db.getAllSales();
|
||||
await createAutoBackup(updatedSales);
|
||||
|
||||
setError(null);
|
||||
return savedSale;
|
||||
} catch (err) {
|
||||
console.error('Error saving sale:', err);
|
||||
setError('Erreur lors de l\'enregistrement');
|
||||
throw err;
|
||||
}
|
||||
}, [db]);
|
||||
|
||||
const deleteSale = useCallback(async (id: number) => {
|
||||
try {
|
||||
await db.deleteSale(id);
|
||||
setSales(prev => prev.filter(sale => sale.id !== id));
|
||||
|
||||
// Update auto backup
|
||||
const updatedSales = await db.getAllSales();
|
||||
await createAutoBackup(updatedSales);
|
||||
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error('Error deleting sale:', err);
|
||||
setError('Erreur lors de la suppression');
|
||||
throw err;
|
||||
}
|
||||
}, [db]);
|
||||
|
||||
useEffect(() => {
|
||||
const initDB = async () => {
|
||||
try {
|
||||
await db.init();
|
||||
await loadSales();
|
||||
} catch (err) {
|
||||
console.error('Error initializing database:', err);
|
||||
setError('Erreur d\'initialisation de la base de données');
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
initDB();
|
||||
}, [db, loadSales]);
|
||||
|
||||
return {
|
||||
sales,
|
||||
isLoading,
|
||||
error,
|
||||
saveSale,
|
||||
deleteSale,
|
||||
loadSales
|
||||
};
|
||||
}
|
||||
+61
-114
@@ -1,119 +1,66 @@
|
||||
// Utilitaires pour l'export et la gestion des données
|
||||
// L'application utilise PostgreSQL via l'API backend
|
||||
|
||||
import { Sale, BackupData } from '@/types/sale';
|
||||
|
||||
const DB_NAME = 'VentesPetardsDB';
|
||||
const DB_VERSION = 1;
|
||||
const STORE_NAME = 'ventes';
|
||||
const BACKUP_KEY = 'petards_backup_csv';
|
||||
|
||||
export class FireworksDB {
|
||||
private db: IDBDatabase | null = null;
|
||||
|
||||
async init(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => {
|
||||
this.db = request.result;
|
||||
resolve();
|
||||
};
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result;
|
||||
|
||||
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
||||
const store = db.createObjectStore(STORE_NAME, {
|
||||
keyPath: 'id',
|
||||
autoIncrement: true
|
||||
});
|
||||
store.createIndex('nom', 'nom', { unique: false });
|
||||
store.createIndex('dateVente', 'dateVente', { unique: false });
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async saveSale(sale: Omit<Sale, 'id'>): Promise<Sale> {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction([STORE_NAME], 'readwrite');
|
||||
const store = transaction.objectStore(STORE_NAME);
|
||||
|
||||
const saleWithTimestamp = {
|
||||
...sale,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
const request = store.add(saleWithTimestamp);
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => {
|
||||
const savedSale = { ...saleWithTimestamp, id: request.result as number };
|
||||
resolve(savedSale);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async getAllSales(): Promise<Sale[]> {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction([STORE_NAME], 'readonly');
|
||||
const store = transaction.objectStore(STORE_NAME);
|
||||
const request = store.getAll();
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
});
|
||||
}
|
||||
|
||||
async deleteSale(id: number): Promise<void> {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = this.db!.transaction([STORE_NAME], 'readwrite');
|
||||
const store = transaction.objectStore(STORE_NAME);
|
||||
const request = store.delete(id);
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function createAutoBackup(sales: Sale[]): Promise<void> {
|
||||
const backupData: BackupData = {
|
||||
timestamp: new Date().toISOString(),
|
||||
sales,
|
||||
recordCount: sales.length
|
||||
};
|
||||
|
||||
localStorage.setItem(BACKUP_KEY, JSON.stringify(backupData));
|
||||
}
|
||||
|
||||
export function getAutoBackup(): BackupData | null {
|
||||
try {
|
||||
const backup = localStorage.getItem(BACKUP_KEY);
|
||||
return backup ? JSON.parse(backup) : null;
|
||||
} catch (error) {
|
||||
console.error('Error loading backup:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function downloadBackup(): void {
|
||||
const backup = getAutoBackup();
|
||||
if (!backup) return;
|
||||
|
||||
const blob = new Blob([JSON.stringify(backup, null, 2)], {
|
||||
type: 'application/json'
|
||||
});
|
||||
// Fonction utilitaire pour télécharger des données JSON
|
||||
export function downloadJSON(data: any, filename: string): void {
|
||||
const dataStr = JSON.stringify(data, null, 2);
|
||||
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
|
||||
|
||||
const linkElement = document.createElement('a');
|
||||
linkElement.setAttribute('href', dataUri);
|
||||
linkElement.setAttribute('download', filename);
|
||||
linkElement.click();
|
||||
}
|
||||
|
||||
// Fonction utilitaire pour créer un CSV des ventes
|
||||
export function exportSalesToCSV(sales: Sale[]): void {
|
||||
const csvHeader = [
|
||||
'Date/Heure',
|
||||
'Vendeur',
|
||||
'Date de vente',
|
||||
'Type d\'article',
|
||||
'Catégorie',
|
||||
'Quantité',
|
||||
'Gencode',
|
||||
'Nom client',
|
||||
'Prénom client',
|
||||
'Date de naissance',
|
||||
'Lieu de naissance',
|
||||
'Type identité',
|
||||
'Numéro identité',
|
||||
'Autorité délivrance',
|
||||
'Date délivrance'
|
||||
];
|
||||
|
||||
const csvData = sales.map(sale => [
|
||||
sale.timestamp ? new Date(sale.timestamp).toLocaleString('fr-FR') : '',
|
||||
sale.vendeur,
|
||||
sale.dateVente,
|
||||
sale.typeArticle,
|
||||
sale.categorie,
|
||||
sale.quantite,
|
||||
sale.gencode,
|
||||
sale.nom,
|
||||
sale.prenom,
|
||||
sale.dateNaissance,
|
||||
sale.lieuNaissance || '',
|
||||
sale.typeIdentite,
|
||||
sale.numeroIdentite,
|
||||
sale.autoriteDelivrance,
|
||||
sale.dateDelivrance
|
||||
]);
|
||||
|
||||
const csvContent = [csvHeader, ...csvData]
|
||||
.map(row => row.map(cell => `"${cell}"`).join(','))
|
||||
.join('\n');
|
||||
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = `backup_petards_${new Date().toISOString().split('T')[0]}.json`;
|
||||
const url = URL.createObjectURL(blob);
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `ventes_${new Date().toISOString().split('T')[0]}.csv`);
|
||||
link.click();
|
||||
|
||||
URL.revokeObjectURL(link.href);
|
||||
}
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
Reference in new issue
Block a user