up pages
This commit is contained in:
parent
2bb77d2300
commit
98c40b2447
16 changed files with 1836 additions and 361 deletions
|
@ -44,6 +44,8 @@ class DemoMainResource:
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OpenEventDatabase Demo</title>
|
||||
<link rel="icon" type="image/png" href="/static/oedb.png">
|
||||
<link rel="icon" type="image/png" href="/static/oedb.png">
|
||||
<script src="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.js"></script>
|
||||
<link href="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
||||
|
@ -287,38 +289,24 @@ class DemoMainResource:
|
|||
<script>
|
||||
// Fonction pour gérer les listes dépliantes et sections collapsibles
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const endpointsHeader = document.getElementById('endpoints_list_header');
|
||||
const endpointsList = document.getElementById('endpoints_list');
|
||||
const demoPagesHeader = document.getElementById('demo_pages_list_header');
|
||||
const demoPagesList = document.getElementById('demo_pages_list');
|
||||
const infoPanelHeader = document.getElementById('info_panel_header');
|
||||
const infoPanelContent = document.getElementById('info_panel_content');
|
||||
const filtersPanel = document.getElementById('filters_panel');
|
||||
const filtersHeader = document.getElementById('filters_header');
|
||||
|
||||
// Fonction pour basculer l'affichage d'une liste ou section
|
||||
function toggleList(header, list) {
|
||||
header.addEventListener('click', function() {
|
||||
if (list.style.display === 'none' || list.style.display === '') {
|
||||
list.style.display = 'block';
|
||||
header.classList.add('active');
|
||||
} else {
|
||||
list.style.display = 'none';
|
||||
header.classList.remove('active');
|
||||
}
|
||||
});
|
||||
if (header && list) {
|
||||
header.addEventListener('click', function() {
|
||||
if (list.style.display === 'none' || list.style.display === '') {
|
||||
list.style.display = 'block';
|
||||
header.classList.add('active');
|
||||
} else {
|
||||
list.style.display = 'none';
|
||||
header.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialiser les listes et sections comme repliées
|
||||
endpointsList.style.display = 'none';
|
||||
demoPagesList.style.display = 'none';
|
||||
infoPanelContent.style.display = 'none';
|
||||
|
||||
// Ajouter les écouteurs d'événements
|
||||
toggleList(endpointsHeader, endpointsList);
|
||||
toggleList(demoPagesHeader, demoPagesList);
|
||||
toggleList(infoPanelHeader, infoPanelContent);
|
||||
|
||||
// Toggle pour le panneau de filtres via le titre "Filtres"
|
||||
if (filtersHeader && filtersPanel) {
|
||||
filtersHeader.addEventListener('click', function() {
|
||||
|
@ -331,8 +319,9 @@ class DemoMainResource:
|
|||
}
|
||||
});
|
||||
|
||||
// Variable globale pour stocker les marqueurs d'événements
|
||||
// Variables globales pour stocker les marqueurs d'événements et le premier chargement
|
||||
window.eventMarkers = [];
|
||||
window.isFirstLoad = true;
|
||||
|
||||
function addEventsToMap(geojsonData) {
|
||||
if (!geojsonData || !geojsonData.features) return;
|
||||
|
@ -533,6 +522,8 @@ class DemoMainResource:
|
|||
|
||||
// Function to fetch events from the API
|
||||
function fetchEvents() {
|
||||
console.log('🔄 Chargement des événements...', isFirstLoad ? '(Premier chargement)' : '(Rechargement)');
|
||||
|
||||
// Fetch events from the API - using the local API endpoint
|
||||
fetch('https://api.openeventdatabase.org/event?')
|
||||
.then(response => response.json())
|
||||
|
@ -542,8 +533,8 @@ class DemoMainResource:
|
|||
addEventsToMap(data);
|
||||
// Render histogram for retrieved events
|
||||
try { renderEventsHistogram(data.features); } catch(e) { console.warn('Histogram error', e); }
|
||||
|
||||
// Fit map to events bounds
|
||||
|
||||
// Fit map to events bounds (seulement au premier chargement)
|
||||
fitMapToBounds(data);
|
||||
} else {
|
||||
console.log('No events found');
|
||||
|
@ -826,23 +817,29 @@ class DemoMainResource:
|
|||
return (currentTime - createTime) > oneHourInMs;
|
||||
}
|
||||
|
||||
// Function to fit map to events bounds
|
||||
// Function to fit map to events bounds (only on first load)
|
||||
function fitMapToBounds(geojson) {
|
||||
if (geojson.features.length === 0) return;
|
||||
|
||||
if (geojson.features.length === 0 || !window.isFirstLoad) return;
|
||||
|
||||
console.log('🎯 Premier chargement - Ajustement de la vue sur les événements');
|
||||
|
||||
// Create a bounds object
|
||||
const bounds = new maplibregl.LngLatBounds();
|
||||
|
||||
|
||||
// Extend bounds with each feature
|
||||
geojson.features.forEach(feature => {
|
||||
bounds.extend(feature.geometry.coordinates);
|
||||
});
|
||||
|
||||
|
||||
// Fit map to bounds with padding
|
||||
map.fitBounds(bounds, {
|
||||
padding: 50,
|
||||
maxZoom: 12
|
||||
});
|
||||
|
||||
// Marquer que le premier chargement est terminé
|
||||
window.isFirstLoad = false;
|
||||
console.log('✅ Vue initiale définie, les prochains rafraîchissements ne déplaceront plus la carte');
|
||||
}
|
||||
|
||||
// Function to update user information display
|
||||
|
|
|
@ -520,6 +520,7 @@ button:hover {
|
|||
position: fixed;
|
||||
bottom: 0.5rem;
|
||||
right: 0.5rem;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
/* Tab styles */
|
||||
|
@ -625,4 +626,12 @@ button{
|
|||
padding: 1rem 0.5rem;
|
||||
border-radius: 5px;
|
||||
background-color: #79a2d1;
|
||||
}
|
||||
|
||||
#eventsHistogram{
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.maplibregl-ctrl-attrib + .maplibregl-ctrl-attrib {
|
||||
display: none;
|
||||
}
|
|
@ -170,40 +170,52 @@ document.getElementById('deleteButton').addEventListener('click', function() {
|
|||
const eventId = document.getElementById('eventId').value;
|
||||
|
||||
// Show confirmation dialog
|
||||
if (confirm('Are you sure you want to delete this event? This action cannot be undone.')) {
|
||||
// Submit delete request to API
|
||||
fetch(`/event/${eventId}`, {
|
||||
if (confirm('Êtes-vous sûr de vouloir supprimer cet événement ? Cette action ne peut pas être annulée.')) {
|
||||
// Show loading message
|
||||
showResult('Suppression en cours...', 'info');
|
||||
|
||||
// Submit delete request to external API
|
||||
fetch(`https://api.openeventdatabase.org/event/${eventId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
showResult('Event deleted successfully', 'success');
|
||||
|
||||
if (response.ok || response.status === 204) {
|
||||
showResult('✅ Événement supprimé avec succès !', 'success');
|
||||
|
||||
// Add link to go back to map
|
||||
const resultElement = document.getElementById('result');
|
||||
resultElement.innerHTML += `<p><a href="/demo">Back to Map</a></p>`;
|
||||
|
||||
resultElement.innerHTML += `<p><a href="/demo">Retour à la carte</a></p>`;
|
||||
|
||||
// Disable form controls
|
||||
const formElements = document.querySelectorAll('#eventForm input, #eventForm select, #eventForm button');
|
||||
formElements.forEach(element => {
|
||||
element.disabled = true;
|
||||
});
|
||||
|
||||
// Redirect to demo page after 2 seconds
|
||||
|
||||
// Redirect to demo page after 3 seconds
|
||||
setTimeout(() => {
|
||||
window.location.href = '/demo';
|
||||
}, 2000);
|
||||
}, 3000);
|
||||
} else if (response.status === 404) {
|
||||
throw new Error('Événement non trouvé sur l\'API externe');
|
||||
} else if (response.status === 403) {
|
||||
throw new Error('Accès non autorisé - un secret pourrait être requis pour supprimer cet événement');
|
||||
} else {
|
||||
return response.text().then(text => {
|
||||
throw new Error(text || response.statusText);
|
||||
throw new Error(text || `Erreur HTTP ${response.status}: ${response.statusText}`);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showResult(`Error deleting event: ${error.message}`, 'error');
|
||||
console.error('Erreur lors de la suppression:', error);
|
||||
if (error.name === 'TypeError' && error.message.includes('Failed to fetch')) {
|
||||
showResult('❌ Erreur de connexion : Impossible de joindre l\'API api.openeventdatabase.org', 'error');
|
||||
} else {
|
||||
showResult(`❌ Erreur lors de la suppression : ${error.message}`, 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
|
@ -3,7 +3,6 @@
|
|||
// Global variables
|
||||
let map;
|
||||
let marker;
|
||||
let geocoder;
|
||||
let currentPosition;
|
||||
let currentIssueType = null;
|
||||
let photoFiles = [];
|
||||
|
@ -11,171 +10,542 @@ let panoramaxUploadUrl = '';
|
|||
let panoramaxToken = '';
|
||||
|
||||
// Initialize the map when the page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
initMap();
|
||||
initTabs();
|
||||
initForm();
|
||||
|
||||
setupFormValidation();
|
||||
|
||||
// Get Panoramax configuration
|
||||
panoramaxUploadUrl = document.getElementById('panoramaxUploadUrl').value;
|
||||
panoramaxToken = document.getElementById('panoramaxToken').value;
|
||||
|
||||
const panoramaxUploadUrlElement = document.getElementById('panoramaxUploadUrl');
|
||||
const panoramaxTokenElement = document.getElementById('panoramaxToken');
|
||||
|
||||
panoramaxUploadUrl = panoramaxUploadUrlElement ? panoramaxUploadUrlElement.value : '';
|
||||
panoramaxToken = panoramaxTokenElement ? panoramaxTokenElement.value : '';
|
||||
|
||||
// Set up photo upload
|
||||
const photoInput = document.getElementById('photo');
|
||||
if (photoInput) {
|
||||
photoInput.addEventListener('change', handlePhotoUpload);
|
||||
}
|
||||
|
||||
// Set up form submission
|
||||
const reportForm = document.getElementById('reportForm');
|
||||
if (reportForm) {
|
||||
reportForm.addEventListener('submit', submitReport);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize the map
|
||||
function initMap() {
|
||||
// Create the map
|
||||
map = new maplibregl.Map({
|
||||
container: 'map',
|
||||
style: 'https://tiles.openfreemap.org/styles/liberty',
|
||||
center: [2.2137, 46.2276], // Default center (center of metropolitan France)
|
||||
zoom: 5
|
||||
});
|
||||
|
||||
// Add navigation controls
|
||||
map.addControl(new maplibregl.NavigationControl());
|
||||
|
||||
// Add attribution control with OpenStreetMap attribution
|
||||
map.addControl(new maplibregl.AttributionControl({
|
||||
customAttribution: '© <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors'
|
||||
}));
|
||||
|
||||
// Try to get the user's current location
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
function(position) {
|
||||
currentPosition = [position.coords.longitude, position.coords.latitude];
|
||||
|
||||
// Center the map on the user's location
|
||||
map.flyTo({
|
||||
center: currentPosition,
|
||||
zoom: 12
|
||||
});
|
||||
|
||||
// Add a marker at the user's location
|
||||
function initMap() {
|
||||
// Create the map
|
||||
map = new maplibregl.Map({
|
||||
container: 'map',
|
||||
style: 'https://tiles.openfreemap.org/styles/liberty',
|
||||
center: [2.2137, 46.2276], // Default center (center of metropolitan France)
|
||||
zoom: 5
|
||||
});
|
||||
|
||||
// Add navigation controls
|
||||
map.addControl(new maplibregl.NavigationControl());
|
||||
|
||||
// Add attribution control with OpenStreetMap attribution
|
||||
map.addControl(new maplibregl.AttributionControl({
|
||||
customAttribution: '© <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a> contributors'
|
||||
}));
|
||||
|
||||
// Try to get the user's current location
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
function (position) {
|
||||
currentPosition = [position.coords.longitude, position.coords.latitude];
|
||||
|
||||
// Center the map on the user's location
|
||||
map.flyTo({
|
||||
center: currentPosition,
|
||||
zoom: 12
|
||||
});
|
||||
|
||||
// Add a marker at the user's location
|
||||
marker = new maplibregl.Marker({
|
||||
draggable: true
|
||||
})
|
||||
.setLngLat(currentPosition)
|
||||
.addTo(map);
|
||||
|
||||
// Update coordinates when marker is dragged
|
||||
marker.on('dragend', updateCoordinates);
|
||||
|
||||
// Update the coordinates display
|
||||
updateCoordinates();
|
||||
},
|
||||
function (error) {
|
||||
console.error('Error getting location:', error);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Add click handler to the map
|
||||
map.on('click', function (e) {
|
||||
// If we don't have a marker yet, create one
|
||||
if (!marker) {
|
||||
marker = new maplibregl.Marker({
|
||||
draggable: true
|
||||
})
|
||||
.setLngLat(currentPosition)
|
||||
.addTo(map);
|
||||
|
||||
.setLngLat(e.lngLat)
|
||||
.addTo(map);
|
||||
|
||||
// Update coordinates when marker is dragged
|
||||
marker.on('dragend', updateCoordinates);
|
||||
|
||||
// Update the coordinates display
|
||||
updateCoordinates();
|
||||
},
|
||||
function(error) {
|
||||
console.error('Error getting location:', error);
|
||||
} else {
|
||||
// Otherwise, move the existing marker
|
||||
marker.setLngLat(e.lngLat);
|
||||
}
|
||||
);
|
||||
|
||||
// Update the coordinates display
|
||||
updateCoordinates();
|
||||
});
|
||||
}
|
||||
|
||||
// Add click handler to the map
|
||||
map.on('click', function(e) {
|
||||
// If we don't have a marker yet, create one
|
||||
if (!marker) {
|
||||
marker = new maplibregl.Marker({
|
||||
draggable: true
|
||||
})
|
||||
.setLngLat(e.lngLat)
|
||||
.addTo(map);
|
||||
|
||||
// Update coordinates when marker is dragged
|
||||
marker.on('dragend', updateCoordinates);
|
||||
} else {
|
||||
// Otherwise, move the existing marker
|
||||
marker.setLngLat(e.lngLat);
|
||||
}
|
||||
|
||||
// Update the coordinates display
|
||||
updateCoordinates();
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize the tabs
|
||||
function initTabs() {
|
||||
const tabItems = document.querySelectorAll('.tab-item');
|
||||
const tabPanes = document.querySelectorAll('.tab-pane');
|
||||
|
||||
tabItems.forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
// Remove active class from all tabs
|
||||
tabItems.forEach(tab => tab.classList.remove('active'));
|
||||
tabPanes.forEach(pane => pane.classList.remove('active'));
|
||||
|
||||
// Add active class to clicked tab
|
||||
this.classList.add('active');
|
||||
|
||||
// Show the corresponding tab content
|
||||
const tabId = this.getAttribute('data-tab');
|
||||
document.getElementById(tabId + '-tab').classList.add('active');
|
||||
function initTabs() {
|
||||
const tabItems = document.querySelectorAll('.tab-item');
|
||||
const tabPanes = document.querySelectorAll('.tab-pane');
|
||||
|
||||
tabItems.forEach(item => {
|
||||
item.addEventListener('click', function () {
|
||||
// Remove active class from all tabs
|
||||
tabItems.forEach(tab => tab.classList.remove('active'));
|
||||
tabPanes.forEach(pane => pane.classList.remove('active'));
|
||||
|
||||
// Add active class to clicked tab
|
||||
this.classList.add('active');
|
||||
|
||||
// Show the corresponding tab content
|
||||
const tabId = this.getAttribute('data-tab');
|
||||
document.getElementById(tabId + '-tab').classList.add('active');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the form
|
||||
function initForm() {
|
||||
// Set the current date and time as the default
|
||||
const now = new Date();
|
||||
const dateTimeString = now.toISOString().slice(0, 16);
|
||||
|
||||
const startTimeInput = document.getElementById('start');
|
||||
if (startTimeInput) {
|
||||
startTimeInput.value = dateTimeString;
|
||||
function initForm() {
|
||||
// Set the current date and time as the default
|
||||
const now = new Date();
|
||||
const dateTimeString = now.toISOString().slice(0, 16);
|
||||
|
||||
const startTimeInput = document.getElementById('start');
|
||||
if (startTimeInput) {
|
||||
startTimeInput.value = dateTimeString;
|
||||
}
|
||||
|
||||
const stopTimeInput = document.getElementById('stop');
|
||||
if (stopTimeInput) {
|
||||
// Set default end time to 1 hour from now
|
||||
const oneHourLater = new Date(now.getTime() + 60 * 60 * 1000);
|
||||
stopTimeInput.value = oneHourLater.toISOString().slice(0, 16);
|
||||
}
|
||||
|
||||
// Set up form submission after DOM is loaded
|
||||
const reportForm = document.getElementById('trafficForm');
|
||||
if (reportForm) {
|
||||
reportForm.addEventListener('submit', submitReport);
|
||||
} else {
|
||||
console.warn('Traffic form not found in DOM');
|
||||
}
|
||||
}
|
||||
|
||||
const stopTimeInput = document.getElementById('stop');
|
||||
if (stopTimeInput) {
|
||||
// Set default end time to 1 hour from now
|
||||
const oneHourLater = new Date(now.getTime() + 60 * 60 * 1000);
|
||||
stopTimeInput.value = oneHourLater.toISOString().slice(0, 16);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the coordinates display when the marker is moved
|
||||
function updateCoordinates() {
|
||||
if (!marker) return;
|
||||
|
||||
const lngLat = marker.getLngLat();
|
||||
currentPosition = [lngLat.lng, lngLat.lat];
|
||||
|
||||
// Update the coordinates display
|
||||
const coordinatesElement = document.getElementById('coordinates');
|
||||
if (coordinatesElement) {
|
||||
coordinatesElement.textContent = `${lngLat.lat.toFixed(6)}, ${lngLat.lng.toFixed(6)}`;
|
||||
function updateCoordinates() {
|
||||
if (!marker) return;
|
||||
|
||||
const lngLat = marker.getLngLat();
|
||||
currentPosition = [lngLat.lng, lngLat.lat];
|
||||
|
||||
// Update the coordinates display
|
||||
const coordinatesElement = document.getElementById('coordinates');
|
||||
if (coordinatesElement) {
|
||||
coordinatesElement.textContent = `${lngLat.lat.toFixed(6)}, ${lngLat.lng.toFixed(6)}`;
|
||||
}
|
||||
|
||||
// Update the hidden coordinates input
|
||||
const coordinatesInput = document.getElementById('coordinates-input');
|
||||
if (coordinatesInput) {
|
||||
coordinatesInput.value = JSON.stringify({
|
||||
type: 'Point',
|
||||
coordinates: [lngLat.lng, lngLat.lat]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update the hidden coordinates input
|
||||
const coordinatesInput = document.getElementById('coordinates-input');
|
||||
if (coordinatesInput) {
|
||||
coordinatesInput.value = JSON.stringify({
|
||||
type: 'Point',
|
||||
coordinates: [lngLat.lng, lngLat.lat]
|
||||
|
||||
// Handle photo upload
|
||||
function handlePhotoUpload(event) {
|
||||
const files = event.target.files;
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
// Store the files for later upload
|
||||
photoFiles = Array.from(files);
|
||||
|
||||
// Show preview of the photos
|
||||
const previewContainer = document.getElementById('photoPreview');
|
||||
if (previewContainer) {
|
||||
previewContainer.innerHTML = '';
|
||||
|
||||
photoFiles.forEach(file => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
const img = document.createElement('img');
|
||||
img.src = e.target.result;
|
||||
img.className = 'photo-preview';
|
||||
previewContainer.appendChild(img);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
previewContainer.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
// Submit the report
|
||||
async function submitReport(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Show loading message
|
||||
const resultElement = document.getElementById('result');
|
||||
resultElement.textContent = 'Submitting report...';
|
||||
resultElement.className = '';
|
||||
resultElement.style.display = 'block';
|
||||
|
||||
try {
|
||||
// Check if we have coordinates
|
||||
if (!currentPosition) {
|
||||
throw new Error('Please select a location on the map');
|
||||
}
|
||||
|
||||
// Get form values
|
||||
const formData = new FormData(event.target);
|
||||
const eventData = {
|
||||
type: formData.get('type') || 'unscheduled',
|
||||
what: formData.get('what'),
|
||||
label: formData.get('label'),
|
||||
description: formData.get('description'),
|
||||
start: new Date(formData.get('start')).toISOString(),
|
||||
stop: new Date(formData.get('stop')).toISOString(),
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: currentPosition
|
||||
}
|
||||
};
|
||||
|
||||
// Add username if authenticated
|
||||
const osmUsername = document.getElementById('osmUsername');
|
||||
if (osmUsername && osmUsername.value) {
|
||||
eventData.source = {
|
||||
name: 'OpenStreetMap user',
|
||||
id: osmUsername.value
|
||||
};
|
||||
}
|
||||
|
||||
// Upload photos to Panoramax if available
|
||||
let photoUrls = [];
|
||||
if (photoFiles.length > 0 && panoramaxUploadUrl && panoramaxToken) {
|
||||
photoUrls = await uploadPhotos(photoFiles);
|
||||
if (photoUrls.length > 0) {
|
||||
eventData.media = photoUrls.map(url => ({
|
||||
type: 'image',
|
||||
url: url
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Submit the event to the API
|
||||
const response = await fetch('/event', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(eventData)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(errorText || response.statusText);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Show success message
|
||||
resultElement.textContent = `Report submitted successfully! Event ID: ${data.id}`;
|
||||
resultElement.className = 'success';
|
||||
|
||||
// Reset the form
|
||||
event.target.reset();
|
||||
photoFiles = [];
|
||||
const previewContainer = document.getElementById('photoPreview');
|
||||
if (previewContainer) {
|
||||
previewContainer.innerHTML = '';
|
||||
previewContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
// Initialize the form again
|
||||
initForm();
|
||||
} catch (error) {
|
||||
// Show error message
|
||||
resultElement.textContent = `Error: ${error.message}`;
|
||||
resultElement.className = 'error';
|
||||
}
|
||||
}
|
||||
|
||||
// Upload photos to Panoramax
|
||||
async function uploadPhotos(files) {
|
||||
const urls = [];
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const uploadData = new FormData();
|
||||
uploadData.append('file', file);
|
||||
|
||||
const response = await fetch(panoramaxUploadUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Token ${panoramaxToken}`
|
||||
},
|
||||
body: uploadData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Failed to upload photo:', await response.text());
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.url) {
|
||||
urls.push(data.url);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error uploading photo:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
// Setup form validation
|
||||
function setupFormValidation() {
|
||||
const form = document.getElementById('trafficForm');
|
||||
if (!form) return;
|
||||
|
||||
// Get all form fields that need validation
|
||||
const requiredFields = [
|
||||
{id: 'label', name: 'Description du problème'},
|
||||
{id: 'severity', name: 'Gravité'},
|
||||
{id: 'start', name: 'Heure de début'},
|
||||
{id: 'stop', name: 'Heure de fin'}
|
||||
];
|
||||
|
||||
const optionalFields = [
|
||||
{id: 'cause', name: 'Détails supplémentaires'},
|
||||
{id: 'where', name: 'Route/Nom du lieu'}
|
||||
];
|
||||
|
||||
// Add event listeners for real-time validation
|
||||
[...requiredFields, ...optionalFields].forEach(field => {
|
||||
const element = document.getElementById(field.id);
|
||||
if (element) {
|
||||
element.addEventListener('input', validateForm);
|
||||
element.addEventListener('change', validateForm);
|
||||
element.addEventListener('blur', validateForm);
|
||||
}
|
||||
});
|
||||
|
||||
// Add listener for map clicks (location validation)
|
||||
if (map) {
|
||||
map.on('click', () => {
|
||||
setTimeout(validateForm, 100); // Small delay to ensure marker is set
|
||||
});
|
||||
}
|
||||
|
||||
// Initial validation
|
||||
validateForm();
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
// Clear error message for a field
|
||||
function clearFieldError(element) {
|
||||
const existingError = element.parentNode.querySelector('.field-error');
|
||||
if (existingError) {
|
||||
existingError.remove();
|
||||
}
|
||||
}
|
||||
// Validate the entire form
|
||||
function validateForm() {
|
||||
const form = document.getElementById('trafficForm');
|
||||
const submitButton = document.getElementById('report_issue_button');
|
||||
|
||||
if (!form || !submitButton) {
|
||||
console.warn('🔍 Validation: Formulaire ou bouton de soumission non trouvé');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.group('🔍 Validation du formulaire traffic');
|
||||
|
||||
let isValid = true;
|
||||
let firstInvalidField = null;
|
||||
const errors = [];
|
||||
const validFields = [];
|
||||
|
||||
// Check required fields
|
||||
const requiredFields = [
|
||||
{id: 'label', name: 'Description du problème', minLength: 3},
|
||||
{id: 'severity', name: 'Gravité'},
|
||||
{id: 'start', name: 'Heure de début'},
|
||||
{id: 'stop', name: 'Heure de fin'}
|
||||
];
|
||||
|
||||
console.log('📝 Vérification des champs requis...');
|
||||
|
||||
requiredFields.forEach(field => {
|
||||
const element = document.getElementById(field.id);
|
||||
if (element) {
|
||||
let fieldValid = true;
|
||||
let errorMessage = '';
|
||||
const value = element.value.trim();
|
||||
|
||||
// Check if field is empty
|
||||
if (!value) {
|
||||
fieldValid = false;
|
||||
errorMessage = `${field.name} est requis`;
|
||||
console.error(`❌ ${field.name}: champ vide`);
|
||||
}
|
||||
// Check minimum length for text fields
|
||||
else if (field.minLength && value.length < field.minLength) {
|
||||
fieldValid = false;
|
||||
errorMessage = `${field.name} doit contenir au moins ${field.minLength} caractères`;
|
||||
console.error(`❌ ${field.name}: trop court (${value.length}/${field.minLength} caractères) - "${value}"`);
|
||||
} else {
|
||||
console.log(`✅ ${field.name}: OK - "${value}"`);
|
||||
validFields.push(field.name);
|
||||
}
|
||||
|
||||
// Visual feedback
|
||||
if (fieldValid) {
|
||||
element.classList.remove('error');
|
||||
element.classList.add('valid');
|
||||
clearFieldError(element);
|
||||
} else {
|
||||
element.classList.remove('valid');
|
||||
element.classList.add('error');
|
||||
showFieldError(element, errorMessage);
|
||||
isValid = false;
|
||||
if (!firstInvalidField) {
|
||||
firstInvalidField = element;
|
||||
}
|
||||
errors.push(errorMessage);
|
||||
}
|
||||
} else {
|
||||
console.warn(`⚠️ ${field.name}: élément non trouvé dans le DOM`);
|
||||
}
|
||||
});
|
||||
|
||||
// Check date logic (start time should be before stop time)
|
||||
console.log('📅 Vérification de la logique des dates...');
|
||||
const startElement = document.getElementById('start');
|
||||
const stopElement = document.getElementById('stop');
|
||||
|
||||
if (startElement && stopElement && startElement.value && stopElement.value) {
|
||||
const startTime = new Date(startElement.value);
|
||||
const stopTime = new Date(stopElement.value);
|
||||
|
||||
console.log(`📅 Heure début: ${startTime.toLocaleString()}`);
|
||||
console.log(`📅 Heure fin: ${stopTime.toLocaleString()}`);
|
||||
|
||||
if (startTime >= stopTime) {
|
||||
stopElement.classList.remove('valid');
|
||||
stopElement.classList.add('error');
|
||||
showFieldError(stopElement, 'L\'heure de fin doit être après l\'heure de début');
|
||||
isValid = false;
|
||||
if (!firstInvalidField) {
|
||||
firstInvalidField = stopElement;
|
||||
}
|
||||
errors.push('L\'heure de fin doit être après l\'heure de début');
|
||||
console.error(`❌ Dates: L'heure de fin (${stopTime.toLocaleString()}) doit être après l'heure de début (${startTime.toLocaleString()})`);
|
||||
} else {
|
||||
console.log(`✅ Dates: Logique correcte (durée: ${Math.round((stopTime - startTime) / 1000 / 60)} minutes)`);
|
||||
validFields.push('Logique des dates');
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ Dates: Impossible de vérifier la logique (éléments ou valeurs manquants)');
|
||||
}
|
||||
|
||||
// Check if location is set (marker exists)
|
||||
console.log('📍 Vérification de la localisation...');
|
||||
if (!marker || !marker.getLngLat()) {
|
||||
isValid = false;
|
||||
errors.push('Veuillez sélectionner une localisation sur la carte');
|
||||
console.error('❌ Localisation: Aucun marqueur placé sur la carte');
|
||||
|
||||
// Highlight map container
|
||||
const mapContainer = document.getElementById('map');
|
||||
if (mapContainer) {
|
||||
mapContainer.classList.add('error');
|
||||
if (!firstInvalidField) {
|
||||
firstInvalidField = mapContainer;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const lngLat = marker.getLngLat();
|
||||
console.log(`✅ Localisation: Marqueur placé à [${lngLat.lng.toFixed(6)}, ${lngLat.lat.toFixed(6)}]`);
|
||||
validFields.push('Localisation');
|
||||
|
||||
// Remove error highlight from map
|
||||
const mapContainer = document.getElementById('map');
|
||||
if (mapContainer) {
|
||||
mapContainer.classList.remove('error');
|
||||
}
|
||||
}
|
||||
|
||||
// Update submit button state
|
||||
if (isValid) {
|
||||
submitButton.disabled = false;
|
||||
submitButton.classList.remove('disabled');
|
||||
submitButton.textContent = 'Signaler le problème';
|
||||
console.log(`🎉 VALIDATION RÉUSSIE! Tous les champs sont valides:`);
|
||||
validFields.forEach(field => console.log(` ✅ ${field}`));
|
||||
console.log('🔓 Bouton de soumission débloqué');
|
||||
} else {
|
||||
submitButton.disabled = true;
|
||||
submitButton.classList.add('disabled');
|
||||
submitButton.textContent = `Signaler le problème (${errors.length} erreur${errors.length > 1 ? 's' : ''})`;
|
||||
console.warn(`⚠️ VALIDATION ÉCHOUÉE! ${errors.length} erreur${errors.length > 1 ? 's' : ''} trouvée${errors.length > 1 ? 's' : ''}:`);
|
||||
errors.forEach((error, index) => console.error(` ${index + 1}. ${error}`));
|
||||
console.log('🔒 Bouton de soumission bloqué');
|
||||
|
||||
if (validFields.length > 0) {
|
||||
console.log(`Champs valides (${validFields.length}):`);
|
||||
validFields.forEach(field => console.log(` ✅ ${field}`));
|
||||
}
|
||||
}
|
||||
|
||||
// Focus on first invalid field if validation was triggered by user action
|
||||
if (!isValid && firstInvalidField && document.activeElement !== firstInvalidField) {
|
||||
// Only auto-focus if the user isn't currently typing in another field
|
||||
if (document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'SELECT') {
|
||||
firstInvalidField.focus();
|
||||
console.log(`🎯 Focus placé sur le premier champ invalide: ${firstInvalidField.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.groupEnd();
|
||||
return isValid;
|
||||
}
|
||||
|
||||
|
||||
// Fill the form with predefined values based on the selected issue type
|
||||
function fillForm(issueType) {
|
||||
currentIssueType = issueType;
|
||||
|
||||
|
||||
// Get the form elements
|
||||
const whatInput = document.getElementById('what');
|
||||
const labelInput = document.getElementById('label');
|
||||
const descriptionInput = document.getElementById('description');
|
||||
|
||||
|
||||
// Set default values based on the issue type
|
||||
switch (issueType) {
|
||||
case 'pothole':
|
||||
|
@ -220,159 +590,24 @@ function fillForm(issueType) {
|
|||
break;
|
||||
// Add more cases for other issue types
|
||||
}
|
||||
|
||||
|
||||
// Scroll to the form
|
||||
document.getElementById('reportForm').scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// Handle photo upload
|
||||
function handlePhotoUpload(event) {
|
||||
const files = event.target.files;
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
// Store the files for later upload
|
||||
photoFiles = Array.from(files);
|
||||
|
||||
// Show preview of the photos
|
||||
const previewContainer = document.getElementById('photoPreview');
|
||||
if (previewContainer) {
|
||||
previewContainer.innerHTML = '';
|
||||
|
||||
photoFiles.forEach(file => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
const img = document.createElement('img');
|
||||
img.src = e.target.result;
|
||||
img.className = 'photo-preview';
|
||||
previewContainer.appendChild(img);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
previewContainer.style.display = 'flex';
|
||||
const formElement = document.getElementById('trafficForm');
|
||||
if (formElement) {
|
||||
formElement.scrollIntoView({behavior: 'smooth'});
|
||||
}
|
||||
}
|
||||
|
||||
// Submit the report
|
||||
async function submitReport(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Show loading message
|
||||
const resultElement = document.getElementById('result');
|
||||
resultElement.textContent = 'Submitting report...';
|
||||
resultElement.className = '';
|
||||
resultElement.style.display = 'block';
|
||||
|
||||
try {
|
||||
// Check if we have coordinates
|
||||
if (!currentPosition) {
|
||||
throw new Error('Please select a location on the map');
|
||||
}
|
||||
|
||||
// Get form values
|
||||
const formData = new FormData(event.target);
|
||||
const eventData = {
|
||||
type: formData.get('type') || 'unscheduled',
|
||||
what: formData.get('what'),
|
||||
label: formData.get('label'),
|
||||
description: formData.get('description'),
|
||||
start: new Date(formData.get('start')).toISOString(),
|
||||
stop: new Date(formData.get('stop')).toISOString(),
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: currentPosition
|
||||
}
|
||||
};
|
||||
|
||||
// Add username if authenticated
|
||||
const osmUsername = document.getElementById('osmUsername');
|
||||
if (osmUsername && osmUsername.value) {
|
||||
eventData.source = {
|
||||
name: 'OpenStreetMap user',
|
||||
id: osmUsername.value
|
||||
};
|
||||
}
|
||||
|
||||
// Upload photos to Panoramax if available
|
||||
let photoUrls = [];
|
||||
if (photoFiles.length > 0 && panoramaxUploadUrl && panoramaxToken) {
|
||||
photoUrls = await uploadPhotos(photoFiles);
|
||||
if (photoUrls.length > 0) {
|
||||
eventData.media = photoUrls.map(url => ({
|
||||
type: 'image',
|
||||
url: url
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Submit the event to the API
|
||||
const response = await fetch('/event', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(eventData)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(errorText || response.statusText);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Show success message
|
||||
resultElement.textContent = `Report submitted successfully! Event ID: ${data.id}`;
|
||||
resultElement.className = 'success';
|
||||
|
||||
// Reset the form
|
||||
event.target.reset();
|
||||
photoFiles = [];
|
||||
const previewContainer = document.getElementById('photoPreview');
|
||||
if (previewContainer) {
|
||||
previewContainer.innerHTML = '';
|
||||
previewContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
// Initialize the form again
|
||||
initForm();
|
||||
} catch (error) {
|
||||
// Show error message
|
||||
resultElement.textContent = `Error: ${error.message}`;
|
||||
resultElement.className = 'error';
|
||||
}
|
||||
}
|
||||
// Show error message for a field
|
||||
function showFieldError(element, message) {
|
||||
// Remove existing error message
|
||||
clearFieldError(element);
|
||||
|
||||
// Upload photos to Panoramax
|
||||
async function uploadPhotos(files) {
|
||||
const urls = [];
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const uploadData = new FormData();
|
||||
uploadData.append('file', file);
|
||||
|
||||
const response = await fetch(panoramaxUploadUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Token ${panoramaxToken}`
|
||||
},
|
||||
body: uploadData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Failed to upload photo:', await response.text());
|
||||
continue;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (data.url) {
|
||||
urls.push(data.url);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error uploading photo:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
// Create error message element
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'field-error';
|
||||
errorDiv.textContent = message;
|
||||
|
||||
// Insert error message after the field
|
||||
element.parentNode.insertBefore(errorDiv, element.nextSibling);
|
||||
}
|
||||
|
|
|
@ -1,45 +1,348 @@
|
|||
{% extends "layout.html" %}
|
||||
|
||||
{% block title %}Event {{ id }} - OpenEventDatabase{% endblock %}
|
||||
{% block title %}Événement {{ properties.label|default(id) }} - OpenEventDatabase{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<style>
|
||||
#map { width:100%; height: 360px; border:1px solid #ddd; border-radius:4px; }
|
||||
table { width:100%; border-collapse: collapse; margin-top:12px; }
|
||||
th, td { padding: 6px 8px; border-bottom: 1px solid #eee; text-align:left; }
|
||||
th { background:#f9fafb; }
|
||||
.event-header {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
margin: 0 0 10px 0;
|
||||
color: #333;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.event-subtitle {
|
||||
color: #666;
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#map {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.properties-table {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
background: #f8f9fa;
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.table-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f9fafb;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
td {
|
||||
word-wrap: break-word;
|
||||
max-width: 0;
|
||||
}
|
||||
|
||||
.what-link {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
background: #0078ff;
|
||||
color: white !important;
|
||||
text-decoration: none;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.what-link:hover {
|
||||
background: #0056b3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: #0078ff;
|
||||
text-decoration: none;
|
||||
margin-right: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #6c757d;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}Évènement {{ id }}{% endblock %}
|
||||
{% block header %}
|
||||
<div class="event-header">
|
||||
<h1 class="event-title">{{ properties.label|default('Événement sans titre') }}</h1>
|
||||
<p class="event-subtitle">ID: {{ id }}</p>
|
||||
<div class="nav-links">
|
||||
<a href="/demo"><i class="fas fa-home"></i> Accueil</a>
|
||||
<a href="/demo/view-events"><i class="fas fa-table"></i> Tous les événements</a>
|
||||
<a href="https://api.openeventdatabase.org/event/{{ id }}" target="_blank"><i class="fas fa-code"></i> API JSON</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="map"></div>
|
||||
<table>
|
||||
<thead><tr><th>Clé</th><th>Valeur</th></tr></thead>
|
||||
<tbody>
|
||||
{% for key, value in properties.items()|sort %}
|
||||
<tr>
|
||||
<td>{{ key }}</td>
|
||||
<td>{{ value }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="loading-map" class="loading">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
Chargement de la carte...
|
||||
</div>
|
||||
<div id="map-error" class="error-message" style="display: none;">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
Erreur lors du chargement de la carte
|
||||
</div>
|
||||
<div id="map" style="display: none;"></div>
|
||||
|
||||
<div class="properties-table">
|
||||
<div class="table-header">
|
||||
<h2><i class="fas fa-info-circle"></i> Propriétés de l'événement</h2>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Propriété</th>
|
||||
<th>Valeur</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for key, value in properties.items()|sort %}
|
||||
<tr>
|
||||
<td><strong>{{ key }}</strong></td>
|
||||
<td>
|
||||
{% if key == 'what' %}
|
||||
<a href="/demo/map-by-what/{{ value }}" class="what-link">
|
||||
<i class="fas fa-tag"></i> {{ value }}
|
||||
</a>
|
||||
<small style="display: block; margin-top: 5px; color: #6c757d;">
|
||||
Cliquez pour voir tous les événements de ce type
|
||||
</small>
|
||||
{% elif key in ['start', 'stop', 'createdate', 'lastupdate'] and value %}
|
||||
{{ value }}
|
||||
<small style="display: block; color: #6c757d;">
|
||||
<script>
|
||||
document.write(formatDate('{{ value }}'));
|
||||
</script>
|
||||
</small>
|
||||
{% elif key == 'url' and value %}
|
||||
<a href="{{ value }}" target="_blank" style="color: #0078ff;">
|
||||
{{ value }}
|
||||
<i class="fas fa-external-link-alt" style="font-size: 0.8em;"></i>
|
||||
</a>
|
||||
{% elif value is string and value.startswith('http') %}
|
||||
<a href="{{ value }}" target="_blank" style="color: #0078ff;">
|
||||
{{ value }}
|
||||
<i class="fas fa-external-link-alt" style="font-size: 0.8em;"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
{{ value|safe if value else '-' }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const f = JSON.parse('{{ feature_json|safe }}');
|
||||
const map = new maplibregl.Map({
|
||||
container: 'map',
|
||||
style: 'https://tiles.openfreemap.org/styles/liberty',
|
||||
center: f.geometry && f.geometry.coordinates ? f.geometry.coordinates : [2.3522,48.8566],
|
||||
zoom: 12
|
||||
});
|
||||
map.addControl(new maplibregl.NavigationControl());
|
||||
if (f.geometry && f.geometry.type === 'Point') {
|
||||
new maplibregl.Marker().setLngLat(f.geometry.coordinates).addTo(map);
|
||||
// Fonction pour formater les dates
|
||||
function formatDate(dateString) {
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('fr-FR', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
} catch (e) {
|
||||
return dateString;
|
||||
}
|
||||
}
|
||||
|
||||
// Formater les dates dans le tableau après le chargement de la page
|
||||
function formatDatesInTable() {
|
||||
const dateElements = document.querySelectorAll('.date-display[data-date]');
|
||||
dateElements.forEach(element => {
|
||||
const dateValue = element.getAttribute('data-date');
|
||||
if (dateValue) {
|
||||
const formattedDate = formatDate(dateValue);
|
||||
element.textContent = formattedDate;
|
||||
element.title = dateValue; // Garder la date originale en tooltip
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Gestion des erreurs et logging
|
||||
function logError(message, error) {
|
||||
console.error(message, error);
|
||||
document.getElementById('loading-map').style.display = 'none';
|
||||
document.getElementById('map-error').style.display = 'block';
|
||||
document.getElementById('map-error').innerHTML = `
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
${message}: ${error.message || error}
|
||||
`;
|
||||
}
|
||||
|
||||
// Formater les dates dans le tableau
|
||||
formatDatesInTable();
|
||||
|
||||
// Initialisation de la carte
|
||||
try {
|
||||
console.log('🗺️ Initialisation de la carte...');
|
||||
|
||||
// Parser les données de l'événement de manière sécurisée
|
||||
let eventFeature;
|
||||
try {
|
||||
const featureJson = '{{ feature_json|safe }}';
|
||||
console.log('📄 JSON reçu:', featureJson);
|
||||
eventFeature = JSON.parse(featureJson);
|
||||
console.log('✅ Données de l\'événement parsées:', eventFeature);
|
||||
} catch (parseError) {
|
||||
throw new Error(`Impossible de parser les données JSON: ${parseError.message}`);
|
||||
}
|
||||
|
||||
// Vérifier la structure des données
|
||||
if (!eventFeature) {
|
||||
throw new Error('Aucune donnée d\'événement trouvée');
|
||||
}
|
||||
|
||||
// Déterminer les coordonnées pour la carte
|
||||
let center = [2.3522, 48.8566]; // Paris par défaut
|
||||
let hasValidGeometry = false;
|
||||
|
||||
if (eventFeature.geometry &&
|
||||
eventFeature.geometry.type === 'Point' &&
|
||||
eventFeature.geometry.coordinates &&
|
||||
Array.isArray(eventFeature.geometry.coordinates) &&
|
||||
eventFeature.geometry.coordinates.length === 2) {
|
||||
|
||||
const coords = eventFeature.geometry.coordinates;
|
||||
// Vérifier que les coordonnées sont des nombres valides
|
||||
if (typeof coords[0] === 'number' && typeof coords[1] === 'number' &&
|
||||
!isNaN(coords[0]) && !isNaN(coords[1])) {
|
||||
center = coords;
|
||||
hasValidGeometry = true;
|
||||
console.log('📍 Coordonnées trouvées:', center);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasValidGeometry) {
|
||||
console.warn('⚠️ Pas de coordonnées valides, utilisation du centre par défaut');
|
||||
}
|
||||
|
||||
// Créer la carte
|
||||
const map = new maplibregl.Map({
|
||||
container: 'map',
|
||||
style: 'https://tiles.openfreemap.org/styles/liberty',
|
||||
center: center,
|
||||
zoom: hasValidGeometry ? 14 : 6
|
||||
});
|
||||
|
||||
// Ajouter les contrôles
|
||||
map.addControl(new maplibregl.NavigationControl());
|
||||
map.addControl(new maplibregl.AttributionControl({
|
||||
customAttribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}));
|
||||
|
||||
// Quand la carte est chargée
|
||||
map.on('load', function() {
|
||||
console.log('✅ Carte chargée avec succès');
|
||||
document.getElementById('loading-map').style.display = 'none';
|
||||
document.getElementById('map').style.display = 'block';
|
||||
|
||||
// Ajouter le marqueur si on a des coordonnées valides
|
||||
if (hasValidGeometry) {
|
||||
const marker = new maplibregl.Marker({
|
||||
color: '#0078ff'
|
||||
})
|
||||
.setLngLat(center)
|
||||
.addTo(map);
|
||||
|
||||
// Créer une popup avec les informations de l'événement
|
||||
const properties = eventFeature.properties || {};
|
||||
const popupContent = `
|
||||
<div style="max-width: 250px;">
|
||||
<h3 style="margin: 0 0 10px 0;">${properties.label || 'Événement'}</h3>
|
||||
${properties.what ? `<p><strong>Type:</strong> ${properties.what}</p>` : ''}
|
||||
${properties.start ? `<p><strong>Début:</strong> ${formatDate(properties.start)}</p>` : ''}
|
||||
${properties.where ? `<p><strong>Lieu:</strong> ${properties.where}</p>` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
const popup = new maplibregl.Popup({ offset: 25 })
|
||||
.setHTML(popupContent);
|
||||
|
||||
marker.setPopup(popup);
|
||||
|
||||
console.log('📍 Marqueur ajouté à la carte');
|
||||
}
|
||||
});
|
||||
|
||||
// Gestion des erreurs de carte
|
||||
map.on('error', function(e) {
|
||||
logError('Erreur lors du chargement de la carte', e.error || e);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logError('Erreur lors de l\'initialisation de la carte', error);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<!-- Common CSS -->
|
||||
<link rel="stylesheet" href="/static/demo_styles.css">
|
||||
|
||||
<link rel="icon" type="image/png" href="/static/oedb.png">
|
||||
<!-- Page-specific CSS -->
|
||||
{% block css %}{% endblock %}
|
||||
|
||||
|
|
533
oedb/resources/demo/templates/map_by_what_type.html
Normal file
533
oedb/resources/demo/templates/map_by_what_type.html
Normal file
|
@ -0,0 +1,533 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Événements {{ event_type_label }} - OpenEventDatabase</title>
|
||||
<link rel="icon" type="image/png" href="/static/oedb.png">
|
||||
|
||||
<!-- MapLibre GL JS -->
|
||||
<script src="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.js"></script>
|
||||
<link href="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.css" rel="stylesheet" />
|
||||
|
||||
<!-- Font Awesome -->
|
||||
<script defer src="https://use.fontawesome.com/releases/v5.15.4/js/all.js"></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: #0078ff;
|
||||
text-decoration: none;
|
||||
margin-right: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.content-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
height: calc(100vh - 200px);
|
||||
}
|
||||
|
||||
.map-container, .table-container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #eee;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.table-header h2 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 0 3px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.legend-past { background-color: #6c757d; }
|
||||
.legend-current { background-color: #28a745; }
|
||||
.legend-future { background-color: #007bff; }
|
||||
|
||||
.table-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.events-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.events-table th,
|
||||
.events-table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.events-table th {
|
||||
background: #f8f9fa;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.events-table tr:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.event-title a {
|
||||
color: #0078ff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.event-title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.event-date {
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.event-status {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-past {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-current {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-future {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.error {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.content-grid {
|
||||
grid-template-columns: 1fr;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
height: 500px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>
|
||||
<i class="fas fa-map-marked-alt"></i>
|
||||
Événements {{ event_type_label }}
|
||||
</h1>
|
||||
<p>Visualisation et liste détaillée des événements de type "{{ event_type }}"</p>
|
||||
|
||||
<div class="nav-links">
|
||||
<a href="/demo"><i class="fas fa-home"></i> Accueil</a>
|
||||
<a href="/demo/map-by-what"><i class="fas fa-list"></i> Tous les types</a>
|
||||
<a href="/demo/view-events"><i class="fas fa-table"></i> Vue tableau</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-grid">
|
||||
<div class="map-container">
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<div class="table-header">
|
||||
<h2><i class="fas fa-table"></i> Liste des événements</h2>
|
||||
|
||||
<div class="legend">
|
||||
<div class="legend-item">
|
||||
<div class="legend-color legend-past"></div>
|
||||
<span>Passés</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color legend-current"></div>
|
||||
<span>En cours</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-color legend-future"></div>
|
||||
<span>À venir</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<span id="total-events">Total: 0</span>
|
||||
<span id="past-events">Passés: 0</span>
|
||||
<span id="current-events">En cours: 0</span>
|
||||
<span id="future-events">À venir: 0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-content">
|
||||
<div id="loading" class="loading">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
Chargement des événements...
|
||||
</div>
|
||||
|
||||
<div id="error" class="error" style="display: none;">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
Erreur lors du chargement des événements
|
||||
</div>
|
||||
|
||||
<table class="events-table" id="events-table" style="display: none;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Statut</th>
|
||||
<th>Événement</th>
|
||||
<th>Dates</th>
|
||||
<th>Lieu</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="events-tbody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Configuration
|
||||
const eventType = '{{ event_type }}';
|
||||
let map;
|
||||
let markers = [];
|
||||
let eventsData = [];
|
||||
|
||||
// Initialiser la carte
|
||||
function initMap() {
|
||||
map = new maplibregl.Map({
|
||||
container: 'map',
|
||||
style: 'https://tiles.openfreemap.org/styles/liberty',
|
||||
center: [2.3522, 48.8566], // Paris par défaut
|
||||
zoom: 6
|
||||
});
|
||||
|
||||
map.addControl(new maplibregl.NavigationControl());
|
||||
map.addControl(new maplibregl.AttributionControl({
|
||||
customAttribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}));
|
||||
}
|
||||
|
||||
// Déterminer le statut temporel d'un événement
|
||||
function getEventStatus(start, stop) {
|
||||
const now = new Date();
|
||||
const startDate = new Date(start);
|
||||
const stopDate = new Date(stop);
|
||||
|
||||
if (now < startDate) {
|
||||
return 'future';
|
||||
} else if (now > stopDate) {
|
||||
return 'past';
|
||||
} else {
|
||||
return 'current';
|
||||
}
|
||||
}
|
||||
|
||||
// Obtenir la couleur selon le statut
|
||||
function getStatusColor(status) {
|
||||
switch (status) {
|
||||
case 'past': return '#6c757d';
|
||||
case 'current': return '#28a745';
|
||||
case 'future': return '#007bff';
|
||||
default: return '#6c757d';
|
||||
}
|
||||
}
|
||||
|
||||
// Obtenir le label selon le statut
|
||||
function getStatusLabel(status) {
|
||||
switch (status) {
|
||||
case 'past': return 'Passé';
|
||||
case 'current': return 'En cours';
|
||||
case 'future': return 'À venir';
|
||||
default: return 'Inconnu';
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter les marqueurs sur la carte
|
||||
function addMarkersToMap(events) {
|
||||
// Supprimer les anciens marqueurs
|
||||
markers.forEach(marker => marker.remove());
|
||||
markers = [];
|
||||
|
||||
const bounds = new maplibregl.LngLatBounds();
|
||||
|
||||
events.forEach(event => {
|
||||
if (!event.geometry || !event.geometry.coordinates) return;
|
||||
|
||||
const coords = event.geometry.coordinates;
|
||||
const properties = event.properties || {};
|
||||
const status = getEventStatus(properties.start, properties.stop);
|
||||
const color = getStatusColor(status);
|
||||
|
||||
// Créer l'élément marqueur
|
||||
const el = document.createElement('div');
|
||||
el.style.width = '20px';
|
||||
el.style.height = '20px';
|
||||
el.style.borderRadius = '50%';
|
||||
el.style.backgroundColor = color;
|
||||
el.style.border = '3px solid white';
|
||||
el.style.boxShadow = '0 0 5px rgba(0,0,0,0.3)';
|
||||
el.style.cursor = 'pointer';
|
||||
|
||||
// Créer le contenu de la popup
|
||||
const popupContent = `
|
||||
<div style="max-width: 300px;">
|
||||
<h3 style="margin: 0 0 10px 0;">${properties.label || 'Événement'}</h3>
|
||||
<p><strong>Statut:</strong> <span class="event-status status-${status}">${getStatusLabel(status)}</span></p>
|
||||
<p><strong>Début:</strong> ${new Date(properties.start).toLocaleString('fr-FR')}</p>
|
||||
<p><strong>Fin:</strong> ${new Date(properties.stop).toLocaleString('fr-FR')}</p>
|
||||
${properties.where ? `<p><strong>Lieu:</strong> ${properties.where}</p>` : ''}
|
||||
${properties.description ? `<p><strong>Description:</strong> ${properties.description}</p>` : ''}
|
||||
<div style="margin-top: 15px;">
|
||||
<a href="/demo/by_id/${properties.id}" style="color: #0078ff; font-weight: bold;">Voir les détails</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const popup = new maplibregl.Popup({ offset: 25 })
|
||||
.setHTML(popupContent);
|
||||
|
||||
const marker = new maplibregl.Marker(el)
|
||||
.setLngLat(coords)
|
||||
.setPopup(popup)
|
||||
.addTo(map);
|
||||
|
||||
markers.push(marker);
|
||||
bounds.extend(coords);
|
||||
});
|
||||
|
||||
// Ajuster la vue pour englober tous les marqueurs
|
||||
if (events.length > 0) {
|
||||
map.fitBounds(bounds, { padding: 50 });
|
||||
}
|
||||
}
|
||||
|
||||
// Remplir le tableau
|
||||
function populateTable(events) {
|
||||
const tbody = document.getElementById('events-tbody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
// Trier par date (futurs d'abord, puis en cours, puis passés)
|
||||
const sortedEvents = events.sort((a, b) => {
|
||||
const statusA = getEventStatus(a.properties.start, a.properties.stop);
|
||||
const statusB = getEventStatus(b.properties.start, b.properties.stop);
|
||||
|
||||
const statusOrder = { 'future': 0, 'current': 1, 'past': 2 };
|
||||
|
||||
if (statusOrder[statusA] !== statusOrder[statusB]) {
|
||||
return statusOrder[statusA] - statusOrder[statusB];
|
||||
}
|
||||
|
||||
return new Date(a.properties.start) - new Date(b.properties.start);
|
||||
});
|
||||
|
||||
sortedEvents.forEach(event => {
|
||||
const props = event.properties || {};
|
||||
const status = getEventStatus(props.start, props.stop);
|
||||
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>
|
||||
<span class="event-status status-${status}">${getStatusLabel(status)}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="event-title">
|
||||
<a href="/demo/by_id/${props.id}">${props.label || 'Événement sans titre'}</a>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="event-date">
|
||||
<strong>Début:</strong> ${new Date(props.start).toLocaleDateString('fr-FR')}<br>
|
||||
<strong>Fin:</strong> ${new Date(props.stop).toLocaleDateString('fr-FR')}
|
||||
</div>
|
||||
</td>
|
||||
<td>${props.where || '-'}</td>
|
||||
<td>
|
||||
<a href="/demo/by_id/${props.id}" style="color: #0078ff; text-decoration: none;">
|
||||
<i class="fas fa-eye"></i> Détails
|
||||
</a>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Mettre à jour les statistiques
|
||||
function updateStats(events) {
|
||||
const stats = {
|
||||
total: events.length,
|
||||
past: 0,
|
||||
current: 0,
|
||||
future: 0
|
||||
};
|
||||
|
||||
events.forEach(event => {
|
||||
const status = getEventStatus(event.properties.start, event.properties.stop);
|
||||
stats[status]++;
|
||||
});
|
||||
|
||||
document.getElementById('total-events').textContent = `Total: ${stats.total}`;
|
||||
document.getElementById('past-events').textContent = `Passés: ${stats.past}`;
|
||||
document.getElementById('current-events').textContent = `En cours: ${stats.current}`;
|
||||
document.getElementById('future-events').textContent = `À venir: ${stats.future}`;
|
||||
}
|
||||
|
||||
// Charger les événements
|
||||
async function loadEvents() {
|
||||
try {
|
||||
const response = await fetch(`https://api.openeventdatabase.org/event?what=${eventType}&limit=1000`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
eventsData = data.features || [];
|
||||
|
||||
// Masquer le loading
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
|
||||
if (eventsData.length === 0) {
|
||||
document.getElementById('error').style.display = 'block';
|
||||
document.getElementById('error').innerHTML = `
|
||||
<i class="fas fa-info-circle"></i>
|
||||
Aucun événement trouvé pour le type "${eventType}"
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Afficher le tableau
|
||||
document.getElementById('events-table').style.display = 'table';
|
||||
|
||||
// Peupler la carte et le tableau
|
||||
addMarkersToMap(eventsData);
|
||||
populateTable(eventsData);
|
||||
updateStats(eventsData);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des événements:', error);
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
document.getElementById('error').style.display = 'block';
|
||||
document.getElementById('error').innerHTML = `
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
Erreur lors du chargement: ${error.message}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisation
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initMap();
|
||||
loadEvents();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -4,13 +4,14 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Report Traffic Jam - OpenEventDatabase</title>
|
||||
<link rel="icon" type="image/png" href="/static/oedb.png">
|
||||
<link rel="icon" type="image/png" href="/static/oedb.png">
|
||||
<script src="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.js"></script>
|
||||
<link href="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="/static/demo_styles.css">
|
||||
<script defer src="https://use.fontawesome.com/releases/v5.15.4/js/all.js"></script>
|
||||
<link rel="stylesheet" href="/static/traffic.css">
|
||||
<script src="/static/demo_auth.js"></script>
|
||||
<script src="/static/traffic.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
|
|
@ -260,7 +260,7 @@
|
|||
<div class="note">Cliquez sur la carte pour définir la localisation du problème ou utilisez le bouton "Obtenir ma position actuelle"</div>
|
||||
</div>
|
||||
|
||||
<button id="report_issue_button" type="submit" disabled>Signaler le problème</button>
|
||||
<button id="report_issue_button" type="submit">Signaler le problème</button>
|
||||
</form>
|
||||
|
||||
<div id="result"></div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue