This commit is contained in:
Tykayn 2025-09-27 01:10:47 +02:00 committed by tykayn
parent dea71fc6b3
commit 11cd3236c5
13 changed files with 1952 additions and 71 deletions

View file

@ -19,6 +19,9 @@ function initializeForm() {
const properties = window.eventData.properties || {};
const geometry = window.eventData.geometry || {};
// Remplir le tableau des propriétés
populatePropertiesTable();
// Remplir les champs du formulaire
document.getElementById('label').value = properties.label || '';
document.getElementById('type').value = properties.type || 'scheduled';
@ -102,6 +105,54 @@ function initializeMap() {
console.log('✅ Carte initialisée');
}
function populatePropertiesTable() {
if (!window.eventData || !window.eventData.properties) {
return;
}
const tableBody = document.getElementById('propertiesTableBody');
const properties = window.eventData.properties;
// Vider le tableau
tableBody.innerHTML = '';
// Trier les propriétés alphabétiquement
const sortedKeys = Object.keys(properties).sort();
// Ajouter chaque propriété au tableau
sortedKeys.forEach(key => {
const value = properties[key];
const valueType = typeof value;
const row = document.createElement('tr');
row.style.borderBottom = '1px solid #eee';
// Formater la valeur pour l'affichage
let displayValue = '';
if (value === null) {
displayValue = '<em style="color: #999;">null</em>';
} else if (value === undefined) {
displayValue = '<em style="color: #999;">undefined</em>';
} else if (typeof value === 'object') {
displayValue = `<pre style="margin: 0; white-space: pre-wrap; font-size: 12px;">${JSON.stringify(value, null, 2)}</pre>`;
} else if (typeof value === 'string' && value.length > 50) {
displayValue = `<span title="${value}">${value.substring(0, 50)}...</span>`;
} else {
displayValue = String(value);
}
row.innerHTML = `
<td style="padding: 8px; font-weight: bold; background-color: #f8f9fa;">${key}</td>
<td style="padding: 8px; word-break: break-word; max-width: 300px;">${displayValue}</td>
<td style="padding: 8px; color: #666; font-style: italic;">${valueType}</td>
`;
tableBody.appendChild(row);
});
console.log('✅ Tableau des propriétés rempli');
}
function setupEventHandlers() {
// Gestionnaire de soumission du formulaire
const form = document.getElementById('eventForm');
@ -114,6 +165,34 @@ function setupEventHandlers() {
if (deleteButton) {
deleteButton.addEventListener('click', handleDelete);
}
// Gestionnaire du bouton d'inversion des coordonnées
const swapButton = document.getElementById('swapCoordinatesButton');
if (swapButton) {
swapButton.addEventListener('click', handleSwapCoordinates);
// Vérifier si c'est un Point pour activer/désactiver le bouton
const geometry = window.eventData?.geometry;
if (!geometry || geometry.type !== 'Point') {
swapButton.disabled = true;
swapButton.textContent = '🔄 Non disponible (pas un Point)';
swapButton.style.backgroundColor = '#6c757d';
}
}
// Initialiser l'autocomplétion pour le champ "what"
const whatInput = document.getElementById('what');
if (whatInput && window.initializeEventTypeAutocomplete) {
initializeEventTypeAutocomplete(whatInput, function(suggestion) {
console.log('Type d\'événement sélectionné:', suggestion);
// Émettre un événement de validation du formulaire
setTimeout(() => {
whatInput.dispatchEvent(new Event('input', { bubbles: true }));
}, 100);
});
console.log('✅ Autocomplétion initialisée pour le champ "what"');
}
}
async function handleFormSubmit(e) {
@ -230,3 +309,115 @@ async function handleDelete() {
`;
}
}
async function handleSwapCoordinates() {
const eventId = document.getElementById('eventId').value;
const resultElement = document.getElementById('result');
const geometry = window.eventData?.geometry;
// Vérifications de sécurité
if (!geometry || geometry.type !== 'Point') {
resultElement.innerHTML = `
<div style="color: #dc3545; padding: 15px; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;">
<strong>Erreur:</strong> L'inversion des coordonnées n'est disponible que pour les événements de type Point.
</div>
`;
resultElement.style.display = 'block';
return;
}
const currentCoords = geometry.coordinates;
if (!Array.isArray(currentCoords) || currentCoords.length < 2) {
resultElement.innerHTML = `
<div style="color: #dc3545; padding: 15px; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;">
<strong>Erreur:</strong> Coordonnées invalides détectées.
</div>
`;
resultElement.style.display = 'block';
return;
}
// Afficher les coordonnées actuelles et futures
const [currentLon, currentLat] = currentCoords;
const newCoords = [currentLat, currentLon]; // Inverser longitude et latitude
const confirmMessage = `Voulez-vous inverser les coordonnées ?
Actuelles: [${currentLon.toFixed(6)}, ${currentLat.toFixed(6)}] (lon, lat)
Nouvelles: [${newCoords[0].toFixed(6)}, ${newCoords[1].toFixed(6)}] (latlon, lonlat)
Cette action sera sauvegardée immédiatement sur le serveur.`;
if (!confirm(confirmMessage)) {
return;
}
resultElement.innerHTML = '<div style="color: #17a2b8;">⏳ Inversion des coordonnées en cours...</div>';
resultElement.style.display = 'block';
try {
// Créer l'événement modifié avec coordonnées inversées
const updatedEvent = {
...window.eventData,
geometry: {
...geometry,
coordinates: newCoords
}
};
// Envoyer la mise à jour au serveur
const response = await fetch(`https://api.openeventdatabase.org/event/${eventId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(updatedEvent)
});
if (response.ok) {
// Mettre à jour les données locales
window.eventData = updatedEvent;
// Mettre à jour le marqueur sur la carte
if (currentMarker) {
currentMarker.setLngLat(newCoords);
}
// Centrer la carte sur les nouvelles coordonnées
if (map) {
map.flyTo({
center: newCoords,
zoom: Math.max(map.getZoom(), 12)
});
}
// Mettre à jour le tableau des propriétés
populatePropertiesTable();
resultElement.innerHTML = `
<div style="color: #28a745; padding: 15px; background: #d4edda; border: 1px solid #c3e6cb; border-radius: 4px;">
<i class="fas fa-check"></i>
<strong>Succès:</strong> Les coordonnées ont été inversées avec succès.<br>
<small>Anciennes: [${currentLon.toFixed(6)}, ${currentLat.toFixed(6)}]<br>
Nouvelles: [${newCoords[0].toFixed(6)}, ${newCoords[1].toFixed(6)}]</small>
</div>
`;
console.log('✅ Coordonnées inversées avec succès:', {
avant: [currentLon, currentLat],
après: newCoords
});
} else {
const errorData = await response.text();
throw new Error(`HTTP ${response.status}: ${errorData}`);
}
} catch (error) {
console.error('Erreur lors de l\'inversion des coordonnées:', error);
resultElement.innerHTML = `
<div style="color: #dc3545; padding: 15px; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px;">
<i class="fas fa-exclamation-triangle"></i>
<strong>Erreur:</strong> Impossible d'inverser les coordonnées. ${error.message}
</div>
`;
}
}

