204 lines
8.2 KiB
Vue
204 lines
8.2 KiB
Vue
<template>
|
|
<FlightMap :flights="mappedFlights" class="profile-map__map" />
|
|
|
|
<div class="profile-map__toolbar">
|
|
<v-select
|
|
v-model="selectedYears"
|
|
:items="availableYears"
|
|
label="Year"
|
|
multiple
|
|
clearable
|
|
hide-details
|
|
density="compact"
|
|
variant="outlined"
|
|
>
|
|
<template #selection="{ item, index }">
|
|
<span v-if="index < 2" class="v-select__selection-text">
|
|
{{ item }}<span v-if="index < Math.min(selectedYears.length, 2) - 1">, </span>
|
|
</span>
|
|
<span v-if="index === 2" class="text-caption text-medium-emphasis">+{{ selectedYears.length - 2 }}</span>
|
|
</template>
|
|
</v-select>
|
|
|
|
<v-select
|
|
v-model="selectedAirlines"
|
|
:items="availableAirlines"
|
|
item-title="name"
|
|
item-value="id"
|
|
label="Airline"
|
|
multiple
|
|
clearable
|
|
hide-details
|
|
density="compact"
|
|
variant="outlined"
|
|
>
|
|
<template #item="{ item, props: itemProps }">
|
|
<v-list-item v-bind="itemProps">
|
|
<template #prepend="{ isSelected }">
|
|
<v-checkbox-btn :model-value="isSelected" tabindex="-1" />
|
|
</template>
|
|
<template #title>
|
|
<img
|
|
:src="airlineLogoUrl((item as any).id)"
|
|
width="32"
|
|
height="32"
|
|
style="object-fit: contain; margin-right: 8px; vertical-align: middle;"
|
|
alt=""
|
|
/>
|
|
{{ (item as any).name }}
|
|
</template>
|
|
</v-list-item>
|
|
</template>
|
|
<template #selection="{ item, index }">
|
|
<span v-if="index < 2" class="v-select__selection-text">
|
|
{{ (item as any).name }}<span v-if="index < Math.min(selectedAirlines.length, 2) - 1">, </span>
|
|
</span>
|
|
<span v-if="index === 2" class="text-caption text-medium-emphasis">+{{ selectedAirlines.length - 2 }}</span>
|
|
</template>
|
|
</v-select>
|
|
|
|
<v-select
|
|
v-model="selectedCountries"
|
|
:items="availableCountries"
|
|
item-title="name"
|
|
item-value="code"
|
|
label="Country"
|
|
multiple
|
|
clearable
|
|
hide-details
|
|
density="compact"
|
|
variant="outlined"
|
|
>
|
|
<template #item="{ item, props: itemProps }">
|
|
<v-list-item v-bind="itemProps">
|
|
<template #prepend="{ isSelected }">
|
|
<v-checkbox-btn :model-value="isSelected" tabindex="-1" />
|
|
</template>
|
|
<template #title>
|
|
<span :class="countryFlagClass((item as any).code)" style="margin-right: 8px; font-size: 1.1em;" />
|
|
{{ (item as any).name }}
|
|
</template>
|
|
</v-list-item>
|
|
</template>
|
|
<template #selection="{ item, index }">
|
|
<span v-if="index < 2" class="v-select__selection-text">
|
|
<span :class="countryFlagClass((item as any).code)" style="margin-right: 4px;" />
|
|
{{ (item as any).name }}<span v-if="index < Math.min(selectedCountries.length, 2) - 1">, </span>
|
|
</span>
|
|
<span v-if="index === 2" class="text-caption text-medium-emphasis">+{{ selectedCountries.length - 2 }}</span>
|
|
</template>
|
|
</v-select>
|
|
</div>
|
|
|
|
<FlightStatsBar :flights="pastFlights" :upcoming-flights="upcomingFlights" />
|
|
<FlightCharts :flights="pastFlights" :upcoming-flights="upcomingFlights" />
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { usePage } from '@inertiajs/vue3'
|
|
import FlightMap from '@/Components/FlightsGoneBy/FlightMap.vue'
|
|
import type { Flight } from '@/Types/types'
|
|
import FlightStatsBar from "@/Components/FlightsGoneBy/FlightStatsBar.vue";
|
|
import FlightCharts from "@/Components/FlightsGoneBy/FlightCharts.vue";
|
|
|
|
const props = defineProps<{
|
|
flights: Flight[]
|
|
}>()
|
|
|
|
const mappedFlights = computed(() => [
|
|
...pastFlights.value,
|
|
...upcomingFlights.value,
|
|
])
|
|
|
|
const page = usePage()
|
|
const now = new Date()
|
|
|
|
const selectedYears = ref<number[]>([])
|
|
const selectedAirlines = ref<number[]>([])
|
|
const selectedCountries = ref<string[]>([])
|
|
|
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
|
|
const airlineLogoUrl = (id: number) =>
|
|
`${page.props.logo_api_url}/airlines/logos/tail/id/${id}`
|
|
|
|
const countryFlagClass = (code: string) =>
|
|
`fi fi-${code.toLowerCase()}`
|
|
|
|
// ── Available filter options ──────────────────────────────────────────────────
|
|
|
|
const availableYears = computed(() => {
|
|
const years = new Set<number>()
|
|
props.flights.forEach(f => years.add(new Date(f.departure_date).getFullYear()))
|
|
return [...years].sort((a, b) => b - a)
|
|
})
|
|
|
|
const availableAirlines = computed((): { id: number; name: string }[] => {
|
|
const map = new Map<number, { id: number; name: string }>()
|
|
props.flights.forEach(f => {
|
|
if (f.airline?.id && f.airline?.name) {
|
|
map.set(f.airline.id, { id: f.airline.id, name: f.airline.name })
|
|
}
|
|
})
|
|
return [...map.values()].sort((a, b) => a.name.localeCompare(b.name))
|
|
})
|
|
|
|
const availableCountries = computed((): { code: string; name: string }[] => {
|
|
const map = new Map<string, { code: string; name: string }>()
|
|
props.flights.forEach(f => {
|
|
const dep = f.departure_airport.region?.country
|
|
const arr = f.arrival_airport.region?.country
|
|
if (dep) map.set(dep.code, { code: dep.code, name: dep.name })
|
|
if (arr) map.set(arr.code, { code: arr.code, name: arr.name })
|
|
})
|
|
return [...map.values()].sort((a, b) => a.name.localeCompare(b.name))
|
|
})
|
|
|
|
// ── Filtered flights ──────────────────────────────────────────────────────────
|
|
|
|
const pastFlights = computed(() => {
|
|
return props.flights.filter(f => {
|
|
const date = new Date(f.departure_date)
|
|
if (date > now) return false
|
|
if (selectedYears.value.length && !selectedYears.value.includes(date.getFullYear())) return false
|
|
if (selectedAirlines.value.length && !selectedAirlines.value.includes(f.airline?.id ?? -1)) return false
|
|
if (selectedCountries.value.length) {
|
|
const depCode = f.departure_airport.region?.country?.code
|
|
const arrCode = f.arrival_airport.region?.country?.code
|
|
if (!selectedCountries.value.includes(depCode ?? '') && !selectedCountries.value.includes(arrCode ?? '')) return false
|
|
}
|
|
return true
|
|
})
|
|
})
|
|
|
|
const upcomingFlights = computed(() => {
|
|
return props.flights.filter(f => {
|
|
const date = new Date(f.departure_date)
|
|
if (date <= now) return false
|
|
if (selectedYears.value.length && !selectedYears.value.includes(date.getFullYear())) return false
|
|
if (selectedAirlines.value.length && !selectedAirlines.value.includes(f.airline?.id ?? -1)) return false
|
|
if (selectedCountries.value.length) {
|
|
const depCode = f.departure_airport.region?.country?.code
|
|
const arrCode = f.arrival_airport.region?.country?.code
|
|
if (!selectedCountries.value.includes(depCode ?? '') && !selectedCountries.value.includes(arrCode ?? '')) return false
|
|
}
|
|
return true
|
|
})
|
|
})
|
|
|
|
// ── Stats ─────────────────────────────────────────────────────────────────────
|
|
</script>
|
|
|
|
<style scoped>
|
|
.profile-map__toolbar {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.profile-map__toolbar .v-select {
|
|
flex: 1;
|
|
}
|
|
</style>
|