Files
vuelato/app/composables/useResultFilters.ts
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

145 lines
4.1 KiB
TypeScript

import type { Trip } from '~/server/utils/flightics'
export type SortKey = 'price' | 'duration' | 'departure' | 'stops'
interface Filters {
maxPrice: number | null
maxStops: number | null
airlines: string[]
departureTimeRange: [number, number] // hours 0-24
}
function getTripDuration(trip: Trip): number {
let total = 0
for (const leg of trip.legs) {
for (const seg of leg.segments) {
total += (seg.arrivalUtcTimestamp ?? 0) - (seg.departureUtcTimestamp ?? 0)
}
}
return total
}
function getTripStops(trip: Trip): number {
return trip.legs.reduce((sum, leg) => sum + Math.max(0, leg.segments.length - 1), 0)
}
function getTripAirlines(trip: Trip): string[] {
const codes = new Set<string>()
for (const leg of trip.legs) {
for (const seg of leg.segments) {
if (seg.company?.code) codes.add(seg.company.code)
}
}
return [...codes]
}
function getDepartureHour(trip: Trip): number {
const dep = trip.legs[0]?.segments[0]?.departureDate
return dep ? new Date(dep).getHours() : 0
}
export function useResultFilters(trips: Ref<Trip[]>) {
const sortBy = ref<SortKey>('price')
const filters = reactive<Filters>({
maxPrice: null,
maxStops: null,
airlines: [],
departureTimeRange: [0, 24]
})
const viewMode = ref<'full' | 'compact'>('full')
// Extract available airlines from results
const { resolve: resolveAirline } = useAirlineNames()
const availableAirlines = computed(() => {
const codes = new Set<string>()
for (const trip of trips.value) {
for (const leg of trip.legs) {
for (const seg of leg.segments) {
if (seg.company?.code) codes.add(seg.company.code)
}
}
}
return [...codes].sort().map(code => ({ code, name: resolveAirline(code) }))
})
// Price range in results
const priceRange = computed(() => {
if (!trips.value.length) return { min: 0, max: 1000 }
const prices = trips.value.map(t => t.totalCost)
return { min: Math.floor(Math.min(...prices)), max: Math.ceil(Math.max(...prices)) }
})
const filtered = computed(() => {
let result = [...trips.value]
// Filter by price
if (filters.maxPrice != null) {
result = result.filter(t => t.totalCost <= filters.maxPrice!)
}
// Filter by stops
if (filters.maxStops != null) {
result = result.filter(t => getTripStops(t) <= filters.maxStops!)
}
// Filter by airlines (exclusive: all airlines in the trip must be in the selected set)
if (filters.airlines.length > 0) {
result = result.filter(t => {
const tripAirlines = getTripAirlines(t)
return tripAirlines.every(a => filters.airlines.includes(a))
})
}
// Filter by departure time
if (filters.departureTimeRange[0] > 0 || filters.departureTimeRange[1] < 24) {
result = result.filter(t => {
const hour = getDepartureHour(t)
return hour >= filters.departureTimeRange[0] && hour <= filters.departureTimeRange[1]
})
}
// Sort
if (sortBy.value === 'price') {
result.sort((a, b) => a.totalCost - b.totalCost)
} else if (sortBy.value === 'departure') {
result.sort((a, b) => {
const aTime = a.legs[0]?.segments[0]?.departureTimestamp ?? 0
const bTime = b.legs[0]?.segments[0]?.departureTimestamp ?? 0
return aTime - bTime
})
} else if (sortBy.value === 'duration') {
result.sort((a, b) => getTripDuration(a) - getTripDuration(b))
} else if (sortBy.value === 'stops') {
result.sort((a, b) => getTripStops(a) - getTripStops(b))
}
return result
})
function resetFilters() {
filters.maxPrice = null
filters.maxStops = null
filters.airlines = []
filters.departureTimeRange = [0, 24]
}
const hasActiveFilters = computed(() =>
filters.maxPrice != null ||
filters.maxStops != null ||
filters.airlines.length > 0 ||
filters.departureTimeRange[0] > 0 ||
filters.departureTimeRange[1] < 24
)
return {
sortBy,
filters,
viewMode,
filtered,
availableAirlines,
priceRange,
hasActiveFilters,
resetFilters
}
}