Fix: map singleton state, schedules view sync, and carousel image error handling

This commit is contained in:
2026-02-28 22:38:59 -05:00
parent 1dd250ca42
commit 91ef07b3ed
3 changed files with 31 additions and 24 deletions

View File

@ -1,10 +1,11 @@
import { ref } from 'vue' import { ref } from 'vue'
// Registro global de todo lo que está en el mapa
const markers = ref<google.maps.Marker[]>([]) const markers = ref<google.maps.Marker[]>([])
const polylines = ref<google.maps.Polyline[]>([]) const polylines = ref<google.maps.Polyline[]>([])
const infoWindows = ref<google.maps.InfoWindow[]>([]) const infoWindows = ref<google.maps.InfoWindow[]>([])
const circles = ref<google.maps.Circle[]>([]) const circles = ref<google.maps.Circle[]>([])
// Callback para sincronización externa (ej. useGoogleMaps globalOverlays)
const onLimpiarCallback = ref<(() => void) | null>(null)
// Singleton pattern using composable // Singleton pattern using composable
export const useMapState = () => { export const useMapState = () => {
@ -14,8 +15,6 @@ export const useMapState = () => {
return marker return marker
} }
// Registrar una polyline // Registrar una polyline
const registrarPolyline = (polyline: google.maps.Polyline) => { const registrarPolyline = (polyline: google.maps.Polyline) => {
if (polyline) polylines.value.push(polyline) if (polyline) polylines.value.push(polyline)
@ -34,9 +33,6 @@ export const useMapState = () => {
return infoWindow return infoWindow
} }
// Callback para sincronización externa (ej. useGoogleMaps globalOverlays)
const onLimpiarCallback = ref<(() => void) | null>(null)
const registrarCallbackLimpieza = (fn: () => void) => { const registrarCallbackLimpieza = (fn: () => void) => {
onLimpiarCallback.value = fn onLimpiarCallback.value = fn
} }
@ -46,14 +42,15 @@ export const useMapState = () => {
console.log(`SIBU | Iniciando limpieza de ${markers.value.length} markers, ${polylines.value.length} polylines...`) console.log(`SIBU | Iniciando limpieza de ${markers.value.length} markers, ${polylines.value.length} polylines...`)
// Eliminar markers y overlays HTML // Eliminar markers y overlays HTML
markers.value.forEach(m => { markers.value.forEach((m: any) => {
try { try {
if (m) { if (m) {
if (typeof m.setMap === 'function') m.setMap(null); if (typeof m.setMap === 'function') m.setMap(null);
if (typeof (m as any).remove === 'function') (m as any).remove(); if (typeof m.remove === 'function') m.remove();
if (typeof (m as any).onRemove === 'function') (m as any).onRemove(); if (typeof m.onRemove === 'function') m.onRemove();
if (google?.maps?.event?.clearInstanceListeners) {
google.maps.event.clearInstanceListeners(m); if (typeof (window as any).google !== 'undefined' && (window as any).google.maps?.event?.clearInstanceListeners) {
(window as any).google.maps.event.clearInstanceListeners(m);
} }
} }
} catch (e) { } catch (e) {
@ -62,10 +59,8 @@ export const useMapState = () => {
}) })
markers.value = [] markers.value = []
// Eliminar polylines // Eliminar polylines
polylines.value.forEach(p => { polylines.value.forEach((p: any) => {
try { try {
if (p && typeof p.setMap === 'function') p.setMap(null) if (p && typeof p.setMap === 'function') p.setMap(null)
} catch (e) { } catch (e) {
@ -75,7 +70,7 @@ export const useMapState = () => {
polylines.value = [] polylines.value = []
// Cerrar y limpiar infoWindows // Cerrar y limpiar infoWindows
infoWindows.value.forEach(iw => { infoWindows.value.forEach((iw: any) => {
try { try {
if (iw && typeof iw.close === 'function') iw.close() if (iw && typeof iw.close === 'function') iw.close()
} catch (e) { } catch (e) {
@ -85,10 +80,10 @@ export const useMapState = () => {
infoWindows.value = [] infoWindows.value = []
// Eliminar circles // Eliminar circles
circles.value.forEach(c => { circles.value.forEach((c: any) => {
try { try {
if (c && typeof c.setMap === 'function') c.setMap(null) if (c && typeof c.setMap === 'function') c.setMap(null)
if (c && typeof (c as any).remove === 'function') (c as any).remove() if (c && typeof c.remove === 'function') c.remove()
} catch (e) { } catch (e) {
console.warn('Error limpiando circle', e) console.warn('Error limpiando circle', e)
} }

View File

@ -45,7 +45,6 @@ const unitFetchInterval = ref<any>(null);
const userCoords = ref<{ lat: number; lng: number } | null>(null); const userCoords = ref<{ lat: number; lng: number } | null>(null);
const optimalStopPulse = ref<any>(null); const optimalStopPulse = ref<any>(null);
const showRouteDropdown = ref(false); const showRouteDropdown = ref(false);
const routeCardRef = ref<HTMLElement | null>(null);
const wasSelectedFromMap = ref(false); const wasSelectedFromMap = ref(false);
const isInternalSelection = ref(false); const isInternalSelection = ref(false);
@ -129,6 +128,13 @@ async function claimPromo() {
} }
} }
function handleImageError(event: Event) {
const target = event.target as HTMLImageElement;
if (target) {
target.src = getImageUrl(null, 'coupon');
}
}
onMounted(async () => { onMounted(async () => {
const navbar = document.querySelector('#navbar-admin') ?? document.querySelector('nav') ?? document.querySelector('header'); const navbar = document.querySelector('#navbar-admin') ?? document.querySelector('nav') ?? document.querySelector('header');
if (navbar) { if (navbar) {
@ -468,6 +474,10 @@ function locateUser(): Promise<void> {
if (userMarker.value) { if (userMarker.value) {
if (typeof userMarker.value.setMap === 'function') { if (typeof userMarker.value.setMap === 'function') {
userMarker.value.setMap(null); userMarker.value.setMap(null);
// Clear listeners for the old marker
if (typeof (window as any).google !== 'undefined' && (window as any).google.maps?.event?.clearInstanceListeners) {
(window as any).google.maps.event.clearInstanceListeners(userMarker.value);
}
} }
} }
@ -525,6 +535,10 @@ async function highlightOptimalStopForRoute() {
// Añadir el PULSO NARANJA // Añadir el PULSO NARANJA
if (optimalStopPulse.value && typeof optimalStopPulse.value.setMap === 'function') { if (optimalStopPulse.value && typeof optimalStopPulse.value.setMap === 'function') {
optimalStopPulse.value.setMap(null); optimalStopPulse.value.setMap(null);
// Clear listeners for the old pulse marker
if (typeof (window as any).google !== 'undefined' && (window as any).google.maps?.event?.clearInstanceListeners) {
(window as any).google.maps.event.clearInstanceListeners(optimalStopPulse.value);
}
} }
optimalStopPulse.value = addHtmlMarker( optimalStopPulse.value = addHtmlMarker(
@ -548,10 +562,6 @@ async function highlightOptimalStopForRoute() {
} }
} }
// walking route functions removed
</script> </script>
<template> <template>
@ -754,7 +764,7 @@ async function highlightOptimalStopForRoute() {
<img <img
:src="getImageUrl(currentPromo.image_url, 'coupon')" :src="getImageUrl(currentPromo.image_url, 'coupon')"
class="sheet-img" class="sheet-img"
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'coupon')" @error="handleImageError"
/> />
<span v-if="currentPromo.discount_percentage" class="sheet-discount">-{{ currentPromo.discount_percentage }}%</span> <span v-if="currentPromo.discount_percentage" class="sheet-discount">-{{ currentPromo.discount_percentage }}%</span>
</div> </div>
@ -795,7 +805,7 @@ async function highlightOptimalStopForRoute() {
<img <img
:src="getImageUrl(selectedPromo.image_url, 'coupon')" :src="getImageUrl(selectedPromo.image_url, 'coupon')"
class="promo-img-modal" class="promo-img-modal"
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'coupon')" @error="handleImageError"
/> />
<div class="promo-badge-modal">PROMO</div> <div class="promo-badge-modal">PROMO</div>
</div> </div>

View File

@ -159,6 +159,8 @@ const stopWatch = watch(
onUnmounted(() => { onUnmounted(() => {
stopWatch() stopWatch()
document.removeEventListener('click', handleOutsideClick) document.removeEventListener('click', handleOutsideClick)
// Limpiamos la selección al salir para que no aparezca marcada en el buscador del mapa
routeStore.clearSelection()
}) })
</script> </script>