fix: implement global app-refocus listener and data recovery pattern in critical views to prevent infinite loading after app suspension
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { computed, onMounted, onUnmounted } from 'vue'
|
||||
import { RouterView, useRoute } from "vue-router";
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import MainLayout from "./components/layouts/MainLayout.vue";
|
||||
@ -20,6 +20,24 @@ const isAuthScreen = computed(() => {
|
||||
return route.path === '/login' || route.path === '/register' || route.name === 'auth'
|
||||
})
|
||||
|
||||
let lastHiddenAt: number | null = null
|
||||
|
||||
function handleVisibilityChange() {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
lastHiddenAt = Date.now()
|
||||
return
|
||||
}
|
||||
if (document.visibilityState === 'visible' && lastHiddenAt !== null) {
|
||||
const secondsAway = (Date.now() - lastHiddenAt) / 1000
|
||||
// Si pasaron más de 3 segundos, asumimos que el proceso pudo ser suspendido
|
||||
if (secondsAway > 3) {
|
||||
console.log(`SIBU | App recuperada tras ${secondsAway.toFixed(1)}s. Disparando refocus...`)
|
||||
window.dispatchEvent(new CustomEvent('app-refocus'))
|
||||
}
|
||||
lastHiddenAt = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
themeStore.applyTheme()
|
||||
analyticsService.logEvent({
|
||||
@ -30,6 +48,11 @@ onMounted(() => {
|
||||
if (authStore.isAuthenticated) {
|
||||
favoritesStore.loadFavorites()
|
||||
}
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
import { businessService } from '@/services/businessService'
|
||||
import type { Business } from '@/types'
|
||||
import { useRouter } from 'vue-router'
|
||||
@ -54,9 +54,18 @@ async function loadBusinesses() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleRefocus() {
|
||||
loadBusinesses()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'Discover' })
|
||||
loadBusinesses()
|
||||
window.addEventListener('app-refocus', handleRefocus)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('app-refocus', handleRefocus)
|
||||
})
|
||||
|
||||
// ── Computados
|
||||
|
||||
@ -129,14 +129,27 @@ function closePromoModal() {
|
||||
selectedPromo.value = null;
|
||||
}
|
||||
|
||||
// Map initialization & Lifecycle
|
||||
onMounted(async () => {
|
||||
analyticsService.logEvent({ event_name: 'screen_view', properties: { screen_name: 'Map' } })
|
||||
|
||||
async function fetchData() {
|
||||
await Promise.all([
|
||||
routeStore.loadRoutes(),
|
||||
couponStore.loadCoupons({ active_only: true })
|
||||
]);
|
||||
updateActiveUnits();
|
||||
}
|
||||
|
||||
function handleRefocus() {
|
||||
fetchData();
|
||||
if (map.value) {
|
||||
google.maps.event.trigger(map.value, 'resize');
|
||||
}
|
||||
}
|
||||
|
||||
// Map initialization & Lifecycle
|
||||
onMounted(async () => {
|
||||
analyticsService.logEvent({ event_name: 'screen_view', properties: { screen_name: 'Map' } })
|
||||
window.addEventListener('app-refocus', handleRefocus);
|
||||
|
||||
await fetchData();
|
||||
|
||||
const queryRouteId = router.currentRoute.value.query.routeId as string;
|
||||
if (queryRouteId && queryRouteId !== routeStore.selectedRouteId) {
|
||||
@ -158,7 +171,6 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
unitFetchInterval.value = setInterval(updateActiveUnits, 15000);
|
||||
updateActiveUnits();
|
||||
startCarousel();
|
||||
|
||||
// 🛰️ RESIZE FIX: Trigger map resize when app becomes visible again
|
||||
@ -169,7 +181,7 @@ onUnmounted(() => {
|
||||
if (unitFetchInterval.value) clearInterval(unitFetchInterval.value);
|
||||
if (carouselTimer.value) clearInterval(carouselTimer.value);
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
// NOTA: No llamamos a clearMapMarkers() para mantener la ruta si el usuario vuelve
|
||||
window.removeEventListener('app-refocus', handleRefocus);
|
||||
});
|
||||
|
||||
function handleVisibilityChange() {
|
||||
|
||||
@ -144,8 +144,21 @@ function handleOutsideClick(e: MouseEvent) {
|
||||
if (!target.closest('.route-selector')) dropdownOpen.value = false
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
await routeStore.loadRoutes()
|
||||
if (localSelectedRouteId.value) {
|
||||
await scheduleStore.loadRouteSchedules(localSelectedRouteId.value)
|
||||
}
|
||||
}
|
||||
|
||||
function handleRefocus() {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'Schedules' })
|
||||
window.addEventListener('app-refocus', handleRefocus)
|
||||
|
||||
await routeStore.loadRoutes()
|
||||
document.addEventListener('click', handleOutsideClick)
|
||||
|
||||
@ -177,6 +190,7 @@ const stopWatch = watch(
|
||||
onUnmounted(() => {
|
||||
stopWatch()
|
||||
document.removeEventListener('click', handleOutsideClick)
|
||||
window.removeEventListener('app-refocus', handleRefocus)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { onMounted, onUnmounted, ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useTaxiStore } from '@/stores/taxi'
|
||||
import { analyticsService } from '@/services/analyticsService'
|
||||
@ -18,13 +18,26 @@ const onlyEnglish = ref(false)
|
||||
const corregimientos = ['all', 'Boquete', 'David - Boquete', 'Boquete - David', 'Aeropuerto - Boquete']
|
||||
const shifts = ['all', 'dia', 'tarde', 'noche']
|
||||
|
||||
function fetchData() {
|
||||
taxiStore.loadTaxis()
|
||||
}
|
||||
|
||||
function handleRefocus() {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'TaxisLocales' })
|
||||
window.addEventListener('app-refocus', handleRefocus)
|
||||
if(taxiStore.taxis.length === 0) {
|
||||
await taxiStore.loadTaxis()
|
||||
await fetchData()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('app-refocus', handleRefocus)
|
||||
})
|
||||
|
||||
const filteredTaxis = computed(() => {
|
||||
return taxiStore.taxis.filter(taxi => {
|
||||
const matchesZone = selectedZone.value === 'all' || taxi.corregimiento === selectedZone.value
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { onMounted, onUnmounted, ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useShuttleStore } from '@/stores/shuttle'
|
||||
@ -29,12 +29,25 @@ const verDetalle = (shuttleId: string) => {
|
||||
})
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
shuttleStore.loadShuttles()
|
||||
}
|
||||
|
||||
function handleRefocus() {
|
||||
fetchData()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'ViajesTuristicos' })
|
||||
window.addEventListener('app-refocus', handleRefocus)
|
||||
if(shuttleStore.shuttles.length === 0) {
|
||||
await shuttleStore.loadShuttles()
|
||||
await fetchData()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('app-refocus', handleRefocus)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
Reference in New Issue
Block a user