Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Enregistrement Clients Pro</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @media (max-width: 640px) { | |
| .mobile-view { | |
| max-width: 100%; | |
| margin: 0 auto; | |
| border-radius: 0; | |
| box-shadow: none; | |
| height: 100vh; | |
| } | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.3s ease-in-out; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .rotate { | |
| animation: rotate 2s linear infinite; | |
| } | |
| @keyframes rotate { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| /* Custom scrollbar */ | |
| #data-list::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| #data-list::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| border-radius: 10px; | |
| } | |
| #data-list::-webkit-scrollbar-thumb { | |
| background: #c7d2fe; | |
| border-radius: 10px; | |
| } | |
| #data-list::-webkit-scrollbar-thumb:hover { | |
| background: #a5b4fc; | |
| } | |
| /* Tab styling */ | |
| .tab-button { | |
| transition: all 0.3s ease; | |
| } | |
| .tab-button.active { | |
| background-color: #4f46e5; | |
| color: white; | |
| } | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| animation: fadeIn 0.3s ease-in-out; | |
| } | |
| /* Professional logo */ | |
| .logo-pro { | |
| background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| color: transparent; | |
| font-weight: 800; | |
| letter-spacing: -0.05em; | |
| } | |
| .logo-icon { | |
| background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); | |
| color: white; | |
| border-radius: 50%; | |
| padding: 8px; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| /* Edit mode */ | |
| .edit-mode { | |
| border: 2px solid #4f46e5; | |
| box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.2); | |
| } | |
| /* Jean color for new form tab */ | |
| .jean-tab { | |
| background-color: #5d8aa8; | |
| color: white; | |
| } | |
| .jean-tab:hover { | |
| background-color: #4a6f8a; | |
| } | |
| .jean-tab.active { | |
| background-color: #3a5770; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 font-sans"> | |
| <div class="container mx-auto px-4 py-8 max-w-md mobile-view bg-white"> | |
| <!-- Header with professional logo --> | |
| <header class="mb-6"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <div class="flex items-center"> | |
| <div class="logo-icon mr-3"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path> | |
| <circle cx="9" cy="7" r="4"></circle> | |
| <path d="M22 21v-2a4 4 0 0 0-3-3.87"></path> | |
| <path d="M16 3.13a4 4 0 0 1 0 7.75"></path> | |
| </svg> | |
| </div> | |
| <div> | |
| <h1 class="text-2xl font-bold logo-pro"> | |
| Fiche Client <span class="text-xs bg-indigo-100 text-indigo-800 px-2 py-1 rounded-full ml-1">PRO</span> | |
| </h1> | |
| <p class="text-xs text-gray-500 mt-1" id="current-date">Date: <span id="today-date"></span></p> | |
| </div> | |
| </div> | |
| <div id="save-status" class="text-sm text-gray-500 flex items-center"> | |
| <span id="save-text">Sauvegardé</span> | |
| <i id="save-icon" class="fas fa-check-circle ml-1 text-green-500"></i> | |
| </div> | |
| </div> | |
| <div class="bg-indigo-50 p-3 rounded-lg border border-indigo-100"> | |
| <p class="text-indigo-800 text-sm flex items-start"> | |
| <i class="fas fa-info-circle mr-2 mt-1"></i> | |
| <span>Les fiches clients sont sauvegardées automatiquement. Exportez en Excel ou PDF quand vous voulez.</span> | |
| </p> | |
| </div> | |
| </header> | |
| <!-- Tabs Navigation --> | |
| <div class="flex border-b border-gray-200 mb-6"> | |
| <button class="tab-button jean-tab flex-1 py-2 px-4 text-center font-medium rounded-t-lg active" data-tab="form-tab"> | |
| <i class="fas fa-plus mr-2"></i> Nouvelle Fiche | |
| </button> | |
| <button class="tab-button flex-1 py-2 px-4 text-center font-medium rounded-t-lg" data-tab="list-tab"> | |
| <i class="fas fa-list mr-2"></i> Fiches Enregistrées | |
| <span id="entries-count" class="ml-1 bg-indigo-100 text-indigo-800 text-xs py-0.5 px-1.5 rounded-full"> | |
| 0 | |
| </span> | |
| </button> | |
| </div> | |
| <!-- Main Content --> | |
| <main> | |
| <!-- Form Tab --> | |
| <div id="form-tab" class="tab-content active"> | |
| <div class="mb-6"> | |
| <form id="data-form" class="space-y-3"> | |
| <input type="hidden" id="edit-id" name="edit-id" value=""> | |
| <!-- Date et Heure --> | |
| <div class="grid grid-cols-2 gap-3"> | |
| <div> | |
| <label for="date" class="block text-sm font-medium text-gray-700 mb-1"> | |
| <i class="fas fa-calendar-day mr-2"></i> Date* | |
| </label> | |
| <input type="date" id="date" name="date" required | |
| class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| <div> | |
| <label for="time" class="block text-sm font-medium text-gray-700 mb-1"> | |
| <i class="fas fa-clock mr-2"></i> Heure* | |
| </label> | |
| <input type="time" id="time" name="time" required | |
| class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| </div> | |
| <!-- Opérateur --> | |
| <div class="grid grid-cols-2 gap-3"> | |
| <div> | |
| <label for="operator-name" class="block text-sm font-medium text-gray-700 mb-1"> | |
| <i class="fas fa-user mr-2"></i> Nom Opérateur* | |
| </label> | |
| <input type="text" id="operator-name" name="operator-name" required | |
| class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| <div> | |
| <label for="operator-number" class="block text-sm font-medium text-gray-700 mb-1"> | |
| <i class="fas fa-id-card mr-2"></i> N° Opérateur* | |
| </label> | |
| <input type="text" id="operator-number" name="operator-number" required | |
| class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| </div> | |
| <!-- Technicien --> | |
| <div> | |
| <label for="technician" class="block text-sm font-medium text-gray-700 mb-1"> | |
| <i class="fas fa-tools mr-2"></i> Technicien* | |
| </label> | |
| <input type="text" id="technician" name="technician" required | |
| class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| <!-- Client --> | |
| <div class="grid grid-cols-2 gap-3"> | |
| <div> | |
| <label for="client-name" class="block text-sm font-medium text-gray-700 mb-1"> | |
| <i class="fas fa-user-tag mr-2"></i> Nom Client* | |
| </label> | |
| <input type="text" id="client-name" name="client-name" required | |
| class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| <div> | |
| <label for="client-number" class="block text-sm font-medium text-gray-700 mb-1"> | |
| <i class="fas fa-hashtag mr-2"></i> N° Client* | |
| </label> | |
| <input type="text" id="client-number" name="client-number" required | |
| class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| </div> | |
| <!-- Commentaire --> | |
| <div> | |
| <label for="comment" class="block text-sm font-medium text-gray-700 mb-1"> | |
| <i class="fas fa-comment-dots mr-2"></i> Commentaire | |
| </label> | |
| <textarea id="comment" name="comment" rows="2" | |
| class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"></textarea> | |
| </div> | |
| <div class="pt-2 flex space-x-3"> | |
| <button type="submit" id="submit-button" class="flex-1 bg-indigo-600 text-white py-2 px-4 rounded-lg hover:bg-indigo-700 transition duration-200 flex items-center justify-center"> | |
| <i class="fas fa-save mr-2"></i> Enregistrer | |
| </button> | |
| <button type="button" id="cancel-edit" class="flex-1 bg-gray-200 text-gray-700 py-2 px-4 rounded-lg hover:bg-gray-300 transition duration-200 flex items-center justify-center hidden"> | |
| <i class="fas fa-times mr-2"></i> Annuler | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- List Tab --> | |
| <div id="list-tab" class="tab-content"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-lg font-semibold text-gray-700"> | |
| <i class="fas fa-list-ol mr-2 text-indigo-500"></i> Fiches enregistrées | |
| </h2> | |
| <div class="flex space-x-2"> | |
| <button id="export-excel" class="bg-green-600 text-white px-3 py-1 rounded text-sm hover:bg-green-700 transition duration-200 flex items-center"> | |
| <i class="fas fa-file-excel mr-1"></i> Excel | |
| </button> | |
| <button id="export-pdf" class="bg-red-600 text-white px-3 py-1 rounded text-sm hover:bg-red-700 transition duration-200 flex items-center"> | |
| <i class="fas fa-file-pdf mr-1"></i> PDF | |
| </button> | |
| <button id="clear-all" class="bg-gray-200 text-gray-700 px-3 py-1 rounded text-sm hover:bg-gray-300 transition duration-200 flex items-center"> | |
| <i class="fas fa-trash-alt mr-1"></i> Tout effacer | |
| </button> | |
| </div> | |
| </div> | |
| <div id="data-list" class="space-y-3 max-h-96 overflow-y-auto pr-2"> | |
| <!-- Les fiches seront ajoutées ici dynamiquement --> | |
| </div> | |
| <div id="no-data" class="text-center py-8 text-gray-500"> | |
| <i class="fas fa-database text-4xl mb-2 opacity-30"></i> | |
| <p>Aucune fiche enregistrée</p> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Footer --> | |
| <footer class="mt-8 text-center text-sm text-gray-500 border-t pt-4"> | |
| <p>Application professionnelle d'enregistrement client</p> | |
| <p class="mt-1">2025 Made Love ❤️ By Imed Ramdani & R.Nawel. | |
| Version NL par Steve Demachaer. | |
| All rights reserved.</p> | |
| </footer> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const { jsPDF } = window.jspdf; | |
| const dataForm = document.getElementById('data-form'); | |
| const dataList = document.getElementById('data-list'); | |
| const noData = document.getElementById('no-data'); | |
| const exportExcel = document.getElementById('export-excel'); | |
| const exportPdf = document.getElementById('export-pdf'); | |
| const clearAll = document.getElementById('clear-all'); | |
| const saveStatus = document.getElementById('save-status'); | |
| const saveText = document.getElementById('save-text'); | |
| const saveIcon = document.getElementById('save-icon'); | |
| const entriesCount = document.getElementById('entries-count'); | |
| const tabButtons = document.querySelectorAll('.tab-button'); | |
| const tabContents = document.querySelectorAll('.tab-content'); | |
| const submitButton = document.getElementById('submit-button'); | |
| const cancelEditButton = document.getElementById('cancel-edit'); | |
| const editIdInput = document.getElementById('edit-id'); | |
| const todayDateElement = document.getElementById('today-date'); | |
| // Set today's date in the header | |
| const today = new Date(); | |
| const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; | |
| todayDateElement.textContent = today.toLocaleDateString('fr-FR', options); | |
| let entries = JSON.parse(localStorage.getItem('clientEntries')) || []; | |
| let saveTimeout; | |
| let isEditing = false; | |
| // Set default date and time to now | |
| const now = new Date(); | |
| document.getElementById('date').valueAsDate = now; | |
| document.getElementById('time').value = now.toTimeString().substring(0, 5); | |
| // Initial render | |
| renderDataList(); | |
| updateEntriesCount(); | |
| // Tab switching | |
| tabButtons.forEach(button => { | |
| button.addEventListener('click', () => { | |
| const tabId = button.getAttribute('data-tab'); | |
| // Update active tab button | |
| tabButtons.forEach(btn => btn.classList.remove('active')); | |
| button.classList.add('active'); | |
| // Update active tab content | |
| tabContents.forEach(content => content.classList.remove('active')); | |
| document.getElementById(tabId).classList.add('active'); | |
| }); | |
| }); | |
| // Form submission | |
| dataForm.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const formData = new FormData(dataForm); | |
| const entryId = formData.get('edit-id'); | |
| const entry = { | |
| id: entryId || Date.now(), | |
| date: formData.get('date'), | |
| time: formData.get('time'), | |
| operatorName: formData.get('operator-name'), | |
| operatorNumber: formData.get('operator-number'), | |
| technician: formData.get('technician'), | |
| clientName: formData.get('client-name'), | |
| clientNumber: formData.get('client-number'), | |
| comment: formData.get('comment') | |
| }; | |
| // Validate required fields | |
| if (!entry.date || !entry.time || !entry.operatorName || !entry.operatorNumber || | |
| !entry.technician || !entry.clientName || !entry.clientNumber) { | |
| showAlert('Veuillez remplir tous les champs obligatoires', 'error'); | |
| return; | |
| } | |
| // Format date for display (JJ/MM/AAAA) | |
| const formattedDate = new Date(entry.date).toLocaleDateString('fr-FR'); | |
| if (isEditing) { | |
| // Update existing entry | |
| const index = entries.findIndex(e => e.id == entryId); | |
| if (index !== -1) { | |
| entries[index] = { | |
| ...entry, | |
| displayDate: formattedDate | |
| }; | |
| } | |
| showAlert('Fiche client mise à jour avec succès', 'success'); | |
| } else { | |
| // Add new entry | |
| entries.unshift({ | |
| ...entry, | |
| displayDate: formattedDate | |
| }); | |
| showAlert('Fiche client enregistrée avec succès', 'success'); | |
| } | |
| saveData(); | |
| resetForm(); | |
| renderDataList(); | |
| updateEntriesCount(); | |
| // Switch to list tab after submission | |
| document.querySelector('[data-tab="list-tab"]').click(); | |
| }); | |
| // Cancel edit | |
| cancelEditButton.addEventListener('click', function() { | |
| resetForm(); | |
| }); | |
| // Export to Excel | |
| exportExcel.addEventListener('click', function() { | |
| if (entries.length === 0) { | |
| showAlert('Aucune fiche à exporter', 'error'); | |
| return; | |
| } | |
| // Create worksheet | |
| const ws = XLSX.utils.json_to_sheet(entries.map(entry => ({ | |
| 'Date': entry.displayDate, | |
| 'Heure': entry.time, | |
| 'Opérateur': entry.operatorName, | |
| 'N° Opérateur': entry.operatorNumber, | |
| 'Technicien': entry.technician, | |
| 'Client': entry.clientName, | |
| 'N° Client': entry.clientNumber, | |
| 'Commentaire': entry.comment || '' | |
| }))); | |
| // Create workbook | |
| const wb = XLSX.utils.book_new(); | |
| XLSX.utils.book_append_sheet(wb, ws, "Fiches Clients"); | |
| // Export to file | |
| XLSX.writeFile(wb, "fiches_clients.xlsx"); | |
| showAlert('Export Excel réussi', 'success'); | |
| }); | |
| // Export to PDF | |
| exportPdf.addEventListener('click', function() { | |
| if (entries.length === 0) { | |
| showAlert('Aucune fiche à exporter', 'error'); | |
| return; | |
| } | |
| // Create a temporary div to hold the PDF content | |
| const pdfContent = document.createElement('div'); | |
| pdfContent.style.padding = '20px'; | |
| pdfContent.style.backgroundColor = 'white'; | |
| pdfContent.style.maxWidth = '600px'; | |
| pdfContent.style.margin = '0 auto'; | |
| // Add title | |
| const title = document.createElement('h1'); | |
| title.textContent = 'Fiches Clients Exportées'; | |
| title.style.textAlign = 'center'; | |
| title.style.marginBottom = '20px'; | |
| title.style.fontSize = '20px'; | |
| title.style.color = '#4f46e5'; | |
| title.style.fontWeight = 'bold'; | |
| pdfContent.appendChild(title); | |
| // Add date | |
| const exportDate = document.createElement('p'); | |
| exportDate.textContent = 'Exporté le: ' + new Date().toLocaleDateString('fr-FR'); | |
| exportDate.style.textAlign = 'center'; | |
| exportDate.style.marginBottom = '20px'; | |
| exportDate.style.color = '#6b7280'; | |
| pdfContent.appendChild(exportDate); | |
| // Create table | |
| const table = document.createElement('table'); | |
| table.style.width = '100%'; | |
| table.style.borderCollapse = 'collapse'; | |
| table.style.fontSize = '12px'; | |
| // Add table header | |
| const thead = document.createElement('thead'); | |
| const headerRow = document.createElement('tr'); | |
| headerRow.style.backgroundColor = '#eef2ff'; | |
| ['Date', 'Heure', 'Opérateur', 'N° Op', 'Technicien', 'Client', 'N° Client', 'Commentaire'].forEach(text => { | |
| const th = document.createElement('th'); | |
| th.textContent = text; | |
| th.style.padding = '8px'; | |
| th.style.border = '1px solid #e5e7eb'; | |
| th.style.textAlign = 'left'; | |
| headerRow.appendChild(th); | |
| }); | |
| thead.appendChild(headerRow); | |
| table.appendChild(thead); | |
| // Add table body | |
| const tbody = document.createElement('tbody'); | |
| entries.forEach(entry => { | |
| const row = document.createElement('tr'); | |
| row.style.borderBottom = '1px solid #e5e7eb'; | |
| [ | |
| entry.displayDate, | |
| entry.time, | |
| entry.operatorName, | |
| entry.operatorNumber, | |
| entry.technician, | |
| entry.clientName, | |
| entry.clientNumber, | |
| entry.comment || '' | |
| ].forEach(text => { | |
| const td = document.createElement('td'); | |
| td.textContent = text; | |
| td.style.padding = '8px'; | |
| td.style.border = '1px solid #e5e7eb'; | |
| row.appendChild(td); | |
| }); | |
| tbody.appendChild(row); | |
| }); | |
| table.appendChild(tbody); | |
| pdfContent.appendChild(table); | |
| // Add to document temporarily | |
| document.body.appendChild(pdfContent); | |
| // Generate PDF | |
| html2canvas(pdfContent).then(canvas => { | |
| const imgData = canvas.toDataURL('image/png'); | |
| const pdf = new jsPDF({ | |
| orientation: 'landscape', | |
| unit: 'mm' | |
| }); | |
| const imgProps = pdf.getImageProperties(imgData); | |
| const pdfWidth = pdf.internal.pageSize.getWidth(); | |
| const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width; | |
| pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight); | |
| pdf.save('fiches_clients.pdf'); | |
| // Remove temporary element | |
| document.body.removeChild(pdfContent); | |
| showAlert('Export PDF réussi', 'success'); | |
| }); | |
| }); | |
| // Clear all data | |
| clearAll.addEventListener('click', function() { | |
| if (entries.length === 0) return; | |
| if (confirm('Êtes-vous sûr de vouloir supprimer toutes les fiches clients ?')) { | |
| entries = []; | |
| saveData(); | |
| renderDataList(); | |
| updateEntriesCount(); | |
| showAlert('Toutes les fiches ont été supprimées', 'success'); | |
| } | |
| }); | |
| // Delete single entry | |
| function deleteEntry(id) { | |
| if (confirm('Êtes-vous sûr de vouloir supprimer cette fiche client ?')) { | |
| entries = entries.filter(entry => entry.id !== id); | |
| saveData(); | |
| renderDataList(); | |
| updateEntriesCount(); | |
| showAlert('Fiche supprimée', 'success'); | |
| } | |
| } | |
| // Edit single entry | |
| function editEntry(id) { | |
| const entry = entries.find(e => e.id == id); | |
| if (!entry) return; | |
| // Fill form with entry data | |
| document.getElementById('edit-id').value = entry.id; | |
| document.getElementById('date').value = entry.date; | |
| document.getElementById('time').value = entry.time; | |
| document.getElementById('operator-name').value = entry.operatorName; | |
| document.getElementById('operator-number').value = entry.operatorNumber; | |
| document.getElementById('technician').value = entry.technician; | |
| document.getElementById('client-name').value = entry.clientName; | |
| document.getElementById('client-number').value = entry.clientNumber; | |
| document.getElementById('comment').value = entry.comment || ''; | |
| // Update UI for edit mode | |
| isEditing = true; | |
| submitButton.innerHTML = '<i class="fas fa-save mr-2"></i> Mettre à jour'; | |
| cancelEditButton.classList.remove('hidden'); | |
| document.querySelector('[data-tab="form-tab"]').click(); | |
| // Scroll to top | |
| window.scrollTo(0, 0); | |
| } | |
| // Reset form to default state | |
| function resetForm() { | |
| dataForm.reset(); | |
| document.getElementById('edit-id').value = ''; | |
| isEditing = false; | |
| submitButton.innerHTML = '<i class="fas fa-save mr-2"></i> Enregistrer'; | |
| cancelEditButton.classList.add('hidden'); | |
| // Reset date and time to now | |
| const now = new Date(); | |
| document.getElementById('date').valueAsDate = now; | |
| document.getElementById('time').value = now.toTimeString().substring(0, 5); | |
| } | |
| // Save data to localStorage | |
| function saveData() { | |
| // Show saving status | |
| saveText.textContent = 'Sauvegarde...'; | |
| saveIcon.className = 'fas fa-circle-notch ml-1 text-indigo-500 rotate'; | |
| // Clear any existing timeout | |
| if (saveTimeout) clearTimeout(saveTimeout); | |
| // Set timeout to simulate async save | |
| saveTimeout = setTimeout(() => { | |
| localStorage.setItem('clientEntries', JSON.stringify(entries)); | |
| // Update status | |
| saveText.textContent = 'Sauvegardé'; | |
| saveIcon.className = 'fas fa-check-circle ml-1 text-green-500'; | |
| // Reset status after 3 seconds | |
| setTimeout(() => { | |
| saveText.textContent = 'Auto-sauvegarde'; | |
| saveIcon.className = 'fas fa-history ml-1 text-gray-500'; | |
| }, 3000); | |
| }, 800); | |
| } | |
| // Render data list | |
| function renderDataList() { | |
| if (entries.length === 0) { | |
| dataList.innerHTML = ''; | |
| noData.style.display = 'block'; | |
| return; | |
| } | |
| noData.style.display = 'none'; | |
| dataList.innerHTML = entries.map(entry => ` | |
| <div class="bg-white p-3 rounded-lg shadow-sm border border-gray-100 fade-in" id="entry-${entry.id}"> | |
| <div class="flex justify-between items-start mb-1"> | |
| <div> | |
| <span class="text-xs font-medium text-indigo-600">${entry.displayDate} à ${entry.time}</span> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button onclick="editEntry(${entry.id})" class="text-blue-500 text-xs hover:text-blue-700"> | |
| <i class="fas fa-edit mr-1"></i> | |
| </button> | |
| <button onclick="deleteEntry(${entry.id})" class="text-red-500 text-xs hover:text-red-700"> | |
| <i class="fas fa-trash-alt"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-2 text-xs mb-2"> | |
| <div> | |
| <span class="font-medium">Opérateur:</span> | |
| <p>${entry.operatorName} (${entry.operatorNumber})</p> | |
| </div> | |
| <div> | |
| <span class="font-medium">Technicien:</span> | |
| <p>${entry.technician}</p> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-2 text-xs mb-2"> | |
| <div> | |
| <span class="font-medium">Client:</span> | |
| <p>${entry.clientName}</p> | |
| </div> | |
| <div> | |
| <span class="font-medium">N° Client:</span> | |
| <p>${entry.clientNumber}</p> | |
| </div> | |
| </div> | |
| ${entry.comment ? ` | |
| <div class="text-xs mt-1"> | |
| <span class="font-medium">Commentaire:</span> | |
| <p class="text-gray-600">${entry.comment}</p> | |
| </div> | |
| ` : ''} | |
| </div> | |
| `).join(''); | |
| } | |
| // Update entries count in tab button | |
| function updateEntriesCount() { | |
| entriesCount.textContent = entries.length; | |
| } | |
| // Show alert message | |
| function showAlert(message, type) { | |
| const alert = document.createElement('div'); | |
| alert.className = `fixed top-4 left-1/2 transform -translate-x-1/2 px-6 py-3 rounded-lg shadow-lg text-white font-medium text-sm flex items-center ${ | |
| type === 'success' ? 'bg-green-500' : 'bg-red-500' | |
| }`; | |
| alert.innerHTML = ` | |
| <i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'} mr-2"></i> | |
| ${message} | |
| `; | |
| document.body.appendChild(alert); | |
| setTimeout(() => { | |
| alert.classList.add('opacity-0', 'transition-opacity', 'duration-300'); | |
| setTimeout(() => alert.remove(), 300); | |
| }, 3000); | |
| } | |
| // Auto-save indicator | |
| setInterval(() => { | |
| if (saveText.textContent === 'Auto-sauvegarde') { | |
| saveIcon.className = 'fas fa-circle-notch ml-1 text-indigo-500 rotate'; | |
| setTimeout(() => { | |
| if (saveText.textContent === 'Auto-sauvegarde') { | |
| saveIcon.className = 'fas fa-history ml-1 text-gray-500'; | |
| } | |
| }, 1000); | |
| } | |
| }, 30000); | |
| // Make functions available globally | |
| window.deleteEntry = deleteEntry; | |
| window.editEntry = editEntry; | |
| }); | |
| </script> | |
| </body> | |
| </html> |