Files
vuelato/app/components/tracking/RunHistory.vue
Alejandro Martinez b8906efc80
Some checks failed
ci / ci (22, ubuntu-latest) (push) Has been cancelled
Initial commit: Vuelato - buscador de vuelos
Nuxt 4 + Supabase + Flightics API. Incluye búsqueda de vuelos,
inspiraciones, watchlist, tracking de precios y mapa interactivo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:37:06 +02:00

97 lines
3.7 KiB
Vue

<script setup lang="ts">
defineProps<{
runs: SearchRun[]
}>()
function statusBadge(status: string) {
switch (status) {
case 'completed': return { label: 'Completado', color: 'success' as const }
case 'running': return { label: 'Ejecutando', color: 'info' as const }
case 'failed': return { label: 'Error', color: 'error' as const }
default: return { label: 'Pendiente', color: 'neutral' as const }
}
}
function formatDate(date: string) {
return new Date(date).toLocaleString('es-ES', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit'
})
}
function formatShortDate(dateStr: string) {
const d = new Date(dateStr)
return d.toLocaleDateString('es-ES', { day: 'numeric', month: 'short' })
}
function duration(start: string | null, end: string | null) {
if (!start || !end) return '-'
const ms = new Date(end).getTime() - new Date(start).getTime()
if (ms < 1000) return `${ms}ms`
return `${(ms / 1000).toFixed(1)}s`
}
</script>
<template>
<div class="space-y-2">
<h3 class="font-semibold text-sm mb-3">Historial de ejecuciones</h3>
<div v-if="runs.length === 0" class="text-center py-6">
<p class="text-sm text-neutral-500">Aun no hay ejecuciones</p>
</div>
<UCard v-for="run in runs" :key="run.id" class="!p-3">
<div class="flex items-center justify-between gap-3">
<div class="flex items-center gap-2 flex-1 min-w-0">
<UBadge :label="statusBadge(run.status).label" :color="statusBadge(run.status).color" size="xs" />
<span class="text-xs text-muted">{{ formatDate(run.created_at) }}</span>
<UBadge v-if="run.from_cache" label="Cache" color="neutral" variant="outline" size="xs" />
</div>
<div class="flex items-center gap-3 text-sm shrink-0">
<span v-if="run.cheapest_price != null" class="font-medium">
{{ run.cheapest_price.toFixed(0) }}&euro;
</span>
<span v-if="run.total_trips_found > 0" class="text-xs text-muted">
{{ run.total_trips_found }} vuelos
</span>
<span class="text-xs text-muted">
{{ duration(run.started_at, run.completed_at) }}
</span>
</div>
</div>
<!-- Error message -->
<p v-if="run.error_message" class="text-xs text-red-500 mt-1">
{{ run.error_message }}
</p>
<!-- Top trips preview -->
<div v-if="run.top_trips && run.top_trips.length > 0" class="mt-2 space-y-1.5">
<NuxtLink
v-for="(trip, i) in run.top_trips.slice(0, 3)"
:key="i"
:to="trip.bookingToken ? `/detail/${encodeURIComponent(trip.bookingToken)}?adults=1` : undefined"
class="block text-xs text-muted rounded px-1.5 py-1 -mx-1.5 transition-colors"
:class="trip.bookingToken ? 'hover:bg-neutral-100 dark:hover:bg-neutral-800 cursor-pointer' : ''"
>
<div class="flex items-center gap-2">
<span class="font-medium text-foreground">{{ trip.price?.toFixed(0) }}&euro;</span>
<div class="flex flex-wrap gap-x-3 gap-y-0.5 flex-1">
<span v-for="(leg, j) in trip.legs" :key="j" class="flex items-center gap-1">
<UIcon :name="j === 0 ? 'i-lucide-plane-takeoff' : 'i-lucide-plane-landing'" class="text-[10px]" />
{{ leg.from }} > {{ leg.to }}
<span v-if="leg.departure" class="text-muted">{{ formatShortDate(leg.departure) }}</span>
<span v-if="leg.airlines?.length" class="text-muted">({{ leg.airlines.join(', ') }})</span>
</span>
</div>
<UIcon v-if="trip.bookingToken" name="i-lucide-arrow-right" class="text-neutral-400 text-xs shrink-0" />
</div>
</NuxtLink>
</div>
</UCard>
</div>
</template>