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/Http/Controllers/FlightController.php b/app/Http/Controllers/FlightController.php index 2115b7c..7747999 100644 --- a/app/Http/Controllers/FlightController.php +++ b/app/Http/Controllers/FlightController.php @@ -245,15 +245,19 @@ class FlightController extends Controller $newFlight = auth()->user()->flights()->create($this->flightPayload($validated)); UserAction::create([ - 'user_id' => $newFlight->user_id, - 'type' => $newFlight->departure_date->isFuture() ? 'flight_booked' : 'flight_logged', - 'data' => [ + 'user_id' => $newFlight->user_id, + 'type' => $newFlight->departure_date->isFuture() ? 'flight_booked' : 'flight_logged', + 'data' => [ 'flight' => $newFlight->snapshot($newFlight->id), ], ]); + if ($request->boolean('add_more')) { + return redirect()->route('flights.add') + ->with('success', 'Flight saved. Add another below.'); + } - return redirect()->route('profile.departure-board', [Auth::user()->name, $newFlight->id]); + return redirect()->route('profile.flight', [Auth::user()->name, $newFlight->id]); } 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/Achievements/AchievementService.php b/app/Services/Achievements/AchievementService.php index f5b869f..9ab7d72 100644 --- a/app/Services/Achievements/AchievementService.php +++ b/app/Services/Achievements/AchievementService.php @@ -98,7 +98,8 @@ class AchievementService ->where('achievement_id', $achievement->id) ->first(); - $alreadyUnlocked = $existing !== null; + $alreadyUnlocked = $existing !== null + && (! $achievement->progressive || $existing->progress >= $achievement->threshold); UserAchievement::updateOrCreate( ['user_id' => $user->id, 'achievement_id' => $achievement->id], 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/app/Settings/SettingsRegistry.php b/app/Settings/SettingsRegistry.php index f6120a1..3b354b5 100644 --- a/app/Settings/SettingsRegistry.php +++ b/app/Settings/SettingsRegistry.php @@ -72,7 +72,7 @@ class SettingsRegistry 'type' => 'select', 'label' => 'Default Page', 'category' => 'FlightsGoneBy Settings', - 'default' => 'feed_first', + 'default' => 'profile', 'options' => [ ['value' => 'feed_first', 'label' => 'My Feed if Following People, My Profile if Not'], ['value' => 'profile', 'label' => 'My Profile'], @@ -155,7 +155,7 @@ class SettingsRegistry ['value' => 'seat_type_id', 'label' => 'Seat Type'], ['value' => 'seat_number', 'label' => 'Seat Number'], ['value' => 'flight_reason_id', 'label' => 'Reason'], - + ['value' => 'note', 'label' => 'Note'], ['value' => 'aircraft.display_name_short', 'label' => 'Aircraft Full Name'], ['value' => 'class_seat_combined', 'label' => 'Class/Seat Type/Seat Number Combined'], ], diff --git a/bootstrap/app.php b/bootstrap/app.php index f9260c0..5ead8d9 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -67,7 +67,7 @@ return Application::configure(basePath: dirname(__DIR__)) 'message' => 'You don\'t have permission to access this page.', ], 404 => [ - 'title' => 'You Flight Has Been Cancelled', + 'title' => 'Your Flight Has Been Cancelled', 'message' => 'The page you are looking for doesn\'t exist or has been moved.', ], 419 => [ 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/Components/FlightsGoneBy/DepartureBoard.vue b/resources/js/Components/FlightsGoneBy/DepartureBoard.vue index ed405ee..6f241ab 100644 --- a/resources/js/Components/FlightsGoneBy/DepartureBoard.vue +++ b/resources/js/Components/FlightsGoneBy/DepartureBoard.vue @@ -17,7 +17,7 @@ const props = defineProps<{ const page = usePage().props const ITEMS_PER_PAGE = 25 -const defaultColumns = ['airline', 'flight_number', 'departure_airport', 'arrival_airport', 'departure_date', 'departure_time', 'arrival_time', 'duration', 'distance', 'aircraft', 'registration', 'class_seat_combined'] +const defaultColumns = ['airline', 'flight_number', 'departure_airport', 'arrival_airport', 'departure_date', 'departure_time', 'arrival_time', 'duration', 'distance', 'aircraft', 'registration', 'class_seat_combined', 'note'] const columnsToShow = computed(() => page.auth.user?.resolved_settings?.departure_board_columns ?? defaultColumns) const showColumn = (column: string) => columnsToShow.value.includes(column) @@ -44,6 +44,7 @@ const allHeaders = [ { title: 'SEAT TYPE', key: 'seat_type_id', sortable: true }, { title: 'SEAT NO.', key: 'seat_number', sortable: true }, { title: 'REASON', key: 'flight_reason_id', sortable: true }, + { title: 'NOTE', key: 'note', sortable: true }, { title: '', key: 'actions', sortable: false }, ] diff --git a/resources/js/Components/FlightsGoneBy/DepartureBoardTableRow.vue b/resources/js/Components/FlightsGoneBy/DepartureBoardTableRow.vue index 504f4c5..ddfbabc 100644 --- a/resources/js/Components/FlightsGoneBy/DepartureBoardTableRow.vue +++ b/resources/js/Components/FlightsGoneBy/DepartureBoardTableRow.vue @@ -41,7 +41,7 @@ const reasonBadgeVariant = (reason: string) => { } const showColumn = (column: string) => props.columnsToShow.includes(column) - +console.log(props.columnsToShow) 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'); + });