- server/api/chatbot-reseaux.post.ts : keyword search sur reseaux-bifurcation.json (120 structures, même pattern que chatbot-taff) - ChatbotSheet.vue : prop endpoint? (défaut /api/chatbot) + renderMd déjà actif - agences.vue : endpoint='/api/chatbot-reseaux' Markdown s'active au prochain restart du bat (cache .nuxt à nettoyer). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
519 lines
22 KiB
Vue
519 lines
22 KiB
Vue
<template>
|
|
<div class="flex h-full overflow-hidden" style="background: var(--nav-bg);">
|
|
|
|
<!-- ═══════════════════════════════════════ SIDEBAR DESKTOP (>= 1024px) -->
|
|
<div class="hidden lg:block overflow-y-auto" style="width: 320px; min-width: 320px; flex-shrink: 0; border-right: 1px solid var(--nav-bg-alt); height: 100%;">
|
|
|
|
<!-- IntentionBanner s'auto-affiche via Teleport (overlay plein ecran) -->
|
|
<IntentionBanner />
|
|
|
|
<!-- Filtres familles + hashtags -->
|
|
<HashtagFilter
|
|
:allHashtags="allHashtags"
|
|
:selectedHashtags="selectedHashtags"
|
|
:selectedFamille="selectedFamille"
|
|
@update:selectedHashtags="selectedHashtags = $event"
|
|
@update:selectedFamille="selectedFamille = $event"
|
|
/>
|
|
|
|
<!-- Separateur -->
|
|
<div style="height: 1px; background: var(--nav-bg-alt);"></div>
|
|
|
|
<!-- Barre de recherche -->
|
|
<div class="px-3 py-2" style="border-bottom: 1px solid var(--nav-bg-alt);">
|
|
<label class="sidebar-search-label" aria-label="Rechercher une structure">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="sidebar-search-icon">
|
|
<circle cx="11" cy="11" r="8"/>
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
|
</svg>
|
|
<input
|
|
v-model="search"
|
|
type="search"
|
|
placeholder="Rechercher une structure..."
|
|
class="sidebar-search-input"
|
|
autocomplete="off"
|
|
/>
|
|
<button
|
|
v-if="search"
|
|
type="button"
|
|
class="sidebar-search-clear"
|
|
aria-label="Effacer"
|
|
@click.stop="search = ''"
|
|
>
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
|
|
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
</svg>
|
|
</button>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Header compteur + reset -->
|
|
<div class="flex items-center justify-between px-4 py-2" style="border-bottom: 1px solid var(--nav-bg-alt);">
|
|
<span class="text-xs font-bold uppercase tracking-widest" style="color: var(--nav-text-muted);">
|
|
{{ filtered.length }} structure{{ filtered.length > 1 ? 's' : '' }}
|
|
</span>
|
|
<button
|
|
v-if="hasActiveFilters"
|
|
@click="resetFilters"
|
|
class="text-xs underline hover:opacity-70"
|
|
style="color: var(--nav-text-muted);"
|
|
>Effacer les filtres</button>
|
|
</div>
|
|
|
|
<!-- Liste fiches (sidebar entiere scroll - pas de scroll interne) -->
|
|
<div class="px-3 py-2 space-y-1.5">
|
|
<div v-if="pending" class="flex items-center justify-center py-8" style="color: var(--nav-text-muted);">
|
|
Chargement...
|
|
</div>
|
|
<div v-else-if="filtered.length === 0" class="text-center py-8">
|
|
<p class="text-xs" style="color: var(--nav-text-muted);">Aucun résultat</p>
|
|
</div>
|
|
<div
|
|
v-for="structure in filtered"
|
|
:key="structure.id"
|
|
class="rounded-lg px-3 py-2 cursor-pointer transition-all"
|
|
:style="selectedId === structure.id
|
|
? `background: var(--nav-bg-alt); border-left: 3px solid ${familleColor(structure.famille_principale)}; padding-left: 9px;`
|
|
: 'background: var(--nav-bg); border-left: 3px solid transparent; padding-left: 9px;'"
|
|
@click="onSelectStructure(structure.id)"
|
|
@mouseenter="hoveredId = structure.id"
|
|
@mouseleave="hoveredId = null"
|
|
>
|
|
<div class="flex items-start justify-between gap-1.5">
|
|
<span class="font-semibold text-sm leading-snug" style="color: var(--nav-text);">{{ structure.nom }}</span>
|
|
<span
|
|
class="shrink-0 w-2.5 h-2.5 rounded-full mt-1"
|
|
:style="`background: ${familleColor(structure.famille_principale)};`"
|
|
/>
|
|
</div>
|
|
<div class="mt-0.5 text-xs" style="color: var(--nav-text-muted);">{{ structure.type_principal }} - {{ structure.ville }}</div>
|
|
<div v-if="structure.hashtags.length" class="mt-1 flex flex-wrap gap-1">
|
|
<span
|
|
v-for="tag in structure.hashtags.slice(0, 2)"
|
|
:key="tag"
|
|
class="text-xs"
|
|
style="color: var(--nav-text-muted);"
|
|
>{{ tag }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- ═══════════════════════════════════════ ZONE CENTRALE (carte) -->
|
|
<main class="flex-1 flex flex-col overflow-hidden relative">
|
|
|
|
<!-- ── VUE DESKTOP : Onglets Métro/Outre-mer ── -->
|
|
<div class="hidden lg:flex lg:flex-1 lg:flex-col lg:overflow-hidden">
|
|
<!-- Onglets desktop -->
|
|
<div class="shrink-0 flex" style="background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt);">
|
|
<button
|
|
class="px-5 py-2 text-sm font-medium transition-colors"
|
|
:style="desktopMapView === 'metropole'
|
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
|
@click="desktopMapView = 'metropole'"
|
|
>Métropolitain</button>
|
|
<button
|
|
class="px-5 py-2 text-sm font-medium transition-colors"
|
|
:style="desktopMapView === 'outremer'
|
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
|
@click="desktopMapView = 'outremer'"
|
|
>Outre-mer</button>
|
|
<button
|
|
class="px-5 py-2 text-sm font-medium transition-colors"
|
|
:style="desktopMapView === 'graphe'
|
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
|
@click="desktopMapView = 'graphe'"
|
|
>Vue graphique</button>
|
|
</div>
|
|
|
|
<!-- Carte Métropole desktop -->
|
|
<div v-show="desktopMapView === 'metropole'" class="flex-1 flex flex-col overflow-hidden">
|
|
<div class="relative flex-1" style="min-height: 200px;">
|
|
<ClientOnly>
|
|
<NavMapV2
|
|
ref="navMapRef"
|
|
:structures="metropoleStructures"
|
|
:selectedId="selectedId"
|
|
@select-structure="onSelectStructure"
|
|
/>
|
|
<template #fallback>
|
|
<div
|
|
class="w-full h-full flex items-center justify-center"
|
|
style="background: var(--nav-bg-alt); color: var(--nav-text-muted);"
|
|
>
|
|
Chargement de la carte…
|
|
</div>
|
|
</template>
|
|
</ClientOnly>
|
|
</div>
|
|
<ChatbotPlaceholder
|
|
@highlightOrgs="() => {}"
|
|
@applyHashtag="(tag) => { if (!selectedHashtags.includes(tag)) selectedHashtags = [...selectedHashtags, tag] }"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Carte Outre-mer desktop -->
|
|
<div v-show="desktopMapView === 'outremer'" class="flex-1 overflow-y-auto" style="background: var(--nav-bg);">
|
|
<ClientOnly>
|
|
<OutremerMap
|
|
:orgs="outremerOrgsLegacy"
|
|
:selectedId="selectedIdLegacyNum"
|
|
@select-org="() => {}"
|
|
/>
|
|
<template #fallback>
|
|
<div class="flex items-center justify-center h-full text-sm" style="color: var(--nav-text-muted);">
|
|
Chargement…
|
|
</div>
|
|
</template>
|
|
</ClientOnly>
|
|
</div>
|
|
|
|
<!-- Vue graphique desktop -->
|
|
<div v-show="desktopMapView === 'graphe'" class="flex-1 overflow-hidden flex flex-col">
|
|
<div class="flex-1 overflow-hidden relative">
|
|
<ClientOnly>
|
|
<GraphView
|
|
:data="bifurcationData"
|
|
:allHashtags="allHashtags"
|
|
:active="desktopMapView === 'graphe'"
|
|
@select-structure="onSelectStructure"
|
|
/>
|
|
<template #fallback>
|
|
<div class="flex items-center justify-center h-full" style="color: var(--nav-text-muted);">
|
|
Chargement du graphe...
|
|
</div>
|
|
</template>
|
|
</ClientOnly>
|
|
</div>
|
|
<ChatbotPlaceholder
|
|
@highlightOrgs="() => {}"
|
|
@applyHashtag="(tag) => { if (!selectedHashtags.includes(tag)) selectedHashtags = [...selectedHashtags, tag] }"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── VUE MOBILE : Onglets Métro/Outre-mer + sheet swipable ── -->
|
|
<div class="lg:hidden shrink-0 flex" style="background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt);">
|
|
<button
|
|
class="flex-1 py-2 text-sm font-medium transition-colors"
|
|
:style="mobileMapView === 'metropole'
|
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
|
@click="mobileMapView = 'metropole'"
|
|
>Métropolitain</button>
|
|
<button
|
|
class="flex-1 py-2 text-sm font-medium transition-colors"
|
|
:style="mobileMapView === 'outremer'
|
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
|
@click="mobileMapView = 'outremer'"
|
|
>Outre-mer</button>
|
|
</div>
|
|
|
|
<div class="lg:hidden flex-1 relative overflow-hidden">
|
|
<!-- Carte mobile Métropole -->
|
|
<div v-show="mobileMapView === 'metropole'" class="absolute inset-0">
|
|
<ClientOnly>
|
|
<NavMapV2
|
|
ref="navMapMobileRef"
|
|
:structures="metropoleStructures"
|
|
:selectedId="selectedId"
|
|
@select-structure="onSelectStructureMobile"
|
|
/>
|
|
<template #fallback>
|
|
<div class="w-full h-full flex items-center justify-center" style="background: var(--nav-bg-alt); color: var(--nav-text-muted);">
|
|
Chargement de la carte…
|
|
</div>
|
|
</template>
|
|
</ClientOnly>
|
|
</div>
|
|
|
|
<!-- Carte mobile Outre-mer -->
|
|
<div v-show="mobileMapView === 'outremer'" class="absolute inset-0 overflow-y-auto" style="background: var(--nav-bg);">
|
|
<ClientOnly>
|
|
<OutremerMap
|
|
:orgs="outremerOrgsLegacy"
|
|
:selectedId="selectedIdLegacyNum"
|
|
@select-org="() => {}"
|
|
/>
|
|
<template #fallback>
|
|
<div class="flex items-center justify-center h-48" style="color: var(--nav-text-muted);">
|
|
Chargement…
|
|
</div>
|
|
</template>
|
|
</ClientOnly>
|
|
</div>
|
|
|
|
<!-- Bottom sheet swipable -->
|
|
<ClientOnly>
|
|
<MobileSheet :resultCount="filtered.length" :pending="pending">
|
|
<!-- Bandeau intention mobile -->
|
|
<div class="px-3 py-2" style="background: var(--bifurc-banner-bg, #faf8f5); border-bottom: 1px solid var(--bifurc-banner-border, #e0d8cc);">
|
|
<p class="text-xs leading-relaxed" style="color: var(--bifurc-banner-text, #2c2416); margin: 0;">
|
|
120 réseaux, collectifs et agences où des pensées écologiques deviennent des pratiques d'architecture.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Filtres hashtags mobile -->
|
|
<div class="px-3 py-2" style="border-bottom: 1px solid var(--nav-bg-alt);">
|
|
<HashtagFilter
|
|
:allHashtags="allHashtags"
|
|
:selectedHashtags="selectedHashtags"
|
|
:selectedFamille="selectedFamille"
|
|
@update:selectedHashtags="selectedHashtags = $event"
|
|
@update:selectedFamille="selectedFamille = $event"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Barre recherche mobile -->
|
|
<div class="px-3 pt-2 pb-2" style="border-bottom: 1px solid var(--nav-bg-alt);">
|
|
<label class="mobile-search-label" aria-label="Rechercher une structure">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="color: var(--nav-text-muted); flex-shrink: 0;">
|
|
<circle cx="11" cy="11" r="8"/>
|
|
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
|
</svg>
|
|
<input
|
|
v-model="search"
|
|
type="search"
|
|
placeholder="Rechercher…"
|
|
class="mobile-search-input"
|
|
autocomplete="off"
|
|
/>
|
|
<button
|
|
v-if="search"
|
|
type="button"
|
|
class="mobile-search-clear"
|
|
aria-label="Effacer"
|
|
@click.stop="search = ''"
|
|
>
|
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
|
|
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
</svg>
|
|
</button>
|
|
</label>
|
|
<button
|
|
v-if="hasActiveFilters"
|
|
@click="resetFilters"
|
|
class="mt-1 text-xs"
|
|
style="color: var(--nav-text-muted); text-decoration: underline;"
|
|
>Effacer les filtres</button>
|
|
</div>
|
|
|
|
<!-- Liste fiches mobile -->
|
|
<div class="px-3 py-2">
|
|
<div class="text-xs font-bold uppercase tracking-wide mb-2" style="color: var(--nav-text-muted);">
|
|
{{ filtered.length }} structure{{ filtered.length > 1 ? 's' : '' }}
|
|
</div>
|
|
<div v-if="pending" class="flex items-center justify-center py-8" style="color: var(--nav-text-muted);">
|
|
Chargement des fiches…
|
|
</div>
|
|
<div v-else-if="filtered.length === 0" class="text-center py-8">
|
|
<p class="text-sm mb-2" style="color: var(--nav-text-muted);">Aucun résultat pour ces filtres.</p>
|
|
<button @click="resetFilters" class="text-sm underline" style="color: var(--nav-primary-solid);">
|
|
Effacer les filtres
|
|
</button>
|
|
</div>
|
|
<div class="space-y-2">
|
|
<div
|
|
v-for="structure in filtered"
|
|
:key="structure.id"
|
|
class="block rounded-lg p-3 transition-all cursor-pointer"
|
|
:style="selectedId === structure.id
|
|
? `background: var(--nav-bg-alt); border-left: 3px solid ${familleColor(structure.famille_principale)};`
|
|
: 'background: var(--nav-surface); border-left: 3px solid transparent;'"
|
|
@click="onSelectStructureMobile(structure.id)"
|
|
>
|
|
<div class="flex items-start justify-between gap-2">
|
|
<span class="font-semibold text-sm leading-snug" style="color: var(--nav-text);">{{ structure.nom }}</span>
|
|
<span
|
|
class="shrink-0 w-2.5 h-2.5 rounded-full mt-1"
|
|
:style="`background: ${familleColor(structure.famille_principale)};`"
|
|
/>
|
|
</div>
|
|
<div class="mt-0.5 text-xs" style="color: var(--nav-text-muted);">{{ structure.type_principal }} · {{ structure.ville }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</MobileSheet>
|
|
</ClientOnly>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- ═══════════════════════════════════════ MODAL FICHE V2 (desktop) -->
|
|
<FicheModalV2
|
|
v-model="ficheModalOpen"
|
|
:structureId="ficheModalId"
|
|
:data="bifurcationData"
|
|
@update:structureId="ficheModalId = $event"
|
|
/>
|
|
|
|
<!-- ═══════════════════════════════════════ BOUTON CHATBOT FLOTTANT (mobile) -->
|
|
<button
|
|
class="lg:hidden fixed bottom-6 right-4 z-[1000] flex items-center gap-2 px-4 rounded-full shadow-lg"
|
|
style="
|
|
height: 48px;
|
|
background: var(--nav-primary);
|
|
opacity: 0.92;
|
|
color: var(--nav-text-on-primary);
|
|
box-shadow: 0 4px 16px rgba(26,34,56,0.25);
|
|
font-family: var(--nav-font);
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
"
|
|
aria-label="Ouvrir l'assistant Chatbot"
|
|
@click="chatbotOpen = true"
|
|
>
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
</svg>
|
|
<span>Chatbot</span>
|
|
</button>
|
|
|
|
<!-- ═══════════════════════════════════════ CHATBOT BOTTOM SHEET (mobile) -->
|
|
<ChatbotSheet
|
|
:modelValue="chatbotOpen"
|
|
endpoint="/api/chatbot-reseaux"
|
|
@update:modelValue="chatbotOpen = $event"
|
|
@highlightOrgs="() => {}"
|
|
/>
|
|
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { ReseauxBifurcationData, StructureV2 } from '~/types/structure-v2'
|
|
|
|
// ── Couleurs familles ──────────────────────────────────────────────────────
|
|
const FAMILLE_COLORS: Record<number, string> = {
|
|
1: '#a85d3e',
|
|
2: '#c4a472',
|
|
3: '#d4a017',
|
|
4: '#5a7a4a',
|
|
5: '#3d6a8c',
|
|
6: '#6b3fa0',
|
|
}
|
|
|
|
function familleColor(f: number): string {
|
|
return FAMILLE_COLORS[f] ?? '#888'
|
|
}
|
|
|
|
// ── État UI ────────────────────────────────────────────────────────────────
|
|
const selectedId = ref<string | null>(null)
|
|
const hoveredId = ref<string | null>(null)
|
|
const ficheModalOpen = ref(false)
|
|
const ficheModalId = ref<string | null>(null)
|
|
const chatbotOpen = ref(false)
|
|
const mobileMapView = ref<'metropole' | 'outremer'>('metropole')
|
|
const desktopMapView = ref<'metropole' | 'outremer' | 'graphe'>('metropole')
|
|
|
|
// Filtres
|
|
const search = ref('')
|
|
const selectedFamille = ref<number | null>(null)
|
|
const selectedHashtags = ref<string[]>([])
|
|
|
|
// Refs cartes
|
|
const navMapRef = ref<any>(null)
|
|
const navMapMobileRef = ref<any>(null)
|
|
|
|
// ── Données V2 - JSON statique ─────────────────────────────────────────────
|
|
const bifurcationData = ref<ReseauxBifurcationData | null>(null)
|
|
const pending = ref(true)
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
bifurcationData.value = await $fetch<ReseauxBifurcationData>('/data/reseaux-bifurcation.json')
|
|
} catch (e) {
|
|
console.error('Erreur chargement reseaux-bifurcation.json', e)
|
|
} finally {
|
|
pending.value = false
|
|
}
|
|
})
|
|
|
|
const structures = computed<StructureV2[]>(() => bifurcationData.value?.structures ?? [])
|
|
|
|
// Tous les hashtags uniques triés
|
|
const allHashtags = computed<string[]>(() => {
|
|
const set = new Set<string>()
|
|
structures.value.forEach(s => s.hashtags.forEach(h => set.add(h)))
|
|
return Array.from(set).sort()
|
|
})
|
|
|
|
// ── Filtrage ───────────────────────────────────────────────────────────────
|
|
const filtered = computed<StructureV2[]>(() => {
|
|
let result = structures.value
|
|
|
|
// Filtre texte
|
|
if (search.value.trim()) {
|
|
const q = search.value.toLowerCase()
|
|
result = result.filter(
|
|
s =>
|
|
s.nom.toLowerCase().includes(q) ||
|
|
s.ville.toLowerCase().includes(q) ||
|
|
s.description_courte.toLowerCase().includes(q) ||
|
|
s.hashtags.some(h => h.toLowerCase().includes(q))
|
|
)
|
|
}
|
|
|
|
// Filtre famille - F6 = badge_f6_recherche_politique, pas famille_principale
|
|
if (selectedFamille.value !== null) {
|
|
if (selectedFamille.value === 6) {
|
|
result = result.filter(s => (s.badges as any)?.f6_recherche_politique === true)
|
|
} else {
|
|
result = result.filter(
|
|
s => s.famille_principale === selectedFamille.value ||
|
|
(s.familles_secondaires ?? []).includes(selectedFamille.value!)
|
|
)
|
|
}
|
|
}
|
|
|
|
// Filtre hashtags (AND logique si plusieurs)
|
|
if (selectedHashtags.value.length) {
|
|
result = result.filter(
|
|
s => selectedHashtags.value.every(h => s.hashtags.includes(h))
|
|
)
|
|
}
|
|
|
|
return result
|
|
})
|
|
|
|
const hasActiveFilters = computed(
|
|
() => !!search.value || selectedFamille.value !== null || selectedHashtags.value.length > 0
|
|
)
|
|
|
|
function resetFilters() {
|
|
search.value = ''
|
|
selectedFamille.value = null
|
|
selectedHashtags.value = []
|
|
}
|
|
|
|
// Structures métropole (pays != DOM-TOM, et avec coordonnées)
|
|
// Pour simplifier : toutes les structures (la carte gère les sans-coords)
|
|
const metropoleStructures = computed<StructureV2[]>(() => filtered.value)
|
|
|
|
// Outre-mer : pas de structures V2 DOM-TOM pour l'instant - garder le composant existant vide
|
|
// OutremerMap attend le format Org legacy - on passe un tableau vide
|
|
const outremerOrgsLegacy = computed(() => [])
|
|
const selectedIdLegacyNum = computed(() => null)
|
|
|
|
// ── Sélection ─────────────────────────────────────────────────────────────
|
|
function onSelectStructure(id: string) {
|
|
selectedId.value = selectedId.value === id ? null : id
|
|
if (typeof window !== 'undefined' && window.innerWidth >= 1024) {
|
|
ficheModalId.value = id
|
|
ficheModalOpen.value = true
|
|
}
|
|
}
|
|
|
|
function onSelectStructureMobile(id: string) {
|
|
selectedId.value = id
|
|
ficheModalId.value = id
|
|
ficheModalOpen.value = true
|
|
}
|
|
|
|
useHead({ title: "AEP - Réseaux de bifurcation architecturale" })
|
|
</script>
|