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:
144
app/composables/useResultFilters.ts
Normal file
144
app/composables/useResultFilters.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user