feat: show past bus in ETACard and move disclaimer to tooltip

This commit is contained in:
2026-03-28 14:33:07 -05:00
parent 269691c900
commit f0cbfe8ae7
2 changed files with 82 additions and 20 deletions

View File

@ -27,6 +27,37 @@
Desliza hacia abajo para minimizar Desliza hacia abajo para minimizar
</p> </p>
<!-- Tooltip informativo en la esquina superior derecha -->
<div class="absolute top-4 right-5 z-20">
<button
@click="showTooltip = !showTooltip"
class="flex items-center justify-center w-8 h-8 rounded-full bg-gray-100 dark:bg-gray-800 text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
>
<span class="material-icons text-xl">info</span>
</button>
<!-- Contenido del Tooltip -->
<Transition name="fade">
<div
v-if="showTooltip"
class="absolute top-10 right-0 w-64 p-4 bg-gray-900/95 dark:bg-white/95 text-white dark:text-gray-900 rounded-2xl shadow-2xl backdrop-blur-sm text-[11px] leading-relaxed border border-white/20 dark:border-gray-200 pointer-events-auto"
>
<div class="flex items-start gap-2 mb-2">
<span class="material-icons text-yellow-400 text-sm">warning</span>
<span class="font-bold uppercase tracking-wider text-[10px]">Información de horarios</span>
</div>
<p class="opacity-90">
Este es un tiempo estimado basado en la velocidad promedio. <strong>No existe rastreo GPS en tiempo real.</strong>
</p>
<p class="mt-2 opacity-90">
El tráfico y paradas intermedias pueden alterar el tiempo de llegada dramáticamente.
</p>
<!-- Flecha del tooltip -->
<div class="absolute -top-1.5 right-3.5 w-3 h-3 bg-gray-900/95 dark:bg-white/95 rotate-45 border-l border-t border-white/20 dark:border-gray-200"></div>
</div>
</Transition>
</div>
<!-- Cabecera de la parada --> <!-- Cabecera de la parada -->
<div v-if="stopName && !isLoading" class="mt-4 flex items-start gap-4 pb-4 border-b border-gray-100 dark:border-gray-800"> <div v-if="stopName && !isLoading" class="mt-4 flex items-start gap-4 pb-4 border-b border-gray-100 dark:border-gray-800">
<div class="bg-blue-100 dark:bg-blue-900/40 p-3 rounded-2xl flex-shrink-0"> <div class="bg-blue-100 dark:bg-blue-900/40 p-3 rounded-2xl flex-shrink-0">
@ -64,13 +95,16 @@
</p> </p>
</div> </div>
<!-- Lista de llegadas (Max 2) --> <!-- Lista de llegadas (Próximos + Pasado) -->
<template v-else> <template v-else>
<div <div
v-for="(bus, index) in buses.slice(0, 2)" v-for="bus in displayBuses"
:key="bus.horario_id" :key="bus.horario_id"
class="group bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-2xl p-4 flex items-center justify-between" class="group bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-2xl p-4 flex items-center justify-between transition-all"
:class="{ 'ring-2 ring-green-500/50 dark:ring-green-400/50 bg-green-50/30 dark:bg-green-900/10': bus.estado === 'en_camino' }" :class="{
'ring-2 ring-green-500/50 dark:ring-green-400/50 bg-green-50/30 dark:bg-green-900/10': bus.estado === 'en_camino',
'opacity-60 grayscale-[0.5]': bus.estado === 'pasó'
}"
> >
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<!-- Icono dinámico según estado --> <!-- Icono dinámico según estado -->
@ -87,7 +121,7 @@
<div class="flex flex-col"> <div class="flex flex-col">
<span class="text-sm font-bold text-gray-900 dark:text-white line-clamp-1"> <span class="text-sm font-bold text-gray-900 dark:text-white line-clamp-1">
{{ index === 0 ? 'Bus más cercano' : 'Siguiente bus' }} {{ bus.label }}
</span> </span>
<div class="flex flex-col mt-0.5 gap-0.5"> <div class="flex flex-col mt-0.5 gap-0.5">
<span class="text-xs font-semibold text-gray-500 dark:text-gray-400 flex items-center gap-1"> <span class="text-xs font-semibold text-gray-500 dark:text-gray-400 flex items-center gap-1">
@ -119,7 +153,7 @@
<span v-else-if="bus.estado === 'próximo'" class="inline-flex px-2.5 py-1 rounded-lg bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-400 text-[10px] font-black uppercase tracking-wider"> <span v-else-if="bus.estado === 'próximo'" class="inline-flex px-2.5 py-1 rounded-lg bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-400 text-[10px] font-black uppercase tracking-wider">
Programado Programado
</span> </span>
<span v-else class="inline-flex px-2.5 py-1 rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 text-[10px] font-black uppercase tracking-wider line-through"> <span v-else class="inline-flex px-2.5 py-1 rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-500 dark:text-gray-400 text-[10px] font-black uppercase tracking-wider">
Ya pasó Ya pasó
</span> </span>
</div> </div>
@ -128,28 +162,21 @@
</template> </template>
</div> </div>
<!-- Legal Disclaimer Intocable --> <!-- Disclaimer removed from bottom and moved to tooltip bubble -->
<div v-if="stopName" class="mt-2 p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-xl flex items-start gap-3 border border-yellow-100 dark:border-yellow-900/50">
<span class="material-icons text-yellow-600 dark:text-yellow-500 text-lg mt-0.5 shrink-0">info</span>
<p class="text-[11px] leading-snug text-yellow-800 dark:text-yellow-600/90 font-medium">
<strong>Aviso:</strong> Este es un tiempo estimado basado en la velocidad promedio de las unidades en la ciudad. No existe rastreo GPS en tiempo real.
El tráfico y paradas intermedias pueden alterar el tiempo de llegada dramáticamente.
</p>
</div>
</div> </div>
</div> </div>
</Transition> </Transition>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'; import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import type { BusETA } from '@/composables/useETA'; import type { BusETA } from '@/composables/useETA';
import { formatDurationMinutes } from '@/utils/durationFormatter'; import { formatDurationMinutes } from '@/utils/durationFormatter';
const { t } = useI18n(); const { t } = useI18n();
defineProps<{ const props = defineProps<{
isOpen: boolean; isOpen: boolean;
stopName: string; stopName: string;
walkDistance: number; walkDistance: number;
@ -163,6 +190,27 @@ const emit = defineEmits<{
(e: 'refresh'): void; (e: 'refresh'): void;
}>(); }>();
// Tooltip state
const showTooltip = ref(false);
// Categorización de buses
const upcomingBuses = computed(() => props.buses.filter(b => b.estado !== 'pasó'));
const lastPastBus = computed(() => {
const pastBuses = props.buses.filter(b => b.estado === 'pasó');
// useETA ordena por etaMinutos asc, así que en negativos (-20, -10, -5) el último es el más reciente
return pastBuses.length > 0 ? pastBuses[pastBuses.length - 1] : null;
});
const displayBuses = computed(() => {
const result = [];
// Agregamos los dos próximos
if (upcomingBuses.value[0]) result.push({ ...upcomingBuses.value[0], label: 'Bus más cercano' });
if (upcomingBuses.value[1]) result.push({ ...upcomingBuses.value[1], label: 'Siguiente bus' });
// Agregamos el que ya pasó
if (lastPastBus.value) result.push({ ...lastPastBus.value, label: 'Bus anterior' });
return result;
});
// ── DRAG TO DISMISS ────────────────────────────────── // ── DRAG TO DISMISS ──────────────────────────────────
const sheetRef = ref<HTMLElement | null>(null); const sheetRef = ref<HTMLElement | null>(null);
const dragY = ref(0); // desplazamiento actual del drag const dragY = ref(0); // desplazamiento actual del drag
@ -244,4 +292,16 @@ onUnmounted(() => {
.fixed.inset-0 { .fixed.inset-0 {
transition: opacity 0.4s ease; transition: opacity 0.4s ease;
} }
/* Transición para el Tooltip */
.fade-enter-active,
.fade-leave-active {
transition: all 0.2s ease-out;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: translateY(10px) scale(0.95);
}
</style> </style>

View File

@ -153,13 +153,15 @@ export function useETA() {
if (etaMinutos > 5) { if (etaMinutos > 5) {
estado = busYaSalio ? 'en_camino' : 'próximo'; estado = busYaSalio ? 'en_camino' : 'próximo';
} else { } else if (etaMinutos >= 0) {
estado = 'en_camino'; estado = 'en_camino';
} else {
estado = 'pasó';
} }
// Filtrar buses que ya pasaron (ETA < 0) // Conservar buses que pasaron hace poco (ej: últimos 30 minutos)
// Solo mostramos buses a los que el usuario tiene posibilidad de llegar // para que el usuario sepa que acaba de perder uno
if (etaMinutos < 0) continue; if (etaMinutos < -30) continue;
resultados.push({ resultados.push({
horario_id: h.id, horario_id: h.id,