-
|
|
-
{{ (item as any).flight_number }}
|
-
- {{ (item as Flight).departure_airport.display_code }}
+ {{ (item as Flight).departure_airport.display_code }}
- {{ (item as Flight).departure_airport.municipality }}
-
+ {{ (item as Flight).departure_airport.municipality }}
|
-
{{ (item as Flight).arrival_airport.display_code }}
@@ -168,17 +192,14 @@ const tableItems = computed(() =>
{{ (item as Flight).arrival_airport.municipality }}
|
-
{{ (item as Flight).departure_date_display }}
|
-
{{ (item as Flight).departure_time_display }}
|
-
{{ (item as Flight).arrival_time_display }}
@@ -190,21 +211,18 @@ const tableItems = computed(() =>
{{ (item as Flight).duration_display ?? '—' }}
|
-
{{ (item as Flight).distance ? Math.round((item as Flight).distance).toLocaleString() + ' km' : '' }}
|
-
{{ (item as any).aircraft?.designator }}
|
-
@@ -213,7 +231,6 @@ const tableItems = computed(() =>
|
-
@@ -241,7 +258,6 @@ const tableItems = computed(() =>
-
NO FLIGHTS ON RECORD
@@ -251,14 +267,12 @@ const tableItems = computed(() =>
diff --git a/resources/js/Components/FlightsGoneBy/FlightCharts.vue b/resources/js/Components/FlightsGoneBy/FlightCharts.vue
index e4964d9..eb4267e 100644
--- a/resources/js/Components/FlightsGoneBy/FlightCharts.vue
+++ b/resources/js/Components/FlightsGoneBy/FlightCharts.vue
@@ -24,44 +24,46 @@ defineProps<{
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/Components/FlightsGoneBy/FlightFilter.vue b/resources/js/Components/FlightsGoneBy/FlightFilter.vue
index ec4792d..caaef40 100644
--- a/resources/js/Components/FlightsGoneBy/FlightFilter.vue
+++ b/resources/js/Components/FlightsGoneBy/FlightFilter.vue
@@ -1,18 +1,12 @@
{
{
{
{
()
-const chartsVisible = ref(false)
-onMounted(async () => {
- setTimeout(() => { chartsVisible.value = true }, 1)
-})
const mappedFlights = computed(() => [
...props.stats.pastFlights.value,
@@ -41,9 +37,9 @@ const mappedFlights = computed(() => [
-
-
+
+
diff --git a/resources/js/Composables/useFlightStats.ts b/resources/js/Composables/useFlightStats.ts
index af2b346..1983300 100644
--- a/resources/js/Composables/useFlightStats.ts
+++ b/resources/js/Composables/useFlightStats.ts
@@ -1,64 +1,45 @@
// composables/useFlightStats.ts
-import {computed, ComputedRef, Ref} from 'vue'
-import type {Airport, Flight} from '@/Types/types'
+import { computed, Ref, watch } from 'vue'
+import type { Airport, Flight } from '@/Types/types'
-const flightYear = (f: Flight): number =>
- Number(new Intl.DateTimeFormat('en', {
- timeZone: f.departure_airport.timezone,
- year: 'numeric',
- }).format(new Date(f.departure_date)))
-
-const DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
+const DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
+// ── Date part cache ───────────────────────────────────────────────────────────
+// Intl.DateTimeFormat is expensive. We compute each flight's date parts once
+// and cache them for the lifetime of the page.
-const dayIndex = (f: Flight): number => {
- const localDay = new Intl.DateTimeFormat('en', {
- timeZone: f.departure_airport.timezone,
- weekday: 'long',
- }).format(new Date(f.departure_date))
- const idx = DAYS.indexOf(localDay)
- return idx === -1 ? 0 : idx
-}
+const dateCache = new Map()
-export function getFlightsPerDay(flights: Flight[], upcomingFlights: Flight[]) {
- const countByDay = (list: Flight[]) =>
- DAYS.map((_, i) => list.filter(f => dayIndex(f) === i).length)
+function getDateParts(f: Flight): { year: number; month: number; day: number } {
+ if (dateCache.has(f.id)) return dateCache.get(f.id)!
- return {
- days: DAYS,
- series: [
- { name: 'Flown', data: countByDay(flights) },
- { name: 'Upcoming', data: countByDay(upcomingFlights) },
- ],
+ const tz = f.departure_airport.timezone
+ const date = new Date(f.departure_date)
+ const fmt = (opts: Intl.DateTimeFormatOptions) =>
+ new Intl.DateTimeFormat('en', { timeZone: tz, ...opts }).format(date)
+
+ const localDay = fmt({ weekday: 'long' })
+ const dayIdx = DAYS.indexOf(localDay)
+
+ const parts = {
+ year: Number(fmt({ year: 'numeric' })),
+ month: Number(fmt({ month: 'numeric' })) - 1,
+ day: dayIdx === -1 ? 0 : dayIdx,
}
+
+ dateCache.set(f.id, parts)
+ return parts
}
-const monthIndex = (f: Flight): number =>
- Number(new Intl.DateTimeFormat('en', {
- timeZone: f.departure_airport.timezone,
- month: 'numeric',
- }).format(new Date(f.departure_date))) - 1
-
-export function getFlightsPerMonth(flights: Flight[], upcomingFlights: Flight[]) {
- const countByMonth = (list: Flight[]) =>
- MONTHS.map((_, i) => list.filter(f => monthIndex(f) === i).length)
-
- return {
- months: MONTHS,
- series: [
- { name: 'Flown', data: countByMonth(flights) },
- { name: 'Upcoming', data: countByMonth(upcomingFlights) },
- ],
- }
-}
+// ── Per year / month / day ────────────────────────────────────────────────────
export function getFlightsPerYear(flights: Flight[], upcomingFlights: Flight[]) {
+ console.time('getFlightsPerYear')
const allFlights = [...flights, ...upcomingFlights]
- const allYears = new Set()
- allFlights.forEach(f => allYears.add(flightYear(f)))
- const sorted = [...allYears].sort((a, b) => a - b)
+ const allYears = new Set(allFlights.map(f => getDateParts(f).year))
+ const sorted = [...allYears].sort((a, b) => a - b)
let min = sorted[0]
let max = sorted[sorted.length - 1]
@@ -69,17 +50,53 @@ export function getFlightsPerYear(flights: Flight[], upcomingFlights: Flight[])
const years = Array.from({ length: max - min + 1 }, (_, i) => min + i)
const countByYear = (list: Flight[]) =>
- years.map(year => list.filter(f => flightYear(f) === year).length)
+ years.map(year => list.filter(f => getDateParts(f).year === year).length)
- return {
+ const result = {
years,
series: [
{ name: 'Flown', data: countByYear(flights) },
{ name: 'Upcoming', data: countByYear(upcomingFlights) },
],
}
+ console.timeEnd('getFlightsPerYear')
+ return result
}
+export function getFlightsPerMonth(flights: Flight[], upcomingFlights: Flight[]) {
+ console.time('getFlightsPerMonth')
+ const countByMonth = (list: Flight[]) =>
+ MONTHS.map((_, i) => list.filter(f => getDateParts(f).month === i).length)
+
+ const result = {
+ months: MONTHS,
+ series: [
+ { name: 'Flown', data: countByMonth(flights) },
+ { name: 'Upcoming', data: countByMonth(upcomingFlights) },
+ ],
+ }
+ console.timeEnd('getFlightsPerMonth')
+ return result
+}
+
+export function getFlightsPerDay(flights: Flight[], upcomingFlights: Flight[]) {
+ console.time('getFlightsPerDay')
+ const countByDay = (list: Flight[]) =>
+ DAYS.map((_, i) => list.filter(f => getDateParts(f).day === i).length)
+
+ const result = {
+ days: DAYS,
+ series: [
+ { name: 'Flown', data: countByDay(flights) },
+ { name: 'Upcoming', data: countByDay(upcomingFlights) },
+ ],
+ }
+ console.timeEnd('getFlightsPerDay')
+ return result
+}
+
+// ── Grouping helpers ──────────────────────────────────────────────────────────
+
function groupByName(flights: Flight[], accessor: (f: Flight) => string) {
const counts = new Map()
flights.forEach(f => {
@@ -94,17 +111,28 @@ function groupByName(flights: Flight[], accessor: (f: Flight) => string) {
}
export function getFlightReasons(flights: Flight[]) {
- return groupByName(flights, f => f.flight_reason?.name ?? 'Unknown')
+ console.time('getFlightReasons')
+ const result = groupByName(flights, f => f.flight_reason?.name ?? 'Unknown')
+ console.timeEnd('getFlightReasons')
+ return result
}
export function getFlightClasses(flights: Flight[]) {
- return groupByName(flights, f => f.flight_class?.name ?? 'Unknown')
+ console.time('getFlightClasses')
+ const result = groupByName(flights, f => f.flight_class?.name ?? 'Unknown')
+ console.timeEnd('getFlightClasses')
+ return result
}
export function getSeatTypes(flights: Flight[]) {
- return groupByName(flights, f => f.seat_type?.name ?? 'Unknown')
+ console.time('getSeatTypes')
+ const result = groupByName(flights, f => f.seat_type?.name ?? 'Unknown')
+ console.timeEnd('getSeatTypes')
+ return result
}
+// ── Countries ─────────────────────────────────────────────────────────────────
+
function countCountries(flights: Flight[]) {
const counts = new Map()
flights.forEach(f => {
@@ -126,11 +154,12 @@ function countCountries(flights: Flight[]) {
}
export function getCountries(flights: Flight[], upcomingFlights: Flight[]) {
+ console.time('getCountries')
const past = countCountries(flights)
const upcoming = countCountries(upcomingFlights)
- const allCountries = new Set([...past.keys(), ...upcoming.keys()])
+ const allNames = new Set([...past.keys(), ...upcoming.keys()])
- const sorted = [...allCountries]
+ const sorted = [...allNames]
.map(name => ({
name,
code: (past.get(name) ?? upcoming.get(name))!.code,
@@ -139,16 +168,21 @@ export function getCountries(flights: Flight[], upcomingFlights: Flight[]) {
}))
.sort((a, b) => (b.past + b.upcoming) - (a.past + a.upcoming))
- return {
+ const result = {
countries: sorted,
series: [
{ name: 'Flights', data: sorted.map(s => s.past) },
{ name: 'Upcoming', data: sorted.map(s => s.upcoming) },
],
}
+ console.timeEnd('getCountries')
+ return result
}
+// ── Continents ────────────────────────────────────────────────────────────────
+
export function getContinents(flights: Flight[]) {
+ console.time('getContinents')
const counts = new Map()
flights.forEach(f => {
const continents = new Set()
@@ -159,12 +193,16 @@ export function getContinents(flights: Flight[]) {
continents.forEach(c => counts.set(c, (counts.get(c) ?? 0) + 1))
})
const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1])
- return {
+ const result = {
labels: sorted.map(([name]) => name),
series: sorted.map(([, count]) => count),
}
+ console.timeEnd('getContinents')
+ return result
}
+// ── Airlines ──────────────────────────────────────────────────────────────────
+
function countAirlines(flights: Flight[]) {
const counts = new Map()
flights.forEach(f => {
@@ -178,11 +216,12 @@ function countAirlines(flights: Flight[]) {
}
export function getTopAirlines(flights: Flight[], upcomingFlights: Flight[]) {
+ console.time('getTopAirlines')
const past = countAirlines(flights)
const upcoming = countAirlines(upcomingFlights)
- const allAirlines = new Set([...past.keys(), ...upcoming.keys()])
+ const allNames = new Set([...past.keys(), ...upcoming.keys()])
- const sorted = [...allAirlines]
+ const sorted = [...allNames]
.map(name => ({
name,
id: (past.get(name) ?? upcoming.get(name))!.id,
@@ -191,15 +230,19 @@ export function getTopAirlines(flights: Flight[], upcomingFlights: Flight[]) {
}))
.sort((a, b) => (b.past + b.upcoming) - (a.past + a.upcoming))
- return {
+ const result = {
airlines: sorted,
series: [
{ name: 'Flights', data: sorted.map(s => s.past) },
{ name: 'Upcoming', data: sorted.map(s => s.upcoming) },
],
}
+ console.timeEnd('getTopAirlines')
+ return result
}
+// ── Airports ──────────────────────────────────────────────────────────────────
+
interface AirportItem {
label: string
fullName: string
@@ -214,7 +257,8 @@ function airportLabel(airport: Airport | null | undefined): string | null {
}
export function getTopAirports(flights: Flight[], upcomingFlights: Flight[]) {
- const map = new Map()
+ console.time('getTopAirports')
+ const map = new Map()
const empty = (): AirportItem => ({ departures: 0, arrivals: 0, upcoming: 0, label: '', fullName: '' })
flights.forEach(f => {
@@ -262,7 +306,7 @@ export function getTopAirports(flights: Flight[], upcomingFlights: Flight[]) {
(b.departures + b.arrivals + b.upcoming) - (a.departures + a.arrivals + a.upcoming)
)
- return {
+ const result = {
airports: sorted,
series: [
{ name: 'Departures', data: sorted.map(s => s.departures) },
@@ -270,9 +314,14 @@ export function getTopAirports(flights: Flight[], upcomingFlights: Flight[]) {
{ name: 'Upcoming', data: sorted.map(s => s.upcoming) },
],
}
+ console.timeEnd('getTopAirports')
+ return result
}
+// ── Flight types ──────────────────────────────────────────────────────────────
+
export function getFlightTypes(flights: Flight[]) {
+ console.time('getFlightTypes')
const counts = { International: 0, Domestic: 0 }
flights.forEach(f => {
const dep = f.departure_airport.region?.country?.id
@@ -282,37 +331,56 @@ export function getFlightTypes(flights: Flight[]) {
}
})
const sorted = Object.entries(counts).filter(([, count]) => count > 0)
- return {
+ const result = {
labels: sorted.map(([name]) => name),
series: sorted.map(([, count]) => count),
}
+ console.timeEnd('getFlightTypes')
+ return result
}
+// ── Composable ────────────────────────────────────────────────────────────────
+
export type FlightStats = ReturnType
export function useFlightStats(flights: Ref) {
const now = new Date()
- const pastFlights = computed(() =>
- flights.value.filter(f => new Date(f.departure_date) <= now)
- )
- const upcomingFlights = computed(() =>
- flights.value.filter(f => new Date(f.departure_date) > now)
- )
+ // Pre-warm the date cache as soon as flights are available so the first
+ // filter interaction doesn't pay the Intl.DateTimeFormat cost.
+ watch(flights, (list) => {
+ console.time('dateCache warm')
+ list.forEach(f => getDateParts(f))
+ console.timeEnd('dateCache warm')
+ }, { immediate: true })
+
+ const pastFlights = computed(() => {
+ console.time('pastFlights')
+ const result = flights.value.filter(f => new Date(f.departure_date) <= now)
+ console.timeEnd('pastFlights')
+ return result
+ })
+
+ const upcomingFlights = computed(() => {
+ console.time('upcomingFlights')
+ const result = flights.value.filter(f => new Date(f.departure_date) > now)
+ console.timeEnd('upcomingFlights')
+ return result
+ })
const allFlights = computed(() => flights.value)
- const perYear = computed(() => getFlightsPerYear(pastFlights.value, upcomingFlights.value))
- const perMonth = computed(() => getFlightsPerMonth(pastFlights.value, upcomingFlights.value))
- const perDay = computed(() => getFlightsPerDay(pastFlights.value, upcomingFlights.value))
- const reasons = computed(() => getFlightReasons(allFlights.value))
- const classes = computed(() => getFlightClasses(allFlights.value))
- const seatTypes = computed(() => getSeatTypes(allFlights.value))
- const countries = computed(() => getCountries(pastFlights.value, upcomingFlights.value))
- const continents = computed(() => getContinents(allFlights.value))
- const topAirlines = computed(() => getTopAirlines(pastFlights.value, upcomingFlights.value))
- const topAirports = computed(() => getTopAirports(pastFlights.value, upcomingFlights.value))
- const flightTypes = computed(() => getFlightTypes(allFlights.value))
+ const perYear = computed(() => getFlightsPerYear(pastFlights.value, upcomingFlights.value))
+ const perMonth = computed(() => getFlightsPerMonth(pastFlights.value, upcomingFlights.value))
+ const perDay = computed(() => getFlightsPerDay(pastFlights.value, upcomingFlights.value))
+ const reasons = computed(() => getFlightReasons(allFlights.value))
+ const classes = computed(() => getFlightClasses(allFlights.value))
+ const seatTypes = computed(() => getSeatTypes(allFlights.value))
+ const countries = computed(() => getCountries(pastFlights.value, upcomingFlights.value))
+ const continents = computed(() => getContinents(allFlights.value))
+ const topAirlines = computed(() => getTopAirlines(pastFlights.value, upcomingFlights.value))
+ const topAirports = computed(() => getTopAirports(pastFlights.value, upcomingFlights.value))
+ const flightTypes = computed(() => getFlightTypes(allFlights.value))
return {
pastFlights,
diff --git a/resources/js/Pages/AddFlight.vue b/resources/js/Pages/AddFlight.vue
index 7e97431..2b0128f 100644
--- a/resources/js/Pages/AddFlight.vue
+++ b/resources/js/Pages/AddFlight.vue
@@ -135,7 +135,7 @@ const submitForm = useForm({
})
function submit() {
- submitForm.flight_number = form.flight_number
+ submitForm.flight_number = flightNumber.value
submitForm.departure_date = form.departure_date
submitForm.arrival_date = form.arrival_date
submitForm.from_id = form.from?.value ?? null
diff --git a/resources/js/Pages/UserProfile.vue b/resources/js/Pages/UserProfile.vue
index 6900a40..ee44906 100644
--- a/resources/js/Pages/UserProfile.vue
+++ b/resources/js/Pages/UserProfile.vue
@@ -1,7 +1,7 @@
|