add search filter, toggle options, count unlocated events

This commit is contained in:
Tykayn 2025-10-10 16:59:13 +02:00 committed by tykayn
parent ee48a3c665
commit d22dbde2e7
12 changed files with 797 additions and 165 deletions

View file

@ -39,33 +39,68 @@ export class Home implements OnInit, OnDestroy {
selected: any | null = null;
showTable = false;
showFilters = false;
showEditForm = true;
showEditForm = false;
showOptions = false;
selectionMode: 'none' | 'rectangle' | 'polygon' = 'none';
selectedIds: Array<string | number> = [];
batchAction: 'none' | 'changeWhat' | 'delete' = 'none';
batchAction: 'none' | 'changeWhat' | 'setField' | 'delete' = 'none';
batchWhat = '';
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;
daysAhead = 7; // Nombre de jours dans le futur par défaut
isLoading = false;
// Formulaire de recherche
startDateStr: string | null = null;
endDateStr: string | null = null;
// Propriétés pour les filtres
searchText = '';
selectedWhatFilter = '';
selectedWhatFilter = 'culture';
availableWhatTypes: string[] = [];
theme = signal<string | null>(null);
subthemes: Array<{ key: string, label: string, emoji: string }> = [];
activeSubtheme = signal<string | null>(null);
// Option bbox
useBboxFilter = false;
currentBbox: { minLng: number, minLat: number, maxLng: number, maxLat: number } | null = null;
// Debounce pour la recherche
private searchDebounceTimer: any = null;
// Non localisés / en ligne
unlocatedOrOnline: Array<any> = [];
showUnlocatedList = false;
ngOnInit() {
this.loadEvents();
this.route.queryParamMap.subscribe(map => {
const id = (map.get('id') || '').trim();
const what = (map.get('what') || '').trim();
const limitParam = map.get('limit');
const limit = limitParam ? Number(limitParam) : null;
// Charger selon les query params
if (id) {
this.loadSingleEvent(id);
} else {
this.loadEvents({ what: what || undefined, limit: limit || undefined });
}
// Appliquer filtre par what côté client si fourni
if (what) {
this.selectedWhatFilter = what;
}
});
this.startAutoReload();
}
ngOnDestroy() {
this.stopAutoReload();
// Nettoyer le timer de debounce
if (this.searchDebounceTimer) {
clearTimeout(this.searchDebounceTimer);
}
}
createEvent() {
@ -76,26 +111,61 @@ export class Home implements OnInit, OnDestroy {
}
loadEvents() {
loadEvents(overrides: { what?: string; limit?: number; start?: string; end?: string; daysAhead?: number } = {}) {
this.isLoading = true;
const today = new Date();
const endDate = new Date(today);
endDate.setDate(today.getDate() + this.daysAhead);
const startIso = overrides.start || this.startDateStr || today.toISOString().split('T')[0];
let endIso = overrides.end || this.endDateStr || '';
if (!endIso) {
const d = new Date(today);
const span = overrides.daysAhead ?? this.daysAhead;
d.setDate(today.getDate() + span);
endIso = d.toISOString().split('T')[0];
}
const params = {
start: today.toISOString().split('T')[0],
end: endDate.toISOString().split('T')[0],
limit: 3000
const params: any = {
start: startIso,
end: endIso,
limit: overrides.limit ?? 10000
};
if (overrides.what) {
params.what = overrides.what;
} else if (this.selectedWhatFilter) {
params.what = this.selectedWhatFilter;
}
// Ajouter bbox si activé et disponible
if (this.useBboxFilter && this.currentBbox) {
params.bbox = `${this.currentBbox.minLng},${this.currentBbox.minLat},${this.currentBbox.maxLng},${this.currentBbox.maxLat}`;
}
this.OedbApi.getEvents(params).subscribe((events: any) => {
this.features = Array.isArray(events?.features) ? events.features : [];
this.computeUnlocatedOrOnline();
this.updateAvailableWhatTypes();
this.applyFilters();
this.isLoading = false;
});
}
loadSingleEvent(id: string | number) {
this.isLoading = true;
this.OedbApi.getEventById(id).subscribe({
next: (feature: any) => {
const f = (feature && (feature as any).type === 'Feature') ? feature : (feature?.feature || null);
this.features = f ? [f] : [];
this.filteredFeatures = this.features;
this.updateAvailableWhatTypes();
this.isLoading = false;
},
error: () => {
this.features = [];
this.filteredFeatures = [];
this.isLoading = false;
}
});
}
startAutoReload() {
if (this.autoReloadEnabled && !this.autoReloadInterval) {
this.autoReloadInterval = setInterval(() => {
@ -121,7 +191,7 @@ export class Home implements OnInit, OnDestroy {
}
onDaysAheadChange() {
this.loadEvents();
this.loadEvents({ daysAhead: this.daysAhead, what: this.selectedWhatFilter || undefined });
}
updateAvailableWhatTypes() {
@ -141,7 +211,15 @@ export class Home implements OnInit, OnDestroy {
}
onSearchChange() {
this.applyFilters();
// Annuler le timer précédent s'il existe
if (this.searchDebounceTimer) {
clearTimeout(this.searchDebounceTimer);
}
// Créer un nouveau timer de 500ms
this.searchDebounceTimer = setTimeout(() => {
this.applyFilters();
}, 500);
}
onWhatFilterChange() {
@ -189,6 +267,8 @@ export class Home implements OnInit, OnDestroy {
...this.selected,
geometry: { type: 'Point', coordinates: [lon, lat] }
};
this.showOptions = true;
} else {
const osmUsername = this.osmAuth.getUsername();
const whatKey = this.activeSubtheme();
@ -246,34 +326,51 @@ export class Home implements OnInit, OnDestroy {
this.selectedIds = [];
this.batchAction = 'none';
this.batchWhat = '';
this.batchFieldKey = '';
this.batchFieldValue = '';
this.batchSummary = null;
}
async applyBatch() {
if (!this.selectedIds.length || this.batchAction === 'none') return;
let success = 0;
let failed = 0;
let networkErrors = 0;
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; }
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(); }
});
});
};
if (this.batchAction === 'delete') {
for (const id of this.selectedIds) {
await new Promise<void>((resolve) => {
this.OedbApi.deleteEvent(id).subscribe({ next: () => resolve(), error: () => resolve() });
this.OedbApi.deleteEvent(id).subscribe({ next: () => { success++; resolve(); }, error: (err) => { (err?.status === 0 ? networkErrors++ : failed++); resolve(); } });
});
}
this.loadEvents();
this.clearSelection();
return;
}
if (this.batchAction === 'changeWhat') {
} else if (this.batchAction === 'changeWhat') {
const what = this.batchWhat.trim();
if (!what) return;
for (const id of this.selectedIds) {
const feature = this.features.find(f => (f?.properties?.id ?? f?.id) === id);
if (!feature) continue;
const updated = { ...feature, properties: { ...feature.properties, what } };
await new Promise<void>((resolve) => {
this.OedbApi.updateEvent(id, updated).subscribe({ next: () => resolve(), error: () => resolve() });
});
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 } }));
}
this.loadEvents();
this.clearSelection();
}
this.batchSummary = { success, failed, networkErrors };
this.loadEvents();
}
onCanceled() {
@ -288,6 +385,46 @@ export class Home implements OnInit, OnDestroy {
this.showTable = !this.showTable;
}
private isNonLocated(feature: any): boolean {
const geom = feature?.geometry;
if (!geom || geom.type !== 'Point') return true;
const coords = geom.coordinates;
if (!Array.isArray(coords) || coords.length !== 2) return true;
const [lon, lat] = coords;
if (lon == null || lat == null) return true;
if (lon === 0 && lat === 0) return true;
return false;
}
private isOnline(feature: any): boolean {
const v = feature?.properties?.online;
return v === 'yes' || v === true;
}
private computeUnlocatedOrOnline() {
this.unlocatedOrOnline = (this.features || []).filter(f => this.isNonLocated(f) || this.isOnline(f));
}
toggleUnlocatedPanel() {
this.showUnlocatedList = !this.showUnlocatedList;
}
createMammoth() {
const osmUsername = this.osmAuth.getUsername();
this.selected = {
id: null,
properties: {
label: '',
description: '',
what: 'traffic.mammoth',
where: '',
...(osmUsername && { last_modified_by: osmUsername })
},
geometry: { type: 'Point', coordinates: [0, 0] }
};
this.showEditForm = true;
}
private buildSubthemes() {
const t = this.theme();
if (!t) { this.subthemes = []; this.activeSubtheme.set(null); return; }
@ -337,4 +474,34 @@ export class Home implements OnInit, OnDestroy {
URL.revokeObjectURL(url);
a.remove();
}
onQuickSearchSubmit() {
const start = (this.startDateStr || '').trim() || undefined;
const end = (this.endDateStr || '').trim() || undefined;
const days = this.daysAhead;
const what = (this.selectedWhatFilter || '').trim() || undefined;
this.loadEvents({ start, end, daysAhead: days, what });
}
onBboxFilterToggle() {
this.useBboxFilter = !this.useBboxFilter;
if (this.useBboxFilter) {
// Demander la bbox actuelle à la carte
this.requestCurrentBbox();
}
this.loadEvents();
}
requestCurrentBbox() {
// Cette méthode sera appelée par le composant de carte
// pour obtenir la bbox actuelle
console.log('Demande de bbox actuelle...');
}
setCurrentBbox(bbox: { minLng: number, minLat: number, maxLng: number, maxLat: number }) {
this.currentBbox = bbox;
if (this.useBboxFilter) {
this.loadEvents();
}
}
}