From 4a296705a6d4671dae8086270a3d80d0975eafd9 Mon Sep 17 00:00:00 2001 From: josh Date: Fri, 21 Aug 2026 22:41:29 +1000 Subject: [PATCH] Added admin controls for missing airlines --- app/Http/Controllers/AdminController.php | 14 ++++ app/Models/UserFlight.php | 11 +++ app/Models/UserFlightIgnoreMissingAirline.php | 20 +++++ app/Services/AdminService.php | 8 +- ...122451_add_ignore_user_flight_airlines.php | 26 +++++++ .../Components/Admin/MissingAirlineCard.vue | 73 +++++++++++++------ resources/js/Pages/Admin/AdminSidebar.vue | 2 +- resources/js/Types/types.d.ts | 1 + routes/web.php | 2 + 9 files changed, 132 insertions(+), 25 deletions(-) create mode 100644 app/Models/UserFlightIgnoreMissingAirline.php create mode 100644 database/migrations/2026_08_21_122451_add_ignore_user_flight_airlines.php diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 000dcfb..9274b2c 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -7,6 +7,7 @@ use App\Models\IgnoredMissingLivery; use App\Models\User; use App\Models\UserFlight; use App\Services\AdminService; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Carbon; use Inertia\Inertia; @@ -66,4 +67,17 @@ class AdminController extends Controller return back(); } + public function ignoreMissingAirline(Request $request): JsonResponse + { + $validated = $request->validate([ + 'flight_id' => ['required', 'integer', 'exists:user_flights,id'], + ]); + + $flight = UserFlight::findOrFail($validated['flight_id']); + + $flight->ignoreMissingAirline()->firstOrCreate([]); + + return response()->json(['status' => 'ok']); + } + } diff --git a/app/Models/UserFlight.php b/app/Models/UserFlight.php index 0f4a584..4ecc3b2 100644 --- a/app/Models/UserFlight.php +++ b/app/Models/UserFlight.php @@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Storage; @@ -271,6 +272,16 @@ class UserFlight extends Model } + public function ignoreMissingAirline(): HasOne + { + return $this->hasOne(UserFlightIgnoreMissingAirline::class); + } + + public function isIgnoringMissingAirline(): bool + { + return $this->ignoreMissingAirline()->exists(); + } + public function aircraft(): BelongsTo { return $this->belongsTo(Aircraft::class); diff --git a/app/Models/UserFlightIgnoreMissingAirline.php b/app/Models/UserFlightIgnoreMissingAirline.php new file mode 100644 index 0000000..aed3b9e --- /dev/null +++ b/app/Models/UserFlightIgnoreMissingAirline.php @@ -0,0 +1,20 @@ +belongsTo(UserFlight::class); + } +} diff --git a/app/Services/AdminService.php b/app/Services/AdminService.php index 2b70761..6b16969 100644 --- a/app/Services/AdminService.php +++ b/app/Services/AdminService.php @@ -51,26 +51,28 @@ class AdminService { $flights = UserFlight::whereNotNull('flight_number') ->whereNull('airline_id') - ->with('user:id,name') // adjust to your actual user relation/columns + ->whereDoesntHave('ignoreMissingAirline') + ->with('user:id,name') ->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 [ + 'id' => $flight->id, '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 + ->filter(fn ($f) => $f['code'] !== null) ->groupBy('code') ->map(function ($group, $code) { return [ 'code' => $code, 'flights' => $group->map(fn ($f) => [ + 'id' => $f['id'], 'flight_number' => $f['flight_number'], 'link' => $f['link'], ])->values()->all(), diff --git a/database/migrations/2026_08_21_122451_add_ignore_user_flight_airlines.php b/database/migrations/2026_08_21_122451_add_ignore_user_flight_airlines.php new file mode 100644 index 0000000..69619dd --- /dev/null +++ b/database/migrations/2026_08_21_122451_add_ignore_user_flight_airlines.php @@ -0,0 +1,26 @@ +id(); + $table->foreignId('user_flight_id') + ->constrained('user_flights') + ->cascadeOnDelete(); + $table->timestamps(); + + $table->unique('user_flight_id'); + }); + } + + public function down(): void + { + Schema::dropIfExists('user_flights_ignore_missing_airlines'); + } +}; diff --git a/resources/js/Components/Admin/MissingAirlineCard.vue b/resources/js/Components/Admin/MissingAirlineCard.vue index bff6252..ccadde2 100644 --- a/resources/js/Components/Admin/MissingAirlineCard.vue +++ b/resources/js/Components/Admin/MissingAirlineCard.vue @@ -1,25 +1,63 @@ @@ -62,30 +100,23 @@ defineProps<{ display: flex; flex-wrap: wrap; gap: 0.5rem; - list-style: none; - padding: 0; - margin: 0; } -.flight-item { - display: flex; -} - -.flight-link { +.flight-chip { font-family: 'Share Tech Mono', monospace; font-size: 0.72rem; letter-spacing: 0.06em; + background: rgba(255, 255, 255, 0.04) !important; + border: 1px solid rgba(255, 255, 255, 0.06); +} + +.flight-link { color: #9fb3c8; text-decoration: none; - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.06); - border-radius: 4px; - padding: 0.25rem 0.5rem; - transition: color 0.15s ease, border-color 0.15s ease; + transition: color 0.15s ease; } .flight-link:hover { color: #ffc107; - border-color: rgba(255, 193, 7, 0.4); } diff --git a/resources/js/Pages/Admin/AdminSidebar.vue b/resources/js/Pages/Admin/AdminSidebar.vue index f07bc10..15f5d36 100644 --- a/resources/js/Pages/Admin/AdminSidebar.vue +++ b/resources/js/Pages/Admin/AdminSidebar.vue @@ -19,7 +19,7 @@ defineProps<{ Reconcile Missing Liveries {{ missingLiveryCount }} - + Reconcile Missing Airlines {{ missingAirlineCount }} diff --git a/resources/js/Types/types.d.ts b/resources/js/Types/types.d.ts index f9b7dcb..26ea0d3 100644 --- a/resources/js/Types/types.d.ts +++ b/resources/js/Types/types.d.ts @@ -334,6 +334,7 @@ export interface MissingAirline{ flights: { flight_number: string; link: string; + id: number; }[] } diff --git a/routes/web.php b/routes/web.php index 359725b..f4ef151 100644 --- a/routes/web.php +++ b/routes/web.php @@ -42,6 +42,8 @@ use Inertia\Inertia; 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'); + Route::post('/ignore-missing-airline', [AdminController::class, 'ignoreMissingAirline'])->name('admin.ignore-missing-airline'); + });