Spaces:
Configuration error
Configuration error
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Carte des Points de Contrôle - Contrôle de Patrouille</title> | |
| <link rel="stylesheet" href="style.css"> | |
| <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://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> | |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <!-- Sidebar --> | |
| <custom-sidebar></custom-sidebar> | |
| <!-- Header --> | |
| <custom-header></custom-header> | |
| <!-- Main Content --> | |
| <main class="ml-64 mt-16 p-6"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <div> | |
| <h1 class="text-2xl font-bold text-gray-800">Carte des Points de Contrôle</h1> | |
| <p class="text-gray-600">Visualisation géographique de tous les points de contrôle</p> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg flex items-center"> | |
| <i data-feather="filter" class="mr-2 w-4 h-4"></i> | |
| Filtres | |
| </button> | |
| <button class="bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded-lg flex items-center" id="locateBtn"> | |
| <i data-feather="crosshair" class="mr-2 w-4 h-4"></i> | |
| Ma position | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Map Container --> | |
| <div class="bg-white rounded-xl shadow-sm overflow-hidden"> | |
| <div class="p-4 border-b border-gray-200"> | |
| <div class="flex flex-wrap gap-4"> | |
| <div class="flex items-center"> | |
| <div class="w-4 h-4 bg-green-500 rounded-full mr-2"></div> | |
| <span class="text-sm text-gray-700">Actif (${activePoints})</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-4 h-4 bg-gray-500 rounded-full mr-2"></div> | |
| <span class="text-sm text-gray-700">Inactif (${inactivePoints})</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-4 h-4 bg-yellow-500 rounded-full mr-2"></div> | |
| <span class="text-sm text-gray-700">Maintenance (${maintenancePoints})</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-4 h-4 bg-blue-500 rounded-full mr-2"></div> | |
| <span class="text-sm text-gray-700">NFC</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-4 h-4 bg-purple-500 rounded-full mr-2"></div> | |
| <span class="text-sm text-gray-700">QR Code</span> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-4 h-4 bg-gray-400 rounded-full mr-2"></div> | |
| <span class="text-sm text-gray-700">Manuel</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="map" style="height: calc(100vh - 200px);"></div> | |
| </div> | |
| <!-- Point Details Sidebar (Hidden by default) --> | |
| <div id="pointSidebar" class="fixed right-0 top-16 bottom-0 w-80 bg-white shadow-lg transform translate-x-full transition-transform duration-300 z-40"> | |
| <div class="p-6 border-b border-gray-200"> | |
| <div class="flex justify-between items-center"> | |
| <h3 class="text-lg font-semibold text-gray-800" id="sidebarTitle">Détails du Point</h3> | |
| <button id="closeSidebar" class="text-gray-400 hover:text-gray-600"> | |
| <i data-feather="x" class="w-5 h-5"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="p-6" id="sidebarContent"> | |
| <!-- Content will be populated dynamically --> | |
| </div> | |
| </div> | |
| </main> | |
| <script src="components/sidebar.js"></script> | |
| <script src="components/header.js"></script> | |
| <script src="script.js"></script> | |
| <script> | |
| feather.replace(); | |
| // Control points data | |
| const controlPoints = [ | |
| { | |
| id: 'NFC-001', | |
| name: 'Entrée Principale', | |
| location: 'Bâtiment A - Rez-de-chaussée', | |
| type: 'NFC', | |
| status: 'active', | |
| latitude: 48.8566, | |
| longitude: 2.3522, | |
| icon: 'green' | |
| }, | |
| { | |
| id: 'QR-002', | |
| name: 'Salle des Serveurs', | |
| location: 'Bâtiment B - 3ème étage', | |
| type: 'QR', | |
| status: 'maintenance', | |
| latitude: 48.8584, | |
| longitude: 2.3545, | |
| icon: 'yellow' | |
| }, | |
| { | |
| id: 'MAN-003', | |
| name: 'Parking', | |
| location: 'Extérieur - Côté Sud', | |
| type: 'Manual', | |
| status: 'active', | |
| latitude: 48.8575, | |
| longitude: 2.3518, | |
| icon: 'green' | |
| }, | |
| { | |
| id: 'NFC-004', | |
| name: 'Porte Arrière', | |
| location: 'Bâtiment A - Arrière', | |
| type: 'NFC', | |
| status: 'inactive', | |
| latitude: 48.8569, | |
| longitude: 2.3515, | |
| icon: 'gray' | |
| }, | |
| { | |
| id: 'QR-005', | |
| name: 'Réception', | |
| location: 'Hall d\'accueil - Bâtiment A', | |
| type: 'QR', | |
| status: 'active', | |
| latitude: 48.8563, | |
| longitude: 2.3528, | |
| icon: 'green' | |
| }, | |
| { | |
| id: 'MAN-006', | |
| name: 'Sortie d\'Urgence', | |
| location: 'Bâtiment C - Aile Est', | |
| type: 'Manual', | |
| status: 'active', | |
| latitude: 48.8590, | |
| longitude: 2.3550, | |
| icon: 'green' | |
| }, | |
| { | |
| id: 'NFC-007', | |
| name: 'Entrepôt', | |
| location: 'Zone de stockage - Aile Nord', | |
| type: 'NFC', | |
| status: 'active', | |
| latitude: 48.8578, | |
| longitude: 2.3505, | |
| icon: 'green' | |
| }, | |
| { | |
| id: 'QR-008', | |
| name: 'Salle de Conférence', | |
| location: 'Bâtiment B - 2ème étage', | |
| type: 'QR', | |
| status: 'maintenance', | |
| latitude: 48.8581, | |
| longitude: 2.3540, | |
| icon: 'yellow' | |
| }, | |
| { | |
| id: 'MAN-009', | |
| name: 'Quai de Chargement', | |
| location: 'Entrée de Service', | |
| type: 'Manual', | |
| status: 'active', | |
| latitude: 48.8572, | |
| longitude: 2.3498, | |
| icon: 'green' | |
| } | |
| ]; | |
| // Count points by status | |
| const activePoints = controlPoints.filter(p => p.status === 'active').length; | |
| const inactivePoints = controlPoints.filter(p => p.status === 'inactive').length; | |
| const maintenancePoints = controlPoints.filter(p => p.status === 'maintenance').length; | |
| // Initialize map | |
| let map; | |
| let markers = []; | |
| function initMap() { | |
| map = L.map('map').setView([48.8566, 2.3522], 15); | |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
| attribution: '© OpenStreetMap contributors' | |
| }).addTo(map); | |
| // Add markers for each control point | |
| controlPoints.forEach(point => { | |
| const marker = createMarker(point); | |
| markers.push(marker); | |
| }); | |
| } | |
| function createMarker(point) { | |
| const iconColor = getIconColor(point.status, point.type); | |
| const customIcon = L.divIcon({ | |
| className: 'custom-marker', | |
| html: `<div style="background-color: ${iconColor}; width: 20px; height: 20px; border-radius: 50%; border: 2px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.3);"></div>`, | |
| iconSize: [20, 20], | |
| iconAnchor: [10, 10] | |
| }); | |
| const marker = L.marker([point.latitude, point.longitude], { icon: customIcon }) | |
| .addTo(map) | |
| .bindPopup(createPopupContent(point)); | |
| marker.on('click', () => showPointDetails(point)); | |
| return marker; | |
| } | |
| function getIconColor(status, type) { | |
| if (status === 'active') { | |
| return type === 'NFC' ? '#059669' : type === 'QR' ? '#7c3aed' : '#6b7280'; | |
| } else if (status === 'inactive') { | |
| return '#6b7280'; | |
| } else { | |
| return '#d97706'; | |
| } | |
| } | |
| function createPopupContent(point) { | |
| const statusColor = point.status === 'active' ? 'green' : point.status === 'inactive' ? 'gray' : 'yellow'; | |
| const typeColor = point.type === 'NFC' ? 'blue' : point.type === 'QR' ? 'purple' : 'gray'; | |
| return ` | |
| <div class="p-2"> | |
| <h4 class="font-semibold text-sm">${point.name}</h4> | |
| <p class="text-xs text-gray-600 mb-2">${point.location}</p> | |
| <div class="flex gap-2"> | |
| <span class="px-2 py-1 bg-${statusColor}-100 text-${statusColor}-800 text-xs rounded">${point.status}</span> | |
| <span class="px-2 py-1 bg-${typeColor}-100 text-${typeColor}-800 text-xs rounded">${point.type}</span> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| function showPointDetails(point) { | |
| const sidebar = document.getElementById('pointSidebar'); | |
| const title = document.getElementById('sidebarTitle'); | |
| const content = document.getElementById('sidebarContent'); | |
| title.textContent = point.name; | |
| content.innerHTML = ` | |
| <div class="space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700">Localisation</label> | |
| <p class="text-gray-900">${point.location}</p> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700">Type</label> | |
| <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"> | |
| ${point.type} | |
| </span> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700">Statut</label> | |
| <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${point.status === 'active' ? 'bg-green-100 text-green-800' : point.status === 'inactive' ? 'bg-gray-100 text-gray-800' : 'bg-yellow-100 text-yellow-800'}"> | |
| ${point.status === 'active' ? 'Actif' : point.status === 'inactive' ? 'Inactif' : 'Maintenance'} | |
| </span> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700">ID</label> | |
| <p class="text-gray-900">${point.id}</p> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700">Coordonnées</label> | |
| <p class="text-gray-900">${point.latitude}, ${point.longitude}</p> | |
| </div> | |
| <div class="pt-4 space-y-2"> | |
| <button class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg flex items-center justify-center"> | |
| <i data-feather="edit-2" class="mr-2 w-4 h-4"></i> | |
| Modifier | |
| </button> | |
| <button class="w-full bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded-lg flex items-center justify-center"> | |
| <i data-feather="navigation" class="mr-2 w-4 h-4"></i> | |
| Itinéraire | |
| </button> | |
| ${point.type === 'NFC' ? ` | |
| <button class="w-full bg-purple-600 hover:bg-purple-700 text-white font-medium py-2 px-4 rounded-lg flex items-center justify-center"> | |
| <i data-feather="credit-card" class="mr-2 w-4 h-4"></i> | |
| Scanner NFC | |
| </button> | |
| ` : ''} | |
| </div> | |
| </div> | |
| `; | |
| sidebar.classList.remove('translate-x-full'); | |
| feather.replace(); | |
| } | |
| // Close sidebar | |
| document.getElementById('closeSidebar').addEventListener('click', () => { | |
| document.getElementById('pointSidebar').classList.add('translate-x-full'); | |
| }); | |
| // Locate user | |
| document.getElementById('locateBtn').addEventListener('click', () => { | |
| if (navigator.geolocation) { | |
| navigator.geolocation.getCurrentPosition((position) => { | |
| const lat = position.coords.latitude; | |
| const lng = position.coords.longitude; | |
| map.setView([lat, lng], 16); | |
| // Add user location marker | |
| const userIcon = L.divIcon({ | |
| className: 'user-marker', | |
| html: '<div style="background-color: #3b82f6; width: 12px; height: 12px; border-radius: 50%; border: 3px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.3);"></div>', | |
| iconSize: [12, 12], | |
| iconAnchor: [6, 6] | |
| }); | |
| L.marker([lat, lng], { icon: userIcon }) | |
| .addTo(map) | |
| .bindPopup('Votre position') | |
| .openPopup(); | |
| }); | |
| } else { | |
| alert('La géolocalisation n\'est pas supportée par ce navigateur.'); | |
| } | |
| }); | |
| // Initialize map when page loads | |
| document.addEventListener('DOMContentLoaded', initMap); | |
| </script> | |
| </body> | |
| </html> |