519 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			519 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // Configuration partagée des types d'événements avec leurs emojis et descriptions
 | |
| window.EVENT_TYPES = {
 | |
|     // Community / OSM
 | |
|     'community.osm.event': {
 | |
|         emoji: '🗺️',
 | |
|         label: 'Événement OpenStreetMap',
 | |
|         category: 'Communauté',
 | |
|         description: 'Événement lié à la communauté OpenStreetMap'
 | |
|     },
 | |
| 
 | |
|     // Culture / Arts
 | |
|     'culture.arts': {
 | |
|         emoji: '🎨',
 | |
|         label: 'Arts et culture',
 | |
|         category: 'Culture',
 | |
|         description: 'Événement artistique et culturel'
 | |
|     },
 | |
|     'culture.geek': {
 | |
|         emoji: '🤓',
 | |
|         label: 'Culture geek',
 | |
|         category: 'Culture',
 | |
|         description: 'Événement geek, technologie, gaming'
 | |
|     },
 | |
|     'culture.music': {
 | |
|         emoji: '🎵',
 | |
|         label: 'Musique',
 | |
|         category: 'Culture',
 | |
|         description: 'Événement musical général'
 | |
|     },
 | |
| 
 | |
|     // Music specific
 | |
|     'music.festival': {
 | |
|         emoji: '🎪',
 | |
|         label: 'Festival de musique',
 | |
|         category: 'Musique',
 | |
|         description: 'Festival musical'
 | |
|     },
 | |
| 
 | |
|     // Power / Energy
 | |
|     'power.production.unavail': {
 | |
|         emoji: '⚡',
 | |
|         label: 'Production électrique indisponible',
 | |
|         category: 'Énergie',
 | |
|         description: 'Arrêt ou réduction de production électrique'
 | |
|     },
 | |
| 
 | |
|     // Sale / Commerce
 | |
|     'sale': {
 | |
|         emoji: '🛒',
 | |
|         label: 'Vente / Commerce',
 | |
|         category: 'Commerce',
 | |
|         description: 'Événement commercial, vente, marché'
 | |
|     },
 | |
| 
 | |
|     // Time / Temporal
 | |
|     'time.daylight.summer': {
 | |
|         emoji: '☀️',
 | |
|         label: 'Heure d\'été',
 | |
|         category: 'Temps',
 | |
|         description: 'Passage à l\'heure d\'été'
 | |
|     },
 | |
| 
 | |
|     // Tourism
 | |
|     'tourism.exhibition': {
 | |
|         emoji: '🖼️',
 | |
|         label: 'Exposition',
 | |
|         category: 'Tourisme',
 | |
|         description: 'Exposition, salon, foire'
 | |
|     },
 | |
| 
 | |
|     // Traffic / Transportation
 | |
|     'traffic.accident': {
 | |
|         emoji: '💥',
 | |
|         label: 'Accident',
 | |
|         category: 'Circulation',
 | |
|         description: 'Accident de la circulation'
 | |
|     },
 | |
|     'traffic.incident': {
 | |
|         emoji: '⚠️',
 | |
|         label: 'Incident de circulation',
 | |
|         category: 'Circulation',
 | |
|         description: 'Incident sur la route'
 | |
|     },
 | |
|     'traffic.obstacle': {
 | |
|         emoji: '🚧',
 | |
|         label: 'Obstacle',
 | |
|         category: 'Circulation',
 | |
|         description: 'Obstacle sur la voie'
 | |
|     },
 | |
|     'traffic.partially_closed': {
 | |
|         emoji: '🚦',
 | |
|         label: 'Voie partiellement fermée',
 | |
|         category: 'Circulation',
 | |
|         description: 'Fermeture partielle de voie'
 | |
|     },
 | |
|     'traffic.roadwork': {
 | |
|         emoji: '<img src="static/cone.png" class="icone cone" />',
 | |
|         label: 'Travaux routiers',
 | |
|         category: 'Circulation',
 | |
|         description: 'Travaux sur la chaussée'
 | |
|     },
 | |
|     'wildlife': {
 | |
|         emoji: '🦌',
 | |
|         label: 'Animal',
 | |
|         category: 'Vie sauvage',
 | |
|         description: 'Détection d\'animaux'
 | |
|     },
 | |
|     'traffic.mammoth': {
 | |
|         emoji: '🦣',
 | |
|         label: 'Mammouth laineux',
 | |
|         category: 'Obstacle',
 | |
|         description: 'Un mammouth laineux bloque la route'
 | |
|     },
 | |
|     'hazard.piranha': {
 | |
|         emoji: '🐟',
 | |
|         label: 'Piranha dans la piscine',
 | |
|         category: 'Danger',
 | |
|         description: 'Des pirana attaquent dans cette piscine'
 | |
|     }
 | |
| };
 | |
