ajout infos dans event preset

This commit is contained in:
Tykayn 2025-10-14 23:54:59 +02:00 committed by tykayn
parent 6e3965e515
commit e7a2d93d18
4 changed files with 203 additions and 61 deletions

View file

@ -363,6 +363,7 @@ export class EditForm implements OnChanges {
onCancelEdit() {
this.selected = null;
this.featureId.set(null);
this.form.reset({
label: '',

View file

@ -9,7 +9,7 @@
@if(showOptions){
<div class="aside-content">
@ -81,7 +81,7 @@
<!-- <app-osm></app-osm>
<app-menu></app-menu> -->
<hr>
}
@ -89,7 +89,7 @@
</div>
<!-- <app-unlocated-events [events]="filteredFeatures"></app-unlocated-events> -->
@if(showEditForm){
<div class="guide">
<h3>Guide</h3>
@ -129,7 +129,7 @@
@if(selected !== null){
<div class="selected">
<h3> sélectionné: {{selected.properties.name}} {{selected.properties.title}} {{selected.properties.label}}</h3>
<h3 (click)="showEditForm = !showEditForm"> sélectionné: {{selected.properties.name}} {{selected.properties.title}} {{selected.properties.label}}</h3>
<table>
<tbody>
@ -208,7 +208,7 @@
</div>
}
</div>
@if (selectedIds.length) {
<div class="batch-panel">
@ -252,27 +252,63 @@
</div>
</div>
}
}
</div>
<div class="main {{showOptions? 'is-small' : 'is-full'}}">
<button class="button toggle-options" (click)="showOptions = !showOptions">
<button class="button toggle-options btn-primary" (click)="showOptions = !showOptions">
Options
</button>
@if(!showUnlocatedList){
@if (pleinAirMode) {
@if (showQuickActions) {
<div class="quick-actions" style="margin-top:8px; display:flex; gap:6px; flex-wrap:wrap;">
<button class="btn" (click)="quickCreate('traffic.contestation')">🚩 Contester</button>
<button class="btn" (click)="quickCreate('traffic.interruption')">⛓️ Interruption</button>
<button class="btn" (click)="quickCreate('traffic.wrong_way')">⛖ Détourné</button>
<button [ngClass]="{'is-active' : selectedPreset == 'traffic.contestation'}"
class="btn" (click)="quickCreate('traffic.contestation')"
>🚩 Contester</button>
<button [ngClass]="{'is-active' : selectedPreset == 'traffic.interruption'}"
class="btn" (click)="quickCreate('traffic.interruption')"
>⛓️ Interruption</button>
<button [ngClass]="{'is-active' : selectedPreset == 'traffic.wrong_way'}"
class="btn" (click)="quickCreate('traffic.wrong_way')"
>⛖ Détourné</button>
</div>
}
}
@if (civilianMode) {
@if (showQuickActions) {
<div class="quick-actions" style="margin-top:8px; display:flex; gap:6px; flex-wrap:wrap;">
<button [ngClass]="{'is-active' : selectedPreset == 'power.lights.should_be_off'}"
class="btn" (click)="quickCreate('power.lights.should_be_off')"
>🚩 Lumières devraient être éteintes</button>
</div>
}
}
@if (showGuidePresetPlace) {
<p class="guide-preset">
Sélectionnez l'endroit sur la carte
</p>
}
@if (showGuidePresetMoreInfo) {
<p class="guide-preset-more">
Vous pouvez donner plus de détails
<input type="text" [(ngModel)]="guidePresetMoreInfoPseudo" placeholder="votre pseudo ou nom">
<textarea [(ngModel)]="presetMoreDetails" placeholder="tronc d'arbre dans le chemin"></textarea>
<button class="btn btn-primary" (click)="submitPreset()">Envoyer</button>
</p>
}
}
<!--fin des actions rapides-->
@if (toasts.length) {
<div class="toaster"
style="position:fixed;right:16px;top:16px;display:flex;flex-direction:column;gap:8px;z-index:1000;">
style="">
@for (t of toasts; track t.id) {
<div class="toast" [class.success]="t.type==='success'" [class.error]="t.type==='error'"
[class.info]="t.type==='info'"
@ -314,7 +350,7 @@
<ul class="event-list">
@for (f of unlocatedOrOnline; track f.id) {
<li class="event-item"
(click)="onSelect({ id: f?.properties?.id ?? f?.id, properties: f.properties, geometry: f.geometry })"
(click)="onSelectFromCalendarView({ id: f?.properties?.id ?? f?.id, properties: f.properties, geometry: f.geometry })"
[class.active]="selected?.id === (f?.properties?.id ?? f?.id)">
<span class="event-icon">📌</span>
<div class="event-meta">
@ -327,15 +363,19 @@
</div>
</aside>
<main class="agenda-main">
@if (selected) {
@if (selected && showEditForm) {
<div class="event-edit-panel">
<div class="panel-header">
<h3>Détails</h3>
<button class="btn-close" (click)="selected = null">×</button>
</div>
<div class="panel-content">
<app-edit-form [selected]="selected" (saved)="onSaved($event)" (created)="onCreated($event)"
(deleted)="onDeleted($event)">
<app-edit-form [selected]="selected"
(saved)="onSaved($event)"
(created)="onCreated($event)"
(deleted)="onDeleted($event)"
(canceled)="showEditForm=false"
>
</app-edit-form>
</div>
</div>
@ -379,6 +419,9 @@
<button class="fab counter" (click)="toggleUnlocatedPanel()" title="{{unlocatedOrOnline.length}} évènements non localisés ou en ligne">
{{unlocatedOrOnline.length}}
</button>
<button class="fab plus" (click)="createMammoth()" title="Créer un nouvel évènement (mammouth)">+
@if(!showUnlocatedList){
<button class="fab plus btn-primary" (click)="createMammoth()" title="Créer un nouvel évènement (mammouth)">+
</button>
</div>
}
</div>

View file

@ -176,12 +176,23 @@
}
.fab.counter{
background: #0d9488;
margin-left: 0.5rem;
}
.fab.plus{
background: #1976d2;
}
}
.filters{
label{
padding: 1rem;
display: block;
cursor: pointer;
&:hover{
background: lemonchiffon;
}
}
}
.presets{
position: fixed;
@ -196,6 +207,15 @@
margin-left: 0;
}
}
.agenda-main{
app-edit-form{
right: 2rem;
top: 3.5rem;
.actions{
right: 1rem !important;
}
}
}
app-edit-form{
position: fixed;
top: 135px;
@ -205,7 +225,7 @@ app-edit-form{
max-height: 77.7vh;
display: block;
overflow: auto;
background: rgba(228, 235, 255, 0.5);
background: rgba(228, 235, 255, 0.85);
border: 1px solid rgba(0,0,0,0.06);
border-radius: 10px;
padding: 10px;
@ -249,9 +269,9 @@ app-edit-form{
&:focus{
outline: none;
}
}
}
// presets de boutons
.quick-actions{
margin-top: 8px;
display: inline-block;
@ -263,7 +283,38 @@ app-edit-form{
z-index: 100;
}
.guide-preset{
background: #4caf50;
color: #222;
padding: 1rem;
border-radius: 0.5rem;
margin: 1rem;
position: fixed;
z-index: 100;
bottom: 2rem;
right: 3.5rem;
}
.guide-preset-more{
background: white;
padding: 1rem;
position: fixed;
z-index: 100;
bottom: 1rem;
margin: 1rem;
width: 70%;
input, textarea{
width: 90%;
}
button{
margin: 1rem;
display: block;
background: #4caf50;
color: white;
padding: 2rem 1rem;
border-radius: 0.5rem;
}
}
table{
width: 100%;
border-collapse: collapse;
@ -289,4 +340,20 @@ table{
th{
background: #f0f0f0;
}
}
}
.toaster{
position:fixed;
right:16px;
top:16px;
display:flex;
flex-direction:column;
gap:8px;
z-index:1000;
&.success{
background: greenyellow;
}
&.info{
background: cornflowerblue;
}
}

View file

@ -12,6 +12,7 @@ import { UnlocatedEvents } from '../../shared/unlocated-events/unlocated-events'
import { OsmAuth } from '../../services/osm-auth';
import { Osm } from '../../forms/osm/osm';
import { WhatFilterComponent } from '../../shared/what-filter/what-filter';
import {NgClass} from '@angular/common';
@Component({
selector: 'app-home',
standalone: true,
@ -22,7 +23,8 @@ import { WhatFilterComponent } from '../../shared/what-filter/what-filter';
EditForm,
Osm,
FormsModule,
WhatFilterComponent
WhatFilterComponent,
NgClass
],
templateUrl: './home.html',
styleUrl: './home.scss'
@ -42,6 +44,7 @@ export class Home implements OnInit, OnDestroy {
showEditForm = false;
showOptions = true;
pleinAirMode = false;
civilianMode = false;
toasts: Array<{ id: number, type: 'success' | 'error' | 'info', message: string }> = [];
selectionMode: 'none' | 'rectangle' | 'polygon' = 'none';
@ -51,7 +54,7 @@ export class Home implements OnInit, OnDestroy {
batchFieldKey = '';
batchFieldValue: any = '';
batchSummary: { success: number; failed: number; networkErrors: number } | null = null;
// Nouvelles propriétés pour le rechargement automatique et la sélection de jours
autoReloadEnabled = true;
autoReloadInterval: any = null;
@ -71,7 +74,7 @@ export class Home implements OnInit, OnDestroy {
// Option bbox
useBboxFilter = true;
currentBbox: { minLng: number, minLat: number, maxLng: number, maxLat: number } | null = null;
// Bbox par défaut pour l'Île-de-France
private readonly IDF_BBOX = {
minLng: 1.4,
@ -80,10 +83,11 @@ export class Home implements OnInit, OnDestroy {
maxLat: 49.2
};
// Debounce pour la recherche
private searchDebounceTimer: any = null;
protected searchDebounceTimer: any = null;
// Non localisés / en ligne
unlocatedOrOnline: Array<any> = [];
showUnlocatedList = false;
protected showQuickActions: boolean = true;
ngOnInit() {
// Écouteur global pour toasts
@ -92,10 +96,10 @@ export class Home implements OnInit, OnDestroy {
const d = e?.detail || {}; this.pushToast(d.type || 'info', d.message || '');
});
} catch {}
// Initialiser la bbox par défaut pour l'Île-de-France
this.currentBbox = { ...this.IDF_BBOX };
this.route.queryParamMap.subscribe(map => {
const id = (map.get('id') || '').trim();
const what = (map.get('what') || 'culture').trim();
@ -239,11 +243,11 @@ export class Home implements OnInit, OnDestroy {
this.theme.set(t || null);
this.buildSubthemes();
});
// Ajouter les catégories principales
whatTypes.add('culture');
whatTypes.add('traffic');
this.availableWhatTypes = Array.from(whatTypes).sort();
}
@ -252,7 +256,7 @@ export class Home implements OnInit, OnDestroy {
if (this.searchDebounceTimer) {
clearTimeout(this.searchDebounceTimer);
}
// Créer un nouveau timer de 500ms
this.searchDebounceTimer = setTimeout(() => {
this.applyFilters();
@ -311,6 +315,7 @@ export class Home implements OnInit, OnDestroy {
}
enablePleinAirMode() {
this.pushToast('info', "mode plein air activé")
if (!this.pleinAirMode) {
this.pleinAirMode = true;
this.applyFilters();
@ -318,8 +323,10 @@ export class Home implements OnInit, OnDestroy {
}
// Actions rapides plein air
quickCreate(what: 'traffic.contestation' | 'traffic.interruption' | 'traffic.wrong_way') {
quickCreate(what: string) {
const osmUsername = this.osmAuth.getUsername();
this.selectedPreset = what;
this.showGuidePresetPlace = true;
this.selected = {
id: null,
properties: {
@ -332,7 +339,40 @@ export class Home implements OnInit, OnDestroy {
geometry: { type: 'Point', coordinates: [0, 0] }
};
// Ensuite, l'utilisateur clique sur la carte: voir onPickCoords()
this.showEditForm = true;
// this.showEditForm = true;
}
submitPreset(){
const now = new Date();
const w = this.selected.properties.what;
// fin du signalement par défaut dans 20 jours
const stop = new Date(now.getTime() + 20* 24 * 3600 * 1000);
const feature = {
type: 'Feature',
properties: {
type: 'unscheduled',
label: this.selected.properties.label || (oedb.presets.what as any)[w]?.label || 'Évènement',
description: this.selected.properties.description || (oedb.presets.what as any)[w]?.description || '',
what: w,
reporter: this.guidePresetMoreInfoPseudo,
'reporter:description': this.presetMoreDetails,
where: this.selected.properties.where || '',
start: now.toISOString(),
stop: stop.toISOString()
},
geometry: { type: 'Point', coordinates: [this.selected.lon, this.selected.lat] }
} as any;
this.OedbApi.createEvent(feature).subscribe({
next: () => {
this.pushToast('success', 'Évènement créé');
this.selected = null;
this.presetMoreDetails = ''
this.showGuidePresetMoreInfo = false
// Après création rapide en plein air: recharger uniquement ce type pour feedback instantané
this.selectedWhatFilter = w;
this.loadEvents({ what: 'traffic' });
},
error: () => { this.pushToast('error', 'Échec de création'); }
});
}
goToNewCategories() {
@ -342,6 +382,10 @@ export class Home implements OnInit, OnDestroy {
onSelect(feature: any) {
this.selected = feature;
}
onSelectFromCalendarView(feature: any) {
this.selected = feature;
this.showEditForm = false;
}
onPickCoords(coords: [number, number]) {
const [lon, lat] = coords;
@ -350,39 +394,20 @@ export class Home implements OnInit, OnDestroy {
...this.selected,
geometry: { type: 'Point', coordinates: [lon, lat] }
};
this.showOptions = true;
// this.showOptions = true;
// En mode plein air, si c'est une création rapide, proposer l'envoi direct
if (this.pleinAirMode && (this.selected.id == null)) {
const w = this.selected.properties.what;
const allowed = new Set(['traffic.contestation','traffic.interruption','traffic.wrong_way']);
if (allowed.has(w)) {
this.showGuidePresetPlace = true;
this.showGuidePresetMoreInfo = true;
this.showQuickActions = false;
const self:this = this;
const ok = typeof window !== 'undefined' ? window.confirm('Envoyer cet évènement maintenant ?') : true;
if (ok) {
const now = new Date();
const stop = new Date(now.getTime() + 6 * 3600 * 1000);
const feature = {
type: 'Feature',
properties: {
type: 'unscheduled',
label: this.selected.properties.label || (oedb.presets.what as any)[w]?.label || 'Évènement',
description: this.selected.properties.description || (oedb.presets.what as any)[w]?.description || '',
what: w,
where: this.selected.properties.where || '',
start: now.toISOString(),
stop: stop.toISOString()
},
geometry: { type: 'Point', coordinates: [lon, lat] }
} as any;
this.OedbApi.createEvent(feature).subscribe({
next: () => {
this.pushToast('success', 'Évènement créé');
this.selected = null;
// Après création rapide en plein air: recharger uniquement ce type pour feedback instantané
this.selectedWhatFilter = w;
this.loadEvents({ what: 'traffic' });
},
error: () => { this.pushToast('error', 'Échec de création'); }
});
self.submitPreset()
}
}
}
@ -418,7 +443,7 @@ export class Home implements OnInit, OnDestroy {
this.toasts.push({ id, type, message });
setTimeout(() => {
this.toasts = this.toasts.filter(t => t.id !== id);
}, 3000);
}, 6000);
}
onSaved(_res: any) {
@ -632,6 +657,12 @@ export class Home implements OnInit, OnDestroy {
}
// Méthode pour recharger les événements quand la carte bouge
showGuidePresetPlace: boolean = false;
showGuidePresetMoreInfo: boolean= false;
guidePresetMoreInfoPseudo: string = '';
presetMoreDetails: string = '';
selectedPreset: string = '';
onMapMove(bbox: { minLng: number, minLat: number, maxLng: number, maxLat: number }) {
this.setCurrentBbox(bbox);
}