style marqueurs events, page nouvelles catégories
This commit is contained in:
parent
20a8445a5f
commit
9fb9986a2c
15 changed files with 987 additions and 203 deletions
|
@ -1,14 +1,20 @@
|
|||
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import * as moment from 'moment';
|
||||
import 'moment/locale/fr';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
|
||||
// Configuration du locale français pour moment
|
||||
moment.locale('fr');
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
provideHttpClient()
|
||||
provideHttpClient(),
|
||||
{ provide: 'moment', useValue: moment }
|
||||
]
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Routes } from '@angular/router';
|
||||
import {Home} from './pages/home/home';
|
||||
import { Agenda } from './pages/agenda/agenda';
|
||||
import { NouvellesCategories } from './pages/nouvelles-categories/nouvelles-categories';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
|
@ -10,5 +11,9 @@ export const routes: Routes = [
|
|||
{
|
||||
path : 'agenda',
|
||||
component: Agenda
|
||||
},
|
||||
{
|
||||
path : 'nouvelles-categories',
|
||||
component: NouvellesCategories
|
||||
}
|
||||
];
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import oedb_what_categories from '../../../oedb-types';
|
||||
|
||||
@Component({
|
||||
|
@ -8,7 +9,7 @@ import oedb_what_categories from '../../../oedb-types';
|
|||
templateUrl: './all-events.html',
|
||||
styleUrl: './all-events.scss'
|
||||
})
|
||||
export class AllEvents {
|
||||
export class AllEvents implements OnInit, OnDestroy {
|
||||
@Input() features: Array<any> = [];
|
||||
@Input() selected: any | null = null;
|
||||
@Input() highlight: { id: string | number, type: 'saved' | 'deleted' } | null = null;
|
||||
|
@ -22,6 +23,13 @@ export class AllEvents {
|
|||
private pickedMarker: any | null = null;
|
||||
private originalCoords: [number, number] | null = null;
|
||||
private currentPicked: [number, number] | null = null;
|
||||
private isInitialLoad = true;
|
||||
private mapInitialized = false;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.ensureMapLibre();
|
||||
|
@ -76,11 +84,17 @@ export class AllEvents {
|
|||
|
||||
private initMap() {
|
||||
const maplibregl = (window as any).maplibregl;
|
||||
|
||||
// Récupérer les paramètres de l'URL ou utiliser les valeurs par défaut (Île-de-France)
|
||||
const lat = parseFloat(this.route.snapshot.queryParams['lat']) || 48.8566;
|
||||
const lon = parseFloat(this.route.snapshot.queryParams['lon']) || 2.3522;
|
||||
const zoom = parseFloat(this.route.snapshot.queryParams['zoom']) || 8;
|
||||
|
||||
this.map = new maplibregl.Map({
|
||||
container: this.mapContainer.nativeElement,
|
||||
style: 'https://tiles.openfreemap.org/styles/liberty',
|
||||
center: [2.3522, 48.8566],
|
||||
zoom: 5
|
||||
center: [lon, lat],
|
||||
zoom: zoom
|
||||
});
|
||||
this.map.addControl(new maplibregl.NavigationControl());
|
||||
this.map.addControl(new maplibregl.GeolocateControl({ positionOptions: { enableHighAccuracy: true }, trackUserLocation: true }));
|
||||
|
@ -90,6 +104,21 @@ export class AllEvents {
|
|||
this.showPickedMarker(coords);
|
||||
this.pickCoords.emit(coords);
|
||||
});
|
||||
|
||||
// Écouter les changements de vue pour mettre à jour l'URL
|
||||
this.map.on('moveend', () => {
|
||||
if (this.mapInitialized) {
|
||||
this.updateUrlFromMap();
|
||||
}
|
||||
});
|
||||
|
||||
this.map.on('zoomend', () => {
|
||||
if (this.mapInitialized) {
|
||||
this.updateUrlFromMap();
|
||||
}
|
||||
});
|
||||
|
||||
this.mapInitialized = true;
|
||||
}
|
||||
|
||||
private getEmojiForWhat(what: string): string {
|
||||
|
@ -164,6 +193,24 @@ export class AllEvents {
|
|||
if (this.map) this.map.flyTo({ center: this.originalCoords, zoom: Math.max(this.map.getZoom() || 12, 12) });
|
||||
}
|
||||
|
||||
private updateUrlFromMap() {
|
||||
if (!this.map) return;
|
||||
|
||||
const center = this.map.getCenter();
|
||||
const zoom = this.map.getZoom();
|
||||
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: {
|
||||
lat: center.lat.toFixed(6),
|
||||
lon: center.lng.toFixed(6),
|
||||
zoom: Math.round(zoom)
|
||||
},
|
||||
queryParamsHandling: 'merge',
|
||||
replaceUrl: true
|
||||
});
|
||||
}
|
||||
|
||||
private renderFeatures() {
|
||||
if (!this.map || !Array.isArray(this.features)) return;
|
||||
// clear existing markers
|
||||
|
@ -225,7 +272,15 @@ export class AllEvents {
|
|||
bounds.extend(coords);
|
||||
});
|
||||
|
||||
if (!bounds.isEmpty()) {
|
||||
// Ne pas faire de fitBounds lors du chargement initial si on a des paramètres URL
|
||||
if (!bounds.isEmpty() && this.isInitialLoad) {
|
||||
const hasUrlParams = this.route.snapshot.queryParams['lat'] || this.route.snapshot.queryParams['lon'] || this.route.snapshot.queryParams['zoom'];
|
||||
if (!hasUrlParams) {
|
||||
this.map.fitBounds(bounds, { padding: 40, maxZoom: 12 });
|
||||
}
|
||||
this.isInitialLoad = false;
|
||||
} else if (!bounds.isEmpty() && !this.isInitialLoad) {
|
||||
// Pour les mises à jour suivantes, on peut faire un fitBounds léger
|
||||
this.map.fitBounds(bounds, { padding: 40, maxZoom: 12 });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,35 +2,59 @@
|
|||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agenda-content">
|
||||
<div class="days-grid">
|
||||
@for (day of daysWithEvents; track day.date.getTime()) {
|
||||
<div class="day-card" [class.today]="isToday(day.date)" [class.past]="isPast(day.date)">
|
||||
<div class="day-header">
|
||||
<h3>{{ formatDate(day.date) }}</h3>
|
||||
<span class="event-count">{{ day.events.length }} événement(s)</span>
|
||||
</div>
|
||||
|
||||
<div class="events-list">
|
||||
@if (day.events.length === 0) {
|
||||
<p class="no-events">Aucun événement</p>
|
||||
} @else {
|
||||
@for (event of day.events; track event.id) {
|
||||
<div class="event-item" (click)="selectEvent(event)">
|
||||
<div class="event-time">{{ getEventTime(event) }}</div>
|
||||
<div class="event-title">{{ getEventTitle(event) }}</div>
|
||||
@if (event.properties.what) {
|
||||
<div class="event-type">{{ event.properties.what }}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<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 -->
|
||||
|
@ -56,4 +80,12 @@
|
|||
@if (showSidePanel) {
|
||||
<div class="overlay" (click)="closeSidePanel()"></div>
|
||||
}
|
||||
</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>
|
|
@ -1,6 +1,6 @@
|
|||
.agenda-container {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
@ -17,122 +17,132 @@
|
|||
p {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar-controls {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.agenda-content {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.days-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.day-card {
|
||||
background: white;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
&.today {
|
||||
border-color: #007bff;
|
||||
background: #f8f9ff;
|
||||
// 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;
|
||||
}
|
||||
|
||||
.day-header h3 {
|
||||
color: #007bff;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
&.past {
|
||||
opacity: 0.7;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.day-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.event-count {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.events-list {
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.no-events {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.event-item {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: #e9ecef;
|
||||
border-color: #007bff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.event-time {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.event-title {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.event-type {
|
||||
font-size: 12px;
|
||||
color: #007bff;
|
||||
background: #e7f3ff;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// Panneau latéral
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { Component, inject, OnInit, ViewChild, TemplateRef } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
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';
|
||||
|
||||
interface Event {
|
||||
interface OedbEvent {
|
||||
id: string;
|
||||
properties: {
|
||||
label?: string;
|
||||
|
@ -23,23 +26,31 @@ interface Event {
|
|||
|
||||
interface DayEvents {
|
||||
date: Date;
|
||||
events: Event[];
|
||||
events: OedbEvent[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-agenda',
|
||||
standalone: true,
|
||||
imports: [CommonModule, EditForm],
|
||||
imports: [CommonModule, EditForm, CalendarModule],
|
||||
templateUrl: './agenda.html',
|
||||
styleUrl: './agenda.scss'
|
||||
})
|
||||
export class Agenda implements OnInit {
|
||||
private oedbApi = inject(OedbApi);
|
||||
|
||||
events: Event[] = [];
|
||||
daysWithEvents: DayEvents[] = [];
|
||||
selectedEvent: Event | null = null;
|
||||
@ViewChild('eventTitleTemplate', { static: true }) eventTitleTemplate!: TemplateRef<any>;
|
||||
|
||||
events: OedbEvent[] = [];
|
||||
calendarEvents: CalendarEvent[] = [];
|
||||
selectedEvent: OedbEvent | null = null;
|
||||
showSidePanel = false;
|
||||
view: CalendarView = CalendarView.Month;
|
||||
viewDate: Date = new Date();
|
||||
oedbPresets = oedb.presets.what;
|
||||
|
||||
// Exposer CalendarView pour l'utiliser dans le template
|
||||
CalendarView = CalendarView;
|
||||
|
||||
ngOnInit() {
|
||||
this.loadEvents();
|
||||
|
@ -66,37 +77,24 @@ export class Agenda implements OnInit {
|
|||
}
|
||||
|
||||
organizeEventsByDay() {
|
||||
const daysMap = new Map<string, DayEvents>();
|
||||
|
||||
// Initialiser les 20 jours
|
||||
for (let i = -10; i <= 10; i++) {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + i);
|
||||
const dateKey = date.toISOString().split('T')[0];
|
||||
daysMap.set(dateKey, {
|
||||
date: new Date(date),
|
||||
events: []
|
||||
});
|
||||
}
|
||||
|
||||
// Organiser les événements par jour
|
||||
this.events.forEach(event => {
|
||||
this.calendarEvents = this.events.map(event => {
|
||||
const eventDate = this.getEventDate(event);
|
||||
if (eventDate) {
|
||||
const dateKey = eventDate.toISOString().split('T')[0];
|
||||
const dayEvents = daysMap.get(dateKey);
|
||||
if (dayEvents) {
|
||||
dayEvents.events.push(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
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
this.daysWithEvents = Array.from(daysMap.values()).sort((a, b) =>
|
||||
a.date.getTime() - b.date.getTime()
|
||||
);
|
||||
}
|
||||
|
||||
getEventDate(event: Event): Date | null {
|
||||
getEventDate(event: OedbEvent): Date | null {
|
||||
const startDate = event.properties.start || event.properties.when;
|
||||
if (startDate) {
|
||||
return new Date(startDate);
|
||||
|
@ -104,11 +102,11 @@ export class Agenda implements OnInit {
|
|||
return null;
|
||||
}
|
||||
|
||||
getEventTitle(event: Event): string {
|
||||
getEventTitle(event: OedbEvent): string {
|
||||
return event.properties.label || event.properties.name || 'Événement sans titre';
|
||||
}
|
||||
|
||||
getEventTime(event: Event): string {
|
||||
getEventTime(event: OedbEvent): string {
|
||||
const startDate = event.properties.start || event.properties.when;
|
||||
if (startDate) {
|
||||
const date = new Date(startDate);
|
||||
|
@ -120,11 +118,55 @@ export class Agenda implements OnInit {
|
|||
return '';
|
||||
}
|
||||
|
||||
selectEvent(event: Event) {
|
||||
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;
|
||||
|
@ -145,22 +187,19 @@ export class Agenda implements OnInit {
|
|||
this.closeSidePanel();
|
||||
}
|
||||
|
||||
isToday(date: Date): boolean {
|
||||
const today = new Date();
|
||||
return date.toDateString() === today.toDateString();
|
||||
setView(view: CalendarView) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
isPast(date: Date): boolean {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
return date < today;
|
||||
dayClicked({ date, events }: { date: Date; events: CalendarEvent[] }): void {
|
||||
console.log('Day clicked:', date, events);
|
||||
}
|
||||
|
||||
formatDate(date: Date): string {
|
||||
return date.toLocaleDateString('fr-FR', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long'
|
||||
});
|
||||
eventTimesChanged({
|
||||
event,
|
||||
newStart,
|
||||
newEnd,
|
||||
}: CalendarEventTimesChangedEvent): void {
|
||||
console.log('Event times changed:', event, newStart, newEnd);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,43 @@
|
|||
<div class="toolbar">
|
||||
<strong>OpenEventDatabase</strong>
|
||||
<span class="muted">{{features.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>
|
||||
<input class="input" type="text" placeholder="Rechercher...">
|
||||
|
|
|
@ -29,6 +29,69 @@
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
|
||||
.loading {
|
||||
color: #007bff;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin: 15px 0;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e9ecef;
|
||||
|
||||
.control-group {
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
margin-bottom: 4px;
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 2px rgba(0,123,255,0.25);
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.map {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { Component, inject } from '@angular/core';
|
||||
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import {Menu} from './menu/menu';
|
||||
import { AllEvents } from '../../maps/all-events/all-events';
|
||||
import { EditForm } from '../../forms/edit-form/edit-form';
|
||||
|
@ -11,24 +13,86 @@ import { UnlocatedEvents } from '../../shared/unlocated-events/unlocated-events'
|
|||
Menu,
|
||||
AllEvents,
|
||||
UnlocatedEvents,
|
||||
EditForm
|
||||
EditForm,
|
||||
FormsModule
|
||||
],
|
||||
templateUrl: './home.html',
|
||||
styleUrl: './home.scss'
|
||||
})
|
||||
export class Home {
|
||||
export class Home implements OnInit, OnDestroy {
|
||||
|
||||
OedbApi = inject(OedbApi);
|
||||
private router = inject(Router);
|
||||
|
||||
features: Array<any> = [];
|
||||
selected: any | null = null;
|
||||
showTable = false;
|
||||
|
||||
// Nouvelles propriétés pour le rechargement automatique et la sélection de jours
|
||||
autoReloadEnabled = true;
|
||||
autoReloadInterval: any = null;
|
||||
daysAhead = 7; // Nombre de jours dans le futur par défaut
|
||||
isLoading = false;
|
||||
|
||||
constructor() {
|
||||
this.OedbApi.getEvents({ when: 'now', limit: 500 }).subscribe((events: any) => {
|
||||
ngOnInit() {
|
||||
this.loadEvents();
|
||||
this.startAutoReload();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.stopAutoReload();
|
||||
}
|
||||
|
||||
loadEvents() {
|
||||
this.isLoading = true;
|
||||
const today = new Date();
|
||||
const endDate = new Date(today);
|
||||
endDate.setDate(today.getDate() + this.daysAhead);
|
||||
|
||||
const params = {
|
||||
start: today.toISOString().split('T')[0],
|
||||
end: endDate.toISOString().split('T')[0],
|
||||
limit: 1000
|
||||
};
|
||||
|
||||
this.OedbApi.getEvents(params).subscribe((events: any) => {
|
||||
this.features = Array.isArray(events?.features) ? events.features : [];
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
startAutoReload() {
|
||||
if (this.autoReloadEnabled && !this.autoReloadInterval) {
|
||||
this.autoReloadInterval = setInterval(() => {
|
||||
this.loadEvents();
|
||||
}, 60000); // 1 minute
|
||||
}
|
||||
}
|
||||
|
||||
stopAutoReload() {
|
||||
if (this.autoReloadInterval) {
|
||||
clearInterval(this.autoReloadInterval);
|
||||
this.autoReloadInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
toggleAutoReload() {
|
||||
this.autoReloadEnabled = !this.autoReloadEnabled;
|
||||
if (this.autoReloadEnabled) {
|
||||
this.startAutoReload();
|
||||
} else {
|
||||
this.stopAutoReload();
|
||||
}
|
||||
}
|
||||
|
||||
onDaysAheadChange() {
|
||||
this.loadEvents();
|
||||
}
|
||||
|
||||
goToNewCategories() {
|
||||
this.router.navigate(['/nouvelles-categories']);
|
||||
}
|
||||
|
||||
onSelect(feature: any) {
|
||||
this.selected = feature;
|
||||
}
|
||||
|
@ -52,24 +116,18 @@ export class Home {
|
|||
|
||||
onSaved(_res: any) {
|
||||
// refresh list after update
|
||||
this.OedbApi.getEvents({ when: 'now', limit: 500 }).subscribe((events: any) => {
|
||||
this.features = Array.isArray(events?.features) ? events.features : [];
|
||||
});
|
||||
this.loadEvents();
|
||||
}
|
||||
|
||||
onCreated(_res: any) {
|
||||
// refresh and clear selection after create
|
||||
this.selected = null;
|
||||
this.OedbApi.getEvents({ when: 'now', limit: 500 }).subscribe((events: any) => {
|
||||
this.features = Array.isArray(events?.features) ? events.features : [];
|
||||
});
|
||||
this.loadEvents();
|
||||
}
|
||||
|
||||
onDeleted(_res: any) {
|
||||
this.selected = null;
|
||||
this.OedbApi.getEvents({ when: 'now', limit: 500 }).subscribe((events: any) => {
|
||||
this.features = Array.isArray(events?.features) ? events.features : [];
|
||||
});
|
||||
this.loadEvents();
|
||||
}
|
||||
|
||||
// Menu callbacks
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<div class="nouvelles-categories-container">
|
||||
<div class="header">
|
||||
<h1>📋 Nouvelles catégories d'événements</h1>
|
||||
<p>Découvrez les nouveaux types d'événements qui ne sont pas encore dans la configuration OEDB</p>
|
||||
|
||||
<div class="controls">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
(click)="loadNewCategories()"
|
||||
[disabled]="isLoading">
|
||||
@if (isLoading) {
|
||||
⏳ Chargement...
|
||||
} @else {
|
||||
🔄 Actualiser
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (isLoading) {
|
||||
<div class="loading">
|
||||
<p>Analyse des événements des 30 prochains jours...</p>
|
||||
</div>
|
||||
} @else if (eventTypes.length === 0) {
|
||||
<div class="no-data">
|
||||
<p>🎉 Aucune nouvelle catégorie trouvée ! Tous les types d'événements sont déjà dans la configuration.</p>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="categories-list">
|
||||
<h2>{{ eventTypes.length }} nouvelle(s) catégorie(s) trouvée(s)</h2>
|
||||
|
||||
@for (eventType of eventTypes; track eventType.what) {
|
||||
<div class="category-card">
|
||||
<div class="category-header">
|
||||
<h3>{{ eventType.what }}</h3>
|
||||
<span class="count">{{ eventType.count }} événement(s)</span>
|
||||
</div>
|
||||
|
||||
<div class="examples">
|
||||
<h4>Exemples :</h4>
|
||||
<ul>
|
||||
@for (example of eventType.examples; track example.id) {
|
||||
<li>
|
||||
<strong>{{ example.properties?.label || example.properties?.name || 'Sans titre' }}</strong>
|
||||
@if (example.properties?.start || example.properties?.when) {
|
||||
- {{ example.properties?.start || example.properties?.when }}
|
||||
}
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="code-generation">
|
||||
<h4>Code à ajouter dans oedb-types.ts :</h4>
|
||||
<div class="code-block">
|
||||
<pre><code>{{ generateJsonCode(eventType) }}</code></pre>
|
||||
<button
|
||||
class="btn btn-sm copy-btn"
|
||||
(click)="copyToClipboard(generateJsonCode(eventType))">
|
||||
📋 Copier
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
|
@ -0,0 +1,168 @@
|
|||
.nouvelles-categories-container {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading, .no-data {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
|
||||
p {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.categories-list {
|
||||
h2 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #007bff;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.category-card {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
|
||||
.category-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
|
||||
h3 {
|
||||
color: #007bff;
|
||||
margin: 0;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.count {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.examples {
|
||||
margin-bottom: 20px;
|
||||
|
||||
h4 {
|
||||
color: #495057;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
|
||||
li {
|
||||
margin-bottom: 5px;
|
||||
color: #6c757d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.code-generation {
|
||||
h4 {
|
||||
color: #495057;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
position: relative;
|
||||
background: #2d3748;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
color: #e2e8f0;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
overflow-x: auto;
|
||||
|
||||
code {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: #4a5568;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: #2d3748;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.btn-primary {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: #0056b3;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #6c757d;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-sm {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NouvellesCategories } from './nouvelles-categories';
|
||||
|
||||
describe('NouvellesCategories', () => {
|
||||
let component: NouvellesCategories;
|
||||
let fixture: ComponentFixture<NouvellesCategories>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NouvellesCategories]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(NouvellesCategories);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,190 @@
|
|||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { OedbApi } from '../../services/oedb-api';
|
||||
import oedb from '../../../oedb-types';
|
||||
|
||||
interface EventType {
|
||||
what: string;
|
||||
count: number;
|
||||
examples: any[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-nouvelles-categories',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './nouvelles-categories.html',
|
||||
styleUrl: './nouvelles-categories.scss'
|
||||
})
|
||||
export class NouvellesCategories implements OnInit {
|
||||
private oedbApi = inject(OedbApi);
|
||||
|
||||
eventTypes: EventType[] = [];
|
||||
isLoading = false;
|
||||
oedbPresets = oedb.presets.what;
|
||||
|
||||
ngOnInit() {
|
||||
this.loadNewCategories();
|
||||
}
|
||||
|
||||
loadNewCategories() {
|
||||
this.isLoading = true;
|
||||
const today = new Date();
|
||||
const endDate = new Date(today);
|
||||
endDate.setDate(today.getDate() + 30); // 30 prochains jours
|
||||
|
||||
const params = {
|
||||
start: today.toISOString().split('T')[0],
|
||||
end: endDate.toISOString().split('T')[0],
|
||||
limit: 1000
|
||||
};
|
||||
|
||||
this.oedbApi.getEvents(params).subscribe((response: any) => {
|
||||
const events = Array.isArray(response?.features) ? response.features : [];
|
||||
this.analyzeEventTypes(events);
|
||||
this.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
private analyzeEventTypes(events: any[]) {
|
||||
const typeMap = new Map<string, { count: number; examples: any[] }>();
|
||||
|
||||
events.forEach(event => {
|
||||
const what = event.properties?.what;
|
||||
if (what && !this.isKnownType(what)) {
|
||||
if (!typeMap.has(what)) {
|
||||
typeMap.set(what, { count: 0, examples: [] });
|
||||
}
|
||||
const typeData = typeMap.get(what)!;
|
||||
typeData.count++;
|
||||
if (typeData.examples.length < 3) {
|
||||
typeData.examples.push(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.eventTypes = Array.from(typeMap.entries())
|
||||
.map(([what, data]) => ({
|
||||
what,
|
||||
count: data.count,
|
||||
examples: data.examples
|
||||
}))
|
||||
.sort((a, b) => b.count - a.count);
|
||||
}
|
||||
|
||||
private isKnownType(what: string): boolean {
|
||||
return !!(this.oedbPresets as any)[what];
|
||||
}
|
||||
|
||||
generateJsonCode(eventType: EventType): string {
|
||||
const what = eventType.what;
|
||||
const example = eventType.examples[0];
|
||||
const properties = example?.properties || {};
|
||||
|
||||
// Générer un emoji basé sur le type
|
||||
const emoji = this.suggestEmoji(what);
|
||||
|
||||
// Générer une catégorie basée sur le premier mot
|
||||
const category = this.suggestCategory(what);
|
||||
|
||||
// Générer des propriétés basées sur l'exemple
|
||||
const suggestedProperties = this.suggestProperties(properties);
|
||||
|
||||
return `'${what}': {
|
||||
emoji: '${emoji}',
|
||||
label: '${this.suggestLabel(what)}',
|
||||
category: '${category}',
|
||||
description: '${this.suggestDescription(what)}',
|
||||
durationHours: 24${suggestedProperties ? ',\n properties: {\n' + suggestedProperties + '\n }' : ''}
|
||||
}`;
|
||||
}
|
||||
|
||||
private suggestEmoji(what: string): string {
|
||||
const emojiMap: { [key: string]: string } = {
|
||||
'music': '🎵',
|
||||
'sport': '⚽',
|
||||
'food': '🍽️',
|
||||
'art': '🎨',
|
||||
'tech': '💻',
|
||||
'health': '🏥',
|
||||
'education': '📚',
|
||||
'business': '💼',
|
||||
'travel': '✈️',
|
||||
'nature': '🌿',
|
||||
'social': '👥',
|
||||
'festival': '🎪',
|
||||
'conference': '🎤',
|
||||
'workshop': '🔧',
|
||||
'exhibition': '🖼️',
|
||||
'sale': '🛒',
|
||||
'meeting': '🤝',
|
||||
'party': '🎉',
|
||||
'concert': '🎸',
|
||||
'theater': '🎭'
|
||||
};
|
||||
|
||||
const firstWord = what.split('.')[0].toLowerCase();
|
||||
return emojiMap[firstWord] || '📅';
|
||||
}
|
||||
|
||||
private suggestCategory(what: string): string {
|
||||
const categoryMap: { [key: string]: string } = {
|
||||
'music': 'Musique',
|
||||
'sport': 'Sport',
|
||||
'food': 'Gastronomie',
|
||||
'art': 'Culture',
|
||||
'tech': 'Technologie',
|
||||
'health': 'Santé',
|
||||
'education': 'Éducation',
|
||||
'business': 'Commerce',
|
||||
'travel': 'Tourisme',
|
||||
'nature': 'Nature',
|
||||
'social': 'Communauté',
|
||||
'festival': 'Culture',
|
||||
'conference': 'Professionnel',
|
||||
'workshop': 'Formation',
|
||||
'exhibition': 'Culture',
|
||||
'sale': 'Commerce',
|
||||
'meeting': 'Professionnel',
|
||||
'party': 'Social',
|
||||
'concert': 'Musique',
|
||||
'theater': 'Culture'
|
||||
};
|
||||
|
||||
const firstWord = what.split('.')[0].toLowerCase();
|
||||
return categoryMap[firstWord] || 'Autre';
|
||||
}
|
||||
|
||||
private suggestLabel(what: string): string {
|
||||
return what.split('.').map(word =>
|
||||
word.charAt(0).toUpperCase() + word.slice(1)
|
||||
).join(' ');
|
||||
}
|
||||
|
||||
private suggestDescription(what: string): string {
|
||||
return `Événement de type ${this.suggestLabel(what)}`;
|
||||
}
|
||||
|
||||
private suggestProperties(properties: any): string {
|
||||
const suggestedProps: string[] = [];
|
||||
|
||||
// Analyser les propriétés communes pour suggérer des champs
|
||||
Object.keys(properties).forEach(key => {
|
||||
if (key !== 'what' && key !== 'label' && key !== 'name' && key !== 'id' && key !== 'uuid') {
|
||||
const value = properties[key];
|
||||
if (typeof value === 'string' && value.length > 0) {
|
||||
suggestedProps.push(` ${key}: { label: '${this.suggestLabel(key)}', writable: true }`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return suggestedProps.join(',\n');
|
||||
}
|
||||
|
||||
copyToClipboard(text: string) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
// Optionnel: afficher une notification de succès
|
||||
console.log('Code copié dans le presse-papiers');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -186,6 +186,25 @@ const oedb = {
|
|||
properties: {
|
||||
lightning_count: { label: 'Nombre d’éclairs', writable: true }
|
||||
}
|
||||
}, 'weather.flood': {
|
||||
emoji: '🌊',
|
||||
label: 'Inondation',
|
||||
category: 'Météo',
|
||||
description: 'Inondation',
|
||||
durationHours: 24,
|
||||
properties: {
|
||||
flood_level: { label: 'Niveau d\'inondation', writable: true }
|
||||
}
|
||||
},
|
||||
'weather.snow': {
|
||||
emoji: '❄️',
|
||||
label: 'Neige',
|
||||
category: 'Météo',
|
||||
description: 'Neige',
|
||||
durationHours: 12,
|
||||
properties: {
|
||||
snow_level: { label: 'Niveau de neige', writable: true }
|
||||
}
|
||||
},
|
||||
'weather.earthquake': {
|
||||
emoji: '🌎',
|
||||
|
|
|
@ -110,3 +110,15 @@ label { font-size: 0.85rem; color: $color-muted; }
|
|||
.toast.is-success { background: $color-success; }
|
||||
.toast.is-error { background: $color-error; }
|
||||
.toast.is-info { background: $color-info; }
|
||||
|
||||
|
||||
// marquerus maplibre
|
||||
|
||||
.maplibregl-marker{
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 0 10px 5px rgba(0,0,0,0.2);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue