mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 03:51:10 -05:00
186 lines
11 KiB
PHP
186 lines
11 KiB
PHP
<div class="container mx-auto px-4 py-8">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<div class="flex items-center space-x-4">
|
|
<h1 class="text-2xl font-bold text-gray-900">Parks</h1>
|
|
|
|
<div class="flex items-center space-x-2 bg-gray-100 rounded-lg p-1" role="group" aria-label="View mode selection">
|
|
<button wire:click="$set('viewMode', 'grid')"
|
|
class="p-2 rounded transition-colors duration-200 {{ $viewMode == 'grid' ? 'bg-white shadow-sm' : '' }}"
|
|
aria-label="Grid view"
|
|
aria-pressed="{{ $viewMode == 'grid' ? 'true' : 'false' }}">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"/>
|
|
</svg>
|
|
</button>
|
|
<button wire:click="$set('viewMode', 'list')"
|
|
class="p-2 rounded transition-colors duration-200 {{ $viewMode == 'list' ? 'bg-white shadow-sm' : '' }}"
|
|
aria-label="List view"
|
|
aria-pressed="{{ $viewMode == 'list' ? 'true' : 'false' }}">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h7"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<a href="{{ route('parks.create') }}"
|
|
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
data-testid="add-park-button">
|
|
<svg class="-ml-1 mr-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
</svg>
|
|
Add Park
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Search and Filters -->
|
|
<div class="mb-6">
|
|
<div class="max-w-3xl mx-auto relative mb-8">
|
|
<label for="search" class="sr-only">Search parks</label>
|
|
<input type="search"
|
|
wire:model.live.debounce.300ms="search"
|
|
id="search"
|
|
class="block w-full rounded-md border-gray-300 bg-white py-3 pl-4 pr-10 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 sm:text-sm"
|
|
placeholder="Search parks by name or location..."
|
|
aria-label="Search parks">
|
|
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
|
|
<div wire:loading wire:target="search">
|
|
<svg class="h-5 w-5 text-gray-400 animate-spin" viewBox="0 0 24 24" aria-hidden="true">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"/>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white shadow sm:rounded-lg">
|
|
<div class="px-4 py-5 sm:p-6">
|
|
<h3 class="text-lg font-medium leading-6 text-gray-900">Filters</h3>
|
|
<div class="mt-4 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
<!-- Status Filter -->
|
|
<div>
|
|
<label for="status" class="block text-sm font-medium text-gray-700">Status</label>
|
|
<div class="mt-1">
|
|
<select wire:model.live="status" id="status"
|
|
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
|
@foreach($statusOptions as $value => $label)
|
|
<option value="{{ $value }}">{{ $label }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Operator Filter -->
|
|
<div>
|
|
<label for="operator" class="block text-sm font-medium text-gray-700">Operator</label>
|
|
<div class="mt-1">
|
|
<select wire:model.live="operator" id="operator"
|
|
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
|
@foreach($operatorOptions as $id => $name)
|
|
<option value="{{ $id }}">{{ $name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sort -->
|
|
<div>
|
|
<label for="sort" class="block text-sm font-medium text-gray-700">Sort By</label>
|
|
<div class="mt-1">
|
|
<select wire:model.live="sort" id="sort"
|
|
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
|
@foreach($sortOptions as $value => $label)
|
|
<option value="{{ $value }}">{{ $label }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Parks Grid/List -->
|
|
<div id="park-results"
|
|
class="bg-white rounded-lg shadow"
|
|
data-view-mode="{{ $viewMode }}">
|
|
<div class="{{ $viewMode == 'grid' ? 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 p-4' : 'flex flex-col gap-4 p-4' }}"
|
|
data-testid="park-list">
|
|
@forelse($parks as $park)
|
|
<article class="park-card group relative bg-white border rounded-lg transition-all duration-200 ease-in-out hover:shadow-lg {{ $viewMode == 'list' ? 'flex gap-4 p-4' : '' }}"
|
|
data-testid="park-card"
|
|
data-park-id="{{ $park->id }}"
|
|
data-view-mode="{{ $viewMode }}">
|
|
|
|
<a href="{{ route('parks.show', $park) }}"
|
|
class="absolute inset-0 z-0"
|
|
aria-label="View details for {{ $park->name }}"></a>
|
|
|
|
<div class="relative z-10 {{ $viewMode == 'grid' ? 'aspect-video' : '' }}">
|
|
<div class="{{ $viewMode == 'grid' ? 'w-full h-full bg-gray-100 rounded-t-lg flex items-center justify-center' : 'w-24 h-24 bg-gray-100 rounded-lg flex-shrink-0 flex items-center justify-center' }}"
|
|
role="img"
|
|
aria-label="Park initial letter">
|
|
<span class="text-2xl font-medium text-gray-400">{{ substr($park->name, 0, 1) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="{{ $viewMode == 'grid' ? 'p-4' : 'flex-1 min-w-0' }}">
|
|
<h3 class="text-lg font-semibold text-gray-900 truncate group-hover:text-blue-600">
|
|
{{ $park->name }}
|
|
</h3>
|
|
|
|
<div class="mt-1 text-sm text-gray-500 truncate">
|
|
@if($park->location)
|
|
{{ $park->location->city }}{{ $park->location->state ? ', ' . $park->location->state : '' }}{{ $park->location->country ? ', ' . $park->location->country : '' }}
|
|
@else
|
|
Location unknown
|
|
@endif
|
|
</div>
|
|
|
|
<div class="mt-2 flex flex-wrap gap-2">
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $park->status_classes }}"
|
|
data-testid="park-status">
|
|
{{ $park->status->label() }}
|
|
</span>
|
|
|
|
@if($park->opening_date)
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800"
|
|
data-testid="park-opening-date">
|
|
Opened {{ date('Y', strtotime($park->opening_date)) }}
|
|
</span>
|
|
@endif
|
|
|
|
@if($park->ride_count)
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
|
|
data-testid="park-ride-count">
|
|
{{ $park->ride_count }} ride{{ $park->ride_count != 1 ? 's' : '' }}
|
|
</span>
|
|
@endif
|
|
|
|
@if($park->coaster_count)
|
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800"
|
|
data-testid="park-coaster-count">
|
|
{{ $park->coaster_count }} coaster{{ $park->coaster_count != 1 ? 's' : '' }}
|
|
</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</article>
|
|
@empty
|
|
<div class="{{ $viewMode == 'grid' ? 'col-span-full' : '' }} p-4 text-sm text-gray-500 text-center" data-testid="no-parks-found">
|
|
@if($search)
|
|
No parks found matching "{{ $search }}". Try adjusting your search terms.
|
|
@else
|
|
No parks found matching your criteria. Try adjusting your filters.
|
|
@endif
|
|
<a href="{{ route('parks.create') }}" class="text-blue-600 hover:underline">Add a new park</a>.
|
|
</div>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<div class="mt-6">
|
|
{{ $parks->links() }}
|
|
</div>
|
|
</div> |