From e7f7e9e19e58ce159c803cba230fa4bddd2c0b4c Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sat, 4 Oct 2025 23:36:37 +0200 Subject: [PATCH] style agenda --- frontend/src/app/pages/agenda/agenda.html | 147 +---- frontend/src/app/pages/agenda/agenda.scss | 338 +++-------- frontend/src/app/pages/agenda/agenda.ts | 283 ++------- .../app/pages/agenda/calendar/calendar.html | 132 ++++ .../app/pages/agenda/calendar/calendar.scss | 563 ++++++++++++++++++ .../src/app/pages/agenda/calendar/calendar.ts | 8 +- frontend/src/app/pages/home/home.html | 97 +-- frontend/src/app/pages/home/home.ts | 1 + frontend/src/app/pages/home/menu/menu.html | 17 +- frontend/src/app/pages/home/menu/menu.scss | 13 +- .../unlocated-events/unlocated-events.scss | 6 + 11 files changed, 928 insertions(+), 677 deletions(-) create mode 100644 frontend/src/app/pages/agenda/calendar/calendar.html create mode 100644 frontend/src/app/pages/agenda/calendar/calendar.scss diff --git a/frontend/src/app/pages/agenda/agenda.html b/frontend/src/app/pages/agenda/agenda.html index 1d16b8d..1327d2c 100644 --- a/frontend/src/app/pages/agenda/agenda.html +++ b/frontend/src/app/pages/agenda/agenda.html @@ -1,134 +1,31 @@ -
-
-

Agenda des événements

-

Événements des 20 derniers jours (10 jours avant et 10 jours après aujourd'hui)

- -
- - - - -
-
- - - @if (showFiltersPanel) { -
-

Filtres d'événements

- -
- -
- -
-

Types d'événements

-
- @for (eventType of availableEventTypes; track eventType) { - - } -
-
- -
- -
+
+ @if (isLoading) { +
+
+

Chargement des événements...

+ } @else { + + } -
- - - - - - - - -
- - - @if (showSidePanel && selectedEvent) { -
-
-

Détails de l'événement

- + @if (selectedEvent) { +
+
+

Modifier l'événement

+
- -
+
+ [selected]="selectedEvent" + (saved)="onEventSaved()" + (created)="onEventCreated()" + (deleted)="onEventDeleted()">
} - - - @if (showSidePanel) { -
- } -
- - - -
- {{ getEventIcon(event.meta?.preset) }} - {{ event.title }} -
-
\ No newline at end of file +
\ 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 @@ +
+ +
+
+ +

{{getMonthName()}} {{currentYear}}

+ +
+ +
+ + +
+
+ {{getTotalEventsCount()}} + Total événements +
+
+ {{getEventsThisMonth()}} + Ce mois +
+
+ + +
+ +
+ @for (day of weekDays; track day) { +
{{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) { +
+
+

Détails de l'événement

+ +
+ +
+
{{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 @@ OpenEventDatabase - - + + +
- +-->