Refactor: Map UI improvements, ETA metrics, Schedule fixes, and Transport Detail styling
This commit is contained in:
@ -98,6 +98,7 @@ function selectStopFromSearch(stop: BusStop) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openUberSearch() {
|
function openUberSearch() {
|
||||||
|
showPromos.value = false; // Cerramos ofertas para evitar solapamiento
|
||||||
showUberSearch.value = true;
|
showUberSearch.value = true;
|
||||||
showRoutesToggle.value = true; // Forzar que al abrir estemos en modo rutas
|
showRoutesToggle.value = true; // Forzar que al abrir estemos en modo rutas
|
||||||
}
|
}
|
||||||
@ -180,6 +181,10 @@ function closeBusStopModal() {
|
|||||||
selectedBusStop.value = null;
|
selectedBusStop.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reloadPage() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
function handlePromoClick(promo: any) {
|
function handlePromoClick(promo: any) {
|
||||||
selectedPromo.value = promo;
|
selectedPromo.value = promo;
|
||||||
showPromoModal.value = true;
|
showPromoModal.value = true;
|
||||||
@ -503,7 +508,9 @@ async function updateActiveUnits() {
|
|||||||
if (!isLoaded.value) return;
|
if (!isLoaded.value) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// No-op for now. Backend is purely Supabase now.
|
if (routeStore.selectedRouteId && paradaCercana.value) {
|
||||||
|
await calcularETA(routeStore.selectedRouteId, paradaCercana.value as BusStop);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to update active units', e);
|
console.error('Failed to update active units', e);
|
||||||
}
|
}
|
||||||
@ -841,21 +848,35 @@ async function calculateWalkingPath(origin: { lat: number, lng: number }, target
|
|||||||
<span class="trigger-label">ver rutas</span>
|
<span class="trigger-label">ver rutas</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Nuevo Banner de Parada Cercana Alineado -->
|
<!-- Nuevo Banner de Parada Cercana Alineado (Redimensionado y con ETA) -->
|
||||||
<div
|
<div
|
||||||
v-if="paradaCercana && routeStore.selectedRouteId && !showETACard"
|
v-if="paradaCercana && routeStore.selectedRouteId && !showETACard"
|
||||||
class="uber-search-trigger best-stop-banner"
|
class="best-stop-banner-compact"
|
||||||
>
|
>
|
||||||
<span class="material-icons text-yellow-500 mr-3">directions_bus</span>
|
<div class="banner-icon-bg">
|
||||||
<div class="flex flex-col flex-1 truncate">
|
<span class="material-icons text-white text-[16px]">directions_bus</span>
|
||||||
<span class="text-[10px] uppercase font-bold text-yellow-500 leading-tight">Parada cercana</span>
|
|
||||||
<span class="trigger-text truncate leading-tight">{{ paradaCercana?.name }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="text-[11px] font-bold bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded-lg ml-2 whitespace-nowrap">
|
|
||||||
{{ (distanciaMetros && distanciaMetros < 1000) ? Math.round(distanciaMetros) + 'm' : (distanciaMetros ? (distanciaMetros / 1000).toFixed(1) + 'km' : '') }}
|
<div class="flex flex-col flex-1 truncate ml-2">
|
||||||
|
<span class="text-[9px] uppercase font-bold text-gray-500 dark:text-gray-400 leading-none">Tiempo de llegada</span>
|
||||||
|
<span class="trigger-text-compact truncate leading-tight">{{ paradaCercana?.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button @click.stop="clearAllMapData" class="ml-3 p-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
|
|
||||||
<span class="material-icons text-[20px] text-gray-400 hover:text-red-500">close</span>
|
<div class="eta-badge">
|
||||||
|
<template v-if="etaCargando">
|
||||||
|
<div class="eta-loader"></div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="busesActivos.length > 0">
|
||||||
|
<span class="eta-value">{{ busesActivos[0].etaMinutos > 0 ? busesActivos[0].etaMinutos : '0' }}</span>
|
||||||
|
<span class="eta-unit">min</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="eta-unit">-- min</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button @click.stop="reloadPage" class="ml-2 p-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
|
||||||
|
<span class="material-icons text-[18px] text-gray-400 hover:text-red-500">close</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1531,6 +1552,76 @@ html.light-theme .uber-search-trigger-compact {
|
|||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.best-stop-banner-compact {
|
||||||
|
flex: 1;
|
||||||
|
background: var(--header-bg);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
height: 40px; /* Más compacto (de 44px a 40px) */
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 10px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
max-width: none;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-icon-bg {
|
||||||
|
background: #EAB308; /* yellow-500 */
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-text-compact {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eta-badge {
|
||||||
|
background: rgba(234, 179, 8, 0.1); /* yellow-500 with opacity */
|
||||||
|
color: #EAB308;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 2px;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-left: 8px;
|
||||||
|
border: 1px solid rgba(234, 179, 8, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.eta-value {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eta-unit {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eta-loader {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border: 2px solid #EAB308;
|
||||||
|
border-top-color: transparent;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
.uber-search-panel {
|
.uber-search-panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 70px; /* Debajo del header superior */
|
top: 70px; /* Debajo del header superior */
|
||||||
|
|||||||
@ -56,7 +56,7 @@ function getScheduleDay(schedule: any): 'today' | 'tomorrow' | 'other' {
|
|||||||
|
|
||||||
// Comparar con el tipo del horario
|
// Comparar con el tipo del horario
|
||||||
// Nota: Si el horario es 'todos', cuenta para hoy y mañana (pero priorizamos hoy si pides hoy)
|
// Nota: Si el horario es 'todos', cuenta para hoy y mañana (pero priorizamos hoy si pides hoy)
|
||||||
const type = schedule.schedule_type || 'todos'
|
const type = (schedule.schedule_type as string) || 'todos'
|
||||||
|
|
||||||
const isToday = type === todayType || type === 'todos'
|
const isToday = type === todayType || type === 'todos'
|
||||||
const isTomorrow = type === tomorrowType || type === 'todos'
|
const isTomorrow = type === tomorrowType || type === 'todos'
|
||||||
@ -81,27 +81,35 @@ function getDayLabel(schedule: any): string {
|
|||||||
// ── Filtrado de horarios
|
// ── Filtrado de horarios
|
||||||
const filteredSchedules = computed(() => {
|
const filteredSchedules = computed(() => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
const tomorrow = new Date(now)
|
||||||
|
tomorrow.setDate(now.getDate() + 1)
|
||||||
|
|
||||||
const hhmmAhora = now.getHours() * 100 + now.getMinutes()
|
const hhmmAhora = now.getHours() * 100 + now.getMinutes()
|
||||||
|
|
||||||
return scheduleStore.schedules.filter(s => {
|
return scheduleStore.schedules.filter(s => {
|
||||||
const d = getScheduleDay(s)
|
const type = (s.schedule_type as string) || 'todos'
|
||||||
|
const todayType = (now.getDay() === 0 || now.getDay() === 6) ? 'weekend' : 'weekday'
|
||||||
|
const tomorrowType = (tomorrow.getDay() === 0 || tomorrow.getDay() === 6) ? 'weekend' : 'weekday'
|
||||||
|
|
||||||
|
const isActuallyToday = type === todayType || type === 'todos'
|
||||||
|
const isActuallyTomorrow = type === tomorrowType || type === 'todos'
|
||||||
|
|
||||||
|
// Filtro Hoy: Es hoy Y no ha pasado (o es de los que dice salir en este rango)
|
||||||
|
if (dayFilter.value === 'today') {
|
||||||
const [hStr, mStr] = (s.departure_time || '00:00').split(':')
|
const [hStr, mStr] = (s.departure_time || '00:00').split(':')
|
||||||
const h = parseInt(hStr || '0')
|
const h = parseInt(hStr || '0')
|
||||||
const m = parseInt(mStr || '0')
|
const m = parseInt(mStr || '0')
|
||||||
const hhmmSched = h * 100 + m
|
const hhmmSched = h * 100 + m
|
||||||
const isPassed = hhmmSched < hhmmAhora - 2 // margen de 2 min
|
const isPassed = hhmmSched < hhmmAhora - 2 // margen de 2 min
|
||||||
|
return isActuallyToday && !isPassed
|
||||||
// Filtro Hoy: Es hoy Y no ha pasado (o es de los que dice salir en este rango)
|
|
||||||
if (dayFilter.value === 'today') {
|
|
||||||
return d === 'today' && !isPassed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtro Mañana: Es mañana
|
// Filtro Mañana: Es mañana (sin importar si pasó la hora hoy)
|
||||||
if (dayFilter.value === 'tomorrow') {
|
if (dayFilter.value === 'tomorrow') {
|
||||||
return d === 'tomorrow'
|
return isActuallyTomorrow
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtro Todos: Mostrar todo sin importar si pasó o es otro día
|
// Filtro Todos: Mostrar todo
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -281,7 +289,7 @@ onUnmounted(() => {
|
|||||||
v-for="(schedule, i) in filteredSchedules"
|
v-for="(schedule, i) in filteredSchedules"
|
||||||
:key="schedule.id"
|
:key="schedule.id"
|
||||||
class="schedule-card"
|
class="schedule-card"
|
||||||
:class="`schedule-card--${getBusStatus(schedule.departure_time)}`"
|
:class="dayFilter === 'today' ? `schedule-card--${getBusStatus(schedule.departure_time)}` : 'schedule-card--upcoming'"
|
||||||
>
|
>
|
||||||
<!-- Borde izquierdo decorativo -->
|
<!-- Borde izquierdo decorativo -->
|
||||||
<div class="card-accent"></div>
|
<div class="card-accent"></div>
|
||||||
@ -295,8 +303,8 @@ onUnmounted(() => {
|
|||||||
<!-- Info -->
|
<!-- Info -->
|
||||||
<div class="card-info">
|
<div class="card-info">
|
||||||
<div class="card-top-row">
|
<div class="card-top-row">
|
||||||
<span class="day-tag" :class="`day-tag--${getScheduleDay(schedule)}`">
|
<span class="day-tag" :class="dayFilter === 'today' ? `day-tag--${getScheduleDay(schedule)}` : 'day-tag--tomorrow'">
|
||||||
{{ getDayLabel(schedule) }}
|
{{ dayFilter === 'tomorrow' ? 'Mañana' : (dayFilter === 'all' ? getDayLabel(schedule) : getDayLabel(schedule)) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="i === 0 && getBusStatus(schedule.departure_time) === 'departing'" class="departing-pulse">SALIENDO</span>
|
<span v-if="i === 0 && getBusStatus(schedule.departure_time) === 'departing'" class="departing-pulse">SALIENDO</span>
|
||||||
</div>
|
</div>
|
||||||
@ -308,11 +316,11 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Badge estado -->
|
<!-- Badge estado -->
|
||||||
<div class="status-badge" :class="`status-badge--${getBusStatus(schedule.departure_time)}`">
|
<div class="status-badge" :class="dayFilter === 'today' ? `status-badge--${getBusStatus(schedule.departure_time)}` : 'status-badge--upcoming'">
|
||||||
<span class="material-icons status-icon">
|
<span class="material-icons status-icon">
|
||||||
{{ getBusStatus(schedule.departure_time) === 'departing' ? 'directions_run' :
|
{{ (dayFilter === 'today' && getBusStatus(schedule.departure_time) === 'departing') ? 'directions_run' :
|
||||||
getBusStatus(schedule.departure_time) === 'ontime' ? 'check_circle' :
|
(dayFilter === 'today' && getBusStatus(schedule.departure_time) === 'ontime') ? 'check_circle' :
|
||||||
getBusStatus(schedule.departure_time) === 'passed' ? 'history' : 'access_time' }}
|
(dayFilter === 'today' && getBusStatus(schedule.departure_time) === 'passed') ? 'history' : 'access_time' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
// Solo mostrar el header con tabs en las vistas principales
|
||||||
|
const isMainView = computed(() => {
|
||||||
|
return route.name === 'TaxisLocales' || route.name === 'ViajesTuristicos'
|
||||||
|
})
|
||||||
const mountError = ref(false)
|
const mountError = ref(false)
|
||||||
|
|
||||||
const reloadPage = () => {
|
const reloadPage = () => {
|
||||||
@ -24,7 +29,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="taxi-view">
|
<div class="taxi-view">
|
||||||
<header class="header-main">
|
<header v-if="isMainView" class="header-main">
|
||||||
<h1 class="brand-title">{{ t('taxi.title') }}</h1>
|
<h1 class="brand-title">{{ t('taxi.title') }}</h1>
|
||||||
<div class="hub-tabs">
|
<div class="hub-tabs">
|
||||||
<div class="tabs-background">
|
<div class="tabs-background">
|
||||||
|
|||||||
@ -54,13 +54,13 @@ const volver = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="shuttle-detalle-container bg-surface pb-24 min-h-screen relative">
|
<div class="shuttle-detalle-container bg-[var(--bg-primary)] pb-24 min-h-screen relative">
|
||||||
<!-- Header con botón volver -->
|
<!-- Header con botón volver (Solid background to avoid overlap on scroll) -->
|
||||||
<div class="sticky top-0 z-10 bg-surface border-b border-border flex items-center gap-3 px-4 py-3 shadow-sm" style="padding-top: max(env(safe-area-inset-top), 12px);">
|
<div class="sticky top-0 z-50 bg-[var(--bg-primary)] border-b border-border flex items-center gap-3 px-4 py-3 shadow-md" style="padding-top: max(env(safe-area-inset-top), 12px);">
|
||||||
<button @click="volver" class="p-2 rounded-full hover:bg-hover flex items-center justify-center transition">
|
<button @click="volver" class="p-2 rounded-full hover:bg-[var(--hover-bg)] flex items-center justify-center transition">
|
||||||
<span class="material-icons text-text-primary">arrow_back</span>
|
<span class="material-icons text-[var(--text-primary)]">arrow_back</span>
|
||||||
</button>
|
</button>
|
||||||
<h1 class="font-bold text-text-primary text-lg truncate flex-1">
|
<h1 class="font-bold text-[var(--text-primary)] text-lg truncate flex-1">
|
||||||
{{ shuttle?.company_name || 'Detalle del viaje' }}
|
{{ shuttle?.company_name || 'Detalle del viaje' }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@ -90,32 +90,32 @@ const volver = () => {
|
|||||||
class="w-full h-full object-cover"
|
class="w-full h-full object-cover"
|
||||||
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'shuttle')"
|
@error="(e) => (e.target as HTMLImageElement).src = getImageUrl(null, 'shuttle')"
|
||||||
/>
|
/>
|
||||||
<div class="absolute bottom-3 left-3 bg-surface/90 backdrop-blur-sm px-3 py-1 rounded-full text-sm font-bold shadow-sm flex items-center gap-1">
|
<div class="absolute bottom-3 left-3 bg-[var(--bg-primary)]/90 backdrop-blur-sm px-3 py-1 rounded-full text-sm font-bold shadow-sm flex items-center gap-1">
|
||||||
<span class="material-icons text-sm" style="color: var(--active-color)">directions_bus</span>
|
<span class="material-icons text-sm" style="color: var(--active-color)">directions_bus</span>
|
||||||
{{ shuttle.vehicle_type }}
|
{{ shuttle.vehicle_type }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rutas Origen - Destino prominente -->
|
<!-- Rutas Origen - Destino prominente -->
|
||||||
<div class="bg-surface rounded-2xl p-5 shadow-sm space-y-3 border border-border">
|
<div class="bg-[var(--bg-secondary)] rounded-2xl p-5 shadow-sm space-y-3 border border-border">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex flex-col flex-1">
|
<div class="flex flex-col flex-1">
|
||||||
<span class="text-xs text-text-tertiary font-semibold mb-1 uppercase tracking-wider">Origen</span>
|
<span class="text-xs text-[var(--text-secondary)] font-semibold mb-1 uppercase tracking-wider">Origen</span>
|
||||||
<span class="font-bold text-text-primary text-lg leading-tight break-words">
|
<span class="font-bold text-[var(--text-primary)] text-lg leading-tight break-words">
|
||||||
{{ shuttle.origin }}
|
{{ shuttle.origin }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col items-center px-4 shrink-0">
|
<div class="flex flex-col items-center px-4 shrink-0">
|
||||||
<span class="text-xs text-text-secondary font-bold mb-1">{{ shuttle.estimated_duration }}</span>
|
<span class="text-xs text-[var(--text-secondary)] font-bold mb-1">{{ shuttle.estimated_duration }}</span>
|
||||||
<div class="w-16 border-t-2 border-dashed border-border relative my-1">
|
<div class="w-16 border-t-2 border-dashed border-border relative my-1">
|
||||||
<span class="material-icons absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-text-secondary bg-surface px-1 text-sm">east</span>
|
<span class="material-icons absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-[var(--text-secondary)] bg-[var(--bg-secondary)] px-1 text-sm">east</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col flex-1 text-right">
|
<div class="flex flex-col flex-1 text-right">
|
||||||
<span class="text-xs text-text-tertiary font-semibold mb-1 uppercase tracking-wider">Destino</span>
|
<span class="text-xs text-[var(--text-secondary)] font-semibold mb-1 uppercase tracking-wider">Destino</span>
|
||||||
<span class="font-bold text-text-primary text-lg leading-tight break-words">
|
<span class="font-bold text-[var(--text-primary)] text-lg leading-tight break-words">
|
||||||
{{ shuttle.destination }}
|
{{ shuttle.destination }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -123,28 +123,28 @@ const volver = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Info principal -->
|
<!-- Info principal -->
|
||||||
<div class="bg-surface rounded-2xl p-5 shadow-sm space-y-4 border border-border">
|
<div class="bg-[var(--bg-secondary)] rounded-2xl p-5 shadow-sm space-y-4 border border-border">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-xl font-bold text-text-primary">{{ shuttle.company_name }}</h2>
|
<h2 class="text-xl font-bold text-[var(--text-primary)]">{{ shuttle.company_name }}</h2>
|
||||||
<p class="text-text-secondary text-sm mt-1 leading-relaxed" v-if="shuttle.description" style="white-space: pre-wrap;">{{ shuttle.description }}</p>
|
<p class="text-[var(--text-secondary)] text-sm mt-1 leading-relaxed" v-if="shuttle.description" style="white-space: pre-wrap;">{{ shuttle.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4 pt-4 border-t border-border">
|
<div class="grid grid-cols-2 gap-4 pt-4 border-t border-border">
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<span class="text-xs text-text-tertiary uppercase tracking-wider font-semibold flex items-center gap-1"><span class="material-icons text-sm">schedule</span> Hora de salida</span>
|
<span class="text-xs text-[var(--text-secondary)] uppercase tracking-wider font-semibold flex items-center gap-1"><span class="material-icons text-sm">schedule</span> Hora de salida</span>
|
||||||
<span class="font-semibold text-text-primary bg-bg-secondary p-2 rounded-lg text-sm text-center border border-border">
|
<span class="font-semibold text-[var(--text-primary)] bg-[var(--bg-primary)] p-2 rounded-lg text-sm text-center border border-border">
|
||||||
{{ shuttle.departure_times }}
|
{{ shuttle.departure_times }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<span class="text-xs text-text-tertiary uppercase tracking-wider font-semibold flex items-center gap-1"><span class="material-icons text-sm">swap_horiz</span> Tipo de viaje</span>
|
<span class="text-xs text-[var(--text-secondary)] uppercase tracking-wider font-semibold flex items-center gap-1"><span class="material-icons text-sm">swap_horiz</span> Tipo de viaje</span>
|
||||||
<span class="font-semibold text-text-primary bg-bg-secondary p-2 rounded-lg text-sm text-center border border-border capitalize">
|
<span class="font-semibold text-[var(--text-primary)] bg-[var(--bg-primary)] p-2 rounded-lg text-sm text-center border border-border capitalize">
|
||||||
{{ shuttle.trip_type.replace('_', ' ') }}
|
{{ shuttle.trip_type.replace('_', ' ') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-1" v-if="shuttle.english_speaking">
|
<div class="flex flex-col gap-1" v-if="shuttle.english_speaking">
|
||||||
<span class="text-xs text-text-tertiary uppercase tracking-wider font-semibold flex items-center gap-1"><span class="material-icons text-sm">g_translate</span> Idiomas</span>
|
<span class="text-xs text-[var(--text-secondary)] uppercase tracking-wider font-semibold flex items-center gap-1"><span class="material-icons text-sm">g_translate</span> Idiomas</span>
|
||||||
<span class="font-semibold text-text-primary bg-bg-secondary p-2 rounded-lg text-sm text-center border border-border">
|
<span class="font-semibold text-[var(--text-primary)] bg-[var(--bg-primary)] p-2 rounded-lg text-sm text-center border border-border">
|
||||||
Español · English
|
Español · English
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -167,10 +167,10 @@ const volver = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Contacto -->
|
<!-- Contacto -->
|
||||||
<div class="bg-surface rounded-2xl p-5 shadow-sm space-y-4 border border-border mb-8">
|
<div class="bg-[var(--bg-secondary)] rounded-2xl p-5 shadow-sm space-y-4 border border-border mb-8">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-text-primary text-lg">Reserva e Información</h3>
|
<h3 class="font-bold text-[var(--text-primary)] text-lg">Reserva e Información</h3>
|
||||||
<p class="text-sm text-text-secondary mt-1">Contacta directamente al operador para confirmar disponibilidad.</p>
|
<p class="text-sm text-[var(--text-secondary)] mt-1">Contacta directamente al operador para confirmar disponibilidad.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
@ -186,7 +186,7 @@ const volver = () => {
|
|||||||
|
|
||||||
<a v-if="shuttle.phone_number"
|
<a v-if="shuttle.phone_number"
|
||||||
:href="`tel:${shuttle.phone_number}`"
|
:href="`tel:${shuttle.phone_number}`"
|
||||||
class="flex justify-center items-center gap-2 p-3.5 bg-bg-secondary text-text-primary rounded-xl font-bold hover:bg-hover transition active:scale-95 border border-border"
|
class="flex justify-center items-center gap-2 p-3.5 bg-[var(--bg-primary)] text-[var(--text-primary)] rounded-xl font-bold hover:bg-[var(--hover-bg)] transition active:scale-95 border border-border"
|
||||||
@click="analyticsService.logEvent({ event_name: 'shuttle_contact', item_id: shuttle.id, properties: { action: 'call' } })"
|
@click="analyticsService.logEvent({ event_name: 'shuttle_contact', item_id: shuttle.id, properties: { action: 'call' } })"
|
||||||
>
|
>
|
||||||
<span class="material-icons">phone_in_talk</span>
|
<span class="material-icons">phone_in_talk</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user