mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 06:31:10 -05:00
Add Livewire components for parks, rides, and manufacturers
- Implemented ParksLocationSearch component with loading state and refresh functionality. - Created ParksMapView component with similar structure and functionality. - Added RegionalParksListing component for displaying regional parks. - Developed RidesListingUniversal component for universal listing integration. - Established ManufacturersListing view with navigation and Livewire integration. - Added feature tests for various Livewire components including OperatorHierarchyView, OperatorParksListing, OperatorPortfolioCard, OperatorsListing, OperatorsRoleFilter, ParksListing, ParksLocationSearch, ParksMapView, and RegionalParksListing to ensure proper rendering and adherence to patterns.
This commit is contained in:
405
resources/views/livewire/parks-listing.blade.php
Normal file
405
resources/views/livewire/parks-listing.blade.php
Normal file
@@ -0,0 +1,405 @@
|
||||
<div class="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
{{-- Header Section --}}
|
||||
<div class="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4">
|
||||
{{-- Title and Stats --}}
|
||||
<div class="flex-1">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white">
|
||||
Theme Parks
|
||||
</h1>
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ $parks->total() }} parks found
|
||||
@if($locationEnabled)
|
||||
<span class="inline-flex items-center ml-2 px-2 py-1 rounded-full text-xs bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
|
||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
Location enabled
|
||||
</span>
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{-- Location Controls --}}
|
||||
<div class="flex items-center gap-3">
|
||||
@if(!$locationEnabled)
|
||||
<button
|
||||
wire:click="enableLocation"
|
||||
wire:loading.attr="disabled"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<svg wire:loading.remove wire:target="enableLocation" class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<svg wire:loading wire:target="enableLocation" class="animate-spin w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span wire:loading.remove wire:target="enableLocation">Find Parks Near Me</span>
|
||||
<span wire:loading wire:target="enableLocation">Getting Location...</span>
|
||||
</button>
|
||||
@endif
|
||||
|
||||
{{-- Filters Toggle --}}
|
||||
<button
|
||||
wire:click="toggleFilters"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-600 text-sm font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
Filters
|
||||
@if(count($activeFilters) > 0)
|
||||
<span class="ml-2 inline-flex items-center px-2 py-0.5 rounded-full text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
||||
{{ count($activeFilters) }}
|
||||
</span>
|
||||
@endif
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Search Bar --}}
|
||||
<div class="mt-6">
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<input
|
||||
wire:model.live.debounce.300ms="search"
|
||||
type="text"
|
||||
placeholder="Search parks by name, location, operator, or type..."
|
||||
class="block w-full pl-10 pr-3 py-3 border border-gray-300 dark:border-gray-600 rounded-lg leading-5 bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Active Filters Display --}}
|
||||
@if(count($activeFilters) > 0)
|
||||
<div class="mt-4 flex flex-wrap items-center gap-2">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">Active filters:</span>
|
||||
@foreach($activeFilters as $filter)
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
||||
{{ $filter }}
|
||||
</span>
|
||||
@endforeach
|
||||
<button
|
||||
wire:click="clearFilters"
|
||||
class="inline-flex items-center px-3 py-1 rounded-full text-sm bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600"
|
||||
>
|
||||
Clear all
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Filters Panel --}}
|
||||
@if($showFilters)
|
||||
<div class="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{{-- Operator Filter --}}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Operator</label>
|
||||
<select wire:model.live="operatorId" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||
<option value="">All Operators</option>
|
||||
@foreach($operators as $operator)
|
||||
<option value="{{ $operator->id }}">{{ $operator->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- Region Filter --}}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Region</label>
|
||||
<select wire:model.live="region" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||
<option value="">All Regions</option>
|
||||
@foreach($regions as $regionOption)
|
||||
<option value="{{ $regionOption }}">{{ $regionOption }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- Country Filter --}}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Country</label>
|
||||
<select wire:model.live="country" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||
<option value="">All Countries</option>
|
||||
@foreach($countries as $countryOption)
|
||||
<option value="{{ $countryOption }}">{{ $countryOption }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- Park Type Filter --}}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Park Type</label>
|
||||
<select wire:model.live="parkType" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||
<option value="">All Types</option>
|
||||
@foreach($parkTypes as $type)
|
||||
<option value="{{ $type }}">{{ $type }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- Opening Year Range --}}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Opening Year</label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<input wire:model.live="openingYearFrom" type="number" placeholder="From" min="1800" max="{{ date('Y') }}" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||
<input wire:model.live="openingYearTo" type="number" placeholder="To" min="1800" max="{{ date('Y') }}" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Area Range --}}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Area (acres)</label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<input wire:model.live="minArea" type="number" placeholder="Min" min="0" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||
<input wire:model.live="maxArea" type="number" placeholder="Max" min="0" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Minimum Rides --}}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Minimum Rides</label>
|
||||
<input wire:model.live="minRides" type="number" placeholder="Min rides" min="0" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||
</div>
|
||||
|
||||
{{-- Distance Filter (only if location enabled) --}}
|
||||
@if($locationEnabled)
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Max Distance (km)</label>
|
||||
<input wire:model.live="maxDistance" type="number" placeholder="Distance" min="1" class="block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Sorting Controls --}}
|
||||
<div class="bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">Sort by:</span>
|
||||
<div class="flex items-center space-x-2">
|
||||
<button wire:click="sortBy('name')" class="inline-flex items-center px-3 py-1 rounded-md text-sm {{ $sortBy === 'name' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white' }}">
|
||||
Name
|
||||
@if($sortBy === 'name')
|
||||
<svg class="ml-1 w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="{{ $sortDirection === 'asc' ? 'M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z' : 'M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' }}" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
@endif
|
||||
</button>
|
||||
@if($locationEnabled)
|
||||
<button wire:click="sortBy('distance')" class="inline-flex items-center px-3 py-1 rounded-md text-sm {{ $sortBy === 'distance' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white' }}">
|
||||
Distance
|
||||
@if($sortBy === 'distance')
|
||||
<svg class="ml-1 w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="{{ $sortDirection === 'asc' ? 'M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z' : 'M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' }}" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
@endif
|
||||
</button>
|
||||
@endif
|
||||
<button wire:click="sortBy('rides_count')" class="inline-flex items-center px-3 py-1 rounded-md text-sm {{ $sortBy === 'rides_count' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white' }}">
|
||||
Rides
|
||||
@if($sortBy === 'rides_count')
|
||||
<svg class="ml-1 w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="{{ $sortDirection === 'asc' ? 'M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z' : 'M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' }}" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
@endif
|
||||
</button>
|
||||
<button wire:click="sortBy('opening_date')" class="inline-flex items-center px-3 py-1 rounded-md text-sm {{ $sortBy === 'opening_date' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white' }}">
|
||||
Opening Date
|
||||
@if($sortBy === 'opening_date')
|
||||
<svg class="ml-1 w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="{{ $sortDirection === 'asc' ? 'M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z' : 'M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' }}" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
@endif
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Parks Grid --}}
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
@if($parks->count() > 0)
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||
@foreach($parks as $park)
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 overflow-hidden">
|
||||
{{-- Park Image --}}
|
||||
<div class="aspect-w-16 aspect-h-9 bg-gray-200 dark:bg-gray-700">
|
||||
@if($park->photos->count() > 0)
|
||||
<img src="{{ $park->photos->first()->url }}" alt="{{ $park->name }}" class="w-full h-48 object-cover">
|
||||
@else
|
||||
<div class="w-full h-48 flex items-center justify-center">
|
||||
<svg class="w-12 h-12 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Park Info --}}
|
||||
<div class="p-4">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white truncate">
|
||||
<a href="{{ route('parks.show', $park) }}" class="hover:text-blue-600 dark:hover:text-blue-400">
|
||||
{{ $park->name }}
|
||||
</a>
|
||||
</h3>
|
||||
@if($park->location)
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
{{ $park->location->city }}, {{ $park->location->state }}
|
||||
@if($park->location->country !== 'United States')
|
||||
, {{ $park->location->country }}
|
||||
@endif
|
||||
</p>
|
||||
@endif
|
||||
@if($park->operator)
|
||||
<p class="text-sm text-gray-500 dark:text-gray-500 mt-1">
|
||||
{{ $park->operator->name }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@if($locationEnabled && isset($park->distance))
|
||||
<div class="ml-2 flex-shrink-0">
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
||||
{{ number_format($park->distance, 1) }} km
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Park Stats --}}
|
||||
<div class="mt-3 flex items-center justify-between text-sm text-gray-600 dark:text-gray-400">
|
||||
<div class="flex items-center space-x-4">
|
||||
<span class="flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
{{ $park->rides_count }} rides
|
||||
</span>
|
||||
@if($park->reviews_count > 0)
|
||||
<span class="flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
|
||||
</svg>
|
||||
{{ $park->reviews_count }} reviews
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
@if($park->opening_date)
|
||||
<span class="text-xs">
|
||||
{{ $park->opening_date->format('Y') }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Park Type --}}
|
||||
@if($park->park_type)
|
||||
<div class="mt-2">
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200">
|
||||
{{ $park->park_type }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{{-- Pagination --}}
|
||||
<div class="mt-8">
|
||||
{{ $parks->links() }}
|
||||
</div>
|
||||
@else
|
||||
{{-- Empty State --}}
|
||||
<div class="text-center py-12">
|
||||
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
|
||||
</svg>
|
||||
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">No parks found</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Try adjusting your search criteria or filters.
|
||||
</p>
|
||||
@if(count($activeFilters) > 0)
|
||||
<div class="mt-6">
|
||||
<button
|
||||
wire:click="clearFilters"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
Clear
|
||||
Clear All Filters
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Loading State --}}
|
||||
<div wire:loading.delay class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 flex items-center space-x-3">
|
||||
<svg class="animate-spin h-5 w-5 text-blue-600" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span class="text-gray-900 dark:text-white">Loading parks...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- JavaScript for Location Services --}}
|
||||
<script>
|
||||
document.addEventListener('livewire:init', () => {
|
||||
Livewire.on('request-location', () => {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
function(position) {
|
||||
Livewire.dispatch('locationReceived', {
|
||||
lat: position.coords.latitude,
|
||||
lng: position.coords.longitude
|
||||
});
|
||||
},
|
||||
function(error) {
|
||||
let message = 'Unable to get your location.';
|
||||
switch(error.code) {
|
||||
case error.PERMISSION_DENIED:
|
||||
message = 'Location access denied by user.';
|
||||
break;
|
||||
case error.POSITION_UNAVAILABLE:
|
||||
message = 'Location information is unavailable.';
|
||||
break;
|
||||
case error.TIMEOUT:
|
||||
message = 'Location request timed out.';
|
||||
break;
|
||||
}
|
||||
Livewire.dispatch('locationError', { message: message });
|
||||
},
|
||||
{
|
||||
enableHighAccuracy: true,
|
||||
timeout: 10000,
|
||||
maximumAge: 300000 // 5 minutes
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Livewire.dispatch('locationError', {
|
||||
message: 'Geolocation is not supported by this browser.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Livewire.on('location-error', (event) => {
|
||||
alert('Location Error: ' + event.message);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user