mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 02:31:09 -05:00
- 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.
217 lines
14 KiB
PHP
217 lines
14 KiB
PHP
<div>
|
|
{{-- Universal Listing Component Integration --}}
|
|
<x-universal-listing
|
|
:entity-type="$entityType"
|
|
:items="$parks"
|
|
:search="$search"
|
|
:sort-by="$sortBy"
|
|
:sort-direction="$sortDirection"
|
|
:show-filters="$showFilters"
|
|
:active-filters="$activeFilters"
|
|
:location-enabled="$locationEnabled"
|
|
:location-loading="$locationLoading"
|
|
:user-location="$userLocation"
|
|
wire:model.live="search"
|
|
>
|
|
{{-- Custom Location Controls Slot --}}
|
|
<x-slot name="locationControls">
|
|
@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
|
|
</x-slot>
|
|
|
|
{{-- Custom Filters Slot for Parks-Specific Filters --}}
|
|
<x-slot name="customFilters">
|
|
<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>
|
|
</x-slot>
|
|
|
|
{{-- Custom Sort Options Slot --}}
|
|
<x-slot name="customSortOptions">
|
|
@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>
|
|
</x-slot>
|
|
|
|
{{-- Custom Card Content for Parks --}}
|
|
<x-slot name="customCardContent" :item="null">
|
|
@foreach($parks as $park)
|
|
<x-universal-listing-card
|
|
:item="$park"
|
|
:entity-type="$entityType"
|
|
:location-enabled="$locationEnabled"
|
|
:user-location="$userLocation"
|
|
>
|
|
{{-- Custom Park-Specific Content --}}
|
|
<x-slot name="customContent">
|
|
@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
|
|
</x-slot>
|
|
</x-universal-listing-card>
|
|
@endforeach
|
|
</x-slot>
|
|
</x-universal-listing>
|
|
|
|
{{-- JavaScript for Location Services (GPS Integration) --}}
|
|
<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>
|
|
</div> |