Added Charts

This commit is contained in:
2026-04-11 23:00:35 +10:00
parent 95624f345c
commit f335951784
4 changed files with 508 additions and 84 deletions
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers;
use App\Models\Airline;
use Illuminate\Http\Request;
class FlightController extends Controller
{
public function lookup(Request $request)
{
$number = strtoupper(trim($request->query('number', '')));
// Extract the airline code prefix — letters at the start e.g. "QF" from "QF1"
preg_match('/^([A-Z]{2,3})/', $number, $matches);
$iataCode = $matches[1] ?? null;
$airlines = $iataCode
? Airline::where('IATA_code', $iataCode)
->get()
->map(fn ($a) => ['value' => $a->id, 'title' => $a->name])
: collect();
return response()->json([
'airline_options' => $airlines,
'from_options' => [], // populate from a flight schedule API if you have one
'to_options' => [],
'aircraft_options' => [],
]);
}
}
@@ -14,7 +14,7 @@ import maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css' import 'maplibre-gl/dist/maplibre-gl.css'
import { SharedProps } from '@/Types/types' import { SharedProps } from '@/Types/types'
import { usePage } from '@inertiajs/vue3' import { usePage } from '@inertiajs/vue3'
import {Flight, Airport, Airline, Region, Country} from "@/Types/types"; import { Flight, Airport } from '@/Types/types'
interface RouteFlightBucket { interface RouteFlightBucket {
historical: Flight[] historical: Flight[]
@@ -34,9 +34,11 @@ interface AirportFeatureProperties {
id: number id: number
} }
// ── GeoJSON helpers ─────────────────────────────────────────────────────────── interface RoutesGeoJSON {
historical: GeoJSON.FeatureCollection<GeoJSON.LineString, RouteFeatureProperties>
future: GeoJSON.FeatureCollection<GeoJSON.LineString, RouteFeatureProperties>
}
const page = usePage<SharedProps>().props
function greatCircleGeoJSON(from: LngLat, to: LngLat, steps = 64): LngLat[] { function greatCircleGeoJSON(from: LngLat, to: LngLat, steps = 64): LngLat[] {
const toRad = (d: number): number => d * Math.PI / 180 const toRad = (d: number): number => d * Math.PI / 180
@@ -94,6 +96,7 @@ function airportPopupHTML(airport: Airport): string {
} }
function routePopupHTML(historical: Flight[], future: Flight[]): string { function routePopupHTML(historical: Flight[], future: Flight[]): string {
const logoApiUrl = usePage<SharedProps>().props.logo_api_url
interface DirectionGroup { interface DirectionGroup {
label: string label: string
airlines: string[] airlines: string[]
@@ -106,7 +109,7 @@ function routePopupHTML(historical: Flight[], future: Flight[]): string {
const label = `${f.departure_airport.municipality} to ${f.arrival_airport.municipality}` const label = `${f.departure_airport.municipality} to ${f.arrival_airport.municipality}`
if (!dirs.has(key)) dirs.set(key, { label, airlines: [] }) if (!dirs.has(key)) dirs.set(key, { label, airlines: [] })
const airline = `<span style="display:inline-flex;align-items:center;gap:6px;"> const airline = `<span style="display:inline-flex;align-items:center;gap:6px;">
<img src="${page.logo_api_url}/airlines/logos/tail/id/${f.airline?.id}" width="24" height="24" alt="${f.airline?.IATA_code}" style="flex-shrink:0;" /> <img src="${logoApiUrl}/airlines/logos/tail/id/${f.airline?.id}" width="24" height="24" alt="${f.airline?.IATA_code}" style="flex-shrink:0;" />
${f.airline?.name} ${f.airline?.name}
</span>` </span>`
if (airline && !dirs.get(key)!.airlines.includes(airline)) { if (airline && !dirs.get(key)!.airlines.includes(airline)) {
@@ -118,14 +121,11 @@ function routePopupHTML(historical: Flight[], future: Flight[]): string {
const renderSection = (title: string, list: Flight[]): string => { const renderSection = (title: string, list: Flight[]): string => {
if (!list.length) return '' if (!list.length) return ''
const rows = groupByDirection(list).map(({ label, airlines }) => { const rows = groupByDirection(list).map(({ label, airlines }) => `
return `
<div class="rp-direction"> <div class="rp-direction">
<div class="rp-route">${label}</div> <div class="rp-route">${label}</div>
<div class="rp-airlines">${airlines.join('<br/> ') || '—'}</div> <div class="rp-airlines">${airlines.join('<br/>') || '—'}</div>
</div>` </div>`).join('')
}).join('')
return `<div class="rp-section"><div class="rp-section-title">${title}</div>${rows}</div>` return `<div class="rp-section"><div class="rp-section-title">${title}</div>${rows}</div>`
} }
@@ -155,10 +155,10 @@ export default defineComponent({
let pulseFrame: number | null = null let pulseFrame: number | null = null
const PULSE_PHASES = 6 const PULSE_PHASES = 6
const TOUCH_RADIUS = 20
const airportById = new Map<number, Airport>() const airportById = new Map<number, Airport>()
const routeFlights = new Map<string, RouteFlightBucket>() const routeFlights = new Map<string, RouteFlightBucket>()
let selectedAirportId: number | null = null let selectedAirportId: number | null = null
let suppressRoutePopup = false
const isTouchDevice = (): boolean => window.matchMedia('(pointer: coarse)').matches const isTouchDevice = (): boolean => window.matchMedia('(pointer: coarse)').matches
@@ -200,11 +200,6 @@ export default defineComponent({
}) })
} }
interface RoutesGeoJSON {
historical: GeoJSON.FeatureCollection<GeoJSON.LineString, RouteFeatureProperties>
future: GeoJSON.FeatureCollection<GeoJSON.LineString, RouteFeatureProperties>
}
// ── GeoJSON builders ────────────────────────────────────────────────── // ── GeoJSON builders ──────────────────────────────────────────────────
const buildRoutesGeoJSON = (): RoutesGeoJSON => { const buildRoutesGeoJSON = (): RoutesGeoJSON => {
buildRouteFlights() buildRouteFlights()
@@ -349,10 +344,13 @@ export default defineComponent({
offset: 12, offset: 12,
}) })
const airportLayerIds = Array.from({ length: PULSE_PHASES }, (_, i) => `airports-dot-${i}`)
// ── Desktop hover events ──────────────────────────────────────────
if (!isTouch) {
for (let i = 0; i < PULSE_PHASES; i++) { for (let i = 0; i < PULSE_PHASES; i++) {
map!.on('mouseenter', `airports-dot-${i}`, (e) => { map!.on('mouseenter', `airports-dot-${i}`, (e) => {
map!.getCanvas().style.cursor = 'pointer' map!.getCanvas().style.cursor = 'pointer'
if (isTouch) return
const airport = airportById.get(Number(e.features![0].properties.id)) const airport = airportById.get(Number(e.features![0].properties.id))
const geom = e.features![0].geometry const geom = e.features![0].geometry
if (airport && geom.type === 'Point') { if (airport && geom.type === 'Point') {
@@ -361,70 +359,73 @@ export default defineComponent({
}) })
map!.on('mouseleave', `airports-dot-${i}`, () => { map!.on('mouseleave', `airports-dot-${i}`, () => {
map!.getCanvas().style.cursor = '' map!.getCanvas().style.cursor = ''
if (isTouch) return
popup!.remove() popup!.remove()
}) })
map!.on('click', `airports-dot-${i}`, (e) => {
suppressRoutePopup = true
popup!.remove()
const id = Number(e.features![0].properties.id)
selectedAirportId = selectedAirportId === id ? null : id
applyFilter()
setTimeout(() => { suppressRoutePopup = false }, 0)
})
} }
map!.on('mouseenter', 'routes-hit', (e) => { map!.on('mouseenter', 'routes-hit', (e) => {
if (isTouch) return
map!.getCanvas().style.cursor = 'pointer' map!.getCanvas().style.cursor = 'pointer'
const rf = routeFlights.get(e.features![0].properties.routeKey as string) const rf = routeFlights.get(e.features![0].properties.routeKey as string)
if (rf) showPopup(e.lngLat, routePopupHTML(rf.historical, rf.future)) if (rf) showPopup(e.lngLat, routePopupHTML(rf.historical, rf.future))
}) })
map!.on('mouseleave', 'routes-hit', () => { map!.on('mouseleave', 'routes-hit', () => {
if (isTouch) return
map!.getCanvas().style.cursor = '' map!.getCanvas().style.cursor = ''
popup!.remove() popup!.remove()
}) })
map!.on('click', 'routes-hit', (e) => {
if (!isTouch) return
if (suppressRoutePopup) return
const rf = routeFlights.get(e.features![0].properties.routeKey as string)
if (rf) showPopup(e.lngLat, routePopupHTML(rf.historical, rf.future))
})
map!.on('mouseenter', 'routes-future-hit', (e) => { map!.on('mouseenter', 'routes-future-hit', (e) => {
if (isTouch) return
map!.getCanvas().style.cursor = 'pointer' map!.getCanvas().style.cursor = 'pointer'
const rf = routeFlights.get(e.features![0].properties.routeKey as string) const rf = routeFlights.get(e.features![0].properties.routeKey as string)
if (rf) showPopup(e.lngLat, routePopupHTML(rf.historical, rf.future)) if (rf) showPopup(e.lngLat, routePopupHTML(rf.historical, rf.future))
}) })
map!.on('mouseleave', 'routes-future-hit', () => { map!.on('mouseleave', 'routes-future-hit', () => {
if (isTouch) return
map!.getCanvas().style.cursor = '' map!.getCanvas().style.cursor = ''
popup!.remove() popup!.remove()
}) })
map!.on('click', 'routes-future-hit', (e) => { }
if (!isTouch) return
if (suppressRoutePopup) return // ── Unified click handler — airports take priority over routes ────
const rf = routeFlights.get(e.features![0].properties.routeKey as string) map!.on('click', (e) => {
if (rf) showPopup(e.lngLat, routePopupHTML(rf.historical, rf.future)) // Query airports with a larger bounding box on touch for easier tapping
const airportQuery = isTouch
? [[e.point.x - TOUCH_RADIUS, e.point.y - TOUCH_RADIUS], [e.point.x + TOUCH_RADIUS, e.point.y + TOUCH_RADIUS]] as [maplibregl.PointLike, maplibregl.PointLike]
: e.point as maplibregl.PointLike
const airportFeatures = map!.queryRenderedFeatures(airportQuery, { layers: airportLayerIds })
if (airportFeatures.length) {
const id = Number(airportFeatures[0].properties.id)
selectedAirportId = selectedAirportId === id ? null : id
applyFilter()
if (isTouch) {
const airport = airportById.get(id)
const geom = airportFeatures[0].geometry
if (airport && geom.type === 'Point') {
showPopup(geom.coordinates as LngLat, airportPopupHTML(airport))
}
}
return
}
// Then check routes
const routeFeatures = map!.queryRenderedFeatures(e.point, {
layers: ['routes-hit', 'routes-future-hit'],
}) })
map!.on('click', (e) => { if (routeFeatures.length) {
const features = map!.queryRenderedFeatures(e.point, { const rf = routeFlights.get(routeFeatures[0].properties.routeKey as string)
layers: Array.from({ length: PULSE_PHASES }, (_, i) => `airports-dot-${i}`), if (rf) showPopup(e.lngLat, routePopupHTML(rf.historical, rf.future))
}) return
if (!features.length) { }
// Empty map click — deselect everything
selectedAirportId = null selectedAirportId = null
applyFilter() applyFilter()
popup!.remove() popup!.remove()
}
}) })
// ── Pulse animation ───────────────────────────────────────────────
const period = 2200 const period = 2200
const animate = (): void => { const animate = (): void => {
const now = Date.now() const now = Date.now()
@@ -557,7 +558,6 @@ export default defineComponent({
} }
.ap-popup .maplibregl-popup-tip { border-top-color: rgba(10,14,22,0.95); } .ap-popup .maplibregl-popup-tip { border-top-color: rgba(10,14,22,0.95); }
/* Close button styling for touch devices */
.ap-popup .maplibregl-popup-close-button { .ap-popup .maplibregl-popup-close-button {
color: #556677; color: #556677;
font-size: 18px; font-size: 18px;
+381
View File
@@ -0,0 +1,381 @@
<script setup lang="ts">
import MainLayout from '@/Layouts/MainLayout.vue'
import GlassBox from '@/Components/FlightsGoneBy/GlassBox.vue'
import { Head, useForm } from '@inertiajs/vue3'
import AirlineSearchBox from '@/Components/FlightsGoneBy/AirlineSearchBox.vue'
import AircraftSearchBox from '@/Components/FlightsGoneBy/AircraftSearchBox.vue'
import AirportSearchBox from '@/Components/FlightsGoneBy/AirportSearchBox.vue'
import { ref } from 'vue'
defineOptions({ layout: MainLayout })
const props = defineProps<{
flight?: {
id: number
flight_number: string
departure_date: string
arrival_date: string
aircraft_registration: string
seat_number: string
note: string
auto_update: boolean
seat_type: { value: number; title: string } | null
flight_class: { value: number; title: string } | null
flight_reason: { value: number; title: string } | null
airline_options: { value: number; title: string }[]
from_options: { value: number; title: string; country_code: string }[]
to_options: { value: number; title: string; country_code: string }[]
aircraft_options: { value: number; title: string }[]
}
seat_types: { value: number; title: string }[]
flight_classes: { value: number; title: string }[]
flight_reasons: { value: number; title: string }[]
}>()
const isEdit = !!props.flight
// ── Flight number lookup ──────────────────────────────────────────────────────
const flightNumber = ref(props.flight?.flight_number ?? '')
const lookupLoading = ref(false)
const lookupError = ref<string | null>(null)
const lookupComplete = ref(isEdit)
interface LookupResult {
airline_options: { value: number; title: string }[]
from_options: { value: number; title: string; country_code: string }[]
to_options: { value: number; title: string; country_code: string }[]
aircraft_options: { value: number; title: string }[]
}
const lookupResult = ref<LookupResult | null>(null)
const lookupKey = ref(0)
async function lookupFlight() {
if (!flightNumber.value.trim()) return
lookupLoading.value = true
lookupError.value = null
lookupResult.value = null
try {
const response = await fetch(`${route('flights.lookup')}?number=${encodeURIComponent(flightNumber.value.trim())}`, {
headers: { Accept: 'application/json' },
})
const data = await response.json()
if (!response.ok) {
lookupError.value = data.message ?? 'Lookup failed.'
return
}
lookupResult.value = data
lookupComplete.value = true
if (data.airline_options?.length) {
airlineOptionsData.value = data.airline_options
if (!form.airline) form.airline = data.airline_options[0]
}
if (data.from_options?.length) {
fromOptionsData.value = data.from_options
if (data.from_options.length === 1 && !form.from) form.from = data.from_options[0]
}
if (data.to_options?.length) {
toOptionsData.value = data.to_options
if (data.to_options.length === 1 && !form.to) form.to = data.to_options[0]
}
if (data.aircraft_options?.length) {
aircraftOptionsData.value = data.aircraft_options
if (data.aircraft_options.length === 1 && !form.aircraft) form.aircraft = data.aircraft_options[0]
}
lookupKey.value++
} catch (e) {
lookupError.value = String(e)
} finally {
lookupLoading.value = false
}
}
// ── Form ──────────────────────────────────────────────────────────────────────
const form = useForm({
flight_number: props.flight?.flight_number ?? '',
departure_date: props.flight?.departure_date ?? '',
arrival_date: props.flight?.arrival_date ?? '',
from: props.flight?.from_options[0] ?? null as { value: number; title: string; country_code: string } | null,
to: props.flight?.to_options[0] ?? null as { value: number; title: string; country_code: string } | null,
airline: props.flight?.airline_options[0] ?? null as { value: number; title: string } | null,
aircraft: props.flight?.aircraft_options[0] ?? null as { value: number; title: string } | null,
aircraft_registration: props.flight?.aircraft_registration ?? '',
seat_number: props.flight?.seat_number ?? '',
seat_type: props.flight?.seat_type ?? props.seat_types[0],
flight_class: props.flight?.flight_class ?? props.flight_classes[0],
flight_reason: props.flight?.flight_reason ?? props.flight_reasons[0],
note: props.flight?.note ?? '',
auto_update: props.flight?.auto_update ?? false,
})
const submitForm = useForm({
flight_number: '' as string | null,
departure_date: '' as string | null,
arrival_date: '' as string | null,
from_id: null as number | null,
to_id: null as number | null,
airline_id: null as number | null,
aircraft_id: null as number | null,
aircraft_registration: '' as string | null,
seat_number: '' as string | null,
seat_type_id: null as number | null,
flight_class_id: null as number | null,
flight_reason_id: null as number | null,
note: '' as string | null,
auto_update: false,
})
function submit() {
submitForm.flight_number = form.flight_number
submitForm.departure_date = form.departure_date
submitForm.arrival_date = form.arrival_date
submitForm.from_id = form.from?.value ?? null
submitForm.to_id = form.to?.value ?? null
submitForm.airline_id = form.airline?.value ?? null
submitForm.aircraft_id = form.aircraft?.value ?? null
submitForm.aircraft_registration = form.aircraft_registration
submitForm.seat_number = form.seat_number
submitForm.seat_type_id = form.seat_type?.value ?? null
submitForm.flight_class_id = form.flight_class?.value ?? null
submitForm.flight_reason_id = form.flight_reason?.value ?? null
submitForm.note = form.note
submitForm.auto_update = form.auto_update
if (isEdit) {
submitForm.put(route('flights.update', { flight: props.flight!.id }))
} else {
submitForm.post(route('flights.store'))
}
}
// ── Prefilled options ─────────────────────────────────────────────────────────
const airlineOptionsData = ref<{ value: number; title: string }[]>(props.flight?.airline_options ?? [])
const fromOptionsData = ref<{ value: number; title: string; country_code: string }[]>(props.flight?.from_options ?? [])
const toOptionsData = ref<{ value: number; title: string; country_code: string }[]>(props.flight?.to_options ?? [])
const aircraftOptionsData = ref<{ value: number; title: string }[]>(props.flight?.aircraft_options ?? [])
</script>
<template>
<Head :title="isEdit ? 'Edit Flight' : 'Add Flight'" />
<GlassBox
:title="isEdit ? 'Edit Flight' : 'Add Flight'"
:blurb="isEdit
? 'Update the details for this flight.'
: 'Enter a flight number then press Look Up to continue.'"
>
<v-form style="width: 100%">
<v-container>
<!-- Flight number + lookup -->
<v-row>
<v-col cols="12">
<div class="d-flex ga-3 align-start">
<v-text-field
v-model="flightNumber"
label="Flight Number"
placeholder="e.g. QF1"
hide-details
@keydown.enter="lookupFlight"
/>
<v-btn
:loading="lookupLoading"
:disabled="!flightNumber.trim()"
size="large"
style="height: 56px"
@click="lookupFlight"
>
Look Up
</v-btn>
</div>
<div v-if="lookupError" class="text-error text-caption mt-1">
{{ lookupError }}
</div>
</v-col>
</v-row>
<!-- Departure + Arrival datetime -->
<v-row>
<v-col cols="12" md="6">
<v-text-field
v-model="form.departure_date"
label="Departure Date & Time"
type="datetime-local"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.departure_date"
/>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="form.arrival_date"
label="Arrival Date & Time"
type="datetime-local"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.arrival_date"
/>
</v-col>
</v-row>
<!-- From -->
<v-row>
<v-col cols="12">
<AirportSearchBox
v-model="form.from"
label="From"
:prefilled-options="fromOptionsData"
:error-messages="submitForm.errors.from_id"
:disabled="!lookupComplete"
/>
</v-col>
</v-row>
<!-- To -->
<v-row>
<v-col cols="12">
<AirportSearchBox
v-model="form.to"
label="To"
:prefilled-options="toOptionsData"
:error-messages="submitForm.errors.to_id"
:disabled="!lookupComplete"
/>
</v-col>
</v-row>
<!-- Airline -->
<v-row>
<v-col cols="12">
<AirlineSearchBox
:key="`airline-${lookupKey}`"
v-model="form.airline"
:prefilled-options="airlineOptionsData"
:error-messages="submitForm.errors.airline_id"
:disabled="!lookupComplete"
/>
</v-col>
</v-row>
<!-- Aircraft -->
<v-row>
<v-col cols="12">
<AircraftSearchBox
v-model="form.aircraft"
:prefilled-options="aircraftOptionsData"
:error-messages="submitForm.errors.aircraft_id"
:disabled="!lookupComplete"
/>
</v-col>
</v-row>
<!-- Registration + Flight class -->
<v-row>
<v-col cols="12" md="6">
<v-text-field
v-model="form.aircraft_registration"
label="Aircraft Registration"
placeholder="e.g. VH-OQA"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.aircraft_registration"
/>
</v-col>
<v-col cols="12" md="6">
<v-select
v-model="form.flight_class"
label="Flight Class"
:items="flight_classes"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.flight_class_id"
/>
</v-col>
</v-row>
<!-- Seat number + Seat type -->
<v-row>
<v-col cols="12" md="6">
<v-text-field
v-model="form.seat_number"
label="Seat Number"
placeholder="e.g. 12A"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.seat_number"
/>
</v-col>
<v-col cols="12" md="6">
<v-select
v-model="form.seat_type"
label="Seat Type"
:items="seat_types"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.seat_type_id"
/>
</v-col>
</v-row>
<!-- Flight reason -->
<v-row>
<v-col cols="12" md="6">
<v-select
v-model="form.flight_reason"
label="Flight Reason"
:items="flight_reasons"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.flight_reason_id"
/>
</v-col>
</v-row>
<!-- Note -->
<v-row>
<v-col cols="12">
<v-textarea
v-model="form.note"
label="Note"
placeholder="Any additional notes…"
:disabled="!lookupComplete"
:error-messages="submitForm.errors.note"
rows="3"
auto-grow
/>
</v-col>
</v-row>
<!-- Auto update -->
<v-row>
<v-col cols="12">
<v-checkbox
v-model="form.auto_update"
label="Automatically update aircraft details when flight departs."
:disabled="!lookupComplete"
hide-details
density="compact"
/>
</v-col>
</v-row>
<!-- Submit -->
<v-row>
<v-col cols="12">
<v-btn
block
size="large"
:loading="submitForm.processing"
:disabled="!lookupComplete || submitForm.processing"
@click="submit"
>
{{ isEdit ? 'Save Changes' : 'Add Flight' }}
</v-btn>
</v-col>
</v-row>
</v-container>
</v-form>
</GlassBox>
</template>
<style scoped>
</style>
+13 -1
View File
@@ -1,11 +1,15 @@
<?php <?php
use App\Http\Controllers\FlightController;
use App\Http\Controllers\FlightImportController; use App\Http\Controllers\FlightImportController;
use App\Http\Controllers\FlightProfileController; use App\Http\Controllers\FlightProfileController;
use App\Http\Controllers\LogoController; use App\Http\Controllers\LogoController;
use App\Http\Controllers\ProfileController; use App\Http\Controllers\ProfileController;
use App\Http\Controllers\SearchController; use App\Http\Controllers\SearchController;
use App\Models\Airline; use App\Models\Airline;
use App\Models\FlightClass;
use App\Models\FlightReason;
use App\Models\SeatType;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Inertia\Inertia; use Inertia\Inertia;
@@ -33,6 +37,14 @@ Route::domain(config('app.domain'))->group(
return Inertia::render('Fr24Import'); return Inertia::render('Fr24Import');
})->name('import.fr24'); })->name('import.fr24');
Route::get('/add-flight', function () {
return Inertia::render('AddFlight', [
'seat_types' => SeatType::all()->map(fn ($s) => ['value' => $s->id, 'title' => $s->name]),
'flight_reasons' => FlightReason::all()->map(fn ($f) => ['value' => $f->id, 'title' => $f->name]),
'flight_classes' => FlightClass::all()->map(fn ($f) => ['value' => $f->id, 'title' => $f->name]),
]);
});
Route::get('/reconcile', function () { Route::get('/reconcile', function () {
$flight = new FlightImportController()->reconcile(request()); $flight = new FlightImportController()->reconcile(request());
@@ -46,7 +58,7 @@ Route::domain(config('app.domain'))->group(
]); ]);
})->name('reconcile'); })->name('reconcile');
Route::get('/flights/lookup', [FlightController::class, 'lookup'])->name('flights.lookup');
Route::post('/flights/import', [FlightImportController::class, 'store'])->name('flights.import.store'); Route::post('/flights/import', [FlightImportController::class, 'store'])->name('flights.import.store');
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');