diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b61501c..5863d61 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ "html2canvas": "^1.4.1", "jspdf": "^4.1.0", "pinia": "^3.0.4", + "pinia-plugin-persistedstate": "^4.7.1", "vue": "^3.5.24", "vue-chartjs": "^5.3.3", "vue-i18n": "^9.14.5", @@ -4602,6 +4603,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -6680,6 +6687,7 @@ "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", "license": "MIT", + "peer": true, "dependencies": { "@vue/devtools-api": "^7.7.7" }, @@ -6696,6 +6704,31 @@ } } }, + "node_modules/pinia-plugin-persistedstate": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.7.1.tgz", + "integrity": "sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==", + "license": "MIT", + "dependencies": { + "defu": "^6.1.4" + }, + "peerDependencies": { + "@nuxt/kit": ">=3.0.0", + "@pinia/nuxt": ">=0.10.0", + "pinia": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@pinia/nuxt": { + "optional": true + }, + "pinia": { + "optional": true + } + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 05e225e..f5f2174 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,6 +20,7 @@ "html2canvas": "^1.4.1", "jspdf": "^4.1.0", "pinia": "^3.0.4", + "pinia-plugin-persistedstate": "^4.7.1", "vue": "^3.5.24", "vue-chartjs": "^5.3.3", "vue-i18n": "^9.14.5", diff --git a/frontend/src/App.vue b/frontend/src/App.vue index c57ee7d..a1b04df 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -85,13 +85,13 @@ html.light-theme { --header-bg: rgba(255, 255, 255, 0.85); --header-text: #0f172a; --card-bg: rgba(255, 255, 255, 0.95); - --hover-bg: rgba(37, 99, 235, 0.05); /* Soft Blue hover */ + --hover-bg: rgba(254, 231, 21, 0.08); --glass-bg: rgba(255, 255, 255, 0.8); - --glass-border: rgba(37, 99, 235, 0.1); + --glass-border: rgba(254, 231, 21, 0.2); --shadow: 0 15px 40px rgba(15, 23, 42, 0.1); - --active-bg: rgba(37, 99, 235, 0.1); - --active-color: #2563eb; /* Premium Nexus Blue */ - --accent-color: #2563eb; + --active-bg: rgba(254, 231, 21, 0.15); + --active-color: #fee715; + --accent-color: #fee715; } html, diff --git a/frontend/src/i18n/locales/es.json b/frontend/src/i18n/locales/es.json index 7070047..c81c99f 100644 --- a/frontend/src/i18n/locales/es.json +++ b/frontend/src/i18n/locales/es.json @@ -167,7 +167,7 @@ "englishSpeakers": "Conductores bilingües", "callNow": "Llamar ahora", "englishLabel": "INGLÉS", - "allZones": "Todas las zonas", + "allZones": "Todas las áreas", "dayShift": "Día", "afternoonShift": "Tarde", "nightShift": "Noche", diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 466f573..69d29fe 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,5 +1,6 @@ import { createApp } from 'vue' import { createPinia } from 'pinia' +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import router from './router' import i18n from './i18n' import './style.css' @@ -16,6 +17,7 @@ if ('serviceWorker' in navigator) { const app = createApp(App) const pinia = createPinia() +pinia.use(piniaPluginPersistedstate) app.use(pinia) app.use(router) diff --git a/frontend/src/stores/coupon.ts b/frontend/src/stores/coupon.ts index 8b1d770..3dd4b96 100644 --- a/frontend/src/stores/coupon.ts +++ b/frontend/src/stores/coupon.ts @@ -63,4 +63,8 @@ export const useCouponStore = defineStore('coupon', () => { claimCoupon, setFilters, } +}, { + persist: { + pick: ['coupons', 'filters'] + } }) diff --git a/frontend/src/stores/map.ts b/frontend/src/stores/map.ts index 5dc0bea..e178c45 100644 --- a/frontend/src/stores/map.ts +++ b/frontend/src/stores/map.ts @@ -35,5 +35,9 @@ export const useMapStore = defineStore('map', () => { setCenter, setZoom, } +}, { + persist: { + pick: ['center', 'zoom'] + } }) diff --git a/frontend/src/stores/route.ts b/frontend/src/stores/route.ts index 0dd3a89..fe90f6a 100644 --- a/frontend/src/stores/route.ts +++ b/frontend/src/stores/route.ts @@ -80,6 +80,11 @@ export const useRouteStore = defineStore('route', () => { selectedRouteStops.value = [] // Limpia para forzar recarga atómica en la vista } + // Watcher manual para wasSelectedFromMap ya que se cambia en varios sitios + function setWasSelectedFromMap(val: boolean) { + wasSelectedFromMap.value = val + } + function clearSelection() { selectedRouteId.value = null selectedRouteName.value = null @@ -100,7 +105,12 @@ export const useRouteStore = defineStore('route', () => { loadRoutes, loadRouteStops, selectRoute, + setWasSelectedFromMap, clearSelection, } +}, { + persist: { + pick: ['selectedRouteId', 'selectedRouteName', 'wasSelectedFromMap', 'allRoutes'] + } }) diff --git a/frontend/src/stores/shuttle.ts b/frontend/src/stores/shuttle.ts index 3077b7a..11cf0ef 100644 --- a/frontend/src/stores/shuttle.ts +++ b/frontend/src/stores/shuttle.ts @@ -33,4 +33,8 @@ export const useShuttleStore = defineStore('shuttle', () => { filters, loadShuttles, } +}, { + persist: { + pick: ['shuttles'] + } }) diff --git a/frontend/src/stores/taxi.ts b/frontend/src/stores/taxi.ts index 3977ca3..ebbdcdb 100644 --- a/frontend/src/stores/taxi.ts +++ b/frontend/src/stores/taxi.ts @@ -39,5 +39,9 @@ export const useTaxiStore = defineStore('taxi', () => { loadTaxis, setFilters, } +}, { + persist: { + pick: ['taxis'] + } }) diff --git a/frontend/src/views/BusinessDetailsView.vue b/frontend/src/views/BusinessDetailsView.vue index 4505a52..151bd95 100644 --- a/frontend/src/views/BusinessDetailsView.vue +++ b/frontend/src/views/BusinessDetailsView.vue @@ -7,6 +7,7 @@ import { couponsService } from '@/services/couponsService' import type { Business, Coupon } from '@/types' import FavoriteButton from '@/components/FavoriteButton.vue' import { getImageUrl as utilGetImageUrl } from '@/utils/imageUrl' +import { analyticsService } from '@/services/analyticsService' const route = useRoute() const router = useRouter() @@ -14,32 +15,35 @@ const { t } = useI18n() const business = ref(null) const coupons = ref([]) const isLoading = ref(true) +const fetchError = ref(false) -import { analyticsService } from '@/services/analyticsService' - -onMounted(async () => { +async function fetchData() { const id = route.params.id as string + if (!id) return + isLoading.value = true + fetchError.value = false try { const [bizData, allCoupons] = await Promise.all([ businessService.getBusiness(id), couponsService.getAllCoupons({ active_only: true }) ]) business.value = bizData - // Filter coupons for this business coupons.value = allCoupons.filter(c => c.business_id === id) - analyticsService.logEvent({ - event_name: 'screen_view', - screen_name: 'BusinessDetails', - item_id: bizData.name, - properties: { business_id: id } + event_name: 'screen_view', + screen_name: 'BusinessDetails', + item_id: bizData.name, + properties: { business_id: id } }) } catch (e) { console.error('Failed to load business details', e) + fetchError.value = true } finally { isLoading.value = false } -}) +} + +onMounted(fetchData) function getImageUrl(path: string | null | undefined) { return utilGetImageUrl(path, 'business') @@ -54,463 +58,619 @@ function openDirections() { window.open(`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(business.value.address + ' ' + (business.value.area || ''))}`, '_blank') } } - - diff --git a/frontend/src/views/MapView.vue b/frontend/src/views/MapView.vue index a2d8967..a8841df 100644 --- a/frontend/src/views/MapView.vue +++ b/frontend/src/views/MapView.vue @@ -100,7 +100,7 @@ async function animateAndReload() { isBannerClosing.value = true; // 🔥 CRÍTICO - routeStore.wasSelectedFromMap = false; + routeStore.setWasSelectedFromMap(false); clearMapMarkers(); limpiarCaminata(); @@ -160,14 +160,26 @@ onMounted(async () => { unitFetchInterval.value = setInterval(updateActiveUnits, 15000); updateActiveUnits(); startCarousel(); + + // 🛰️ RESIZE FIX: Trigger map resize when app becomes visible again + document.addEventListener('visibilitychange', handleVisibilityChange); }); 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 }); +function handleVisibilityChange() { + if (document.visibilityState === 'visible' && map.value) { + console.log('SIBU | App visible, redimensionando mapa...'); + google.maps.event.trigger(map.value, 'resize'); + updateActiveUnits(); + } +} + async function initializeMap() { await nextTick(); await new Promise(resolve => setTimeout(resolve, 100)); @@ -330,7 +342,7 @@ function selectRouteAndClose(route: any) { } showUberSearch.value = false; - routeStore.wasSelectedFromMap = true; + routeStore.setWasSelectedFromMap(true); routeStore.selectRoute(route.id, route.name); } diff --git a/frontend/src/views/transporte/TaxisLocales.vue b/frontend/src/views/transporte/TaxisLocales.vue index d7c7edf..f6edd01 100644 --- a/frontend/src/views/transporte/TaxisLocales.vue +++ b/frontend/src/views/transporte/TaxisLocales.vue @@ -6,6 +6,7 @@ import { analyticsService } from '@/services/analyticsService' import type { Taxi } from '@/types' import FavoriteButton from '@/components/FavoriteButton.vue' import { getImageUrl } from '@/utils/imageUrl' +import AuthGuard from '@/components/common/AuthGuard.vue' const { t } = useI18n() const taxiStore = useTaxiStore() @@ -117,8 +118,12 @@ function getShiftLabel(shift: string) { -
-
+ +
+
@@ -186,7 +191,8 @@ function getShiftLabel(shift: string) { no_accounts

{{ t('taxi.noTaxisAvailable') }}

-
+
+