| 
 | |
| // Fonction pour obtenir les suggestions d'autocomplétion
 | |
| function getEventTypeSuggestions(input) {
 | |
|     const inputLower = input.toLowerCase();
 | |
|     const suggestions = [];
 | |
| 
 | |
|     for (const [key, config] of Object.entries(window.EVENT_TYPES)) {
 | |
|         // Recherche dans la clé, le label et la catégorie
 | |
|         const searchableText = `${key} ${config.label} ${config.category}`.toLowerCase();
 | |
| 
 | |
|         if (searchableText.includes(inputLower)) {
 | |
|             suggestions.push({
 | |
|                 value: key,
 | |
|                 label: `${config.emoji} ${config.label}`,
 | |
|                 category: config.category,
 | |
|                 fullText: `${key} - ${config.label}`
 | |
|             });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Trier par pertinence (correspondance exacte en premier)
 | |
|     suggestions.sort((a, b) => {
 | |
|         const aExact = a.value.toLowerCase().startsWith(inputLower);
 | |
|         const bExact = b.value.toLowerCase().startsWith(inputLower);
 | |
| 
 | |
|         if (aExact && !bExact) return -1;
 | |
|         if (!aExact && bExact) return 1;
 | |
| 
 | |
|         return a.value.localeCompare(b.value);
 | |
|     });
 | |
| 
 | |
|     return suggestions.slice(0, 10); // Limiter à 10 suggestions
 | |
| }
 | |
| 
 | |
| // Fonction pour initialiser l'autocomplétion sur un champ
 | |
| function initializeEventTypeAutocomplete(inputElement, onSelect) {
 | |
|     if (!inputElement) return;
 | |
| 
 | |
|     let suggestionsContainer = null;
 | |
|     let currentSuggestions = [];
 | |
|     let selectedIndex = -1;
 | |
|     let selectorButton = null;
 | |
| 
 | |
|     // Créer le bouton de sélection
 | |
|     function createSelectorButton() {
 | |
|         selectorButton = document.createElement('button');
 | |
|         selectorButton.type = 'button';
 | |
|         selectorButton.className = 'event-type-selector-btn';
 | |
|         selectorButton.innerHTML = '📋 Types d\'événements';
 | |
|         selectorButton.style.cssText = `
 | |
|             margin-top: 5px;
 | |
|             padding: 6px 12px;
 | |
|             background-color: #0078ff;
 | |
|             color: white;
 | |
|             border: none;
 | |
|             border-radius: 4px;
 | |
|             cursor: pointer;
 | |
|             font-size: 12px;
 | |
|             display: inline-block;
 | |
|         `;
 | |
| 
 | |
|         selectorButton.addEventListener('click', function() {
 | |
|             showAllEventTypes();
 | |
|         });
 | |
| 
 | |
|         const parent = inputElement.parentElement;
 | |
|         parent.appendChild(selectorButton);
 | |
|     }
 | |
| 
 | |
|     // Créer le conteneur de suggestions
 | |
|     function createSuggestionsContainer() {
 | |
|         suggestionsContainer = document.createElement('div');
 | |
|         suggestionsContainer.className = 'autocomplete-suggestions';
 | |
|         suggestionsContainer.style.cssText = `
 | |
|             position: absolute;
 | |
|             top: 100%;
 | |
|             left: 0;
 | |
|             right: 0;
 | |
|             max-height: 300px;
 | |
|             overflow-y: auto;
 | |
|             background: white;
 | |
|             border: 1px solid #ddd;
 | |
|             border-radius: 8px;
 | |
|             box-shadow: 0 4px 12px rgba(0,0,0,0.15);
 | |
|             z-index: 1000;
 | |
|             display: none;
 | |
|             margin-top: 2px;
 | |
|         `;
 | |
| 
 | |
|         // Positionner le conteneur parent en relatif
 | |
|         const parent = inputElement.parentElement;
 | |
|         if (parent.style.position !== 'relative') {
 | |
|             parent.style.position = 'relative';
 | |
|         }
 | |
|         parent.appendChild(suggestionsContainer);
 | |
|     }
 | |
| 
 | |
|     // Afficher tous les types d'événements dans un beau sélecteur
 | |
|     function showAllEventTypes() {
 | |
|         const allTypes = Object.entries(window.EVENT_TYPES).map(([key, config]) => ({
 | |
|             value: key,
 | |
|             emoji: config.emoji,
 | |
|             label: config.label,
 | |
|             category: config.category,
 | |
|             description: config.description || '',
 | |
|             fullText: `${key} - ${config.label}`
 | |
|         }));
 | |
| 
 | |
|         showEventTypeSelector(allTypes);
 | |
|     }
 | |
| 
 | |
|     // Afficher le sélecteur de types d'événements
 | |
|     function showEventTypeSelector(types) {
 | |
|         if (!suggestionsContainer) return;
 | |
| 
 | |
|         currentSuggestions = types;
 | |
|         selectedIndex = -1;
 | |
| 
 | |
|         // Grouper par catégorie
 | |
|         const groupedTypes = {};
 | |
|         types.forEach(type => {
 | |
|             if (!groupedTypes[type.category]) {
 | |
|                 groupedTypes[type.category] = [];
 | |
|             }
 | |
|             groupedTypes[type.category].push(type);
 | |
|         });
 | |
| 
 | |
|         let html = `
 | |
|             <div style="padding: 10px; border-bottom: 1px solid #eee; background-color: #f8f9fa; border-radius: 8px 8px 0 0;">
 | |
|                 <strong style="color: #0078ff;">🏷️ Types d'événements disponibles</strong>
 | |
|                 <button onclick="this.parentElement.parentElement.style.display='none'" style="float: right; background: none; border: none; font-size: 16px; cursor: pointer;">✕</button>
 | |
|             </div>
 | |
|         `;
 | |
| 
 | |
|         // Afficher chaque catégorie
 | |
|         Object.entries(groupedTypes).forEach(([category, categoryTypes]) => {
 | |
|             html += `
 | |
|                 <div style="padding: 8px 0;">
 | |
|                     <div style="padding: 6px 12px; background-color: #e9ecef; font-weight: bold; color: #495057; font-size: 13px;">
 | |
|                         ${category}
 | |
|                     </div>
 | |
|             `;
 | |
| 
 | |
|             categoryTypes.forEach((type, index) => {
 | |
|                 const globalIndex = types.indexOf(type);
 | |
|                 html += `
 | |
|                     <div class="suggestion-item" data-index="${globalIndex}" style="
 | |
|                         padding: 10px 15px;
 | |
|                         cursor: pointer;
 | |
|                         border-bottom: 1px solid #f1f3f4;
 | |
|                         transition: background-color 0.2s;
 | |
|                         display: flex;
 | |
|                         align-items: center;
 | |
|                         gap: 10px;
 | |
|                     " onmouseover="this.style.backgroundColor='#f0f8ff'" onmouseout="this.style.backgroundColor='white'">
 | |
|                         <span style="font-size: 20px; min-width: 25px;">${type.emoji}</span>
 | |
|                         <div style="flex: 1;">
 | |
|                             <div style="font-weight: 500; color: #212529;">${type.label}</div>
 | |
|                             <div style="font-size: 12px; color: #6c757d; font-family: monospace;">${type.value}</div>
 | |
|                             ${type.description ? `<div style="font-size: 11px; color: #868e96; font-style: italic;">${type.description}</div>` : ''}
 | |
|                         </div>
 | |
|                     </div>
 | |
|                 `;
 | |
|             });
 | |
| 
 | |
|             html += '</div>';
 | |
|         });
 | |
| 
 | |
|         suggestionsContainer.innerHTML = html;
 | |
|         suggestionsContainer.style.display = 'block';
 | |
| 
 | |
|         // Ajouter les gestionnaires d'événements
 | |
|         const suggestionItems = suggestionsContainer.querySelectorAll('.suggestion-item');
 | |
|         suggestionItems.forEach((item, index) => {
 | |
|             const dataIndex = parseInt(item.dataset.index);
 | |
|             item.addEventListener('click', () => selectSuggestion(dataIndex));
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     // Afficher les suggestions
 | |
|     function showSuggestions(suggestions) {
 | |
|         if (!suggestionsContainer) return;
 | |
| 
 | |
|         currentSuggestions = suggestions;
 | |
|         selectedIndex = -1;
 | |
| 
 | |
|         if (suggestions.length === 0) {
 | |
|             suggestionsContainer.style.display = 'none';
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         suggestionsContainer.innerHTML = suggestions.map((suggestion, index) => `
 | |
|             <div class="suggestion-item" data-index="${index}" style="
 | |
|                 padding: 8px 12px;
 | |
|                 cursor: pointer;
 | |
|                 border-bottom: 1px solid #eee;
 | |
|                 font-size: 14px;
 | |
|                 display: flex;
 | |
|                 align-items: center;
 | |
|                 gap: 8px;
 | |
|             ">
 | |
|                 <span style="font-size: 16px;">${suggestion.label.split(' ')[0]}</span>
 | |
|                 <span style="flex: 1;">${suggestion.fullText}</span>
 | |
|                 <small style="color: #666;">${suggestion.category}</small>
 | |
|             </div>
 | |
|         `).join('');
 | |
| 
 | |
|         suggestionsContainer.style.display = 'block';
 | |
| 
 | |
|         // Ajouter les gestionnaires d'événements aux suggestions
 | |
|         const suggestionItems = suggestionsContainer.querySelectorAll('.suggestion-item');
 | |
|         suggestionItems.forEach((item, index) => {
 | |
|             item.addEventListener('click', () => selectSuggestion(index));
 | |
|             item.addEventListener('mouseenter', () => highlightSuggestion(index));
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     // Mettre en surbrillance une suggestion
 | |
|     function highlightSuggestion(index) {
 | |
|         const items = suggestionsContainer.querySelectorAll('.suggestion-item');
 | |
|         items.forEach((item, i) => {
 | |
|             item.style.backgroundColor = i === index ? '#f0f8ff' : 'white';
 | |
|         });
 | |
|         selectedIndex = index;
 | |
|     }
 | |
| 
 | |
|     // Sélectionner une suggestion
 | |
|     function selectSuggestion(index) {
 | |
|         if (index >= 0 && index < currentSuggestions.length) {
 | |
|             const suggestion = currentSuggestions[index];
 | |
|             inputElement.value = suggestion.value;
 | |
|             suggestionsContainer.style.display = 'none';
 | |
| 
 | |
|             if (onSelect) {
 | |
|                 onSelect(suggestion);
 | |
|             }
 | |
| 
 | |
|             // Déclencher l'événement input pour les validations
 | |
|             inputElement.dispatchEvent(new Event('input', { bubbles: true }));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Masquer les suggestions
 | |
|     function hideSuggestions() {
 | |
|         if (suggestionsContainer) {
 | |
|             suggestionsContainer.style.display = 'none';
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // Créer le conteneur de suggestions et le bouton
 | |
|     createSuggestionsContainer();
 | |
|     createSelectorButton();
 | |
| 
 | |
|     // Gestionnaire d'événements pour l'input
 | |
|     inputElement.addEventListener('input', function(e) {
 | |
|         const value = e.target.value.trim();
 | |
| 
 | |
|         if (value.length === 0) {
 | |
|             showAllEventTypes();
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (value.length < 2) {
 | |
|             hideSuggestions();
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         const suggestions = getEventTypeSuggestions(value);
 | |
|         showFilteredSuggestions(suggestions);
 | |
|     });
 | |
| 
 | |
|     // Afficher l'autocomplétion au focus
 | |
|     inputElement.addEventListener('focus', function(e) {
 | |
|         const value = e.target.value.trim();
 | |
| 
 | |
|         if (value.length === 0) {
 | |
|             showAllEventTypes();
 | |
|         } else if (value.length >= 2) {
 | |
|             const suggestions = getEventTypeSuggestions(value);
 | |
|             showFilteredSuggestions(suggestions);
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     // Afficher l'autocomplétion au keyup
 | |
|     inputElement.addEventListener('keyup', function(e) {
 | |
|         // Ignorer les touches de navigation
 | |
|         if (['ArrowDown', 'ArrowUp', 'Enter', 'Escape'].includes(e.key)) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         const value = e.target.value.trim();
 | |
| 
 | |
|         if (value.length === 0) {
 | |
|             showAllEventTypes();
 | |
|         } else if (value.length >= 2) {
 | |
|             const suggestions = getEventTypeSuggestions(value);
 | |
|             showFilteredSuggestions(suggestions);
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     // Fonction pour afficher les suggestions filtrées
 | |
|     function showFilteredSuggestions(suggestions) {
 | |
|         if (!suggestionsContainer) return;
 | |
| 
 | |
|         currentSuggestions = suggestions;
 | |
|         selectedIndex = -1;
 | |
| 
 | |
|         if (suggestions.length === 0) {
 | |
|             suggestionsContainer.innerHTML = `
 | |
|                 <div style="padding: 15px; text-align: center; color: #6c757d;">
 | |
|                     Aucun type d'événement trouvé
 | |
|                 </div>
 | |
|             `;
 | |
|             suggestionsContainer.style.display = 'block';
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         let html = `
 | |
|             <div style="padding: 8px 12px; border-bottom: 1px solid #eee; background-color: #f8f9fa; font-size: 12px; color: #6c757d;">
 | |
|                 ${suggestions.length} résultat${suggestions.length > 1 ? 's' : ''} trouvé${suggestions.length > 1 ? 's' : ''}
 | |
|             </div>
 | |
|         `;
 | |
| 
 | |
|         suggestions.forEach((suggestion, index) => {
 | |
|             const config = window.EVENT_TYPES[suggestion.value] || {};
 | |
|             html += `
 | |
|                 <div class="suggestion-item" data-index="${index}" style="
 | |
|                     padding: 10px 15px;
 | |
|                     cursor: pointer;
 | |
|                     border-bottom: 1px solid #f1f3f4;
 | |
|                     display: flex;
 | |
|                     align-items: center;
 | |
|                     gap: 10px;
 | |
|                 " onmouseover="this.style.backgroundColor='#f0f8ff'" onmouseout="this.style.backgroundColor='white'">
 | |
|                     <span style="font-size: 20px;">${config.emoji || '📍'}</span>
 | |
|                     <div style="flex: 1;">
 | |
|                         <div style="font-weight: 500;">${config.label || suggestion.value}</div>
 | |
|                         <div style="font-size: 12px; color: #6c757d; font-family: monospace;">${suggestion.value}</div>
 | |
|                     </div>
 | |
|                     <small style="color: #6c757d;">${config.category || ''}</small>
 | |
|                 </div>
 | |
|             `;
 | |
|         });
 | |
| 
 | |
|         suggestionsContainer.innerHTML = html;
 | |
|         suggestionsContainer.style.display = 'block';
 | |
| 
 | |
|         // Ajouter les gestionnaires d'événements aux suggestions
 | |
|         const suggestionItems = suggestionsContainer.querySelectorAll('.suggestion-item');
 | |
|         suggestionItems.forEach((item, index) => {
 | |
|             item.addEventListener('click', () => selectSuggestion(index));
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     // Gestionnaire d'événements pour le clavier
 | |
|     inputElement.addEventListener('keydown', function(e) {
 | |
|         if (!suggestionsContainer || suggestionsContainer.style.display === 'none') {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         switch (e.key) {
 | |
|             case 'ArrowDown':
 | |
|                 e.preventDefault();
 | |
|                 const nextIndex = selectedIndex < currentSuggestions.length - 1 ? selectedIndex + 1 : 0;
 | |
|                 highlightSuggestion(nextIndex);
 | |
|                 break;
 | |
| 
 | |
|             case 'ArrowUp':
 | |
|                 e.preventDefault();
 | |
|                 const prevIndex = selectedIndex > 0 ? selectedIndex - 1 : currentSuggestions.length - 1;
 | |
|                 highlightSuggestion(prevIndex);
 | |
|                 break;
 | |
| 
 | |
|             case 'Enter':
 | |
|                 e.preventDefault();
 | |
|                 if (selectedIndex >= 0) {
 | |
|                     selectSuggestion(selectedIndex);
 | |
|                 }
 | |
|                 break;
 | |
| 
 | |
|             case 'Escape':
 | |
|                 hideSuggestions();
 | |
|                 break;
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     // Masquer les suggestions quand on clique ailleurs
 | |
|     document.addEventListener('click', function(e) {
 | |
|         if (!inputElement.contains(e.target) && !suggestionsContainer?.contains(e.target)) {
 | |
|             hideSuggestions();
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     // Masquer les suggestions quand le champ perd le focus
 | |
|     inputElement.addEventListener('blur', function() {
 | |
|         // Délai pour permettre le clic sur une suggestion
 | |
|         setTimeout(hideSuggestions, 200);
 | |
|     });
 | |
| }
 | 
