From ae55f0acbe281d6311a537dc24088a3166ea840f Mon Sep 17 00:00:00 2001 From: Hanzo_dev <2002samudiojohan@gmail.com> Date: Mon, 2 Mar 2026 15:38:52 -0500 Subject: [PATCH] feat(map): implement navigation phases and premium ETACard design --- frontend/src/components/map/ETACard.vue | 17 +++++++--------- frontend/src/views/MapView.vue | 27 +++++++++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/map/ETACard.vue b/frontend/src/components/map/ETACard.vue index 5aa5148..5f603f2 100644 --- a/frontend/src/components/map/ETACard.vue +++ b/frontend/src/components/map/ETACard.vue @@ -2,7 +2,7 @@
-
+
(); const emit = defineEmits<{ - (e: 'close'): void; + (e: 'close'): void; // drag hacia abajo → pasar a fase navigating (e: 'refresh'): void; }>(); @@ -167,6 +167,8 @@ const isDragging = ref(false); const startY = ref(0); const DISMISS_THRESHOLD = 0.30; // 30% de la altura = cerrar +let intervalId: number | undefined; + function onTouchStart(e: TouchEvent) { startY.value = e.touches[0]?.clientY ?? 0; isDragging.value = true; @@ -191,18 +193,13 @@ function onTouchEnd() { const draggedRatio = dragY.value / sheetHeight; if (draggedRatio >= DISMISS_THRESHOLD) { - // Arrastró suficiente → cerrar - emit('close'); + handleDismiss(); } - // Siempre resetear posición (snap back o después de cerrar) dragY.value = 0; } -// ── AUTO REFRESH ───────────────────────────────────── -let intervalId: number; - -function closeCard() { - emit('close'); +function handleDismiss() { + emit('close'); // 'close' en ETACard = drag dismiss = pasar a fase 'navigating' } onMounted(() => { diff --git a/frontend/src/views/MapView.vue b/frontend/src/views/MapView.vue index 9a9bbb1..9407d5d 100644 --- a/frontend/src/views/MapView.vue +++ b/frontend/src/views/MapView.vue @@ -41,6 +41,7 @@ const { procesarSeleccionDeRuta } = useFlujoPrincipal(); const { limpiarMapa: limpiarTodoCentralizado } = useMapState(); const showETACard = ref(false); +const routePhase = ref<'idle' | 'eta' | 'navigating'>('idle'); // PERFORMANCE FIX: Use shallowRef for heavy object arrays and Map objects const promoMarkers = shallowRef([]); @@ -104,6 +105,7 @@ async function animateAndReload() { clearMapMarkers(); limpiarCaminata(); showETACard.value = false; + routePhase.value = 'idle'; // Recentrar en el usuario si está disponible (soft-reset) if (userCoords.value) { @@ -412,25 +414,35 @@ watch([etaCargando, () => busesActivos.value.length], ([loading, count]) => { }); */ +// Cuando el usuario hace drag-down en el ETACard → pasar a fase 'navigating' +// Esto muestra el ArrivalBanner arriba y las paradas quedan en el mapa +function handleETACardDismiss() { + showETACard.value = false; + routePhase.value = 'navigating'; +} + function handleBannerClick() { - // Solo abrir el ETACard, NO borrar paradas ni ruta + // Al tocar el banner superior, volver a mostrar el ETACard showETACard.value = true; + routePhase.value = 'eta'; } // Watch for route selection changes watch(() => routeStore.selectedRouteId, (routeId) => { if (routeId) { if (routeStore.wasSelectedFromMap) { - // Iniciar dibujo y búsqueda de disponibilidad en paralelo - // ETACard se abrirá cuando paradaCercana sea calculada (ver watcher abajo) - showETACard.value = false; + // Al seleccionar ruta: dibujar mapa + mostrar ETACard (fase 'eta') updateMapMarkers(false); - updateActiveUnits(); + updateActiveUnits(); + showETACard.value = true; + routePhase.value = 'eta'; } else { clearMapMarkers(); } } else { clearMapMarkers(); + showETACard.value = false; + routePhase.value = 'idle'; } }); @@ -439,7 +451,6 @@ watch(() => routeStore.selectedRouteId, (routeId) => { watch(paradaCercana, (newStop) => { if (newStop && routeStore.selectedRouteId) { updateActiveUnits(); - showETACard.value = true; // Abrir ahora que sí tenemos datos reales } }); @@ -514,7 +525,7 @@ watch([() => authStore.userProfile?.auto_location, isLoaded], ([canLocate, loade >