Flight Search UI
Native FilamentPHP v4.1 implementation of flight search interface with interactive leg selection, collapsible panels, and component-based architecture.
Quick Start
Section titled “Quick Start”Accessing Flight Search
Section titled “Accessing Flight Search”URL: /admin/flight-searches/flights
Navigation: Admin Panel → Flight Searches → Flights (Aerticket)
Basic Search
Section titled “Basic Search”- Select departure and arrival airports (searchable dropdowns)
- Choose dates and passenger counts
- Configure fare options and preferences
- Click “Search Flights”
- Browse results with interactive leg selection
- View fare details and book flights
Architecture
Section titled “Architecture”Page Structure
Section titled “Page Structure”File: app/Filament/Resources/FlightSearches/Pages/SearchFlights.php
Components:
- Form - Search criteria using FilamentPHP Schema
- Table - Results display using FilamentPHP Table Builder
- Actions - Search, clear, and fare details actions
- Livewire Methods - Interactive leg selection
Component Hierarchy
Section titled “Component Hierarchy”SearchFlights (Page)├── Form (Schema)│ ├── Section: Flight Search│ │ ├── Grid: Origin/Destination/Dates│ │ ├── Grid: Passengers/Cabin│ │ ├── Section: Fare Options│ │ ├── Section: Flight Preferences│ │ └── Section: Airline Filtering├── Table│ ├── Split: Fare Summary│ │ ├── Stack: Price Info│ │ └── Split: Badges (Stops, Duration, Source)│ └── Panel: Collapsible Flight Legs│ ├── Stack: Outbound Options│ │ └── Split: Radio + Leg Details (clickable)│ └── Stack: Return Options│ └── Split: Radio + Leg Details (clickable)└── Actions ├── searchFlights() ├── clearResults() └── viewFareDetails()Search Form
Section titled “Search Form”Trip Type Selection
Section titled “Trip Type Selection”Radio::make('trip_type') ->label('Trip Type') ->options([ 'one_way' => 'One Way', 'round_trip' => 'Round Trip', 'multi_city' => 'Multi-City', ]) ->default('one_way') ->inline() ->live() ->columnSpanFull()Reactive Behavior:
- Return date field visibility tied to
trip_type === 'round_trip' - Multi-city segments visible when
trip_type === 'multi_city'
Multi-City Segments
Section titled “Multi-City Segments”Repeater::make('segments') ->label('Trip Segments') ->visible(fn (Get $get): bool => $get('trip_type') === 'multi_city') ->minItems(2) ->maxItems(6) ->schema([ Grid::make(3) ->schema([ Select::make('departure_airport'), Select::make('arrival_airport'), DatePicker::make('departure_date'), ]), ])Constraints:
- Minimum 2 segments, maximum 6 segments
- All segment dates must be in chronological order
- Validated at search time
Passenger Configuration
Section titled “Passenger Configuration”Grid::make(4) ->schema([ TextInput::make('adults') ->numeric() ->default(1) ->minValue(1) ->maxValue(9),
TextInput::make('children') ->label('Children (2-11)') ->default(0),
TextInput::make('infants') ->label('Infants (0-1)') ->default(0),
Select::make('cabin_class') ->options([ 'ECONOMY' => 'Economy', 'ECONOMY_PREMIUM' => 'Premium Economy', 'BUSINESS' => 'Business', 'FIRST' => 'First', ]), ])Validation:
- Total passengers (adults + children + infants) ≤ 9
- At least 1 adult required
Fare Options
Section titled “Fare Options”CheckboxList::make('fare_sources') ->options([ 'FSC_IATA' => 'IATA Published Fares', 'FSC_NEGO' => 'Negotiated Fares', 'FSC_CONSO' => 'Consolidator Fares', 'FSC_WEB' => 'Direct Channel & Low-Cost Fares', ]) ->default(['FSC_IATA'])Airline Filtering
Section titled “Airline Filtering”TagsInput::make('included_airlines') ->label('Include Airlines') ->placeholder('Enter airline codes (e.g., LH, BA, AA)') ->separator(',')
TagsInput::make('excluded_airlines') ->label('Exclude Airlines') ->separator(',')Validation:
- IATA codes: 2-3 alphanumeric characters
- Cannot have overlapping included/excluded airlines
Results Display
Section titled “Results Display”Component-Based Architecture
Section titled “Component-Based Architecture”Split::make([ // Left side: Price and fare information Stack::make([ TextColumn::make('formattedPrice') ->weight(FontWeight::Bold) ->color('success'), TextColumn::make('fareType'), TextColumn::make('cabinClass') ->icon('heroicon-m-ticket'), ])->space(1),
// Right side: Badges Split::make([ TextColumn::make('numberOfStops') ->badge() ->color(fn (int $state): string => match ($state) { 0 => 'success', 1 => 'warning', default => 'danger', }), TextColumn::make('formattedDuration') ->badge() ->icon('heroicon-m-clock'), ])->from('sm'),])->from('md')Interactive Leg Selection
Section titled “Interactive Leg Selection”Users can select different flight options for each direction:
Panel::make([ Stack::make(fn ($record) => $this->buildFlightLegComponents($record)) ->space(3),])->collapsible()->collapsed(true)Leg Structure (Round-Trip):
Panel (Collapsible)├── Outbound Header├── Leg Option 1 (Split: Radio + Details) [Clickable]├── Leg Option 2 (Split: Radio + Details) [Clickable]├── Return Header├── Leg Option 1 (Split: Radio + Details) [Clickable]└── Leg Option 2 (Split: Radio + Details) [Clickable]Leg Structure (Multi-City):
Panel (Collapsible)├── Leg 1 Header├── Leg Option 1 [Clickable]├── Leg 2 Header├── Leg Option 1 [Clickable]└── ... (up to 6 legs)Livewire Interactivity
Section titled “Livewire Interactivity”public function selectLeg(string $fareId, string $direction, int $legIndex): void{ if (!isset($this->selectedLegs[$fareId])) { $this->selectedLegs[$fareId] = [ 'Outbound' => 0, 'Return' => 0, ]; }
$this->selectedLegs[$fareId][$direction] = $legIndex;}Actions
Section titled “Actions”View Ancillaries
Section titled “View Ancillaries”ViewAncillariesAction::make() ->rawFareData($this->rawFareData) ->selectedLegs($this->selectedLegs)Features:
- Opens modal with available ancillary services
- Component-level caching prevents duplicate API requests
- Displays services grouped by type (BAGGAGE, MEAL, SEAT)
View Fare Details
Section titled “View Fare Details”ViewFareDetailsAction::make() ->rawFareData($this->rawFareData)Features:
- Complete fare breakdown
- Passenger pricing
- Baggage allowance
- Booking action
Upsell Action
Section titled “Upsell Action”UpsellAction::make() ->rawFareData($this->rawFareData)Features:
- Searches for upgraded fare options
- Uses user’s selected leg choices
- Replaces search results with upsell results
Responsive Design
Section titled “Responsive Design”Breakpoints
Section titled “Breakpoints”sm- 640pxmd- 768pxlg- 1024px
Usage:
Split::make([/* ... */])->from('md') // Horizontal on medium+Stack::make([/* ... */]) // Always verticalMobile Layout
Section titled “Mobile Layout”- Full-width fields on mobile
- Grid collapses to single column
- Collapsible panels for details
- Touch-friendly input sizes
Performance
Section titled “Performance”Airport Search
Section titled “Airport Search”- Limit to 10 results for dropdowns
- Uses GIN index for sub-millisecond queries
- See Airport Search documentation
Ancillary Caching
Section titled “Ancillary Caching”- Component-level cache prevents duplicate API requests
- Cache key:
fareId|itinerary1:itinerary2 - Automatic invalidation on search state changes
Memory Management
Section titled “Memory Management”public function clearResults(): void{ $this->searchResults = collect(); $this->rawFareData = []; $this->selectedLegs = []; $this->fareAncillaryCache = []; $this->hasSearched = false;}Testing
Section titled “Testing”Manual Testing
Section titled “Manual Testing”# Access flight search page# Test scenarios:1. One-way search: BCN → MAD, 1 adult, Economy2. Round-trip search: LHR → JFK, 2 adults + 1 child, Business3. Multi-city search: BCN → BKK → CNX → BCN, 2 adults4. Multi-leg selection: Expand panel, click different options5. Fare details: Click "View Details" action6. Validation: Try >9 passengersComponent Testing
Section titled “Component Testing”use function Pest\Livewire\livewire;
test('flight search form renders correctly', function () { livewire(SearchFlights::class) ->assertFormExists() ->assertFormFieldExists('departure_airport') ->assertFormFieldExists('arrival_airport') ->assertFormFieldExists('departure_date') ->assertFormFieldExists('adults');});
test('leg selection updates state', function () { $component = livewire(SearchFlights::class); $component->selectLeg('fare-123', 'Outbound', 1);
expect($component->selectedLegs['fare-123']['Outbound'])->toBe(1);});Troubleshooting
Section titled “Troubleshooting”No Search Results
Section titled “No Search Results”- Check API credentials configured
- Verify AerTicket service accessible
- Validate airport codes
- Check date range (max 11 months)
php artisan aerticket:test-connectionphp artisan pail --filter="Aerticket"Leg Selection Not Working
Section titled “Leg Selection Not Working”- Check Livewire scripts loaded
- Check browser console for errors
- Verify
wire:clickattribute present
Related Documentation
Section titled “Related Documentation”- Airport Search - Full-text search implementation
- AerTicket Integration - Flight search API service