diff --git a/app/Http/Controllers/FlightImportController.php b/app/Http/Controllers/FlightImportController.php
index d95812e..33ee42e 100644
--- a/app/Http/Controllers/FlightImportController.php
+++ b/app/Http/Controllers/FlightImportController.php
@@ -2,7 +2,9 @@
namespace App\Http\Controllers;
+use App\Models\Aircraft;
use App\Models\Airline;
+use App\Models\Airport;
use App\Models\ImportedFlight;
use Carbon\Carbon;
use Illuminate\Http\Request;
@@ -43,6 +45,64 @@ class FlightImportController extends Controller
return str_pad($hours, 2, '0', STR_PAD_LEFT) . ':' . $minutes;
}
+ public function getPossibleAircraft(string $aircraftQuery) {
+ preg_match('/\((\w+)\)/', $aircraftQuery, $matches);
+ $designator = $matches[1] ?? null;
+
+ if(!$designator){
+ $aircraft = [];
+ } else {
+
+ $aircraft = Aircraft::when($designator, fn($query) => $query->where('designator', 'ilike', $designator))
+ ->orderBy('model_full_name')
+ ->limit(10)
+ ->get(['id', 'manufacturer_code', 'model_full_name', 'designator'])
+ ->map(fn($a) => [
+ 'value' => $a->id,
+ 'title' => "{$a->manufacturer_code} {$a->model_full_name} ({$a->designator})",
+ ])
+ ->values()
+ ->toArray();
+ }
+
+ return $aircraft;
+ }
+
+ public function getPossibleAirports(string $airportQuery) {
+ preg_match('/\((\w{3})\/(\w{4})\)/', $airportQuery, $matches);
+ $iata = $matches[1] ?? null;
+ $icao = $matches[2] ?? null;
+
+ if (!$iata && !$icao) {
+ return [];
+ }
+
+ $airports = Airport::with('region.country')
+ ->where(function ($q) use ($iata, $icao) {
+ $q->where('iata_code', 'ilike', $iata)
+ ->orWhere('icao_code', 'ilike', $icao);
+ })
+ ->orderByRaw("
+ CASE
+ WHEN iata_code = ? AND icao_code = ? THEN 0
+ WHEN iata_code = ? THEN 1
+ WHEN icao_code = ? THEN 2
+ ELSE 3
+ END
+ ", [$iata, $icao, $iata, $icao])
+ ->limit(10)
+ ->get(['id', 'name', 'municipality', 'iata_code', 'icao_code', 'region_id'])
+ ->map(fn($a) => [
+ 'value' => $a->id,
+ 'title' => "{$a->name} ({$a->iata_code}/{$a->icao_code})",
+ 'country_code' => strtolower($a?->region->country->code ?? ''),
+ ])
+ ->values()
+ ->toArray();
+
+ return $airports;
+ }
+
public function getPossibleAirlines(string $airlineQuery) {
preg_match('/\((\w{2,3})\/(\w{3,4})\)/', $airlineQuery, $matches);
$iata = $matches[1] ?? null;
@@ -72,10 +132,8 @@ class FlightImportController extends Controller
->values()
->toArray();
- return [
- 'airline_options' => $airlines,
- 'raw_airline' => $airlineQuery,
- ];
+
+ return $airlines;
}
public function reconcile(Request $request)
@@ -119,9 +177,40 @@ class FlightImportController extends Controller
'flight_class' => $flightToReconcile->flight_class ?? '',
'seat_type' => $flightToReconcile->seat_type ?? '',
'flight_reason' => $flightToReconcile->flight_reason ?? '',
- 'airline_options' => $this->getPossibleAirlines($flightToReconcile->airline ?? '')['airline_options'],
+ 'airline_options' => $this->getPossibleAirlines($flightToReconcile->airline ?? ''),
+ 'to_options' => $this->getPossibleAirports($flightToReconcile->to ?? ''),
+ 'from_options' => $this->getPossibleAirports($flightToReconcile->from ?? ''),
+ 'aircraft_options' => $this->getPossibleAircraft($flightToReconcile->aircraft ?? ''),
];
}
+
+ public function save(Request $request){
+ $validated = $request->validate([
+ 'date' => 'required|date',
+ 'flight_number' => 'required|string',
+ 'from_id' => 'required|integer|exists:airports,id',
+ 'to_id' => 'required|integer|exists:airports,id',
+ 'dep_time' => 'nullable|date_format:H:i',
+ 'arr_time' => 'nullable|date_format:H:i',
+ 'duration' => 'nullable|date_format:H:i',
+ 'airline_id' => 'nullable|integer|exists:airlines,id',
+ 'aircraft_id' => 'nullable|integer|exists:aircraft,id',
+ 'registration' => 'nullable|string',
+ 'seat_number' => 'nullable|string',
+ 'seat_type_id' => 'nullable|integer',
+ 'flight_class_id' => 'nullable|integer',
+ 'flight_reason_id' => 'nullable|integer',
+ 'note' => 'nullable|string',
+ ],[
+ 'from_id.required' => 'Please select a departure airport.',
+ 'to_id.required' => 'Please select an arrival airport.',
+ 'dep_time.date_format' => 'Departure time must be in HH:MM format, i.e: 01:25',
+ 'arr_time.date_format' => 'Arrival time must be in HH:MM format, i.e: 12:25',
+ 'duration.date_format' => 'Must be in HH:MM format, e.g: 03:37',
+ ]);
+
+ }
+
public function store(Request $request)
{
try {
diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php
new file mode 100644
index 0000000..3edc6a5
--- /dev/null
+++ b/app/Http/Controllers/SearchController.php
@@ -0,0 +1,74 @@
+where(function ($query) use ($q) {
+ $query->where('name', 'ilike', "%{$q}%")
+ ->orWhere('IATA_code', 'ilike', "%{$q}%")
+ ->orWhere('ICAO_code', 'ilike', "%{$q}%");
+ })
+ ->limit(15)
+ ->get(['id', 'name', 'IATA_code', 'ICAO_code', 'logo'])
+ ->map(fn($a) => [
+ 'value' => $a->id,
+ 'title' => "{$a->name} ({$a->IATA_code}/{$a->ICAO_code})",
+ ])
+ ->values();
+ }
+
+ public function aircraft()
+ {
+ $q = request('q', '');
+
+ return Aircraft::where('designator', 'ilike', "%{$q}%")
+ ->orWhereRaw("CONCAT(manufacturer_code, ' ', model_full_name) ilike ?", ["%{$q}%"])
+ ->limit(15)
+ ->get(['id', 'manufacturer_code', 'model_full_name', 'designator'])
+ ->map(fn($a) => [
+ 'value' => $a->id,
+ 'title' => "{$a->manufacturer_code} {$a->model_full_name} ({$a->designator})",
+ ])
+ ->values();
+ }
+
+ public function airports()
+ {
+ $q = request('q', '');
+ $len = strlen($q);
+
+ if ($len < 3) return [];
+
+ return Airport::with('region.country')
+ ->when($len === 3, fn($query) => $query->where('iata_code', 'ilike', $q))
+ ->when($len >= 4, fn($query) => $query->where(function ($sub) use ($q, $len) {
+ $sub->when($len === 4, fn($s) => $s->where('icao_code', 'ilike', $q))
+ ->orWhere('name', 'ilike', "%{$q}%")
+ ->orWhere('municipality', 'ilike', "%{$q}%");
+ })->orderByRaw("
+ CASE
+ WHEN icao_code = ? THEN 0
+ WHEN iata_code = ? THEN 1
+ ELSE 2
+ END
+ ", [$q, $q]))
+ ->limit(15)
+ ->get(['id', 'name', 'municipality', 'iata_code', 'icao_code', 'region_id'])
+ ->map(fn($a) => [
+ 'value' => $a->id,
+ 'title' => "{$a->name} ({$a->iata_code}/{$a->icao_code})",
+ 'country_code' => strtolower($a->region->country->code),
+ ])
+ ->values();
+ }
+}
diff --git a/app/Models/Airline.php b/app/Models/Airline.php
index 8b71ed9..99e5b05 100644
--- a/app/Models/Airline.php
+++ b/app/Models/Airline.php
@@ -24,5 +24,6 @@ class Airline extends Model
'active' => 'boolean',
];
+
public $timestamps = false;
}
diff --git a/package-lock.json b/package-lock.json
index a09c094..21c6e11 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,6 +6,7 @@
"": {
"dependencies": {
"@mdi/font": "^7.4.47",
+ "flag-icons": "^7.5.0",
"vuetify": "^4.0.5"
},
"devDependencies": {
@@ -2703,6 +2704,12 @@
"node": ">=8"
}
},
+ "node_modules/flag-icons": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-7.5.0.tgz",
+ "integrity": "sha512-kd+MNXviFIg5hijH766tt+3x76ele1AXlo4zDdCxIvqWZhKt4T83bOtxUOOMlTx/EcFdUMH5yvQgYlFh1EqqFg==",
+ "license": "MIT"
+ },
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
diff --git a/package.json b/package.json
index 2790603..32e267e 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
},
"dependencies": {
"@mdi/font": "^7.4.47",
+ "flag-icons": "^7.5.0",
"vuetify": "^4.0.5"
}
}
diff --git a/resources/css/app.css b/resources/css/app.css
index e899ff5..9c59507 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -15,8 +15,8 @@
.glass {
background: rgba(17, 24, 39, 0.2); /* --surface at 60% */
- backdrop-filter: blur(12px) saturate(180%);
-webkit-backdrop-filter: blur(12px) saturate(180%);
+ backdrop-filter: blur(12px) saturate(180%);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(56, 189, 248, 0.05);
}
diff --git a/resources/js/Components/FlightsGoneBy/AircraftSearchBox.vue b/resources/js/Components/FlightsGoneBy/AircraftSearchBox.vue
new file mode 100644
index 0000000..ce530da
--- /dev/null
+++ b/resources/js/Components/FlightsGoneBy/AircraftSearchBox.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
diff --git a/resources/js/Components/FlightsGoneBy/AirlineSearchBox.vue b/resources/js/Components/FlightsGoneBy/AirlineSearchBox.vue
index 801b0fc..7e58106 100644
--- a/resources/js/Components/FlightsGoneBy/AirlineSearchBox.vue
+++ b/resources/js/Components/FlightsGoneBy/AirlineSearchBox.vue
@@ -31,7 +31,7 @@ const searchAirlines = async (query: string) => {
if (query === model.value?.title) return
- const { data } = await axios.get('/airlines/search', { params: { q: query } })
+ const { data } = await axios.get('/search/airlines', { params: { q: query } })
airlineOptions.value = data
}
diff --git a/resources/js/Components/FlightsGoneBy/AirportSearchBox.vue b/resources/js/Components/FlightsGoneBy/AirportSearchBox.vue
new file mode 100644
index 0000000..1894328
--- /dev/null
+++ b/resources/js/Components/FlightsGoneBy/AirportSearchBox.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/Pages/ReconcileFlight.vue b/resources/js/Pages/ReconcileFlight.vue
index fc66049..8749578 100644
--- a/resources/js/Pages/ReconcileFlight.vue
+++ b/resources/js/Pages/ReconcileFlight.vue
@@ -3,6 +3,8 @@ 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";
defineOptions({ layout: MainLayout });
@@ -24,6 +26,9 @@ const props = defineProps<{
arr_time: string
duration: string
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 flight = props.flight;
@@ -31,13 +36,13 @@ const flight = props.flight;
const form = useForm({
date: flight.date,
flight_number: flight.flight_number,
- from: '',
- to: '',
+ from: flight.from_options[0] ?? null,
+ to: flight.to_options[0] ?? null,
dep_time: flight.dep_time,
arr_time: flight.arr_time,
duration: flight.duration,
airline_id: flight.airline_options[0] ?? null,
- aircraft: '',
+ aircraft: flight.aircraft_options[0] ?? null,
registration: flight.registration,
seat_number: flight.seat_number,
seat_type: flight.seat_types[flight.seat_type],
@@ -47,8 +52,42 @@ const form = useForm({
});
+const submitForm = useForm({
+ date: '' as string | null,
+ flight_number: '' as string | null,
+ from_id: null as number | null,
+ to_id: null as number | null,
+ dep_time: '' as string | null,
+ arr_time: '' as string | null,
+ duration: '' as string | null,
+ airline_id: null as number | null,
+ aircraft_id: null as number | null,
+ 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,
+});
+
function submit() {
- form.post(route('reconcile.store'));
+ submitForm.date = form.date;
+ submitForm.flight_number = form.flight_number;
+ submitForm.from_id = form.from?.value ?? null;
+ submitForm.to_id = form.to?.value ?? null;
+ submitForm.dep_time = form.dep_time;
+ submitForm.arr_time = form.arr_time;
+ submitForm.duration = form.duration;
+ submitForm.airline_id = form.airline_id?.value ?? null;
+ submitForm.aircraft_id = form.aircraft?.value ?? null;
+ submitForm.registration = form.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.post(route('import.save'));
}
@@ -65,36 +104,47 @@ function submit() {
-
+
-
+
-
+
-
-
-
+
+
-
-
+
+
+
+
-
+
-
+
-
+
@@ -104,35 +154,39 @@ function submit() {
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/resources/js/app.ts b/resources/js/app.ts
index 6f7ddcc..35e228e 100644
--- a/resources/js/app.ts
+++ b/resources/js/app.ts
@@ -7,6 +7,7 @@ import { ZiggyVue } from '../../vendor/tightenco/ziggy';
import { createApp, h, DefineComponent } from 'vue';
import vuetify from './plugins/vuetify';
import '@mdi/font/css/materialdesignicons.css'
+import 'flag-icons/css/flag-icons.min.css'
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
diff --git a/routes/web.php b/routes/web.php
index 0e3c7e7..6dd2c52 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -3,6 +3,7 @@
use App\Http\Controllers\FlightImportController;
use App\Http\Controllers\LogoController;
use App\Http\Controllers\ProfileController;
+use App\Http\Controllers\SearchController;
use App\Models\Airline;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
@@ -43,24 +44,6 @@ Route::domain(config('app.domain'))->group(
})->name('reconcile');
- Route::get('/airlines/search', function () {
- $q = request('q', '');
-
- return Airline::orderByDesc('active')
- ->where(function ($query) use ($q) {
- $query->where('name', 'ilike', "%{$q}%")
- ->orWhere('IATA_code', 'ilike', "%{$q}%")
- ->orWhere('ICAO_code', 'ilike', "%{$q}%");
- })
- ->limit(15)
- ->get(['id', 'name', 'IATA_code', 'ICAO_code', 'logo'])
- ->map(fn($a) => [
- 'value' => $a->id,
- 'title' => "{$a->name} ({$a->IATA_code}/{$a->ICAO_code})",
- ])
- ->values();
- })->name('airlines.search');
-
Route::post('/flights/import', [FlightImportController::class, 'store'])->name('flights.import.store');
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
@@ -68,6 +51,13 @@ Route::domain(config('app.domain'))->group(
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
+ Route::post('/import/save', [FlightImportController::class, 'save'])->name('import.save');
+
+ //Search Routes
+ Route::get('/search/airlines', [SearchController::class, 'airlines'])->name('search.airlines');
+ Route::get('/search/aircraft', [SearchController::class, 'aircraft'])->name('search.aircraft');
+ Route::get('/search/airports', [SearchController::class, 'airports'])->name('search.airports');
+
require __DIR__.'/auth.php';
}