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:
@ -2,20 +2,22 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useFavoritesStore } from '@/stores/favorites'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { getImageUrl as utilGetImageUrl } from '@/utils/imageUrl'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
const favoritesStore = useFavoritesStore()
|
||||
const selectedFilter = ref('all')
|
||||
|
||||
const filters = [
|
||||
{ key: 'all', label: 'Todos', icon: 'star' },
|
||||
{ key: 'routes', label: 'Buses', icon: 'directions_bus' },
|
||||
{ key: 'taxis', label: 'Taxis', icon: 'local_taxi' },
|
||||
{ key: 'businesses',label: 'Comercios', icon: 'store' },
|
||||
{ key: 'coupons', label: 'Ofertas', icon: 'confirmation_number' },
|
||||
{ key: 'stops', label: 'Paradas', icon: 'location_on' },
|
||||
]
|
||||
const filters = computed(() => [
|
||||
{ key: 'all', label: t('common.all'), icon: 'star' },
|
||||
{ key: 'routes', label: t('favorites.tabs.routes'), icon: 'directions_bus' },
|
||||
{ key: 'taxis', label: t('favorites.tabs.taxis'), icon: 'local_taxi' },
|
||||
{ key: 'businesses',label: t('favorites.tabs.businesses'), icon: 'store' },
|
||||
{ key: 'coupons', label: t('favorites.tabs.coupons'), icon: 'confirmation_number' },
|
||||
{ key: 'stops', label: t('navigation.routes'), icon: 'location_on' }, // Reusing navigation.routes or adding a specific one
|
||||
])
|
||||
|
||||
onMounted(async () => {
|
||||
await favoritesStore.loadFavorites()
|
||||
@ -67,9 +69,9 @@ const hasVisibleItems = computed(() =>
|
||||
|
||||
<!-- ── Header ── -->
|
||||
<header class="fav-header">
|
||||
<h1 class="fav-title">Mis Favoritos</h1>
|
||||
<h1 class="fav-title">{{ t('favorites.title') }}</h1>
|
||||
<p class="fav-count" v-if="totalFavorites > 0">
|
||||
{{ totalFavorites }} guardado{{ totalFavorites !== 1 ? 's' : '' }}
|
||||
{{ t('favorites.count', { count: totalFavorites }) }}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
@ -92,7 +94,7 @@ const hasVisibleItems = computed(() =>
|
||||
<!-- ── Loading ── -->
|
||||
<div v-if="favoritesStore.isLoading" class="state-center">
|
||||
<div class="spinner"></div>
|
||||
<p>Cargando favoritos...</p>
|
||||
<p>{{ t('common.loading') }}</p>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
@ -106,18 +108,18 @@ const hasVisibleItems = computed(() =>
|
||||
<line x1="43" y1="45" x2="57" y2="45" stroke="var(--active-color)" stroke-width="4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="empty-title">Nada guardado aún</h2>
|
||||
<p class="empty-sub">Explora rutas, taxis y negocios para guardar tus favoritos aquí</p>
|
||||
<h2 class="empty-title">{{ t('favorites.empty.title') }}</h2>
|
||||
<p class="empty-sub">{{ t('favorites.empty.description') }}</p>
|
||||
<button class="cta-btn" @click="router.push('/map')">
|
||||
<span class="material-icons">explore</span>
|
||||
Explorar ahora
|
||||
{{ t('favorites.cta.exploreNow') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ── Empty de categoría ── -->
|
||||
<div v-else-if="!hasVisibleItems" class="empty-state empty-state--sm">
|
||||
<span class="material-icons empty-cat-icon">search_off</span>
|
||||
<p class="empty-sub">No tienes favoritos en esta categoría</p>
|
||||
<p class="empty-sub">{{ t('favorites.empty.noResultsCategory') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- ── Contenido ── -->
|
||||
@ -127,7 +129,7 @@ const hasVisibleItems = computed(() =>
|
||||
<section v-if="visibleRoutes.length > 0" class="fav-section">
|
||||
<div class="section-label">
|
||||
<span class="material-icons">directions_bus</span>
|
||||
<span>Rutas</span>
|
||||
<span>{{ t('favorites.tabs.routes') }}</span>
|
||||
</div>
|
||||
<div class="card-list">
|
||||
<div
|
||||
@ -141,9 +143,9 @@ const hasVisibleItems = computed(() =>
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<p class="card-name">{{ item.item_name }}</p>
|
||||
<p class="card-sub">Toca para ver horarios</p>
|
||||
<p class="card-sub">{{ t('favorites.viewSchedules') }}</p>
|
||||
</div>
|
||||
<button class="heart-btn heart-btn--active" @click.stop="removeFavorite($event, item.item_type, item.item_id)" title="Quitar de favoritos">
|
||||
<button class="heart-btn heart-btn--active" @click.stop="removeFavorite($event, item.item_type, item.item_id)" :title="t('favorites.removeTitle')">
|
||||
<span class="material-icons">favorite</span>
|
||||
</button>
|
||||
</div>
|
||||
@ -154,7 +156,7 @@ const hasVisibleItems = computed(() =>
|
||||
<section v-if="visibleTaxis.length > 0" class="fav-section">
|
||||
<div class="section-label">
|
||||
<span class="material-icons">local_taxi</span>
|
||||
<span>Taxis</span>
|
||||
<span>{{ t('favorites.tabs.taxis') }}</span>
|
||||
</div>
|
||||
<div class="card-list">
|
||||
<div
|
||||
@ -168,9 +170,9 @@ const hasVisibleItems = computed(() =>
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<p class="card-name">{{ item.item_name }}</p>
|
||||
<p class="card-sub">Taxi • Ver disponibilidad</p>
|
||||
<p class="card-sub">{{ t('favorites.availability') }}</p>
|
||||
</div>
|
||||
<button class="heart-btn heart-btn--active" @click.stop="removeFavorite($event, item.item_type, item.item_id)" title="Quitar de favoritos">
|
||||
<button class="heart-btn heart-btn--active" @click.stop="removeFavorite($event, item.item_type, item.item_id)" :title="t('favorites.removeTitle')">
|
||||
<span class="material-icons">favorite</span>
|
||||
</button>
|
||||
</div>
|
||||
@ -181,7 +183,7 @@ const hasVisibleItems = computed(() =>
|
||||
<section v-if="visibleBusinesses.length > 0" class="fav-section">
|
||||
<div class="section-label">
|
||||
<span class="material-icons">store</span>
|
||||
<span>Negocios</span>
|
||||
<span>{{ t('favorites.tabs.businesses') }}</span>
|
||||
</div>
|
||||
<div class="biz-grid">
|
||||
<div
|
||||
@ -195,11 +197,11 @@ const hasVisibleItems = computed(() =>
|
||||
<button class="heart-btn heart-btn--active heart-btn--overlay" @click.stop="removeFavorite($event, item.item_type, item.item_id)">
|
||||
<span class="material-icons">favorite</span>
|
||||
</button>
|
||||
<span class="biz-badge">Negocio</span>
|
||||
<span class="biz-badge">{{ t('favorites.tabs.businesses') }}</span>
|
||||
</div>
|
||||
<div class="biz-body">
|
||||
<p class="card-name">{{ item.item_name }}</p>
|
||||
<p class="card-sub">Ver detalles →</p>
|
||||
<p class="card-sub">{{ t('favorites.details') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -209,7 +211,7 @@ const hasVisibleItems = computed(() =>
|
||||
<section v-if="visibleCoupons.length > 0" class="fav-section">
|
||||
<div class="section-label">
|
||||
<span class="material-icons">confirmation_number</span>
|
||||
<span>Ofertas y Viajes</span>
|
||||
<span>{{ t('favorites.tabs.coupons') }}</span>
|
||||
</div>
|
||||
<div class="card-list">
|
||||
<div
|
||||
@ -224,9 +226,9 @@ const hasVisibleItems = computed(() =>
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<p class="card-name">{{ item.item_name }}</p>
|
||||
<span class="badge-avail">Cupón</span>
|
||||
<span class="badge-avail">{{ t('favorites.tabs.coupons') }}</span>
|
||||
</div>
|
||||
<button class="heart-btn heart-btn--active" @click.stop="removeFavorite($event, item.item_type, item.item_id)" title="Quitar de favoritos">
|
||||
<button class="heart-btn heart-btn--active" @click.stop="removeFavorite($event, item.item_type, item.item_id)" :title="t('favorites.removeTitle')">
|
||||
<span class="material-icons">favorite</span>
|
||||
</button>
|
||||
</div>
|
||||
@ -237,7 +239,7 @@ const hasVisibleItems = computed(() =>
|
||||
<section v-if="visibleStops.length > 0" class="fav-section">
|
||||
<div class="section-label">
|
||||
<span class="material-icons">location_on</span>
|
||||
<span>Paradas</span>
|
||||
<span>{{ t('navigation.routes') }}</span>
|
||||
</div>
|
||||
<div class="card-list">
|
||||
<div
|
||||
@ -251,7 +253,7 @@ const hasVisibleItems = computed(() =>
|
||||
</div>
|
||||
<div class="card-info">
|
||||
<p class="card-name">{{ item.item_name }}</p>
|
||||
<span class="badge-avail">Favorito</span>
|
||||
<span class="badge-avail">{{ t('favorites.saved') }}</span>
|
||||
</div>
|
||||
<button class="heart-btn heart-btn--active" @click.stop="removeFavorite($event, item.item_type, item.item_id)">
|
||||
<span class="material-icons">favorite</span>
|
||||
@ -268,7 +270,7 @@ const hasVisibleItems = computed(() =>
|
||||
<style scoped>
|
||||
/* ══════════════════════════════════════════
|
||||
PÁGINA BASE
|
||||
══════════════════════════════════════════ */
|
||||
══════════════════════════════════════════ */
|
||||
.fav-page {
|
||||
min-height: 100vh;
|
||||
background: var(--bg-primary);
|
||||
@ -277,7 +279,7 @@ const hasVisibleItems = computed(() =>
|
||||
|
||||
/* ══════════════════════════════════════════
|
||||
HEADER
|
||||
══════════════════════════════════════════ */
|
||||
══════════════════════════════════════════ */
|
||||
.fav-header {
|
||||
padding: 1.5rem 1.25rem 1rem;
|
||||
background: var(--bg-secondary);
|
||||
@ -301,7 +303,7 @@ const hasVisibleItems = computed(() =>
|
||||
|
||||
/* ══════════════════════════════════════════
|
||||
CHIPS
|
||||
══════════════════════════════════════════ */
|
||||
══════════════════════════════════════════ */
|
||||
.chips-wrap {
|
||||
background: var(--bg-secondary);
|
||||
padding: 0.75rem 0 0.75rem;
|
||||
@ -355,7 +357,7 @@ const hasVisibleItems = computed(() =>
|
||||
|
||||
/* ══════════════════════════════════════════
|
||||
CONTENIDO
|
||||
══════════════════════════════════════════ */
|
||||
══════════════════════════════════════════ */
|
||||
.fav-content {
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
@ -385,7 +387,7 @@ const hasVisibleItems = computed(() =>
|
||||
|
||||
/* ══════════════════════════════════════════
|
||||
TARJETA FILA (rutas / taxis / eventos)
|
||||
══════════════════════════════════════════ */
|
||||
══════════════════════════════════════════ */
|
||||
.card-list { display: flex; flex-direction: column; gap: 0.625rem; }
|
||||
|
||||
.card {
|
||||
@ -440,6 +442,13 @@ const hasVisibleItems = computed(() =>
|
||||
|
||||
.card-thumb--event .material-icons { font-size: 1.5rem; }
|
||||
|
||||
.card-thumb--blue {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.card-thumb--blue .material-icons { font-size: 1.5rem; }
|
||||
|
||||
/* Info */
|
||||
.card-info { flex: 1; min-width: 0; }
|
||||
|
||||
@ -475,7 +484,7 @@ const hasVisibleItems = computed(() =>
|
||||
|
||||
/* ══════════════════════════════════════════
|
||||
BOTÓN CORAZÓN
|
||||
══════════════════════════════════════════ */
|
||||
══════════════════════════════════════════ */
|
||||
.heart-btn {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
@ -522,7 +531,7 @@ const hasVisibleItems = computed(() =>
|
||||
|
||||
/* ══════════════════════════════════════════
|
||||
GRID DE NEGOCIOS
|
||||
══════════════════════════════════════════ */
|
||||
══════════════════════════════════════════ */
|
||||
.biz-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
@ -584,7 +593,7 @@ const hasVisibleItems = computed(() =>
|
||||
|
||||
/* ══════════════════════════════════════════
|
||||
ESTADOS
|
||||
══════════════════════════════════════════ */
|
||||
══════════════════════════════════════════ */
|
||||
.state-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -676,7 +685,7 @@ const hasVisibleItems = computed(() =>
|
||||
|
||||
/* ══════════════════════════════════════════
|
||||
RESPONSIVE
|
||||
══════════════════════════════════════════ */
|
||||
══════════════════════════════════════════ */
|
||||
@media (max-width: 480px) {
|
||||
.biz-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
@ -689,4 +698,3 @@ const hasVisibleItems = computed(() =>
|
||||
.biz-grid { grid-template-columns: repeat(3, 1fr); }
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user