oedb-backend/frontend/src/app/pages/home/home.html
2025-11-03 00:37:35 +01:00

431 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div class="layout">
<div class="aside">
<div class="toolbar">
@if (isLoading) {
<span class="loading-indicator"></span>
}
<a [routerLink]="['/embed']" [queryParams]="getShareQueryParams()" class="btn-share" title="Partager et intégrer les événements">
🔗 Partager
</a>
</div>
@if(showOptions){
<div class="aside-content">
<div class="filters">
<label (click)="showFilters = !showFilters">
Filtre rapide
@if (showFilters) {
<span></span>
} @else {
<span></span>
}
</label>
<div class="control-group" id="pleinAirMode">
<label>
<input type="checkbox" [(ngModel)]="pleinAirMode" (change)="togglePleinAir()">
Mode plein air
</label>
</div>
<div class="filters-group">
@if (showFilters) {
<span class="muted">{{filteredFeatures.length}} évènements chargés</span>
<hr>
<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">
<label>
<input type="checkbox" [(ngModel)]="useBboxFilter" (change)="onBboxFilterToggle()">
Filtrer par zone visible (bbox)
</label>
</div>
</div>
<input class="input" type="text" placeholder="Rechercher..." [(ngModel)]="searchText"
(ngModelChange)="onSearchChange()">
<div class="control-group">
<label>Période (début / fin)</label>
<div style="display:flex; gap:6px;">
<input class="input" type="date" [(ngModel)]="startDateStr">
<input class="input" type="date" [(ngModel)]="endDateStr">
</div>
</div>
<div class="control-group">
<button class="btn" (click)="onQuickSearchSubmit()">Rechercher</button>
</div>
<div class="control-group">
<app-what-filter [label]="'Filtrer par type d\'événement'" [available]="availableWhatTypes"
[selected]="selectedWhatFilter"
(selectedChange)="selectedWhatFilter = $event; onWhatFilterChange()"></app-what-filter>
</div>
<!-- <app-osm></app-osm>
<app-menu></app-menu> -->
<hr>
}
</div>
</div>
<!-- <app-unlocated-events [events]="filteredFeatures"></app-unlocated-events> -->
@if(showEditForm){
<div class="guide">
<h3>Guide</h3>
<ul>
<li> Créer un évènement: Cliquez sur le bouton "+" pour créer un nouvel évènement. Sélectionnez un preset,
remplissez les informations, cliquez quelque part sur la carte pour définir un emplacement. Puis appuyez sur
créer.</li>
<li> Mettre à jour un évènement: Sélectionnez un évènement sur la carte ou dans la liste pour le modifier.
</li>
</ul>
</div>
@if(!pleinAirMode){
<app-edit-form [selected]="selected" (saved)="onSaved($event)" (created)="onCreated($event)"
(deleted)="onDeleted($event)" (canceled)="onCanceled()"></app-edit-form>
}
}
<div id="fixed_actions">
<button class="button btn btn-primary" (click)="createEvent()" title="Créer un évènement">+ nouvel
évènement</button>
<button class="button" (click)="toggleView()" title="Basculer carte / tableau">📊</button>
<div class="downloaders">
<button class="button" (click)="downloadGeoJSON()" title="Télécharger GeoJSON">📥 GeoJSON</button>
<button class="button" (click)="downloadCSV()" title="Télécharger CSV">📥 CSV</button>
</div>
<div class="selectors">
<button class="button" [class.active]="selectionMode==='rectangle'" (click)="startRectSelection()"
title="Sélection rectangulaire"></button>
<button class="button" [class.active]="selectionMode==='polygon'" (click)="startPolySelection()"
title="Sélection polygone"></button>
@if (selectedIds.length) {
<span class="muted">{{selectedIds.length}} sélectionné(s)</span>
}
</div>
</div>
@if(selected !== null){
<div class="selected">
<h3 (click)="showEditForm = !showEditForm"> sélectionné: {{selected.properties.name}} {{selected.properties.title}} {{selected.properties.label}}</h3>
<table>
<tbody>
<tr>
<td>label</td>
<td>{{selected.properties.label}}</td>
</tr>
<tr>
<td>name</td>
<td>{{selected.properties.name}}</td>
</tr>
<tr>
<td>title</td>
<td>{{selected.properties.title}}</td>
</tr>
<tr>
<td>description</td>
<td>{{selected.properties.description}}</td>
</tr>
<tr>
<td>what</td>
<td>{{selected.properties.what}}</td>
</tr>
<tr>
<td>where</td>
<td>{{selected.properties.where}}</td>
</tr>
<tr>
<td>lat</td>
<td>{{selected.properties.lat}}</td>
</tr>
<tr>
<td>lon</td>
<td>{{selected.properties.lon}}</td>
</tr>
<tr>
<td>wikidata</td>
<td>{{selected.properties.wikidata}}</td>
</tr>
<tr>
<td>featureType</td>
<td>{{selected.properties.featureType}}</td>
</tr>
<tr>
<td>type</td>
<td>{{selected.properties.type}}</td>
</tr>
<tr>
<td>start</td>
<td>{{selected.properties.start}}</td>
</tr>
<tr>
<td>stop</td>
<td>{{selected.properties.stop}}</td>
</tr>
<tr>
<td>source</td>
<td>{{selected.properties.source}}</td>
</tr>
<tr>
<td>createdate</td>
<td>{{selected.properties.createdate}}</td>
</tr>
<tr>
<td>lastupdate</td>
<td>{{selected.properties.lastupdate}}</td>
</tr>
<tr>
<td>type</td>
<td>{{selected.properties.type}}</td>
</tr>
</tbody>
</table>
</div>
}
</div>
@if (selectedIds.length) {
<div class="batch-panel">
<div class="panel">
<div class="row">
<label>Action de masse</label>
<select class="input" [(ngModel)]="batchAction">
<option value="none">Choisir...</option>
<option value="changeWhat">Changer le type d'évènement (what)</option>
<option value="setField">Remplacer une propriété</option>
<option value="delete">Supprimer</option>
</select>
</div>
@if (batchAction==='changeWhat') {
<div class="row">
<label>Nouveau "what"</label>
<input class="input" type="text" [(ngModel)]="batchWhat" placeholder="ex: traffic.roadwork" />
</div>
}
@if (batchAction==='setField') {
<div class="row">
<label>Clé de propriété</label>
<input class="input" type="text" [(ngModel)]="batchFieldKey" placeholder="ex: where" />
</div>
<div class="row">
<label>Nouvelle valeur</label>
<input class="input" type="text" [(ngModel)]="batchFieldValue" placeholder="ex: Paris, 12e" />
</div>
}
<div class="actions">
<button class="btn" (click)="applyBatch()" [disabled]="batchAction==='none'">Appliquer</button>
<button class="btn btn-ghost" (click)="clearSelection()">Annuler</button>
</div>
@if (batchSummary) {
<div class="summary">
<span>Succès: {{batchSummary.success}}</span>
<span>Échecs: {{batchSummary.failed}}</span>
<span>Erreurs réseau: {{batchSummary.networkErrors}}</span>
</div>
}
</div>
</div>
}
}
</div>
<div class="main {{showOptions? 'is-small' : 'is-full'}}">
<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 [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>
<button class="btn btn-secondary" (click)="cancelSubmitPreset()">Annuler</button>
</p>
}
}
<!--fin des actions rapides-->
@if (toasts.length) {
<div class="toaster">
@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'"
style="padding:10px 12px;border-radius:6px;box-shadow:0 2px 8px rgba(0,0,0,0.15);background:#fff;min-width:200px;">
{{t.message}}
</div>
}
</div>
}
@if (theme()) {
<div class="subtheme-bar">
<div class="help">Thème: {{ theme() }} — Cliquez sur la carte pour définir des coordonnées puis créez un
évènement du sous-thème choisi.</div>
<div class="chips">
@for (t of subthemes; track t.key) {
<button class="chip" [class.active]="activeSubtheme()===t.key" (click)="activeSubtheme.set(t.key)">
<span class="emoji">{{t.emoji}}</span>
<span>{{t.label}}</span>
</button>
}
</div>
</div>
}
@if (!showTable && !showUnlocatedList) {
<div class="map">
<app-all-events #allEventsMap [features]="filteredFeatures" [selected]="selected" [selectMode]="selectionMode"
(selection)="onSelection($event)" (select)="onSelect($event)" (pickCoords)="onPickCoords($event)"
(mapMove)="onMapMove($event)"></app-all-events>
</div>
} @else if (showUnlocatedList) {
<div class="table-wrapper" style="overflow:auto;height:100%;">
<div class="unlocated-layout">
<aside class="agenda-sidebar">
<div class="sidebar-header">
<h3>Sans lieu / en ligne</h3>
<small>{{unlocatedOrOnline.length}} évènements</small>
</div>
<div class="day-groups">
<ul class="event-list">
@for (f of unlocatedOrOnline; track f.id) {
<li class="event-item"
(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">
<div class="event-title">{{f?.properties?.label || f?.properties?.name || 'Événement'}}</div>
<div class="event-when">{{f?.properties?.start || f?.properties?.when || '—'}}</div>
</div>
</li>
}
</ul>
</div>
</aside>
<main class="agenda-main">
@if ((selected || (showEditForm && addMode)) && showEditForm) {
<div class="event-edit-panel">
<div class="panel-header">
<h3>@if (selected && selected.id) { Détails } @else { Créer un événement }</h3>
<button class="btn-close" (click)="closeEditForm()">×</button>
</div>
<div class="panel-content">
<app-edit-form [selected]="selected"
[filterByPrefix]="addMode"
(saved)="onSaved($event)"
(created)="onCreated($event)"
(deleted)="onDeleted($event)"
(canceled)="onCanceled()"
>
</app-edit-form>
</div>
</div>
} @else {
<div class="hint">Sélectionnez un évènement à gauche pour voir les détails.</div>
}
</main>
</div>
</div>
} @else {
<div class="table-wrapper" style="overflow:auto;height:100%;">
<table style="width:100%;border-collapse:collapse;">
<thead>
<tr>
<th style="text-align:left;padding:6px;border-bottom:1px solid #e5e7eb;">Type</th>
<th style="text-align:left;padding:6px;border-bottom:1px solid #e5e7eb;">Label</th>
<th style="text-align:left;padding:6px;border-bottom:1px solid #e5e7eb;">Start</th>
<th style="text-align:left;padding:6px;border-bottom:1px solid #e5e7eb;">Stop</th>
</tr>
</thead>
<tbody>
@for (f of filteredFeatures; track f.id) {
<tr (click)="onSelect({ id: f?.properties?.id ?? f?.id, properties: f.properties, geometry: f.geometry })"
style="cursor:pointer;">
<td style="padding:6px;border-bottom:1px solid #f1f5f9;">{{f?.properties?.what}}</td>
<td style="padding:6px;border-bottom:1px solid #f1f5f9;">{{f?.properties?.label || f?.properties?.name}}
</td>
<td style="padding:6px;border-bottom:1px solid #f1f5f9;">{{f?.properties?.start || f?.properties?.when}}
</td>
<td style="padding:6px;border-bottom:1px solid #f1f5f9;">{{f?.properties?.stop}}</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
<!-- Boutons flottants en bas à droite -->
<div class="floating-actions">
<button class="fab counter" (click)="toggleUnlocatedPanel()" title="{{unlocatedOrOnline.length}} évènements non localisés ou en ligne">
{{unlocatedOrOnline.length}}
</button>
@if(!showUnlocatedList){
<button class="fab plus btn-primary" (click)="createMammoth()" title="Créer un nouvel évènement (mammouth)">+
</button>
}
</div>