style agenda
This commit is contained in:
parent
ba6ec93860
commit
e7f7e9e19e
11 changed files with 928 additions and 677 deletions
|
@ -1,134 +1,31 @@
|
|||
<div class="agenda-container">
|
||||
<div class="agenda-header">
|
||||
<h1>Agenda des événements</h1>
|
||||
<p>Événements des 20 derniers jours (10 jours avant et 10 jours après aujourd'hui)</p>
|
||||
|
||||
<div class="calendar-controls">
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
[class.btn-primary]="view === CalendarView.Month"
|
||||
(click)="setView(CalendarView.Month)">
|
||||
Mois
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
[class.btn-primary]="view === CalendarView.Week"
|
||||
(click)="setView(CalendarView.Week)">
|
||||
Semaine
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
[class.btn-primary]="view === CalendarView.Day"
|
||||
(click)="setView(CalendarView.Day)">
|
||||
Jour
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
(click)="toggleFiltersPanel()">
|
||||
{{showFiltersPanel ? 'Masquer' : 'Afficher'}} les filtres
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panneau de filtres latéral -->
|
||||
@if (showFiltersPanel) {
|
||||
<div class="filters-panel">
|
||||
<h3>Filtres d'événements</h3>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
[(ngModel)]="hideTrafficEvents"
|
||||
(change)="onHideTrafficChange()">
|
||||
Masquer les événements de circulation
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<h4>Types d'événements</h4>
|
||||
<div class="event-types-list">
|
||||
@for (eventType of availableEventTypes; track eventType) {
|
||||
<label class="event-type-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
[checked]="isEventTypeSelected(eventType)"
|
||||
(change)="onEventTypeChange(eventType, $event.target.checked)">
|
||||
{{eventType}}
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-actions">
|
||||
<button class="btn btn-sm" (click)="clearAllFilters()">
|
||||
Effacer tous les filtres
|
||||
</button>
|
||||
</div>
|
||||
<div class="agenda-page">
|
||||
@if (isLoading) {
|
||||
<div class="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Chargement des événements...</p>
|
||||
</div>
|
||||
} @else {
|
||||
<app-calendar
|
||||
[events]="calendarEvents"
|
||||
(eventClick)="onEventClick($event)"
|
||||
(dateClick)="onDateClick($event)">
|
||||
</app-calendar>
|
||||
}
|
||||
|
||||
<div class="agenda-content">
|
||||
<mwl-calendar-month-view
|
||||
*ngIf="view === CalendarView.Month"
|
||||
[viewDate]="viewDate"
|
||||
[events]="calendarEvents"
|
||||
(eventClicked)="onEventClicked($event)"
|
||||
(dayClicked)="dayClicked($event)"
|
||||
[locale]="'fr'"
|
||||
[eventTitleTemplate]="eventTitleTemplate">
|
||||
</mwl-calendar-month-view>
|
||||
|
||||
<mwl-calendar-week-view
|
||||
*ngIf="view === CalendarView.Week"
|
||||
[viewDate]="viewDate"
|
||||
[events]="calendarEvents"
|
||||
(eventClicked)="onEventClicked($event)"
|
||||
(dayClicked)="dayClicked($event)"
|
||||
[locale]="'fr'"
|
||||
[eventTitleTemplate]="eventTitleTemplate">
|
||||
</mwl-calendar-week-view>
|
||||
|
||||
<mwl-calendar-day-view
|
||||
*ngIf="view === CalendarView.Day"
|
||||
[viewDate]="viewDate"
|
||||
[events]="calendarEvents"
|
||||
(eventClicked)="onEventClicked($event)"
|
||||
(dayClicked)="dayClicked($event)"
|
||||
[locale]="'fr'"
|
||||
[eventTitleTemplate]="eventTitleTemplate">
|
||||
</mwl-calendar-day-view>
|
||||
</div>
|
||||
|
||||
<!-- Panneau latéral pour les détails de l'événement -->
|
||||
@if (showSidePanel && selectedEvent) {
|
||||
<div class="side-panel">
|
||||
<div class="side-panel-header">
|
||||
<h2>Détails de l'événement</h2>
|
||||
<button class="close-btn" (click)="closeSidePanel()">×</button>
|
||||
@if (selectedEvent) {
|
||||
<div class="event-edit-panel">
|
||||
<div class="panel-header">
|
||||
<h3>Modifier l'événement</h3>
|
||||
<button class="btn-close" (click)="selectedEvent = null">×</button>
|
||||
</div>
|
||||
|
||||
<div class="side-panel-content">
|
||||
<div class="panel-content">
|
||||
<app-edit-form
|
||||
[selected]="selectedEvent"
|
||||
(saved)="onEventSaved($event)"
|
||||
(created)="onEventCreated($event)"
|
||||
(deleted)="onEventDeleted($event)">
|
||||
[selected]="selectedEvent"
|
||||
(saved)="onEventSaved()"
|
||||
(created)="onEventCreated()"
|
||||
(deleted)="onEventDeleted()">
|
||||
</app-edit-form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Overlay pour fermer le panneau latéral -->
|
||||
@if (showSidePanel) {
|
||||
<div class="overlay" (click)="closeSidePanel()"></div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Template personnalisé pour les événements du calendrier -->
|
||||
<ng-template #eventTitleTemplate let-event="event">
|
||||
<div class="custom-event">
|
||||
<span class="event-emoji">{{ getEventIcon(event.meta?.preset) }}</span>
|
||||
<span class="event-title">{{ event.title }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<any>;
|
||||
|
||||
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<string>();
|
||||
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();
|
||||
}
|
||||
}
|
132
frontend/src/app/pages/agenda/calendar/calendar.html
Normal file
132
frontend/src/app/pages/agenda/calendar/calendar.html
Normal file
|
@ -0,0 +1,132 @@
|
|||
<div class="calendar-container">
|
||||
<!-- En-tête du calendrier -->
|
||||
<div class="calendar-header">
|
||||
<div class="calendar-controls">
|
||||
<button class="btn btn-nav" (click)="previousMonth()">‹</button>
|
||||
<h2 class="calendar-title">{{getMonthName()}} {{currentYear}}</h2>
|
||||
<button class="btn btn-nav" (click)="nextMonth()">›</button>
|
||||
</div>
|
||||
<button class="btn btn-today" (click)="goToToday()">Aujourd'hui</button>
|
||||
</div>
|
||||
|
||||
<!-- Statistiques -->
|
||||
<div class="calendar-stats">
|
||||
<div class="stat-item">
|
||||
<span class="stat-number">{{getTotalEventsCount()}}</span>
|
||||
<span class="stat-label">Total événements</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="stat-number">{{getEventsThisMonth()}}</span>
|
||||
<span class="stat-label">Ce mois</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Grille du calendrier -->
|
||||
<div class="calendar-grid">
|
||||
<!-- En-têtes des jours -->
|
||||
<div class="calendar-weekdays">
|
||||
@for (day of weekDays; track day) {
|
||||
<div class="weekday-header">{{day}}</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Jours du calendrier -->
|
||||
<div class="calendar-days">
|
||||
@for (day of calendarDays; track day.getTime()) {
|
||||
<div
|
||||
class="calendar-day"
|
||||
[class.today]="isToday(day)"
|
||||
[class.other-month]="!isCurrentMonth(day)"
|
||||
[class.weekend]="isWeekend(day)"
|
||||
[class.selected]="selectedDate?.toDateString() === day.toDateString()"
|
||||
(click)="onDateClick(day)">
|
||||
|
||||
<div class="day-number">{{day.getDate()}}</div>
|
||||
|
||||
@if (getEventCountForDate(day) > 0) {
|
||||
<div class="event-indicator">
|
||||
<span class="event-count">{{getEventCountForDate(day)}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="day-events">
|
||||
@for (event of getEventsForDate(day).slice(0, 3); track event.id) {
|
||||
<div
|
||||
class="event-preview"
|
||||
[class]="'event-type-' + (event.type || 'default')"
|
||||
(click)="onEventClick(event, $event)"
|
||||
[title]="event.title">
|
||||
{{event.title}}
|
||||
</div>
|
||||
}
|
||||
@if (getEventsForDate(day).length > 3) {
|
||||
<div class="more-events">+{{getEventsForDate(day).length - 3}} autres</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Panel de détails de l'événement -->
|
||||
@if (showEventDetails && selectedEvent) {
|
||||
<div class="event-details-panel">
|
||||
<div class="panel-header">
|
||||
<h3>Détails de l'événement</h3>
|
||||
<button class="btn-close" (click)="closeEventDetails()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="panel-content">
|
||||
<div class="event-title">{{selectedEvent.title}}</div>
|
||||
|
||||
@if (selectedEvent.description) {
|
||||
<div class="event-description">
|
||||
<strong>Description :</strong>
|
||||
<p>{{selectedEvent.description}}</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (selectedEvent.location) {
|
||||
<div class="event-location">
|
||||
<strong>📍 Lieu :</strong>
|
||||
<span>{{selectedEvent.location}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="event-datetime">
|
||||
<strong>📅 Date :</strong>
|
||||
<span>{{selectedEvent.start | date:'dd/MM/yyyy à HH:mm'}}</span>
|
||||
</div>
|
||||
|
||||
@if (selectedEvent.end) {
|
||||
<div class="event-end">
|
||||
<strong>⏰ Fin :</strong>
|
||||
<span>{{selectedEvent.end | date:'dd/MM/yyyy à HH:mm'}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (selectedEvent.type) {
|
||||
<div class="event-type">
|
||||
<strong>Type :</strong>
|
||||
<span class="type-badge">{{selectedEvent.type}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (selectedEvent.properties) {
|
||||
<div class="event-properties">
|
||||
<strong>Propriétés :</strong>
|
||||
<div class="properties-list">
|
||||
@for (prop of getObjectKeys(selectedEvent.properties); track prop) {
|
||||
<div class="property-item">
|
||||
<span class="property-key">{{prop}} :</span>
|
||||
<span class="property-value">{{selectedEvent.properties[prop]}}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
563
frontend/src/app/pages/agenda/calendar/calendar.scss
Normal file
563
frontend/src/app/pages/agenda/calendar/calendar.scss
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 || {});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,64 +1,79 @@
|
|||
<div class="layout">
|
||||
<div class="aside">
|
||||
<div class="toolbar">
|
||||
<strong>OpenEventDatabase</strong>
|
||||
<span class="muted">{{filteredFeatures.length}} évènements</span>
|
||||
|
||||
@if (isLoading) {
|
||||
<span class="loading">⏳ Chargement...</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="control-group">
|
||||
<label>Jours à venir</label>
|
||||
<input
|
||||
type="number"
|
||||
class="input"
|
||||
[(ngModel)]="daysAhead"
|
||||
(ngModelChange)="onDaysAheadChange()"
|
||||
min="1"
|
||||
max="30"
|
||||
placeholder="7">
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
[(ngModel)]="autoReloadEnabled"
|
||||
(change)="toggleAutoReload()">
|
||||
Rechargement auto (1min)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
(click)="goToNewCategories()">
|
||||
📋 Nouvelles catégories
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="filters">
|
||||
<label>Filtre rapide</label>
|
||||
|
||||
<label (click)="showFilters = !showFilters">
|
||||
Filtre rapide
|
||||
@if (showFilters) {
|
||||
<span>▼</span>
|
||||
} @else {
|
||||
<span>▶</span>
|
||||
}
|
||||
</label>
|
||||
|
||||
<div class="filters-group">
|
||||
@if (showFilters) {
|
||||
<span class="muted">{{filteredFeatures.length}} évènements chargés</span>
|
||||
<hr>
|
||||
<div class="controls">
|
||||
<div class="control-group">
|
||||
<label>Jours à venir</label>
|
||||
<input
|
||||
type="number"
|
||||
class="input"
|
||||
[(ngModel)]="daysAhead"
|
||||
(ngModelChange)="onDaysAheadChange()"
|
||||
min="1"
|
||||
max="30"
|
||||
placeholder="7">
|
||||
</div>
|
||||
|
||||
<div class="control-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
[(ngModel)]="autoReloadEnabled"
|
||||
(change)="toggleAutoReload()">
|
||||
Rechargement auto (1min)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<input class="input" type="text" placeholder="Rechercher..." [(ngModel)]="searchText" (ngModelChange)="onSearchChange()">
|
||||
|
||||
<div class="control-group">
|
||||
<label>Filtrer par type d'événement</label>
|
||||
<select class="input" [(ngModel)]="selectedWhatFilter" (ngModelChange)="onWhatFilterChange()">
|
||||
<option value="">Tous les types</option>
|
||||
@for (whatType of availableWhatTypes; track whatType) {
|
||||
<option [value]="whatType">{{whatType}}</option>
|
||||
<option value="">Tous les types</option>
|
||||
@for (whatType of availableWhatTypes; track whatType) {
|
||||
<option [value]="whatType">{{whatType}}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<app-osm></app-osm>
|
||||
|
||||
<app-menu></app-menu>
|
||||
<hr>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <app-unlocated-events [events]="filteredFeatures"></app-unlocated-events> -->
|
||||
|
||||
<hr>
|
||||
<app-unlocated-events [events]="filteredFeatures"></app-unlocated-events>
|
||||
<app-menu></app-menu>
|
||||
<hr>
|
||||
<app-osm></app-osm>
|
||||
|
||||
<app-edit-form [selected]="selected" (saved)="onSaved($event)" (created)="onCreated($event)" (deleted)="onDeleted($event)"></app-edit-form>
|
||||
</div>
|
||||
<div class="main">
|
||||
|
|
|
@ -32,6 +32,7 @@ export class Home implements OnInit, OnDestroy {
|
|||
filteredFeatures: Array<any> = [];
|
||||
selected: any | null = null;
|
||||
showTable = false;
|
||||
showFilters = false;
|
||||
|
||||
// Nouvelles propriétés pour le rechargement automatique et la sélection de jours
|
||||
autoReloadEnabled = true;
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
<menu>
|
||||
OpenEventDatabase
|
||||
<nav>
|
||||
|
||||
|
||||
<a routerLink="/agenda">agenda</a>
|
||||
<a routerLink="/unlocated-events">événements non localisés</a>
|
||||
<a href="/demo/stats">stats</a>
|
||||
<a href="https://source.cipherbliss.com/tykayn/oedb-backend">sources</a>
|
||||
</nav>
|
||||
|
||||
|
||||
<!--
|
||||
<div id="editor_form">
|
||||
<!-- <div id="search_input">
|
||||
<div id="search_input">
|
||||
<input type="text" value="" placeholder="Rechercher une catégorie d'évènement">
|
||||
</div>
|
||||
<div id="what_categories">
|
||||
|
@ -56,10 +49,10 @@
|
|||
<option value="point"></option>
|
||||
<option value="polyline"></option>
|
||||
<option value="bbox"></option>
|
||||
</select> -->
|
||||
</select>
|
||||
|
||||
</div>
|
||||
|
||||
-->
|
||||
<!-- <div id="found_list">
|
||||
<h2>données</h2>
|
||||
(liste des éléments trouvés)
|
||||
|
|
|
@ -1,17 +1,6 @@
|
|||
:host {
|
||||
display: block;
|
||||
nav{
|
||||
a {
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(0,0,0,0.08);
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
&:hover{
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#what_categories {
|
||||
|
|
|
@ -110,6 +110,9 @@
|
|||
font-size: 1.1rem;
|
||||
flex: 1;
|
||||
line-height: 1.3;
|
||||
max-width: 200px;
|
||||
overflow: auto;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.event-type {
|
||||
|
@ -120,6 +123,9 @@
|
|||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
margin-left: 10px;
|
||||
max-width: 100px;
|
||||
overflow: auto;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue