| <!DOCTYPE html> |
| <html lang="fr"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Gestion des Appareils - ComSync Pro</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://unpkg.com/feather-icons"></script> |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.net.min.js"></script> |
| <link rel="stylesheet" href="style.css"> |
| <style> |
| .device-type-tab { |
| transition: all 0.3s ease; |
| } |
| .device-type-tab.active { |
| background: rgba(59, 130, 246, 0.2); |
| border-color: #3b82f6; |
| color: #3b82f6; |
| } |
| .qr-scanner { |
| background: repeating-linear-gradient( |
| 0deg, |
| rgba(0,0,0,0.15), |
| rgba(0,0,0,0.15) 1px, |
| transparent 1px, |
| transparent 2px |
| ); |
| animation: scan 2s linear infinite; |
| } |
| @keyframes scan { |
| 0% { background-position: 0 0; } |
| 100% { background-position: 0 100px; } |
| } |
| .status-badge { |
| display: inline-flex; |
| align-items: center; |
| padding: 0.25rem 0.75rem; |
| border-radius: 9999px; |
| font-size: 0.75rem; |
| font-weight: 500; |
| } |
| .status-online { background: rgba(16, 185, 129, 0.2); color: #10b981; } |
| .status-offline { background: rgba(239, 68, 68, 0.2); color: #ef4444; } |
| .status-pending { background: rgba(245, 158, 11, 0.2); color: #f59e0b; } |
| .brand-logo { |
| width: 40px; |
| height: 40px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| border-radius: 8px; |
| font-weight: bold; |
| font-size: 0.75rem; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-900 text-gray-100"> |
| <div id="vanta-bg" class="fixed inset-0 z-0"></div> |
| |
| <script src="components/navbar.js"></script> |
| <custom-navbar></custom-navbar> |
| |
| <div class="relative z-10 min-h-screen p-4 md:p-6"> |
| |
| <header class="bg-gray-800/80 backdrop-blur-md rounded-xl p-4 mb-6 border border-gray-700/50 shadow-lg"> |
| <div class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4"> |
| <div> |
| <h1 class="text-2xl font-bold bg-gradient-to-r from-blue-400 to-purple-400 bg-clip-text text-transparent">Gestion des Appareils</h1> |
| <p class="text-gray-400 text-sm mt-1" id="company-info">Société : <span class="text-white">Chargement...</span></p> |
| </div> |
| <div class="flex items-center gap-3"> |
| <div class="bg-gray-700/50 rounded-lg p-2 flex items-center space-x-3"> |
| <span class="text-sm text-gray-400">ID Société:</span> |
| <span class="font-mono text-sm text-blue-400" id="company-id-display">-</span> |
| <button onclick="copyCompanyId()" class="text-gray-400 hover:text-white"> |
| <i data-feather="copy" class="w-4 h-4"></i> |
| </button> |
| </div> |
| <button onclick="openAddDeviceModal()" class="btn-primary flex items-center"> |
| <i data-feather="plus" class="w-4 h-4 mr-2"></i> |
| Ajouter Appareil |
| </button> |
| </div> |
| </div> |
| </header> |
|
|
| |
| <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6"> |
| <div class="bg-gray-800/80 backdrop-blur-sm rounded-xl p-4 border border-gray-700/50"> |
| <div class="flex items-center justify-between mb-2"> |
| <i data-feather="radio" class="w-5 h-5 text-green-400"></i> |
| <span class="text-xs text-gray-400">Talkies FM</span> |
| </div> |
| <p class="text-2xl font-bold" id="count-radios">0</p> |
| </div> |
| <div class="bg-gray-800/80 backdrop-blur-sm rounded-xl p-4 border border-gray-700/50"> |
| <div class="flex items-center justify-between mb-2"> |
| <i data-feather="smartphone" class="w-5 h-5 text-blue-400"></i> |
| <span class="text-xs text-gray-400">Android</span> |
| </div> |
| <p class="text-2xl font-bold" id="count-android">0</p> |
| </div> |
| <div class="bg-gray-800/80 backdrop-blur-sm rounded-xl p-4 border border-gray-700/50"> |
| <div class="flex items-center justify-between mb-2"> |
| <i data-feather="tablet" class="w-5 h-5 text-purple-400"></i> |
| <span class="text-xs text-gray-400">Tablettes</span> |
| </div> |
| <p class="text-2xl font-bold" id="count-tablets">0</p> |
| </div> |
| <div class="bg-gray-800/80 backdrop-blur-sm rounded-xl p-4 border border-gray-700/50"> |
| <div class="flex items-center justify-between mb-2"> |
| <i data-feather="activity" class="w-5 h-5 text-green-400"></i> |
| <span class="text-xs text-gray-400">En ligne</span> |
| </div> |
| <p class="text-2xl font-bold" id="count-online">0</p> |
| </div> |
| </div> |
|
|
| |
| <div class="bg-gray-800/80 backdrop-blur-md rounded-xl p-2 mb-6 border border-gray-700/50"> |
| <div class="flex space-x-2 overflow-x-auto"> |
| <button onclick="switchTab('all')" class="device-type-tab active px-4 py-2 rounded-lg text-sm font-medium border border-transparent" id="tab-all"> |
| Tous les appareils |
| </button> |
| <button onclick="switchTab('radios')" class="device-type-tab px-4 py-2 rounded-lg text-sm font-medium border border-transparent" id="tab-radios"> |
| <i data-feather="radio" class="w-4 h-4 inline mr-1"></i> Talkies FM |
| </button> |
| <button onclick="switchTab('android')" class="device-type-tab px-4 py-2 rounded-lg text-sm font-medium border border-transparent" id="tab-android"> |
| <i data-feather="smartphone" class="w-4 h-4 inline mr-1"></i> Android |
| </button> |
| <button onclick="switchTab('tablets')" class="device-type-tab px-4 py-2 rounded-lg text-sm font-medium border border-transparent" id="tab-tablets"> |
| <i data-feather="tablet" class="w-4 h-4 inline mr-1"></i> Tablettes |
| </button> |
| <button onclick="switchTab('pending')" class="device-type-tab px-4 py-2 rounded-lg text-sm font-medium border border-transparent" id="tab-pending"> |
| <i data-feather="clock" class="w-4 h-4 inline mr-1"></i> En attente |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="devices-container" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> |
| |
| </div> |
|
|
| |
| <div id="empty-state" class="hidden text-center py-12"> |
| <i data-feather="smartphone" class="w-16 h-16 text-gray-600 mx-auto mb-4"></i> |
| <h3 class="text-xl font-semibold text-gray-400 mb-2">Aucun appareil enregistré</h3> |
| <p class="text-gray-500 mb-6">Commencez par ajouter votre premier appareil</p> |
| <button onclick="openAddDeviceModal()" class="btn-primary"> |
| Ajouter un appareil |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="add-device-modal" class="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4"> |
| <div class="bg-gray-800 rounded-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto border border-gray-700"> |
| <div class="p-6 border-b border-gray-700 flex justify-between items-center"> |
| <h2 class="text-xl font-semibold">Ajouter un nouvel appareil</h2> |
| <button onclick="closeAddDeviceModal()" class="text-gray-400 hover:text-white"> |
| <i data-feather="x" class="w-6 h-6"></i> |
| </button> |
| </div> |
| |
| <div class="p-6"> |
| |
| <div class="mb-6"> |
| <label class="block text-sm font-medium mb-3">Type d'appareil *</label> |
| <div class="grid grid-cols-2 md:grid-cols-4 gap-3"> |
| <button onclick="selectDeviceType('radio')" class="device-type-btn p-4 border border-gray-600 rounded-lg hover:border-blue-500 transition-all text-center" id="btn-type-radio"> |
| <i data-feather="radio" class="w-8 h-8 mx-auto mb-2 text-green-400"></i> |
| <div class="text-sm font-medium">Talkie FM</div> |
| </button> |
| <button onclick="selectDeviceType('android')" class="device-type-btn p-4 border border-gray-600 rounded-lg hover:border-blue-500 transition-all text-center" id="btn-type-android"> |
| <i data-feather="smartphone" class="w-8 h-8 mx-auto mb-2 text-blue-400"></i> |
| <div class="text-sm font-medium">Smartphone</div> |
| </button> |
| <button onclick="selectDeviceType('tablet')" class="device-type-btn p-4 border border-gray-600 rounded-lg hover:border-blue-500 transition-all text-center" id="btn-type-tablet"> |
| <i data-feather="tablet" class="w-8 h-8 mx-auto mb-2 text-purple-400"></i> |
| <div class="text-sm font-medium">Tablette</div> |
| </button> |
| <button onclick="selectDeviceType('desktop')" class="device-type-btn p-4 border border-gray-600 rounded-lg hover:border-blue-500 transition-all text-center" id="btn-type-desktop"> |
| <i data-feather="monitor" class="w-8 h-8 mx-auto mb-2 text-yellow-400"></i> |
| <div class="text-sm font-medium">PC/Mac</div> |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="radio-fields" class="hidden space-y-4"> |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| <div> |
| <label class="block text-sm font-medium mb-2">Marque *</label> |
| <select id="radio-brand" class="w-full bg-gray-700 rounded-lg px-4 py-3 border border-gray-600"> |
| <option value="">Sélectionner...</option> |
| <option value="Motorola">Motorola</option> |
| <option value="Kenwood">Kenwood</option> |
| <option value="Hytera">Hytera</option> |
| <option value="Icom">Icom</option> |
| <option value="Midland">Midland</option> |
| <option value="Retevis">Retevis</option> |
| <option value="Baofeng">Baofeng</option> |
| <option value="TYT">TYT</option> |
| <option value="Autre">Autre</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-sm font-medium mb-2">Modèle *</label> |
| <input type="text" id="radio-model" class="w-full bg-gray-700 rounded-lg px-4 py-3 border border-gray-600" placeholder="Ex: DP4400e"> |
| </div> |
| <div> |
| <label class="block text-sm font-medium mb-2">Numéro de série / IMEI</label> |
| <input type="text" id="radio-serial" class="w-full bg-gray-700 rounded-lg px-4 py-3 border border-gray-600" placeholder="Numéro de série"> |
| </div> |
| <div> |
| <label class="block text-sm font-medium mb-2">Fréquence par défaut (MHz)</label> |
| <input type="text" id="radio-freq" class="w-full bg-gray-700 rounded-lg px-4 py-3 border border-gray-600" placeholder="462.550"> |
| </div> |
| </div> |
| <div> |
| <label class="block text-sm font-medium mb-2">Bluetooth MAC Address</label> |
| <input type="text" id="radio-mac" class="w-full bg-gray-700 rounded-lg px-4 py-3 border border-gray-600" placeholder="AA:BB:CC:DD:EE:FF"> |
| </div> |
| </div> |
|
|
| |
| <div id="android-fields" class="hidden space-y-4"> |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| <div> |
| <label class="block text-sm font-medium mb-2">Marque</label> |
| <select id="android-brand" class="w-full bg-gray-700 rounded-lg px-4 py-3 border border-gray-600"> |
| <option value="">Sélectionner...</option> |
| <option value="Samsung">Samsung</option> |
| <option value="Google">Google Pixel</option> |
| <option value="Xiaomi">Xiaomi</option> |
| <option value="Huawei">Huawei</option> |
| <option value="Sony">Sony</option> |
| <option value="CAT">CAT (Caterpillar)</option> |
| <option value="Crosscall">Crosscall</option> |
| <option value="Autre">Autre</option> |
| </select> |
| </div> |
| <div> |
| <label class="block text-sm font-medium mb-2">Modèle</label> |
| <input type="text" id="android-model" class="w-full bg-gray-700 rounded-lg px-4 py-3 border border-gray-600" placeholder="Ex: Galaxy XCover Pro"> |
| </div> |
| </div> |
| <div> |
| <label class="block text-sm font-medium mb-2">Numéro IMEI *</label> |
| <input type="text" id="android-imei" class="w-full bg-gray-700 rounded-lg px-4 py-3 border border-gray-600" placeholder="Tapez *#06# sur l'appareil"> |
| </div> |
| <div class="bg-blue-900/20 border border-blue-700/50 rounded-lg p-4"> |
| <div class="flex items-start space-x-3"> |
| <i data-feather="info" class="w-5 h-5 text-blue-400 mt-0.5"></i> |
| <div class="text-sm"> |
| <p class="text-blue-400 font-medium mb-1">Installation requise</p> |
| <p class="text-gray-300">L'appareil doit avoir l'application ComSync Pro installée. <a href="#" class="text-blue-400 hover:underline">Télécharger l'APK</a></p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="space-y-4 mt-4"> |
| <div> |
| <label class="block text-sm font-medium mb-2">Nom de l'appareil (pour l'identifier)</label> |
| <input type="text" id="device-name" class="w-full bg-gray-700 rounded-lg px-4 py-3 border border-gray-600" placeholder="Ex: Radio Sécurité Nord"> |
| </div> |
| <div> |
| <label class="block text-sm font-medium mb-2">Assigné à (optionnel)</label> |
| <select id="device-assignee" class="w-full bg-gray-700 rounded-lg px-4 py-3 border border-gray-600"> |
| <option value="">Non assigné</option> |
| </select> |
| </div> |
| </div> |
|
|
| |
| <div class="mt-6 border-t border-gray-700 pt-6"> |
| <h3 class="text-sm font-medium mb-4">Configuration rapide par QR Code</h3> |
| <div class="flex items-center space-x-4"> |
| <div class="bg-white p-4 rounded-lg"> |
| |
| <div class="w-32 h-32 bg-gray-900 rounded relative overflow-hidden"> |
| <div class="absolute inset-0 qr-scanner"></div> |
| <div class="absolute inset-4 border-2 border-white rounded flex items-center justify-center"> |
| <i data-feather="smartphone" class="w-8 h-8 text-white"></i> |
| </div> |
| </div> |
| </div> |
| <div class="flex-1"> |
| <p class="text-sm text-gray-400 mb-2">Scannez ce QR code avec l'appareil pour l'enregistrer automatiquement.</p> |
| <button onclick="generateNewQR()" class="text-blue-400 text-sm hover:underline flex items-center"> |
| <i data-feather="refresh-cw" class="w-4 h-4 mr-1"></i> |
| Générer un nouveau code |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="p-6 border-t border-gray-700 flex justify-end space-x-3"> |
| <button onclick="closeAddDeviceModal()" class="btn-secondary">Annuler</button> |
| <button onclick="saveDevice()" class="btn-primary">Enregistrer l'appareil</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="device-details-modal" class="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4"> |
| <div class="bg-gray-800 rounded-xl max-w-lg w-full border border-gray-700" id="device-details-content"> |
| |
| </div> |
| </div> |
|
|
| <script> |
| |
| let vantaInstance = null; |
| try { |
| if (typeof VANTA !== 'undefined' && document.getElementById('vanta-bg')) { |
| vantaInstance = VANTA.NET({ |
| el: "#vanta-bg", |
| mouseControls: true, |
| touchControls: true, |
| gyroControls: false, |
| minHeight: 200.00, |
| minWidth: 200.00, |
| scale: 1.00, |
| scaleMobile: 1.00, |
| color: 0x3b82f6, |
| backgroundColor: 0x111827, |
| points: 10.00, |
| maxDistance: 22.00, |
| spacing: 18.00 |
| }); |
| } |
| } catch (e) { |
| console.warn('Vanta.js non disponible:', e); |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', function() { |
| setTimeout(function() { |
| if (typeof feather !== 'undefined') { |
| feather.replace(); |
| } |
| }, 100); |
| }); |
| |
| let currentTab = 'all'; |
| let selectedDeviceType = null; |
| let companyData = null; |
| |
| |
| function loadCompanyData() { |
| try { |
| const data = localStorage.getItem('comsync_current_company'); |
| const companyInfo = document.getElementById('company-info'); |
| const companyIdDisplay = document.getElementById('company-id-display'); |
| |
| if (data && companyInfo && companyIdDisplay) { |
| companyData = JSON.parse(data); |
| companyInfo.innerHTML = `Société : <span class="text-white font-semibold">${companyData.name || 'Sans nom'}</span>`; |
| companyIdDisplay.textContent = companyData.id || 'N/A'; |
| } else { |
| |
| if (companyInfo) { |
| companyInfo.innerHTML = `Société : <span class="text-white font-semibold">Démo Mode</span>`; |
| } |
| if (companyIdDisplay) { |
| companyIdDisplay.textContent = 'DEMO-001'; |
| } |
| } |
| loadDevices(); |
| } catch (e) { |
| console.error('Erreur chargement données société:', e); |
| |
| const companyInfo = document.getElementById('company-info'); |
| const companyIdDisplay = document.getElementById('company-id-display'); |
| if (companyInfo) companyInfo.innerHTML = `Société : <span class="text-white font-semibold">Erreur chargement</span>`; |
| if (companyIdDisplay) companyIdDisplay.textContent = 'ERROR'; |
| loadDevices(); |
| } |
| } |
| |
| function copyCompanyId() { |
| const id = document.getElementById('company-id-display').textContent; |
| navigator.clipboard.writeText(id).then(() => { |
| alert('ID copié dans le presse-papier !'); |
| }); |
| } |
| |
| function switchTab(tab) { |
| currentTab = tab; |
| |
| |
| document.querySelectorAll('.device-type-tab').forEach(t => t.classList.remove('active')); |
| document.getElementById(`tab-${tab}`).classList.add('active'); |
| |
| loadDevices(); |
| } |
| |
| function loadDevices() { |
| try { |
| const devices = JSON.parse(localStorage.getItem('comsync_devices') || '[]'); |
| const container = document.getElementById('devices-container'); |
| const emptyState = document.getElementById('empty-state'); |
| |
| if (!container || !emptyState) return; |
| |
| |
| let filtered = devices || []; |
| if (currentTab !== 'all' && currentTab !== 'pending') { |
| const typeFilter = currentTab === 'radios' ? 'radio' : |
| currentTab === 'tablets' ? 'tablet' : 'android'; |
| filtered = devices.filter(d => d && d.type === typeFilter); |
| } else if (currentTab === 'pending') { |
| filtered = devices.filter(d => d && d.status === 'pending'); |
| } |
| |
| |
| const countRadios = document.getElementById('count-radios'); |
| const countAndroid = document.getElementById('count-android'); |
| const countTablets = document.getElementById('count-tablets'); |
| const countOnline = document.getElementById('count-online'); |
| |
| if (countRadios) countRadios.textContent = devices.filter(d => d && d.type === 'radio').length; |
| if (countAndroid) countAndroid.textContent = devices.filter(d => d && (d.type === 'android' || d.type === 'tablet')).length; |
| if (countTablets) countTablets.textContent = devices.filter(d => d && d.type === 'tablet').length; |
| if (countOnline) countOnline.textContent = devices.filter(d => d && d.status === 'online').length; |
| |
| if (filtered.length === 0) { |
| container.innerHTML = ''; |
| emptyState.classList.remove('hidden'); |
| return; |
| } |
| |
| emptyState.classList.add('hidden'); |
| container.innerHTML = filtered.map(device => createDeviceCard(device)).join(''); |
| |
| |
| setTimeout(() => { |
| if (typeof feather !== 'undefined') { |
| feather.replace(); |
| } |
| }, 50); |
| |
| } catch (e) { |
| console.error('Erreur chargement appareils:', e); |
| const container = document.getElementById('devices-container'); |
| if (container) { |
| container.innerHTML = '<div class="col-span-full text-center text-red-400">Erreur lors du chargement des appareils</div>'; |
| } |
| } |
| } |
| |
| function createDeviceCard(device) { |
| if (!device || !device.id) return ''; |
| |
| const icons = { |
| radio: 'radio', |
| android: 'smartphone', |
| tablet: 'tablet', |
| desktop: 'monitor' |
| }; |
| |
| const colors = { |
| radio: 'green', |
| android: 'blue', |
| tablet: 'purple', |
| desktop: 'yellow' |
| }; |
| |
| const deviceType = device.type || 'unknown'; |
| const deviceStatus = device.status || 'offline'; |
| const deviceBrand = (device.brand && String(device.brand)) || 'Inconnu'; |
| const deviceModel = (device.model && String(device.model)) || ''; |
| const deviceName = (device.name && String(device.name)) || 'Appareil sans nom'; |
| |
| const statusClass = deviceStatus === 'online' ? 'status-online' : |
| deviceStatus === 'pending' ? 'status-pending' : 'status-offline'; |
| const statusText = deviceStatus === 'online' ? 'En ligne' : |
| deviceStatus === 'pending' ? 'En attente' : 'Hors ligne'; |
| |
| const brandColors = { |
| 'Motorola': 'bg-blue-600', |
| 'Kenwood': 'bg-red-600', |
| 'Hytera': 'bg-orange-600', |
| 'Icom': 'bg-green-600', |
| 'Midland': 'bg-purple-600', |
| 'Retevis': 'bg-yellow-600', |
| 'Baofeng': 'bg-gray-600', |
| 'TYT': 'bg-indigo-600', |
| 'Samsung': 'bg-blue-500', |
| 'Google': 'bg-gray-600', |
| 'Xiaomi': 'bg-orange-500', |
| 'Huawei': 'bg-red-500', |
| 'Sony': 'bg-black', |
| 'CAT': 'bg-yellow-600', |
| 'Crosscall': 'bg-green-500' |
| }; |
| |
| const brandColor = brandColors[deviceBrand] || 'bg-gray-600'; |
| const iconName = icons[deviceType] || 'help-circle'; |
| const colorName = colors[deviceType] || 'gray'; |
| |
| const shortId = device.id ? String(device.id).slice(-8) : 'UNKNOWN'; |
| |
| return ` |
| <div class="bg-gray-800/80 backdrop-blur-sm rounded-xl p-5 border border-gray-700/50 hover:border-blue-500/50 transition-all cursor-pointer" onclick="showDeviceDetails('${device.id}')"> |
| <div class="flex items-start justify-between mb-4"> |
| <div class="flex items-center space-x-3"> |
| <div class="brand-logo ${brandColor} text-white flex-shrink-0"> |
| ${deviceBrand.substring(0, 2).toUpperCase()} |
| </div> |
| <div class="min-w-0 flex-1"> |
| <h3 class="font-semibold truncate">${deviceName}</h3> |
| <p class="text-xs text-gray-400 truncate">${deviceBrand} ${deviceModel}</p> |
| </div> |
| </div> |
| <span class="status-badge ${statusClass} flex-shrink-0">${statusText}</span> |
| </div> |
| |
| <div class="grid grid-cols-2 gap-3 mb-4 text-sm"> |
| <div class="bg-gray-700/50 rounded p-2 min-w-0"> |
| <p class="text-gray-400 text-xs">ID</p> |
| <p class="font-mono text-xs truncate">${shortId}</p> |
| </div> |
| <div class="bg-gray-700/50 rounded p-2 min-w-0"> |
| <p class="text-gray-400 text-xs">Dernier sync</p> |
| <p class="text-xs truncate">${device.lastSync || 'Jamais'}</p> |
| </div> |
| </div> |
| |
| <div class="flex items-center justify-between pt-3 border-t border-gray-700"> |
| <div class="flex items-center space-x-2"> |
| <i data-feather="${iconName}" class="w-4 h-4 text-${colorName}-400"></i> |
| <span class="text-xs text-gray-400 capitalize">${deviceType}</span> |
| </div> |
| <div class="flex space-x-2"> |
| <button onclick="event.stopPropagation(); toggleDeviceStatus('${device.id}')" class="p-2 hover:bg-gray-700 rounded transition-colors" title="Changer statut"> |
| <i data-feather="power" class="w-4 h-4"></i> |
| </button> |
| <button onclick="event.stopPropagation(); deleteDevice('${device.id}')" class="p-2 hover:bg-gray-700 rounded text-red-400 transition-colors" title="Supprimer"> |
| <i data-feather="trash-2" class="w-4 h-4"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
| `; |
| } |
| |
| function openAddDeviceModal() { |
| document.getElementById('add-device-modal').classList.remove('hidden'); |
| selectDeviceType('radio'); |
| } |
| |
| function closeAddDeviceModal() { |
| document.getElementById('add-device-modal').classList.add('hidden'); |
| |
| document.querySelectorAll('.device-type-btn').forEach(b => b.classList.remove('border-blue-500', 'bg-blue-900/20')); |
| selectedDeviceType = null; |
| } |
| |
| function selectDeviceType(type) { |
| selectedDeviceType = type; |
| |
| |
| document.querySelectorAll('.device-type-btn').forEach(b => { |
| b.classList.remove('border-blue-500', 'bg-blue-900/20'); |
| }); |
| document.getElementById(`btn-type-${type}`).classList.add('border-blue-500', 'bg-blue-900/20'); |
| |
| |
| document.getElementById('radio-fields').classList.add('hidden'); |
| document.getElementById('android-fields').classList.add('hidden'); |
| |
| if (type === 'radio') { |
| document.getElementById('radio-fields').classList.remove('hidden'); |
| } else if (type === 'android' || type === 'tablet') { |
| document.getElementById('android-fields').classList.remove('hidden'); |
| } |
| } |
| |
| function saveDevice() { |
| if (!selectedDeviceType) { |
| alert('Veuillez sélectionner un type d\'appareil'); |
| return; |
| } |
| |
| try { |
| const deviceId = 'DEV-' + Date.now().toString(36).toUpperCase(); |
| |
| |
| const getInputValue = (id) => { |
| const el = document.getElementById(id); |
| return el ? el.value.trim() : ''; |
| }; |
| |
| let deviceData = { |
| id: deviceId, |
| type: selectedDeviceType, |
| name: getInputValue('device-name'), |
| assignee: getInputValue('device-assignee'), |
| status: 'pending', |
| lastSync: null, |
| companyId: (companyData && companyData.id) ? companyData.id : 'DEMO-001', |
| createdAt: new Date().toISOString() |
| }; |
| |
| |
| if (selectedDeviceType === 'radio') { |
| deviceData.brand = getInputValue('radio-brand'); |
| deviceData.model = getInputValue('radio-model'); |
| deviceData.serial = getInputValue('radio-serial'); |
| deviceData.frequency = getInputValue('radio-freq'); |
| deviceData.macAddress = getInputValue('radio-mac'); |
| |
| if (!deviceData.brand || !deviceData.model) { |
| alert('Veuillez remplir la marque et le modèle'); |
| return; |
| } |
| } else { |
| deviceData.brand = getInputValue('android-brand'); |
| deviceData.model = getInputValue('android-model'); |
| deviceData.imei = getInputValue('android-imei'); |
| |
| if (!deviceData.imei) { |
| alert('L\'IMEI est requis pour les appareils Android'); |
| return; |
| } |
| } |
| |
| |
| let devices = []; |
| try { |
| const existing = localStorage.getItem('comsync_devices'); |
| if (existing) { |
| devices = JSON.parse(existing); |
| } |
| } catch (parseError) { |
| console.warn('Erreur parsing devices:', parseError); |
| devices = []; |
| } |
| |
| devices.push(deviceData); |
| localStorage.setItem('comsync_devices', JSON.stringify(devices)); |
| |
| closeAddDeviceModal(); |
| loadDevices(); |
| |
| alert(`Appareil enregistré avec succès !\n\nID: ${deviceId}\nNom: ${deviceData.name || 'Non nommé'}\n\nL'appareil doit être activé pour apparaître en ligne.`); |
| |
| } catch (error) { |
| console.error('Erreur sauvegarde appareil:', error); |
| alert('Erreur lors de l\'enregistrement. Veuillez réessayer.'); |
| } |
| } |
| |
| function deleteDevice(deviceId) { |
| if (!confirm('Êtes-vous sûr de vouloir supprimer cet appareil ?')) return; |
| |
| let devices = JSON.parse(localStorage.getItem('comsync_devices') || '[]'); |
| devices = devices.filter(d => d.id !== deviceId); |
| localStorage.setItem('comsync_devices', JSON.stringify(devices)); |
| |
| loadDevices(); |
| } |
| |
| function toggleDeviceStatus(deviceId) { |
| let devices = JSON.parse(localStorage.getItem('comsync_devices') || '[]'); |
| const device = devices.find(d => d.id === deviceId); |
| if (device) { |
| device.status = device.status === 'online' ? 'offline' : 'online'; |
| if (device.status === 'online') { |
| device.lastSync = new Date().toLocaleString(); |
| } |
| localStorage.setItem('comsync_devices', JSON.stringify(devices)); |
| loadDevices(); |
| } |
| } |
| |
| function showDeviceDetails(deviceId) { |
| const devices = JSON.parse(localStorage.getItem('comsync_devices') || '[]'); |
| const device = devices.find(d => d.id === deviceId); |
| if (!device) return; |
| |
| const content = ` |
| <div class="p-6 border-b border-gray-700 flex justify-between items-center"> |
| <h2 class="text-xl font-semibold">Détails de l'appareil</h2> |
| <button onclick="closeDeviceDetails()" class="text-gray-400 hover:text-white"> |
| <i data-feather="x" class="w-6 h-6"></i> |
| </button> |
| </div> |
| <div class="p-6"> |
| <div class="flex items-center space-x-4 mb-6"> |
| <div class="w-16 h-16 bg-blue-600 rounded-xl flex items-center justify-center text-2xl font-bold"> |
| ${device.brand ? device.brand.substring(0, 2).toUpperCase() : 'NA'} |
| </div> |
| <div> |
| <h3 class="text-lg font-semibold">${device.name || 'Appareil sans nom'}</h3> |
| <p class="text-gray-400">${device.brand} ${device.model}</p> |
| <span class="status-badge ${device.status === 'online' ? 'status-online' : 'status-offline'} mt-1"> |
| ${device.status === 'online' ? 'En ligne' : 'Hors ligne'} |
| </span> |
| </div> |
| </div> |
| |
| <div class="space-y-3 mb-6"> |
| <div class="flex justify-between py-2 border-b border-gray-700"> |
| <span class="text-gray-400">ID Appareil</span> |
| <span class="font-mono text-sm">${device.id}</span> |
| </div> |
| <div class="flex justify-between py-2 border-b border-gray-700"> |
| <span class="text-gray-400">Type</span> |
| <span class="capitalize">${device.type}</span> |
| </div> |
| ${device.imei ? ` |
| <div class="flex justify-between py-2 border-b border-gray-700"> |
| <span class="text-gray-400">IMEI</span> |
| <span class="font-mono text-sm">${device.imei}</span> |
| </div>` : ''} |
| ${device.frequency ? ` |
| <div class="flex justify-between py-2 border-b border-gray-700"> |
| <span class="text-gray-400">Fréquence</span> |
| <span>${device.frequency} MHz</span> |
| </div>` : ''} |
| ${device.macAddress ? ` |
| <div class="flex justify-between py-2 border-b border-gray-700"> |
| <span class="text-gray-400">Adresse MAC</span> |
| <span class="font-mono text-sm">${device.macAddress}</span> |
| </div>` : ''} |
| <div class="flex justify-between py-2 border-b border-gray-700"> |
| <span class="text-gray-400">Dernière synchronisation</span> |
| <span>${device.lastSync || 'Jamais'}</span> |
| </div> |
| <div class="flex justify-between py-2 border-b border-gray-700"> |
| <span class="text-gray-400">Ajouté le</span> |
| <span>${new Date(device.createdAt).toLocaleDateString()}</span> |
| </div> |
| </div> |
| |
| <div class="flex space-x-3"> |
| <button onclick="closeDeviceDetails()" class="flex-1 btn-secondary">Fermer</button> |
| <button onclick="closeDeviceDetails(); deleteDevice('${device.id}')" class="flex-1 bg-red-600 hover:bg-red-700 text-white py-2 rounded-lg"> |
| Supprimer |
| </button> |
| </div> |
| </div> |
| `; |
| |
| document.getElementById('device-details-content').innerHTML = content; |
| document.getElementById('device-details-modal').classList.remove('hidden'); |
| feather.replace(); |
| } |
| |
| function closeDeviceDetails() { |
| document.getElementById('device-details-modal').classList.add('hidden'); |
| } |
| |
| function generateNewQR() { |
| alert('Nouveau QR Code généré pour l\'appareil.'); |
| } |
| |
| |
| setInterval(() => { |
| try { |
| const devices = JSON.parse(localStorage.getItem('comsync_devices') || '[]'); |
| if (!Array.isArray(devices)) return; |
| |
| let updated = false; |
| |
| devices.forEach(device => { |
| if (device && device.status === 'online' && Math.random() > 0.95) { |
| device.lastSync = new Date().toLocaleString(); |
| updated = true; |
| } |
| }); |
| |
| if (updated) { |
| localStorage.setItem('comsync_devices', JSON.stringify(devices)); |
| loadDevices(); |
| } |
| } catch (e) { |
| console.warn('Erreur mise à jour temps réel:', e); |
| } |
| }, 5000); |
| |
| |
| loadCompanyData(); |
| |
| |
| window.addEventListener('popstate', function(event) { |
| location.reload(); |
| }); |
| history.pushState(null, null, location.href); |
| </script> |
| </body> |
| </html> |