Decouple route selection: Map uses persistent store state, Schedules uses local state. Map selection only persists on Map banner.
This commit is contained in:
@ -12,6 +12,7 @@ export const useRouteStore = defineStore('route', () => {
|
|||||||
const isLoadingRoutes = ref(false)
|
const isLoadingRoutes = ref(false)
|
||||||
const isLoadingStops = ref(false)
|
const isLoadingStops = ref(false)
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
|
const wasSelectedFromMap = ref(false)
|
||||||
const lastFetched = ref<number>(0) // ⚡ CACHÉ ESTÁTICO RUTAS
|
const lastFetched = ref<number>(0) // ⚡ CACHÉ ESTÁTICO RUTAS
|
||||||
const stopsCache = ref<Map<string, { fetchedAt: number, stops: BusStop[] }>>(new Map()) // ⚡ CACHÉ ESTÁTICO PARADAS
|
const stopsCache = ref<Map<string, { fetchedAt: number, stops: BusStop[] }>>(new Map()) // ⚡ CACHÉ ESTÁTICO PARADAS
|
||||||
|
|
||||||
@ -78,6 +79,7 @@ export const useRouteStore = defineStore('route', () => {
|
|||||||
selectedRouteId.value = null
|
selectedRouteId.value = null
|
||||||
selectedRouteName.value = null
|
selectedRouteName.value = null
|
||||||
selectedRouteStops.value = []
|
selectedRouteStops.value = []
|
||||||
|
wasSelectedFromMap.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -88,6 +90,7 @@ export const useRouteStore = defineStore('route', () => {
|
|||||||
isLoadingRoutes,
|
isLoadingRoutes,
|
||||||
isLoadingStops,
|
isLoadingStops,
|
||||||
error,
|
error,
|
||||||
|
wasSelectedFromMap,
|
||||||
hasSelectedRoute,
|
hasSelectedRoute,
|
||||||
loadRoutes,
|
loadRoutes,
|
||||||
loadRouteStops,
|
loadRouteStops,
|
||||||
|
|||||||
@ -47,7 +47,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 wasSelectedFromMap = ref(false);
|
|
||||||
const isInternalSelection = ref(false);
|
const isInternalSelection = ref(false);
|
||||||
|
|
||||||
const alturaNavbar = ref(64);
|
const alturaNavbar = ref(64);
|
||||||
@ -238,7 +237,7 @@ async function initializeMap() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we have a selected route AND it was from map, show its stops
|
// If we have a selected route AND it was from map, show its stops
|
||||||
if (routeStore.selectedRouteId && routeStore.selectedRouteStops.length > 0 && wasSelectedFromMap.value) {
|
if (routeStore.selectedRouteId && routeStore.selectedRouteStops.length > 0 && routeStore.wasSelectedFromMap) {
|
||||||
updateMapMarkers();
|
updateMapMarkers();
|
||||||
} else {
|
} else {
|
||||||
// If no route or not from map, ensure it's clean (promos stay though)
|
// If no route or not from map, ensure it's clean (promos stay though)
|
||||||
@ -265,7 +264,7 @@ watch(
|
|||||||
// Si la selección no viene de dentro de MapView (selectRouteAndClose),
|
// Si la selección no viene de dentro de MapView (selectRouteAndClose),
|
||||||
// reseteamos el flag de origen Mapa para que el buscador no se "fije"
|
// reseteamos el flag de origen Mapa para que el buscador no se "fije"
|
||||||
if (!isInternalSelection.value) {
|
if (!isInternalSelection.value) {
|
||||||
wasSelectedFromMap.value = false;
|
routeStore.wasSelectedFromMap = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ALWAYS clear markers first when route changes - do this immediately
|
// ALWAYS clear markers first when route changes - do this immediately
|
||||||
@ -279,7 +278,7 @@ watch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (routeId) {
|
if (routeId) {
|
||||||
if (wasSelectedFromMap.value) {
|
if (routeStore.wasSelectedFromMap) {
|
||||||
// Only update map visuals if selection came from the Map search flow
|
// Only update map visuals if selection came from the Map search flow
|
||||||
await updateMapMarkers(true);
|
await updateMapMarkers(true);
|
||||||
} else {
|
} else {
|
||||||
@ -290,7 +289,7 @@ watch(
|
|||||||
} else {
|
} else {
|
||||||
// Clear markers when no route is selected
|
// Clear markers when no route is selected
|
||||||
lastProcessedRouteId.value = null;
|
lastProcessedRouteId.value = null;
|
||||||
wasSelectedFromMap.value = false; // Reset selection origin
|
routeStore.wasSelectedFromMap = false; // Reset selection origin
|
||||||
clearMapMarkers();
|
clearMapMarkers();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -409,7 +408,7 @@ async function updatePromoMarkers() {
|
|||||||
async function selectRouteAndClose(routeId: string, routeName: string) {
|
async function selectRouteAndClose(routeId: string, routeName: string) {
|
||||||
console.log(`🤖 JARVIS: Iniciando viaje hacia ${routeName}`);
|
console.log(`🤖 JARVIS: Iniciando viaje hacia ${routeName}`);
|
||||||
isInternalSelection.value = true;
|
isInternalSelection.value = true;
|
||||||
wasSelectedFromMap.value = true;
|
routeStore.wasSelectedFromMap = true;
|
||||||
await routeStore.selectRoute(routeId, routeName);
|
await routeStore.selectRoute(routeId, routeName);
|
||||||
showRouteDropdown.value = false;
|
showRouteDropdown.value = false;
|
||||||
showUberSearch.value = false; // Close the expanded search panel
|
showUberSearch.value = false; // Close the expanded search panel
|
||||||
@ -631,12 +630,12 @@ async function highlightOptimalStopForRoute() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Uber-like Search Interface -->
|
<!-- Uber-like Search Interface -->
|
||||||
<div class="uber-search-container" :class="{ 'compact-mode': routeStore.selectedRouteId && wasSelectedFromMap && !showUberSearch }">
|
<div class="uber-search-container" :class="{ 'compact-mode': routeStore.selectedRouteId && routeStore.wasSelectedFromMap && !showUberSearch }">
|
||||||
<!-- Floating Triggers -->
|
<!-- Floating Triggers -->
|
||||||
<div v-if="!showUberSearch" class="triggers-row">
|
<div v-if="!showUberSearch" class="triggers-row">
|
||||||
<!-- Shrunk Trigger (Icon only) - Only if selected from MAP -->
|
<!-- Shrunk Trigger (Icon only) - Only if selected from MAP -->
|
||||||
<div
|
<div
|
||||||
v-if="routeStore.selectedRouteId && wasSelectedFromMap"
|
v-if="routeStore.selectedRouteId && routeStore.wasSelectedFromMap"
|
||||||
class="uber-search-trigger circular"
|
class="uber-search-trigger circular"
|
||||||
@click="openUberSearch"
|
@click="openUberSearch"
|
||||||
:title="t('map.search')"
|
:title="t('map.search')"
|
||||||
@ -657,7 +656,7 @@ async function highlightOptimalStopForRoute() {
|
|||||||
<!-- Nuevo Banner de Parada Cercana Alineado (Redimensionado y con ETA) -->
|
<!-- Nuevo Banner de Parada Cercana Alineado (Redimensionado y con ETA) -->
|
||||||
<Transition name="banner-slide">
|
<Transition name="banner-slide">
|
||||||
<div
|
<div
|
||||||
v-if="paradaCercana && routeStore.selectedRouteId && !showETACard && !isBannerClosing && wasSelectedFromMap"
|
v-if="paradaCercana && routeStore.selectedRouteId && !showETACard && !isBannerClosing && routeStore.wasSelectedFromMap"
|
||||||
class="best-stop-banner-compact"
|
class="best-stop-banner-compact"
|
||||||
>
|
>
|
||||||
<div class="banner-icon-bg">
|
<div class="banner-icon-bg">
|
||||||
@ -712,7 +711,7 @@ async function highlightOptimalStopForRoute() {
|
|||||||
v-for="route in routeStore.allRoutes"
|
v-for="route in routeStore.allRoutes"
|
||||||
:key="route.id"
|
:key="route.id"
|
||||||
class="uber-result-item"
|
class="uber-result-item"
|
||||||
:class="{ 'selected-route': route.id === routeStore.selectedRouteId && wasSelectedFromMap }"
|
:class="{ 'selected-route': route.id === routeStore.selectedRouteId && routeStore.wasSelectedFromMap }"
|
||||||
@click="selectRouteAndClose(route.id, route.name)"
|
@click="selectRouteAndClose(route.id, route.name)"
|
||||||
>
|
>
|
||||||
<div class="result-icon">
|
<div class="result-icon">
|
||||||
|
|||||||
@ -15,6 +15,11 @@ const routeStore = useRouteStore()
|
|||||||
const dropdownOpen = ref(false)
|
const dropdownOpen = ref(false)
|
||||||
const dayFilter = ref<'all' | 'today' | 'tomorrow'>('today')
|
const dayFilter = ref<'all' | 'today' | 'tomorrow'>('today')
|
||||||
|
|
||||||
|
// SIBU | Estado local para independizar el selector de horarios del mapa
|
||||||
|
const localSelectedRouteId = ref<string | null>(null)
|
||||||
|
const localSelectedRouteName = ref<string | null>(null)
|
||||||
|
const hasLocalSelection = computed(() => localSelectedRouteId.value !== null)
|
||||||
|
|
||||||
// ── Tipos de día
|
// ── Tipos de día
|
||||||
const DAY_TYPES: Record<string, string> = {
|
const DAY_TYPES: Record<string, string> = {
|
||||||
'weekday': t('schedules.types.weekday'),
|
'weekday': t('schedules.types.weekday'),
|
||||||
@ -123,7 +128,10 @@ function pickRoute(id: string, name: string) {
|
|||||||
item_id: name,
|
item_id: name,
|
||||||
properties: { route_id: id }
|
properties: { route_id: id }
|
||||||
})
|
})
|
||||||
routeStore.selectRoute(id, name)
|
|
||||||
|
// SIBU | Solo actualizamos estado local (Independiente del mapa)
|
||||||
|
localSelectedRouteId.value = id
|
||||||
|
localSelectedRouteName.value = name
|
||||||
scheduleStore.loadRouteSchedules(id)
|
scheduleStore.loadRouteSchedules(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,8 +154,13 @@ onMounted(async () => {
|
|||||||
const found = routeStore.allRoutes.find(r => r.id === queryRouteId)
|
const found = routeStore.allRoutes.find(r => r.id === queryRouteId)
|
||||||
if (found) pickRoute(found.id, found.name)
|
if (found) pickRoute(found.id, found.name)
|
||||||
} else if (routeStore.selectedRouteId) {
|
} else if (routeStore.selectedRouteId) {
|
||||||
// SIBU | Si venimos de otra vista (Mapa) con una ruta, cargamos sus horarios
|
// SIBU | Inicializamos con la ruta del mapa, pero a partir de aquí son independientes
|
||||||
scheduleStore.loadRouteSchedules(routeStore.selectedRouteId)
|
const mapRoute = routeStore.allRoutes.find(r => r.id === routeStore.selectedRouteId)
|
||||||
|
if (mapRoute) {
|
||||||
|
localSelectedRouteId.value = mapRoute.id
|
||||||
|
localSelectedRouteName.value = mapRoute.name
|
||||||
|
scheduleStore.loadRouteSchedules(mapRoute.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -173,7 +186,7 @@ onUnmounted(() => {
|
|||||||
<!-- ── HEADER ── -->
|
<!-- ── HEADER ── -->
|
||||||
<header class="sch-header">
|
<header class="sch-header">
|
||||||
<h1 class="sch-title">{{ t('schedules.title') }}</h1>
|
<h1 class="sch-title">{{ t('schedules.title') }}</h1>
|
||||||
<p class="sch-subtitle" v-if="!routeStore.hasSelectedRoute">
|
<p class="sch-subtitle" v-if="!hasLocalSelection">
|
||||||
{{ t('schedules.selectRoute') }}
|
{{ t('schedules.selectRoute') }}
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
@ -185,7 +198,7 @@ onUnmounted(() => {
|
|||||||
<!-- Botón disparador -->
|
<!-- Botón disparador -->
|
||||||
<button
|
<button
|
||||||
class="selector-trigger"
|
class="selector-trigger"
|
||||||
:class="{ 'selector-trigger--active': routeStore.hasSelectedRoute }"
|
:class="{ 'selector-trigger--active': hasLocalSelection }"
|
||||||
@click.stop="dropdownOpen = !dropdownOpen"
|
@click.stop="dropdownOpen = !dropdownOpen"
|
||||||
:disabled="routeStore.isLoadingRoutes"
|
:disabled="routeStore.isLoadingRoutes"
|
||||||
>
|
>
|
||||||
@ -193,8 +206,8 @@ onUnmounted(() => {
|
|||||||
<div class="trigger-icon">
|
<div class="trigger-icon">
|
||||||
<span class="material-icons">directions_bus</span>
|
<span class="material-icons">directions_bus</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="trigger-text" :class="{ 'trigger-text--selected': routeStore.hasSelectedRoute }">
|
<span class="trigger-text" :class="{ 'trigger-text--selected': hasLocalSelection }">
|
||||||
{{ routeStore.isLoadingRoutes ? t('schedules.loadingRoutes') : (routeStore.selectedRouteName || t('schedules.placeholder')) }}
|
{{ routeStore.isLoadingRoutes ? t('schedules.loadingRoutes') : (localSelectedRouteName || t('schedules.placeholder')) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="material-icons trigger-arrow" :class="{ 'trigger-arrow--up': dropdownOpen }">
|
<span class="material-icons trigger-arrow" :class="{ 'trigger-arrow--up': dropdownOpen }">
|
||||||
@ -217,12 +230,12 @@ onUnmounted(() => {
|
|||||||
v-for="r in routeStore.allRoutes"
|
v-for="r in routeStore.allRoutes"
|
||||||
:key="r.id"
|
:key="r.id"
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
:class="{ 'dropdown-item--active': r.id === routeStore.selectedRouteId }"
|
:class="{ 'dropdown-item--active': r.id === localSelectedRouteId }"
|
||||||
@click.stop="pickRoute(r.id, r.name)"
|
@click.stop="pickRoute(r.id, r.name)"
|
||||||
>
|
>
|
||||||
<span class="material-icons dropdown-item-icon">directions_bus</span>
|
<span class="material-icons dropdown-item-icon">directions_bus</span>
|
||||||
<span class="dropdown-item-name">{{ r.name }}</span>
|
<span class="dropdown-item-name">{{ r.name }}</span>
|
||||||
<span v-if="r.id === routeStore.selectedRouteId" class="material-icons dropdown-item-check">check</span>
|
<span v-if="r.id === localSelectedRouteId" class="material-icons dropdown-item-check">check</span>
|
||||||
</button>
|
</button>
|
||||||
<div v-if="routeStore.allRoutes.length === 0" class="dropdown-empty">
|
<div v-if="routeStore.allRoutes.length === 0" class="dropdown-empty">
|
||||||
{{ t('schedules.noRoutesAvailable') }}
|
{{ t('schedules.noRoutesAvailable') }}
|
||||||
@ -234,7 +247,7 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── ESTADO VACÍO — sin ruta seleccionada ── -->
|
<!-- ── ESTADO VACÍO — sin ruta seleccionada ── -->
|
||||||
<div v-if="!routeStore.hasSelectedRoute" class="empty-state">
|
<div v-if="!hasLocalSelection" class="empty-state">
|
||||||
<div class="empty-illustration">
|
<div class="empty-illustration">
|
||||||
<svg width="80" height="80" viewBox="0 0 80 80" fill="none">
|
<svg width="80" height="80" viewBox="0 0 80 80" fill="none">
|
||||||
<circle cx="40" cy="38" r="24" stroke="var(--active-color)" stroke-width="3.5" stroke-linecap="round" stroke-dasharray="6 4"/>
|
<circle cx="40" cy="38" r="24" stroke="var(--active-color)" stroke-width="3.5" stroke-linecap="round" stroke-dasharray="6 4"/>
|
||||||
@ -304,7 +317,7 @@ onUnmounted(() => {
|
|||||||
</span>
|
</span>
|
||||||
<span v-if="i === 0 && getBusStatus(schedule.departure_time) === 'departing'" class="departing-pulse">{{ t('schedules.departing') || 'SALIENDO' }}</span>
|
<span v-if="i === 0 && getBusStatus(schedule.departure_time) === 'departing'" class="departing-pulse">{{ t('schedules.departing') || 'SALIENDO' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="route-name">{{ routeStore.selectedRouteName }}</p>
|
<p class="route-name">{{ localSelectedRouteName }}</p>
|
||||||
<p class="card-detail">
|
<p class="card-detail">
|
||||||
<span class="material-icons card-detail-icon">schedule</span>
|
<span class="material-icons card-detail-icon">schedule</span>
|
||||||
{{ DAY_TYPES[schedule.schedule_type] || schedule.schedule_type }}
|
{{ DAY_TYPES[schedule.schedule_type] || schedule.schedule_type }}
|
||||||
|
|||||||
Reference in New Issue
Block a user