Initial commit: Vuelato - buscador de vuelos
Some checks failed
ci / ci (22, ubuntu-latest) (push) Has been cancelled
Some checks failed
ci / ci (22, ubuntu-latest) (push) Has been cancelled
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>
This commit is contained in:
96
app/components/tracking/RunHistory.vue
Normal file
96
app/components/tracking/RunHistory.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<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) }}€
|
||||
</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) }}€</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>
|
||||
Reference in New Issue
Block a user