Files
SIB/frontend/src/views/DiscoverView.vue

224 lines
9.0 KiB
Vue
Raw Normal View History

2026-02-21 09:53:31 -05:00
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { businessService } from '@/services/businessService';
import { API_URL } from '@/services/apiClient';
import type { Business } from '@/types';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import FavoriteButton from '@/components/FavoriteButton.vue';
import { analyticsService } from '@/services/analyticsService';
2026-02-21 09:53:31 -05:00
const { t } = useI18n();
const router = useRouter();
const businesses = ref<Business[]>([]);
const isLoading = ref(true);
const selectedArea = ref('Todas');
const selectedCategory = ref('Todas');
const searchQuery = ref('');
2026-02-21 09:53:31 -05:00
onMounted(async () => {
analyticsService.logEvent({ event_name: 'screen_view', screen_name: 'Discover' });
try {
businesses.value = await businessService.getAllBusinesses();
} catch (error) {
console.error('Error loading tourist spots:', error);
} finally {
isLoading.value = false;
}
});
function handleExplore(biz: Business) {
analyticsService.logEvent({
event_name: 'promo_click',
item_id: biz.name,
properties: { business_id: biz.id }
});
router.push('/business/' + biz.id);
}
const filteredBusinesses = computed(() => {
let filtered = businesses.value;
if (selectedArea.value !== 'Todas') {
filtered = filtered.filter(b => b.area === selectedArea.value);
}
if (selectedCategory.value !== 'Todas') {
filtered = filtered.filter(b => b.category === selectedCategory.value);
}
if (searchQuery.value.trim() !== '') {
const query = searchQuery.value.toLowerCase();
filtered = filtered.filter(b =>
b.name.toLowerCase().includes(query) ||
(b.area && b.area.toLowerCase().includes(query)) ||
(b.category && b.category.toLowerCase().includes(query))
);
}
2026-02-21 09:53:31 -05:00
return filtered;
});
const categories = computed<string[]>(() => {
const cats = new Set(businesses.value.map(b => b.category).filter(Boolean) as string[]);
return ['Todas', ...Array.from(cats)];
});
const areas = computed<string[]>(() => {
const ars = new Set(businesses.value.map(b => b.area).filter(Boolean) as string[]);
return ['Todas', ...Array.from(ars)];
});
2026-02-21 09:53:31 -05:00
function getImageUrl(path: string | null | undefined) {
if (!path) return '/default-business.jpg';
if (path.startsWith('http')) return path;
return `${API_URL}${path.startsWith('/') ? '' : '/'}${path}`;
}
function getCategoryIcon(category: string) {
const icons: Record<string, string> = {
'Restaurante': 'restaurant',
'Turismo': 'landscape',
'Bebidas': 'local_bar',
'Comercio': 'store',
'Hotel': 'hotel',
'Café': 'local_cafe'
};
return icons[category] || 'place';
}
</script>
<template>
<div class="discover-view bg-white dark:bg-zinc-950 text-slate-900 dark:text-gray-200 min-h-screen pb-32">
<!-- Header Area -->
<header class="bg-white/80 dark:bg-zinc-950/80 backdrop-blur-md px-6 pt-12 pb-4 sticky top-0 z-50 border-b border-slate-100 dark:border-zinc-800">
<div class="flex justify-between items-center mb-2">
<button @click="router.back()" class="material-icons text-3xl font-bold cursor-pointer dark:text-white">arrow_back</button>
<h1 class="text-2xl font-extrabold tracking-tighter dark:text-white uppercase italic">SIBU</h1>
<div class="w-8"></div>
2026-02-21 09:53:31 -05:00
</div>
</header>
<main class="px-5 pt-6">
<!-- Search Bar -->
<div class="mb-6">
<div class="relative flex items-center bg-slate-50 dark:bg-zinc-900 rounded-2xl shadow-sm p-1 border border-slate-100 dark:border-zinc-800">
<div class="p-3">
<span class="material-icons text-slate-400 dark:text-gray-500">search</span>
2026-02-21 09:53:31 -05:00
</div>
<input
v-model="searchQuery"
class="w-full bg-transparent border-none focus:ring-0 text-slate-900 dark:text-gray-200 font-medium placeholder:text-slate-400 dark:placeholder:text-gray-500 text-[15px]"
:placeholder="t('discover.searchPlaceholder') || 'Explora los mejores lugares en Chiriquí'"
type="text"
/>
2026-02-21 09:53:31 -05:00
</div>
</div>
2026-02-21 09:53:31 -05:00
<!-- Region & Category Selectors -->
<div class="flex gap-3 mb-8">
<div class="flex-1 bg-slate-50 dark:bg-zinc-900 p-4 rounded-2xl shadow-sm border border-slate-100 dark:border-zinc-800 flex flex-col gap-1">
<div class="flex items-center gap-2">
<span class="material-icons text-blue-500 text-[18px] font-bold">location_on</span>
<span class="text-[10px] font-bold text-slate-400 dark:text-gray-500 uppercase tracking-widest">Región</span>
2026-02-21 09:53:31 -05:00
</div>
<select v-model="selectedArea" class="bg-transparent border-none focus:ring-0 p-0 font-bold text-slate-900 dark:text-white text-sm appearance-none cursor-pointer">
<option value="Todas">{{ t('discover.allAreas') }}</option>
<option v-for="area in areas" :key="area" :value="area">{{ area }}</option>
</select>
</div>
<div class="flex-1 bg-slate-50 dark:bg-zinc-900 p-4 rounded-2xl shadow-sm border border-slate-100 dark:border-zinc-800 flex flex-col gap-1">
<div class="flex items-center gap-2">
<span class="material-icons text-blue-500 text-[18px] font-bold">category</span>
<span class="text-[10px] font-bold text-slate-400 dark:text-gray-500 uppercase tracking-widest">Categoría</span>
2026-02-21 09:53:31 -05:00
</div>
<select v-model="selectedCategory" class="bg-transparent border-none focus:ring-0 p-0 font-bold text-slate-900 dark:text-white text-sm appearance-none cursor-pointer">
<option v-for="cat in categories" :key="cat" :value="cat">{{ cat }}</option>
</select>
2026-02-21 09:53:31 -05:00
</div>
</div>
<!-- Loading State -->
<div v-if="isLoading" class="flex flex-col items-center justify-center py-20">
<div class="w-10 h-10 border-4 border-blue-500/20 border-t-blue-500 rounded-full animate-spin mb-4"></div>
<p class="text-slate-500 dark:text-gray-400 font-bold tracking-widest uppercase text-xs">Sincronizando con SIBU...</p>
2026-02-21 09:53:31 -05:00
</div>
<!-- Empty State -->
<div v-else-if="filteredBusinesses.length === 0" class="text-center py-20">
<div class="bg-slate-50 dark:bg-zinc-900 rounded-[2rem] p-10 border border-dashed border-slate-200 dark:border-zinc-800">
<span class="material-icons text-slate-300 dark:text-gray-600 text-5xl mb-4">search_off</span>
<h3 class="text-lg font-bold text-slate-900 dark:text-white mb-2">Sin resultados</h3>
<p class="text-slate-500 dark:text-gray-400 mb-6">La búsqueda no devolvió datos en esta frecuencia.</p>
<button @click="selectedArea = 'Todas'; selectedCategory = 'Todas'; searchQuery = ''" class="bg-blue-500 text-white px-8 py-3 rounded-xl font-black uppercase text-xs tracking-widest active:scale-95 transition-all">
2026-02-21 09:53:31 -05:00
REINICIAR SENSORES
</button>
</div>
</div>
<!-- Business Grid -->
2026-02-21 09:53:31 -05:00
<TransitionGroup
v-else
name="fade"
2026-02-21 09:53:31 -05:00
tag="div"
class="grid grid-cols-2 gap-4"
2026-02-21 09:53:31 -05:00
>
<div v-for="biz in filteredBusinesses"
2026-02-21 09:53:31 -05:00
:key="biz.id"
class="bg-slate-50 dark:bg-zinc-900 rounded-[2rem] overflow-hidden shadow-sm border border-slate-100 dark:border-zinc-800 flex flex-col active:scale-95 transition-all cursor-pointer"
2026-02-21 09:53:31 -05:00
@click="handleExplore(biz)"
>
<div class="relative h-44">
<img :src="getImageUrl(biz.image_url)" alt="" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-gradient-to-b from-black/40 to-transparent"></div>
<div class="absolute top-3 left-3 bg-black/60 backdrop-blur-md p-1.5 rounded-xl border border-white/10">
<span class="material-icons text-blue-400 text-[18px] flex items-center justify-center">
{{ getCategoryIcon(biz.category || '') }}
</span>
2026-02-21 09:53:31 -05:00
</div>
<div class="absolute top-3 right-3">
<FavoriteButton
item-type="business"
:item-id="biz.id"
:item-name="biz.name"
:item-image="biz.image_url || undefined"
/>
</div>
</div>
<div class="p-4 flex flex-col flex-grow">
<h3 class="font-bold text-slate-900 dark:text-white text-[15px] leading-tight mb-2">{{ biz.name }}</h3>
<div class="mt-auto flex items-center justify-between">
<div class="flex items-center gap-1">
<span class="material-icons text-blue-400 text-[14px]">near_me</span>
<span class="text-xs font-semibold text-slate-500 dark:text-gray-500">{{ biz.area }}</span>
2026-02-21 09:53:31 -05:00
</div>
</div>
</div>
</div>
</TransitionGroup>
</main>
</div>
</template>
<style scoped>
.discover-view {
font-family: 'Plus Jakarta Sans', sans-serif;
2026-02-21 09:53:31 -05:00
}
.fade-move,
.fade-enter-active,
.fade-leave-active {
2026-02-21 09:53:31 -05:00
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.fade-enter-from,
.fade-leave-to {
2026-02-21 09:53:31 -05:00
opacity: 0;
transform: translateY(20px);
2026-02-21 09:53:31 -05:00
}
.fade-leave-active {
position: absolute;
2026-02-21 09:53:31 -05:00
}
</style>