| <!DOCTYPE html> |
| <html lang="fr"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Classico Party - Kandi</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.1/build/qrcode.min.js"></script> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap'); |
| |
| body { |
| font-family: 'Poppins', sans-serif; |
| background-color: #f8f9fa; |
| } |
| |
| .ticket { |
| background: linear-gradient(135deg, #1e3a8a 0%, #7c3aed 100%); |
| border-radius: 12px; |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .ticket::before { |
| content: ""; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| height: 8px; |
| background: linear-gradient(90deg, #f59e0b 0%, #ef4444 50%, #f59e0b 100%); |
| } |
| |
| .perforation { |
| position: absolute; |
| width: 20px; |
| height: 20px; |
| background-color: #f8f9fa; |
| border-radius: 50%; |
| top: 50%; |
| transform: translateY(-50%); |
| } |
| |
| .perforation-left { |
| left: -10px; |
| } |
| |
| .perforation-right { |
| right: -10px; |
| } |
| |
| .qr-container { |
| background: white; |
| padding: 8px; |
| border-radius: 8px; |
| } |
| |
| .ticket-strip { |
| background: repeating-linear-gradient( |
| 45deg, |
| #f59e0b, |
| #f59e0b 10px, |
| #ef4444 10px, |
| #ef4444 20px |
| ); |
| height: 6px; |
| margin: 10px 0; |
| } |
| |
| .ticket-type { |
| background-color: rgba(255, 255, 255, 0.2); |
| border-radius: 20px; |
| padding: 2px 12px; |
| font-weight: 600; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100 min-h-screen"> |
| <div class="container mx-auto px-4 py-8"> |
| <div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden mb-8"> |
| <div class="bg-blue-900 p-4"> |
| <h1 class="text-2xl font-bold text-white text-center">Classico Party</h1> |
| <p class="text-white text-center">Hôtel Bide de Kandi</p> |
| </div> |
| |
| <div class="p-6"> |
| <div class="mb-6"> |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Générer un ticket</h2> |
| |
| <div class="space-y-4"> |
| <div> |
| <label class="block text-gray-700 mb-2" for="name">Nom du client</label> |
| <input type="text" id="name" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> |
| </div> |
| |
| <div> |
| <label class="block text-gray-700 mb-2" for="phone">Téléphone</label> |
| <input type="tel" id="phone" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> |
| </div> |
| |
| <div> |
| <label class="block text-gray-700 mb-2" for="type">Type de ticket</label> |
| <select id="type" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> |
| <option value="standard">Standard - 2000 FCFA</option> |
| <option value="vip">VIP - 5000 FCFA</option> |
| <option value="table">Table VIP - 25000 FCFA</option> |
| </select> |
| </div> |
| |
| <div> |
| <label class="block text-gray-700 mb-2" for="payment">Méthode de paiement</label> |
| <select id="payment" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"> |
| <option value="cash">Cash</option> |
| <option value="momo">Mobile Money</option> |
| <option value="wave">Wave</option> |
| </select> |
| </div> |
| |
| <button id="generateBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200"> |
| Générer le ticket |
| </button> |
| </div> |
| </div> |
| |
| <div class="border-t pt-6"> |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Historique des ventes</h2> |
| |
| <div class="overflow-x-auto"> |
| <table class="min-w-full bg-white rounded-lg overflow-hidden"> |
| <thead class="bg-gray-100"> |
| <tr> |
| <th class="py-2 px-4 text-left">Nom</th> |
| <th class="py-2 px-4 text-left">Type</th> |
| <th class="py-2 px-4 text-left">Prix</th> |
| </tr> |
| </thead> |
| <tbody id="salesHistory" class="divide-y divide-gray-200"> |
| |
| </tbody> |
| </table> |
| </div> |
| |
| <div class="mt-4 flex space-x-2"> |
| <button id="exportBtn" class="flex-1 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-lg transition duration-200"> |
| Exporter les données |
| </button> |
| <button id="clearBtn" class="flex-1 bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-lg transition duration-200"> |
| Effacer tout |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div id="ticketPreview" class="max-w-md mx-auto hidden"> |
| <div class="ticket text-white p-6 mb-8"> |
| <div class="flex justify-between items-start mb-4"> |
| <div> |
| <h2 class="text-2xl font-bold">CLASSICO PARTY</h2> |
| <p class="text-sm opacity-80">Hôtel Bide de Kandi</p> |
| </div> |
| <div class="ticket-type" id="ticketTypeDisplay">Standard</div> |
| </div> |
| |
| <div class="ticket-strip"></div> |
| |
| <div class="flex justify-between items-center mb-4"> |
| <div> |
| <p class="text-sm opacity-80">Nom</p> |
| <p class="font-semibold" id="ticketName">John Doe</p> |
| </div> |
| <div> |
| <p class="text-sm opacity-80">Téléphone</p> |
| <p class="font-semibold" id="ticketPhone">+229 XX XX XX XX</p> |
| </div> |
| </div> |
| |
| <div class="flex justify-between items-center mb-6"> |
| <div> |
| <p class="text-sm opacity-80">Date</p> |
| <p class="font-semibold" id="ticketDate">01 Jan 2023</p> |
| </div> |
| <div> |
| <p class="text-sm opacity-80">Prix</p> |
| <p class="font-semibold" id="ticketPrice">2000 FCFA</p> |
| </div> |
| </div> |
| |
| <div class="flex justify-center mb-4"> |
| <div class="qr-container"> |
| <canvas id="qrCode"></canvas> |
| </div> |
| </div> |
| |
| <div class="text-center"> |
| <p class="text-xs opacity-70">Présentez ce ticket à l'entrée</p> |
| <p class="text-xs font-mono mt-1" id="ticketId">ID: CL2024-XXXXX</p> |
| </div> |
| </div> |
| |
| <div class="flex space-x-4"> |
| <button id="printBtn" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200"> |
| Imprimer |
| </button> |
| <button id="closeTicketBtn" class="flex-1 bg-gray-600 hover:bg-gray-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200"> |
| Fermer |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| let salesData = JSON.parse(localStorage.getItem('classicoPartySales')) || []; |
| updateSalesHistory(); |
| |
| |
| document.getElementById('generateBtn').addEventListener('click', generateTicket); |
| |
| |
| document.getElementById('exportBtn').addEventListener('click', exportData); |
| |
| |
| document.getElementById('clearBtn').addEventListener('click', clearData); |
| |
| |
| document.getElementById('printBtn').addEventListener('click', printTicket); |
| |
| |
| document.getElementById('closeTicketBtn').addEventListener('click', function() { |
| document.getElementById('ticketPreview').classList.add('hidden'); |
| }); |
| |
| function generateTicket() { |
| const name = document.getElementById('name').value.trim(); |
| const phone = document.getElementById('phone').value.trim(); |
| const type = document.getElementById('type').value; |
| const payment = document.getElementById('payment').value; |
| |
| if (!name) { |
| alert('Veuillez entrer le nom du client'); |
| return; |
| } |
| |
| |
| const timestamp = new Date().getTime(); |
| const randomNum = Math.floor(Math.random() * 10000); |
| const ticketId = `CL2024-${randomNum.toString().padStart(5, '0')}`; |
| |
| |
| let price; |
| let typeDisplay; |
| switch(type) { |
| case 'vip': |
| price = 5000; |
| typeDisplay = 'VIP'; |
| break; |
| case 'table': |
| price = 25000; |
| typeDisplay = 'Table VIP'; |
| break; |
| default: |
| price = 2000; |
| typeDisplay = 'Standard'; |
| } |
| |
| |
| const ticketData = { |
| id: ticketId, |
| name: name, |
| phone: phone, |
| type: type, |
| typeDisplay: typeDisplay, |
| price: price, |
| payment: payment, |
| date: new Date().toLocaleDateString('fr-FR', { |
| day: '2-digit', |
| month: 'short', |
| year: 'numeric' |
| }), |
| timestamp: timestamp |
| }; |
| |
| |
| salesData.push(ticketData); |
| localStorage.setItem('classicoPartySales', JSON.stringify(salesData)); |
| |
| |
| updateSalesHistory(); |
| |
| |
| displayTicket(ticketData); |
| |
| |
| document.getElementById('name').value = ''; |
| document.getElementById('phone').value = ''; |
| } |
| |
| function displayTicket(ticketData) { |
| |
| document.getElementById('ticketName').textContent = ticketData.name; |
| document.getElementById('ticketPhone').textContent = ticketData.phone; |
| document.getElementById('ticketTypeDisplay').textContent = ticketData.typeDisplay; |
| document.getElementById('ticketDate').textContent = ticketData.date; |
| document.getElementById('ticketPrice').textContent = `${ticketData.price} FCFA`; |
| document.getElementById('ticketId').textContent = `ID: ${ticketData.id}`; |
| |
| |
| const qrData = JSON.stringify({ |
| id: ticketData.id, |
| name: ticketData.name, |
| event: 'Classico Party Kandi' |
| }); |
| |
| QRCode.toCanvas(document.getElementById('qrCode'), qrData, { |
| width: 150, |
| margin: 0, |
| color: { |
| dark: '#1e3a8a', |
| light: '#ffffff' |
| } |
| }, function(error) { |
| if (error) console.error(error); |
| }); |
| |
| |
| document.getElementById('ticketPreview').classList.remove('hidden'); |
| document.getElementById('ticketPreview').scrollIntoView({ behavior: 'smooth' }); |
| } |
| |
| function updateSalesHistory() { |
| const historyTable = document.getElementById('salesHistory'); |
| historyTable.innerHTML = ''; |
| |
| if (salesData.length === 0) { |
| const row = document.createElement('tr'); |
| row.innerHTML = '<td colspan="3" class="py-4 px-4 text-center text-gray-500">Aucune vente enregistrée</td>'; |
| historyTable.appendChild(row); |
| return; |
| } |
| |
| |
| const sortedSales = [...salesData].sort((a, b) => b.timestamp - a.timestamp); |
| |
| sortedSales.forEach(sale => { |
| const row = document.createElement('tr'); |
| row.innerHTML = ` |
| <td class="py-2 px-4">${sale.name}</td> |
| <td class="py-2 px-4">${sale.typeDisplay}</td> |
| <td class="py-2 px-4">${sale.price} FCFA</td> |
| `; |
| historyTable.appendChild(row); |
| }); |
| } |
| |
| function exportData() { |
| if (salesData.length === 0) { |
| alert('Aucune donnée à exporter'); |
| return; |
| } |
| |
| |
| const headers = ['ID', 'Nom', 'Téléphone', 'Type', 'Prix (FCFA)', 'Paiement', 'Date']; |
| const csvRows = [ |
| headers.join(','), |
| ...salesData.map(sale => |
| [ |
| sale.id, |
| `"${sale.name.replace(/"/g, '""')}"`, |
| sale.phone, |
| sale.typeDisplay, |
| sale.price, |
| sale.payment, |
| sale.date |
| ].join(',') |
| ) |
| ]; |
| |
| const csvContent = csvRows.join('\n'); |
| const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); |
| const url = URL.createObjectURL(blob); |
| |
| // Create download link |
| const link = document.createElement('a'); |
| link.setAttribute('href', url); |
| link.setAttribute('download', `classico_party_sales_${new Date().toISOString().slice(0, 10)}.csv`); |
| link.style.visibility = 'hidden'; |
| document.body.appendChild(link); |
| link.click(); |
| document.body.removeChild(link); |
| |
| alert('Données exportées avec succès'); |
| } |
| |
| function clearData() { |
| if (confirm('Êtes-vous sûr de vouloir effacer toutes les données de vente ? Cette action est irréversible.')) { |
| localStorage.removeItem('classicoPartySales'); |
| salesData = []; |
| updateSalesHistory(); |
| alert('Toutes les données ont été effacées'); |
| } |
| } |
| |
| function printTicket() { |
| // In a real app, this would use a print API or generate a PDF |
| // For this demo, we'll just show an alert |
| alert('Fonction d\'impression: Dans une application réelle, cela enverrait le ticket à une imprimante Bluetooth ou générerait un PDF.'); |
| |
| // Alternative: Open print dialog for the ticket |
| // const ticketElement = document.getElementById('ticketPreview').cloneNode(true); |
| // ticketElement.classList.remove('hidden'); |
| // const printWindow = window.open('', '_blank'); |
| // printWindow.document.write('<html><head><title>Ticket Classico Party</title></head><body>'); |
| // printWindow.document.write(ticketElement.innerHTML); |
| // printWindow.document.write('<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=KINGFAUS/ticket" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body></html> |