oedb-backend/oedb/resources/demo/static/traffic.js
2025-09-27 15:57:59 +02:00

937 lines
34 KiB
JavaScript

// Traffic.js - JavaScript for the traffic reporting page
// Global variables
let map;
let marker;
let currentPosition;
let currentIssueType = null;
let photoFiles = [];
let panoramaxUploadUrl = '';
let panoramaxToken = '';
// Initialize the map when the page loads
document.addEventListener('DOMContentLoaded', function () {
initMap();
initTabs();
initForm();
setupFormValidation();
// Get Panoramax configuration
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);
}
// 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
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(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');
});
});
}
// 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;
}
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('reportForm');
if (reportForm) {
reportForm.addEventListener('submit', submitReport);
} else {
console.warn('Report form not found in DOM');
}
}
// 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)}`;
}
// 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();
// Disable validation during submission
window.validationEnabled = false;
console.log('🔒 Validation désactivée pendant la soumission');
// Show loading message
const resultElement = document.getElementById('result');
showFixedResult('Création de l\'événement en cours...', 'loading');
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 OEDB API
const response = await fetch('https://api.openeventdatabase.org/event', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'Feature',
geometry: eventData.geometry,
properties: {
type: eventData.type,
what: eventData.what,
label: eventData.label,
description: eventData.description,
start: eventData.start,
stop: eventData.stop,
...(eventData.source && { source: eventData.source }),
...(eventData.media && { media: eventData.media })
}
})
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(errorText || response.statusText);
}
const data = await response.json();
// Extract event ID from response (handle different response structures)
let eventId = null;
if (data.properties && data.properties.id) {
eventId = data.properties.id;
} else if (data.id) {
eventId = data.id;
} else if (typeof data === 'string') {
eventId = data;
}
console.log('📅 Réponse API complète:', data);
console.log('🆔 ID événement extrait:', eventId);
// Show success message with links
let successMessage = `✅ Événement créé avec succès !`;
if (eventId) {
successMessage += `<br>ID: ${eventId}<br><br>
<a href="/demo/by_id/${eventId}" style="color: #0078ff; text-decoration: none; font-weight: bold;">
<i class="fas fa-eye"></i> Voir l'événement
</a>
|
<a href="/demo" style="color: #0078ff; text-decoration: none; font-weight: bold;">
<i class="fas fa-map"></i> Retour à la carte
</a>`;
} else {
successMessage += `<br><small>ID d'événement non disponible</small><br><br>
<a href="/demo" style="color: #0078ff; text-decoration: none; font-weight: bold;">
<i class="fas fa-map"></i> Retour à la carte
</a>`;
}
showFixedResult(successMessage, 'success');
// Reset the form
event.target.reset();
photoFiles = [];
const previewContainer = document.getElementById('photoPreview');
if (previewContainer) {
previewContainer.innerHTML = '';
previewContainer.style.display = 'none';
}
// Remove marker from map
if (marker) {
marker.remove();
marker = null;
currentPosition = null;
}
// Initialize the form again
initForm();
// Re-validate form to disable submit button
validateForm();
} catch (error) {
console.error('❌ Erreur lors de la création d\'événement:', error);
// Show error message
showFixedResult(
`❌ Erreur lors de la création de l'événement<br><small>${error.message}</small>`,
'error'
);
} finally {
// Re-enable validation after submission is complete
setTimeout(() => {
window.validationEnabled = true;
console.log('🔓 Validation réactivée');
}, 1000);
}
}
// 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('reportForm');
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() {
// Skip validation if disabled (during submission)
if (window.validationEnabled === false) {
console.log('⏸️ Validation ignorée (désactivée)');
return true;
}
const form = document.getElementById('reportForm');
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;
console.log('🎯 Remplissage formulaire pour type:', issueType);
// Get the form elements
const whatInput = document.getElementById('what');
const labelInput = document.getElementById('label');
const descriptionInput = document.getElementById('description');
const typeSelect = document.getElementById('type');
if (!whatInput || !labelInput || !descriptionInput) {
console.error('❌ Éléments de formulaire non trouvés');
return;
}
// Set default values based on the issue type
switch (issueType) {
// === ROAD TAB ===
case 'pothole':
whatInput.value = 'traffic.obstacle';
labelInput.value = 'Nid de poule';
descriptionInput.value = 'Nid de poule sur la chaussée';
typeSelect.value = 'unscheduled';
break;
case 'obstacle':
whatInput.value = 'traffic.obstacle';
labelInput.value = 'Obstacle sur la route';
descriptionInput.value = 'Obstacle sur la chaussée';
typeSelect.value = 'unscheduled';
break;
case 'vehicle':
whatInput.value = 'traffic.obstacle';
labelInput.value = 'Véhicule en panne';
descriptionInput.value = 'Véhicule arrêté sur le bas côté de la route';
typeSelect.value = 'unscheduled';
break;
case 'danger':
whatInput.value = 'traffic.incident';
labelInput.value = 'Danger sur la route';
descriptionInput.value = 'Situation dangereuse sur la route';
typeSelect.value = 'unscheduled';
break;
case 'accident':
whatInput.value = 'traffic.accident';
labelInput.value = 'Accident de la route';
descriptionInput.value = 'Accident de la circulation';
typeSelect.value = 'unscheduled';
break;
case 'flooded_road':
whatInput.value = 'traffic.obstacle';
labelInput.value = 'Route inondée';
descriptionInput.value = 'Route inondée, circulation difficile ou impossible';
typeSelect.value = 'unscheduled';
break;
case 'roadwork':
whatInput.value = 'traffic.roadwork';
labelInput.value = 'Travaux routiers';
descriptionInput.value = 'Travaux en cours sur la chaussée';
typeSelect.value = 'scheduled';
break;
case 'traffic_jam':
whatInput.value = 'traffic.partially_closed';
labelInput.value = 'Embouteillage';
descriptionInput.value = 'Circulation dense, embouteillage';
typeSelect.value = 'unscheduled';
break;
// === RAIL TAB ===
case 'rail_delay':
whatInput.value = 'transport.rail.delay';
labelInput.value = 'Retard de train';
descriptionInput.value = 'Retard important sur la ligne ferroviaire';
typeSelect.value = 'unscheduled';
break;
case 'rail_cancellation':
whatInput.value = 'transport.rail.cancellation';
labelInput.value = 'Train annulé';
descriptionInput.value = 'Annulation de service ferroviaire';
typeSelect.value = 'unscheduled';
break;
case 'rail_works':
whatInput.value = 'transport.rail.works';
labelInput.value = 'Travaux ferroviaires';
descriptionInput.value = 'Travaux de maintenance sur la voie ferrée';
typeSelect.value = 'scheduled';
break;
case 'rail_incident':
whatInput.value = 'transport.rail.incident';
labelInput.value = 'Incident ferroviaire';
descriptionInput.value = 'Incident technique sur la ligne';
typeSelect.value = 'unscheduled';
break;
// === WEATHER TAB ===
case 'weather_storm':
whatInput.value = 'weather.storm';
labelInput.value = 'Orage';
descriptionInput.value = 'Conditions météorologiques orageuses';
typeSelect.value = 'forecast';
break;
case 'weather_flood':
whatInput.value = 'weather.flood';
labelInput.value = 'Inondation';
descriptionInput.value = 'Inondation affectant la circulation';
typeSelect.value = 'unscheduled';
break;
case 'weather_snow':
whatInput.value = 'weather.snow';
labelInput.value = 'Chutes de neige';
descriptionInput.value = 'Conditions de neige affectant la circulation';
typeSelect.value = 'forecast';
break;
case 'weather_fog':
whatInput.value = 'weather.fog';
labelInput.value = 'Brouillard dense';
descriptionInput.value = 'Brouillard réduisant la visibilité';
typeSelect.value = 'forecast';
break;
case 'weather_heat':
whatInput.value = 'weather.heat';
labelInput.value = 'Canicule';
descriptionInput.value = 'Températures extrêmes, risques de déformation de chaussée';
typeSelect.value = 'forecast';
break;
// === EMERGENCY TAB ===
case 'emergency_fire':
whatInput.value = 'emergency.fire';
labelInput.value = 'Incendie';
descriptionInput.value = 'Incendie nécessitant une intervention des secours';
typeSelect.value = 'unscheduled';
break;
case 'emergency_medical':
whatInput.value = 'emergency.medical';
labelInput.value = 'Urgence médicale';
descriptionInput.value = 'Intervention médicale d\'urgence';
typeSelect.value = 'unscheduled';
break;
case 'emergency_police':
whatInput.value = 'emergency.police';
labelInput.value = 'Intervention police';
descriptionInput.value = 'Intervention des forces de l\'ordre';
typeSelect.value = 'unscheduled';
break;
case 'emergency_evacuation':
whatInput.value = 'emergency.evacuation';
labelInput.value = 'Évacuation';
descriptionInput.value = 'Évacuation de zone pour raisons de sécurité';
typeSelect.value = 'unscheduled';
break;
// === CIVIC TAB ===
case 'civic_bike_lane':
whatInput.value = 'civic.infrastructure.bike';
labelInput.value = 'Problème piste cyclable';
descriptionInput.value = 'Dégradation ou obstacle sur piste cyclable';
typeSelect.value = 'unscheduled';
break;
case 'civic_sidewalk':
whatInput.value = 'civic.infrastructure.pedestrian';
labelInput.value = 'Problème trottoir';
descriptionInput.value = 'Dégradation ou obstacle sur trottoir';
typeSelect.value = 'unscheduled';
break;
case 'civic_lighting':
whatInput.value = 'civic.infrastructure.lighting';
labelInput.value = 'Éclairage défectueux';
descriptionInput.value = 'Éclairage public en panne ou défaillant';
typeSelect.value = 'unscheduled';
break;
case 'civic_garbage':
whatInput.value = 'civic.sanitation.garbage';
labelInput.value = 'Problème de déchets';
descriptionInput.value = 'Déchets non collectés ou dépôt sauvage';
typeSelect.value = 'unscheduled';
break;
// === DEFAULT CASE ===
default:
console.warn('⚠️ Type d\'événement non reconnu:', issueType);
whatInput.value = 'traffic.incident';
labelInput.value = 'Incident de circulation';
descriptionInput.value = 'Incident affectant la circulation';
typeSelect.value = 'unscheduled';
}
console.log('✅ Formulaire rempli:', {
what: whatInput.value,
label: labelInput.value,
type: typeSelect.value
});
// Trigger validation after form is filled
validateForm();
// Scroll to the form
const formElement = document.getElementById('reportForm');
if (formElement) {
formElement.scrollIntoView({behavior: 'smooth'});
// Focus on first empty field or description for additional details
setTimeout(() => {
const descTextarea = document.getElementById('description');
if (descTextarea) {
descTextarea.focus();
// Position cursor at end of existing text
descTextarea.setSelectionRange(descTextarea.value.length, descTextarea.value.length);
}
}, 500);
}
}
// Show error message for a field
function showFieldError(element, message) {
// Remove existing error message
clearFieldError(element);
// 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);
}
// Show fixed result message at bottom of screen
function showFixedResult(message, type) {
let resultElement = document.getElementById('result');
if (!resultElement) {
// Create result element if it doesn't exist
resultElement = document.createElement('div');
resultElement.id = 'result';
document.body.appendChild(resultElement);
}
// Style the element as fixed at bottom
resultElement.style.cssText = `
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 10000;
max-width: 90%;
width: auto;
min-width: 300px;
padding: 15px 45px 15px 15px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
font-family: Arial, sans-serif;
font-size: 14px;
line-height: 1.4;
display: block;
`;
// Set colors based on type
let bgColor, textColor, borderColor;
switch (type) {
case 'success':
bgColor = '#d4edda';
textColor = '#155724';
borderColor = '#c3e6cb';
break;
case 'error':
bgColor = '#f8d7da';
textColor = '#721c24';
borderColor = '#f5c6cb';
break;
case 'loading':
bgColor = '#d1ecf1';
textColor = '#0c5460';
borderColor = '#bee5eb';
break;
default:
bgColor = '#e2e3e5';
textColor = '#383d41';
borderColor = '#d6d8db';
}
resultElement.style.backgroundColor = bgColor;
resultElement.style.color = textColor;
resultElement.style.border = `1px solid ${borderColor}`;
// Add close button
const closeButton = document.createElement('span');
closeButton.innerHTML = '&times;';
closeButton.style.cssText = `
position: absolute;
top: 8px;
right: 12px;
font-size: 20px;
font-weight: bold;
cursor: pointer;
color: ${textColor};
opacity: 0.7;
line-height: 1;
`;
closeButton.addEventListener('click', function() {
resultElement.style.display = 'none';
});
closeButton.addEventListener('mouseenter', function() {
this.style.opacity = '1';
});
closeButton.addEventListener('mouseleave', function() {
this.style.opacity = '0.7';
});
// Set message content
resultElement.innerHTML = message;
resultElement.appendChild(closeButton);
// Auto-hide loading messages after 10 seconds
if (type === 'loading') {
setTimeout(() => {
if (resultElement.style.display !== 'none') {
resultElement.style.display = 'none';
}
}, 10000);
}
console.log(`📢 Message fixe affiché (${type}):`, message);
}