501
static/event-types.js Normal file
View file

@ -0,0 +1,501 @@
// 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: '⛑️',
label: 'Travaux routiers',
category: 'Circulation',
description: 'Travaux sur la chaussée'
}
};
// 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);
});
}

View file

@ -240,13 +240,23 @@ function createPopupContent(properties) {
const title = properties.label || properties.name || 'Événement';
const what = properties.what || 'Non spécifié';
const description = properties.description || 'Aucune description disponible';
const eventId = properties.id;
return `
<div class="event-popup">
<h3 style="margin-top: 0; color: #0078ff;">${title}</h3>
${eventId ?
`<h3 style="margin-top: 0; color: #0078ff; cursor: pointer; text-decoration: underline;" onclick="window.location.href='/demo/edit/${eventId}'" title="Cliquer pour modifier l'événement">${title}</h3>` :
`<h3 style="margin-top: 0; color: #0078ff;">${title}</h3>`
}
<p><strong>Type:</strong> ${what}</p>
<p><strong>Description:</strong> ${description}</p>
${properties.id ? `<p><a href="/demo/edit/${properties.id}" style="color: #0078ff; font-weight: bold;">Modifier</a></p>` : ''}
${eventId ?
`<div style="margin-top: 10px;">
<a href="/demo/edit/${eventId}" style="color: #0078ff; font-weight: bold; margin-right: 10px;"> Modifier</a>
<a href="/demo/by_id/${eventId}" style="color: #0078ff; font-weight: bold;">👁 Détails</a>
</div>` :
`<p><small style="color: #666;">ID non disponible</small></p>`
}
</div>
`;
}