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);
|
|
});
|
|
}
|