From 23c598034c8ad761634c5abd327d270d3f657c0c Mon Sep 17 00:00:00 2001 From: Tykayn Date: Tue, 7 Oct 2025 12:14:51 +0200 Subject: [PATCH] routes doc, future, express mode thematique --- frontend/src/app/app.routes.ts | 10 +++ .../community-upcoming.html | 9 +++ .../community-upcoming.scss | 8 +++ .../community-upcoming/community-upcoming.ts | 34 ++++++++++ .../app/pages/events-docs/events-docs.html | 15 +++++ .../app/pages/events-docs/events-docs.scss | 21 ++++++ .../src/app/pages/events-docs/events-docs.ts | 44 +++++++++++++ frontend/src/app/pages/home/home.html | 13 ++++ frontend/src/app/pages/home/home.scss | 12 ++++ frontend/src/app/pages/home/home.ts | 65 ++++++++++++++----- frontend/src/app/pages/home/menu/menu.html | 15 ++++- 11 files changed, 228 insertions(+), 18 deletions(-) create mode 100644 frontend/src/app/pages/community-upcoming/community-upcoming.html create mode 100644 frontend/src/app/pages/community-upcoming/community-upcoming.scss create mode 100644 frontend/src/app/pages/community-upcoming/community-upcoming.ts create mode 100644 frontend/src/app/pages/events-docs/events-docs.html create mode 100644 frontend/src/app/pages/events-docs/events-docs.scss create mode 100644 frontend/src/app/pages/events-docs/events-docs.ts diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 792b582..69e06fe 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -3,12 +3,22 @@ import {Home} from './pages/home/home'; import { Agenda } from './pages/agenda/agenda'; import { NouvellesCategories } from './pages/nouvelles-categories/nouvelles-categories'; import { UnlocatedEventsPage } from './pages/unlocated-events/unlocated-events'; +import { CommunityUpcoming } from './pages/community-upcoming/community-upcoming'; +import { EventsDocs } from './pages/events-docs/events-docs'; export const routes: Routes = [ { path : '', component: Home }, + { + path: 'community-upcoming', + component: CommunityUpcoming + }, + { + path: 'events-docs', + component: EventsDocs + }, { path : 'agenda', component: Agenda diff --git a/frontend/src/app/pages/community-upcoming/community-upcoming.html b/frontend/src/app/pages/community-upcoming/community-upcoming.html new file mode 100644 index 0000000..5abe668 --- /dev/null +++ b/frontend/src/app/pages/community-upcoming/community-upcoming.html @@ -0,0 +1,9 @@ +
+ +
+ + + + diff --git a/frontend/src/app/pages/community-upcoming/community-upcoming.scss b/frontend/src/app/pages/community-upcoming/community-upcoming.scss new file mode 100644 index 0000000..404e5b9 --- /dev/null +++ b/frontend/src/app/pages/community-upcoming/community-upcoming.scss @@ -0,0 +1,8 @@ +.toolbar { + display: flex; + gap: 12px; + align-items: center; + padding: 8px 0; +} + + diff --git a/frontend/src/app/pages/community-upcoming/community-upcoming.ts b/frontend/src/app/pages/community-upcoming/community-upcoming.ts new file mode 100644 index 0000000..ff259ae --- /dev/null +++ b/frontend/src/app/pages/community-upcoming/community-upcoming.ts @@ -0,0 +1,34 @@ +import { Component, inject, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { AllEvents } from '../../maps/all-events/all-events'; +import { OedbApi } from '../../services/oedb-api'; + +@Component({ + selector: 'app-community-upcoming', + imports: [CommonModule, FormsModule, AllEvents], + templateUrl: './community-upcoming.html', + styleUrl: './community-upcoming.scss' +}) +export class CommunityUpcoming { + private api = inject(OedbApi); + + days = signal(7); + features: Array = []; + selected: any | null = null; + + ngOnInit() { + this.load(); + } + + load() { + // when: NEXT7DAYS etc. Utilise le param 'when' déjà supporté par l'API + const d = Math.max(1, Number(this.days()) || 7); + const when = `NEXT${d}DAYS`; + this.api.getEvents({ when, what: 'commu', limit: 1000 }).subscribe((events: any) => { + this.features = Array.isArray(events?.features) ? events.features : []; + }); + } +} + + diff --git a/frontend/src/app/pages/events-docs/events-docs.html b/frontend/src/app/pages/events-docs/events-docs.html new file mode 100644 index 0000000..fc03dc5 --- /dev/null +++ b/frontend/src/app/pages/events-docs/events-docs.html @@ -0,0 +1,15 @@ +
+ +
+ +
+
+ + diff --git a/frontend/src/app/pages/events-docs/events-docs.scss b/frontend/src/app/pages/events-docs/events-docs.scss new file mode 100644 index 0000000..bae842a --- /dev/null +++ b/frontend/src/app/pages/events-docs/events-docs.scss @@ -0,0 +1,21 @@ +.docs { + display: grid; + grid-template-columns: 280px 1fr; + gap: 12px; +} +.sidebar { + max-height: calc(100vh - 160px); + overflow: auto; +} +.badge { + background: #1976d2; + color: #fff; + border-radius: 10px; + padding: 0 8px; + margin-left: 6px; +} +.map-panel { + min-height: 60vh; +} + + diff --git a/frontend/src/app/pages/events-docs/events-docs.ts b/frontend/src/app/pages/events-docs/events-docs.ts new file mode 100644 index 0000000..4f91407 --- /dev/null +++ b/frontend/src/app/pages/events-docs/events-docs.ts @@ -0,0 +1,44 @@ +import { Component, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { OedbApi } from '../../services/oedb-api'; +import { AllEvents } from '../../maps/all-events/all-events'; + +@Component({ + selector: 'app-events-docs', + imports: [CommonModule, AllEvents], + templateUrl: './events-docs.html', + styleUrl: './events-docs.scss' +}) +export class EventsDocs { + private api = inject(OedbApi); + + features: Array = []; + counts: Array<{ what: string, count: number }> = []; + filtered: Array = []; + selected: any | null = null; + + ngOnInit() { + // Charger 1000 events récents + this.api.getEvents({ when: 'NEXT30DAYS', limit: 1000 }).subscribe((events: any) => { + this.features = Array.isArray(events?.features) ? events.features : []; + this.buildCounts(); + this.filtered = this.features; + }); + } + + buildCounts() { + const map = new Map(); + for (const f of this.features) { + const w = (f?.properties?.what || '').trim(); + if (!w) continue; + map.set(w, (map.get(w) || 0) + 1); + } + this.counts = Array.from(map.entries()).sort((a,b) => b[1]-a[1]).map(([what, count]) => ({ what, count })); + } + + filterByWhat(what: string) { + this.filtered = this.features.filter(f => String(f?.properties?.what || '').startsWith(what)); + } +} + + diff --git a/frontend/src/app/pages/home/home.html b/frontend/src/app/pages/home/home.html index e9e5426..4852df3 100644 --- a/frontend/src/app/pages/home/home.html +++ b/frontend/src/app/pages/home/home.html @@ -135,6 +135,19 @@ lastupdate: }
+ @if (theme()) { +
+
Thème: {{ theme() }} — Cliquez sur la carte pour définir des coordonnées puis créez un évènement du sous-thème choisi.
+
+ @for (t of subthemes; track t.key) { + + } +
+
+ } @if (!showTable) {
diff --git a/frontend/src/app/pages/home/home.scss b/frontend/src/app/pages/home/home.scss index 51090cd..bca2646 100644 --- a/frontend/src/app/pages/home/home.scss +++ b/frontend/src/app/pages/home/home.scss @@ -142,3 +142,15 @@ app-edit-form{ z-index: 1000; padding-bottom: 150px; } + +.subtheme-bar { + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px 12px; + .help { font-size: 12px; color: #64748b; } + .chips { display: flex; gap: 6px; flex-wrap: wrap; } + .chip { border: 1px solid #e2e8f0; border-radius: 999px; padding: 6px 10px; background: #fff; cursor: pointer; } + .chip.active { background: #e3f2fd; border-color: #90caf9; } + .emoji { margin-right: 6px; } +} diff --git a/frontend/src/app/pages/home/home.ts b/frontend/src/app/pages/home/home.ts index dabb1df..270c022 100644 --- a/frontend/src/app/pages/home/home.ts +++ b/frontend/src/app/pages/home/home.ts @@ -1,3 +1,4 @@ +import { Component, inject, signal } from '@angular/core'; import { Component, inject, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; @@ -5,6 +6,9 @@ import {Menu} from './menu/menu'; import { AllEvents } from '../../maps/all-events/all-events'; import { EditForm } from '../../forms/edit-form/edit-form'; import { OedbApi } from '../../services/oedb-api'; +import { ActivatedRoute } from '@angular/router'; +import oedb from '../../../oedb-types'; + import { UnlocatedEvents } from '../../shared/unlocated-events/unlocated-events'; import { OsmAuth } from '../../services/osm-auth'; import { Osm } from '../../forms/osm/osm'; @@ -27,26 +31,30 @@ import { WhatFilterComponent } from '../../shared/what-filter/what-filter'; export class Home implements OnInit, OnDestroy { OedbApi = inject(OedbApi); + route = inject(ActivatedRoute); private router = inject(Router); private osmAuth = inject(OsmAuth); - + features: Array = []; filteredFeatures: Array = []; selected: any | null = null; showTable = false; showFilters = false; showEditForm = true; - + // 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; - + // Propriétés pour les filtres searchText = ''; selectedWhatFilter = ''; availableWhatTypes: string[] = []; + theme = signal(null); + subthemes: Array<{ key: string, label: string, emoji: string }> = []; + activeSubtheme = signal(null); ngOnInit() { this.loadEvents(); @@ -62,7 +70,7 @@ export class Home implements OnInit, OnDestroy { //this.showTable = false; //this.showFilters = true; this.showEditForm = true; - + } loadEvents() { @@ -70,7 +78,7 @@ export class Home implements OnInit, OnDestroy { 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], @@ -120,6 +128,12 @@ export class Home implements OnInit, OnDestroy { whatTypes.add(feature.properties.what); } }); + + this.route.queryParams.subscribe(p => { + const t = (p?.['theme'] || '').trim(); + this.theme.set(t || null); + this.buildSubthemes(); + }); this.availableWhatTypes = Array.from(whatTypes).sort(); } @@ -149,7 +163,7 @@ export class Home implements OnInit, OnDestroy { // Filtre par type d'événement if (this.selectedWhatFilter) { - filtered = filtered.filter(feature => + filtered = filtered.filter(feature => feature?.properties?.what === this.selectedWhatFilter ); } @@ -166,7 +180,6 @@ export class Home implements OnInit, OnDestroy { } onPickCoords(coords: [number, number]) { - // Autofill lat/lon in the form selection or prepare a new feature shell const [lon, lat] = coords; if (this.selected && this.selected.properties) { this.selected = { @@ -175,12 +188,22 @@ export class Home implements OnInit, OnDestroy { }; } else { const osmUsername = this.osmAuth.getUsername(); + const whatKey = this.activeSubtheme(); + let label = ''; + let description = ''; + if (whatKey) { + const preset = (oedb.presets.what as any)[whatKey]; + if (preset) { + label = preset.label || ''; + description = preset.description || ''; + } + } this.selected = { id: null, - properties: { - label: '', - description: '', - what: '', + properties: { + label: '', + description: '', + what: whatKey || '', where: '', ...(osmUsername && { last_modified_by: osmUsername }) }, @@ -195,7 +218,6 @@ export class Home implements OnInit, OnDestroy { } onCreated(_res: any) { - // refresh and clear selection after create this.selected = null; this.loadEvents(); } @@ -209,16 +231,29 @@ export class Home implements OnInit, OnDestroy { this.showEditForm = false; } - // Menu callbacks ngAfterViewInit() { - // Wire menu callbacks if needed via querySelector; left simple for now - // We keep logic here: toggling and downloads + // reserved } toggleView() { this.showTable = !this.showTable; } + private buildSubthemes() { + const t = this.theme(); + if (!t) { this.subthemes = []; this.activeSubtheme.set(null); return; } + const what = oedb.presets.what as Record; + const list: Array<{ key: string, label: string, emoji: string }> = []; + Object.keys(what).forEach(k => { + if (k === t || k.startsWith(`${t}.`)) { + list.push({ key: k, label: what[k].label || k, emoji: what[k].emoji || '' }); + } + }); + this.subthemes = list.sort((a, b) => a.key.localeCompare(b.key)); + const exact = this.subthemes.find(s => s.key === t); + this.activeSubtheme.set(exact ? exact.key : (this.subthemes[0]?.key || null)); + } + downloadGeoJSON() { const blob = new Blob([JSON.stringify({ type: 'FeatureCollection', features: this.filteredFeatures }, null, 2)], { type: 'application/geo+json' }); const url = URL.createObjectURL(blob); diff --git a/frontend/src/app/pages/home/menu/menu.html b/frontend/src/app/pages/home/menu/menu.html index ed11d56..127860a 100644 --- a/frontend/src/app/pages/home/menu/menu.html +++ b/frontend/src/app/pages/home/menu/menu.html @@ -1,7 +1,16 @@ OpenEventDatabase - -