Files
fuel-price/resources/js/components/LeafletMap.vue
2026-04-11 18:45:26 +01:00

109 lines
2.7 KiB
Vue

<template>
<div class="space-y-2">
<button
@click="toggleMap"
class="flex items-center gap-2 text-sm font-bold text-accent hover:text-accent-content transition-colors"
>
<iconify-icon :icon="isOpen ? 'lucide:chevron-up' : 'lucide:chevron-down'"></iconify-icon>
{{ isOpen ? 'Hide map' : 'Show map' }}
</button>
<div
v-show="isOpen"
ref="mapContainer"
class="w-full h-72 rounded-2xl overflow-hidden border border-zinc-300 shadow-sm"
></div>
</div>
</template>
<script setup>
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
// Fix Leaflet default marker icon path broken by Vite
delete L.Icon.Default.prototype._getIconUrl
L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
})
const props = defineProps({
stations: { type: Array, required: true },
defaultOpen: { type: Boolean, default: false },
})
const mapContainer = ref(null)
const isOpen = ref(false)
let mapInstance = null
let markersLayer = null
function initMap() {
if (mapInstance || !mapContainer.value) return
mapInstance = L.map(mapContainer.value)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
}).addTo(mapInstance)
markersLayer = L.layerGroup().addTo(mapInstance)
}
function renderMarkers() {
if (!mapInstance || !markersLayer) return
markersLayer.clearLayers()
if (!props.stations.length) return
const bounds = []
props.stations.forEach(station => {
const marker = L.marker([station.lat, station.lng])
.bindPopup(`<strong>${station.name}</strong><br>${station.price}p`)
markersLayer.addLayer(marker)
bounds.push([station.lat, station.lng])
})
if (bounds.length) {
mapInstance.fitBounds(bounds, { padding: [30, 30] })
}
}
async function toggleMap() {
isOpen.value = !isOpen.value
if (isOpen.value) {
await nextTick()
initMap()
mapInstance.invalidateSize()
renderMarkers()
}
}
onMounted(async () => {
if (props.defaultOpen) {
isOpen.value = true
await nextTick()
initMap()
mapInstance.invalidateSize()
renderMarkers()
}
})
watch(() => props.stations, () => {
if (isOpen.value) {
renderMarkers()
}
})
onUnmounted(() => {
if (mapInstance) {
mapInstance.remove()
mapInstance = null
}
})
</script>