feat: auto-geolocation improves, fix route stops query and map soft-reset

This commit is contained in:
2026-03-02 09:58:29 -05:00
parent fb57f13d62
commit e9b5acdc48
3 changed files with 127 additions and 19 deletions

View File

@ -35,11 +35,22 @@
</button> </button>
<div class="search-title">{{ t('map.availableRoutes') }}</div> <div class="search-title">{{ t('map.availableRoutes') }}</div>
</div> </div>
<!-- Search Input -->
<div class="search-input-wrapper">
<span class="material-icons search-field-icon">search</span>
<input
v-model="searchQuery"
type="text"
:placeholder="t('map.search')"
class="route-search-input"
autofocus
/>
</div>
<!-- Results --> <!-- Results -->
<div class="uber-results custom-scrollbar"> <div class="uber-results custom-scrollbar">
<div <div
v-for="route in allRoutes" v-for="route in filteredRoutes"
:key="route.id" :key="route.id"
class="uber-result-item" class="uber-result-item"
:class="{ 'selected-route': route.id === selectedRouteId && wasSelectedFromMap }" :class="{ 'selected-route': route.id === selectedRouteId && wasSelectedFromMap }"
@ -63,6 +74,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
const props = defineProps<{ const props = defineProps<{
@ -77,6 +89,17 @@ const props = defineProps<{
defineEmits(['open', 'close', 'select-route']) defineEmits(['open', 'close', 'select-route'])
const { t } = useI18n() const { t } = useI18n()
const searchQuery = ref('')
const filteredRoutes = computed(() => {
if (!searchQuery.value.trim()) return props.allRoutes
const query = searchQuery.value.toLowerCase().trim()
return props.allRoutes.filter(r =>
r.name.toLowerCase().includes(query) ||
(r.origin_city && r.origin_city.toLowerCase().includes(query)) ||
(r.destination_city && r.destination_city.toLowerCase().includes(query))
)
})
</script> </script>
<style scoped> <style scoped>
@ -175,6 +198,39 @@ const { t } = useI18n()
font-weight: 800; font-weight: 800;
} }
.search-input-wrapper {
position: relative;
margin-bottom: 20px;
}
.search-field-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: var(--text-secondary);
pointer-events: none;
}
.route-search-input {
width: 100%;
padding: 12px 12px 12px 40px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 12px;
color: var(--text-primary);
font-family: inherit;
font-size: 0.95rem;
font-weight: 600;
transition: border-color 0.2s;
}
.route-search-input:focus {
outline: none;
border-color: var(--active-color);
box-shadow: 0 0 0 2px rgba(254, 231, 21, 0.2);
}
.uber-results { .uber-results {
max-height: 400px; max-height: 400px;
overflow-y: auto; overflow-y: auto;

View File

@ -7,11 +7,11 @@ export const routesService = {
async getAllRoutes(filters?: { originCity?: string, destinationCity?: string }): Promise<Route[]> { async getAllRoutes(filters?: { originCity?: string, destinationCity?: string }): Promise<Route[]> {
let query = supabase.from('routes').select('id, name, description, color, direction, origin_city, destination_city, distance_km, estimated_duration_minutes, average_speed_kmh, status, created_at, updated_at') let query = supabase.from('routes').select('id, name, description, color, direction, origin_city, destination_city, distance_km, estimated_duration_minutes, average_speed_kmh, status, created_at, updated_at')
if (filters?.originCity) { if (filters?.originCity?.trim()) {
query = query.eq('origin_city', filters.originCity) query = query.ilike('origin_city', `%${filters.originCity.trim()}%`)
} }
if (filters?.destinationCity) { if (filters?.destinationCity?.trim()) {
query = query.eq('destination_city', filters.destinationCity) query = query.ilike('destination_city', `%${filters.destinationCity.trim()}%`)
} }
const { data, error } = await query const { data, error } = await query
@ -37,7 +37,7 @@ export const routesService = {
stop_delay_minutes, stop_delay_minutes,
is_pickup_point, is_pickup_point,
is_dropoff_point, is_dropoff_point,
bus_stops (*) bus_stops (id, name, latitude, longitude, city, address, stop_type, has_shelter, has_seating, is_accessible, created_at, updated_at)
`) `)
.eq('route_id', routeId) .eq('route_id', routeId)
.order('stop_order', { ascending: true }) .order('stop_order', { ascending: true })

View File

@ -59,9 +59,32 @@ const carouselTimer = ref<any>(null);
const isMapMoved = ref(false); const isMapMoved = ref(false);
// Search optimization: Simple debounce implementation // Search optimization: Simple debounce implementation
// Helper functions // REQUISITO TÉCNICO: Implementar geolocalización automática al iniciar sesión.
function calculateDistance(point1: { lat: number; lng: number }, point2: { lat: number; lng: number }) {
const R = 6371; // Radio de la Tierra en km
const dLat = (point2.lat - point1.lat) * Math.PI / 180;
const dLng = (point2.lng - point1.lng) * Math.PI / 180;
const a =
Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(point1.lat * Math.PI / 180) * Math.cos(point2.lat * Math.PI / 180) *
Math.sin(dLng/2) * Math.sin(dLng/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
// performSearch removed function updateIsMapMoved() {
if (!map.value || !userCoords.value) return;
const center = map.value.getCenter();
if (!center) return;
const dist = calculateDistance(
{ lat: center.lat(), lng: center.lng() },
userCoords.value
);
// Si se movió más de 0.1 km (100 metros), mostrar botón
isMapMoved.value = dist > 0.1;
}
function openUberSearch() { function openUberSearch() {
showPromos.value = false; showPromos.value = false;
@ -72,11 +95,24 @@ function closeUberSearch() {
showUberSearch.value = false; showUberSearch.value = false;
} }
function animateAndReload() { async function animateAndReload() {
isBannerClosing.value = true; isBannerClosing.value = true;
routeStore.clearSelection(); routeStore.clearSelection();
// Solución anterior: Recargar para mapa limpio router.replace({ query: {} });
window.location.href = window.location.origin + window.location.pathname;
// Limpiar mapa sin recargar
clearMapMarkers();
// Recentrar en el usuario si está disponible (soft-reset)
if (userCoords.value) {
setCenter(userCoords.value.lat, userCoords.value.lng);
setZoom(16);
reDrawUserMarker();
}
setTimeout(() => {
isBannerClosing.value = false;
}, 500);
} }
function handlePromoClick(promo: any) { function handlePromoClick(promo: any) {
@ -143,7 +179,10 @@ async function initializeMap() {
}); });
// Detect user interaction with the map to show/hide location button // Detect user interaction with the map to show/hide location button
map.value.addListener('center_changed', updateIsMapMoved);
map.value.addListener('dragstart', () => { map.value.addListener('dragstart', () => {
// Forzar visibilidad inmediata en drag si se desea un feedback instantáneo,
// pero el watcher de distancia es el que manda finalmente.
isMapMoved.value = true; isMapMoved.value = true;
}); });
} }
@ -281,18 +320,30 @@ async function updateActiveUnits() {
function locateUser(): Promise<void> { function locateUser(): Promise<void> {
return new Promise((resolve) => { return new Promise((resolve) => {
if (!navigator.geolocation) { return resolve(); } if (!navigator.geolocation) {
console.warn('Geolocation no soportado');
return resolve();
}
navigator.geolocation.getCurrentPosition( navigator.geolocation.getCurrentPosition(
(position) => { (position) => {
const { latitude, longitude } = position.coords; const { latitude, longitude } = position.coords;
userCoords.value = { lat: latitude, lng: longitude }; userCoords.value = { lat: latitude, lng: longitude };
setCenter(latitude, longitude);
setZoom(16); // Centrar y mostrar
if (map.value) {
setCenter(latitude, longitude);
setZoom(16);
}
reDrawUserMarker(); reDrawUserMarker();
isMapMoved.value = false; // Reset interaction state isMapMoved.value = false;
resolve(); resolve();
}, },
() => { (error) => {
console.error('SIBU | Error obteniendo ubicación:', error);
// Si falló por falta de permisos o error y el usuario tenía auto_location activo,
// lo desactivamos para no re-intentar infinitamente
if (authStore.userProfile?.auto_location) { if (authStore.userProfile?.auto_location) {
authStore.updateProfile({ auto_location: false }); authStore.updateProfile({ auto_location: false });
} }
@ -374,9 +425,10 @@ function handleImageError(event: Event) {
(event.target as HTMLImageElement).src = getImageUrl(null, 'coupon'); (event.target as HTMLImageElement).src = getImageUrl(null, 'coupon');
} }
// AUTO-LOCATION: Watch for user profile to trigger location if preference is enabled // Watch for user profile to trigger location if preference is enabled OR on auth changes
watch(() => authStore.userProfile?.auto_location, (canLocate) => { watch([() => authStore.userProfile?.auto_location, isLoaded], ([canLocate, loaded]) => {
if (canLocate && isLoaded.value && !userCoords.value) { if (canLocate && loaded && !userCoords.value) {
console.log('SIBU | Iniciando geolocalización automática...');
locateUser(); locateUser();
} }
}, { immediate: true }); }, { immediate: true });