From fd3b5b0405561357c5e190e187dae532d5867dcd Mon Sep 17 00:00:00 2001 From: josh Date: Sun, 12 Jul 2026 01:42:57 +1000 Subject: [PATCH] Add preliminary missing airline check --- app/Http/Controllers/AdminController.php | 11 +++ app/Services/AdminService.php | 33 +++++++ .../Components/Admin/MissingAirlineCard.vue | 91 +++++++++++++++++++ resources/js/Pages/Admin/AdminLayout.vue | 3 +- resources/js/Pages/Admin/AdminSidebar.vue | 6 ++ resources/js/Pages/Admin/MissingAirlines.vue | 72 +++++++++++++++ resources/js/Types/types.d.ts | 8 ++ routes/web.php | 2 + 8 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 resources/js/Components/Admin/MissingAirlineCard.vue create mode 100644 resources/js/Pages/Admin/MissingAirlines.vue diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 0d20463..000dcfb 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -19,6 +19,7 @@ class AdminController extends Controller function staticAdminData(){ return [ 'missingLiveryCount' => $this->adminService->getMissingLiveries()->count(), + 'missingAirlineCount' => $this->adminService->getMissingAirlines()->count(), ]; } @@ -44,6 +45,16 @@ class AdminController extends Controller ]); } + function reconcileMissingAirlines(){ + $missingAirlines = $this->adminService->getMissingAirlines()->values(); + + return Inertia::render('Admin/MissingAirlines', [ + 'title' => $missingAirlines->count() . ' Missing Airlines', + 'missingAirlines' => $missingAirlines, + ...$this->staticAdminData(), + ]); + } + public function ignoreMissingLivery(Request $request) { $validated = $request->validate([ diff --git a/app/Services/AdminService.php b/app/Services/AdminService.php index 86f5f1f..2b70761 100644 --- a/app/Services/AdminService.php +++ b/app/Services/AdminService.php @@ -5,6 +5,7 @@ namespace App\Services; use App\DTOs\MissingLivery; use App\Models\IgnoredMissingLivery; use App\Models\UserFlight; +use Illuminate\Support\Enumerable; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Collection; @@ -45,4 +46,36 @@ class AdminService ->sortBy('airline_name') ->values(); } + + function getMissingAirlines(): Enumerable|Collection + { + $flights = UserFlight::whereNotNull('flight_number') + ->whereNull('airline_id') + ->with('user:id,name') // adjust to your actual user relation/columns + ->get(['id', 'flight_number', 'user_id']); + + return $flights + ->map(function ($flight) { + // Extract the 2-character IATA-style prefix (letters and/or digits) + preg_match('/^([A-Za-z0-9]{2})\d+/', trim($flight->flight_number), $matches); + + return [ + 'code' => $matches[1] ?? null, + 'flight_number' => $flight->flight_number, + 'link' => "/u/{$flight->user->name}/flight/{$flight->id}", + ]; + }) + ->filter(fn ($f) => $f['code'] !== null) // drop any that didn't match the pattern + ->groupBy('code') + ->map(function ($group, $code) { + return [ + 'code' => $code, + 'flights' => $group->map(fn ($f) => [ + 'flight_number' => $f['flight_number'], + 'link' => $f['link'], + ])->values()->all(), + ]; + }) + ->values(); + } } diff --git a/resources/js/Components/Admin/MissingAirlineCard.vue b/resources/js/Components/Admin/MissingAirlineCard.vue new file mode 100644 index 0000000..bff6252 --- /dev/null +++ b/resources/js/Components/Admin/MissingAirlineCard.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/resources/js/Pages/Admin/AdminLayout.vue b/resources/js/Pages/Admin/AdminLayout.vue index 4e331ca..e4a62de 100644 --- a/resources/js/Pages/Admin/AdminLayout.vue +++ b/resources/js/Pages/Admin/AdminLayout.vue @@ -7,6 +7,7 @@ import {Head} from "@inertiajs/vue3"; defineProps<{ title: string; missingLiveryCount: number; + missingAirlineCount: number; }>() @@ -15,7 +16,7 @@ defineProps<{
- +
diff --git a/resources/js/Pages/Admin/AdminSidebar.vue b/resources/js/Pages/Admin/AdminSidebar.vue index 8883b67..f07bc10 100644 --- a/resources/js/Pages/Admin/AdminSidebar.vue +++ b/resources/js/Pages/Admin/AdminSidebar.vue @@ -3,6 +3,7 @@ import {Link} from "@inertiajs/vue3"; import {number} from "echarts"; defineProps<{ missingLiveryCount: number; + missingAirlineCount: number; }>() @@ -18,6 +19,11 @@ defineProps<{ Reconcile Missing Liveries {{ missingLiveryCount }} + + + Reconcile Missing Airlines + {{ missingAirlineCount }} + diff --git a/resources/js/Pages/Admin/MissingAirlines.vue b/resources/js/Pages/Admin/MissingAirlines.vue new file mode 100644 index 0000000..6d85f41 --- /dev/null +++ b/resources/js/Pages/Admin/MissingAirlines.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/resources/js/Types/types.d.ts b/resources/js/Types/types.d.ts index 7d98382..3d450b0 100644 --- a/resources/js/Types/types.d.ts +++ b/resources/js/Types/types.d.ts @@ -326,6 +326,14 @@ export interface MissingLivery { clipboard_text: string; } +export interface MissingAirline{ + code: string; + flights: { + flight_number: string; + link: string; + }[] +} + declare module '@inertiajs/vue3' { interface PageProps extends SharedProps {} } diff --git a/routes/web.php b/routes/web.php index b4b121a..7a5e651 100644 --- a/routes/web.php +++ b/routes/web.php @@ -40,6 +40,8 @@ use Inertia\Inertia; Route::get('/', [AdminController::class, 'dashboard'])->name('dashboard'); Route::get('/missing-liveries', [AdminController::class, 'reconcileMissingLiveries'])->name('reconcile-missing-liveries')->middleware('permission:reconcile_missing_liveries'); Route::post('/ignore-missing-livery', [AdminController::class, 'ignoreMissingLivery'])->name('ignore-missing-livery'); + + Route::get('/missing-airlines', [AdminController::class, 'reconcileMissingAirlines'])->name('reconcile-missing-airlines')->middleware('permission:reconcile_missing_liveries'); });