Ctrl+K
voulez-vous faire un link entre ce code et améliorer la conseption finale"<!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Gestionnaire de Cartes NAFTAL</title> <script src="https://cdn.tailwindcss.com"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> .card-item:hover { transform: translateY(-2px); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); } .flip-in { animation: flipIn 0.5s ease-out; } @keyframes flipIn { from { opacity: 0; transform: rotateX(90deg); } to { opacity: 1; transform: rotateX(0); } } .transaction-table { border-collapse: collapse; width: 100%; } .transaction-table th, .transaction-table td { padding: 8px 12px; border-bottom: 1px solid #e5e7eb; } .transaction-table tr:last-child td { border-bottom: none; } </style> </head> <body class="bg-gray-50 min-h-screen"> <div class="container mx-auto px-4 py-8"> <div class="max-w-4xl mx-auto"> <!-- En-tête --> <header class="text-center mb-8"> <h1 class="text-3xl font-bold text-indigo-700 mb-2"> ⛽ Gestionnaire de Cartes NAFTAL </h1> <p class="text-gray-600">Suivi des cartes et des dépenses par véhicule/conducteur</p> </header> <!-- Formulaire d'ajout --> <section class="bg-white rounded-xl shadow-sm p-6 mb-8 border border-gray-200"> <h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center"> <i class="fas fa-plus-circle mr-2 text-indigo-500"></i>Nouvelle carte </h2> <form id="cardForm" class="space-y-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div> <label for="cardName" class="block text-sm font-medium text-gray-700 mb-1">Nom du titulaire</label> <input type="text" id="cardName" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="Camion 12 / Jean Dupont"> </div> <div> <label for="cardType" class="block text-sm font-medium text-gray-700 mb-1">Type de carte</label> <select id="cardType" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"> <option value="">Sélectionner...</option> <option value="NAFTAL">Carte NAFTAL</option> <option value="NAFTAL_Pro">Carte NAFTAL Pro</option> <option value="NAFTAL_Fleet">Carte NAFTAL Flotte</option> </select> </div> </div> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div> <label for="cardNumber" class="block text-sm font-medium text-gray-700 mb-1">Numéro de carte</label> <input type="text" id="cardNumber" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="1234567890"> </div> <div> <label for="fuelType" class="block text-sm font-medium text-gray-700 mb-1">Catégorie</label> <select id="fuelType" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"> <option value="">Sélectionner...</option> <option value="SANS PLOMB">SANS PLOMB</option> <option value="DIESEL">DIESEL</option> <option value="GPL">GPL</option> <option value="PRODUITS NAFTAL">PRODUITS NAFTAL</option> </select> </div> </div> <div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <div> <label for="initialAmount" class="block text-sm font-medium text-gray-700 mb-1">Solde initial (DA)</label> <input type="number" step="0.01" id="initialAmount" value="0" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"> </div> <div> <label for="cardLimit" class="block text-sm font-medium text-gray-700 mb-1">Plafond mensuel (DA)</label> <input type="number" step="1" id="cardLimit" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition" placeholder="Optionnel"> </div> <div class="flex items-end"> <button type="submit" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-lg transition duration-300 flex items-center justify-center h-[42px]"> <i class="fas fa-save mr-2"></i> Enregistrer </button> </div> </div> </form> </section> <!-- Liste des cartes --> <section class="bg-white rounded-xl shadow-sm p-6 border border-gray-200"> <div class="flex flex-col md:flex-row md:justify-between md:items-center mb-4 gap-4"> <h2 class="text-xl font-semibold text-gray-800 flex items-center"> <i class="fas fa-list-alt mr-2 text-indigo-500"></i>Cartes enregistrées </h2> <div class="flex flex-col sm:flex-row gap-2"> <div class="relative flex-grow"> <input type="text" id="searchInput" placeholder="Rechercher..." class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition"> <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> </div> <button onclick="exportToExcel()" class="bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-lg transition flex items-center justify-center"> <i class="fas fa-file-excel mr-2"></i> Export </button> </div> </div> <div id="cardList" class="space-y-3"> <!-- État vide --> <div class="text-center py-12 text-gray-500" id="emptyState"> <i class="fas fa-credit-card text-4xl mb-3 text-gray-300"></i> <p>Aucune carte enregistrée</p> <p class="text-sm mt-2">Commencez par ajouter une carte ci-dessus</p> </div> </div> </section> </div> </div> <script> // Stockage des cartes let cards = JSON.parse(localStorage.getItem('naftalCards')) || []; // Initialisation document.addEventListener('DOMContentLoaded', () => { renderCards(); // Gestion du formulaire document.getElementById('cardForm').addEventListener('submit', (e) => { e.preventDefault(); saveCard(); }); }); // Sauvegarde d'une nouvelle carte function saveCard() { const cardName = document.getElementById('cardName').value.trim(); const cardType = document.getElementById('cardType').value; const cardNumber = document.getElementById('cardNumber').value.trim(); const fuelType = document.getElementById('fuelType').value; const initialAmount = parseFloat(document.getElementById('initialAmount').value) || 0; const cardLimit = document.getElementById('cardLimit').value ? parseInt(document.getElementById('cardLimit').value) : null; // Validation if (!cardName || !cardType || !cardNumber || !fuelType) { alert("Veuillez remplir tous les champs obligatoires"); return; } // Création de la carte const newCard = { id: Date.now(), name: cardName, type: cardType, number: cardNumber, fuelType: fuelType, limit: cardLimit, transactions: initialAmount > 0 ? [{ amount: initialAmount, date: new Date().toISOString().split('T')[0], description: "Solde initial", timestamp: new Date().toLocaleString() }] : [], createdAt: new Date().toLocaleString(), updatedAt: new Date().toLocaleString() }; cards.push(newCard); saveToLocalStorage(); renderCards(); resetForm(); } // Affichage des cartes function renderCards(filteredCards = null) { const cardList = document.getElementById('cardList'); const cardsToRender = filteredCards || cards; if (cardsToRender.length === 0) { cardList.innerHTML = document.getElementById('emptyState').outerHTML; return; } cardList.innerHTML = ''; cardsToRender.forEach(card => { const totalSpent = calculateTotal(card.transactions); const cardBg = getCardStyle(card.type); const cardElement = document.createElement('div'); cardElement.className = 'card-item bg-white border border-gray-200 rounded-lg p-4 transition duration-300 flip-in hover:border-indigo-200'; cardElement.innerHTML = ` <div class="flex justify-between items-start mb-2"> <div> <h3 class="font-semibold text-gray-800">${card.name}</h3> <p class="text-xs text-gray-500">${card.type} • ${card.fuelType}</p> </div> <span class="px-2 py-1 text-xs rounded-full ${cardBg.badge}"> ${card.number} </span> </div> <div class="flex items-center justify-between mt-3"> <div> <p class="text-sm text-gray-600">Dépenses totales</p> <p class="text-lg font-bold ${totalSpent > (card.limit || Infinity) ? 'text-red-600' : 'text-gray-800'}"> ${totalSpent.toFixed(2)} DA ${card.limit ? `<span class="text-xs font-normal text-gray-500">/ ${card.limit} DA</span>` : ''} </p> </div> <div class="flex space-x-2"> <button onclick="showCardDetails('${card.id}')" class="text-indigo-600 hover:text-indigo-800 transition p-2 rounded-full hover:bg-indigo-50"> <i class="fas fa-eye"></i> </button> <button onclick="addTransaction('${card.id}')" class="text-green-600 hover:text-green-800 transition p-2 rounded-full hover:bg-green-50"> <i class="fas fa-plus"></i> </button> <button onclick="deleteCard('${card.id}')" class="text-red-600 hover:text-red-800 transition p-2 rounded-full hover:bg-red-50"> <i class="fas fa-trash-alt"></i> </button> </div> </div> `; cardList.appendChild(cardElement); }); } // Détails d'une carte function showCardDetails(id) { const card = cards.find(c => c.id === parseInt(id)); if (!card) return; const totalSpent = calculateTotal(card.transactions); const limitPercentage = card.limit ? Math.min(100, (totalSpent / card.limit) * 100) : 0; const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4'; modal.innerHTML = ` <div class="bg-white rounded-xl shadow-xl max-w-2xl w-full max-h-[90vh] overflow-hidden flex flex-col"> <div class="p-6 border-b border-gray-200"> <div class="flex justify-between items-start"> <div> <h3 class="text-xl font-bold text-gray-800">${card.name}</h3> <p class="text-gray-600">${card.type} • ${card.fuelType} • ${card.number}</p> </div> <button onclick="this.parentElement.parentElement.parentElement.remove()" class="text-gray-500 hover:text-gray-700 p-1"> <i class="fas fa-times"></i> </button> </div> ${card.limit ? ` <div class="mt-4"> <div class="flex justify-between text-sm mb-1"> <span>Utilisation du plafond</span> <span>${totalSpent.toFixed(2)} DA / ${card.limit} DA</span> </div> <div class="w-full bg-gray-200 rounded-full h-2.5"> <div class="bg-${totalSpent > card.limit ? 'red' : 'indigo'}-600 h-2.5 rounded-full" style="width: ${limitPercentage}%"></div> </div> </div> ` : ''} </div> <div class="flex-1 overflow-y-auto p-6"> <h4 class="font-semibold text-gray-800 mb-3">Historique des transactions</h4> <table class="transaction-table w-full text-sm"> <thead> <tr class="text-left text-gray-500 border-b"> <th class="pb-2">Date</th> <th class="pb-2">Description</th> <th class="pb-2 text-right">Montant (DA)</th> </tr> </thead> <tbody> ${card.transactions.length > 0 ? card.transactions.map(t => ` <tr> <td class="py-3">${t.date}</td> <td>${t.description || 'Recharge carburant'}</td> <td class="text-right font-mono">${t.amount.toFixed(2)}</td> </tr> `).join('') : ` <tr> <td colspan="3" class="text-center py-8 text-gray-400"> Aucune transaction enregistrée </td> </tr> `} </tbody> <tfoot class="border-t"> <tr class="font-semibold"> <td class="pt-3" colspan="2">Total dépensé</td> <td class="text-right font-mono pt-3">${totalSpent.toFixed(2)} DA</td> </tr> </tfoot> </table> </div> <div class="p-4 border-t border-gray-200 bg-gray-50 flex justify-between"> <button onclick="addTransaction('${card.id}')" class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition"> <i class="fas fa-plus mr-2"></i>Nouvelle transaction </button> <div class="text-sm text-gray-500"> Créée le ${card.createdAt} </div> </div> </div> `; document.body.appendChild(modal); } // Ajout d'une transaction function addTransaction(cardId) { const card = cards.find(c => c.id === parseInt(cardId)); if (!card) return; const amount = parseFloat(prompt("Montant de la transaction (DA) :")); if (!amount || isNaN(amount)) return; const description = prompt("Description (facultatif) :") || "Recharge carburant"; card.transactions.push({ amount: amount, date: new Date().toISOString().split('T')[0], description: description, timestamp: new Date().toLocaleString() }); card.updatedAt = new Date().toLocaleString(); saveToLocalStorage(); renderCards(); // Ferme et rouvre les modals ouverts const openModal = document.querySelector('.fixed.inset-0'); if (openModal) openModal.remove(); showCardDetails(cardId); } // Suppression d'une carte function deleteCard(id) { if (confirm('Supprimer définitivement cette carte et son historique ?')) { cards = cards.filter(card => card.id !== parseInt(id)); saveToLocalStorage(); renderCards(); } } // Filtrage des cartes function filterCards() { const searchTerm = document.getElementById('searchInput').value.toLowerCase(); const filteredCards = cards.filter(card => card.name.toLowerCase().includes(searchTerm) || card.number.toLowerCase().includes(searchTerm) || card.type.toLowerCase().includes(searchTerm) ); renderCards(filteredCards); } // Export Excel function exportToExcel() { if (cards.length === 0) { alert("Aucune carte à exporter"); return; } // En-têtes let csvContent = "Nom,Type de carte,Numéro,Catégorie,Plafond,Total dépensé,Nb transactions,Dernière transaction,Créée le\n"; // Données cards.forEach(card => { const lastTransaction = card.transactions[card.transactions.length - 1]; csvContent += `"${card.name}","${card.type}","${card.number}","${card.fuelType}",` + `"${card.limit || 'Illimité'}","${calculateTotal(card.transactions).toFixed(2)}",` + `"${card.transactions.length}","${lastTransaction ? lastTransaction.date : '-'}",` + `"${card.createdAt}"\n`; }); // Téléchargement const blob = new Blob(["\uFEFF" + csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `cartes_naftal_${new Date().toISOString().split('T')[0]}.csv`; document.body.appendChild(link); link.click(); document.body.removeChild(link); } // Styles visuels par type de carte function getCardStyle(type) { const styles = { 'NAFTAL': { badge: 'bg-blue-100 text-blue-800' }, 'NAFTAL_Pro': { badge: 'bg-green-100 text-green-800' }, 'NAFTAL_Fleet': { badge: 'bg-purple-100 text-purple-800' }, 'default': { badge: 'bg-gray-100 text-gray-800' } }; return styles[type] || styles.default; } // Calcul du total des transactions function calculateTotal(transactions) { return transactions.reduce((sum, t) => sum + t.amount, 0); } // Sauvegarde locale function saveToLocalStorage() { localStorage.setItem('naftalCards', JSON.stringify(cards)); } // Réinitialisation du formulaire function resetForm() { document.getElementById('cardForm').reset(); document.getElementById('initialAmount').value = "0"; // Animation de confirmation const submitBtn = document.querySelector('#cardForm button[type="submit"]'); submitBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Enregistré !'; submitBtn.classList.replace('bg-indigo-600', 'bg-green-500'); submitBtn.classList.replace('hover:bg-indigo-700', 'hover:bg-green-600'); setTimeout(() => { submitBtn.innerHTML = '<i class="fas fa-save mr-2"></i> Enregistrer'; submitBtn.classList.replace('bg-green-500', 'bg-indigo-600'); submitBtn.classList.replace('hover:bg-green-600', 'hover:bg-indigo-700'); }, 2000); } // Recherche en temps réel document.getElementById('searchInput').addEventListener('input', filterCards); </script> </body> </html>" - Initial Deployment
aeb4da7 verified