+ @if (isLoading) {
+
+
+
Chargement des événements...
+ } @else {
+
+
}
-
-
-
-
-
-
-
-
-
-
-
-
- @if (showSidePanel && selectedEvent) {
-
-
\ 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 d7a5e15..8afeb41 100644
--- a/frontend/src/app/pages/agenda/agenda.scss
+++ b/frontend/src/app/pages/agenda/agenda.scss
@@ -1,294 +1,108 @@
-.agenda-container {
- padding: 20px;
- max-width: 1400px;
- margin: 0 auto;
- position: relative;
-}
-
-.agenda-header {
- text-align: center;
- margin-bottom: 30px;
-
- h1 {
- color: #333;
- margin-bottom: 10px;
- }
-
- p {
- color: #666;
- font-size: 14px;
- margin-bottom: 20px;
- }
-}
-
-.calendar-controls {
+.agenda-page {
+ height: 100vh;
display: flex;
- justify-content: center;
- gap: 10px;
- margin-bottom: 20px;
-
- .btn {
- padding: 8px 16px;
- border: 1px solid #ddd;
- background: white;
- border-radius: 4px;
- cursor: pointer;
- transition: all 0.2s ease;
-
- &:hover {
- background: #f8f9fa;
- }
-
- &.btn-primary {
- background: #007bff;
- color: white;
- border-color: #007bff;
- }
- }
-}
-
-// Panneau de filtres
-.filters-panel {
+ flex-direction: column;
background: #f8f9fa;
- border: 1px solid #e9ecef;
- border-radius: 8px;
- padding: 20px;
- margin-bottom: 20px;
-
- h3 {
- margin: 0 0 20px 0;
- color: #333;
- font-size: 18px;
- }
-
- .filter-group {
- margin-bottom: 20px;
-
- h4 {
- margin: 0 0 10px 0;
- color: #555;
- font-size: 14px;
- font-weight: 600;
- }
-
- label {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 8px;
- cursor: pointer;
- font-size: 14px;
-
- input[type="checkbox"] {
- margin: 0;
- }
- }
- }
-
- .event-types-list {
- max-height: 200px;
- overflow-y: auto;
- border: 1px solid #e9ecef;
- border-radius: 4px;
- padding: 10px;
- background: white;
-
- .event-type-item {
- display: block;
- padding: 4px 0;
- border-bottom: 1px solid #f1f5f9;
-
- &:last-child {
- border-bottom: none;
- }
-
- &:hover {
- background: #f8f9fa;
- }
- }
- }
-
- .filter-actions {
- border-top: 1px solid #e9ecef;
- padding-top: 15px;
- text-align: center;
- }
}
-.agenda-content {
- margin-bottom: 20px;
-
- // Styles pour angular-calendar
- ::ng-deep {
- .cal-month-view,
- .cal-week-view,
- .cal-day-view {
- background: white;
- border-radius: 8px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- overflow: hidden;
- }
-
- .cal-header {
- background: #f8f9fa;
- border-bottom: 1px solid #e9ecef;
- }
-
- .cal-header .cal-cell {
- padding: 15px 10px;
- font-weight: 600;
- color: #495057;
- }
-
- .cal-cell {
- border-right: 1px solid #e9ecef;
- border-bottom: 1px solid #e9ecef;
-
- &:last-child {
- border-right: none;
- }
- }
-
- .cal-today {
- background: #e3f2fd !important;
- }
-
- .cal-event {
- border-radius: 4px;
- padding: 2px 6px;
- margin: 1px;
- font-size: 12px;
- cursor: pointer;
- transition: all 0.2s ease;
-
- &:hover {
- transform: scale(1.02);
- box-shadow: 0 2px 4px rgba(0,0,0,0.2);
- }
- }
-
- .cal-event-title {
- font-weight: 500;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .custom-event {
- display: flex;
- align-items: center;
- gap: 4px;
-
- .event-emoji {
- font-size: 12px;
- flex-shrink: 0;
- }
-
- .event-title {
- font-size: 11px;
- font-weight: 500;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- flex: 1;
- }
- }
-
- .cal-month-view .cal-day-number {
- font-size: 14px;
- font-weight: 500;
- color: #495057;
- }
-
- .cal-today .cal-day-number {
- background: #007bff;
- color: white;
- border-radius: 50%;
- width: 24px;
- height: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- }
+.loading {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ background: #f8f9fa;
}
-// Panneau latéral
-.side-panel {
+.loading-spinner {
+ width: 40px;
+ height: 40px;
+ border: 4px solid #e9ecef;
+ border-top: 4px solid #007bff;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin-bottom: 20px;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+.loading p {
+ color: #6c757d;
+ font-size: 1.1rem;
+ margin: 0;
+}
+
+.event-edit-panel {
position: fixed;
top: 0;
right: 0;
width: 400px;
height: 100vh;
background: white;
- box-shadow: -2px 0 10px rgba(0,0,0,0.1);
+ box-shadow: -4px 0 12px rgba(0,0,0,0.15);
z-index: 1000;
display: flex;
flex-direction: column;
+ animation: slideIn 0.3s ease-out;
}
-.side-panel-header {
+@keyframes slideIn {
+ from {
+ transform: translateX(100%);
+ }
+ to {
+ transform: translateX(0);
+ }
+}
+
+.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
- border-bottom: 1px solid #eee;
+ border-bottom: 1px solid #e9ecef;
background: #f8f9fa;
-
- h2 {
- margin: 0;
- font-size: 18px;
- color: #333;
- }
-
- .close-btn {
- background: none;
- border: none;
- font-size: 24px;
- cursor: pointer;
- color: #666;
- padding: 0;
- width: 30px;
- height: 30px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 50%;
-
- &:hover {
- background: #e9ecef;
- color: #333;
- }
+}
+
+.panel-header h3 {
+ margin: 0;
+ color: #2c3e50;
+ font-size: 1.2rem;
+}
+
+.btn-close {
+ background: none;
+ border: none;
+ font-size: 1.5rem;
+ color: #6c757d;
+ cursor: pointer;
+ padding: 5px;
+ border-radius: 50%;
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease;
+
+ &:hover {
+ background: #e9ecef;
+ color: #495057;
}
}
-.side-panel-content {
+.panel-content {
flex: 1;
- overflow-y: auto;
padding: 20px;
-}
-
-.overlay {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- background: rgba(0,0,0,0.5);
- z-index: 999;
+ overflow-y: auto;
}
// Responsive
@media (max-width: 768px) {
- .agenda-container {
- padding: 10px;
+ .event-edit-panel {
+ width: 100%;
}
-
- .days-grid {
- grid-template-columns: 1fr;
- }
-
- .side-panel {
- width: 100vw;
- }
-}
+}
\ No newline at end of file
diff --git a/frontend/src/app/pages/agenda/agenda.ts b/frontend/src/app/pages/agenda/agenda.ts
index 4a5284a..2b9d0ca 100644
--- a/frontend/src/app/pages/agenda/agenda.ts
+++ b/frontend/src/app/pages/agenda/agenda.ts
@@ -1,11 +1,9 @@
-import { Component, inject, OnInit, ViewChild, TemplateRef } from '@angular/core';
+import { Component, inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { OedbApi } from '../../services/oedb-api';
import { EditForm } from '../../forms/edit-form/edit-form';
-import { CalendarModule, CalendarView, CalendarEvent } from 'angular-calendar';
-import { CalendarEventAction, CalendarEventTimesChangedEvent } from 'angular-calendar';
-import oedb from '../../../oedb-types';
+import { CalendarComponent, CalendarEvent } from './calendar/calendar';
interface OedbEvent {
id: string;
@@ -25,268 +23,107 @@ interface OedbEvent {
};
}
-interface DayEvents {
- date: Date;
- events: OedbEvent[];
-}
-
@Component({
selector: 'app-agenda',
standalone: true,
- imports: [CommonModule, FormsModule, EditForm, CalendarModule],
+ imports: [CommonModule, FormsModule, EditForm, CalendarComponent],
templateUrl: './agenda.html',
styleUrl: './agenda.scss'
})
export class Agenda implements OnInit {
private oedbApi = inject(OedbApi);
- @ViewChild('eventTitleTemplate', { static: true }) eventTitleTemplate!: TemplateRef
;
-
events: OedbEvent[] = [];
- filteredEvents: OedbEvent[] = [];
calendarEvents: CalendarEvent[] = [];
selectedEvent: OedbEvent | null = null;
- showSidePanel = false;
- showFiltersPanel = false;
- view: CalendarView = CalendarView.Month;
- viewDate: Date = new Date();
- oedbPresets = oedb.presets.what;
-
- // Propriétés pour les filtres
- hideTrafficEvents = true; // Par défaut, masquer les événements de type traffic
- selectedEventTypes: string[] = [];
- availableEventTypes: string[] = [];
-
- // Exposer CalendarView pour l'utiliser dans le template
- CalendarView = CalendarView;
+ isLoading = false;
ngOnInit() {
this.loadEvents();
}
loadEvents() {
+ this.isLoading = true;
const today = new Date();
const startDate = new Date(today);
- startDate.setDate(today.getDate() - 10);
-
+ startDate.setMonth(today.getMonth() - 1); // Charger 1 mois avant
const endDate = new Date(today);
- endDate.setDate(today.getDate() + 10);
+ endDate.setMonth(today.getMonth() + 3); // Charger 3 mois après
const params = {
- start: `${startDate.toISOString().split('T')[0]}`,
- end: `${endDate.toISOString().split('T')[0]}`,
+ start: startDate.toISOString().split('T')[0],
+ end: endDate.toISOString().split('T')[0],
limit: 1000
};
this.oedbApi.getEvents(params).subscribe((response: any) => {
this.events = Array.isArray(response?.features) ? response.features : [];
- this.updateAvailableEventTypes();
- this.applyFilters();
+ this.convertToCalendarEvents();
+ this.isLoading = false;
});
}
- updateAvailableEventTypes() {
- const eventTypes = new Set();
- this.events.forEach(event => {
- if (event?.properties?.what) {
- eventTypes.add(event.properties.what);
- }
- });
- this.availableEventTypes = Array.from(eventTypes).sort();
- }
+ convertToCalendarEvents() {
+ this.calendarEvents = this.events.map(event => {
+ const startDate = this.parseEventDate(event.properties.start || event.properties.when);
+ const endDate = event.properties.stop ? this.parseEventDate(event.properties.stop) : null;
- applyFilters() {
- let filtered = [...this.events];
-
- // Filtre par défaut : masquer les événements de type traffic
- if (this.hideTrafficEvents) {
- filtered = filtered.filter(event =>
- !event?.properties?.what?.startsWith('traffic.')
- );
- }
-
- // Filtre par types d'événements sélectionnés
- if (this.selectedEventTypes.length > 0) {
- filtered = filtered.filter(event =>
- this.selectedEventTypes.includes(event?.properties?.what || '')
- );
- }
-
- this.filteredEvents = filtered;
- this.organizeEventsByDay();
- }
-
- organizeEventsByDay() {
- this.calendarEvents = this.filteredEvents.map(event => {
- const eventDate = this.getEventDate(event);
- const preset = this.getEventPreset(event);
-
return {
- id: event.id,
- title: this.getEventTitle(event),
- start: eventDate || new Date(),
- color: this.getEventColor(preset),
- meta: {
- event: event,
- preset: preset
- }
+ id: event.id || Math.random().toString(36).substr(2, 9),
+ title: event.properties.label || event.properties.name || 'Événement sans nom',
+ start: startDate,
+ end: endDate || undefined,
+ description: event.properties.description || '',
+ location: event.properties.where || '',
+ type: event.properties.what || 'default',
+ properties: event.properties
};
});
}
- getEventDate(event: OedbEvent): Date | null {
- const startDate = event.properties.start || event.properties.when;
- if (startDate) {
- return new Date(startDate);
- }
- return null;
- }
-
- getEventTitle(event: OedbEvent): string {
- return event.properties.label || event.properties.name || 'Événement sans titre';
- }
-
- getEventTime(event: OedbEvent): string {
- const startDate = event.properties.start || event.properties.when;
- if (startDate) {
- const date = new Date(startDate);
- return date.toLocaleTimeString('fr-FR', {
- hour: '2-digit',
- minute: '2-digit'
- });
- }
- return '';
- }
-
- selectEvent(event: OedbEvent) {
- this.selectedEvent = event;
- this.showSidePanel = true;
- }
-
- onEventClicked({ event }: { event: CalendarEvent; sourceEvent: MouseEvent | KeyboardEvent }) {
- if (event.meta && event.meta.event) {
- this.selectEvent(event.meta.event);
- }
- }
-
- getEventPreset(event: OedbEvent): any {
- const what = event.properties.what;
- if (what && (this.oedbPresets as any)[what]) {
- return (this.oedbPresets as any)[what];
- }
- return null;
- }
-
- getEventIcon(preset: any): string {
- if (preset) {
- return preset.emoji || '📅';
- }
- return '📅';
- }
-
- getEventColor(preset: any): any {
- if (preset) {
- // Couleurs basées sur la catégorie
- const categoryColors: { [key: string]: any } = {
- 'Communauté': { primary: '#007bff', secondary: '#cce7ff' },
- 'Culture': { primary: '#28a745', secondary: '#d4edda' },
- 'Musique': { primary: '#ffc107', secondary: '#fff3cd' },
- 'Énergie': { primary: '#dc3545', secondary: '#f8d7da' },
- 'Commerce': { primary: '#6f42c1', secondary: '#e2d9f3' },
- 'Temps': { primary: '#17a2b8', secondary: '#d1ecf1' },
- 'Tourisme': { primary: '#fd7e14', secondary: '#ffeaa7' },
- 'Circulation': { primary: '#6c757d', secondary: '#e9ecef' },
- 'Randonnée': { primary: '#20c997', secondary: '#d1f2eb' },
- 'Vie sauvage': { primary: '#795548', secondary: '#efebe9' },
- 'Météo': { primary: '#2196f3', secondary: '#e3f2fd' }
- };
-
- const category = preset.category || 'Communauté';
- return categoryColors[category] || { primary: '#6c757d', secondary: '#e9ecef' };
- }
- return { primary: '#6c757d', secondary: '#e9ecef' };
- }
-
- closeSidePanel() {
- this.showSidePanel = false;
- this.selectedEvent = null;
- }
-
- onEventSaved(event: any) {
- this.loadEvents(); // Recharger les événements après modification
- this.closeSidePanel();
- }
-
- onEventCreated(event: any) {
- this.loadEvents(); // Recharger les événements après création
- this.closeSidePanel();
- }
-
- onEventDeleted(event: any) {
- this.loadEvents(); // Recharger les événements après suppression
- this.closeSidePanel();
- }
-
- setView(view: CalendarView) {
- this.view = view;
- }
-
- dayClicked(event: any): void {
- // Gérer les différents types d'événements selon la vue
- let date: Date;
- let events: CalendarEvent[] = [];
+ parseEventDate(dateString: string | undefined): Date {
+ if (!dateString) return new Date();
- if (event.day) {
- // Vue mois : { day: MonthViewDay, sourceEvent: MouseEvent | KeyboardEvent }
- date = event.day.date;
- events = event.day.events || [];
- } else if (event.date) {
- // Vue semaine/jour : { date: Date, events: CalendarEvent[], sourceEvent: MouseEvent | KeyboardEvent }
- date = event.date;
- events = event.events || [];
- } else {
- // Fallback pour les autres cas
- console.warn('Type d\'événement dayClicked non reconnu:', event);
- return;
- }
-
- console.log('Day clicked:', date, events);
- }
-
- eventTimesChanged({
- event,
- newStart,
- newEnd,
- }: CalendarEventTimesChangedEvent): void {
- console.log('Event times changed:', event, newStart, newEnd);
- }
-
- toggleFiltersPanel() {
- this.showFiltersPanel = !this.showFiltersPanel;
- }
-
- onHideTrafficChange() {
- this.applyFilters();
- }
-
- onEventTypeChange(eventType: string, checked: boolean) {
- if (checked) {
- if (!this.selectedEventTypes.includes(eventType)) {
- this.selectedEventTypes.push(eventType);
+ // Essayer différents formats de date
+ const date = new Date(dateString);
+ if (isNaN(date.getTime())) {
+ // Si la date n'est pas valide, essayer de parser manuellement
+ const parts = dateString.split(/[-T:]/);
+ if (parts.length >= 3) {
+ const year = parseInt(parts[0]);
+ const month = parseInt(parts[1]) - 1; // Les mois commencent à 0
+ const day = parseInt(parts[2]);
+ const hour = parts[3] ? parseInt(parts[3]) : 0;
+ const minute = parts[4] ? parseInt(parts[4]) : 0;
+ return new Date(year, month, day, hour, minute);
}
- } else {
- this.selectedEventTypes = this.selectedEventTypes.filter(type => type !== eventType);
}
- this.applyFilters();
+
+ return date;
}
- isEventTypeSelected(eventType: string): boolean {
- return this.selectedEventTypes.includes(eventType);
+ onEventClick(event: CalendarEvent) {
+ // Trouver l'événement OEDB correspondant
+ this.selectedEvent = this.events.find(e =>
+ (e.id && e.id === event.id) ||
+ (e.properties.label === event.title)
+ ) || null;
}
- clearAllFilters() {
- this.selectedEventTypes = [];
- this.hideTrafficEvents = true;
- this.applyFilters();
+ onDateClick(date: Date) {
+ // Optionnel : gérer le clic sur une date
+ console.log('Date cliquée:', date);
+ }
+
+ onEventSaved() {
+ this.loadEvents();
+ }
+
+ onEventCreated() {
+ this.loadEvents();
+ }
+
+ onEventDeleted() {
+ this.loadEvents();
}
}
\ No newline at end of file
diff --git a/frontend/src/app/pages/agenda/calendar/calendar.html b/frontend/src/app/pages/agenda/calendar/calendar.html
new file mode 100644
index 0000000..d685ad2
--- /dev/null
+++ b/frontend/src/app/pages/agenda/calendar/calendar.html
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+ {{getTotalEventsCount()}}
+ Total événements
+
+
+ {{getEventsThisMonth()}}
+ Ce mois
+
+
+
+
+
+
+
+ @for (day of weekDays; track day) {
+
+ }
+
+
+
+
+ @for (day of calendarDays; track day.getTime()) {
+
+
+
{{day.getDate()}}
+
+ @if (getEventCountForDate(day) > 0) {
+
+ {{getEventCountForDate(day)}}
+
+ }
+
+
+ @for (event of getEventsForDate(day).slice(0, 3); track event.id) {
+
+ {{event.title}}
+
+ }
+ @if (getEventsForDate(day).length > 3) {
+
+{{getEventsForDate(day).length - 3}} autres
+ }
+
+
+ }
+
+
+
+
+ @if (showEventDetails && selectedEvent) {
+
+
+
+
+
{{selectedEvent.title}}
+
+ @if (selectedEvent.description) {
+
+
Description :
+
{{selectedEvent.description}}
+
+ }
+
+ @if (selectedEvent.location) {
+
+ 📍 Lieu :
+ {{selectedEvent.location}}
+
+ }
+
+
+ 📅 Date :
+ {{selectedEvent.start | date:'dd/MM/yyyy à HH:mm'}}
+
+
+ @if (selectedEvent.end) {
+
+ ⏰ Fin :
+ {{selectedEvent.end | date:'dd/MM/yyyy à HH:mm'}}
+
+ }
+
+ @if (selectedEvent.type) {
+
+ Type :
+ {{selectedEvent.type}}
+
+ }
+
+ @if (selectedEvent.properties) {
+
+
Propriétés :
+
+ @for (prop of getObjectKeys(selectedEvent.properties); track prop) {
+
+ {{prop}} :
+ {{selectedEvent.properties[prop]}}
+
+ }
+
+
+ }
+
+
+ }
+
+
diff --git a/frontend/src/app/pages/agenda/calendar/calendar.scss b/frontend/src/app/pages/agenda/calendar/calendar.scss
new file mode 100644
index 0000000..3eb9c3a
--- /dev/null
+++ b/frontend/src/app/pages/agenda/calendar/calendar.scss
@@ -0,0 +1,563 @@
+:host {
+ display: block;
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background: #f8f9fa;
+ border-radius: 12px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+}
+
+.calendar-container {
+ background: white;
+ border-radius: 12px;
+ overflow: hidden;
+}
+
+/* En-tête du calendrier */
+.calendar-header {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 20px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 15px;
+}
+
+.calendar-controls {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+}
+
+.btn {
+ background: rgba(255, 255, 255, 0.2);
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ color: white;
+ padding: 8px 12px;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ font-size: 14px;
+ font-weight: 500;
+}
+
+.btn:hover {
+ background: rgba(255, 255, 255, 0.3);
+ transform: translateY(-1px);
+}
+
+.btn-nav {
+ font-size: 18px;
+ padding: 8px 12px;
+ min-width: 40px;
+}
+
+.btn-today {
+ background: rgba(255, 255, 255, 0.9);
+ color: #667eea;
+ font-weight: 600;
+}
+
+.btn-today:hover {
+ background: white;
+ transform: translateY(-1px);
+}
+
+.calendar-title {
+ margin: 0;
+ font-size: 24px;
+ font-weight: 600;
+ text-align: center;
+ min-width: 200px;
+}
+
+/* Statistiques */
+.calendar-stats {
+ background: #f8f9fa;
+ padding: 15px 20px;
+ display: flex;
+ gap: 30px;
+ border-bottom: 1px solid #e9ecef;
+}
+
+.stat-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+}
+
+.stat-number {
+ font-size: 24px;
+ font-weight: 700;
+ color: #667eea;
+ line-height: 1;
+}
+
+.stat-label {
+ font-size: 12px;
+ color: #6c757d;
+ margin-top: 4px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+/* Grille du calendrier */
+.calendar-grid {
+ background: white;
+}
+
+.calendar-weekdays {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ background: #f8f9fa;
+ border-bottom: 2px solid #e9ecef;
+}
+
+.weekday-header {
+ padding: 15px 8px;
+ text-align: center;
+ font-weight: 600;
+ color: #495057;
+ font-size: 14px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ border-right: 1px solid #e9ecef;
+}
+
+.weekday-header:last-child {
+ border-right: none;
+}
+
+.calendar-days {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ gap: 0;
+}
+
+.calendar-day {
+ min-height: 120px;
+ padding: 8px;
+ border-right: 1px solid #e9ecef;
+ border-bottom: 1px solid #e9ecef;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ position: relative;
+ background: white;
+ display: flex;
+ flex-direction: column;
+}
+
+.calendar-day:nth-child(7n) {
+ border-right: none;
+}
+
+.calendar-day:hover {
+ background: #f8f9fa;
+ transform: scale(1.02);
+ z-index: 1;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.calendar-day.today {
+ background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
+ border: 2px solid #2196f3;
+}
+
+.calendar-day.today .day-number {
+ background: #2196f3;
+ color: white;
+ font-weight: 700;
+}
+
+.calendar-day.other-month {
+ background: #f8f9fa;
+ color: #adb5bd;
+}
+
+.calendar-day.other-month .day-number {
+ color: #adb5bd;
+}
+
+.calendar-day.weekend {
+ background: #f8f9fa;
+}
+
+.calendar-day.selected {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+}
+
+.calendar-day.selected .day-number {
+ background: rgba(255, 255, 255, 0.2);
+ color: white;
+}
+
+.day-number {
+ font-size: 16px;
+ font-weight: 600;
+ color: #495057;
+ background: #f8f9fa;
+ border-radius: 50%;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 8px;
+ transition: all 0.2s ease;
+}
+
+/* Indicateurs d'événements */
+.event-indicator {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ background: #ff4757;
+ color: white;
+ border-radius: 50%;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 11px;
+ font-weight: 700;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}
+
+.day-events {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ margin-top: 4px;
+}
+
+.event-preview {
+ background: #667eea;
+ color: white;
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-size: 11px;
+ font-weight: 500;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ border-left: 3px solid #5a67d8;
+ max-width: 150px;
+ overflow: auto;
+ text-overflow: ellipsis;
+}
+
+.event-preview:hover {
+ background: #5a67d8;
+ transform: translateX(2px);
+}
+
+.event-type-conference {
+ background: #4caf50;
+ border-left-color: #388e3c;
+}
+
+.event-type-conference:hover {
+ background: #388e3c;
+}
+
+.event-type-workshop {
+ background: #ff9800;
+ border-left-color: #f57c00;
+}
+
+.event-type-workshop:hover {
+ background: #f57c00;
+}
+
+.event-type-meeting {
+ background: #9c27b0;
+ border-left-color: #7b1fa2;
+}
+
+.event-type-meeting:hover {
+ background: #7b1fa2;
+}
+
+.event-type-default {
+ background: #6c757d;
+ border-left-color: #495057;
+}
+
+.event-type-default:hover {
+ background: #495057;
+}
+
+.more-events {
+ background: #e9ecef;
+ color: #6c757d;
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-size: 10px;
+ font-weight: 600;
+ text-align: center;
+ margin-top: auto;
+}
+
+/* Panel de détails d'événement */
+.event-details-panel {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: white;
+ border-radius: 12px;
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
+ z-index: 1000;
+ max-width: 500px;
+ width: 90%;
+ max-height: 80vh;
+ overflow-y: auto;
+ animation: slideIn 0.3s ease;
+}
+
+@keyframes slideIn {
+ from {
+ opacity: 0;
+ transform: translate(-50%, -60%);
+ }
+ to {
+ opacity: 1;
+ transform: translate(-50%, -50%);
+ }
+}
+
+.panel-header {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+ padding: 20px;
+ border-radius: 12px 12px 0 0;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.panel-header h3 {
+ margin: 0;
+ font-size: 20px;
+ font-weight: 600;
+}
+
+.btn-close {
+ background: rgba(255, 255, 255, 0.2);
+ border: none;
+ color: white;
+ font-size: 24px;
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease;
+}
+
+.btn-close:hover {
+ background: rgba(255, 255, 255, 0.3);
+ transform: scale(1.1);
+}
+
+.panel-content {
+ padding: 20px;
+}
+
+.event-title {
+ font-size: 24px;
+ font-weight: 700;
+ color: #2c3e50;
+ margin-bottom: 20px;
+ line-height: 1.3;
+}
+
+.event-description,
+.event-location,
+.event-datetime,
+.event-end,
+.event-type {
+ margin-bottom: 15px;
+ padding: 12px;
+ background: #f8f9fa;
+ border-radius: 8px;
+ border-left: 4px solid #667eea;
+}
+
+.event-description strong,
+.event-location strong,
+.event-datetime strong,
+.event-end strong,
+.event-type strong {
+ display: block;
+ color: #495057;
+ font-size: 14px;
+ margin-bottom: 8px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.event-description p {
+ margin: 0;
+ color: #6c757d;
+ line-height: 1.5;
+}
+
+.event-location span,
+.event-datetime span,
+.event-end span {
+ color: #2c3e50;
+ font-weight: 500;
+}
+
+.type-badge {
+ background: #667eea;
+ color: white;
+ padding: 4px 12px;
+ border-radius: 20px;
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.event-properties {
+ margin-top: 20px;
+ padding: 15px;
+ background: #f8f9fa;
+ border-radius: 8px;
+ border: 1px solid #e9ecef;
+}
+
+.event-properties strong {
+ display: block;
+ color: #495057;
+ font-size: 14px;
+ margin-bottom: 12px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.properties-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.property-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 8px 12px;
+ background: white;
+ border-radius: 6px;
+ border: 1px solid #e9ecef;
+}
+
+.property-key {
+ font-weight: 600;
+ color: #495057;
+ font-size: 13px;
+}
+
+.property-value {
+ color: #6c757d;
+ font-size: 13px;
+ text-align: right;
+ max-width: 200px;
+ word-break: break-word;
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+ .calendar-header {
+ flex-direction: column;
+ text-align: center;
+ gap: 10px;
+ }
+
+ .calendar-controls {
+ order: 2;
+ }
+
+ .calendar-title {
+ order: 1;
+ font-size: 20px;
+ min-width: auto;
+ }
+
+ .btn-today {
+ order: 3;
+ }
+
+ .calendar-stats {
+ justify-content: center;
+ gap: 20px;
+ }
+
+ .calendar-day {
+ min-height: 80px;
+ padding: 4px;
+ }
+
+ .day-number {
+ width: 24px;
+ height: 24px;
+ font-size: 14px;
+ }
+
+ .event-preview {
+ font-size: 10px;
+ padding: 1px 4px;
+ }
+
+ .event-details-panel {
+ width: 95%;
+ max-height: 90vh;
+ }
+
+ .panel-content {
+ padding: 15px;
+ }
+
+ .event-title {
+ font-size: 20px;
+ }
+}
+
+@media (max-width: 480px) {
+ .calendar-day {
+ min-height: 60px;
+ padding: 2px;
+ }
+
+ .weekday-header {
+ padding: 10px 4px;
+ font-size: 12px;
+ }
+
+ .day-number {
+ width: 20px;
+ height: 20px;
+ font-size: 12px;
+ }
+
+ .event-preview {
+ font-size: 9px;
+ padding: 1px 2px;
+ }
+
+ .more-events {
+ font-size: 8px;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/pages/agenda/calendar/calendar.ts b/frontend/src/app/pages/agenda/calendar/calendar.ts
index 0c8d45a..3aaf349 100644
--- a/frontend/src/app/pages/agenda/calendar/calendar.ts
+++ b/frontend/src/app/pages/agenda/calendar/calendar.ts
@@ -16,8 +16,8 @@ export interface CalendarEvent {
selector: 'app-calendar',
standalone: true,
imports: [CommonModule],
- templateUrl: './calendar.html',
- styleUrl: './calendar.scss'
+ templateUrl: 'calendar.html',
+ styleUrl: 'calendar.scss'
})
export class CalendarComponent implements OnInit, OnDestroy {
@Input() events: CalendarEvent[] = [];
@@ -157,4 +157,8 @@ export class CalendarComponent implements OnInit, OnDestroy {
eventDate.getFullYear() === this.currentYear;
}).length;
}
+
+ getObjectKeys(obj: any): string[] {
+ return Object.keys(obj || {});
+ }
}
diff --git a/frontend/src/app/pages/home/home.html b/frontend/src/app/pages/home/home.html
index 8ecd971..7ab8aaf 100644
--- a/frontend/src/app/pages/home/home.html
+++ b/frontend/src/app/pages/home/home.html
@@ -1,64 +1,79 @@
- OpenEventDatabase
- {{filteredFeatures.length}} évènements
+
@if (isLoading) {
⏳ Chargement...
}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
+
+
+
+ @if (showFilters) {
+
{{filteredFeatures.length}} évènements chargés
+
+
+
+
+
+
+
+
+
+
+ }
+
+
+
+
-
-
-
-
+
diff --git a/frontend/src/app/pages/home/home.ts b/frontend/src/app/pages/home/home.ts
index 0159b37..3e05628 100644
--- a/frontend/src/app/pages/home/home.ts
+++ b/frontend/src/app/pages/home/home.ts
@@ -32,6 +32,7 @@ export class Home implements OnInit, OnDestroy {
filteredFeatures: Array
= [];
selected: any | null = null;
showTable = false;
+ showFilters = false;
// Nouvelles propriétés pour le rechargement automatique et la sélection de jours
autoReloadEnabled = true;
diff --git a/frontend/src/app/pages/home/menu/menu.html b/frontend/src/app/pages/home/menu/menu.html
index 13a1978..5b03b51 100644
--- a/frontend/src/app/pages/home/menu/menu.html
+++ b/frontend/src/app/pages/home/menu/menu.html
@@ -1,16 +1,9 @@
-
+-->