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

486 lines
11 KiB
Vue

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { businessService } from '@/services/businessService'
import { couponsService } from '@/services/couponsService'
import { API_URL } from '@/services/apiClient'
import type { Business, Coupon } from '@/types'
import FavoriteButton from '@/components/FavoriteButton.vue'
const route = useRoute()
const router = useRouter()
const business = ref<Business | null>(null)
const coupons = ref<Coupon[]>([])
const isLoading = ref(true)
import { analyticsService } from '@/services/analyticsService'
onMounted(async () => {
const id = route.params.id as string
try {
const [bizData, allCoupons] = await Promise.all([
businessService.getBusiness(id),
couponsService.getAllCoupons({ active_only: true })
])
business.value = bizData
// Filter coupons for this business
coupons.value = allCoupons.filter(c => c.business_id === id)
analyticsService.logEvent({
event_name: 'screen_view',
screen_name: 'BusinessDetails',
item_id: bizData.name,
properties: { business_id: id }
})
} catch (e) {
console.error('Failed to load business details', e)
} finally {
isLoading.value = false
}
})
function getImageUrl(path: string | null | undefined) {
if (!path) return '/default-business.jpg'
if (path.startsWith('http')) return path
return `${API_URL}${path.startsWith('/') ? '' : '/'}${path}`
}
const goBack = () => router.back()
function handleDirections() {
analyticsService.logEvent({
event_name: 'promo_click',
item_id: 'directions_' + business.value?.name,
properties: {
business_id: business.value?.id,
action: 'get_directions'
}
})
window.open(`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(business.value?.address || '')}`, '_blank')
}
</script>
<template>
<div class="business-details-view" v-if="!isLoading && business">
<!-- Hero Section -->
<div class="hero-section">
<img :src="getImageUrl(business.image_url)" alt="Business Image" class="hero-image" />
<div class="hero-overlay"></div>
<button class="back-floating" @click="goBack">
<span class="material-icons">arrow_back</span>
</button>
<div class="fav-floating">
<FavoriteButton
item-type="business"
:item-id="business.id"
:item-name="business.name"
:item-image="business.image_url || undefined"
/>
</div>
<div class="hero-content">
<div class="category-badge premium-font">{{ business.category }}</div>
<h1 class="business-name premium-font">{{ business.name }}</h1>
<div class="area-tag">
<span class="material-icons">location_on</span>
{{ business.area }}
</div>
</div>
</div>
<!-- Details Content -->
<div class="details-container">
<div class="premium-story">
<h2 class="premium-font">Explora Nuestra Historia Para Una Cocina Refinada Y Un Ambiente Atemporal</h2>
<p>"Nuestra historia es una de crecimiento, exploración y recuerdos culinarios inolvidables, donde cada capítulo se sirve con elegancia."</p>
</div>
<!-- Highlights Grid (Inspired by the frame) -->
<div class="highlights-grid">
<div class="highlight-item">
<div class="highlight-header">
<h3 class="premium-font">Herencia Atemporal</h3>
<div class="divider"></div>
</div>
<p>Platos de autor que evolucionan con inspiración y cultura local.</p>
</div>
<div class="highlight-item">
<div class="highlight-header">
<h3 class="premium-font">Platos de Clase Mundial</h3>
<div class="divider"></div>
</div>
<p>Experiencia gastronómica diseñada para deleitar los sentidos más exigentes.</p>
</div>
<div class="highlight-item">
<div class="highlight-header">
<h3 class="premium-font">Emoción y Elegancia</h3>
<div class="divider"></div>
</div>
<p>Veladas realzadas por el encanto atemporal de un ambiente exclusivo.</p>
</div>
<div class="highlight-item">
<div class="highlight-header">
<h3 class="premium-font">Experiencia Inigualable</h3>
<div class="divider"></div>
</div>
<p>Servicio personalizado desde un anfitrión dedicado para tu comodidad.</p>
</div>
</div>
<!-- Info Section -->
<div class="info-sections">
<div class="info-card">
<span class="material-icons">map</span>
<div class="info-text">
<h4>Dirección</h4>
<p>{{ business.address }}</p>
</div>
</div>
<div class="info-card">
<span class="material-icons">phone</span>
<div class="info-text">
<h4>Contacto</h4>
<p>{{ business.phone || 'No disponible' }}</p>
</div>
</div>
<div v-if="business.social_media" class="info-card">
<span class="material-icons">language</span>
<div class="info-text">
<h4>Redes Sociales</h4>
<p>{{ business.social_media }}</p>
</div>
</div>
<div class="info-card">
<span class="material-icons">directions</span>
<div class="info-text">
<h4>Cómo llegar</h4>
<button class="track-directions-btn" @click="handleDirections">
Ver mapa y ruta
</button>
</div>
</div>
</div>
<!-- Offers Section -->
<div v-if="coupons.length > 0" class="offers-section">
<h2 class="section-title premium-font">Ofertas Disponibles</h2>
<div class="coupons-grid">
<div v-for="coupon in coupons" :key="coupon.id" class="coupon-card-detail">
<div class="coupon-header-flex">
<div class="coupon-discount">{{ coupon.discount_percentage }}% OFF</div>
<FavoriteButton
item-type="coupon"
:item-id="coupon.id"
:item-name="coupon.title"
:item-image="coupon.image_url || undefined"
/>
</div>
<div class="coupon-info">
<h3>{{ coupon.title }}</h3>
<p>{{ coupon.description }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-else-if="isLoading" class="loading-full">
<div class="loader"></div>
<p>Cargando experiencia premium...</p>
</div>
</template>
<style scoped>
.business-details-view {
min-height: 100vh;
background: var(--bg-primary);
color: var(--text-primary);
padding-bottom: 60px;
}
.premium-font {
font-family: 'Playfair Display', serif;
}
/* Hero Section */
.hero-section {
position: relative;
height: 60vh;
width: 100%;
overflow: hidden;
}
.hero-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.hero-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to bottom, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.8) 100%);
}
.back-floating {
position: absolute;
top: 20px;
left: 20px;
background: rgba(255,255,255,0.2);
backdrop-filter: blur(10px);
border: none;
width: 44px;
height: 44px;
border-radius: 50%;
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10;
}
.fav-floating {
position: absolute;
top: 20px;
right: 20px;
z-index: 10;
}
.coupon-header-flex {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.hero-content {
position: absolute;
bottom: 40px;
left: 40px;
right: 40px;
color: white;
}
.category-badge {
background: var(--active-color);
color: white;
padding: 6px 16px;
border-radius: 100px;
display: inline-block;
font-size: 0.9rem;
font-weight: 700;
margin-bottom: 16px;
text-transform: uppercase;
letter-spacing: 1px;
}
.business-name {
font-size: 3.5rem;
margin: 0 0 12px 0;
line-height: 1.1;
}
.area-tag {
display: flex;
align-items: center;
gap: 8px;
font-size: 1.2rem;
opacity: 0.9;
}
/* Content */
.details-container {
max-width: 1000px;
margin: -40px auto 0;
position: relative;
background: var(--bg-primary);
border-radius: 30px 30px 0 0;
padding: 60px 40px;
box-shadow: 0 -20px 40px rgba(0,0,0,0.1);
}
.premium-story {
text-align: center;
max-width: 700px;
margin: 0 auto 60px;
}
.premium-story h2 {
font-size: 2.2rem;
margin-bottom: 24px;
color: var(--text-primary);
}
.premium-story p {
font-size: 1.2rem;
font-style: italic;
color: var(--text-secondary);
line-height: 1.6;
}
/* Highlights Grid */
.highlights-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 40px;
margin-bottom: 80px;
}
.highlight-item {
padding: 20px;
}
.highlight-header {
margin-bottom: 16px;
}
.highlight-header h3 {
font-size: 1.4rem;
margin-bottom: 8px;
}
.divider {
width: 100%;
height: 1px;
background: var(--border-color);
}
.highlight-item p {
color: var(--text-secondary);
line-height: 1.5;
}
/* Info Cards */
.info-sections {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 80px;
}
.info-card {
background: var(--bg-secondary);
padding: 24px;
border-radius: 20px;
display: flex;
align-items: flex-start;
gap: 16px;
}
.info-card .material-icons {
color: var(--active-color);
font-size: 2rem;
}
.info-text h4 {
margin: 0 0 4px 0;
font-size: 0.9rem;
text-transform: uppercase;
color: var(--text-secondary);
letter-spacing: 1px;
}
.info-text p {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
}
.track-directions-btn {
background: var(--active-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: 8px;
font-weight: 700;
cursor: pointer;
margin-top: 4px;
}
/* Offers Section */
.offers-section {
border-top: 1px solid var(--border-color);
padding-top: 60px;
}
.section-title {
font-size: 2rem;
margin-bottom: 40px;
}
.coupons-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
}
.coupon-card-detail {
background: var(--card-bg);
border: 2px dashed var(--active-color);
padding: 24px;
border-radius: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.coupon-discount {
font-size: 1.5rem;
font-weight: 900;
color: var(--active-color);
}
.coupon-info h3 {
margin: 0 0 8px 0;
font-size: 1.2rem;
}
.coupon-info p {
margin: 0;
color: var(--text-secondary);
font-size: 0.95rem;
}
/* Loader */
.loading-full {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
}
.loader {
width: 50px;
height: 50px;
border: 3px solid var(--bg-secondary);
border-top-color: var(--active-color);
border-radius: 50%;
animation: spin 1s infinite linear;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@media (max-width: 768px) {
.hero-section { height: 50vh; }
.business-name { font-size: 2.5rem; }
.highlights-grid { grid-template-columns: 1fr; }
.details-container { padding: 40px 20px; }
.hero-content { left: 20px; bottom: 30px; }
}
</style>