feat: show past bus in ETACard and move disclaimer to tooltip
This commit is contained in:
@ -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>
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user