Implement Smart Location: auto-detect user location if preference is enabled, hide location button, and handle permission denial by resetting preference

This commit is contained in:
2026-03-01 12:15:08 -05:00
parent d0d75b8c98
commit 4d7b472c6c
20 changed files with 852 additions and 344 deletions

View File

@ -5,7 +5,9 @@ import { useRouteStore } from '@/stores/route'
import { useTaxiStore } from '@/stores/taxi'
import { analyticsService } from '@/services/analyticsService'
import FavoriteButton from '@/components/FavoriteButton.vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const router = useRouter()
const routeStore = useRouteStore()
const taxiStore = useTaxiStore()
@ -80,7 +82,7 @@ const correlimientos = computed(() => {
:class="activeTab === 'routes' ? 'bg-primary shadow-lg text-slate-900' : 'bg-transparent text-slate-500 dark:text-gray-500'"
>
<span class="material-icons text-lg">directions_bus</span>
Rutas de Bus
{{ t('routesView.busTab') }}
</button>
<button
@click="activeTab = 'taxis'"
@ -88,7 +90,7 @@ const correlimientos = computed(() => {
:class="activeTab === 'taxis' ? 'bg-primary shadow-lg text-slate-900' : 'bg-transparent text-slate-500 dark:text-gray-500'"
>
<span class="material-icons text-lg">local_taxi</span>
Taxis Locales
{{ t('routesView.taxiTab') }}
</button>
</div>
@ -102,7 +104,7 @@ const correlimientos = computed(() => {
v-model="originSearch"
@keyup.enter="handleBusSearch"
class="bg-transparent border-none focus:ring-0 text-sm font-semibold w-full placeholder:text-slate-400 dark:placeholder:text-gray-500"
placeholder="Ciudad de Origen"
:placeholder="t('routesView.originPlaceholder')"
/>
</div>
</div>
@ -113,12 +115,12 @@ const correlimientos = computed(() => {
v-model="destinationSearch"
@keyup.enter="handleBusSearch"
class="bg-transparent border-none focus:ring-0 text-sm font-semibold w-full placeholder:text-slate-400 dark:placeholder:text-gray-500"
placeholder="Ciudad de Destino"
:placeholder="t('routesView.destPlaceholder')"
/>
</div>
</div>
<button @click="handleBusSearch" class="w-full bg-primary py-4 rounded-2xl text-slate-900 font-bold uppercase tracking-widest text-xs shadow-lg active:scale-95 transition-all">
Buscar Rutas
{{ t('routesView.searchBtn') }}
</button>
</div>
@ -132,7 +134,7 @@ const correlimientos = computed(() => {
@change="handleTaxiFilter"
class="bg-transparent border-none focus:ring-0 text-sm font-semibold w-full dark:text-gray-200 appearance-none cursor-pointer"
>
<option value="">Todos los corregimientos</option>
<option value="">{{ t('routesView.allCorregimientos') }}</option>
<option v-for="c in correlimientos" :key="c" :value="c">{{ c }}</option>
</select>
<span class="material-icons ml-auto text-gray-500 text-sm">unfold_more</span>
@ -140,7 +142,7 @@ const correlimientos = computed(() => {
</div>
</div>
<div class="flex flex-col items-center justify-center px-2">
<span class="text-[10px] font-black text-slate-400 dark:text-gray-400 mb-2 uppercase">Inglés</span>
<span class="text-[10px] font-black text-slate-400 dark:text-gray-400 mb-2 uppercase">{{ t('routesView.english') }}</span>
<div
@click="englishOnly = !englishOnly; handleTaxiFilter()"
class="size-10 rounded-lg border-2 border-primary flex items-center justify-center bg-transparent cursor-pointer transition-colors"
@ -156,7 +158,7 @@ const correlimientos = computed(() => {
<!-- Results Area -->
<div class="flex-1 px-6 pt-8 space-y-4 pb-32">
<h2 class="text-[11px] font-black text-slate-400 dark:text-gray-500 uppercase tracking-[0.15em] mb-4">
{{ activeTab === 'routes' ? 'Rutas Disponibles' : 'Conductores Recomendados' }}
{{ activeTab === 'routes' ? t('routesView.availableRoutes') : t('routesView.recommendedDrivers') }}
</h2>
<!-- Loading Results -->
@ -181,7 +183,7 @@ const correlimientos = computed(() => {
<div>
<div class="flex items-center gap-2">
<p class="font-extrabold text-xl">{{ route.name }}</p>
<span class="px-2 py-0.5 rounded-full bg-primary text-[9px] font-black uppercase tracking-wider text-slate-900">ACTIVA</span>
<span class="px-2 py-0.5 rounded-full bg-primary text-[9px] font-black uppercase tracking-wider text-slate-900">{{ t('routesView.active') }}</span>
</div>
<p class="text-[11px] text-slate-500 dark:text-gray-400 font-bold uppercase tracking-tight">
{{ route.origin_city }} {{ route.destination_city }}
@ -198,15 +200,15 @@ const correlimientos = computed(() => {
</div>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 bg-white dark:bg-input-dark px-3 py-2 rounded-xl border border-slate-200 dark:border-white/5">
<span class="text-[10px] font-black dark:text-gray-200">{{ route.estimated_duration_minutes }} min {{ route.distance_km }} km</span>
<span class="text-[10px] font-black dark:text-gray-200">{{ route.estimated_duration_minutes }} {{ t('routesView.minutes') }} {{ route.distance_km }} {{ t('routesView.km') }}</span>
</div>
<button class="bg-primary px-8 py-3 rounded-2xl text-xs font-black uppercase tracking-widest shadow-sm hover:brightness-110 text-slate-900 transition-all">
Ver Horarios
{{ t('routesView.findSchedules') }}
</button>
</div>
</div>
<div v-if="routeStore.allRoutes.length === 0" class="text-center py-10 opacity-50 italic">
No se encontraron rutas para tu búsqueda.
{{ t('routesView.noRoutes') }}
</div>
</template>
@ -247,18 +249,18 @@ const correlimientos = computed(() => {
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 bg-white dark:bg-input-dark px-3 py-2 rounded-xl border border-slate-200 dark:border-white/5">
<span class="text-[10px] font-black dark:text-gray-200">{{ taxi.rating || 5.0 }} </span>
<span v-if="taxi.english_speaking" class="text-[10px] font-black text-blue-500 uppercase">EN</span>
<span v-if="taxi.english_speaking" class="text-[10px] font-black text-blue-500 uppercase">{{ t('routesView.english') }}</span>
</div>
<button
@click="callTaxi(taxi.phone_number)"
class="bg-primary px-8 py-3 rounded-2xl text-xs font-black uppercase tracking-widest shadow-sm hover:brightness-110 text-slate-900 transition-all"
>
Contactar
{{ t('routesView.contact') }}
</button>
</div>
</div>
<div v-if="taxiStore.taxis.length === 0" class="text-center py-10 opacity-50 italic">
No hay taxis disponibles en esta zona.
{{ t('routesView.noTaxis') }}
</div>
</template>
</div>