oedb-backend/frontend/src/app/pages/home/home.html

384 lines
15 KiB
HTML
Raw Normal View History

2025-10-03 13:40:08 +02:00
<div class="layout">
2025-10-14 17:37:12 +02:00
2025-10-03 13:40:08 +02:00
<div class="aside">
2025-10-14 18:34:49 +02:00
<div class="toolbar">
@if (isLoading) {
<span class="loading-indicator"></span>
}
</div>
@if(showOptions){
2025-10-04 23:36:37 +02:00
<div class="aside-content">
2025-10-14 18:34:49 +02:00
<div class="filters">
<label (click)="showFilters = !showFilters">
Filtre rapide
@if (showFilters) {
<span></span>
2025-10-14 18:34:49 +02:00
} @else {
<span></span>
2025-10-14 18:34:49 +02:00
}
2025-10-14 17:29:37 +02:00
</label>
2025-10-14 18:34:49 +02:00
<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>
2025-10-14 18:34:49 +02:00
<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>
2025-10-14 18:34:49 +02:00
</div>
<input class="input" type="text" placeholder="Rechercher..." [(ngModel)]="searchText"
(ngModelChange)="onSearchChange()">
2025-10-04 23:36:37 +02:00
<div class="control-group">
2025-10-14 18:34:49 +02:00
<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>
2025-10-04 23:36:37 +02:00
</div>
<div class="control-group">
2025-10-14 18:34:49 +02:00
<button class="btn" (click)="onQuickSearchSubmit()">Rechercher</button>
</div>
2025-10-14 18:34:49 +02:00
<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>
2025-10-14 18:34:49 +02:00
<!-- <app-osm></app-osm>
<app-menu></app-menu> -->
<hr>
}
</div>
</div>
2025-10-14 18:34:49 +02:00
<!-- <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>
2025-10-14 17:29:37 +02:00
2025-10-14 18:34:49 +02:00
@if(selected !== null){
<div class="selected">
<h3> 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>
2025-10-10 10:14:30 +02:00
}
2025-10-14 18:34:49 +02:00
</div>
2025-10-14 18:34:49 +02:00
@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>
2025-10-14 18:34:49 +02:00
}
2025-10-14 18:34:49 +02:00
}
</div>
2025-10-14 18:34:49 +02:00
<div class="main {{showOptions? 'is-small' : 'is-full'}}">
<button class="button toggle-options" (click)="showOptions = !showOptions">
Options
</button>
@if (pleinAirMode) {
<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>
</div>
}
@if (toasts.length) {
<div class="toaster"
style="position:fixed;right:16px;top:16px;display:flex;flex-direction:column;gap:8px;z-index:1000;">
@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;">
2025-10-14 17:29:37 +02:00
{{t.message}}
</div>
2025-10-14 18:34:49 +02:00
}
</div>
2025-10-14 17:29:37 +02:00
}
2025-10-14 18:34:49 +02:00
@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>
2025-10-14 18:34:49 +02:00
}
</div>
</div>
2025-10-14 18:34:49 +02:00
}
@if (!showTable && !showUnlocatedList) {
<div class="map">
<app-all-events [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)="onSelect({ 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>
2025-10-14 18:34:49 +02:00
}
</ul>
</div>
</aside>
<main class="agenda-main">
@if (selected) {
<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">
2025-10-14 18:34:49 +02:00
<app-edit-form [selected]="selected" (saved)="onSaved($event)" (created)="onCreated($event)"
(deleted)="onDeleted($event)">
</app-edit-form>
</div>
</div>
2025-10-14 18:34:49 +02:00
} @else {
<div class="hint">Sélectionnez un évènement à gauche pour voir les détails.</div>
2025-10-14 18:34:49 +02:00
}
</main>
</div>
</div>
2025-10-14 18:34:49 +02:00
} @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;">
2025-10-03 14:00:35 +02:00
<td style="padding:6px;border-bottom:1px solid #f1f5f9;">{{f?.properties?.what}}</td>
2025-10-14 18:34:49 +02:00
<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>
2025-10-03 14:00:35 +02:00
<td style="padding:6px;border-bottom:1px solid #f1f5f9;">{{f?.properties?.stop}}</td>
</tr>
2025-10-14 18:34:49 +02:00
}
</tbody>
</table>
2025-10-10 10:14:30 +02:00
</div>
2025-10-14 18:34:49 +02:00
}
2025-10-10 10:14:30 +02:00
</div>
2025-10-14 18:34:49 +02:00
<!-- 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>
<button class="fab plus" (click)="createMammoth()" title="Créer un nouvel évènement (mammouth)">+
</button>
</div>