chore: apply finalized MapView and ETA card fixes
This commit is contained in:
@ -5,11 +5,26 @@
|
||||
|
||||
<!-- Bottom Sheet container -->
|
||||
<div
|
||||
class="relative bg-white dark:bg-gray-900 rounded-t-3xl shadow-2xl p-5 transform transition-transform duration-300 ease-out flex flex-col gap-4 max-h-[85vh] overflow-y-auto"
|
||||
:class="isOpen ? 'translate-y-0' : 'translate-y-full'"
|
||||
ref="sheetRef"
|
||||
class="relative bg-white dark:bg-gray-900 rounded-t-3xl shadow-2xl p-5 transform flex flex-col gap-4 max-h-[85vh] overflow-y-auto"
|
||||
:style="{
|
||||
transform: `translateY(${dragY}px)`,
|
||||
transition: isDragging ? 'none' : 'transform 0.3s ease-out'
|
||||
}"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove="onTouchMove"
|
||||
@touchend="onTouchEnd"
|
||||
>
|
||||
<!-- Indicador de arrastre (visual) -->
|
||||
<div class="absolute top-3 left-1/2 -translate-x-1/2 w-12 h-1.5 bg-gray-300 dark:bg-gray-600 rounded-full cursor-pointer" @click="closeCard"></div>
|
||||
<!-- Pestaña de arrastre (visual + funcional) -->
|
||||
<div
|
||||
class="absolute top-3 left-1/2 -translate-x-1/2 w-12 h-1.5 bg-gray-300 dark:bg-gray-600 rounded-full cursor-grab active:cursor-grabbing"
|
||||
@touchstart="onTouchStart"
|
||||
></div>
|
||||
|
||||
<!-- Indicador visual de que se puede arrastrar -->
|
||||
<p class="text-center text-[10px] text-gray-400 mt-1 mb-0 pointer-events-none">
|
||||
Desliza hacia abajo para cerrar
|
||||
</p>
|
||||
|
||||
<!-- Cabecera de la parada -->
|
||||
<div class="mt-4 flex items-start gap-4 pb-4 border-b border-gray-100 dark:border-gray-800">
|
||||
@ -115,7 +130,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import type { BusETA } from '@/composables/useETA';
|
||||
|
||||
defineProps<{
|
||||
@ -132,6 +147,45 @@ const emit = defineEmits<{
|
||||
(e: 'refresh'): void;
|
||||
}>();
|
||||
|
||||
// ── DRAG TO DISMISS ──────────────────────────────────
|
||||
const sheetRef = ref<HTMLElement | null>(null);
|
||||
const dragY = ref(0); // desplazamiento actual del drag
|
||||
const isDragging = ref(false);
|
||||
const startY = ref(0);
|
||||
const DISMISS_THRESHOLD = 0.30; // 30% de la altura = cerrar
|
||||
|
||||
function onTouchStart(e: TouchEvent) {
|
||||
startY.value = e.touches[0]?.clientY ?? 0;
|
||||
isDragging.value = true;
|
||||
dragY.value = 0;
|
||||
}
|
||||
|
||||
function onTouchMove(e: TouchEvent) {
|
||||
if (!isDragging.value) return;
|
||||
const delta = (e.touches[0]?.clientY ?? 0) - startY.value;
|
||||
// Solo permitir arrastrar hacia ABAJO (delta positivo)
|
||||
if (delta > 0) {
|
||||
dragY.value = delta;
|
||||
e.preventDefault(); // evitar scroll del contenido mientras arrastra
|
||||
}
|
||||
}
|
||||
|
||||
function onTouchEnd() {
|
||||
if (!isDragging.value) return;
|
||||
isDragging.value = false;
|
||||
|
||||
const sheetHeight = sheetRef.value?.offsetHeight ?? 400;
|
||||
const draggedRatio = dragY.value / sheetHeight;
|
||||
|
||||
if (draggedRatio >= DISMISS_THRESHOLD) {
|
||||
// Arrastró suficiente → cerrar
|
||||
emit('close');
|
||||
}
|
||||
// Siempre resetear posición (snap back o después de cerrar)
|
||||
dragY.value = 0;
|
||||
}
|
||||
|
||||
// ── AUTO REFRESH ─────────────────────────────────────
|
||||
let intervalId: number;
|
||||
|
||||
function closeCard() {
|
||||
|
||||
@ -7,7 +7,8 @@ import { useRouteStore } from '@/stores/route'
|
||||
|
||||
export const useFlujoPrincipal = () => {
|
||||
const { limpiarMapa, registrarMarker } = useMapState()
|
||||
const { encontrarParadaCercana } = useParadaCercana()
|
||||
const paradaCercanaInst = useParadaCercana()
|
||||
const { encontrarParadaCercana, paradaCercana } = paradaCercanaInst
|
||||
const { trazarRuta } = useDirectionsRoute()
|
||||
const cargando = ref(false)
|
||||
const errorMsg = ref('')
|
||||
@ -68,12 +69,6 @@ export const useFlujoPrincipal = () => {
|
||||
|
||||
// ── PASO 4: Calcular parada más cercana ───────────────
|
||||
await encontrarParadaCercana(ubicacion, paradas, map)
|
||||
// Buscamos manualmente porque encontrarParadaCercana guarda en su propio ref interno que no retornamos fácil
|
||||
// o podemos usar useParadaCercana().paradaCercana.value
|
||||
// Actually, refactor from useParadaCercana: finding the closest
|
||||
|
||||
const { paradaCercana } = useParadaCercana()
|
||||
await encontrarParadaCercana(ubicacion, paradas, undefined) // sin mapa para no dibujar polilínea vieja por encima
|
||||
|
||||
const paradaCercanaFound = paradaCercana.value
|
||||
|
||||
|
||||
@ -524,6 +524,9 @@ export function useGoogleMaps() {
|
||||
if ('remove' in overlay && typeof overlay.remove === 'function') {
|
||||
overlay.remove()
|
||||
}
|
||||
if ('onRemove' in overlay && typeof overlay.onRemove === 'function') {
|
||||
overlay.onRemove()
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors when removing overlays
|
||||
console.warn('Error removing overlay:', e)
|
||||
|
||||
@ -107,23 +107,24 @@ function closeUberSearch() {
|
||||
}
|
||||
|
||||
async function clearAllMapData() {
|
||||
console.log('🤖 JARVIS: Iniciando PURGA nuclear con tolerancia a fallos...');
|
||||
console.log('🤖 JARVIS: Iniciando PURGA nuclear...');
|
||||
|
||||
// 1. Respuesta inmediata en UI
|
||||
// 1. UI inmediata
|
||||
showUberSearch.value = false;
|
||||
showRoutesToggle.value = false;
|
||||
destinationQuery.value = "";
|
||||
stopSearchQuery.value = "";
|
||||
showETACard.value = false;
|
||||
|
||||
// 2. Invalidar cualquier hilo de dibujo en curso
|
||||
// 2. Invalidar hilos en curso
|
||||
mappingSequenceId.value++;
|
||||
|
||||
try {
|
||||
// 3. Resetear Store
|
||||
// 3. Resetear stores
|
||||
routeStore.clearSelection();
|
||||
lastProcessedRouteId.value = null;
|
||||
|
||||
// 4. Limpieza manual protegida de marcadores
|
||||
// 4. Limpiar markers locales
|
||||
const sweep = (arrayRef: any) => {
|
||||
if (!arrayRef.value) return;
|
||||
arrayRef.value.forEach((m: any) => {
|
||||
@ -135,7 +136,7 @@ async function clearAllMapData() {
|
||||
sweep(markers);
|
||||
sweep(promoMarkers);
|
||||
|
||||
// Limpiar Unidades de transporte
|
||||
// Limpiar unidades de transporte
|
||||
if (unitMarkers.value) {
|
||||
unitMarkers.value.forEach((m: any) => {
|
||||
try { if (m && m.setMap) m.setMap(null); } catch (e) {}
|
||||
@ -143,37 +144,75 @@ async function clearAllMapData() {
|
||||
unitMarkers.value.clear();
|
||||
}
|
||||
|
||||
// 5. Barrido profundo de Google
|
||||
// 5. Limpiar polilíneas (CORREGIDO: agregar walkingPolylineBorder)
|
||||
if (polyline.value) {
|
||||
polyline.value.setMap(null);
|
||||
polyline.value = null;
|
||||
}
|
||||
if (walkingPolyline.value) {
|
||||
walkingPolyline.value.setMap(null);
|
||||
walkingPolyline.value = null;
|
||||
}
|
||||
// ✅ NUEVO: limpiar el borde blanco de la ruta caminando
|
||||
if (walkingPolylineBorder.value) {
|
||||
walkingPolylineBorder.value.setMap(null);
|
||||
walkingPolylineBorder.value = null;
|
||||
}
|
||||
|
||||
// 6. Limpiar pulso de parada óptima (CORREGIDO)
|
||||
if (optimalStopPulse.value) {
|
||||
try {
|
||||
// Intentar setMap primero
|
||||
if (typeof optimalStopPulse.value.setMap === 'function') {
|
||||
optimalStopPulse.value.setMap(null);
|
||||
}
|
||||
// Si es un overlay HTML, también intentar remove()
|
||||
if (typeof optimalStopPulse.value.remove === 'function') {
|
||||
optimalStopPulse.value.remove();
|
||||
}
|
||||
// Si tiene onRemove (OverlayView pattern)
|
||||
if (typeof optimalStopPulse.value.onRemove === 'function') {
|
||||
optimalStopPulse.value.onRemove();
|
||||
}
|
||||
} catch(e) {
|
||||
console.warn('SIBU | No se pudo limpiar optimalStopPulse:', e);
|
||||
}
|
||||
optimalStopPulse.value = null;
|
||||
}
|
||||
|
||||
// 7. Limpiar composables
|
||||
limpiarCaminata();
|
||||
|
||||
// 8. Barrido profundo de Google Maps overlays
|
||||
if (typeof clearAllOverlays === 'function') {
|
||||
try { clearAllOverlays(); } catch (e) {}
|
||||
}
|
||||
|
||||
// 6. Limpiar polilíneas y pulsos
|
||||
if (polyline.value) { polyline.value.setMap(null); polyline.value = null; }
|
||||
if (walkingPolyline.value) { walkingPolyline.value.setMap(null); walkingPolyline.value = null; }
|
||||
if (optimalStopPulse.value) {
|
||||
try { if (optimalStopPulse.value.setMap) optimalStopPulse.value.setMap(null); } catch(e){}
|
||||
optimalStopPulse.value = null;
|
||||
}
|
||||
limpiarCaminata();
|
||||
showETACard.value = false;
|
||||
|
||||
// Nueva Purgación Centralizada:
|
||||
// 9. Purgación centralizada (useMapState)
|
||||
limpiarTodoCentralizado();
|
||||
|
||||
// 7. Restaurar Solo Usuario tras un breve respiro
|
||||
// 10. Restaurar SOLO el marcador del usuario
|
||||
await nextTick();
|
||||
if (userCoords.value) {
|
||||
const { lat, lng } = userCoords.value;
|
||||
if (userMarker.value && userMarker.value.setMap) {
|
||||
try { userMarker.value.setMap(null); } catch(e){}
|
||||
// Limpiar marcador anterior del usuario
|
||||
if (userMarker.value) {
|
||||
try {
|
||||
if (userMarker.value.setMap) userMarker.value.setMap(null);
|
||||
if (userMarker.value.remove) userMarker.value.remove();
|
||||
} catch(e) {}
|
||||
}
|
||||
userMarker.value = addHtmlMarker({ lat, lng }, sonarHtml, { x: -30, y: -30 });
|
||||
// Redibujar solo el sonar del usuario
|
||||
userMarker.value = addHtmlMarker(
|
||||
{ lat, lng },
|
||||
sonarHtml,
|
||||
{ x: -30, y: -30 }
|
||||
);
|
||||
}
|
||||
|
||||
console.log('🤖 JARVIS: Purga completada con éxito.');
|
||||
console.log('🤖 JARVIS: Purga completada. Solo queda el usuario ✓');
|
||||
} catch (err) {
|
||||
console.error('❌ JARVIS: Error crítico en purga, pero el mapa debería estar limpio:', err);
|
||||
console.error('❌ JARVIS: Error en purga:', err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -344,6 +383,11 @@ async function initializeMap() {
|
||||
map.value.addListener('zoom_changed', () => {
|
||||
updateMarkersStyles();
|
||||
});
|
||||
map.value.addListener('click', () => {
|
||||
if (showETACard.value) {
|
||||
showETACard.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If we have a selected route, show its stops
|
||||
@ -626,9 +670,18 @@ async function highlightOptimalStopForRoute() {
|
||||
{ x: -30, y: -30 }
|
||||
);
|
||||
|
||||
// Calcular ETAs
|
||||
// PASO 1: Mostrar ETACard inferior primero
|
||||
await calcularETA(routeStore.selectedRouteId!, stopObj);
|
||||
showETACard.value = true;
|
||||
|
||||
// PASO 2: Esperar 2 segundos antes de mostrar el banner superior
|
||||
// para que no saturen la pantalla al mismo tiempo
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// PASO 3: Mostrar banner superior solo si ETACard sigue abierto
|
||||
// (si el usuario ya cerró el ETACard, no mostrar el banner)
|
||||
// paradaCercana ya tiene el valor, el banner aparece automáticamente
|
||||
// porque usa v-if="paradaCercana && routeStore.selectedRouteId && !showETACard"
|
||||
}
|
||||
}
|
||||
|
||||
@ -756,26 +809,22 @@ function calculateWalkingPath(origin: { lat: number, lng: number }, targetStop:
|
||||
|
||||
<!-- Banner de Parada Más Cercana Inteligente -->
|
||||
<div
|
||||
v-if="paradaCercana && routeStore.selectedRouteId"
|
||||
v-if="paradaCercana && routeStore.selectedRouteId && !showETACard"
|
||||
class="fixed left-0 right-0 z-40 px-3 transition-transform duration-300 pointer-events-none"
|
||||
:style="{ top: alturaNavbar + 'px' }"
|
||||
>
|
||||
<div class="bg-white dark:bg-gray-900 rounded-b-2xl shadow-xl border-t-4 border-blue-600 border-t-blue-600 p-3 flex items-center gap-3 pointer-events-auto">
|
||||
<div class="bg-blue-100 dark:bg-blue-900/40 rounded-full p-2 shrink-0">
|
||||
<span class="material-icons text-blue-600 dark:text-blue-400">directions_bus</span>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="text-[11px] text-gray-500 font-bold uppercase">Parada más cercana</p>
|
||||
<p class="text-sm font-bold text-gray-800 dark:text-white truncate">
|
||||
{{ paradaCercana?.name }}
|
||||
</p>
|
||||
<p class="text-xs text-blue-600 dark:text-blue-400 font-medium whitespace-nowrap overflow-hidden text-ellipsis">
|
||||
{{ (distanciaMetros && distanciaMetros < 1000) ? Math.round(distanciaMetros) + ' m' : (distanciaMetros ? (distanciaMetros / 1000).toFixed(1) + ' km' : '') }}
|
||||
<span v-if="duracionCaminata">· {{ Math.round(duracionCaminata / 60) }} min caminando</span>
|
||||
</p>
|
||||
</div>
|
||||
<button @click="paradaCercana = null" class="text-gray-400 hover:text-gray-600 shrink-0 p-1">
|
||||
<span class="material-icons">close</span>
|
||||
<!-- Solo mostrar cuando ETACard está CERRADO -->
|
||||
<!-- v-if agrega condición: && !showETACard -->
|
||||
<div class="bg-white/90 dark:bg-gray-900/90 backdrop-blur-sm rounded-b-2xl shadow-lg border-t-2 border-yellow-400 px-4 py-2 flex items-center gap-2 pointer-events-auto">
|
||||
<span class="material-icons text-yellow-500 text-sm">directions_bus</span>
|
||||
<span class="text-sm font-bold text-gray-800 dark:text-white truncate flex-1">
|
||||
{{ paradaCercana?.name }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500 whitespace-nowrap">
|
||||
{{ (distanciaMetros && distanciaMetros < 1000) ? Math.round(distanciaMetros) + 'm' : (distanciaMetros ? (distanciaMetros / 1000).toFixed(1) + 'km' : '') }}
|
||||
</span>
|
||||
<button @click="paradaCercana = null" class="text-gray-400 hover:text-gray-600 shrink-0 p-0.5 ml-1">
|
||||
<span class="material-icons text-sm">close</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user