diff --git a/components/NavMap.vue b/components/NavMap.vue index 6820f8a..b54da7e 100644 --- a/components/NavMap.vue +++ b/components/NavMap.vue @@ -24,10 +24,14 @@ interface Org { const props = defineProps<{ orgs: Org[] selectedId?: number | null + /** Fiche survolée dans la liste (B11) : épingle safran, sans recentrer. */ + survolId?: number | null }>() const emit = defineEmits<{ 'select-org': [id: number] + /** Survol d'une épingle (B11) : la liste allume la carte-fiche. */ + survol: [id: number | null] }>() const mapContainer = ref(null) @@ -35,14 +39,15 @@ let mapInstance: Map | null = null let clusterGroup: any = null const markers = new Map() let tileLayerInstance: any = null +let resizeObserver: ResizeObserver | null = null // Créer une DivIcon pour les pins personnalisés -function createPinIcon(isPrioritaire: boolean, isSelected = false): DivIcon { +function createPinIcon(isPrioritaire: boolean, isSelected = false, isSurvol = false): DivIcon { const L = (window as any).L - const bg = isPrioritaire ? '#f5b342' : 'rgba(26, 34, 56, 0.6)' - const border = isPrioritaire ? '#1a2238' : '#ffffff' - const size = isSelected ? 18 : 14 - const shadow = isSelected ? '0 0 0 4px rgba(245,179,66,0.5)' : 'none' + const bg = isSurvol || isPrioritaire ? '#f5b342' : 'rgba(26, 34, 56, 0.6)' + const border = isPrioritaire || isSurvol ? '#1a2238' : '#ffffff' + const size = isSelected || isSurvol ? 18 : 14 + const shadow = isSelected || isSurvol ? '0 0 0 4px rgba(245,179,66,0.5)' : 'none' return L.divIcon({ className: '', @@ -76,6 +81,7 @@ async function initMap() { ;(window as any).L = L // @ts-ignore — étend L.MarkerClusterGroup en side effect await import('leaflet.markercluster') + if (!mapContainer.value?.isConnected) return // page quittée pendant le chargement (ex. ?random=1) const MarkerClusterGroup = L.MarkerClusterGroup mapInstance = L.map(mapContainer.value, { @@ -125,6 +131,14 @@ async function initMap() { mapInstance.addLayer(clusterGroup) updateMarkers(L) + + // B11 : la carte change de largeur sans que la fenêtre bouge (Mixte ↔ Carte ↔ Fiches) + resizeObserver = new ResizeObserver(() => mapInstance?.invalidateSize({ pan: false })) + resizeObserver.observe(mapContainer.value) +} + +function iconeDe(org: Org): DivIcon { + return createPinIcon(!!org.prioritaire, org.Id === props.selectedId, org.Id === props.survolId) } function updateMarkers(L?: any) { @@ -141,10 +155,7 @@ function updateMarkers(L?: any) { ) orgsWithCoords.forEach((org) => { - const isSelected = org.Id === props.selectedId - const icon = createPinIcon(!!org.prioritaire, isSelected) - - const marker = leaflet.marker([org.latitude!, org.longitude!], { icon }) + const marker = leaflet.marker([org.latitude!, org.longitude!], { icon: iconeDe(org), title: org.nom }) const fonctions = org.tags_fonction ? org.tags_fonction.split(',').map((f: string) => f.trim()).filter(Boolean).slice(0, 2).join(', ') @@ -163,6 +174,8 @@ function updateMarkers(L?: any) { `, { maxWidth: 240 }) marker.on('click', () => emit('select-org', org.Id)) + marker.on('mouseover', () => emit('survol', org.Id)) + marker.on('mouseout', () => emit('survol', null)) markers.set(org.Id, marker) clusterGroup.addLayer(marker) @@ -189,7 +202,7 @@ watch( const oldMarker = markers.get(oldId) const oldOrg = props.orgs.find(o => o.Id === oldId) if (oldMarker && oldOrg) { - oldMarker.setIcon(createPinIcon(!!oldOrg.prioritaire, false)) + oldMarker.setIcon(iconeDe(oldOrg)) } } // Mettre en avant le nouveau marker @@ -197,7 +210,7 @@ watch( const newMarker = markers.get(newId) const newOrg = props.orgs.find(o => o.Id === newId) if (newMarker && newOrg) { - newMarker.setIcon(createPinIcon(!!newOrg.prioritaire, true)) + newMarker.setIcon(iconeDe(newOrg)) // Centrer si visible const latLng = newMarker.getLatLng() mapInstance.panTo(latLng, { animate: true }) @@ -206,6 +219,27 @@ watch( } ) +// Survol depuis la liste : épingle safran, sans recentrer la carte +watch( + () => props.survolId, + (newId, oldId) => { + for (const id of [oldId, newId]) { + if (id == null) continue + const m = markers.get(id) + const o = props.orgs.find(x => x.Id === id) + if (m && o) m.setIcon(iconeDe(o)) + allumerCluster(m, id === newId) + } + } +) + +// Épingle regroupée dans un cluster : c'est le cluster visible qui s'allume +function allumerCluster(m: Marker | undefined, on: boolean) { + const parent = m && clusterGroup?.getVisibleParent(m) + const el = parent && parent !== m ? (parent as any)._icon?.firstElementChild as HTMLElement | undefined : undefined + if (el) el.style.boxShadow = on ? '0 0 0 4px #f5b342' : '' +} + // Watcher dark mode — switch tuile CartoDB light_all ↔ dark_all function updateTileTheme(dark: boolean) { if (!mapInstance || !tileLayerInstance) return @@ -235,6 +269,7 @@ onMounted(() => { onUnmounted(() => { themeObserver?.disconnect() + resizeObserver?.disconnect() if (mapInstance) { mapInstance.remove() mapInstance = null diff --git a/pages/index.vue b/pages/index.vue index 16ca759..f83d4e2 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,344 +1,63 @@