more style for interface; dark top

This commit is contained in:
Tykayn 2025-10-15 00:20:52 +02:00 committed by tykayn
parent e7a2d93d18
commit 2f9b91f2c7
4 changed files with 339 additions and 139 deletions

View file

@ -1,18 +1,19 @@
import { Component, inject, signal , OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { FormsModule } from '@angular/forms';
import {Component, inject, signal, 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';
import { OedbApi } from '../../services/oedb-api';
import { ActivatedRoute } from '@angular/router';
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';
import { WhatFilterComponent } from '../../shared/what-filter/what-filter';
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,
@ -42,7 +43,7 @@ export class Home implements OnInit, OnDestroy {
showTable = false;
showFilters = false;
showEditForm = false;
showOptions = true;
showOptions = false;
pleinAirMode = false;
civilianMode = false;
toasts: Array<{ id: number, type: 'success' | 'error' | 'info', message: string }> = [];
@ -88,17 +89,20 @@ export class Home implements OnInit, OnDestroy {
unlocatedOrOnline: Array<any> = [];
showUnlocatedList = false;
protected showQuickActions: boolean = true;
firstToastDone = false;
ngOnInit() {
// Écouteur global pour toasts
try {
(window as any).addEventListener('toast', (e: any) => {
const d = e?.detail || {}; this.pushToast(d.type || 'info', d.message || '');
const d = e?.detail || {};
this.pushToast(d.type || 'info', d.message || '');
});
} catch {}
} catch {
}
// Initialiser la bbox par défaut pour l'Île-de-France
this.currentBbox = { ...this.IDF_BBOX };
this.currentBbox = {...this.IDF_BBOX};
this.route.queryParamMap.subscribe(map => {
const id = (map.get('id') || '').trim();
@ -111,24 +115,31 @@ export class Home implements OnInit, OnDestroy {
if (id) {
this.loadSingleEvent(id);
} else {
this.loadEvents({ what: what || undefined, limit: limit || undefined });
this.loadEvents({what: what || undefined, limit: limit || undefined});
}
// Appliquer filtre par what côté client si fourni
if (what) {
this.selectedWhatFilter = what;
}
// Activer mode plein air via query param
if (pleinAir === '1' || pleinAir === 'true' || pleinAir === 'yes') {
this.enablePleinAirMode();
}
// if (pleinAir === '1' || pleinAir === 'true' || pleinAir === 'yes') {
// this.enablePleinAirMode();
// }
// Support: preset=plein_air
if (preset === 'plein_air') {
if (!this.firstToastDone) {
this.selectedWhatFilter = "traffic"
this.pushToast('info', "mode plein air activé")
this.useBboxFilter = true;
this.loadEvents({ what: 'traffic' });
}
this.firstToastDone = true
this.enablePleinAirMode();
}
});
this.startAutoReload();
this.loadEvents({ what: "culture" , limit: 100 });
this.loadEvents({what: "culture", limit: 100});
}
ngOnDestroy() {
@ -227,7 +238,7 @@ export class Home implements OnInit, OnDestroy {
}
onDaysAheadChange() {
this.loadEvents({ daysAhead: this.daysAhead, what: this.selectedWhatFilter || undefined });
this.loadEvents({daysAhead: this.daysAhead, what: this.selectedWhatFilter || undefined});
}
updateAvailableWhatTypes() {
@ -278,8 +289,8 @@ export class Home implements OnInit, OnDestroy {
const description = feature?.properties?.description || '';
const what = feature?.properties?.what || '';
return label.toLowerCase().includes(searchLower) ||
description.toLowerCase().includes(searchLower) ||
what.toLowerCase().includes(searchLower);
description.toLowerCase().includes(searchLower) ||
what.toLowerCase().includes(searchLower);
});
}
@ -315,7 +326,7 @@ export class Home implements OnInit, OnDestroy {
}
enablePleinAirMode() {
this.pushToast('info', "mode plein air activé")
if (!this.pleinAirMode) {
this.pleinAirMode = true;
this.applyFilters();
@ -326,7 +337,7 @@ export class Home implements OnInit, OnDestroy {
quickCreate(what: string) {
const osmUsername = this.osmAuth.getUsername();
this.selectedPreset = what;
this.showGuidePresetPlace = true;
this.showGuidePresetPlace = true;
this.selected = {
id: null,
properties: {
@ -334,18 +345,25 @@ export class Home implements OnInit, OnDestroy {
description: '',
what,
where: '',
...(osmUsername && { last_modified_by: osmUsername })
...(osmUsername && {last_modified_by: osmUsername})
},
geometry: { type: 'Point', coordinates: [0, 0] }
geometry: {type: 'Point', coordinates: [0, 0]}
};
// Ensuite, l'utilisateur clique sur la carte: voir onPickCoords()
// this.showEditForm = true;
}
submitPreset(){
cancelSubmitPreset(){
this.selected = null;
this.presetMoreDetails = ''
this.showGuidePresetMoreInfo = false
this.showGuidePresetPlace = false;
this.showQuickActions = 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 stop = new Date(now.getTime() + 20 * 24 * 3600 * 1000);
const feature = {
type: 'Feature',
properties: {
@ -359,7 +377,7 @@ export class Home implements OnInit, OnDestroy {
start: now.toISOString(),
stop: stop.toISOString()
},
geometry: { type: 'Point', coordinates: [this.selected.lon, this.selected.lat] }
geometry: {type: 'Point', coordinates: [this.selected.lon, this.selected.lat]}
} as any;
this.OedbApi.createEvent(feature).subscribe({
next: () => {
@ -367,11 +385,14 @@ export class Home implements OnInit, OnDestroy {
this.selected = null;
this.presetMoreDetails = ''
this.showGuidePresetMoreInfo = false
this.showGuidePresetPlace = false;
// Après création rapide en plein air: recharger uniquement ce type pour feedback instantané
this.selectedWhatFilter = w;
this.loadEvents({ what: 'traffic' });
this.loadEvents({what: 'traffic'});
},
error: () => { this.pushToast('error', 'Échec de création'); }
error: () => {
this.pushToast('error', 'Échec de création');
}
});
}
@ -382,6 +403,7 @@ export class Home implements OnInit, OnDestroy {
onSelect(feature: any) {
this.selected = feature;
}
onSelectFromCalendarView(feature: any) {
this.selected = feature;
this.showEditForm = false;
@ -392,19 +414,19 @@ export class Home implements OnInit, OnDestroy {
if (this.selected && this.selected.properties) {
this.selected = {
...this.selected,
geometry: { type: 'Point', coordinates: [lon, lat] }
geometry: {type: 'Point', coordinates: [lon, lat]}
};
// 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']);
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;
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) {
self.submitPreset()
@ -430,9 +452,9 @@ export class Home implements OnInit, OnDestroy {
description: '',
what: whatKey || '',
where: '',
...(osmUsername && { last_modified_by: osmUsername })
...(osmUsername && {last_modified_by: osmUsername})
},
geometry: { type: 'Point', coordinates: [lon, lat] }
geometry: {type: 'Point', coordinates: [lon, lat]}
};
}
}
@ -440,7 +462,7 @@ export class Home implements OnInit, OnDestroy {
private pushToast(type: 'success' | 'error' | 'info', message: string) {
if (!message) return;
const id = Date.now() + Math.random();
this.toasts.push({ id, type, message });
this.toasts.push({id, type, message});
setTimeout(() => {
this.toasts = this.toasts.filter(t => t.id !== id);
}, 6000);
@ -469,9 +491,11 @@ export class Home implements OnInit, OnDestroy {
startRectSelection() {
this.selectionMode = this.selectionMode === 'rectangle' ? 'none' : 'rectangle';
}
startPolySelection() {
this.selectionMode = this.selectionMode === 'polygon' ? 'none' : 'polygon';
}
clearSelection() {
this.selectionMode = 'none';
this.selectedIds = [];
@ -490,12 +514,21 @@ export class Home implements OnInit, OnDestroy {
const doUpdate = async (id: string | number, updater: (f: any) => any) => {
const feature = this.features.find(f => (f?.properties?.id ?? f?.id) === id);
if (!feature) { failed++; return; }
if (!feature) {
failed++;
return;
}
const updated = updater(feature);
await new Promise<void>((resolve) => {
this.OedbApi.updateEvent(id, updated).subscribe({
next: () => { success++; resolve(); },
error: (err) => { (err?.status === 0 ? networkErrors++ : failed++); resolve(); }
next: () => {
success++;
resolve();
},
error: (err) => {
(err?.status === 0 ? networkErrors++ : failed++);
resolve();
}
});
});
};
@ -503,24 +536,35 @@ export class Home implements OnInit, OnDestroy {
if (this.batchAction === 'delete') {
for (const id of this.selectedIds) {
await new Promise<void>((resolve) => {
this.OedbApi.deleteEvent(id).subscribe({ next: () => { success++; resolve(); }, error: (err) => { (err?.status === 0 ? networkErrors++ : failed++); resolve(); } });
this.OedbApi.deleteEvent(id).subscribe({
next: () => {
success++;
resolve();
}, error: (err) => {
(err?.status === 0 ? networkErrors++ : failed++);
resolve();
}
});
});
}
} else if (this.batchAction === 'changeWhat') {
const what = this.batchWhat.trim();
if (!what) return;
for (const id of this.selectedIds) {
await doUpdate(id, (feature: any) => ({ ...feature, properties: { ...feature.properties, what } }));
await doUpdate(id, (feature: any) => ({...feature, properties: {...feature.properties, what}}));
}
} else if (this.batchAction === 'setField') {
const key = this.batchFieldKey.trim();
if (!key) return;
for (const id of this.selectedIds) {
await doUpdate(id, (feature: any) => ({ ...feature, properties: { ...feature.properties, [key]: this.batchFieldValue } }));
await doUpdate(id, (feature: any) => ({
...feature,
properties: {...feature.properties, [key]: this.batchFieldValue}
}));
}
}
this.batchSummary = { success, failed, networkErrors };
this.batchSummary = {success, failed, networkErrors};
this.loadEvents();
}
@ -569,21 +613,25 @@ export class Home implements OnInit, OnDestroy {
description: '',
what: 'traffic.mammoth',
where: '',
...(osmUsername && { last_modified_by: osmUsername })
...(osmUsername && {last_modified_by: osmUsername})
},
geometry: { type: 'Point', coordinates: [0, 0] }
geometry: {type: 'Point', coordinates: [0, 0]}
};
this.showEditForm = true;
}
private buildSubthemes() {
const t = this.theme();
if (!t) { this.subthemes = []; this.activeSubtheme.set(null); return; }
if (!t) {
this.subthemes = [];
this.activeSubtheme.set(null);
return;
}
const what = oedb.presets.what as Record<string, any>;
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 || '' });
list.push({key: k, label: what[k].label || k, emoji: what[k].emoji || ''});
}
});
this.subthemes = list.sort((a, b) => a.key.localeCompare(b.key));
@ -592,7 +640,10 @@ export class Home implements OnInit, OnDestroy {
}
downloadGeoJSON() {
const blob = new Blob([JSON.stringify({ type: 'FeatureCollection', features: this.filteredFeatures }, null, 2)], { type: 'application/geo+json' });
const blob = new Blob([JSON.stringify({
type: 'FeatureCollection',
features: this.filteredFeatures
}, null, 2)], {type: 'application/geo+json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
@ -615,7 +666,7 @@ export class Home implements OnInit, OnDestroy {
JSON.stringify(f?.geometry?.coordinates?.[1] ?? '')
].join(','));
const csv = [header.join(','), ...rows].join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
const blob = new Blob([csv], {type: 'text/csv'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
@ -631,7 +682,7 @@ export class Home implements OnInit, OnDestroy {
const end = (this.endDateStr || '').trim() || undefined;
const days = this.daysAhead;
const what = (this.selectedWhatFilter || '').trim() || undefined;
this.loadEvents({ start, end, daysAhead: days, what });
this.loadEvents({start, end, daysAhead: days, what});
}
onBboxFilterToggle() {
@ -657,12 +708,13 @@ export class Home implements OnInit, OnDestroy {
}
// Méthode pour recharger les événements quand la carte bouge
showGuidePresetPlace: boolean = false;
showGuidePresetMoreInfo: boolean= false;
showGuidePresetPlace: boolean = false;
showGuidePresetMoreInfo: boolean = false;
guidePresetMoreInfoPseudo: string = '';
presetMoreDetails: string = '';
guidePresetMoreInfoPseudo: string = '';
presetMoreDetails: string = '';
selectedPreset: string = '';
onMapMove(bbox: { minLng: number, minLat: number, maxLng: number, maxLat: number }) {
this.setCurrentBbox(bbox);
}