| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  | <!DOCTYPE html> | 
					
						
							|  |  |  | <html lang="en"> | 
					
						
							|  |  |  | <head> | 
					
						
							|  |  |  |     <meta charset="UTF-8"> | 
					
						
							|  |  |  |     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | 
					
						
							|  |  |  |     <title>Report Traffic Jam - OpenEventDatabase</title> | 
					
						
							| 
									
										
										
										
											2025-09-26 17:38:30 +02:00
										 |  |  |     <link rel="icon" type="image/png" href="/static/oedb.png"> | 
					
						
							|  |  |  |     <link rel="icon" type="image/png" href="/static/oedb.png"> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |     <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> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |     <link rel="stylesheet" href="/static/traffic.css"> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |     <script src="/static/demo_auth.js"></script> | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |     <script src="/static/event-types.js"></script> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  | </head> | 
					
						
							|  |  |  | <body> | 
					
						
							|  |  |  |     <div class="container"> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |         {% include 'partials/demo_nav.html' %} | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |          | 
					
						
							|  |  |  |         <h1>Report Road Issue</h1> | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         <!-- Hidden OAuth2 configuration for the JavaScript module --> | 
					
						
							|  |  |  |         <input type="hidden" id="osmClientId" value="{{ client_id }}"> | 
					
						
							|  |  |  |         <input type="hidden" id="osmClientSecret" value="{{ client_secret }}"> | 
					
						
							|  |  |  |         <input type="hidden" id="osmRedirectUri" value="{{ client_redirect }}"> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |         <!-- Hidden Panoramax configuration (upload endpoint and token) --> | 
					
						
							|  |  |  |         <input type="hidden" id="panoramaxUploadUrl" value="{{ panoramax_upload_url }}"> | 
					
						
							|  |  |  |         <input type="hidden" id="panoramaxToken" value="{{ panoramax_token }}"> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |          | 
					
						
							|  |  |  |         <!-- Authentication section will be rendered by JavaScript or server-side --> | 
					
						
							|  |  |  |         <div id="auth-section"> | 
					
						
							|  |  |  |             {% if is_authenticated %} | 
					
						
							|  |  |  |                 <div class="auth-info"> | 
					
						
							|  |  |  |                     <div> | 
					
						
							|  |  |  |                         <p>Logged in as <strong>{{ osm_username }}</strong></p> | 
					
						
							| 
									
										
										
										
											2025-09-21 17:30:47 +02:00
										 |  |  |                         <p><a href="https://www.openstreetmap.org/user/{{ osm_username }}" >View OSM Profile</a></p> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                         <input type="hidden" id="osmUsername" value="{{ osm_username }}"> | 
					
						
							|  |  |  |                         <input type="hidden" id="osmUserId" value="{{ osm_user_id }}"> | 
					
						
							|  |  |  |                     </div> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |             {% else %} | 
					
						
							|  |  |  |                 <p>Authenticate with your OpenStreetMap account to include your username in the traffic report.</p> | 
					
						
							|  |  |  |                 <a href="https://www.openstreetmap.org/oauth2/authorize?client_id={{ client_id }}&redirect_uri={{ client_redirect }}&response_type=code&scope={{ client_authorizations }}" class="osm-login-btn"> | 
					
						
							|  |  |  |                     <span class="osm-logo"></span> | 
					
						
							|  |  |  |                     Login with OpenStreetMap | 
					
						
							|  |  |  |                 </a> | 
					
						
							|  |  |  |             {% endif %} | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             <script> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                 // Préserve l'affichage serveur si présent, sinon laisse traffic.js/dema_auth gérer | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                 document.addEventListener('DOMContentLoaded', function() { | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                     const hasServerAuth = document.getElementById('osmUsername') && document.getElementById('osmUsername').value; | 
					
						
							|  |  |  |                     if (hasServerAuth) return; | 
					
						
							|  |  |  |                     if (window.osmAuth && osmAuth.renderAuthSection) { | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                         const clientId = document.getElementById('osmClientId').value; | 
					
						
							|  |  |  |                         const redirectUri = document.getElementById('osmRedirectUri').value; | 
					
						
							|  |  |  |                         const authSection = document.getElementById('auth-section'); | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         authSection.innerHTML = osmAuth.renderAuthSection(clientId, redirectUri); | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                     } | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |             </script> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         <h3>Select Issue Type</h3> | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |          | 
					
						
							|  |  |  |         <!-- Tab Navigation --> | 
					
						
							|  |  |  |         <div class="tabs"> | 
					
						
							|  |  |  |             <div class="tab-item active" data-tab="road"> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                 <i class="fas fa-road"></i> Route | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             </div> | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |             <div class="tab-item" data-tab="rail"> | 
					
						
							|  |  |  |                 <i class="fas fa-train"></i> Rail | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             </div> | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |             <div class="tab-item" data-tab="weather"> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                 <i class="fas fa-cloud-sun-rain"></i> Météo | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             </div> | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |             <div class="tab-item" data-tab="emergency"> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                 <i class="fas fa-exclamation-circle"></i> Urgences | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |             <div class="tab-item" data-tab="civic"> | 
					
						
							|  |  |  |                 <i class="fas fa-bicycle"></i> Cycles | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |             </div> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         <!-- Tab Content --> | 
					
						
							|  |  |  |         <div class="tab-content"> | 
					
						
							|  |  |  |             <!-- Road Tab --> | 
					
						
							|  |  |  |             <div class="tab-pane active" id="road-tab"> | 
					
						
							|  |  |  |                 <div class="issue-buttons"> | 
					
						
							|  |  |  |                     <div class="issue-button road pothole" onclick="fillForm('pothole')"> | 
					
						
							|  |  |  |                         <i class="fas fa-dot-circle"></i> | 
					
						
							|  |  |  |                         Pothole | 
					
						
							|  |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button road obstacle" onclick="fillForm('obstacle')"> | 
					
						
							|  |  |  |                         <i class="fas fa-exclamation-triangle"></i> | 
					
						
							|  |  |  |                         Obstacle | 
					
						
							|  |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button road vehicle" onclick="fillForm('vehicle')"> | 
					
						
							|  |  |  |                         <i class="fas fa-car"></i> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         Véhicule sur le bas côté de la route | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button road danger" onclick="fillForm('danger')"> | 
					
						
							|  |  |  |                         <i class="fas fa-skull-crossbones"></i> | 
					
						
							|  |  |  |                         Danger | 
					
						
							|  |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button road accident" onclick="fillForm('accident')"> | 
					
						
							|  |  |  |                         <i class="fas fa-car-crash"></i> | 
					
						
							|  |  |  |                         Accident | 
					
						
							|  |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button road flooded-road" onclick="fillForm('flooded_road')"> | 
					
						
							|  |  |  |                         <i class="fas fa-water"></i> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         Route inondée | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button road roadwork" onclick="fillForm('roadwork')"> | 
					
						
							|  |  |  |                         <i class="fas fa-hard-hat"></i> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         Travaux | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button road black-traffic" onclick="fillForm('black_traffic')"> | 
					
						
							|  |  |  |                         <i class="fas fa-traffic-light"></i> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         journée noire bison futé | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     </div> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             <!-- Rail Tab --> | 
					
						
							|  |  |  |             <div class="tab-pane" id="rail-tab"> | 
					
						
							|  |  |  |                 <div class="issue-buttons"> | 
					
						
							|  |  |  |                     <div class="issue-button rail unattended-luggage" onclick="fillForm('unattended_luggage')"> | 
					
						
							|  |  |  |                         <i class="fas fa-suitcase"></i> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         Bagage abandonné | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button rail transport-delay" onclick="fillForm('transport_delay')"> | 
					
						
							|  |  |  |                         <i class="fas fa-hourglass-half"></i> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         Retard | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button rail major-delay" onclick="fillForm('major_transport_delay')"> | 
					
						
							|  |  |  |                         <i class="fas fa-hourglass-end"></i> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         Retard important | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     </div> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             <!-- Weather Tab --> | 
					
						
							|  |  |  |             <div class="tab-pane" id="weather-tab"> | 
					
						
							|  |  |  |                 <div class="issue-buttons"> | 
					
						
							|  |  |  |                     <div class="issue-button weather flood-danger" onclick="fillForm('flood_danger')"> | 
					
						
							|  |  |  |                         <i class="fas fa-water"></i> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                             Vigilance rouge inondation | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button weather thunderstorm" onclick="fillForm('thunderstorm_alert')"> | 
					
						
							|  |  |  |                         <i class="fas fa-bolt"></i> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         Vigilance orange orages | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button weather fog" onclick="fillForm('fog_warning')"> | 
					
						
							|  |  |  |                         <i class="fas fa-smog"></i> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         Vigilance jaune brouillard | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     </div> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             <!-- Emergency Tab --> | 
					
						
							|  |  |  |             <div class="tab-pane" id="emergency-tab"> | 
					
						
							|  |  |  |                 <div class="issue-buttons"> | 
					
						
							|  |  |  |                     <div class="issue-button emergency emergency-alert" onclick="fillForm('emergency_alert')"> | 
					
						
							|  |  |  |                         <i class="fas fa-exclamation-circle"></i> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         Alerte d'urgence (SAIP) | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button emergency daylight-saving" onclick="fillForm('daylight_saving')"> | 
					
						
							|  |  |  |                         <i class="fas fa-clock"></i> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         Période d'heure d'été | 
					
						
							|  |  |  |                     </div> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             <!-- Civic Tab --> | 
					
						
							|  |  |  |             <div class="tab-pane" id="civic-tab"> | 
					
						
							|  |  |  |                 <div class="issue-buttons"> | 
					
						
							|  |  |  |                     <div class="issue-button civic bike-obstacle" onclick="fillForm('bike_obstacle')"> | 
					
						
							|  |  |  |                         <i class="fas fa-bicycle"></i> | 
					
						
							|  |  |  |                         Obstacle vélo | 
					
						
							|  |  |  |                     </div> | 
					
						
							|  |  |  |                     <div class="issue-button civic illegal-dumping" onclick="fillForm('illegal_dumping')"> | 
					
						
							|  |  |  |                         <i class="fas fa-trash"></i> | 
					
						
							|  |  |  |                         Décharge sauvage | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     </div> | 
					
						
							|  |  |  |                 </div> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             </div> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         <form id="trafficForm"> | 
					
						
							|  |  |  |             <div class="form-group"> | 
					
						
							| 
									
										
										
										
											2025-09-23 11:26:44 +02:00
										 |  |  | <!--                <label for="photo">Photo (optionnelle)</label>--> | 
					
						
							|  |  |  | <!--                <input type="file" id="photo" name="photo" accept="image/*" capture="environment">--> | 
					
						
							|  |  |  | <!--                <div class="note">Prenez une photo géolocalisée de la situation (mobile recommandé)</div>--> | 
					
						
							|  |  |  | <!--                <div id="photoPreviewContainer" style="margin-top:8px; display:none;">--> | 
					
						
							|  |  |  | <!--                    <img id="photoPreview" alt="Aperçu photo" style="max-width:100%; border-radius:4px;"/>--> | 
					
						
							|  |  |  | <!--                </div>--> | 
					
						
							|  |  |  | <!--                <div class="form-row" style="margin-top:8px;">--> | 
					
						
							|  |  |  | <!--                    <div class="form-group">--> | 
					
						
							|  |  |  | <!--                        <label for="panoramaxTokenInput">Token Panoramax</label>--> | 
					
						
							|  |  |  | <!--                        <input type="password" id="panoramaxTokenInput" placeholder="Jeton d'API Panoramax">--> | 
					
						
							|  |  |  | <!--                        <div class="note">Stocké en local sur cet appareil. Utilisé pour envoyer la photo.</div>--> | 
					
						
							|  |  |  | <!--                    </div>--> | 
					
						
							|  |  |  | <!--                    <div class="form-group" style="align-self:flex-end;">--> | 
					
						
							|  |  |  | <!--                        <button type="button" id="savePanoramaxTokenBtn">Enregistrer le token</button>--> | 
					
						
							|  |  |  | <!--                        <button type="button" id="showPanoramaxTokenBtn" style="display:none;">Modifier le token</button>--> | 
					
						
							|  |  |  | <!--                    </div>--> | 
					
						
							|  |  |  | <!--                </div>--> | 
					
						
							|  |  |  | <!--                <div class="camera-block">--> | 
					
						
							|  |  |  | <!--                    <label>Prendre une photo avec la caméra</label>--> | 
					
						
							|  |  |  | <!--                    <div class="camera-controls">--> | 
					
						
							|  |  |  | <!--                        <button type="button" id="startCameraBtn">Démarrer la caméra</button>--> | 
					
						
							|  |  |  | <!--                        <button type="button" id="capturePhotoBtn" disabled>Prendre la photo</button>--> | 
					
						
							|  |  |  | <!--                        <button type="button" id="stopCameraBtn" disabled>Arrêter</button>--> | 
					
						
							|  |  |  | <!--                    </div>--> | 
					
						
							|  |  |  | <!--                    <div class="camera-preview">--> | 
					
						
							|  |  |  | <!--                        <video id="cameraVideo" autoplay playsinline muted></video>--> | 
					
						
							|  |  |  | <!--                        <canvas id="cameraCanvas" style="display:none;"></canvas>--> | 
					
						
							|  |  |  | <!--                    </div>--> | 
					
						
							|  |  |  | <!--                    <div class="note">La photo capturée sera ajoutée au champ ci-dessus.</div>--> | 
					
						
							|  |  |  | <!--                </div>--> | 
					
						
							|  |  |  | <!--            </div>--> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |             <div class="form-group"> | 
					
						
							|  |  |  |                 <label for="label" class="required">Description du problème</label> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                 <input type="text" id="label" name="label" placeholder="e.g., Large pothole on Highway A1" required> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             <input type="hidden" id="issueType" name="issueType" value="traffic.jam"> | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             <div class="form-row"> | 
					
						
							|  |  |  |                 <div class="form-group"> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                     <label for="severity" class="required">Gravité</label> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                     <select id="severity" name="severity" required> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                         <option value="low">Faible (Problème mineur)</option> | 
					
						
							|  |  |  |                         <option value="medium" selected>Moyen (Problème modéré)</option> | 
					
						
							|  |  |  |                         <option value="high">Élevé (Problème grave)</option> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                     </select> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 <div class="form-group"> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                     <label for="cause">Détails supplémentaires</label> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                     <input type="text" id="cause" name="cause" placeholder="e.g., Size, specific location details"> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             <div class="form-row"> | 
					
						
							|  |  |  |                 <div class="form-group"> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                     <label for="start" class="required">Heure de début</label> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                     <input type="datetime-local" id="start" name="start" required value=""> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 <div class="form-group"> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                     <label for="stop" class="required">Heure estimée de fin</label> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                     <input type="datetime-local" id="stop" name="stop" required value=""> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             <div class="form-group"> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                 <label for="where">Route/Nom du lieu</label> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                 <input type="text" id="where" name="where" placeholder="e.g., Highway A1, Main Street"> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             <div class="form-group"> | 
					
						
							|  |  |  |                 <label class="required">Location</label> | 
					
						
							|  |  |  |                 <div id="map"></div> | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |                 <div style="margin-top: 10px;"> | 
					
						
							|  |  |  |                     <button type="button" id="geolocateMapBtn" class="geolocation-btn" style="background-color: #28a745; color: white; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer;"> | 
					
						
							|  |  |  |                         📍 Obtenir ma position actuelle | 
					
						
							|  |  |  |                     </button> | 
					
						
							|  |  |  |                     <span id="geolocateStatus" class="gps-status" style="margin-left: 10px; font-size: 0.9em; color: #666;">Cliquez pour vous géolocaliser</span> | 
					
						
							|  |  |  |                 </div> | 
					
						
							|  |  |  |                 <div class="note">Cliquez sur la carte pour définir la localisation du problème ou utilisez le bouton de géolocalisation</div> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             </div> | 
					
						
							|  |  |  |              | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |             <button id="report_issue_button" type="submit" disabled>Signaler le problème</button> | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |         </form> | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         <div id="result"></div> | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         <a href="/demo/view-events" class="view-saved-events"> | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |             <i class="fas fa-map-marked-alt"></i> Voir tous les événements enregistrés sur la carte | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |         </a> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     <script> | 
					
						
							|  |  |  |         // Set default date values (current time and +1 hour) | 
					
						
							|  |  |  |         function setDefaultDates() { | 
					
						
							|  |  |  |             const now = new Date(); | 
					
						
							|  |  |  |             const nowISO = now.toISOString().slice(0, 16); // Format: YYYY-MM-DDThh:mm | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Set start time to current time | 
					
						
							|  |  |  |             document.getElementById('start').value = nowISO; | 
					
						
							|  |  |  |              | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |             // Set end time to current time + 6 hours (durée par défaut des signalements) | 
					
						
							|  |  |  |             const sixHoursLater = new Date(now.getTime() + 6 * 60 * 60 * 1000); | 
					
						
							|  |  |  |             document.getElementById('stop').value = sixHoursLater.toISOString().slice(0, 16); | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Call function to set default dates | 
					
						
							|  |  |  |         setDefaultDates(); | 
					
						
							|  |  |  |          | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |         // Tab switching functionality | 
					
						
							|  |  |  |         document.addEventListener('DOMContentLoaded', function() { | 
					
						
							|  |  |  |             // Get all tab items | 
					
						
							|  |  |  |             const tabItems = document.querySelectorAll('.tab-item'); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Add click event listener to each tab item | 
					
						
							|  |  |  |             tabItems.forEach(tab => { | 
					
						
							|  |  |  |                 tab.addEventListener('click', function() { | 
					
						
							|  |  |  |                     // Remove active class from all tab items | 
					
						
							|  |  |  |                     tabItems.forEach(item => item.classList.remove('active')); | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     // Add active class to clicked tab item | 
					
						
							|  |  |  |                     this.classList.add('active'); | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     // Get the tab name from data-tab attribute | 
					
						
							|  |  |  |                     const tabName = this.getAttribute('data-tab'); | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     // Get all tab panes | 
					
						
							|  |  |  |                     const tabPanes = document.querySelectorAll('.tab-pane'); | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     // Remove active class from all tab panes | 
					
						
							|  |  |  |                     tabPanes.forEach(pane => pane.classList.remove('active')); | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     // Add active class to the corresponding tab pane | 
					
						
							|  |  |  |                     document.getElementById(tabName + '-tab').classList.add('active'); | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     // Save active tab to localStorage | 
					
						
							|  |  |  |                     localStorage.setItem('activeTab', tabName); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Restore active tab from localStorage | 
					
						
							|  |  |  |             const activeTab = localStorage.getItem('activeTab'); | 
					
						
							|  |  |  |             if (activeTab) { | 
					
						
							|  |  |  |                 // Find the tab item with the saved tab name | 
					
						
							|  |  |  |                 const tabItem = document.querySelector(`.tab-item[data-tab="${activeTab}"]`); | 
					
						
							|  |  |  |                 if (tabItem) { | 
					
						
							|  |  |  |                     // Trigger click event on the tab item | 
					
						
							|  |  |  |                     tabItem.click(); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |         // Initialize the map | 
					
						
							|  |  |  |         const map = new maplibregl.Map({ | 
					
						
							|  |  |  |             container: 'map', | 
					
						
							| 
									
										
										
										
											2025-09-23 11:26:44 +02:00
										 |  |  |             style: 'https://tiles.openfreemap.org/styles/liberty', | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             center: [2.2137, 46.2276], // Default center (center of metropolitan France) | 
					
						
							|  |  |  |             zoom: 5 | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |      | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |         // Add navigation controls | 
					
						
							|  |  |  |         map.addControl(new maplibregl.NavigationControl()); | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |      | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |         // Add marker for issue location | 
					
						
							|  |  |  |         let marker = new maplibregl.Marker({ | 
					
						
							|  |  |  |             draggable: true, | 
					
						
							|  |  |  |             color: '#ff3860' // Red color for traffic jam | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |      | 
					
						
							|  |  |  |         // Store existing traffic event markers | 
					
						
							|  |  |  |         let existingMarkers = []; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |         // Function to fetch and display existing traffic events | 
					
						
							|  |  |  |         function fetchExistingTrafficEvents() { | 
					
						
							|  |  |  |             // Clear existing markers first | 
					
						
							|  |  |  |             existingMarkers.forEach(marker => marker.remove()); | 
					
						
							|  |  |  |             existingMarkers = []; | 
					
						
							| 
									
										
										
										
											2025-09-21 23:59:01 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |             // Fetch traffic events from the API | 
					
						
							|  |  |  |             fetch('https://api.openeventdatabase.org/event?what=traffic') | 
					
						
							|  |  |  |                 .then(response => { | 
					
						
							|  |  |  |                     if (response.ok) { | 
					
						
							|  |  |  |                         return response.json(); | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         throw new Error('Failed to fetch existing traffic events'); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |                 .then(data => { | 
					
						
							|  |  |  |                     if (data && data.features && Array.isArray(data.features)) { | 
					
						
							|  |  |  |                         // Add markers for each event | 
					
						
							|  |  |  |                         data.features.forEach(event => { | 
					
						
							|  |  |  |                             if (event.geometry && event.geometry.type === 'Point') { | 
					
						
							|  |  |  |                                 const coords = event.geometry.coordinates; | 
					
						
							| 
									
										
										
										
											2025-09-21 23:59:01 +02:00
										 |  |  |                          | 
					
						
							|  |  |  |                                 // Check if this event needs reality check (created more than 1 hour ago) | 
					
						
							|  |  |  |                                 const needsRealityCheck = checkIfNeedsRealityCheck(event); | 
					
						
							|  |  |  |                          | 
					
						
							|  |  |  |                                 // Create a marker for existing events (gray for regular, orange for those needing reality check) | 
					
						
							|  |  |  |                                 const markerColor = needsRealityCheck ? '#ff9800' : '#888888'; | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                                 const eventMarker = new maplibregl.Marker({ | 
					
						
							| 
									
										
										
										
											2025-09-21 23:59:01 +02:00
										 |  |  |                                     color: markerColor | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                                 }) | 
					
						
							|  |  |  |                                 .setLngLat(coords) | 
					
						
							|  |  |  |                                 .addTo(map); | 
					
						
							| 
									
										
										
										
											2025-09-21 23:59:01 +02:00
										 |  |  |                      | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                                 // Add popup with event details | 
					
						
							| 
									
										
										
										
											2025-09-21 23:59:01 +02:00
										 |  |  |                                 let popupContent = ` | 
					
						
							|  |  |  |                                     <h3>${event.properties.label || 'Traffic Event'}</h3> | 
					
						
							|  |  |  |                                     <p>Type: ${event.properties.what || 'Unknown'}</p> | 
					
						
							|  |  |  |                                     <p>Start: ${event.properties.start || 'Unknown'}</p> | 
					
						
							|  |  |  |                                     <p>End: ${event.properties.stop || 'Unknown'}</p> | 
					
						
							|  |  |  |                                 `; | 
					
						
							|  |  |  |                          | 
					
						
							|  |  |  |                                 // Add reality check buttons if needed | 
					
						
							|  |  |  |                                 if (needsRealityCheck) { | 
					
						
							|  |  |  |                                     popupContent += ` | 
					
						
							|  |  |  |                                         <div class="reality-check"> | 
					
						
							|  |  |  |                                             <p>Is this traffic event still present?</p> | 
					
						
							|  |  |  |                                             <div class="reality-check-buttons"> | 
					
						
							|  |  |  |                                                 <button class="confirm-btn" onclick="confirmEvent('${event.properties.id}', true)">Yes, still there</button> | 
					
						
							|  |  |  |                                                 <button class="deny-btn" onclick="confirmEvent('${event.properties.id}', false)">No, it's gone</button> | 
					
						
							|  |  |  |                                             </div> | 
					
						
							|  |  |  |                                         </div> | 
					
						
							|  |  |  |                                     `; | 
					
						
							|  |  |  |                                 } else if (event.properties['reality_check']) { | 
					
						
							|  |  |  |                                     // Show reality check information if it exists | 
					
						
							|  |  |  |                                     popupContent += ` | 
					
						
							|  |  |  |                                         <div class="reality-check-info"> | 
					
						
							|  |  |  |                                             <p>Reality check: ${event.properties['reality_check']}</p> | 
					
						
							|  |  |  |                                         </div> | 
					
						
							|  |  |  |                                     `; | 
					
						
							|  |  |  |                                 } | 
					
						
							|  |  |  |                          | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                                 const popup = new maplibregl.Popup({ offset: 25 }) | 
					
						
							| 
									
										
										
										
											2025-09-21 23:59:01 +02:00
										 |  |  |                                     .setHTML(popupContent); | 
					
						
							|  |  |  |                      | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                                 eventMarker.setPopup(popup); | 
					
						
							| 
									
										
										
										
											2025-09-21 23:59:01 +02:00
										 |  |  |                      | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                                 // Store marker reference for later removal | 
					
						
							|  |  |  |                                 existingMarkers.push(eventMarker); | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                         }); | 
					
						
							| 
									
										
										
										
											2025-09-21 23:59:01 +02:00
										 |  |  |              | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                         console.log(`Loaded ${existingMarkers.length} existing traffic events`); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |                 .catch(error => { | 
					
						
							|  |  |  |                     console.error('Error fetching traffic events:', error); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-09-21 23:59:01 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Function to check if an event needs a reality check (created more than 1 hour ago) | 
					
						
							|  |  |  |         function checkIfNeedsRealityCheck(event) { | 
					
						
							|  |  |  |             // Skip if event already has a reality check | 
					
						
							|  |  |  |             if (event.properties['reality_check']) { | 
					
						
							|  |  |  |                 return false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |             // Check if the event is a traffic event | 
					
						
							|  |  |  |             if (!event.properties.what || !event.properties.what.startsWith('traffic')) { | 
					
						
							|  |  |  |                 return false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |             // Check creation date | 
					
						
							|  |  |  |             const createDate = event.properties.createdate; | 
					
						
							|  |  |  |             if (!createDate) { | 
					
						
							|  |  |  |                 return false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |             const createTime = new Date(createDate).getTime(); | 
					
						
							|  |  |  |             const currentTime = new Date().getTime(); | 
					
						
							|  |  |  |             const oneHourInMs = 60 * 60 * 1000; | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |             // Return true if the event was created more than 1 hour ago | 
					
						
							|  |  |  |             return (currentTime - createTime) > oneHourInMs; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |      | 
					
						
							|  |  |  |         // Fetch existing events when the map loads | 
					
						
							|  |  |  |         map.on('load', fetchExistingTrafficEvents); | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |          | 
					
						
							|  |  |  |         // Add marker on map click | 
					
						
							|  |  |  |         map.on('click', function(e) { | 
					
						
							|  |  |  |             marker.setLngLat(e.lngLat).addTo(map); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Function to fill the form based on issue type | 
					
						
							|  |  |  |         function fillForm(issueType) { | 
					
						
							|  |  |  |             const labelInput = document.getElementById('label'); | 
					
						
							|  |  |  |             const issueTypeInput = document.getElementById('issueType'); | 
					
						
							|  |  |  |             const severitySelect = document.getElementById('severity'); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Save current marker position if it exists | 
					
						
							|  |  |  |             let currentLngLat = marker.getLngLat ? marker.getLngLat() : null; | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Remove existing marker from the map | 
					
						
							|  |  |  |             marker.remove(); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Set marker color based on issue type | 
					
						
							|  |  |  |             let markerColor = '#ff3860'; // Default red color | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             switch(issueType) { | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |                 case 'bike_obstacle': | 
					
						
							|  |  |  |                     labelInput.value = 'Obstacle vélo'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'mobility.cycling.obstacle'; | 
					
						
							|  |  |  |                     severitySelect.value = 'medium'; | 
					
						
							|  |  |  |                     markerColor = '#388e3c'; // Green | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'illegal_dumping': | 
					
						
							|  |  |  |                     labelInput.value = 'Décharge sauvage'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'environment.dumping.illegal'; | 
					
						
							|  |  |  |                     severitySelect.value = 'medium'; | 
					
						
							|  |  |  |                     markerColor = '#795548'; // Brown | 
					
						
							|  |  |  |                     break; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                 case 'pothole': | 
					
						
							|  |  |  |                     labelInput.value = 'Nid de poule'; | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     issueTypeInput.value = 'traffic.hazard.pothole'; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                     severitySelect.value = 'medium'; | 
					
						
							|  |  |  |                     markerColor = '#ff9800'; | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'obstacle': | 
					
						
							|  |  |  |                     labelInput.value = 'Obstacle'; | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     issueTypeInput.value = 'traffic.hazard.obstacle'; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                     severitySelect.value = 'high'; | 
					
						
							|  |  |  |                     markerColor = '#f44336'; | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'vehicle': | 
					
						
							|  |  |  |                     labelInput.value = 'Véhicule sur le bas côté de la route'; | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     issueTypeInput.value = 'traffic.hazard.vehicle'; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                     severitySelect.value = 'low'; | 
					
						
							|  |  |  |                     markerColor = '#2196f3'; | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'danger': | 
					
						
							|  |  |  |                     labelInput.value = 'Danger non classé'; | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                     issueTypeInput.value = 'traffic.hazard.danger'; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                     severitySelect.value = 'high'; | 
					
						
							|  |  |  |                     markerColor = '#9c27b0'; | 
					
						
							|  |  |  |                     break; | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                 case 'emergency_alert': | 
					
						
							|  |  |  |                     labelInput.value = 'Alerte d\'urgence (SAIP)'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'alert.emergency'; | 
					
						
							|  |  |  |                     severitySelect.value = 'high'; | 
					
						
							|  |  |  |                     markerColor = '#e91e63'; // Pink | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'daylight_saving': | 
					
						
							|  |  |  |                     labelInput.value = 'Période d\'heure d\'été'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'time.daylight.summer'; | 
					
						
							|  |  |  |                     severitySelect.value = 'low'; | 
					
						
							|  |  |  |                     markerColor = '#ffc107'; // Amber | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'accident': | 
					
						
							|  |  |  |                     labelInput.value = 'Accident de la route'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'traffic.accident'; | 
					
						
							|  |  |  |                     severitySelect.value = 'high'; | 
					
						
							|  |  |  |                     markerColor = '#d32f2f'; // Dark red | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'flooded_road': | 
					
						
							|  |  |  |                     labelInput.value = 'Route inondée'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'traffic.closed.flood'; | 
					
						
							|  |  |  |                     severitySelect.value = 'high'; | 
					
						
							|  |  |  |                     markerColor = '#1976d2'; // Blue | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'black_traffic': | 
					
						
							|  |  |  |                     labelInput.value = 'Période noire bison futé vers la province'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'traffic.forecast.black.out'; | 
					
						
							|  |  |  |                     severitySelect.value = 'high'; | 
					
						
							|  |  |  |                     markerColor = '#212121'; // Black | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'roadwork': | 
					
						
							|  |  |  |                     labelInput.value = 'Travaux'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'traffic.roadwork'; | 
					
						
							|  |  |  |                     severitySelect.value = 'medium'; | 
					
						
							|  |  |  |                     markerColor = '#ff5722'; // Deep orange | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'flood_danger': | 
					
						
							|  |  |  |                     labelInput.value = 'Vigilance rouge inondation'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'weather.danger.flood'; | 
					
						
							|  |  |  |                     severitySelect.value = 'high'; | 
					
						
							|  |  |  |                     markerColor = '#b71c1c'; // Dark red | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'thunderstorm_alert': | 
					
						
							|  |  |  |                     labelInput.value = 'Vigilance orange orages'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'weather.alert.thunderstorm'; | 
					
						
							|  |  |  |                     severitySelect.value = 'medium'; | 
					
						
							|  |  |  |                     markerColor = '#ff9800'; // Orange | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'fog_warning': | 
					
						
							|  |  |  |                     labelInput.value = 'Vigilance jaune brouillard'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'weather.warning.fog'; | 
					
						
							|  |  |  |                     severitySelect.value = 'low'; | 
					
						
							|  |  |  |                     markerColor = '#ffeb3b'; // Yellow | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'unattended_luggage': | 
					
						
							|  |  |  |                     labelInput.value = 'Bagage abandonné'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'public_transport.incident.unattended_luggage'; | 
					
						
							|  |  |  |                     severitySelect.value = 'medium'; | 
					
						
							|  |  |  |                     markerColor = '#673ab7'; // Deep purple | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'transport_delay': | 
					
						
							|  |  |  |                     labelInput.value = 'Retard'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'public_transport.delay'; | 
					
						
							|  |  |  |                     severitySelect.value = 'low'; | 
					
						
							|  |  |  |                     markerColor = '#ffc107'; // Amber | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 case 'major_transport_delay': | 
					
						
							|  |  |  |                     labelInput.value = 'Retard important'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'public_transport.delay.major'; | 
					
						
							|  |  |  |                     severitySelect.value = 'medium'; | 
					
						
							|  |  |  |                     markerColor = '#ff5722'; // Deep orange | 
					
						
							|  |  |  |                     break; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                 default: | 
					
						
							|  |  |  |                     labelInput.value = 'Bouchon'; | 
					
						
							|  |  |  |                     issueTypeInput.value = 'traffic.jam'; | 
					
						
							|  |  |  |                     severitySelect.value = 'medium'; | 
					
						
							|  |  |  |                     markerColor = '#ff3860'; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Create a new marker with the selected color | 
					
						
							|  |  |  |             marker = new maplibregl.Marker({ | 
					
						
							|  |  |  |                 draggable: true, | 
					
						
							|  |  |  |                 color: markerColor | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // If there was a previous marker position, set the new marker at that position | 
					
						
							|  |  |  |             if (currentLngLat) { | 
					
						
							|  |  |  |                 marker.setLngLat(currentLngLat).addTo(map); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |              | 
					
						
							|  |  |  |             // Validate form after filling in values | 
					
						
							|  |  |  |             validateForm(); | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Handle geolocation button click | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |         document.getElementById('geolocateMapBtn').addEventListener('click', function() { | 
					
						
							|  |  |  |             // Update button text and disable it | 
					
						
							|  |  |  |             const originalText = this.textContent; | 
					
						
							|  |  |  |             this.textContent = '📍 Localisation en cours...'; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             this.disabled = true; | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             const statusElement = document.getElementById('geolocateStatus'); | 
					
						
							|  |  |  |             statusElement.textContent = 'Localisation en cours...'; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |              | 
					
						
							|  |  |  |             // Check if geolocation is available | 
					
						
							|  |  |  |             if (navigator.geolocation) { | 
					
						
							|  |  |  |                 navigator.geolocation.getCurrentPosition( | 
					
						
							|  |  |  |                     // Success callback | 
					
						
							|  |  |  |                     function(position) { | 
					
						
							|  |  |  |                         const lat = position.coords.latitude; | 
					
						
							|  |  |  |                         const lng = position.coords.longitude; | 
					
						
							|  |  |  |                          | 
					
						
							|  |  |  |                         // Set marker at current location | 
					
						
							|  |  |  |                         marker.setLngLat([lng, lat]).addTo(map); | 
					
						
							|  |  |  |                          | 
					
						
							|  |  |  |                         // Center map on current location | 
					
						
							|  |  |  |                         map.flyTo({ | 
					
						
							|  |  |  |                             center: [lng, lat], | 
					
						
							|  |  |  |                             zoom: 14 | 
					
						
							|  |  |  |                         }); | 
					
						
							|  |  |  |                          | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |                         // Reset button | 
					
						
							|  |  |  |                         document.getElementById('geolocateMapBtn').textContent = '📍 Position obtenue'; | 
					
						
							|  |  |  |                         document.getElementById('geolocateMapBtn').disabled = false; | 
					
						
							|  |  |  |                         document.getElementById('geolocateMapBtn').style.backgroundColor = '#28a745'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         const statusElement = document.getElementById('geolocateStatus'); | 
					
						
							|  |  |  |                         statusElement.textContent = `Position: ${lat.toFixed(4)}, ${lng.toFixed(4)}`; | 
					
						
							|  |  |  |                         statusElement.style.color = '#28a745'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                         // Show success message | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |                         showResult('Position actuelle détectée avec succès', 'success'); | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                          | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                         // Validate form after setting marker | 
					
						
							|  |  |  |                         validateForm(); | 
					
						
							|  |  |  |                          | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                         // Try to get address using reverse geocoding | 
					
						
							|  |  |  |                         fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`) | 
					
						
							|  |  |  |                             .then(response => response.json()) | 
					
						
							|  |  |  |                             .then(data => { | 
					
						
							|  |  |  |                                 if (data && data.address) { | 
					
						
							|  |  |  |                                     // Extract road name or location | 
					
						
							|  |  |  |                                     let location = ''; | 
					
						
							|  |  |  |                                     if (data.address.road) { | 
					
						
							|  |  |  |                                         location = data.address.road; | 
					
						
							|  |  |  |                                         if (data.address.city) { | 
					
						
							|  |  |  |                                             location += `, ${data.address.city}`; | 
					
						
							|  |  |  |                                         } | 
					
						
							|  |  |  |                                     } else if (data.address.suburb) { | 
					
						
							|  |  |  |                                         location = data.address.suburb; | 
					
						
							|  |  |  |                                         if (data.address.city) { | 
					
						
							|  |  |  |                                             location += `, ${data.address.city}`; | 
					
						
							|  |  |  |                                         } | 
					
						
							|  |  |  |                                     } | 
					
						
							|  |  |  |                                      | 
					
						
							|  |  |  |                                     if (location) { | 
					
						
							|  |  |  |                                         document.getElementById('where').value = location; | 
					
						
							|  |  |  |                                     } | 
					
						
							|  |  |  |                                 } | 
					
						
							|  |  |  |                             }) | 
					
						
							|  |  |  |                             .catch(error => { | 
					
						
							|  |  |  |                                 console.error('Error getting address:', error); | 
					
						
							|  |  |  |                             }); | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     // Error callback | 
					
						
							|  |  |  |                     function(error) { | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |                         // Reset button | 
					
						
							|  |  |  |                         document.getElementById('geolocateMapBtn').textContent = '📍 Réessayer la géolocalisation'; | 
					
						
							|  |  |  |                         document.getElementById('geolocateMapBtn').disabled = false; | 
					
						
							|  |  |  |                         document.getElementById('geolocateMapBtn').style.backgroundColor = '#dc3545'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                         const statusElement = document.getElementById('geolocateStatus'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                         // Show error message | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |                         let errorMsg = 'Impossible d\'obtenir votre position. '; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                         switch(error.code) { | 
					
						
							|  |  |  |                             case error.PERMISSION_DENIED: | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |                                 errorMsg += 'Vous avez refusé la demande de géolocalisation.'; | 
					
						
							|  |  |  |                                 statusElement.textContent = 'Permission refusée'; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                                 break; | 
					
						
							|  |  |  |                             case error.POSITION_UNAVAILABLE: | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |                                 errorMsg += 'Les informations de position ne sont pas disponibles.'; | 
					
						
							|  |  |  |                                 statusElement.textContent = 'Position indisponible'; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                                 break; | 
					
						
							|  |  |  |                             case error.TIMEOUT: | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |                                 errorMsg += 'La demande de géolocalisation a expiré.'; | 
					
						
							|  |  |  |                                 statusElement.textContent = 'Temps dépassé'; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                                 break; | 
					
						
							|  |  |  |                             case error.UNKNOWN_ERROR: | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |                                 errorMsg += 'Une erreur inconnue s\'est produite.'; | 
					
						
							|  |  |  |                                 statusElement.textContent = 'Erreur inconnue'; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                                 break; | 
					
						
							|  |  |  |                         } | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |                         statusElement.style.color = '#dc3545'; | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                         showResult(errorMsg, 'error'); | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     // Options | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         enableHighAccuracy: true, | 
					
						
							|  |  |  |                         timeout: 10000, | 
					
						
							|  |  |  |                         maximumAge: 0 | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |                 // Reset button | 
					
						
							|  |  |  |                 document.getElementById('geolocateMapBtn').textContent = '📍 Non supporté'; | 
					
						
							|  |  |  |                 document.getElementById('geolocateMapBtn').disabled = true; | 
					
						
							|  |  |  |                 document.getElementById('geolocateMapBtn').style.backgroundColor = '#6c757d'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 const statusElement = document.getElementById('geolocateStatus'); | 
					
						
							|  |  |  |                 statusElement.textContent = 'Géolocalisation non supportée'; | 
					
						
							|  |  |  |                 statusElement.style.color = '#dc3545'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                 // Show error message | 
					
						
							| 
									
										
										
										
											2025-09-27 01:10:47 +02:00
										 |  |  |                 showResult('La géolocalisation n\'est pas supportée par votre navigateur', 'error'); | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |         // Form validation function | 
					
						
							|  |  |  |         function validateForm() { | 
					
						
							|  |  |  |             // Get all required form fields | 
					
						
							|  |  |  |             const requiredFields = document.querySelectorAll('#trafficForm [required]'); | 
					
						
							|  |  |  |             const submitButton = document.getElementById('report_issue_button'); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Check if all required fields are filled | 
					
						
							|  |  |  |             let isValid = true; | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Check each required field | 
					
						
							|  |  |  |             requiredFields.forEach(field => { | 
					
						
							|  |  |  |                 if (!field.value.trim()) { | 
					
						
							|  |  |  |                     isValid = false; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Check if marker is set | 
					
						
							|  |  |  |             if (!marker || !marker.getLngLat()) { | 
					
						
							|  |  |  |                 isValid = false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Enable or disable submit button based on form validity | 
					
						
							|  |  |  |             submitButton.disabled = !isValid; | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Add or remove disabled class | 
					
						
							|  |  |  |             if (isValid) { | 
					
						
							|  |  |  |                 submitButton.classList.remove('disabled'); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 submitButton.classList.add('disabled'); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             return isValid; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Add event listeners to form fields to trigger validation | 
					
						
							|  |  |  |         document.addEventListener('DOMContentLoaded', function() { | 
					
						
							|  |  |  |             // Get all form fields | 
					
						
							|  |  |  |             const formFields = document.querySelectorAll('#trafficForm input, #trafficForm select'); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Add input event listener to each field | 
					
						
							|  |  |  |             formFields.forEach(field => { | 
					
						
							|  |  |  |                 field.addEventListener('input', validateForm); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Add change event listener to each field | 
					
						
							|  |  |  |             formFields.forEach(field => { | 
					
						
							|  |  |  |                 field.addEventListener('change', validateForm); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Validate form on page load | 
					
						
							|  |  |  |             validateForm(); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Update validation when marker is set | 
					
						
							|  |  |  |         map.on('click', function() { | 
					
						
							|  |  |  |             // Validate form after marker is set | 
					
						
							|  |  |  |             setTimeout(validateForm, 100); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |         // Photo preview | 
					
						
							|  |  |  |         const photoInput = document.getElementById('photo'); | 
					
						
							|  |  |  |         if (photoInput) { | 
					
						
							|  |  |  |             photoInput.addEventListener('change', function() { | 
					
						
							|  |  |  |                 const file = this.files && this.files[0]; | 
					
						
							|  |  |  |                 if (!file) { | 
					
						
							|  |  |  |                     document.getElementById('photoPreviewContainer').style.display = 'none'; | 
					
						
							|  |  |  |                     return; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 const url = URL.createObjectURL(file); | 
					
						
							|  |  |  |                 const img = document.getElementById('photoPreview'); | 
					
						
							|  |  |  |                 img.src = url; | 
					
						
							|  |  |  |                 document.getElementById('photoPreviewContainer').style.display = 'block'; | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         async function uploadPhotoIfConfigured(file, lng, lat, isoDatetime) { | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |                 const uploadUrl = document.getElementById('panoramaxUploadUrl')?.value || ''; | 
					
						
							|  |  |  |                 const token = document.getElementById('panoramaxToken')?.value || ''; | 
					
						
							|  |  |  |                 if (!uploadUrl || !file) { | 
					
						
							|  |  |  |                     return null; // pas configuré ou pas de fichier | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 const form = new FormData(); | 
					
						
							|  |  |  |                 form.append('file', file, file.name || 'photo.jpg'); | 
					
						
							|  |  |  |                 // Métadonnées géo/temps standard | 
					
						
							|  |  |  |                 if (typeof lng === 'number' && typeof lat === 'number') { | 
					
						
							|  |  |  |                     form.append('lon', String(lng)); | 
					
						
							|  |  |  |                     form.append('lat', String(lat)); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (isoDatetime) { | 
					
						
							|  |  |  |                     form.append('datetime', isoDatetime); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 const headers = {}; | 
					
						
							|  |  |  |                 if (token) { | 
					
						
							|  |  |  |                     headers['Authorization'] = `Bearer ${token}`; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 const res = await fetch(uploadUrl, { method: 'POST', headers, body: form }); | 
					
						
							|  |  |  |                 if (!res.ok) { | 
					
						
							|  |  |  |                     const t = await res.text(); | 
					
						
							|  |  |  |                     throw new Error(t || `Upload failed (${res.status})`); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 const data = await res.json().catch(() => ({})); | 
					
						
							|  |  |  |                 // On essaie de normaliser quelques champs courants | 
					
						
							|  |  |  |                 return { | 
					
						
							|  |  |  |                     id: data.id || data.uuid || data.photo_id || null, | 
					
						
							|  |  |  |                     url: data.url || data.permalink || data.link || null, | 
					
						
							|  |  |  |                     raw: data | 
					
						
							|  |  |  |                 }; | 
					
						
							|  |  |  |             } catch (err) { | 
					
						
							|  |  |  |                 console.error('Panoramax upload error:', err); | 
					
						
							|  |  |  |                 showResult(`Erreur upload photo: ${err.message}`, 'error'); | 
					
						
							|  |  |  |                 return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |         // Handle form submission | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |         document.getElementById('trafficForm').addEventListener('submit', async function(e) { | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             e.preventDefault(); | 
					
						
							|  |  |  |              | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |             // Validate form before submission | 
					
						
							|  |  |  |             if (!validateForm()) { | 
					
						
							|  |  |  |                 showResult('Please fill in all required fields and set a location on the map', 'error'); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             // Get form values | 
					
						
							|  |  |  |             const label = document.getElementById('label').value; | 
					
						
							|  |  |  |             const issueType = document.getElementById('issueType').value; | 
					
						
							|  |  |  |             const severity = document.getElementById('severity').value; | 
					
						
							|  |  |  |             const cause = document.getElementById('cause').value; | 
					
						
							|  |  |  |             const start = document.getElementById('start').value; | 
					
						
							|  |  |  |             const stop = document.getElementById('stop').value; | 
					
						
							|  |  |  |             const where = document.getElementById('where').value; | 
					
						
							|  |  |  |              | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |             // Check if marker is set (redundant but kept for safety) | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             if (!marker.getLngLat()) { | 
					
						
							|  |  |  |                 showResult('Please set a location by clicking on the map or using the geolocation button', 'error'); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Get marker coordinates | 
					
						
							|  |  |  |             const lngLat = marker.getLngLat(); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Create event object | 
					
						
							|  |  |  |             const event = { | 
					
						
							|  |  |  |                 type: 'Feature', | 
					
						
							|  |  |  |                 geometry: { | 
					
						
							|  |  |  |                     type: 'Point', | 
					
						
							|  |  |  |                     coordinates: [lngLat.lng, lngLat.lat] | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 properties: { | 
					
						
							|  |  |  |                     label: label, | 
					
						
							|  |  |  |                     type: 'unscheduled', // Road issues are typically unscheduled | 
					
						
							|  |  |  |                     what: issueType, // Category for the issue | 
					
						
							|  |  |  |                     'issue:severity': severity, // Custom property for severity | 
					
						
							|  |  |  |                     start: start, | 
					
						
							|  |  |  |                     stop: stop | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }; | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Add optional properties if provided | 
					
						
							|  |  |  |             if (cause) { | 
					
						
							|  |  |  |                 event.properties['issue:details'] = cause; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             if (where) { | 
					
						
							|  |  |  |                 event.properties.where = where; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Add OSM username if authenticated - check both DOM element and osmAuth object | 
					
						
							|  |  |  |             let osmUsernameValue = ''; | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // First check the DOM element (for backward compatibility) | 
					
						
							|  |  |  |             const osmUsername = document.getElementById('osmUsername'); | 
					
						
							|  |  |  |             if (osmUsername && osmUsername.value) { | 
					
						
							|  |  |  |                 osmUsernameValue = osmUsername.value; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Then check the osmAuth object (preferred method) | 
					
						
							|  |  |  |             if (window.osmAuth && osmAuth.isUserAuthenticated()) { | 
					
						
							|  |  |  |                 osmUsernameValue = osmAuth.getUsername(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Add the username to the event properties if available | 
					
						
							|  |  |  |             if (osmUsernameValue) { | 
					
						
							|  |  |  |                 event.properties['reporter:osm'] = osmUsernameValue; | 
					
						
							|  |  |  |                 console.log(`Including OSM username in report: ${osmUsernameValue}`); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							| 
									
										
										
										
											2025-09-22 11:44:25 +02:00
										 |  |  |             // Upload photo to Panoramax si configuré | 
					
						
							|  |  |  |             let photoInfo = null; | 
					
						
							|  |  |  |             const photoFile = (photoInput && photoInput.files && photoInput.files[0]) ? photoInput.files[0] : null; | 
					
						
							|  |  |  |             if (photoFile) { | 
					
						
							|  |  |  |                 photoInfo = await uploadPhotoIfConfigured(photoFile, lngLat.lng, lngLat.lat, start); | 
					
						
							|  |  |  |                 if (photoInfo) { | 
					
						
							|  |  |  |                     event.properties['photo:service'] = 'panoramax'; | 
					
						
							|  |  |  |                     if (photoInfo.id) event.properties['photo:id'] = String(photoInfo.id); | 
					
						
							|  |  |  |                     if (photoInfo.url) event.properties['photo:url'] = photoInfo.url; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             // Save event to localStorage | 
					
						
							|  |  |  |             saveEventToLocalStorage(event); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Submit event to API | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |             fetch('https://api.openeventdatabase.org/event', { | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                 method: 'POST', | 
					
						
							|  |  |  |                 headers: { | 
					
						
							|  |  |  |                     'Content-Type': 'application/json' | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 body: JSON.stringify(event) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             .then(response => { | 
					
						
							|  |  |  |                 if (response.ok) { | 
					
						
							|  |  |  |                     return response.json(); | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     return response.text().then(text => { | 
					
						
							|  |  |  |                         throw new Error(text || response.statusText); | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             .then(data => { | 
					
						
							|  |  |  |                 // Update the event in localStorage with the server-assigned ID | 
					
						
							|  |  |  |                 if (data.id) { | 
					
						
							|  |  |  |                     updateEventInLocalStorage(event, data.id); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 showResult(`Issue reported successfully with ID: ${data.id}`, 'success'); | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 // Add links to view the event | 
					
						
							|  |  |  |                 const resultElement = document.getElementById('result'); | 
					
						
							|  |  |  |                 resultElement.innerHTML += ` | 
					
						
							|  |  |  |                     <p> | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                         <a href="https://api.openeventdatabase.org/event/${data.id}" >View Report on Server</a> | | 
					
						
							| 
									
										
										
										
											2025-09-21 17:30:47 +02:00
										 |  |  |                         <a href="/demo/view-events" >View Saved Reports</a> | | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |                         <a href="/demo">Back to Map</a> | 
					
						
							|  |  |  |                     </p>`; | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 // Reset form | 
					
						
							|  |  |  |                 document.getElementById('trafficForm').reset(); | 
					
						
							|  |  |  |                 // Set default dates again | 
					
						
							|  |  |  |                 setDefaultDates(); | 
					
						
							|  |  |  |                 // Remove marker | 
					
						
							|  |  |  |                 marker.remove(); | 
					
						
							| 
									
										
										
										
											2025-09-21 19:18:43 +02:00
										 |  |  |                  | 
					
						
							|  |  |  |                 // Refresh the map to show the new event along with existing ones | 
					
						
							|  |  |  |                 fetchExistingTrafficEvents(); | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |             }) | 
					
						
							|  |  |  |             .catch(error => { | 
					
						
							|  |  |  |                 showResult(`Error reporting issue: ${error.message}`, 'error'); | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Function to save event to localStorage | 
					
						
							|  |  |  |         function saveEventToLocalStorage(event) { | 
					
						
							|  |  |  |             // Get existing events from localStorage | 
					
						
							|  |  |  |             let savedEvents = JSON.parse(localStorage.getItem('oedb_events') || '[]'); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Add timestamp to event for sorting | 
					
						
							|  |  |  |             event.timestamp = new Date().toISOString(); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Add event to array | 
					
						
							|  |  |  |             savedEvents.push(event); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Save back to localStorage | 
					
						
							|  |  |  |             localStorage.setItem('oedb_events', JSON.stringify(savedEvents)); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             console.log('Event saved to localStorage:', event); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Function to update event in localStorage with server ID | 
					
						
							|  |  |  |         function updateEventInLocalStorage(event, serverId) { | 
					
						
							|  |  |  |             // Get existing events from localStorage | 
					
						
							|  |  |  |             let savedEvents = JSON.parse(localStorage.getItem('oedb_events') || '[]'); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Find the event by its timestamp (assuming it was just added) | 
					
						
							|  |  |  |             const eventIndex = savedEvents.findIndex(e =>  | 
					
						
							|  |  |  |                 e.timestamp === event.timestamp &&  | 
					
						
							|  |  |  |                 e.geometry.coordinates[0] === event.geometry.coordinates[0] && | 
					
						
							|  |  |  |                 e.geometry.coordinates[1] === event.geometry.coordinates[1]); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             if (eventIndex !== -1) { | 
					
						
							|  |  |  |                 // Add server ID to the event | 
					
						
							|  |  |  |                 savedEvents[eventIndex].properties.id = serverId; | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 // Save back to localStorage | 
					
						
							|  |  |  |                 localStorage.setItem('oedb_events', JSON.stringify(savedEvents)); | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 console.log('Event updated in localStorage with server ID:', serverId); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Show result message | 
					
						
							|  |  |  |         function showResult(message, type) { | 
					
						
							|  |  |  |             const resultElement = document.getElementById('result'); | 
					
						
							|  |  |  |             resultElement.textContent = message; | 
					
						
							|  |  |  |             resultElement.className = type; | 
					
						
							|  |  |  |             resultElement.style.display = 'block'; | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Scroll to result | 
					
						
							|  |  |  |             resultElement.scrollIntoView({ behavior: 'smooth' }); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-09-21 23:59:01 +02:00
										 |  |  |         // Function to handle event confirmation or denial | 
					
						
							|  |  |  |         function confirmEvent(eventId, isConfirmed) { | 
					
						
							|  |  |  |             // Get username from localStorage or prompt for it | 
					
						
							|  |  |  |             let username = localStorage.getItem('oedb_username'); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             if (!username) { | 
					
						
							|  |  |  |                 username = promptForUsername(); | 
					
						
							|  |  |  |                 if (!username) { | 
					
						
							|  |  |  |                     // User cancelled the prompt | 
					
						
							|  |  |  |                     return; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Current date and time | 
					
						
							|  |  |  |             const now = new Date(); | 
					
						
							|  |  |  |             const dateTimeString = now.toISOString(); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Create reality check string | 
					
						
							|  |  |  |             const realityCheckStatus = isConfirmed ? 'confirmed' : 'not confirmed'; | 
					
						
							|  |  |  |             const realityCheckValue = `${dateTimeString} | ${username} | ${realityCheckStatus}`; | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Fetch the event to update | 
					
						
							|  |  |  |             fetch(`https://api.openeventdatabase.org/event/${eventId}`) | 
					
						
							|  |  |  |                 .then(response => { | 
					
						
							|  |  |  |                     if (response.ok) { | 
					
						
							|  |  |  |                         return response.json(); | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         throw new Error(`Failed to fetch event ${eventId}`); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |                 .then(event => { | 
					
						
							|  |  |  |                     // Add reality_check property | 
					
						
							|  |  |  |                     event.properties['reality_check'] = realityCheckValue; | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                     // Update the event | 
					
						
							|  |  |  |                     return fetch(`https://api.openeventdatabase.org/event/${eventId}`, { | 
					
						
							|  |  |  |                         method: 'PUT', | 
					
						
							|  |  |  |                         headers: { | 
					
						
							|  |  |  |                             'Content-Type': 'application/json' | 
					
						
							|  |  |  |                         }, | 
					
						
							|  |  |  |                         body: JSON.stringify(event) | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |                 .then(response => { | 
					
						
							|  |  |  |                     if (response.ok) { | 
					
						
							|  |  |  |                         // Save contribution to localStorage | 
					
						
							|  |  |  |                         saveContribution(eventId, isConfirmed); | 
					
						
							|  |  |  |                          | 
					
						
							|  |  |  |                         // Award points | 
					
						
							|  |  |  |                         awardPoints(3); | 
					
						
							|  |  |  |                          | 
					
						
							|  |  |  |                         // Show success message | 
					
						
							|  |  |  |                         showResult(`Thank you for your contribution! You've earned 3 points.`, 'success'); | 
					
						
							|  |  |  |                          | 
					
						
							|  |  |  |                         // Update user info display | 
					
						
							|  |  |  |                         updateUserInfoDisplay(); | 
					
						
							|  |  |  |                          | 
					
						
							|  |  |  |                         // Refresh events to update the display | 
					
						
							|  |  |  |                         fetchExistingTrafficEvents(); | 
					
						
							|  |  |  |                     } else { | 
					
						
							|  |  |  |                         throw new Error('Failed to update event'); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |                 .catch(error => { | 
					
						
							|  |  |  |                     console.error('Error updating event:', error); | 
					
						
							|  |  |  |                     showResult(`Error: ${error.message}`, 'error'); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Function to prompt for username | 
					
						
							|  |  |  |         function promptForUsername() { | 
					
						
							|  |  |  |             const username = prompt('Please enter your username:'); | 
					
						
							|  |  |  |             if (username) { | 
					
						
							|  |  |  |                 localStorage.setItem('oedb_username', username); | 
					
						
							|  |  |  |                 return username; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Function to save contribution to localStorage | 
					
						
							|  |  |  |         function saveContribution(eventId, isConfirmed) { | 
					
						
							|  |  |  |             // Get existing contributions | 
					
						
							|  |  |  |             let contributions = JSON.parse(localStorage.getItem('oedb_contributions') || '[]'); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Add new contribution | 
					
						
							|  |  |  |             contributions.push({ | 
					
						
							|  |  |  |                 eventId: eventId, | 
					
						
							|  |  |  |                 timestamp: new Date().toISOString(), | 
					
						
							|  |  |  |                 isConfirmed: isConfirmed | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Save back to localStorage | 
					
						
							|  |  |  |             localStorage.setItem('oedb_contributions', JSON.stringify(contributions)); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Function to award points | 
					
						
							|  |  |  |         function awardPoints(points) { | 
					
						
							|  |  |  |             // Get current points | 
					
						
							|  |  |  |             let currentPoints = parseInt(localStorage.getItem('oedb_points') || '0'); | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Add new points | 
					
						
							|  |  |  |             currentPoints += points; | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Save back to localStorage | 
					
						
							|  |  |  |             localStorage.setItem('oedb_points', currentPoints.toString()); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Function to update user info display in side panel | 
					
						
							|  |  |  |         function updateUserInfoDisplay() { | 
					
						
							|  |  |  |             const username = localStorage.getItem('oedb_username') || 'Anonymous'; | 
					
						
							|  |  |  |             const points = localStorage.getItem('oedb_points') || '0'; | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Check if user info panel exists, create it if not | 
					
						
							|  |  |  |             let userInfoPanel = document.getElementById('user-info-panel'); | 
					
						
							|  |  |  |             if (!userInfoPanel) { | 
					
						
							|  |  |  |                 // Create user info panel | 
					
						
							|  |  |  |                 userInfoPanel = document.createElement('div'); | 
					
						
							|  |  |  |                 userInfoPanel.id = 'user-info-panel'; | 
					
						
							|  |  |  |                 userInfoPanel.className = 'user-info-panel'; | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 // Add it to the page (after the nav-links) | 
					
						
							|  |  |  |                 const navLinks = document.querySelector('.nav-links'); | 
					
						
							|  |  |  |                 navLinks.parentNode.insertBefore(userInfoPanel, navLinks.nextSibling); | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 // Add some CSS for the panel | 
					
						
							|  |  |  |                 const style = document.createElement('style'); | 
					
						
							|  |  |  |                 style.textContent = ` | 
					
						
							|  |  |  |                     .user-info-panel { | 
					
						
							|  |  |  |                         background-color: #f5f5f5; | 
					
						
							|  |  |  |                         border-radius: 4px; | 
					
						
							|  |  |  |                         padding: 10px; | 
					
						
							|  |  |  |                         margin: 10px 0; | 
					
						
							|  |  |  |                         box-shadow: 0 2px 4px rgba(0,0,0,0.1); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     .user-info-panel h3 { | 
					
						
							|  |  |  |                         margin-top: 0; | 
					
						
							|  |  |  |                         margin-bottom: 10px; | 
					
						
							|  |  |  |                         color: #333; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     .user-info-panel p { | 
					
						
							|  |  |  |                         margin: 5px 0; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     .user-points { | 
					
						
							|  |  |  |                         font-weight: bold; | 
					
						
							|  |  |  |                         color: #0078ff; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     .reality-check { | 
					
						
							|  |  |  |                         margin-top: 10px; | 
					
						
							|  |  |  |                         padding: 10px; | 
					
						
							|  |  |  |                         background-color: #fff3e0; | 
					
						
							|  |  |  |                         border-radius: 4px; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     .reality-check-buttons { | 
					
						
							|  |  |  |                         display: flex; | 
					
						
							|  |  |  |                         justify-content: space-between; | 
					
						
							|  |  |  |                         margin-top: 8px; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     .confirm-btn, .deny-btn { | 
					
						
							|  |  |  |                         padding: 5px 10px; | 
					
						
							|  |  |  |                         border: none; | 
					
						
							|  |  |  |                         border-radius: 4px; | 
					
						
							|  |  |  |                         cursor: pointer; | 
					
						
							|  |  |  |                         font-weight: bold; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     .confirm-btn { | 
					
						
							|  |  |  |                         background-color: #4caf50; | 
					
						
							|  |  |  |                         color: white; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     .deny-btn { | 
					
						
							|  |  |  |                         background-color: #f44336; | 
					
						
							|  |  |  |                         color: white; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     .reality-check-info { | 
					
						
							|  |  |  |                         margin-top: 10px; | 
					
						
							|  |  |  |                         padding: 8px; | 
					
						
							|  |  |  |                         background-color: #e8f5e9; | 
					
						
							|  |  |  |                         border-radius: 4px; | 
					
						
							|  |  |  |                         font-size: 0.9em; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 `; | 
					
						
							|  |  |  |                 document.head.appendChild(style); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |              | 
					
						
							|  |  |  |             // Update the content | 
					
						
							|  |  |  |             userInfoPanel.innerHTML = ` | 
					
						
							| 
									
										
										
										
											2025-09-23 11:51:54 +02:00
										 |  |  | <!--                <h3>User Information</h3>--> | 
					
						
							|  |  |  |                 <p> <strong>${username}</strong></p> | 
					
						
							|  |  |  |                 <p> <span class="user-points">${points}</span> points</p> | 
					
						
							| 
									
										
										
										
											2025-09-21 23:59:01 +02:00
										 |  |  |             `; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         // Initialize user info display when page loads | 
					
						
							|  |  |  |         document.addEventListener('DOMContentLoaded', function() { | 
					
						
							|  |  |  |             updateUserInfoDisplay(); | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-09-21 16:57:24 +02:00
										 |  |  |     </script> | 
					
						
							|  |  |  | </body> | 
					
						
							|  |  |  | </html> |