Chargement des événements...
+
- } @else {
-
+
+
+ @if (isLoading) {
+
+
+
Chargement des événements...
+
+ } @else {
+
- }
+
\ No newline at end of file
diff --git a/frontend/src/app/pages/agenda/agenda.scss b/frontend/src/app/pages/agenda/agenda.scss
index d455ac4..1044106 100644
--- a/frontend/src/app/pages/agenda/agenda.scss
+++ b/frontend/src/app/pages/agenda/agenda.scss
@@ -1,8 +1,30 @@
.agenda-page {
- height: 100vh;
- display: flex;
- flex-direction: column;
+ min-height: 100vh;
background: #f8f9fa;
+ overflow-y: auto;
+}
+
+.layout {
+ display: grid;
+ grid-template-columns: 400px 1fr;
+ grid-template-rows: minmax(100vh, auto);
+ gap: 0;
+ min-height: 100vh;
+
+ &.is-small {
+ grid-template-columns: 100px 1fr;
+ }
+}
+
+.aside {
+ background: #f8f9fa;
+ border-right: 1px solid #e9ecef;
+ overflow-y: auto;
+}
+
+.main {
+ background: white;
+ overflow-y: auto;
}
.loading {
@@ -39,7 +61,7 @@
display: grid;
grid-template-columns: 320px 1fr auto;
grid-template-rows: 1fr;
- height: 100vh;
+ min-height: 500px;
}
.agenda-sidebar {
@@ -47,6 +69,7 @@
border-right: 1px solid #e9ecef;
overflow-y: auto;
padding: 12px;
+ max-height: 80vh;
}
.sidebar-header {
diff --git a/frontend/src/app/pages/agenda/agenda.ts b/frontend/src/app/pages/agenda/agenda.ts
index 67c17a4..f44004c 100644
--- a/frontend/src/app/pages/agenda/agenda.ts
+++ b/frontend/src/app/pages/agenda/agenda.ts
@@ -4,9 +4,11 @@ import { FormsModule } from '@angular/forms';
import { OedbApi } from '../../services/oedb-api';
import { EditForm } from '../../forms/edit-form/edit-form';
import { CalendarComponent, CalendarEvent } from './calendar/calendar';
+import { Menu } from '../home/menu/menu';
import oedb from '../../../oedb-types';
import { WhatFilterComponent } from '../../shared/what-filter/what-filter';
import { ActivatedRoute } from '@angular/router';
+import { subMonths, addMonths, format } from 'date-fns';
interface OedbEvent {
id: string;
@@ -29,7 +31,7 @@ interface OedbEvent {
@Component({
selector: 'app-agenda',
standalone: true,
- imports: [CommonModule, FormsModule, EditForm, CalendarComponent, WhatFilterComponent],
+ imports: [CommonModule, FormsModule, EditForm, CalendarComponent, WhatFilterComponent, Menu],
templateUrl: './agenda.html',
styleUrl: './agenda.scss'
})
@@ -47,20 +49,43 @@ export class Agenda implements OnInit {
groupedEvents: Array<{ dateKey: string; date: Date; items: OedbEvent[] }> = [];
availableWhatTypes: string[] = [];
selectedWhatFilter = 'culture';
+ selectedDate: Date | null = null;
ngOnInit() {
+ // Gérer les paramètres de requête ET les paramètres du fragment
this.route.queryParamMap.subscribe(map => {
const id = (map.get('id') || '').trim();
const what = (map.get('what') || '').trim();
const limitParam = map.get('limit');
const limit = limitParam ? Number(limitParam) : null;
+
+ // Définir le filtre what avant de charger les événements
+ if (what) {
+ this.selectedWhatFilter = what;
+ }
+
if (id) {
this.loadSingleEvent(id);
} else {
this.loadEvents({ what: what || undefined, limit: limit || undefined });
}
- if (what) {
- this.selectedWhatFilter = what;
+ });
+
+ // Gérer aussi les paramètres du fragment (pour les URLs avec #)
+ this.route.fragment.subscribe(fragment => {
+ console.log('🔗 Fragment reçu:', fragment);
+ if (fragment) {
+ // Nettoyer le fragment en supprimant le & initial s'il existe
+ const cleanFragment = fragment.startsWith('&') ? fragment.substring(1) : fragment;
+ console.log('🧹 Fragment nettoyé:', cleanFragment);
+ const params = new URLSearchParams(cleanFragment);
+ const what = params.get('what');
+ console.log('🎯 Paramètre what extrait:', what);
+ if (what) {
+ this.selectedWhatFilter = what;
+ console.log('✅ Filtre what défini:', this.selectedWhatFilter);
+ this.loadEvents({ what: what, limit: undefined });
+ }
}
});
}
@@ -68,23 +93,43 @@ export class Agenda implements OnInit {
loadEvents(overrides: { what?: string; limit?: number } = {}) {
this.isLoading = true;
const today = new Date();
- const startDate = new Date(today);
- startDate.setMonth(today.getMonth() - 1); // Charger 1 mois avant
- const endDate = new Date(today);
- endDate.setMonth(today.getMonth() + 3); // Charger 3 mois après
+
+ // Calculer startDate : 3 mois avant aujourd'hui (plus robuste avec date-fns)
+ const startDate = subMonths(today, 3);
+
+ // Calculer endDate : 3 mois après aujourd'hui (plus robuste avec date-fns)
+ const endDate = addMonths(today, 3);
const params: any = {
- start: startDate.toISOString().split('T')[0],
- end: endDate.toISOString().split('T')[0],
+ start: format(startDate, 'yyyy-MM-dd'),
+ end: format(endDate, 'yyyy-MM-dd'),
+ what: "culture",
limit: overrides.limit ?? 1000
};
if (overrides.what) params.what = overrides.what;
+ console.log('🔍 Chargement des événements avec paramètres:', params);
+ console.log('📅 Plage de dates:', {
+ start: startDate.toISOString(),
+ end: endDate.toISOString(),
+ today: today.toISOString(),
+ what: "culture",
+ startFormatted: format(startDate, 'yyyy-MM-dd'),
+ endFormatted: format(endDate, 'yyyy-MM-dd')
+ });
+
this.oedbApi.getEvents(params).subscribe((response: any) => {
+ console.log('📡 Réponse API reçue:', response);
this.events = Array.isArray(response?.features) ? response.features : [];
+ console.log('📊 Nombre d\'événements chargés:', this.events.length);
this.updateAvailableWhatTypes();
this.applyWhatFilter();
this.isLoading = false;
+ // Scroller vers le jour actuel après le chargement
+ this.scrollToToday();
+ }, (error) => {
+ console.error('❌ Erreur lors du chargement des événements:', error);
+ this.isLoading = false;
});
}
@@ -160,8 +205,10 @@ export class Agenda implements OnInit {
}
onDateClick(date: Date) {
- // Optionnel : gérer le clic sur une date
console.log('Date cliquée:', date);
+ this.selectedDate = date;
+ this.applyDateFilter();
+ this.scrollToDateInSidebar(date);
}
onEventSaved() {
@@ -187,14 +234,23 @@ export class Agenda implements OnInit {
}
applyWhatFilter() {
+ console.log('🔍 Application du filtre what:', this.selectedWhatFilter);
+ console.log('📊 Événements avant filtrage:', this.events.length);
+
if (this.selectedWhatFilter) {
const prefix = this.selectedWhatFilter;
this.filteredEvents = this.events.filter(e => String(e?.properties?.what || '').startsWith(prefix));
+ console.log('✅ Événements après filtrage par', prefix + ':', this.filteredEvents.length);
} else {
this.filteredEvents = [...this.events];
+ console.log('📋 Aucun filtre appliqué, tous les événements conservés');
}
this.convertToCalendarEvents();
- this.buildGroupedEvents();
+ if (this.selectedDate) {
+ this.applyDateFilter();
+ } else {
+ this.buildGroupedEvents();
+ }
}
onWhatFilterChange(value: string) {
@@ -228,6 +284,31 @@ export class Agenda implements OnInit {
this.groupedEvents = result;
}
+ applyDateFilter() {
+ if (!this.selectedDate) {
+ this.buildGroupedEvents();
+ return;
+ }
+
+ const selectedDateKey = this.toDateKey(this.selectedDate);
+ const groups: Record
= {};
+ const source = this.filteredEvents.length ? this.filteredEvents : this.events;
+
+ for (const ev of source) {
+ const d = this.getEventStartDate(ev);
+ const key = this.toDateKey(d);
+ if (key === selectedDateKey) {
+ if (!groups[key]) groups[key] = { date: new Date(d.getFullYear(), d.getMonth(), d.getDate()), items: [] };
+ groups[key].items.push(ev);
+ }
+ }
+
+ const result = Object.keys(groups)
+ .sort((a, b) => groups[a].date.getTime() - groups[b].date.getTime())
+ .map(k => ({ dateKey: k, date: groups[k].date, items: groups[k].items.sort((a, b) => this.getEventStartDate(a).getTime() - this.getEventStartDate(b).getTime()) }));
+ this.groupedEvents = result;
+ }
+
formatDayHeader(d: Date): string {
const days = ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'];
const months = ['janvier','février','mars','avril','mai','juin','juillet','août','septembre','octobre','novembre','décembre'];
@@ -253,4 +334,30 @@ export class Agenda implements OnInit {
selectFromSidebar(ev: OedbEvent) {
this.selectedEvent = ev;
}
+
+ scrollToDateInSidebar(date: Date) {
+ setTimeout(() => {
+ const dateKey = this.toDateKey(date);
+ const dayGroupElement = document.querySelector(`[data-date-key="${dateKey}"]`);
+ if (dayGroupElement) {
+ dayGroupElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ }
+ }, 100);
+ }
+
+ scrollToToday() {
+ setTimeout(() => {
+ const today = new Date();
+ const todayKey = this.toDateKey(today);
+ const todayGroupElement = document.querySelector(`[data-date-key="${todayKey}"]`);
+ if (todayGroupElement) {
+ todayGroupElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ }
+ }, 100);
+ }
+
+ clearDateFilter() {
+ this.selectedDate = null;
+ this.buildGroupedEvents();
+ }
}
\ No newline at end of file
diff --git a/frontend/src/app/pages/batch-edit/batch-edit.html b/frontend/src/app/pages/batch-edit/batch-edit.html
new file mode 100644
index 0000000..8ddbb8c
--- /dev/null
+++ b/frontend/src/app/pages/batch-edit/batch-edit.html
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
Filtres
+
+
+ Recherche textuelle :
+
+
+
+
+ Type d'événement :
+
+ Tous les types
+ @for (whatType of availableWhatTypes; track whatType) {
+ {{whatType}}
+ }
+
+
+
+
+
+ {{selectedEvents().size}} événement(s) sélectionné(s)
+
+
+ Tout sélectionner
+ Tout désélectionner
+
+
+
+
+
+
+
+
Opération en masse
+
+
+
+ @if (batchResult()) {
+
+
Résultat de l'opération
+
+
+ {{batchResult()!.success}}
+ Succès
+
+
+ {{batchResult()!.failed}}
+ Échecs
+
+
+ {{batchResult()!.networkErrors}}
+ Erreurs réseau
+
+
+ {{batchResult()!.total}}
+ Total
+
+
+
+ }
+
+
+
+
+
+
+
+
+ @for (event of filteredEvents; track event.id) {
+
+ }
+
+
+
+
+
diff --git a/frontend/src/app/pages/batch-edit/batch-edit.scss b/frontend/src/app/pages/batch-edit/batch-edit.scss
new file mode 100644
index 0000000..0a11bb1
--- /dev/null
+++ b/frontend/src/app/pages/batch-edit/batch-edit.scss
@@ -0,0 +1,365 @@
+.batch-edit-page {
+ min-height: 100vh;
+ background: #f8f9fa;
+ padding: 2rem 0;
+ display: flex;
+ flex-direction: column;
+
+ .container {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 0 1rem;
+ }
+
+ .page-header {
+ text-align: center;
+ margin-bottom: 3rem;
+
+ h1 {
+ color: #2c3e50;
+ margin-bottom: 0.5rem;
+ }
+
+ p {
+ color: #6c757d;
+ font-size: 1.1rem;
+ }
+ }
+
+ .filters-section {
+ margin-bottom: 2rem;
+
+ .filters-card {
+ background: white;
+ padding: 2rem;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+
+ h2 {
+ color: #2c3e50;
+ margin-bottom: 1.5rem;
+ border-bottom: 2px solid #3498db;
+ padding-bottom: 0.5rem;
+ }
+
+ .filter-group {
+ margin-bottom: 1.5rem;
+
+ label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 600;
+ color: #2c3e50;
+ }
+
+ input, select {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ font-size: 1rem;
+ transition: border-color 0.3s;
+
+ &:focus {
+ outline: none;
+ border-color: #3498db;
+ box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
+ }
+ }
+
+ .search-input {
+ font-size: 1.1rem;
+ padding: 1rem;
+ }
+ }
+
+ .selection-info {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem;
+ background: #f8f9fa;
+ border-radius: 6px;
+ margin-top: 1rem;
+
+ .selection-count {
+ font-weight: 600;
+ color: #3498db;
+ }
+
+ .selection-actions {
+ display: flex;
+ gap: 0.5rem;
+ }
+ }
+ }
+ }
+
+ .batch-operation-section {
+ margin-bottom: 2rem;
+
+ .operation-card {
+ background: white;
+ padding: 2rem;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+
+ h2 {
+ color: #2c3e50;
+ margin-bottom: 1.5rem;
+ border-bottom: 2px solid #e74c3c;
+ padding-bottom: 0.5rem;
+ }
+
+ .operation-form {
+ .form-group {
+ margin-bottom: 1.5rem;
+
+ label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 600;
+ color: #2c3e50;
+ }
+
+ input, select {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ font-size: 1rem;
+ transition: border-color 0.3s;
+
+ &:focus {
+ outline: none;
+ border-color: #3498db;
+ box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
+ }
+ }
+ }
+
+ .form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+
+ @media (max-width: 768px) {
+ grid-template-columns: 1fr;
+ }
+ }
+
+ .form-actions {
+ margin-top: 2rem;
+ padding-top: 1rem;
+ border-top: 1px solid #ecf0f1;
+ }
+ }
+
+ .result-summary {
+ margin-top: 2rem;
+ padding: 1.5rem;
+ background: #f8f9fa;
+ border-radius: 6px;
+
+ h3 {
+ color: #2c3e50;
+ margin-bottom: 1rem;
+ }
+
+ .result-stats {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+ gap: 1rem;
+
+ .stat {
+ text-align: center;
+ padding: 1rem;
+ border-radius: 6px;
+
+ .stat-number {
+ display: block;
+ font-size: 2rem;
+ font-weight: 700;
+ margin-bottom: 0.25rem;
+ }
+
+ .stat-label {
+ font-size: 0.9rem;
+ font-weight: 500;
+ }
+
+ &.success {
+ background: #d4edda;
+ color: #155724;
+
+ .stat-number {
+ color: #28a745;
+ }
+ }
+
+ &.failed {
+ background: #f8d7da;
+ color: #721c24;
+
+ .stat-number {
+ color: #dc3545;
+ }
+ }
+
+ &.network-error {
+ background: #fff3cd;
+ color: #856404;
+
+ .stat-number {
+ color: #ffc107;
+ }
+ }
+
+ &.total {
+ background: #d1ecf1;
+ color: #0c5460;
+
+ .stat-number {
+ color: #17a2b8;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .events-section {
+ .events-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1.5rem;
+ padding-bottom: 1rem;
+ border-bottom: 2px solid #3498db;
+
+ h2 {
+ color: #2c3e50;
+ margin: 0;
+ }
+
+ .view-options {
+ display: flex;
+ gap: 0.5rem;
+ }
+ }
+
+ .events-content {
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+
+ .events-list {
+ max-height: 70vh;
+ overflow-y: auto;
+
+ .event-card {
+ display: flex;
+ align-items: center;
+ padding: 1rem;
+ border-bottom: 1px solid #ecf0f1;
+ cursor: pointer;
+ transition: all 0.3s;
+
+ &:hover {
+ background: #f8f9fa;
+ }
+
+ &.selected {
+ background: #e3f2fd;
+ border-left: 4px solid #3498db;
+ }
+
+ .event-checkbox {
+ margin-right: 1rem;
+
+ input[type="checkbox"] {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+ }
+ }
+
+ .event-content {
+ flex: 1;
+
+ .event-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 0.5rem;
+
+ .event-title {
+ color: #2c3e50;
+ margin: 0;
+ font-size: 1rem;
+ font-weight: 600;
+ flex: 1;
+ margin-right: 1rem;
+ }
+
+ .event-type {
+ background: #3498db;
+ color: white;
+ padding: 0.25rem 0.5rem;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ font-weight: 500;
+ white-space: nowrap;
+ }
+ }
+
+ .event-meta {
+ .event-date {
+ font-size: 0.9rem;
+ color: #6c757d;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .btn {
+ padding: 0.75rem 1.5rem;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 1rem;
+ font-weight: 600;
+ transition: all 0.3s;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+
+ &:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ }
+
+ &.btn-primary {
+ background: #e74c3c;
+ color: white;
+
+ &:hover:not(:disabled) {
+ background: #c0392b;
+ }
+ }
+
+ &.btn-secondary {
+ background: #95a5a6;
+ color: white;
+
+ &:hover:not(:disabled) {
+ background: #7f8c8d;
+ }
+ }
+ }
+}
diff --git a/frontend/src/app/pages/batch-edit/batch-edit.ts b/frontend/src/app/pages/batch-edit/batch-edit.ts
new file mode 100644
index 0000000..9f09915
--- /dev/null
+++ b/frontend/src/app/pages/batch-edit/batch-edit.ts
@@ -0,0 +1,239 @@
+import { Component, inject, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { OedbApi } from '../../services/oedb-api';
+import { AllEvents } from '../../maps/all-events/all-events';
+import { Menu } from '../home/menu/menu';
+
+interface BatchOperation {
+ type: 'changeWhat' | 'setField' | 'delete' | 'none';
+ what?: string;
+ fieldKey?: string;
+ fieldValue?: any;
+}
+
+interface BatchResult {
+ success: number;
+ failed: number;
+ networkErrors: number;
+ total: number;
+}
+
+@Component({
+ selector: 'app-batch-edit',
+ standalone: true,
+ imports: [CommonModule, FormsModule, AllEvents, Menu],
+ templateUrl: './batch-edit.html',
+ styleUrl: './batch-edit.scss'
+})
+export class BatchEdit {
+ private oedbApi = inject(OedbApi);
+
+ events = signal([]);
+ selectedEvents = signal>(new Set());
+ batchOperation = signal({ type: 'none' });
+ batchResult = signal(null);
+ isLoading = signal(false);
+ isProcessing = signal(false);
+
+ // Filtres
+ searchText = '';
+ selectedWhatFilter = '';
+ availableWhatTypes: string[] = [];
+
+ constructor() {
+ this.loadEvents();
+ }
+
+ loadEvents() {
+ this.isLoading.set(true);
+ this.oedbApi.getEvents({ limit: 1000 }).subscribe({
+ next: (response: any) => {
+ this.events.set(Array.isArray(response?.features) ? response.features : []);
+ this.updateAvailableWhatTypes();
+ this.isLoading.set(false);
+ },
+ error: (error) => {
+ console.error('Erreur lors du chargement des événements:', error);
+ this.isLoading.set(false);
+ }
+ });
+ }
+
+ updateAvailableWhatTypes() {
+ const whatTypes = new Set();
+ this.events().forEach(event => {
+ if (event?.properties?.what) {
+ whatTypes.add(event.properties.what);
+ }
+ });
+ this.availableWhatTypes = Array.from(whatTypes).sort();
+ }
+
+ get filteredEvents() {
+ let filtered = this.events();
+
+ if (this.searchText.trim()) {
+ const searchLower = this.searchText.toLowerCase();
+ filtered = filtered.filter(event => {
+ const label = event?.properties?.label || event?.properties?.name || '';
+ const description = event?.properties?.description || '';
+ const what = event?.properties?.what || '';
+ return label.toLowerCase().includes(searchLower) ||
+ description.toLowerCase().includes(searchLower) ||
+ what.toLowerCase().includes(searchLower);
+ });
+ }
+
+ if (this.selectedWhatFilter) {
+ filtered = filtered.filter(event => {
+ const what = event?.properties?.what || '';
+ return what.startsWith(this.selectedWhatFilter + '.') || what === this.selectedWhatFilter;
+ });
+ }
+
+ return filtered;
+ }
+
+ toggleEventSelection(eventId: string | number) {
+ const selected = new Set(this.selectedEvents());
+ if (selected.has(eventId)) {
+ selected.delete(eventId);
+ } else {
+ selected.add(eventId);
+ }
+ this.selectedEvents.set(selected);
+ }
+
+ selectAll() {
+ const allIds = this.filteredEvents.map(event => event.id || event.properties?.id);
+ this.selectedEvents.set(new Set(allIds));
+ }
+
+ clearSelection() {
+ this.selectedEvents.set(new Set());
+ }
+
+ onBatchOperationChange() {
+ this.batchResult.set(null);
+ }
+
+ async applyBatchOperation() {
+ const selectedIds = Array.from(this.selectedEvents());
+ const operation = this.batchOperation();
+
+ if (selectedIds.length === 0 || operation.type === 'none') {
+ return;
+ }
+
+ this.isProcessing.set(true);
+ this.batchResult.set(null);
+
+ let success = 0;
+ let failed = 0;
+ let networkErrors = 0;
+
+ try {
+ if (operation.type === 'delete') {
+ for (const id of selectedIds) {
+ try {
+ await this.oedbApi.deleteEvent(id).toPromise();
+ success++;
+ } catch (error: any) {
+ if (error?.status === 0) {
+ networkErrors++;
+ } else {
+ failed++;
+ }
+ }
+ }
+ } else if (operation.type === 'changeWhat') {
+ for (const id of selectedIds) {
+ try {
+ const event = this.events().find(e => (e.id || e.properties?.id) === id);
+ if (event) {
+ const updated = {
+ ...event,
+ properties: { ...event.properties, what: operation.what }
+ };
+ await this.oedbApi.updateEvent(id, updated).toPromise();
+ success++;
+ } else {
+ failed++;
+ }
+ } catch (error: any) {
+ if (error?.status === 0) {
+ networkErrors++;
+ } else {
+ failed++;
+ }
+ }
+ }
+ } else if (operation.type === 'setField') {
+ for (const id of selectedIds) {
+ try {
+ const event = this.events().find(e => (e.id || e.properties?.id) === id);
+ if (event && operation.fieldKey) {
+ const updated = {
+ ...event,
+ properties: { ...event.properties, [operation.fieldKey]: operation.fieldValue }
+ };
+ await this.oedbApi.updateEvent(id, updated).toPromise();
+ success++;
+ } else {
+ failed++;
+ }
+ } catch (error: any) {
+ if (error?.status === 0) {
+ networkErrors++;
+ } else {
+ failed++;
+ }
+ }
+ }
+ }
+
+ this.batchResult.set({
+ success,
+ failed,
+ networkErrors,
+ total: selectedIds.length
+ });
+
+ // Recharger les événements après l'opération
+ this.loadEvents();
+ this.clearSelection();
+
+ } catch (error) {
+ console.error('Erreur lors de l\'opération en masse:', error);
+ } finally {
+ this.isProcessing.set(false);
+ }
+ }
+
+ getEventTitle(event: any): string {
+ return event?.properties?.label || event?.properties?.name || 'Événement sans nom';
+ }
+
+ getEventType(event: any): string {
+ return event?.properties?.what || '';
+ }
+
+ getEventDate(event: any): string {
+ return event?.properties?.start || event?.properties?.when || '';
+ }
+
+ formatDate(dateString: string): string {
+ if (!dateString) return '';
+ try {
+ const date = new Date(dateString);
+ return date.toLocaleDateString('fr-FR', {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric'
+ });
+ } catch {
+ return dateString;
+ }
+ }
+}
diff --git a/frontend/src/app/pages/embed/embed.html b/frontend/src/app/pages/embed/embed.html
new file mode 100644
index 0000000..67ad5dd
--- /dev/null
+++ b/frontend/src/app/pages/embed/embed.html
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
Comment utiliser
+
+ Configurez les paramètres ci-dessus selon vos besoins
+ Copiez le code généré
+ Collez-le dans votre page HTML
+ Le script chargera automatiquement les événements depuis l'API OEDB
+
+
+
+
Fonctionnalités
+
+ ✅ Affichage responsive des événements
+ ✅ Filtrage par type et dates
+ ✅ Thèmes clair et sombre
+ ✅ Mise à jour automatique
+ ✅ Compatible avec tous les navigateurs
+
+
+
+
+
+
diff --git a/frontend/src/app/pages/embed/embed.scss b/frontend/src/app/pages/embed/embed.scss
new file mode 100644
index 0000000..e00e80d
--- /dev/null
+++ b/frontend/src/app/pages/embed/embed.scss
@@ -0,0 +1,220 @@
+.embed-page {
+ min-height: 100vh;
+ background: #f8f9fa;
+ padding: 2rem 0;
+}
+
+.layout {
+ display: grid;
+ grid-template-columns: 400px 1fr;
+ grid-template-rows: minmax(100vh, auto);
+ gap: 0;
+ min-height: 100vh;
+
+ &.is-small {
+ grid-template-columns: 100px 1fr;
+ }
+}
+
+.aside {
+ background: #f8f9fa;
+ border-right: 1px solid #e9ecef;
+ overflow-y: auto;
+}
+
+.main {
+ background: white;
+ overflow-y: auto;
+}
+
+ .container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 1rem;
+ }
+
+ .page-header {
+ text-align: center;
+ margin-bottom: 3rem;
+
+ h1 {
+ color: #2c3e50;
+ margin-bottom: 0.5rem;
+ }
+
+ p {
+ color: #6c757d;
+ font-size: 1.1rem;
+ }
+ }
+
+ .embed-config {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 2rem;
+ margin-bottom: 3rem;
+
+ @media (max-width: 768px) {
+ grid-template-columns: 1fr;
+ }
+ }
+
+ .config-form {
+ background: white;
+ padding: 2rem;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+
+ h2 {
+ color: #2c3e50;
+ margin-bottom: 1.5rem;
+ border-bottom: 2px solid #3498db;
+ padding-bottom: 0.5rem;
+ }
+
+ .form-group {
+ margin-bottom: 1.5rem;
+
+ label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 600;
+ color: #2c3e50;
+ }
+
+ input, select {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ font-size: 1rem;
+ transition: border-color 0.3s;
+
+ &:focus {
+ outline: none;
+ border-color: #3498db;
+ box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
+ }
+ }
+ }
+
+ .form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+ }
+ }
+
+ .code-output {
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+
+ .code-header {
+ background: #2c3e50;
+ color: white;
+ padding: 1rem 1.5rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ h2 {
+ margin: 0;
+ font-size: 1.2rem;
+ }
+
+ .code-actions {
+ display: flex;
+ gap: 0.5rem;
+ }
+ }
+
+ .code-container {
+ padding: 1.5rem;
+ background: #f8f9fa;
+
+ pre {
+ margin: 0;
+ background: #2c3e50;
+ color: #ecf0f1;
+ padding: 1rem;
+ border-radius: 4px;
+ overflow-x: auto;
+ font-size: 0.9rem;
+ line-height: 1.4;
+
+ code {
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ }
+ }
+ }
+ }
+
+ .usage-info {
+ background: white;
+ padding: 2rem;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+
+ h2 {
+ color: #2c3e50;
+ margin-bottom: 1rem;
+ }
+
+ h3 {
+ color: #34495e;
+ margin: 1.5rem 0 1rem 0;
+ }
+
+ ol {
+ padding-left: 1.5rem;
+ margin-bottom: 2rem;
+
+ li {
+ margin-bottom: 0.5rem;
+ line-height: 1.6;
+ }
+ }
+
+ .features {
+ ul {
+ list-style: none;
+ padding: 0;
+
+ li {
+ padding: 0.5rem 0;
+ border-bottom: 1px solid #ecf0f1;
+ color: #2c3e50;
+ }
+ }
+ }
+ }
+
+ .btn {
+ padding: 0.5rem 1rem;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9rem;
+ font-weight: 600;
+ transition: all 0.3s;
+
+ &.btn-primary {
+ background: #3498db;
+ color: white;
+
+ &:hover {
+ background: #2980b9;
+ }
+ }
+
+ &.btn-secondary {
+ background: #95a5a6;
+ color: white;
+
+ &:hover {
+ background: #7f8c8d;
+ }
+ }
+ }
diff --git a/frontend/src/app/pages/embed/embed.spec.ts b/frontend/src/app/pages/embed/embed.spec.ts
new file mode 100644
index 0000000..5fa375a
--- /dev/null
+++ b/frontend/src/app/pages/embed/embed.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { Embed } from './embed';
+
+describe('Embed', () => {
+ let component: Embed;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [Embed]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(Embed);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/pages/embed/embed.ts b/frontend/src/app/pages/embed/embed.ts
new file mode 100644
index 0000000..2523141
--- /dev/null
+++ b/frontend/src/app/pages/embed/embed.ts
@@ -0,0 +1,103 @@
+import { Component, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { Menu } from '../home/menu/menu';
+
+interface EmbedConfig {
+ apiUrl: string;
+ what: string;
+ start: string;
+ end: string;
+ limit: number;
+ width: string;
+ height: string;
+ theme: string;
+}
+
+@Component({
+ selector: 'app-embed',
+ standalone: true,
+ imports: [CommonModule, FormsModule, Menu],
+ templateUrl: './embed.html',
+ styleUrl: './embed.scss'
+})
+export class Embed {
+ config = signal({
+ apiUrl: 'https://api.oedb.fr',
+ what: 'culture',
+ start: '',
+ end: '',
+ limit: 50,
+ width: '100%',
+ height: '400px',
+ theme: 'light'
+ });
+
+ generatedCode = signal('');
+
+ updateConfig() {
+ const config = this.config();
+ const code = this.generateEmbedCode(config);
+ this.generatedCode.set(code);
+ }
+
+ private generateEmbedCode(config: EmbedConfig): string {
+ const params = new URLSearchParams();
+ if (config.what) params.set('what', config.what);
+ if (config.start) params.set('start', config.start);
+ if (config.end) params.set('end', config.end);
+ if (config.limit) params.set('limit', config.limit.toString());
+
+ const queryString = params.toString();
+ const scriptUrl = `${window.location.origin}/embed.js`;
+
+ return `
+
+
+`;
+ }
+
+ copyToClipboard() {
+ const code = this.generatedCode();
+ navigator.clipboard.writeText(code).then(() => {
+ // Optionnel : afficher une notification de succès
+ console.log('Code copié dans le presse-papiers');
+ });
+ }
+
+ preview() {
+ // Ouvrir une nouvelle fenêtre avec un aperçu
+ const previewWindow = window.open('', '_blank', 'width=800,height=600');
+ if (previewWindow) {
+ const config = this.config();
+ const code = this.generateEmbedCode(config);
+ previewWindow.document.write(`
+
+
+
+ Aperçu OEDB Embed
+
+
+
+ Aperçu de l'intégration
+
+ ${code}
+
+
+
+ `);
+ }
+ }
+}
diff --git a/frontend/src/app/pages/home/menu/menu.html b/frontend/src/app/pages/home/menu/menu.html
index 127860a..b4e0fde 100644
--- a/frontend/src/app/pages/home/menu/menu.html
+++ b/frontend/src/app/pages/home/menu/menu.html
@@ -1,12 +1,40 @@
- OpenEventDatabase
+
+
- Accueil
- Community à venir
- Docs événements
+
+
+
+
+
+
+
+
+
- stats
- sources
(editor)
diff --git a/frontend/src/app/pages/home/menu/menu.scss b/frontend/src/app/pages/home/menu/menu.scss
index d79594b..252b38d 100644
--- a/frontend/src/app/pages/home/menu/menu.scss
+++ b/frontend/src/app/pages/home/menu/menu.scss
@@ -1,6 +1,109 @@
:host {
display: block;
+ background: #f8f9fa;
+ border-right: 1px solid #e9ecef;
+ min-height: 100vh;
+ padding: 1rem;
+}
+.menu-header {
+ margin-bottom: 2rem;
+ padding-bottom: 1rem;
+ border-bottom: 2px solid #3498db;
+
+ h1 {
+ color: #2c3e50;
+ margin: 0;
+ font-size: 1.5rem;
+ font-weight: 700;
+ }
+}
+
+.nav {
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+}
+
+.nav-section {
+ h3 {
+ color: #6c757d;
+ font-size: 0.9rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-bottom: 0.75rem;
+ padding-left: 0.5rem;
+ border-left: 3px solid #3498db;
+ }
+
+ &.quick-tools {
+ background: linear-gradient(135deg, #3498db, #2980b9);
+ border-radius: 8px;
+ padding: 1rem;
+ margin-bottom: 1rem;
+
+ h3 {
+ color: white;
+ border-left: 3px solid white;
+ margin-bottom: 1rem;
+ }
+
+ .link.highlight {
+ background: rgba(255, 255, 255, 0.1);
+ color: white;
+ font-weight: 600;
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ margin-bottom: 0.5rem;
+
+ &:hover {
+ background: rgba(255, 255, 255, 0.2);
+ transform: translateX(4px);
+ border-color: rgba(255, 255, 255, 0.4);
+ }
+
+ &.router-link-active {
+ background: rgba(255, 255, 255, 0.3);
+ border-color: rgba(255, 255, 255, 0.5);
+ }
+ }
+ }
+}
+
+.link, .link-button {
+ display: block;
+ padding: 0.75rem 1rem;
+ color: #2c3e50;
+ text-decoration: none;
+ border-radius: 6px;
+ transition: all 0.3s ease;
+ margin-bottom: 0.25rem;
+ font-weight: 500;
+ border: none;
+ background: none;
+ width: 100%;
+ text-align: left;
+ cursor: pointer;
+ font-size: 0.95rem;
+
+ &:hover {
+ background: #3498db;
+ color: white;
+ transform: translateX(4px);
+ }
+
+ &.router-link-active {
+ background: #2980b9;
+ color: white;
+ font-weight: 600;
+ }
+}
+
+.link-button {
+ &:hover {
+ background: #e74c3c;
+ color: white;
+ }
}
#what_categories {
diff --git a/frontend/src/app/pages/home/menu/menu.ts b/frontend/src/app/pages/home/menu/menu.ts
index 7498084..948607f 100644
--- a/frontend/src/app/pages/home/menu/menu.ts
+++ b/frontend/src/app/pages/home/menu/menu.ts
@@ -1,6 +1,6 @@
-import { Component } from '@angular/core';
+import { Component, inject } from '@angular/core';
import oedb_what_categories from '../../../../oedb-types';
-import { RouterLink } from "@angular/router";
+import { RouterLink, Router } from "@angular/router";
@Component({
selector: 'app-menu',
@@ -10,6 +10,7 @@ import { RouterLink } from "@angular/router";
styleUrl: './menu.scss'
})
export class Menu {
+ private router = inject(Router);
public oedb_what_categories: Array = [];
public onToggleView?: () => void;
diff --git a/frontend/src/app/pages/research/research.html b/frontend/src/app/pages/research/research.html
new file mode 100644
index 0000000..be2c55d
--- /dev/null
+++ b/frontend/src/app/pages/research/research.html
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+ @if (hasSearched()) {
+
+
+
+ @if (searchResults() && searchResults()!.features.length > 0) {
+
+
+
+
+
Liste des événements
+
+ @for (event of searchResults()!.features; track event.id) {
+
+
+
+ @if (getEventDate(event)) {
+
+ 📅 {{formatDate(getEventDate(event))}}
+
+ }
+ @if (getEventLocation(event)) {
+
+ 📍 {{getEventLocation(event)}}
+
+ }
+
+
+ }
+
+
+
+ } @else if (searchResults() && searchResults()!.features.length === 0) {
+
+
Aucun événement trouvé
+
Essayez de modifier vos critères de recherche
+
+ }
+
+ }
+
+
diff --git a/frontend/src/app/pages/research/research.scss b/frontend/src/app/pages/research/research.scss
new file mode 100644
index 0000000..62414f0
--- /dev/null
+++ b/frontend/src/app/pages/research/research.scss
@@ -0,0 +1,279 @@
+.research-page {
+ min-height: 100vh;
+ background: #f8f9fa;
+
+ .container {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 2rem 1rem;
+ }
+
+ .page-header {
+ text-align: center;
+ margin-bottom: 3rem;
+
+ h1 {
+ color: #2c3e50;
+ margin-bottom: 0.5rem;
+ }
+
+ p {
+ color: #6c757d;
+ font-size: 1.1rem;
+ }
+ }
+
+ .search-section {
+ margin-bottom: 3rem;
+
+ .search-form {
+ background: white;
+ padding: 2rem;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+
+ h2 {
+ color: #2c3e50;
+ margin-bottom: 1.5rem;
+ border-bottom: 2px solid #3498db;
+ padding-bottom: 0.5rem;
+ }
+
+ .form-group {
+ margin-bottom: 1.5rem;
+
+ label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 600;
+ color: #2c3e50;
+ }
+
+ input, select {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid #ddd;
+ border-radius: 4px;
+ font-size: 1rem;
+ transition: border-color 0.3s;
+
+ &:focus {
+ outline: none;
+ border-color: #3498db;
+ box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
+ }
+ }
+
+ .search-input {
+ font-size: 1.1rem;
+ padding: 1rem;
+ }
+ }
+
+ .form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+
+ @media (max-width: 768px) {
+ grid-template-columns: 1fr;
+ }
+ }
+
+ .form-actions {
+ display: flex;
+ gap: 1rem;
+ margin-top: 2rem;
+ padding-top: 1rem;
+ border-top: 1px solid #ecf0f1;
+ }
+ }
+ }
+
+ .results-section {
+ .results-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 2rem;
+ padding: 1rem 0;
+ border-bottom: 2px solid #3498db;
+
+ h2 {
+ color: #2c3e50;
+ margin: 0;
+ }
+
+ .results-info {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 0.25rem;
+
+ .results-count {
+ font-weight: 600;
+ color: #27ae60;
+ }
+
+ .search-query {
+ font-size: 0.9rem;
+ color: #6c757d;
+ font-style: italic;
+ }
+ }
+ }
+
+ .results-content {
+ display: grid;
+ grid-template-columns: 1fr 400px;
+ gap: 2rem;
+
+ @media (max-width: 1024px) {
+ grid-template-columns: 1fr;
+ }
+
+ .map-container {
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+ min-height: 500px;
+
+ app-all-events {
+ display: block;
+ height: 100%;
+ min-height: 500px;
+ }
+ }
+
+ .events-list {
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+ padding: 1.5rem;
+ max-height: 600px;
+ overflow-y: auto;
+
+ h3 {
+ color: #2c3e50;
+ margin-bottom: 1rem;
+ padding-bottom: 0.5rem;
+ border-bottom: 1px solid #ecf0f1;
+ }
+
+ .events-grid {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ .event-card {
+ padding: 1rem;
+ border: 1px solid #ecf0f1;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 0.3s;
+
+ &:hover {
+ border-color: #3498db;
+ box-shadow: 0 2px 8px rgba(52, 152, 219, 0.2);
+ transform: translateY(-2px);
+ }
+
+ .event-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ margin-bottom: 0.75rem;
+
+ .event-title {
+ color: #2c3e50;
+ margin: 0;
+ font-size: 1rem;
+ font-weight: 600;
+ flex: 1;
+ margin-right: 1rem;
+ }
+
+ .event-type {
+ background: #3498db;
+ color: white;
+ padding: 0.25rem 0.5rem;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ font-weight: 500;
+ white-space: nowrap;
+ }
+ }
+
+ .event-details {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+
+ .event-date, .event-location {
+ font-size: 0.9rem;
+ color: #6c757d;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .no-results {
+ text-align: center;
+ padding: 3rem;
+ background: white;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+
+ h3 {
+ color: #6c757d;
+ margin-bottom: 1rem;
+ }
+
+ p {
+ color: #95a5a6;
+ }
+ }
+ }
+
+ .btn {
+ padding: 0.75rem 1.5rem;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 1rem;
+ font-weight: 600;
+ transition: all 0.3s;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+
+ &:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ }
+
+ &.btn-primary {
+ background: #3498db;
+ color: white;
+
+ &:hover:not(:disabled) {
+ background: #2980b9;
+ }
+ }
+
+ &.btn-secondary {
+ background: #95a5a6;
+ color: white;
+
+ &:hover:not(:disabled) {
+ background: #7f8c8d;
+ }
+ }
+ }
+}
diff --git a/frontend/src/app/pages/research/research.spec.ts b/frontend/src/app/pages/research/research.spec.ts
new file mode 100644
index 0000000..5b8a5d2
--- /dev/null
+++ b/frontend/src/app/pages/research/research.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { Research } from './research';
+
+describe('Research', () => {
+ let component: Research;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [Research]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(Research);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/pages/research/research.ts b/frontend/src/app/pages/research/research.ts
new file mode 100644
index 0000000..86e0a83
--- /dev/null
+++ b/frontend/src/app/pages/research/research.ts
@@ -0,0 +1,157 @@
+import { Component, inject, signal } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { OedbApi } from '../../services/oedb-api';
+import { AllEvents } from '../../maps/all-events/all-events';
+
+interface SearchParams {
+ query: string;
+ what: string;
+ start: string;
+ end: string;
+ limit: number;
+}
+
+interface SearchResult {
+ features: any[];
+ total: number;
+ query: string;
+}
+
+@Component({
+ selector: 'app-research',
+ standalone: true,
+ imports: [CommonModule, FormsModule, AllEvents],
+ templateUrl: './research.html',
+ styleUrl: './research.scss'
+})
+export class Research {
+ private oedbApi = inject(OedbApi);
+
+ searchParams = signal({
+ query: '',
+ what: '',
+ start: '',
+ end: '',
+ limit: 100
+ });
+
+ searchResults = signal(null);
+ isLoading = signal(false);
+ hasSearched = signal(false);
+
+ availableWhatTypes = signal([]);
+
+ constructor() {
+ this.loadAvailableWhatTypes();
+ }
+
+ private loadAvailableWhatTypes() {
+ // Charger les types d'événements disponibles
+ this.oedbApi.getEvents({ limit: 1 }).subscribe((response: any) => {
+ const whatTypes = new Set();
+ if (response?.features) {
+ response.features.forEach((feature: any) => {
+ if (feature?.properties?.what) {
+ whatTypes.add(feature.properties.what);
+ }
+ });
+ }
+ this.availableWhatTypes.set(Array.from(whatTypes).sort());
+ });
+ }
+
+ onSearch() {
+ const params = this.searchParams();
+ if (!params.query.trim()) {
+ return;
+ }
+
+ this.isLoading.set(true);
+ this.hasSearched.set(true);
+
+ const apiParams: any = {
+ q: params.query,
+ limit: params.limit
+ };
+
+ if (params.what) {
+ apiParams.what = params.what;
+ }
+ if (params.start) {
+ apiParams.start = params.start;
+ }
+ if (params.end) {
+ apiParams.end = params.end;
+ }
+
+ this.oedbApi.getEvents(apiParams).subscribe({
+ next: (response: any) => {
+ const features = Array.isArray(response?.features) ? response.features : [];
+ this.searchResults.set({
+ features,
+ total: features.length,
+ query: params.query
+ });
+ this.isLoading.set(false);
+ },
+ error: (error) => {
+ console.error('Erreur lors de la recherche:', error);
+ this.searchResults.set({
+ features: [],
+ total: 0,
+ query: params.query
+ });
+ this.isLoading.set(false);
+ }
+ });
+ }
+
+ onClearSearch() {
+ this.searchParams.set({
+ query: '',
+ what: '',
+ start: '',
+ end: '',
+ limit: 100
+ });
+ this.searchResults.set(null);
+ this.hasSearched.set(false);
+ }
+
+ onEventSelect(event: any) {
+ console.log('Événement sélectionné:', event);
+ }
+
+ getEventTitle(event: any): string {
+ return event?.properties?.label || event?.properties?.name || 'Événement sans nom';
+ }
+
+ getEventDate(event: any): string {
+ return event?.properties?.start || event?.properties?.when || '';
+ }
+
+ getEventLocation(event: any): string {
+ return event?.properties?.where || '';
+ }
+
+ getEventType(event: any): string {
+ return event?.properties?.what || '';
+ }
+
+ formatDate(dateString: string): string {
+ if (!dateString) return '';
+ try {
+ const date = new Date(dateString);
+ return date.toLocaleDateString('fr-FR', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+ } catch {
+ return dateString;
+ }
+ }
+}
diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss
index bc18ac9..2e015d2 100644
--- a/frontend/src/styles.scss
+++ b/frontend/src/styles.scss
@@ -55,9 +55,9 @@ input{
app-root, app-home {
display: block;
- min-height: 100vh;
- height: 100vh;
- overflow: hidden;
+ // min-height: 100vh;
+ // height: 100vh;
+ // overflow: hidden;
}
/* Generic UI elements */
diff --git a/test_agenda_debug.html b/test_agenda_debug.html
new file mode 100644
index 0000000..6b87d67
--- /dev/null
+++ b/test_agenda_debug.html
@@ -0,0 +1,66 @@
+
+
+
+ Test Agenda Debug
+
+
+
+ Test de l'Agenda avec Debug
+
+
+
Instructions de test :
+
+ Ouvrez les outils de développement (F12)
+ Allez dans l'onglet "Console"
+ Cliquez sur un des liens ci-dessous
+ Observez les logs dans la console
+
+
+
+ Tests à effectuer :
+
+
+ 🔗 Test 1: Agenda sans filtre
+
+
+
+ 🔗 Test 2: Agenda avec paramètre de requête (?what=culture)
+
+
+
+ 🔗 Test 3: Agenda avec fragment (#&what=culture)
+
+
+
+ 🔗 Test 4: Agenda avec fragment spécifique (#&what=culture.community.ccpl)
+
+
+ Logs attendus dans la console :
+
+ 🔗 Fragment reçu: [fragment]
+ 🧹 Fragment nettoyé: [fragment nettoyé]
+ 🎯 Paramètre what extrait: [valeur]
+ ✅ Filtre what défini: [valeur]
+ 🔍 Chargement des événements avec paramètres: [paramètres]
+ 📅 Plage de dates: [dates]
+ 📡 Réponse API reçue: [réponse]
+ 📊 Nombre d'événements chargés: [nombre]
+ 🔍 Application du filtre what: [filtre]
+ ✅ Événements après filtrage: [nombre]
+
+
+ Problèmes à vérifier :
+
+ ❌ Si aucun log n'apparaît : Le serveur Angular ne fonctionne pas
+ ❌ Si "Fragment reçu: null" : L'URL ne contient pas de fragment
+ ❌ Si "Paramètre what extrait: null" : Le parsing du fragment échoue
+ ❌ Si "Nombre d'événements chargés: 0" : L'API ne retourne pas d'événements
+ ❌ Si "Événements après filtrage: 0" : Le filtrage ne fonctionne pas
+
+
+