Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1838f4b02d | ||
|
|
5b504b3e04 | ||
|
|
b2b8c13fee |
@@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="fiche-mini-card"
|
||||||
|
:aria-label="fiche.nom"
|
||||||
|
@click="emit('open')"
|
||||||
|
>
|
||||||
|
<span class="fiche-mini-card__nom">{{ fiche.nom }}</span>
|
||||||
|
<span v-if="fiche.resume" class="fiche-mini-card__resume">{{ fiche.resume }}</span>
|
||||||
|
|
||||||
|
<ChipGroup
|
||||||
|
v-if="fiche.chips.length || fiche.echelle"
|
||||||
|
:aria-label="`Étiquettes de ${fiche.nom}`"
|
||||||
|
class="fiche-mini-card__chips"
|
||||||
|
>
|
||||||
|
<Chip v-if="fiche.echelle" as="span" variant="echelle" :label="fiche.echelle" />
|
||||||
|
<Chip v-for="chip in fiche.chips" :key="chip" as="span" variant="fonction" :label="chip" />
|
||||||
|
</ChipGroup>
|
||||||
|
|
||||||
|
<span v-if="!fiche.geoloc" class="fiche-mini-card__no-geo">Sans localisation</span>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { MiniFiche } from '~/composables/useFicheAdapter'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
fiche: MiniFiche
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
open: []
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fiche-mini-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--space-sm);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text);
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: background-color var(--dur-fast) var(--ease-out),
|
||||||
|
box-shadow var(--dur-fast) var(--ease-out);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.fiche-mini-card {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiche-mini-card:hover {
|
||||||
|
background: var(--surface-2);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiche-mini-card:focus-visible {
|
||||||
|
outline: 2px solid var(--focus-ring);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiche-mini-card__nom {
|
||||||
|
width: 100%;
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
font-weight: var(--fw-semibold);
|
||||||
|
line-height: var(--lh-tight);
|
||||||
|
color: var(--text);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiche-mini-card__resume {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: var(--space-2xs);
|
||||||
|
font-size: var(--fs-xs);
|
||||||
|
line-height: var(--lh-base);
|
||||||
|
color: var(--text-muted);
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiche-mini-card__chips {
|
||||||
|
margin-top: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiche-mini-card__no-geo {
|
||||||
|
margin-top: var(--space-2xs);
|
||||||
|
font-size: var(--fs-xs);
|
||||||
|
color: var(--text-subtle);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
<!--
|
||||||
|
FichesPanel — B3-M3
|
||||||
|
Panneau « toutes les fiches » : reçoit une liste déjà filtrée (recherche +
|
||||||
|
filtres en cours côté page), l'affiche en grille de FicheMiniCard, et
|
||||||
|
expose ses propres chips de filtrage branchées sur le MÊME état que la
|
||||||
|
page appelante (pas un second état — voir vérification du brief).
|
||||||
|
Ne crée pas de second champ de recherche : `query` sert uniquement à
|
||||||
|
nuancer le message d'état vide.
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div class="fiches-panel">
|
||||||
|
<div class="fiches-panel__head">
|
||||||
|
<p class="fiches-panel__intention">{{ intention }}</p>
|
||||||
|
|
||||||
|
<ChipGroup v-if="hashtags.length" aria-label="Filtrer les fiches" class="fiches-panel__chips">
|
||||||
|
<Chip
|
||||||
|
v-for="tag in hashtags"
|
||||||
|
:key="tag"
|
||||||
|
:label="tag"
|
||||||
|
variant="fonction"
|
||||||
|
:active="selected.includes(tag)"
|
||||||
|
@toggle="toggle(tag)"
|
||||||
|
/>
|
||||||
|
</ChipGroup>
|
||||||
|
|
||||||
|
<div class="fiches-panel__count">
|
||||||
|
{{ fiches.length }} fiche{{ fiches.length > 1 ? 's' : '' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fiches-panel__body">
|
||||||
|
<p v-if="fiches.length === 0" class="fiches-panel__empty">
|
||||||
|
Aucun résultat<template v-if="query"> pour « {{ query }} »</template>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div v-else class="fiches-panel__grid">
|
||||||
|
<FicheMiniCard
|
||||||
|
v-for="fiche in fiches"
|
||||||
|
:key="`${fiche.kind}-${fiche.id}`"
|
||||||
|
:fiche="fiche"
|
||||||
|
@open="emit('open', fiche)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { MiniFiche } from '~/composables/useFicheAdapter'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
fiches: MiniFiche[]
|
||||||
|
hashtags: string[]
|
||||||
|
selected: string[]
|
||||||
|
query: string
|
||||||
|
intention: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:selected': [value: string[]]
|
||||||
|
open: [fiche: MiniFiche]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
function toggle(tag: string) {
|
||||||
|
if (props.selected.includes(tag)) {
|
||||||
|
emit('update:selected', props.selected.filter((t) => t !== tag))
|
||||||
|
} else {
|
||||||
|
emit('update:selected', [...props.selected, tag])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fiches-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiches-panel__head {
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: var(--space-sm) var(--space-md);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
background: var(--surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiches-panel__intention {
|
||||||
|
margin: 0 0 var(--space-xs);
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
line-height: var(--lh-base);
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiches-panel__count {
|
||||||
|
margin-top: var(--space-xs);
|
||||||
|
font-size: var(--fs-xs);
|
||||||
|
font-weight: var(--fw-semibold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
color: var(--text-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiches-panel__body {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiches-panel__empty {
|
||||||
|
margin: var(--space-xl) 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fiches-panel__grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.fiches-panel__grid {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.fiches-panel__grid {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* useFicheAdapter — B3-M2
|
||||||
|
* Adaptateur léger Org | StructureV2 → MiniFiche, format commun consommé par
|
||||||
|
* FicheMiniCard / FichesPanel. Aucune mutation des types sources.
|
||||||
|
*
|
||||||
|
* Repli résumé (brief B3, le champ `resume_court` du batch B2b n'existe pas
|
||||||
|
* encore) : première phrase du premier champ non vide parmi une liste de
|
||||||
|
* candidats ordonnée par type, tronquée proprement à ~110 caractères sur un
|
||||||
|
* mot (jamais au milieu).
|
||||||
|
*/
|
||||||
|
import type { Org } from '~/types/org'
|
||||||
|
import type { StructureV2 } from '~/types/structure-v2'
|
||||||
|
|
||||||
|
export interface MiniFiche {
|
||||||
|
id: string | number
|
||||||
|
nom: string
|
||||||
|
resume: string
|
||||||
|
chips: string[]
|
||||||
|
echelle?: string
|
||||||
|
geoloc: boolean
|
||||||
|
kind: 'org' | 'structure'
|
||||||
|
}
|
||||||
|
|
||||||
|
const RESUME_MAX = 110
|
||||||
|
|
||||||
|
/** Première phrase d'un texte (coupe sur ". "/"! "/"? " ou fin de chaîne). */
|
||||||
|
function firstSentence(text: string): string {
|
||||||
|
const match = text.match(/^.*?[.!?](?=\s|$)/)
|
||||||
|
return (match ? match[0] : text).trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tronque proprement sur un mot, jamais au milieu. */
|
||||||
|
function truncateOnWord(text: string, max = RESUME_MAX): string {
|
||||||
|
if (text.length <= max) return text
|
||||||
|
const slice = text.slice(0, max)
|
||||||
|
const lastSpace = slice.lastIndexOf(' ')
|
||||||
|
const cut = lastSpace > max * 0.4 ? slice.slice(0, lastSpace) : slice
|
||||||
|
return `${cut.trimEnd()}…`
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildResume(candidates: Array<string | null | undefined>): string {
|
||||||
|
const source = candidates.find((c) => !!c && c.trim().length > 0)
|
||||||
|
if (!source) return ''
|
||||||
|
return truncateOnWord(firstSentence(source.trim()))
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStructure(source: Org | StructureV2): source is StructureV2 {
|
||||||
|
return 'famille_principale' in source
|
||||||
|
}
|
||||||
|
|
||||||
|
function orgFonctions(org: Org): string[] {
|
||||||
|
return (org.tags_fonction ?? '')
|
||||||
|
.split(',')
|
||||||
|
.map((f) => f.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.slice(0, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFicheAdapter() {
|
||||||
|
function toMini(source: Org | StructureV2): MiniFiche {
|
||||||
|
if (isStructure(source)) {
|
||||||
|
return {
|
||||||
|
id: source.id,
|
||||||
|
nom: source.nom,
|
||||||
|
resume: buildResume([source.description_courte, source.description_longue]),
|
||||||
|
chips: (source.hashtags ?? []).slice(0, 3),
|
||||||
|
geoloc: source.latitude != null && source.longitude != null,
|
||||||
|
kind: 'structure',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: source.Id,
|
||||||
|
nom: source.nom,
|
||||||
|
resume: buildResume([source.description_enrichie, source.description]),
|
||||||
|
chips: orgFonctions(source),
|
||||||
|
echelle: source.echelle,
|
||||||
|
geoloc: source.latitude != null && source.longitude != null,
|
||||||
|
kind: 'org',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { toMini }
|
||||||
|
}
|
||||||
@@ -38,7 +38,6 @@ export default defineNuxtConfig({
|
|||||||
ssr: true,
|
ssr: true,
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
cacheDir: 'C:/Users/jules/AppData/Local/nav-carte-vite-cache',
|
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ['leaflet', 'leaflet.markercluster', 'd3'],
|
include: ['leaflet', 'leaflet.markercluster', 'd3'],
|
||||||
},
|
},
|
||||||
|
|||||||
+97
-11
@@ -2,7 +2,15 @@
|
|||||||
<div class="flex h-full overflow-hidden" style="background: var(--nav-bg);">
|
<div class="flex h-full overflow-hidden" style="background: var(--nav-bg);">
|
||||||
|
|
||||||
<!-- ═══════════════════════════════════════ SIDEBAR DESKTOP (>= 1024px) -->
|
<!-- ═══════════════════════════════════════ 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%;">
|
<!-- Masquée en vue « Toutes les fiches » (B3-M3, décision Jules axe 2) :
|
||||||
|
le panneau EST la liste (+ ses propres chips hashtags), la sidebar
|
||||||
|
ferait doublon. IntentionBanner reste monté (Teleport, insensible au
|
||||||
|
v-show de son conteneur). -->
|
||||||
|
<div
|
||||||
|
v-show="desktopMapView !== 'fiches'"
|
||||||
|
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 s'auto-affiche via Teleport (overlay plein ecran) -->
|
||||||
<IntentionBanner />
|
<IntentionBanner />
|
||||||
@@ -113,22 +121,29 @@
|
|||||||
:style="desktopMapView === 'metropole'
|
:style="desktopMapView === 'metropole'
|
||||||
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
@click="desktopMapView = 'metropole'"
|
@click="setDesktopView('metropole')"
|
||||||
>Métropolitain</button>
|
>Métropolitain</button>
|
||||||
<button
|
<button
|
||||||
class="px-5 py-2 text-sm font-medium transition-colors"
|
class="px-5 py-2 text-sm font-medium transition-colors"
|
||||||
:style="desktopMapView === 'outremer'
|
:style="desktopMapView === 'outremer'
|
||||||
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
@click="desktopMapView = 'outremer'"
|
@click="setDesktopView('outremer')"
|
||||||
>Outre-mer</button>
|
>Outre-mer</button>
|
||||||
<button
|
<button
|
||||||
class="px-5 py-2 text-sm font-medium transition-colors"
|
class="px-5 py-2 text-sm font-medium transition-colors"
|
||||||
:style="desktopMapView === 'graphe'
|
:style="desktopMapView === 'graphe'
|
||||||
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
@click="desktopMapView = 'graphe'"
|
@click="setDesktopView('graphe')"
|
||||||
>Vue graphique</button>
|
>Vue graphique</button>
|
||||||
|
<button
|
||||||
|
class="px-5 py-2 text-sm font-medium transition-colors"
|
||||||
|
:style="desktopMapView === 'fiches'
|
||||||
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
|
@click="setDesktopView('fiches')"
|
||||||
|
>Toutes les fiches</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Carte Métropole desktop -->
|
<!-- Carte Métropole desktop -->
|
||||||
@@ -195,31 +210,53 @@
|
|||||||
@applyHashtag="(tag) => { if (!selectedHashtags.includes(tag)) selectedHashtags = [...selectedHashtags, tag] }"
|
@applyHashtag="(tag) => { if (!selectedHashtags.includes(tag)) selectedHashtags = [...selectedHashtags, tag] }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Panneau « Toutes les fiches » desktop (B3-M3) — la sidebar est
|
||||||
|
masquée dans cette vue (décision Jules axe 2), le panneau EST
|
||||||
|
la liste, avec ses propres chips hashtags sur le même état. -->
|
||||||
|
<div v-show="desktopMapView === 'fiches'" class="flex-1 flex flex-col overflow-hidden">
|
||||||
|
<FichesPanel
|
||||||
|
:fiches="allStructuresMini"
|
||||||
|
:hashtags="allHashtags"
|
||||||
|
:selected="selectedHashtags"
|
||||||
|
:query="search"
|
||||||
|
intention="Toutes les agences et réseaux du répertoire, filtrables par hashtag."
|
||||||
|
@update:selected="selectedHashtags = $event"
|
||||||
|
@open="(fiche) => onSelectStructure(String(fiche.id))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── VUE MOBILE : Onglets Métro/Outre-mer/Graphique + sheet swipable ── -->
|
<!-- ── VUE MOBILE : Onglets Métro/Outre-mer/Graphique/Fiches + sheet swipable ── -->
|
||||||
<div class="lg:hidden shrink-0 flex" style="background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt);">
|
<div class="lg:hidden shrink-0 flex" style="background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt);">
|
||||||
<button
|
<button
|
||||||
class="flex-1 py-2 text-sm font-medium transition-colors"
|
class="flex-1 py-2 text-sm font-medium transition-colors"
|
||||||
:style="mobileMapView === 'metropole'
|
:style="mobileMapView === 'metropole'
|
||||||
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
@click="mobileMapView = 'metropole'"
|
@click="setMobileView('metropole')"
|
||||||
>Métropolitain</button>
|
>Métropolitain</button>
|
||||||
<button
|
<button
|
||||||
class="flex-1 py-2 text-sm font-medium transition-colors"
|
class="flex-1 py-2 text-sm font-medium transition-colors"
|
||||||
:style="mobileMapView === 'outremer'
|
:style="mobileMapView === 'outremer'
|
||||||
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
@click="mobileMapView = 'outremer'"
|
@click="setMobileView('outremer')"
|
||||||
>Outre-mer</button>
|
>Outre-mer</button>
|
||||||
<button
|
<button
|
||||||
class="flex-1 py-2 text-sm font-medium transition-colors"
|
class="flex-1 py-2 text-sm font-medium transition-colors"
|
||||||
:style="mobileMapView === 'graphe'
|
:style="mobileMapView === 'graphe'
|
||||||
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
@click="mobileMapView = 'graphe'"
|
@click="setMobileView('graphe')"
|
||||||
>Graphe</button>
|
>Graphe</button>
|
||||||
|
<button
|
||||||
|
class="flex-1 py-2 text-sm font-medium transition-colors"
|
||||||
|
:style="mobileMapView === 'fiches'
|
||||||
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
|
@click="setMobileView('fiches')"
|
||||||
|
>Fiches</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="lg:hidden flex-1 relative overflow-hidden">
|
<div class="lg:hidden flex-1 relative overflow-hidden">
|
||||||
@@ -273,8 +310,11 @@
|
|||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Fond neutre pour l'onglet « Fiches » (pas de carte dessous) -->
|
||||||
|
<div v-show="mobileMapView === 'fiches'" class="absolute inset-0" style="background: var(--nav-bg-alt);" />
|
||||||
|
|
||||||
<!-- Bottom sheet swipable (masqué en vue graphique pour ne pas occulter le canvas) -->
|
<!-- Bottom sheet swipable (masqué en vue graphique pour ne pas occulter le canvas) -->
|
||||||
<ClientOnly v-if="mobileMapView !== 'graphe'">
|
<ClientOnly v-if="mobileMapView !== 'graphe' && mobileMapView !== 'fiches'">
|
||||||
<MobileSheet :resultCount="filtered.length" :pending="pending">
|
<MobileSheet :resultCount="filtered.length" :pending="pending">
|
||||||
<!-- Bandeau intention mobile -->
|
<!-- 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);">
|
<div class="px-3 py-2" style="background: var(--bifurc-banner-bg, #faf8f5); border-bottom: 1px solid var(--bifurc-banner-border, #e0d8cc);">
|
||||||
@@ -365,6 +405,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</MobileSheet>
|
</MobileSheet>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
|
|
||||||
|
<!-- Bottom sheet — onglet « Fiches » mobile (B3-M3), contenu dédié -->
|
||||||
|
<ClientOnly v-else-if="mobileMapView === 'fiches'">
|
||||||
|
<MobileSheet :resultCount="filtered.length" :pending="pending">
|
||||||
|
<FichesPanel
|
||||||
|
:fiches="allStructuresMini"
|
||||||
|
:hashtags="allHashtags"
|
||||||
|
:selected="selectedHashtags"
|
||||||
|
:query="search"
|
||||||
|
intention="Toutes les agences et réseaux du répertoire, filtrables par hashtag."
|
||||||
|
@update:selected="selectedHashtags = $event"
|
||||||
|
@open="(fiche) => onSelectStructureMobile(String(fiche.id))"
|
||||||
|
/>
|
||||||
|
</MobileSheet>
|
||||||
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@@ -455,15 +510,41 @@ function familleColor(f: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── État UI ────────────────────────────────────────────────────────────────
|
// ── État UI ────────────────────────────────────────────────────────────────
|
||||||
|
type MapView = 'metropole' | 'outremer' | 'graphe' | 'fiches'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const selectedId = ref<string | null>(null)
|
const selectedId = ref<string | null>(null)
|
||||||
const hoveredId = ref<string | null>(null)
|
const hoveredId = ref<string | null>(null)
|
||||||
const ficheModalOpen = ref(false)
|
const ficheModalOpen = ref(false)
|
||||||
const ficheModalId = ref<string | null>(null)
|
const ficheModalId = ref<string | null>(null)
|
||||||
const chatbotOpen = ref(false)
|
const chatbotOpen = ref(false)
|
||||||
const mobileMapView = ref<'metropole' | 'outremer' | 'graphe'>('metropole')
|
const mobileMapView = ref<MapView>((route.query.vue as string) === 'fiches' ? 'fiches' : 'metropole')
|
||||||
const desktopMapView = ref<'metropole' | 'outremer' | 'graphe'>('metropole')
|
const desktopMapView = ref<MapView>((route.query.vue as string) === 'fiches' ? 'fiches' : 'metropole')
|
||||||
const missionOpen = ref(false)
|
const missionOpen = ref(false)
|
||||||
|
|
||||||
|
// Sync URL — B3-M3 : pose/retire ?vue=fiches uniquement (agences.vue n'avait
|
||||||
|
// aucune synchronisation URL avant B3 pour search/famille/hashtags/vue ; hors
|
||||||
|
// périmètre de cette mission de l'étendre, seul ?vue=fiches est ajouté ici).
|
||||||
|
function setDesktopView(v: MapView) {
|
||||||
|
desktopMapView.value = v
|
||||||
|
syncViewUrl()
|
||||||
|
}
|
||||||
|
function setMobileView(v: MapView) {
|
||||||
|
mobileMapView.value = v
|
||||||
|
syncViewUrl()
|
||||||
|
}
|
||||||
|
function syncViewUrl() {
|
||||||
|
const q: Record<string, string> = { ...(route.query as Record<string, string>) }
|
||||||
|
if (desktopMapView.value === 'fiches' || mobileMapView.value === 'fiches') {
|
||||||
|
q.vue = 'fiches'
|
||||||
|
} else {
|
||||||
|
delete q.vue
|
||||||
|
}
|
||||||
|
router.replace({ query: Object.keys(q).length ? q : undefined })
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
try {
|
try {
|
||||||
if (!localStorage.getItem('aep_reseaux_seen')) {
|
if (!localStorage.getItem('aep_reseaux_seen')) {
|
||||||
@@ -556,6 +637,11 @@ function resetFilters() {
|
|||||||
// Pour simplifier : toutes les structures (la carte gère les sans-coords)
|
// Pour simplifier : toutes les structures (la carte gère les sans-coords)
|
||||||
const metropoleStructures = computed<StructureV2[]>(() => filtered.value)
|
const metropoleStructures = computed<StructureV2[]>(() => filtered.value)
|
||||||
|
|
||||||
|
// Panneau « Toutes les fiches » (B3-M3) — mêmes structures que `filtered`,
|
||||||
|
// sans filtrer par géolocalisation (adaptateur mutualisé avec `/`).
|
||||||
|
const { toMini } = useFicheAdapter()
|
||||||
|
const allStructuresMini = computed(() => filtered.value.map((s) => toMini(s)))
|
||||||
|
|
||||||
// Outre-mer : pas de structures V2 DOM-TOM pour l'instant - garder le composant existant vide
|
// 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
|
// OutremerMap attend le format Org legacy - on passe un tableau vide
|
||||||
const outremerOrgsLegacy = computed(() => [])
|
const outremerOrgsLegacy = computed(() => [])
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
<!--
|
||||||
|
Page de dev — story de test FicheMiniCard (B3-M2)
|
||||||
|
Sert uniquement à la vérification visuelle. Aucun lien vers cette page
|
||||||
|
nulle part dans l'app (pas dans app.vue, pas dans les pages) : navigation
|
||||||
|
directe par URL uniquement (/dev/fiche-mini-card).
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div class="story">
|
||||||
|
<h1 class="story__title">FicheMiniCard — story de test (B3-M2)</h1>
|
||||||
|
|
||||||
|
<section class="story__section">
|
||||||
|
<h2 class="story__heading">Org — cas nominal</h2>
|
||||||
|
<div class="story__grid">
|
||||||
|
<FicheMiniCard :fiche="orgNominal" @open="log('org-nominal')" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="story__section">
|
||||||
|
<h2 class="story__heading">Structure — cas nominal</h2>
|
||||||
|
<div class="story__grid">
|
||||||
|
<FicheMiniCard :fiche="structureNominal" @open="log('structure-nominal')" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="story__section">
|
||||||
|
<h2 class="story__heading">Cas limites</h2>
|
||||||
|
<div class="story__grid">
|
||||||
|
<FicheMiniCard :fiche="nomLong" @open="log('nom-long')" />
|
||||||
|
<FicheMiniCard :fiche="sansDescription" @open="log('sans-description')" />
|
||||||
|
<FicheMiniCard :fiche="sansChips" @open="log('sans-chips')" />
|
||||||
|
<FicheMiniCard :fiche="sansGeoloc" @open="log('sans-geoloc')" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="story__section">
|
||||||
|
<h2 class="story__heading">Dernier événement « open »</h2>
|
||||||
|
<p class="story__log">{{ lastOpen ?? '(aucun clic pour le moment)' }}</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { MiniFiche } from '~/composables/useFicheAdapter'
|
||||||
|
|
||||||
|
definePageMeta({ layout: false })
|
||||||
|
|
||||||
|
const lastOpen = ref<string | null>(null)
|
||||||
|
function log(label: string) {
|
||||||
|
lastOpen.value = label
|
||||||
|
}
|
||||||
|
|
||||||
|
const orgNominal: MiniFiche = {
|
||||||
|
id: 1,
|
||||||
|
nom: 'Réseau Bâti Vivant',
|
||||||
|
resume: 'Un réseau national d\'accompagnement juridique et technique pour les collectifs d\'auto-construction.',
|
||||||
|
chips: ['Juridique', 'Technique'],
|
||||||
|
echelle: 'National',
|
||||||
|
geoloc: true,
|
||||||
|
kind: 'org',
|
||||||
|
}
|
||||||
|
|
||||||
|
const structureNominal: MiniFiche = {
|
||||||
|
id: 'f1-rotor',
|
||||||
|
nom: 'Rotor',
|
||||||
|
resume: 'Rotor invente en Belgique le protocole de la déconstruction sélective comme geste politique contre la culture du tout-neuf.',
|
||||||
|
chips: ['#reemploi-structurel', '#plateforme-materiaux'],
|
||||||
|
geoloc: true,
|
||||||
|
kind: 'structure',
|
||||||
|
}
|
||||||
|
|
||||||
|
const nomLong: MiniFiche = {
|
||||||
|
id: 2,
|
||||||
|
nom: 'Collectif Régional de Réemploi et d\'Accompagnement des Chantiers Participatifs du Grand Ouest',
|
||||||
|
resume: 'Nom volontairement long pour vérifier l\'ellipse sur une ligne.',
|
||||||
|
chips: ['Chantier'],
|
||||||
|
echelle: 'Régional',
|
||||||
|
geoloc: true,
|
||||||
|
kind: 'org',
|
||||||
|
}
|
||||||
|
|
||||||
|
const sansDescription: MiniFiche = {
|
||||||
|
id: 3,
|
||||||
|
nom: 'Structure sans description',
|
||||||
|
resume: '',
|
||||||
|
chips: ['Formation'],
|
||||||
|
echelle: 'Local',
|
||||||
|
geoloc: true,
|
||||||
|
kind: 'org',
|
||||||
|
}
|
||||||
|
|
||||||
|
const sansChips: MiniFiche = {
|
||||||
|
id: 4,
|
||||||
|
nom: 'Structure sans chips',
|
||||||
|
resume: 'Une organisation dont aucune fonction ni hashtag n\'est renseigné dans la source.',
|
||||||
|
chips: [],
|
||||||
|
geoloc: true,
|
||||||
|
kind: 'org',
|
||||||
|
}
|
||||||
|
|
||||||
|
const sansGeoloc: MiniFiche = {
|
||||||
|
id: 5,
|
||||||
|
nom: 'Structure sans localisation',
|
||||||
|
resume: 'Une fiche nationale sans latitude ni longitude — invisible sur la carte, visible ici.',
|
||||||
|
chips: ['Administratif', 'Comptabilité'],
|
||||||
|
echelle: 'National',
|
||||||
|
geoloc: false,
|
||||||
|
kind: 'org',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.story {
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: var(--space-lg);
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
.story__title {
|
||||||
|
font-size: var(--fs-xl);
|
||||||
|
font-weight: var(--fw-bold);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.story__section {
|
||||||
|
margin-bottom: var(--space-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.story__heading {
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
font-weight: var(--fw-semibold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.story__grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.story__log {
|
||||||
|
font-size: var(--fs-sm);
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
+84
-10
@@ -40,7 +40,7 @@
|
|||||||
Mode dev — données seed
|
Mode dev — données seed
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── VUE DESKTOP : Onglets Métropole / Outre-mer ── -->
|
<!-- ── VUE DESKTOP : Onglets Métropole / Outre-mer / Toutes les fiches ── -->
|
||||||
<div class="hidden lg:flex lg:flex-1 lg:flex-col lg:overflow-hidden">
|
<div class="hidden lg:flex lg:flex-1 lg:flex-col lg:overflow-hidden">
|
||||||
<!-- Barre onglets desktop -->
|
<!-- Barre onglets desktop -->
|
||||||
<div class="shrink-0 flex" style="background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt);">
|
<div class="shrink-0 flex" style="background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt);">
|
||||||
@@ -49,15 +49,22 @@
|
|||||||
:style="desktopMapView === 'metropole'
|
:style="desktopMapView === 'metropole'
|
||||||
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
@click="desktopMapView = 'metropole'"
|
@click="setDesktopView('metropole')"
|
||||||
>Métropolitain</button>
|
>Métropolitain</button>
|
||||||
<button
|
<button
|
||||||
class="px-5 py-2 text-sm font-medium transition-colors"
|
class="px-5 py-2 text-sm font-medium transition-colors"
|
||||||
:style="desktopMapView === 'outremer'
|
:style="desktopMapView === 'outremer'
|
||||||
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
@click="desktopMapView = 'outremer'"
|
@click="setDesktopView('outremer')"
|
||||||
>Outre-mer</button>
|
>Outre-mer</button>
|
||||||
|
<button
|
||||||
|
class="px-5 py-2 text-sm font-medium transition-colors"
|
||||||
|
:style="desktopMapView === 'fiches'
|
||||||
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
|
@click="setDesktopView('fiches')"
|
||||||
|
>Toutes les fiches</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Carte Métropole desktop -->
|
<!-- Carte Métropole desktop -->
|
||||||
@@ -94,26 +101,58 @@
|
|||||||
</div>
|
</div>
|
||||||
<ChatbotPlaceholder @highlightOrgs="onHighlightOrgs" />
|
<ChatbotPlaceholder @highlightOrgs="onHighlightOrgs" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Panneau « Toutes les fiches » desktop (B3-M3) — remplace la carte,
|
||||||
|
fiches sans géoloc incluses (gain principal de cette mission) -->
|
||||||
|
<div v-show="desktopMapView === 'fiches'" class="flex-1 flex flex-col overflow-hidden">
|
||||||
|
<div
|
||||||
|
class="shrink-0 px-4 py-2"
|
||||||
|
style="border-bottom: 1px solid var(--nav-bg-alt); background: var(--nav-surface);"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="text-xs font-medium underline"
|
||||||
|
style="color: var(--nav-text-muted);"
|
||||||
|
@click="setDesktopView('metropole')"
|
||||||
|
>← Carte</button>
|
||||||
|
</div>
|
||||||
|
<FichesPanel
|
||||||
|
:fiches="allFichesMini"
|
||||||
|
:hashtags="[...FONCTIONS]"
|
||||||
|
:selected="fonctions"
|
||||||
|
:query="search"
|
||||||
|
intention="Toutes les structures d'entraide, y compris celles sans adresse sur la carte. Cochez une fonction pour filtrer."
|
||||||
|
@update:selected="onFonctions"
|
||||||
|
@open="(fiche) => onSelectOrg(Number(fiche.id))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── VUE MOBILE : Onglets Métro/Outre-mer + carte pleine hauteur + sheet swipable ── -->
|
<!-- ── VUE MOBILE : Onglets Métro/Outre-mer + carte pleine hauteur + sheet swipable ── -->
|
||||||
|
|
||||||
<!-- Onglets Métropolitain / Outre-mer -->
|
<!-- Onglets Métropolitain / Outre-mer / Toutes les fiches -->
|
||||||
<div class="lg:hidden shrink-0 flex" style="background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt);">
|
<div class="lg:hidden shrink-0 flex" style="background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt);">
|
||||||
<button
|
<button
|
||||||
class="flex-1 py-2 text-sm font-medium transition-colors"
|
class="flex-1 py-2 text-sm font-medium transition-colors"
|
||||||
:style="mobileMapView === 'metropole'
|
:style="mobileMapView === 'metropole'
|
||||||
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
@click="mobileMapView = 'metropole'"
|
@click="setMobileView('metropole')"
|
||||||
>Métropolitain</button>
|
>Métropolitain</button>
|
||||||
<button
|
<button
|
||||||
class="flex-1 py-2 text-sm font-medium transition-colors"
|
class="flex-1 py-2 text-sm font-medium transition-colors"
|
||||||
:style="mobileMapView === 'outremer'
|
:style="mobileMapView === 'outremer'
|
||||||
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
@click="mobileMapView = 'outremer'"
|
@click="setMobileView('outremer')"
|
||||||
>Outre-mer</button>
|
>Outre-mer</button>
|
||||||
|
<button
|
||||||
|
class="flex-1 py-2 text-sm font-medium transition-colors"
|
||||||
|
:style="mobileMapView === 'fiches'
|
||||||
|
? 'color: var(--nav-text); border-bottom: 2px solid var(--nav-primary-solid);'
|
||||||
|
: 'color: var(--nav-text-muted); border-bottom: 2px solid transparent;'"
|
||||||
|
@click="setMobileView('fiches')"
|
||||||
|
>Fiches</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="lg:hidden flex-1 relative overflow-hidden">
|
<div class="lg:hidden flex-1 relative overflow-hidden">
|
||||||
@@ -154,9 +193,12 @@
|
|||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bottom sheet swipable (Métropole et Outre-mer) -->
|
<!-- Fond neutre pour l'onglet « Fiches » (pas de carte dessous) -->
|
||||||
|
<div v-show="mobileMapView === 'fiches'" class="absolute inset-0" style="background: var(--nav-bg-alt);" />
|
||||||
|
|
||||||
|
<!-- Bottom sheet swipable (Métropole, Outre-mer et Fiches) -->
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<MobileSheet :resultCount="filtered.length" :pending="pending">
|
<MobileSheet v-if="mobileMapView !== 'fiches'" :resultCount="filtered.length" :pending="pending">
|
||||||
<!-- Barre recherche -->
|
<!-- Barre recherche -->
|
||||||
<div class="px-3 pt-2 pb-2" style="border-bottom: 1px solid var(--nav-bg-alt);">
|
<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 organisation">
|
<label class="mobile-search-label" aria-label="Rechercher une organisation">
|
||||||
@@ -272,6 +314,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MobileSheet>
|
</MobileSheet>
|
||||||
|
|
||||||
|
<!-- Bottom sheet — onglet « Fiches » mobile (B3-M3), contenu dédié -->
|
||||||
|
<MobileSheet v-else :resultCount="filtered.length" :pending="pending">
|
||||||
|
<FichesPanel
|
||||||
|
:fiches="allFichesMini"
|
||||||
|
:hashtags="[...FONCTIONS]"
|
||||||
|
:selected="fonctions"
|
||||||
|
:query="search"
|
||||||
|
intention="Toutes les structures d'entraide, y compris celles sans adresse sur la carte. Cochez une fonction pour filtrer."
|
||||||
|
@update:selected="onFonctions"
|
||||||
|
@open="(fiche) => onSelectOrgMobile(Number(fiche.id))"
|
||||||
|
/>
|
||||||
|
</MobileSheet>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -358,12 +413,14 @@ const territoireMode = ref<string>(
|
|||||||
(route.query.mode as string) === 'outremer' ? 'outremer' : 'metropole'
|
(route.query.mode as string) === 'outremer' ? 'outremer' : 'metropole'
|
||||||
)
|
)
|
||||||
|
|
||||||
const desktopMapView = ref<'metropole' | 'outremer'>('metropole')
|
type MapView = 'metropole' | 'outremer' | 'fiches'
|
||||||
|
|
||||||
|
const desktopMapView = ref<MapView>((route.query.vue as string) === 'fiches' ? 'fiches' : 'metropole')
|
||||||
const selectedId = ref<number | null>(null)
|
const selectedId = ref<number | null>(null)
|
||||||
const chatbotOpen = ref(false)
|
const chatbotOpen = ref(false)
|
||||||
const ficheModalOpen = ref(false)
|
const ficheModalOpen = ref(false)
|
||||||
const ficheModalId = ref<number | null>(null)
|
const ficheModalId = ref<number | null>(null)
|
||||||
const mobileMapView = ref<'metropole' | 'outremer'>('metropole')
|
const mobileMapView = ref<MapView>((route.query.vue as string) === 'fiches' ? 'fiches' : 'metropole')
|
||||||
const missionOpen = ref(false)
|
const missionOpen = ref(false)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -411,9 +468,20 @@ function syncUrl() {
|
|||||||
if (fonctions.value.length) q.fonctions = fonctions.value.join(',')
|
if (fonctions.value.length) q.fonctions = fonctions.value.join(',')
|
||||||
if (territoire.value) q.territoire = territoire.value
|
if (territoire.value) q.territoire = territoire.value
|
||||||
if (territoireMode.value === 'outremer') q.mode = 'outremer'
|
if (territoireMode.value === 'outremer') q.mode = 'outremer'
|
||||||
|
if (desktopMapView.value === 'fiches' || mobileMapView.value === 'fiches') q.vue = 'fiches'
|
||||||
router.replace({ query: Object.keys(q).length ? q : undefined })
|
router.replace({ query: Object.keys(q).length ? q : undefined })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Changement de vue (carte/fiches) — passe par syncUrl pour poser/retirer ?vue=fiches
|
||||||
|
function setDesktopView(v: MapView) {
|
||||||
|
desktopMapView.value = v
|
||||||
|
syncUrl()
|
||||||
|
}
|
||||||
|
function setMobileView(v: MapView) {
|
||||||
|
mobileMapView.value = v
|
||||||
|
syncUrl()
|
||||||
|
}
|
||||||
|
|
||||||
// Sauvegarde les filtres courants dans sessionStorage pour le bouton retour des fiches
|
// Sauvegarde les filtres courants dans sessionStorage pour le bouton retour des fiches
|
||||||
function storeFiltersForBack() {
|
function storeFiltersForBack() {
|
||||||
if (typeof window === 'undefined') return
|
if (typeof window === 'undefined') return
|
||||||
@@ -541,6 +609,12 @@ const filtered = computed<Org[]>(() => {
|
|||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Panneau « Toutes les fiches » (B3-M3) — mêmes fiches que `filtered`, sans
|
||||||
|
// filtrer par géolocalisation : c'est le gain principal de cette mission,
|
||||||
|
// les fiches sans latitude/longitude (invisibles sur la carte) y apparaissent.
|
||||||
|
const { toMini } = useFicheAdapter()
|
||||||
|
const allFichesMini = computed(() => filtered.value.map((org) => toMini(org)))
|
||||||
|
|
||||||
const DOM_TOM = ['Guadeloupe', 'Martinique', 'Guyane', 'La Réunion', 'Mayotte']
|
const DOM_TOM = ['Guadeloupe', 'Martinique', 'Guyane', 'La Réunion', 'Mayotte']
|
||||||
const DOM_TOM_LIST = DOM_TOM
|
const DOM_TOM_LIST = DOM_TOM
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user