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">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted } from 'vue'
|
import { computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { RouterView, useRoute } from "vue-router";
|
import { RouterView, useRoute } from "vue-router";
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import MainLayout from "./components/layouts/MainLayout.vue";
|
import MainLayout from "./components/layouts/MainLayout.vue";
|
||||||
@ -20,6 +20,24 @@ const isAuthScreen = computed(() => {
|
|||||||
return route.path === '/login' || route.path === '/register' || route.name === 'auth'
|
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(() => {
|
onMounted(() => {
|
||||||
themeStore.applyTheme()
|
themeStore.applyTheme()
|
||||||
analyticsService.logEvent({
|
analyticsService.logEvent({
|
||||||
@ -30,6 +48,11 @@ onMounted(() => {
|
|||||||
if (authStore.isAuthenticated) {
|
if (authStore.isAuthenticated) {
|
||||||
favoritesStore.loadFavorites()
|
favoritesStore.loadFavorites()
|
||||||
}
|
}
|
||||||
|
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||||
import { businessService } from '@/services/businessService'
|
import { businessService } from '@/services/businessService'
|
||||||
import type { Business } from '@/types'
|
import type { Business } from '@/types'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
@ -54,9 +54,18 @@ async function loadBusinesses() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleRefocus() {
|
||||||
|
loadBusinesses()
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'Discover' })
|
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'Discover' })
|
||||||
loadBusinesses()
|
loadBusinesses()
|
||||||
|
window.addEventListener('app-refocus', handleRefocus)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('app-refocus', handleRefocus)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ── Computados
|
// ── Computados
|
||||||
|
|||||||
@ -129,14 +129,27 @@ function closePromoModal() {
|
|||||||
selectedPromo.value = null;
|
selectedPromo.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map initialization & Lifecycle
|
async function fetchData() {
|
||||||
onMounted(async () => {
|
|
||||||
analyticsService.logEvent({ event_name: 'screen_view', properties: { screen_name: 'Map' } })
|
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
routeStore.loadRoutes(),
|
routeStore.loadRoutes(),
|
||||||
couponStore.loadCoupons({ active_only: true })
|
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;
|
const queryRouteId = router.currentRoute.value.query.routeId as string;
|
||||||
if (queryRouteId && queryRouteId !== routeStore.selectedRouteId) {
|
if (queryRouteId && queryRouteId !== routeStore.selectedRouteId) {
|
||||||
@ -158,7 +171,6 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unitFetchInterval.value = setInterval(updateActiveUnits, 15000);
|
unitFetchInterval.value = setInterval(updateActiveUnits, 15000);
|
||||||
updateActiveUnits();
|
|
||||||
startCarousel();
|
startCarousel();
|
||||||
|
|
||||||
// 🛰️ RESIZE FIX: Trigger map resize when app becomes visible again
|
// 🛰️ RESIZE FIX: Trigger map resize when app becomes visible again
|
||||||
@ -169,7 +181,7 @@ onUnmounted(() => {
|
|||||||
if (unitFetchInterval.value) clearInterval(unitFetchInterval.value);
|
if (unitFetchInterval.value) clearInterval(unitFetchInterval.value);
|
||||||
if (carouselTimer.value) clearInterval(carouselTimer.value);
|
if (carouselTimer.value) clearInterval(carouselTimer.value);
|
||||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
// NOTA: No llamamos a clearMapMarkers() para mantener la ruta si el usuario vuelve
|
window.removeEventListener('app-refocus', handleRefocus);
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleVisibilityChange() {
|
function handleVisibilityChange() {
|
||||||
|
|||||||
@ -144,8 +144,21 @@ function handleOutsideClick(e: MouseEvent) {
|
|||||||
if (!target.closest('.route-selector')) dropdownOpen.value = false
|
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 () => {
|
onMounted(async () => {
|
||||||
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'Schedules' })
|
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'Schedules' })
|
||||||
|
window.addEventListener('app-refocus', handleRefocus)
|
||||||
|
|
||||||
await routeStore.loadRoutes()
|
await routeStore.loadRoutes()
|
||||||
document.addEventListener('click', handleOutsideClick)
|
document.addEventListener('click', handleOutsideClick)
|
||||||
|
|
||||||
@ -177,6 +190,7 @@ const stopWatch = watch(
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
stopWatch()
|
stopWatch()
|
||||||
document.removeEventListener('click', handleOutsideClick)
|
document.removeEventListener('click', handleOutsideClick)
|
||||||
|
window.removeEventListener('app-refocus', handleRefocus)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref, computed } from 'vue'
|
import { onMounted, onUnmounted, ref, computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useTaxiStore } from '@/stores/taxi'
|
import { useTaxiStore } from '@/stores/taxi'
|
||||||
import { analyticsService } from '@/services/analyticsService'
|
import { analyticsService } from '@/services/analyticsService'
|
||||||
@ -18,13 +18,26 @@ const onlyEnglish = ref(false)
|
|||||||
const corregimientos = ['all', 'Boquete', 'David - Boquete', 'Boquete - David', 'Aeropuerto - Boquete']
|
const corregimientos = ['all', 'Boquete', 'David - Boquete', 'Boquete - David', 'Aeropuerto - Boquete']
|
||||||
const shifts = ['all', 'dia', 'tarde', 'noche']
|
const shifts = ['all', 'dia', 'tarde', 'noche']
|
||||||
|
|
||||||
|
function fetchData() {
|
||||||
|
taxiStore.loadTaxis()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRefocus() {
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'TaxisLocales' })
|
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'TaxisLocales' })
|
||||||
|
window.addEventListener('app-refocus', handleRefocus)
|
||||||
if(taxiStore.taxis.length === 0) {
|
if(taxiStore.taxis.length === 0) {
|
||||||
await taxiStore.loadTaxis()
|
await fetchData()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('app-refocus', handleRefocus)
|
||||||
|
})
|
||||||
|
|
||||||
const filteredTaxis = computed(() => {
|
const filteredTaxis = computed(() => {
|
||||||
return taxiStore.taxis.filter(taxi => {
|
return taxiStore.taxis.filter(taxi => {
|
||||||
const matchesZone = selectedZone.value === 'all' || taxi.corregimiento === selectedZone.value
|
const matchesZone = selectedZone.value === 'all' || taxi.corregimiento === selectedZone.value
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref, computed } from 'vue'
|
import { onMounted, onUnmounted, ref, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useShuttleStore } from '@/stores/shuttle'
|
import { useShuttleStore } from '@/stores/shuttle'
|
||||||
@ -29,12 +29,25 @@ const verDetalle = (shuttleId: string) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fetchData() {
|
||||||
|
shuttleStore.loadShuttles()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRefocus() {
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'ViajesTuristicos' })
|
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'ViajesTuristicos' })
|
||||||
|
window.addEventListener('app-refocus', handleRefocus)
|
||||||
if(shuttleStore.shuttles.length === 0) {
|
if(shuttleStore.shuttles.length === 0) {
|
||||||
await shuttleStore.loadShuttles()
|
await fetchData()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('app-refocus', handleRefocus)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
Reference in New Issue
Block a user