2025-10-03 13:40:08 +02:00
< div class = "layout" >
2025-10-10 16:59:13 +02:00
@if(showOptions){
2025-10-03 13:40:08 +02:00
< div class = "aside" >
2025-10-10 16:59:13 +02:00
2025-10-04 23:36:37 +02:00
2025-10-10 16:59:13 +02:00
< div class = "aside-content" >
< div class = "toolbar" >
<!-- <span class="loading - indicator">⏳</span> -->
@if (isLoading) {
< span class = "loading-indicator" > ⏳< / span >
2025-10-04 23:36:37 +02:00
}
2025-10-10 16:59:13 +02:00
< / div >
< div class = "filters" >
< label ( click ) = " showFilters = !showFilters" >
Filtre rapide
@if (showFilters) {
< span > ▼< / span >
} @else {
< span > ▶< / span >
}
< / label >
2025-10-14 17:29:37 +02:00
< div class = "control-group" id = "pleinAirMode" >
< label >
< input type = "checkbox" [ ( ngModel ) ] = " pleinAirMode " ( change ) = " togglePleinAir ( ) " >
Mode plein air
< / label >
@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 >
}
< / div >
2025-10-10 16:59:13 +02:00
< 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 >
2025-10-04 23:36:37 +02:00
< div class = "control-group" >
< label >
< input
type="checkbox"
[(ngModel)]="autoReloadEnabled"
(change)="toggleAutoReload()">
Rechargement auto (1min)
< / label >
< / div >
2025-10-10 16:59:13 +02:00
< div class = "control-group" >
< label >
< input
type="checkbox"
[(ngModel)]="useBboxFilter"
(change)="onBboxFilterToggle()">
Filtrer par zone visible (bbox)
< / label >
< / div >
< / div >
2025-10-04 19:18:10 +02:00
< input class = "input" type = "text" placeholder = "Rechercher..." [ ( ngModel ) ] = " searchText " ( ngModelChange ) = " onSearchChange ( ) " >
< div class = "control-group" >
2025-10-10 16:59:13 +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 19:18:10 +02:00
< / div >
2025-10-10 16:59:13 +02:00
< div class = "control-group" >
< button class = "btn" ( click ) = " onQuickSearchSubmit ( ) " > Rechercher< / button >
2025-10-05 00:21:11 +02:00
< / div >
2025-10-10 16:59:13 +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 17:29:37 +02:00
2025-10-10 16:59:13 +02:00
2025-10-14 17:29:37 +02:00
<!-- <app - osm></app - osm>
2025-10-10 16:59:13 +02:00
2025-10-14 17:29:37 +02:00
< app-menu > < / app-menu > -->
2025-10-10 16:59:13 +02:00
< hr >
2025-10-10 10:14:30 +02:00
}
< / div >
2025-10-10 16:59:13 +02:00
< / div >
<!-- <app - unlocated - events [events]="filteredFeatures"></app - unlocated - events> -->
< hr >
@if(showEditForm){
2025-10-05 00:21:11 +02:00
2025-10-10 16:59:13 +02:00
< 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 >
2025-10-14 17:29:37 +02:00
@if(!pleinAirMode){
2025-10-10 16:59:13 +02:00
< app-edit-form [ selected ] = " selected " ( saved ) = " onSaved ( $ event ) " ( created ) = " onCreated ( $ event ) " ( deleted ) = " onDeleted ( $ event ) " ( canceled ) = " onCanceled ( ) " > < / app-edit-form >
2025-10-14 17:29:37 +02:00
}
2025-10-10 16:59:13 +02:00
}
< 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 > sélectionné: {{selected.properties.name}}< / h3 >
{{selected.properties.label}}
< br >
{{selected.properties.label}}
< br >
{{selected.properties.what}}
< br >
{{selected.properties.where}}
< br >
{{selected.properties.lat}}
< br >
{{selected.properties.lon}}
< br >
{{selected.properties.wikidata}}
< br >
{{selected.properties.featureType}}
< br >
{{selected.properties.type}}
< br >
start:
{{selected.properties.start}}
< br >
end:
{{selected.properties.stop}}
< br >
source
{{selected.properties.source}}
< br >
description:
{{selected.properties.description}}
< br >
createdate:
{{selected.properties.createdate}}
< br >
lastupdate:
{{selected.properties.lastupdate}}
< / div >
}
2025-10-05 00:49:12 +02:00
2025-10-10 16:59:13 +02:00
< / div >
2025-10-05 00:49:12 +02:00
< / div >
2025-10-10 16:59:13 +02:00
}
< div class = "main {{showOptions? 'is-small' : 'is-full'}}" >
< button class = "button toggle-options" ( click ) = " showOptions = !showOptions" >
Options
< / button >
2025-10-14 17:29:37 +02:00
@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;" >
{{t.message}}
< / div >
}
< / div >
}
2025-10-07 12:14:51 +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 >
}
< / div >
< / div >
}
2025-10-10 16:59:13 +02:00
@if (!showTable & & !showUnlocatedList) {
2025-10-03 14:00:35 +02:00
< div class = "map" >
2025-10-10 10:14:30 +02:00
< app-all-events [ features ] = " filteredFeatures " [ selected ] = " selected " [ selectMode ] = " selectionMode " ( selection ) = " onSelection ( $ event ) " ( select ) = " onSelect ( $ event ) " ( pickCoords ) = " onPickCoords ( $ event ) " > < / app-all-events >
2025-10-03 14:00:35 +02:00
< / div >
2025-10-10 16:59:13 +02:00
} @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 >
}
< / 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" >
< app-edit-form
[selected]="selected"
(saved)="onSaved($event)"
(created)="onCreated($event)"
(deleted)="onDeleted($event)">
< / app-edit-form >
< / div >
< / div >
} @else {
< div class = "hint" > Sélectionnez un évènement à gauche pour voir les détails.< / div >
}
< / main >
< / div >
< / div >
2025-10-03 14:00:35 +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 >
2025-10-04 19:18:10 +02:00
@for (f of filteredFeatures; track f.id) {
2025-10-03 14:00:35 +02:00
< 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 >
}
2025-10-02 22:53:50 +02:00
< / div >
< / div >
2025-10-10 10:14:30 +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 >
2025-10-10 16:59:13 +02:00
< option value = "setField" > Remplacer une propriété< / option >
2025-10-10 10:14:30 +02:00
< 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 >
}
2025-10-10 16:59:13 +02:00
@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 >
}
2025-10-10 10:14:30 +02:00
< div class = "actions" >
< button class = "btn" ( click ) = " applyBatch ( ) " [ disabled ] = " batchAction = =='none'" > Appliquer< / button >
< button class = "btn btn-ghost" ( click ) = " clearSelection ( ) " > Annuler< / button >
< / div >
2025-10-10 16:59:13 +02:00
@if (batchSummary) {
< div class = "summary" >
< span > Succès: {{batchSummary.success}}< / span >
< span > Échecs: {{batchSummary.failed}}< / span >
< span > Erreurs réseau: {{batchSummary.networkErrors}}< / span >
< / div >
}
2025-10-10 10:14:30 +02:00
< / div >
< / div >
}
2025-10-10 16:59:13 +02:00
<!-- Boutons flottants en bas à droite -->
< div class = "floating-actions" >
< button class = "fab counter" ( click ) = " toggleUnlocatedPanel ( ) " title = "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 >