mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 11:51:11 -05:00
Add models, enums, and services for user roles, theme preferences, slug history, and ID generation
This commit is contained in:
108
resources/views/livewire/area-statistics-component.blade.php
Normal file
108
resources/views/livewire/area-statistics-component.blade.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl overflow-hidden">
|
||||
<!-- Basic Statistics -->
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">Area Statistics</h3>
|
||||
<div class="flex space-x-2">
|
||||
<button wire:click="toggleDetails" class="text-sm text-indigo-600 hover:text-indigo-900">
|
||||
{{ $showDetails ? 'Hide Details' : 'Show Details' }}
|
||||
</button>
|
||||
<button wire:click="toggleHistorical" class="text-sm text-indigo-600 hover:text-indigo-900">
|
||||
{{ $showHistorical ? 'Hide Historical' : 'Show Historical' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ride Counts -->
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4 mb-6">
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-500">Total Rides</span>
|
||||
<p class="mt-1 text-2xl font-semibold text-gray-900">{{ $area->ride_count }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-500">Coasters</span>
|
||||
<p class="mt-1 text-2xl font-semibold text-gray-900">{{ $area->coaster_count }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-500">Rating</span>
|
||||
<p class="mt-1 text-2xl font-semibold text-gray-900">{{ $area->rating_display }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-500">Daily Capacity</span>
|
||||
<p class="mt-1 text-2xl font-semibold text-gray-900">{{ $area->formatted_daily_capacity }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detailed Statistics -->
|
||||
@if($showDetails)
|
||||
<div class="border-t border-gray-200 pt-6">
|
||||
<h4 class="text-sm font-medium text-gray-900 mb-4">Ride Distribution</h4>
|
||||
|
||||
<!-- Distribution Chart -->
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center">
|
||||
<span class="text-sm font-medium text-gray-500 w-24">Coasters</span>
|
||||
<div class="flex-1 h-4 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-indigo-600 rounded-full" style="width: {{ $ridePercentages['coasters'] }}%"></div>
|
||||
</div>
|
||||
<span class="ml-2 text-sm text-gray-500">{{ $ridePercentages['coasters'] }}%</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="text-sm font-medium text-gray-500 w-24">Flat Rides</span>
|
||||
<div class="flex-1 h-4 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-blue-600 rounded-full" style="width: {{ $ridePercentages['flat_rides'] }}%"></div>
|
||||
</div>
|
||||
<span class="ml-2 text-sm text-gray-500">{{ $ridePercentages['flat_rides'] }}%</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="text-sm font-medium text-gray-500 w-24">Water Rides</span>
|
||||
<div class="flex-1 h-4 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-cyan-600 rounded-full" style="width: {{ $ridePercentages['water_rides'] }}%"></div>
|
||||
</div>
|
||||
<span class="ml-2 text-sm text-gray-500">{{ $ridePercentages['water_rides'] }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Details -->
|
||||
<div class="mt-6 grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-500">Peak Wait Time</span>
|
||||
<p class="mt-1 text-lg font-medium text-gray-900">{{ $area->formatted_peak_wait_time }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-500">Operating Status</span>
|
||||
<p class="mt-1 text-lg font-medium {{ $area->isOperating() ? 'text-green-600' : 'text-red-600' }}">
|
||||
{{ $area->isOperating() ? 'Operating' : 'Closed' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Historical Statistics -->
|
||||
@if($showHistorical)
|
||||
<div class="border-t border-gray-200 pt-6">
|
||||
<h4 class="text-sm font-medium text-gray-900 mb-4">Historical Data</h4>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-500">Total Rides Operated</span>
|
||||
<p class="mt-1 text-lg font-medium text-gray-900">{{ $historicalStats['total_operated'] }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-500">Retired Rides</span>
|
||||
<p class="mt-1 text-lg font-medium text-gray-900">{{ $historicalStats['retired_count'] }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-500">Last New Ride</span>
|
||||
<p class="mt-1 text-lg font-medium text-gray-900">{{ $historicalStats['last_addition'] }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm font-medium text-gray-500">Retirement Rate</span>
|
||||
<p class="mt-1 text-lg font-medium text-gray-900">{{ $historicalStats['retirement_rate'] }}%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
82
resources/views/livewire/park-area-form-component.blade.php
Normal file
82
resources/views/livewire/park-area-form-component.blade.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
@if (session()->has('message'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative" role="alert">
|
||||
{{ session('message') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl p-6">
|
||||
<!-- Basic Information -->
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-lg font-medium text-gray-900">Area Information</h3>
|
||||
<span class="text-sm text-gray-500">Part of {{ $park->name }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700">Area Name</label>
|
||||
<div class="mt-1">
|
||||
<input type="text" wire:model="name" id="name"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="Enter area name">
|
||||
</div>
|
||||
@error('name') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
|
||||
<div class="mt-1">
|
||||
<textarea wire:model="description" id="description" rows="4"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="Describe this area"></textarea>
|
||||
</div>
|
||||
@error('description') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl p-6">
|
||||
<!-- Dates -->
|
||||
<div class="space-y-6">
|
||||
<h3 class="text-lg font-medium text-gray-900">Opening and Closing Dates</h3>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<label for="opening_date" class="block text-sm font-medium text-gray-700">Opening Date</label>
|
||||
<div class="mt-1">
|
||||
<input type="date" wire:model="opening_date" id="opening_date"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
||||
</div>
|
||||
@error('opening_date') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="closing_date" class="block text-sm font-medium text-gray-700">Closing Date</label>
|
||||
<div class="mt-1">
|
||||
<input type="date" wire:model="closing_date" id="closing_date"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
||||
</div>
|
||||
@error('closing_date') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-500">
|
||||
<p>Leave closing date empty if the area is still operating.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-4">
|
||||
<a href="{{ route('parks.show', $park) }}"
|
||||
class="inline-flex justify-center rounded-md border border-gray-300 bg-white py-2 px-4 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
||||
Cancel
|
||||
</a>
|
||||
|
||||
<button type="submit"
|
||||
class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
||||
{{ $isEditing ? 'Update Area' : 'Create Area' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
109
resources/views/livewire/park-area-list-component.blade.php
Normal file
109
resources/views/livewire/park-area-list-component.blade.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h2 class="text-xl font-semibold text-gray-900">Areas in {{ $park->name }}</h2>
|
||||
<a href="{{ route('parks.areas.create', $park) }}"
|
||||
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">
|
||||
<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 Area
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Filters and Search -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl p-4 mb-6">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<!-- Search -->
|
||||
<div>
|
||||
<label for="search" class="block text-sm font-medium text-gray-700">Search</label>
|
||||
<div class="mt-1">
|
||||
<input type="text" wire:model.live.debounce.300ms="search" id="search"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="Search areas...">
|
||||
</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>
|
||||
|
||||
<!-- Show Closed Toggle -->
|
||||
<div class="flex items-center mt-6">
|
||||
<label for="showClosed" class="inline-flex relative items-center cursor-pointer">
|
||||
<input type="checkbox" wire:model.live="showClosed" id="showClosed" class="sr-only peer">
|
||||
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-indigo-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600"></div>
|
||||
<span class="ml-3 text-sm font-medium text-gray-700">Show Closed Areas</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Areas List -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl overflow-hidden">
|
||||
@if($areas->isEmpty())
|
||||
<div class="text-center py-12">
|
||||
<h3 class="text-lg font-medium text-gray-900">No areas found</h3>
|
||||
<p class="mt-2 text-sm text-gray-500">Try adjusting your search or add a new area.</p>
|
||||
</div>
|
||||
@else
|
||||
<ul role="list" class="divide-y divide-gray-200">
|
||||
@foreach($areas as $area)
|
||||
<li class="p-4 sm:p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-medium text-gray-900 truncate">
|
||||
<a href="{{ route('parks.areas.show', ['park' => $park, 'area' => $area]) }}" class="hover:text-indigo-600">
|
||||
{{ $area->name }}
|
||||
</a>
|
||||
</h3>
|
||||
@if($area->description)
|
||||
<p class="mt-1 text-sm text-gray-500">{{ $area->brief_description }}</p>
|
||||
@endif
|
||||
<div class="mt-2 flex items-center text-sm text-gray-500 space-x-4">
|
||||
@if($area->opening_date)
|
||||
<div>
|
||||
<svg class="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400" 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="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
Opened: {{ $area->opening_date->format('M Y') }}
|
||||
</div>
|
||||
@endif
|
||||
@if($area->closing_date)
|
||||
<div class="text-red-600">
|
||||
Closed: {{ $area->closing_date->format('M Y') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-shrink-0 flex items-center space-x-3">
|
||||
<a href="{{ route('parks.areas.edit', ['park' => $park, 'area' => $area]) }}"
|
||||
class="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
Edit
|
||||
</a>
|
||||
<button wire:click="deleteArea({{ $area->id }})" wire:confirm="Are you sure you want to delete this area?"
|
||||
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6">
|
||||
{{ $areas->links() }}
|
||||
</div>
|
||||
</div>
|
||||
127
resources/views/livewire/park-area-reorder-component.blade.php
Normal file
127
resources/views/livewire/park-area-reorder-component.blade.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-900">
|
||||
@if($parentArea)
|
||||
Areas in {{ $parentArea->name }}
|
||||
<a href="{{ route('parks.areas.reorder', ['park' => $park]) }}" class="text-sm text-indigo-600 hover:text-indigo-900">
|
||||
(Back to Top Level)
|
||||
</a>
|
||||
@else
|
||||
Areas in {{ $park->name }}
|
||||
@endif
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-gray-500">Drag and drop to reorder areas or move them between levels.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Areas List -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl overflow-hidden">
|
||||
@if(empty($areas))
|
||||
<div class="text-center py-12">
|
||||
<h3 class="text-lg font-medium text-gray-900">No areas found</h3>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
@if($parentArea)
|
||||
This area doesn't have any sub-areas yet.
|
||||
@else
|
||||
This park doesn't have any areas yet.
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
@else
|
||||
<ul id="sortable-areas" class="divide-y divide-gray-200" wire:sortable wire:end.stop="reorder($event.target.sortable.toArray())">
|
||||
@foreach($areas as $area)
|
||||
<li wire:key="area-{{ $area['id'] }}" wire:sortable.item="{{ $area['id'] }}" data-id="{{ $area['id'] }}"
|
||||
class="group p-4 sm:p-6 hover:bg-gray-50 cursor-move">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-3">
|
||||
<!-- Drag Handle -->
|
||||
<div wire:sortable.handle class="cursor-grab">
|
||||
<svg class="h-5 w-5 text-gray-400 group-hover:text-gray-500" 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="M4 6h16M4 12h16m-7 6h7" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Area Name -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<h3 class="text-lg font-medium text-gray-900 truncate">
|
||||
{{ $area['name'] }}
|
||||
@if($area['is_closed'])
|
||||
<span class="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">
|
||||
Closed
|
||||
</span>
|
||||
@endif
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-3">
|
||||
<!-- Sub-Areas Link -->
|
||||
@if($area['has_children'])
|
||||
<a href="{{ route('parks.areas.reorder', ['park' => $park, 'parentId' => $area['id']]) }}"
|
||||
class="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
<svg class="-ml-0.5 mr-2 h-4 w-4" 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="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
Sub-Areas
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<!-- Move Actions -->
|
||||
<div class="relative" x-data="{ open: false }">
|
||||
<button @click="open = !open" type="button"
|
||||
class="inline-flex items-center px-3 py-2 border border-gray-300 shadow-sm text-sm leading-4 font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
Move To
|
||||
<svg class="ml-2 -mr-0.5 h-4 w-4" 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="M8 9l4-4 4 4m0 6l-4 4-4-4" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div x-show="open" @click.away="open = false"
|
||||
class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-100 focus:outline-none"
|
||||
role="menu" aria-orientation="vertical" aria-labelledby="move-button-{{ $area['id'] }}">
|
||||
<div class="py-1" role="none">
|
||||
@if($parentArea)
|
||||
<button wire:click="moveToParent({{ $area['id'] }}, null)" class="text-gray-700 group flex items-center px-4 py-2 text-sm w-full hover:bg-gray-100" role="menuitem">
|
||||
Move to Top Level
|
||||
</button>
|
||||
@endif
|
||||
@foreach($areas as $targetArea)
|
||||
@if($targetArea['id'] !== $area['id'] && !$targetArea['is_closed'])
|
||||
<button wire:click="moveToParent({{ $area['id'] }}, {{ $targetArea['id'] }})" class="text-gray-700 group flex items-center px-4 py-2 text-sm w-full hover:bg-gray-100" role="menuitem">
|
||||
Move to {{ $targetArea['name'] }}
|
||||
</button>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('livewire:init', () => {
|
||||
let sortable = new Sortable(document.getElementById('sortable-areas'), {
|
||||
animation: 150,
|
||||
handle: '[wire\\:sortable\\.handle]',
|
||||
draggable: '[wire\\:sortable\\.item]',
|
||||
onEnd: function(evt) {
|
||||
const items = Array.from(evt.to.children).map(item => item.dataset.id);
|
||||
evt.to.dispatchEvent(new CustomEvent('end.stop', {
|
||||
detail: { items },
|
||||
bubbles: true,
|
||||
}));
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
143
resources/views/livewire/park-form-component.blade.php
Normal file
143
resources/views/livewire/park-form-component.blade.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<div class="max-w-4xl mx-auto p-4 sm:p-6 lg:p-8">
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
@if (session()->has('message'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative" role="alert">
|
||||
{{ session('message') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl p-6">
|
||||
<!-- Basic Information -->
|
||||
<div class="space-y-6">
|
||||
<h3 class="text-lg font-medium text-gray-900">Basic Information</h3>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700">Park Name</label>
|
||||
<div class="mt-1">
|
||||
<input type="text" wire:model="name" id="name"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="Enter park name">
|
||||
</div>
|
||||
@error('name') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="operator_id" class="block text-sm font-medium text-gray-700">Operator</label>
|
||||
<div class="mt-1">
|
||||
<select wire:model="operator_id" id="operator_id"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
||||
<option value="">Select an operator</option>
|
||||
@foreach($operators as $operator)
|
||||
<option value="{{ $operator['id'] }}">{{ $operator['name'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
@error('operator_id') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="description" class="block text-sm font-medium text-gray-700">Description</label>
|
||||
<div class="mt-1">
|
||||
<textarea wire:model="description" id="description" rows="4"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="Enter park description"></textarea>
|
||||
</div>
|
||||
@error('description') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl p-6">
|
||||
<!-- Status and Dates -->
|
||||
<div class="space-y-6">
|
||||
<h3 class="text-lg font-medium text-gray-900">Status and Dates</h3>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-3">
|
||||
<div>
|
||||
<label for="status" class="block text-sm font-medium text-gray-700">Status</label>
|
||||
<div class="mt-1">
|
||||
<select wire:model="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>
|
||||
@error('status') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="opening_date" class="block text-sm font-medium text-gray-700">Opening Date</label>
|
||||
<div class="mt-1">
|
||||
<input type="date" wire:model="opening_date" id="opening_date"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
||||
</div>
|
||||
@error('opening_date') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="closing_date" class="block text-sm font-medium text-gray-700">Closing Date</label>
|
||||
<div class="mt-1">
|
||||
<input type="date" wire:model="closing_date" id="closing_date"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm">
|
||||
</div>
|
||||
@error('closing_date') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl p-6">
|
||||
<!-- Additional Details -->
|
||||
<div class="space-y-6">
|
||||
<h3 class="text-lg font-medium text-gray-900">Additional Details</h3>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-3">
|
||||
<div>
|
||||
<label for="operating_season" class="block text-sm font-medium text-gray-700">Operating Season</label>
|
||||
<div class="mt-1">
|
||||
<input type="text" wire:model="operating_season" id="operating_season"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="e.g., March - November">
|
||||
</div>
|
||||
@error('operating_season') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="size_acres" class="block text-sm font-medium text-gray-700">Size (Acres)</label>
|
||||
<div class="mt-1">
|
||||
<input type="number" step="0.01" wire:model="size_acres" id="size_acres"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="e.g., 100.5">
|
||||
</div>
|
||||
@error('size_acres') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="website" class="block text-sm font-medium text-gray-700">Website</label>
|
||||
<div class="mt-1">
|
||||
<input type="url" wire:model="website" id="website"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="https://example.com">
|
||||
</div>
|
||||
@error('website') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-4">
|
||||
<a href="{{ route('parks.index') }}"
|
||||
class="inline-flex justify-center rounded-md border border-gray-300 bg-white py-2 px-4 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
||||
Cancel
|
||||
</a>
|
||||
|
||||
<button type="submit"
|
||||
class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
||||
{{ $park ? 'Update Park' : 'Create Park' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
136
resources/views/livewire/park-list-component.blade.php
Normal file
136
resources/views/livewire/park-list-component.blade.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<!-- Filters and Search -->
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl p-4 mb-6">
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<!-- Search -->
|
||||
<div>
|
||||
<label for="search" class="block text-sm font-medium text-gray-700">Search</label>
|
||||
<div class="mt-1">
|
||||
<input type="text" wire:model.live.debounce.300ms="search" id="search"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="Search parks...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- Parks Grid -->
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@forelse($parks as $park)
|
||||
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl overflow-hidden">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">
|
||||
<a href="{{ route('parks.show', $park) }}" class="hover:text-indigo-600">
|
||||
{{ $park->name }}
|
||||
</a>
|
||||
</h3>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $park->status_classes }}">
|
||||
{{ $park->status->label() }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="text-sm text-gray-500 mb-4">
|
||||
{{ $park->brief_description }}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span class="text-gray-500">Operator:</span>
|
||||
<span class="text-gray-900">{{ $park->operator?->name ?? 'Unknown' }}</span>
|
||||
</div>
|
||||
@if($park->opening_year)
|
||||
<div>
|
||||
<span class="text-gray-500">Opened:</span>
|
||||
<span class="text-gray-900">{{ $park->opening_year }}</span>
|
||||
</div>
|
||||
@endif
|
||||
@if($park->size_acres)
|
||||
<div>
|
||||
<span class="text-gray-500">Size:</span>
|
||||
<span class="text-gray-900">{{ $park->size_display }}</span>
|
||||
</div>
|
||||
@endif
|
||||
<div>
|
||||
<span class="text-gray-500">Rides:</span>
|
||||
<span class="text-gray-900">{{ $park->ride_count ?? 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex justify-end space-x-3">
|
||||
@if($park->website)
|
||||
<a href="{{ $park->website_url }}" target="_blank" rel="noopener"
|
||||
class="text-sm text-gray-500 hover:text-gray-900">
|
||||
Visit Website
|
||||
</a>
|
||||
@endif
|
||||
<a href="{{ route('parks.edit', $park) }}"
|
||||
class="text-sm text-indigo-600 hover:text-indigo-900">
|
||||
Edit
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="col-span-full text-center py-12">
|
||||
<h3 class="text-lg font-medium text-gray-900">No parks found</h3>
|
||||
<p class="mt-2 text-sm text-gray-500">Try adjusting your filters or search terms.</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6">
|
||||
{{ $parks->links() }}
|
||||
</div>
|
||||
|
||||
<!-- Create Button -->
|
||||
<div class="fixed bottom-6 right-6">
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
120
resources/views/livewire/profile-component.blade.php
Normal file
120
resources/views/livewire/profile-component.blade.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<div class="max-w-2xl mx-auto p-4 sm:p-6 lg:p-8">
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
@if (session()->has('message'))
|
||||
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative" role="alert">
|
||||
{{ session('message') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div>
|
||||
<div class="flex items-center space-x-6">
|
||||
<div class="shrink-0">
|
||||
<img class="h-16 w-16 object-cover rounded-full" src="{{ $profile->getAvatarUrl() }}" alt="Profile photo">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block">
|
||||
<span class="sr-only">Choose profile photo</span>
|
||||
<input type="file" wire:model="avatar" class="block w-full text-sm text-slate-500
|
||||
file:mr-4 file:py-2 file:px-4
|
||||
file:rounded-full file:border-0
|
||||
file:text-sm file:font-semibold
|
||||
file:bg-violet-50 file:text-violet-700
|
||||
hover:file:bg-violet-100
|
||||
"/>
|
||||
</label>
|
||||
@error('avatar') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
|
||||
@if($profile->avatar)
|
||||
<button type="button" wire:click="removeAvatar" class="mt-2 text-sm text-red-600 hover:text-red-900">
|
||||
Remove Photo
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="display_name" class="block text-sm font-medium text-gray-700">Display Name</label>
|
||||
<div class="mt-1">
|
||||
<input type="text" wire:model="display_name" id="display_name"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="How should we display your name?">
|
||||
</div>
|
||||
@error('display_name') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="pronouns" class="block text-sm font-medium text-gray-700">Pronouns</label>
|
||||
<div class="mt-1">
|
||||
<input type="text" wire:model="pronouns" id="pronouns"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="e.g. they/them">
|
||||
</div>
|
||||
@error('pronouns') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="bio" class="block text-sm font-medium text-gray-700">Bio</label>
|
||||
<div class="mt-1">
|
||||
<textarea wire:model="bio" id="bio" rows="3"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="Tell us about yourself"></textarea>
|
||||
</div>
|
||||
@error('bio') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-lg font-medium text-gray-900">Social Media</h3>
|
||||
|
||||
<div>
|
||||
<label for="twitter" class="block text-sm font-medium text-gray-700">Twitter URL</label>
|
||||
<div class="mt-1">
|
||||
<input type="url" wire:model="twitter" id="twitter"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="https://twitter.com/yourusername">
|
||||
</div>
|
||||
@error('twitter') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="instagram" class="block text-sm font-medium text-gray-700">Instagram URL</label>
|
||||
<div class="mt-1">
|
||||
<input type="url" wire:model="instagram" id="instagram"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="https://instagram.com/yourusername">
|
||||
</div>
|
||||
@error('instagram') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="youtube" class="block text-sm font-medium text-gray-700">YouTube URL</label>
|
||||
<div class="mt-1">
|
||||
<input type="url" wire:model="youtube" id="youtube"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="https://youtube.com/@yourchannel">
|
||||
</div>
|
||||
@error('youtube') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="discord" class="block text-sm font-medium text-gray-700">Discord Username</label>
|
||||
<div class="mt-1">
|
||||
<input type="text" wire:model="discord" id="discord"
|
||||
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="username#1234">
|
||||
</div>
|
||||
@error('discord') <span class="text-red-500 text-sm">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<button type="submit" class="inline-flex justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
||||
Save Profile
|
||||
</button>
|
||||
|
||||
<div wire:loading wire:target="save" class="text-sm text-gray-500">
|
||||
Saving...
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
Reference in New Issue
Block a user