up pages
This commit is contained in:
parent
2bb77d2300
commit
98c40b2447
16 changed files with 1836 additions and 361 deletions
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue