style agenda

This commit is contained in:
Tykayn 2025-10-13 10:49:13 +02:00 committed by tykayn
parent 737781e9aa
commit 7dd38624a4
7 changed files with 471 additions and 161 deletions

11
cleanup_test_files.sh Executable file
View file

@ -0,0 +1,11 @@
#!/bin/bash
# Script de nettoyage des fichiers de test CORS
echo "🧹 Nettoyage des fichiers de test CORS..."
# Supprimer les fichiers de test
rm -f test_cors_embed.py
rm -f test_embed_cors.html
rm -f cleanup_test_files.sh
echo "✅ Fichiers de test supprimés"

View file

@ -1,10 +1,6 @@
<div class="agenda-page">
<div class="layout">
<div class="aside">
<app-menu></app-menu>
</div>
<div class="main">
@if (isLoading) {
<div class="loading">
<div class="loading-spinner"></div>
@ -12,31 +8,31 @@
</div>
} @else {
<div class="agenda-layout">
<aside class="agenda-sidebar">
<div class="sidebar-header">
<h3>Agenda</h3>
<small>{{filteredCalendarEvents.length}} évènements</small>
</div>
<div class="sidebar-filters">
<app-what-filter
[label]="'Filtrer par type d\'événement'"
[available]="availableWhatTypes"
[selected]="selectedWhatFilter"
(selectedChange)="onWhatFilterChange($event)"></app-what-filter>
@if (selectedDate) {
<div class="date-filter-info">
<small>Filtré par date: {{formatDayHeader(selectedDate)}}</small>
<button class="btn-reset-date" (click)="clearDateFilter()">Afficher tous les jours</button>
<aside class="agenda-sidebar">
<div class="sidebar-header">
<h3>Agenda</h3>
<small>{{filteredCalendarEvents.length}} évènements</small>
</div>
}
</div>
<div class="day-groups">
@for (group of groupedEvents; track group.dateKey) {
<div class="day-group" [attr.data-date-key]="group.dateKey">
<div class="day-title">{{formatDayHeader(group.date)}}</div>
<ul class="event-list">
@for (ev of group.items; track ev.id) {
<li class="event-item" (click)="selectFromSidebar(ev)" [class.active]="selectedEvent?.id === ev.id">
<div class="sidebar-filters">
<app-what-filter
[label]="'Filtrer par type d\'événement'"
[available]="availableWhatTypes"
[selected]="selectedWhatFilter"
(selectedChange)="onWhatFilterChange($event)"></app-what-filter>
@if (selectedDate) {
<div class="date-filter-info">
<small>Filtré par date: {{formatDayHeader(selectedDate)}}</small>
<button class="btn-reset-date" (click)="clearDateFilter()">Afficher tous les jours</button>
</div>
}
</div>
<div class="day-groups">
@for (group of groupedEvents; track group.dateKey) {
<div class="day-group" [attr.data-date-key]="group.dateKey">
<div class="day-title">{{formatDayHeader(group.date)}}</div>
<ul class="event-list">
@for (ev of group.items; track ev.id) {
<li class="event-item" (click)="selectFromSidebar(ev)" [class.active]="selectedEvent?.id === ev.id">
<span class="event-icon">
@if (getImageForWhat(ev.properties.what)) {
<img [src]="getImageForWhat(ev.properties.what)" alt="" />
@ -46,44 +42,49 @@
📌
}
</span>
<div class="event-meta">
<div class="event-title">{{ev.properties.label || ev.properties.name || 'Événement'}}</div>
<div class="event-when">{{(ev.properties.start || ev.properties.when) || '—'}}</div>
</div>
</li>
}
</ul>
<div class="event-meta">
<div class="event-title">{{ev.properties.label || ev.properties.name || 'Événement'}}</div>
<div class="event-when">{{(ev.properties.start || ev.properties.when) || '—'}}</div>
</div>
</li>
}
</ul>
</div>
}
</div>
</aside>
@if (selectedEvent) {
<div class="event-edit-panel">
<div class="panel-header">
<h3>Modifier l'événement</h3>
<button class="btn-close" (click)="selectedEvent = null">×</button>
</div>
<div class="panel-content">
<app-edit-form
[selected]="selectedEvent"
(saved)="onEventSaved()"
(created)="onEventCreated()"
(deleted)="onEventDeleted()">
</app-edit-form>
</div>
</div>
}
</div>
</aside>
}
</div>
<div class="main">
<main class="agenda-main">
<app-calendar
<app-calendar
[events]="filteredCalendarEvents"
(eventClick)="onEventClick($event)"
(dateClick)="onDateClick($event)">
</app-calendar>
</main>
@if (selectedEvent) {
<div class="event-edit-panel">
<div class="panel-header">
<h3>Modifier l'événement</h3>
<button class="btn-close" (click)="selectedEvent = null">×</button>
</div>
<div class="panel-content">
<app-edit-form
[selected]="selectedEvent"
(saved)="onEventSaved()"
(created)="onEventCreated()"
(deleted)="onEventDeleted()">
</app-edit-form>
</div>
</div>
}
</div>
}
</div>
</div>
</div>
</div>

View file

@ -10,7 +10,7 @@
grid-template-rows: minmax(100vh, auto);
gap: 0;
// min-height: 100vh;
&.is-small {
grid-template-columns: 100px 1fr;
}
@ -220,4 +220,4 @@
.event-edit-panel {
width: 100%;
}
}
}

View file

@ -1,6 +1,6 @@
const oedb = {
presets : {
what : {
presets: {
what: {
'community': {
label: 'Événement de base',
description: 'Événement communautaire',
@ -22,11 +22,11 @@ const oedb = {
description: 'Événement de type Culture ouvert au public',
durationHours: 24,
properties: {
createdate: { label: 'Createdate', writable: true },
lastupdate: { label: 'Lastupdate', writable: true },
start: { label: 'Start', writable: true },
stop: { label: 'Stop', writable: true },
type: { label: 'Type', writable: true }
createdate: {label: 'Createdate', writable: true},
lastupdate: {label: 'Lastupdate', writable: true},
start: {label: 'Start', writable: true},
stop: {label: 'Stop', writable: true},
type: {label: 'Type', writable: true}
}
},
'culture.floss': {
@ -35,15 +35,15 @@ const oedb = {
category: 'Autre',
description: 'Événement de type Culture Floss',
durationHours: 24,
properties: {
}}, 'culture.viparis': {
properties: {}
}, 'culture.viparis': {
emoji: '📅',
label: 'Évènements organisés par Viparis',
category: 'Autre',
description: 'Événement culturel par Viparis, une entreprise qui gère plusieurs grandes salles en Île de France.',
durationHours: 24,
properties: {
}},
properties: {}
},
// Culture / Arts
@ -109,8 +109,8 @@ const oedb = {
description: 'Infrastructure de recharge disponible',
durationHours: 300,
properties: {
"capacity:vehicles": { label: 'Nombre de véhicules qui peuvent actuellement se brancher', writable: true },
"capacity:vehicles:max": { label: 'Nombre de véhicules maximum à pouvoir se brancher', writable: true }
"capacity:vehicles": {label: 'Nombre de véhicules qui peuvent actuellement se brancher', writable: true},
"capacity:vehicles:max": {label: 'Nombre de véhicules maximum à pouvoir se brancher', writable: true}
}
},
'traffic.counter.bicycle': {
@ -162,9 +162,9 @@ const oedb = {
description: 'Accident de la circulation',
durationHours: 6,
properties: {
severity: { label: 'Gravité', writable: true },
lanes_closed: { label: 'Voies fermées', writable: true },
vehicles: { label: 'Nombre de véhicules', writable: true }
severity: {label: 'Gravité', writable: true},
lanes_closed: {label: 'Voies fermées', writable: true},
vehicles: {label: 'Nombre de véhicules', writable: true}
}
},
'traffic.incident': {
@ -193,9 +193,9 @@ const oedb = {
description: 'Travaux sur la chaussée',
durationHours: 72,
properties: {
contractor: { label: 'Entreprise', writable: true },
reason: { label: 'Raison', writable: true },
lanes_affected: { label: 'Voies impactées', writable: true }
contractor: {label: 'Entreprise', writable: true},
reason: {label: 'Raison', writable: true},
lanes_affected: {label: 'Voies impactées', writable: true}
}
},
'traffic.OperatorAction.NetworkManagement.RoadOrCarriagewayOrLaneManagement': {
@ -206,7 +206,7 @@ const oedb = {
description: 'Événement de type Traffic OperatorAction NetworkManagement RoadOrCarriagewayOrLaneManagement',
durationHours: 24,
properties: {
source: { label: 'Source', writable: true },
source: {label: 'Source', writable: true},
}
},
@ -218,7 +218,7 @@ const oedb = {
description: 'Événement de type Traffic OperatorAction NetworkManagement RoadOrCarriagewayOrLaneManagement',
durationHours: 200,
properties: {
source: { label: 'Source', writable: true },
source: {label: 'Source', writable: true},
}
},
'traffic.OperatorAction.NetworkManagement.ReroutingManagement': {
@ -229,7 +229,7 @@ const oedb = {
description: 'Événement de type Traffic OperatorAction NetworkManagement ReroutingManagement',
durationHours: 200,
properties: {
source: { label: 'Source', writable: true },
source: {label: 'Source', writable: true},
}
},
'traffic.OperatorAction.ConstructionWorks': {
@ -240,7 +240,7 @@ const oedb = {
description: 'Événement de type Traffic OperatorAction ConstructionWorks',
durationHours: 200,
properties: {
source: { label: 'Source', writable: true },
source: {label: 'Source', writable: true},
}
},
'traffic.OperatorAction.NetworkManagement.SpeedManagement': {
@ -251,75 +251,75 @@ const oedb = {
description: 'Événement de type Traffic OperatorAction SpeedManagement',
durationHours: 200,
properties: {
source: { label: 'Source', writable: true },
source: {label: 'Source', writable: true},
}
},
// Gestion générale du réseau
'traffic.OperatorAction.NetworkManagement.GeneralNetworkManagement': {
emoji: '',
'traffic.OperatorAction.NetworkManagement.GeneralNetworkManagement': {
emoji: '',
image: 'static/cone.png',
label: 'Gestion générale du réseau',
category: 'Circulation',
description: 'Événement de type Traffic OperatorAction GeneralNetworkManagement',
durationHours: 200,
properties: {
source: { label: 'Source', writable: true },
}
},
'traffic.closed': {
emoji: '⛔',
label: 'Chaussée fermée',
category: 'Circulation',
description: 'Événement de type Traffic Closed',
durationHours: 200,
properties: {
source: { label: 'Source', writable: true },
}
},
'traffic.TrafficElement.GeneralInstructionOrMessageToRoadUsers': {
emoji: '',
image: 'static/cone.png',
label: 'Instruction ou message aux usagers',
category: 'Circulation',
description: 'Événement de type Traffic TrafficElement GeneralInstructionOrMessageToRoadUsers',
durationHours: 200,
properties: {
source: { label: 'Source', writable: true },
}
},
'traffic.TrafficElement.Obstruction.GeneralObstruction': {
emoji: '',
image: 'static/cone.png',
label: 'Obstruction générale',
category: 'Circulation',
description: 'Événement de type Traffic TrafficElement Obstruction GeneralObstruction',
durationHours: 200,
properties: {
source: { label: 'Source', writable: true },
}
},
'traffic.TrafficElement.Obstruction.InfrastructureDamageObstruction': {
emoji: '⛔',
label: 'Gestion générale du réseau',
category: 'Circulation',
description: 'Événement de type Traffic OperatorAction GeneralNetworkManagement',
durationHours: 200,
properties: {
source: {label: 'Source', writable: true},
}
},
'traffic.closed': {
emoji: '⛔',
label: 'Chaussée fermée',
category: 'Circulation',
description: 'Événement de type Traffic Closed',
durationHours: 200,
properties: {
source: {label: 'Source', writable: true},
}
},
'traffic.TrafficElement.GeneralInstructionOrMessageToRoadUsers': {
emoji: '',
image: 'static/cone.png',
label: 'Instruction ou message aux usagers',
category: 'Circulation',
description: 'Événement de type Traffic TrafficElement GeneralInstructionOrMessageToRoadUsers',
durationHours: 200,
properties: {
source: {label: 'Source', writable: true},
}
},
'traffic.TrafficElement.Obstruction.GeneralObstruction': {
emoji: '',
image: 'static/cone.png',
label: 'Obstruction générale',
category: 'Circulation',
description: 'Événement de type Traffic TrafficElement Obstruction GeneralObstruction',
durationHours: 200,
properties: {
source: {label: 'Source', writable: true},
}
},
'traffic.TrafficElement.Obstruction.InfrastructureDamageObstruction': {
emoji: '⛔',
label: 'Obstruction d\'infrastructure',
category: 'Circulation',
description: 'Événement de type Traffic TrafficElement Obstruction InfrastructureDamageObstruction',
durationHours: 200,
properties: {
source: { label: 'Source', writable: true },
}
},
'traffic.TrafficElement.Obstruction.EnvironmentalObstruction': {
emoji: '⛔',
label: 'Obstruction d\'infrastructure',
category: 'Circulation',
description: 'Événement de type Traffic TrafficElement Obstruction InfrastructureDamageObstruction',
durationHours: 200,
properties: {
source: {label: 'Source', writable: true},
}
},
'traffic.TrafficElement.Obstruction.EnvironmentalObstruction': {
emoji: '⛔',
label: 'Obstruction environnementale',
category: 'Circulation',
description: 'Événement de type Traffic TrafficElement Obstruction EnvironmentalObstruction',
durationHours: 200,
properties: {
source: { label: 'Source', writable: true },
}
},
label: 'Obstruction environnementale',
category: 'Circulation',
description: 'Événement de type Traffic TrafficElement Obstruction EnvironmentalObstruction',
durationHours: 200,
properties: {
source: {label: 'Source', writable: true},
}
},
'traffic.obstacle.flood': {
emoji: '🌊',
label: 'Chaussée inondée',
@ -379,9 +379,9 @@ const oedb = {
description: 'Tempête (vent fort)',
durationHours: 48,
properties: {
wind_speed: { label: 'Vent moyen (km/h)', writable: true },
wind_gust: { label: 'Rafales (km/h)', writable: true },
severity: { label: 'Sévérité', writable: true }
wind_speed: {label: 'Vent moyen (km/h)', writable: true},
wind_gust: {label: 'Rafales (km/h)', writable: true},
severity: {label: 'Sévérité', writable: true}
}
},
'weather.thunder': {
@ -391,7 +391,7 @@ const oedb = {
description: 'Activité orageuse',
durationHours: 12,
properties: {
lightning_count: { label: 'Nombre déclairs', writable: true }
lightning_count: {label: 'Nombre déclairs', writable: true}
}
}, 'weather.flood': {
emoji: '🌊',
@ -400,7 +400,7 @@ const oedb = {
description: 'Inondation',
durationHours: 24,
properties: {
flood_level: { label: 'Niveau d\'inondation', writable: true }
flood_level: {label: 'Niveau d\'inondation', writable: true}
}
},
'weather.snow': {
@ -410,7 +410,7 @@ const oedb = {
description: 'Neige',
durationHours: 12,
properties: {
snow_level: { label: 'Niveau de neige', writable: true }
snow_level: {label: 'Niveau de neige', writable: true}
}
},
'weather.earthquake': {
@ -420,8 +420,8 @@ const oedb = {
description: 'Séisme',
durationHours: 6,
properties: {
magnitude: { label: 'Magnitude (Mw)', writable: true },
depth_km: { label: 'Profondeur (km)', writable: true }
magnitude: {label: 'Magnitude (Mw)', writable: true},
depth_km: {label: 'Profondeur (km)', writable: true}
}
},
@ -433,8 +433,8 @@ const oedb = {
description: 'Interruption d\'itinéraire',
durationHours: 200,
properties: {
reason: { label: 'Raison', writable: true },
route: { label: 'Itinéraire', writable: true },
reason: {label: 'Raison', writable: true},
route: {label: 'Itinéraire', writable: true},
}
},
@ -445,8 +445,8 @@ const oedb = {
description: 'Mauvais sens de circulation',
durationHours: 200,
properties: {
reason: { label: 'Raison', writable: true },
route: { label: 'Itinéraire', writable: true },
reason: {label: 'Raison', writable: true},
route: {label: 'Itinéraire', writable: true},
}
},
@ -457,7 +457,7 @@ const oedb = {
description: 'Contestation d\'itinéraire',
durationHours: 200,
properties: {
route: { label: 'Itinéraire', writable: true },
route: {label: 'Itinéraire', writable: true},
}
}
// ici ajouter d'autres catégories d'évènements à suggérer

View file

@ -2,6 +2,7 @@
Middleware components for the OpenEventDatabase.
"""
import falcon
from oedb.utils.logging import logger
class HeaderMiddleware:
@ -9,6 +10,21 @@ class HeaderMiddleware:
Middleware that adds standard headers to all responses.
"""
def process_request(self, req, resp, resource, params):
"""
Handle preflight OPTIONS requests for CORS.
Args:
req: The request object.
resp: The response object.
resource: The resource object.
params: The request parameters.
"""
if req.method == 'OPTIONS':
logger.debug("Handling CORS preflight request")
resp.status = falcon.HTTP_200
return True # Skip further processing
def process_response(self, req, resp, resource, params):
"""
Add standard headers to the response.
@ -21,7 +37,15 @@ class HeaderMiddleware:
"""
logger.debug("Adding standard headers to response")
resp.set_header('X-Powered-By', 'OpenEventDatabase')
# CORS headers - Configuration optimisée pour embed.js
resp.set_header('Access-Control-Allow-Origin', '*')
resp.set_header('Access-Control-Allow-Headers', 'X-Requested-With')
resp.set_header('Access-Control-Allow-Headers', 'Content-Type')
resp.set_header('Access-Control-Allow-Methods','GET, POST, PUT, DELETE, OPTIONS')
resp.set_header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Authorization, Accept, Origin, User-Agent, Referer')
resp.set_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, HEAD')
resp.set_header('Access-Control-Allow-Credentials', 'false')
resp.set_header('Access-Control-Max-Age', '86400') # 24 hours
resp.set_header('Access-Control-Expose-Headers', 'Content-Length, Content-Type, Date, Server, X-Powered-By')
# Headers supplémentaires pour embed.js
resp.set_header('Vary', 'Origin')
resp.set_header('Cache-Control', 'public, max-age=300') # Cache de 5 minutes pour les requêtes embed

94
test_cors_embed.py Normal file
View file

@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""
Script de test pour vérifier la configuration CORS avec embed.js
"""
import requests
import json
def test_cors_headers():
"""Test des headers CORS pour l'API OEDB"""
# URL de l'API (à adapter selon votre configuration)
api_url = "http://localhost:8080/events"
print("🔍 Test de la configuration CORS pour embed.js")
print("=" * 50)
# Test 1: Requête GET simple
print("\n1. Test requête GET simple...")
try:
response = requests.get(api_url, timeout=10)
print(f" Status: {response.status_code}")
# Vérifier les headers CORS
cors_headers = {
'Access-Control-Allow-Origin': response.headers.get('Access-Control-Allow-Origin'),
'Access-Control-Allow-Headers': response.headers.get('Access-Control-Allow-Headers'),
'Access-Control-Allow-Methods': response.headers.get('Access-Control-Allow-Methods'),
'Access-Control-Max-Age': response.headers.get('Access-Control-Max-Age'),
'Access-Control-Expose-Headers': response.headers.get('Access-Control-Expose-Headers')
}
print(" Headers CORS:")
for header, value in cors_headers.items():
status = "" if value else ""
print(f" {status} {header}: {value}")
except requests.exceptions.RequestException as e:
print(f" ❌ Erreur de connexion: {e}")
return False
# Test 2: Requête OPTIONS (preflight)
print("\n2. Test requête OPTIONS (preflight)...")
try:
headers = {
'Origin': 'https://example.com',
'Access-Control-Request-Method': 'GET',
'Access-Control-Request-Headers': 'Content-Type'
}
response = requests.options(api_url, headers=headers, timeout=10)
print(f" Status: {response.status_code}")
if response.status_code == 200:
print(" ✅ Requête preflight réussie")
else:
print(" ❌ Requête preflight échouée")
except requests.exceptions.RequestException as e:
print(f" ❌ Erreur de connexion: {e}")
return False
# Test 3: Simulation d'une requête depuis embed.js
print("\n3. Test simulation embed.js...")
try:
headers = {
'Origin': 'https://example.com',
'User-Agent': 'Mozilla/5.0 (compatible; OEDB-Embed/1.0)',
'Referer': 'https://example.com/page-with-embed'
}
response = requests.get(api_url, headers=headers, timeout=10)
print(f" Status: {response.status_code}")
# Vérifier que la réponse contient des données
if response.status_code == 200:
try:
data = response.json()
print(f" ✅ Données reçues: {len(data.get('features', []))} événements")
except json.JSONDecodeError:
print(" ⚠️ Réponse reçue mais pas de JSON valide")
else:
print(f" ❌ Erreur HTTP: {response.status_code}")
except requests.exceptions.RequestException as e:
print(f" ❌ Erreur de connexion: {e}")
return False
print("\n" + "=" * 50)
print("✅ Tests CORS terminés")
return True
if __name__ == "__main__":
test_cors_headers()

180
test_embed_cors.html Normal file
View file

@ -0,0 +1,180 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test OEDB Embed avec CORS</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.test-section {
background: white;
padding: 20px;
margin: 20px 0;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.test-title {
color: #2c3e50;
border-bottom: 2px solid #3498db;
padding-bottom: 10px;
}
.embed-container {
border: 2px dashed #3498db;
min-height: 400px;
margin: 20px 0;
}
.config-display {
background: #f8f9fa;
padding: 15px;
border-radius: 4px;
font-family: monospace;
white-space: pre-wrap;
}
.status {
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.status.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
</style>
</head>
<body>
<h1>🧪 Test OEDB Embed avec Configuration CORS</h1>
<div class="test-section">
<h2 class="test-title">Configuration du Test</h2>
<div class="config-display" id="config-display">
Chargement de la configuration...
</div>
</div>
<div class="test-section">
<h2 class="test-title">Test de Connexion CORS</h2>
<div id="cors-status" class="status">
Test en cours...
</div>
<button onclick="testCorsConnection()">🔄 Tester la Connexion CORS</button>
</div>
<div class="test-section">
<h2 class="test-title">Intégration OEDB Embed</h2>
<p>Configuration utilisée :</p>
<div class="config-display" id="embed-config"></div>
<div class="embed-container" id="oedb-embed-container">
<div style="text-align: center; padding: 50px; color: #666;">
Chargement du widget OEDB...
</div>
</div>
</div>
<div class="test-section">
<h2 class="test-title">Logs de Debug</h2>
<div id="debug-logs" style="background: #2c3e50; color: #ecf0f1; padding: 15px; border-radius: 4px; font-family: monospace; max-height: 300px; overflow-y: auto;">
<div>🚀 Initialisation du test...</div>
</div>
</div>
<!-- Script OEDB Embed -->
<script src="frontend/public/embed.js"></script>
<script>
// Configuration de test
const testConfig = {
apiUrl: 'http://localhost:8080', // Ajustez selon votre configuration
theme: 'light',
limit: 10,
width: '100%',
height: '400px',
showMap: true,
showList: true,
autoRefresh: false
};
// Fonction de logging
function log(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString();
const logElement = document.getElementById('debug-logs');
const icon = type === 'error' ? '❌' : type === 'success' ? '✅' : '';
logElement.innerHTML += `<div>[${timestamp}] ${icon} ${message}</div>`;
logElement.scrollTop = logElement.scrollHeight;
}
// Test de connexion CORS
async function testCorsConnection() {
const statusElement = document.getElementById('cors-status');
statusElement.innerHTML = 'Test en cours...';
statusElement.className = 'status';
log('Début du test CORS...');
try {
const response = await fetch(`${testConfig.apiUrl}/events?limit=1`);
if (response.ok) {
const data = await response.json();
statusElement.innerHTML = `✅ Connexion CORS réussie ! (${data.features?.length || 0} événements)`;
statusElement.className = 'status success';
log('Connexion CORS réussie', 'success');
} else {
statusElement.innerHTML = `❌ Erreur HTTP: ${response.status}`;
statusElement.className = 'status error';
log(`Erreur HTTP: ${response.status}`, 'error');
}
} catch (error) {
statusElement.innerHTML = `❌ Erreur de connexion: ${error.message}`;
statusElement.className = 'status error';
log(`Erreur de connexion: ${error.message}`, 'error');
}
}
// Initialisation
document.addEventListener('DOMContentLoaded', function() {
log('Page chargée, initialisation...');
// Afficher la configuration
document.getElementById('config-display').textContent = JSON.stringify(testConfig, null, 2);
document.getElementById('embed-config').textContent = JSON.stringify(testConfig, null, 2);
// Test automatique de connexion
setTimeout(testCorsConnection, 1000);
// Initialiser le widget OEDB
setTimeout(() => {
try {
log('Initialisation du widget OEDB...');
if (window.OEDBEmbed) {
const embed = new OEDBEmbed(document.getElementById('oedb-embed-container'), testConfig);
log('Widget OEDB initialisé avec succès', 'success');
// Écouter les événements du widget
document.getElementById('oedb-embed-container').addEventListener('oedb-event-click', function(event) {
log(`Événement cliqué: ${event.detail.eventId}`);
});
} else {
log('Erreur: OEDBEmbed non disponible', 'error');
}
} catch (error) {
log(`Erreur lors de l'initialisation: ${error.message}`, 'error');
}
}, 2000);
});
</script>
</body>
</html>