Refactor Park model, update routes for parks management, and enhance database migrations for park areas and locations

This commit is contained in:
pacnpal
2025-02-25 14:17:13 -05:00
parent 45f9e45b9a
commit 15b2d4ebcf
12 changed files with 301 additions and 151 deletions

View File

@@ -6,7 +6,7 @@ use App\Models\Park;
use App\Enums\ParkStatus; use App\Enums\ParkStatus;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
class ParkListComponent extends Component class ParkListComponent extends Component
{ {
@@ -17,6 +17,7 @@ class ParkListComponent extends Component
public string $sort = 'name'; public string $sort = 'name';
public string $direction = 'asc'; public string $direction = 'asc';
public ?string $operator = null; public ?string $operator = null;
public string $viewMode = 'grid';
/** @var array<string, string> */ /** @var array<string, string> */
public array $sortOptions = [ public array $sortOptions = [
@@ -33,6 +34,7 @@ class ParkListComponent extends Component
'sort' => ['except' => 'name'], 'sort' => ['except' => 'name'],
'direction' => ['except' => 'asc'], 'direction' => ['except' => 'asc'],
'operator' => ['except' => ''], 'operator' => ['except' => ''],
'viewMode' => ['except' => 'grid'],
]; ];
public function mount(): void public function mount(): void
@@ -55,6 +57,11 @@ class ParkListComponent extends Component
$this->resetPage('parks-page'); $this->resetPage('parks-page');
} }
public function updatedViewMode(): void
{
// No need to reset page when changing view mode
}
public function sortBy(string $field): void public function sortBy(string $field): void
{ {
if ($this->sort === $field) { if ($this->sort === $field) {
@@ -84,10 +91,12 @@ class ParkListComponent extends Component
public function render() public function render()
{ {
$query = Park::query() $query = Park::query()
->with(['operator']) ->with(['operator', 'location'])
->when($this->search, function (Builder $query) { ->when($this->search, function (Builder $query) {
$query->where('name', 'like', '%' . $this->search . '%') $query->where(function (Builder $q) {
$q->where('name', 'like', '%' . $this->search . '%')
->orWhere('description', 'like', '%' . $this->search . '%'); ->orWhere('description', 'like', '%' . $this->search . '%');
});
}) })
->when($this->status, function (Builder $query) { ->when($this->status, function (Builder $query) {
$query->where('status', $this->status); $query->where('status', $this->status);
@@ -119,6 +128,7 @@ class ParkListComponent extends Component
'parks' => $query->paginate(12, pageName: 'parks-page'), 'parks' => $query->paginate(12, pageName: 'parks-page'),
'statusOptions' => $this->getStatusOptions(), 'statusOptions' => $this->getStatusOptions(),
'operatorOptions' => $this->getOperatorOptions(), 'operatorOptions' => $this->getOperatorOptions(),
'viewMode' => $this->viewMode,
]); ]);
} }
} }

View File

@@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use App\Enums\ParkStatus; use App\Enums\ParkStatus;
use App\Traits\HasLocation;
use App\Traits\HasSlugHistory; use App\Traits\HasSlugHistory;
use App\Traits\HasParkStatistics; use App\Traits\HasParkStatistics;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@@ -12,7 +13,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
class Park extends Model class Park extends Model
{ {
use HasFactory, HasSlugHistory, HasParkStatistics; use HasFactory, HasSlugHistory, HasParkStatistics, HasLocation;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.

View File

@@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration return new class extends Migration
{ {
@@ -11,6 +12,9 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
// Enable PostGIS extension if not enabled
DB::statement('CREATE EXTENSION IF NOT EXISTS postgis');
Schema::create('locations', function (Blueprint $table) { Schema::create('locations', function (Blueprint $table) {
$table->id(); $table->id();
@@ -24,9 +28,6 @@ return new class extends Migration
$table->string('country'); $table->string('country');
$table->string('postal_code')->nullable(); $table->string('postal_code')->nullable();
// Enable PostGIS extension if not enabled
DB::statement('CREATE EXTENSION IF NOT EXISTS postgis');
// Location name and type // Location name and type
$table->string('name')->nullable(); $table->string('name')->nullable();
$table->string('location_type', 50)->nullable(); $table->string('location_type', 50)->nullable();
@@ -36,7 +37,7 @@ return new class extends Migration
$table->decimal('longitude', 9, 6)->nullable(); $table->decimal('longitude', 9, 6)->nullable();
// Coordinates using PostGIS // Coordinates using PostGIS
$table->point('coordinates')->spatialIndex(); // We'll add the coordinates column after the table is created
$table->decimal('elevation', 8, 2)->nullable(); $table->decimal('elevation', 8, 2)->nullable();
// Additional details // Additional details
@@ -58,6 +59,10 @@ return new class extends Migration
$table->index('name'); $table->index('name');
$table->index('location_type'); $table->index('location_type');
}); });
// Add the coordinates column using PostGIS
DB::statement('ALTER TABLE locations ADD COLUMN coordinates GEOMETRY(Point, 4326)');
DB::statement('CREATE INDEX locations_coordinates_idx ON locations USING GIST (coordinates)');
} }
/** /**

View File

@@ -18,15 +18,14 @@ return new class extends Migration
$table->integer('closed_areas')->default(0); $table->integer('closed_areas')->default(0);
// Ride statistics // Ride statistics
$table->integer('total_rides')->default(0); // Note: ride_count and coaster_count already exist in the parks table
$table->integer('total_coasters')->default(0);
$table->integer('total_flat_rides')->default(0); $table->integer('total_flat_rides')->default(0);
$table->integer('total_water_rides')->default(0); $table->integer('total_water_rides')->default(0);
// Visitor statistics // Visitor statistics
$table->integer('total_daily_capacity')->default(0); $table->integer('total_daily_capacity')->default(0);
$table->integer('average_wait_time')->nullable(); $table->integer('average_wait_time')->nullable();
$table->decimal('average_rating', 3, 2)->nullable(); // Note: average_rating already exists in the parks table
// Historical data // Historical data
$table->integer('total_rides_operated')->default(0); $table->integer('total_rides_operated')->default(0);
@@ -40,8 +39,8 @@ return new class extends Migration
$table->decimal('guest_satisfaction', 3, 2)->nullable(); $table->decimal('guest_satisfaction', 3, 2)->nullable();
// Add indexes for common queries // Add indexes for common queries
$table->index(['operator_id', 'total_rides']); $table->index(['operator_id', 'ride_count']);
$table->index(['operator_id', 'total_coasters']); $table->index(['operator_id', 'coaster_count']);
$table->index(['operator_id', 'average_rating']); $table->index(['operator_id', 'average_rating']);
}); });
} }
@@ -52,21 +51,18 @@ return new class extends Migration
public function down(): void public function down(): void
{ {
Schema::table('parks', function (Blueprint $table) { Schema::table('parks', function (Blueprint $table) {
$table->dropIndex(['operator_id', 'total_rides']); $table->dropIndex(['operator_id', 'ride_count']);
$table->dropIndex(['operator_id', 'total_coasters']); $table->dropIndex(['operator_id', 'coaster_count']);
$table->dropIndex(['operator_id', 'average_rating']); $table->dropIndex(['operator_id', 'average_rating']);
$table->dropColumn([ $table->dropColumn([
'total_areas', 'total_areas',
'operating_areas', 'operating_areas',
'closed_areas', 'closed_areas',
'total_rides',
'total_coasters',
'total_flat_rides', 'total_flat_rides',
'total_water_rides', 'total_water_rides',
'total_daily_capacity', 'total_daily_capacity',
'average_wait_time', 'average_wait_time',
'average_rating',
'total_rides_operated', 'total_rides_operated',
'total_rides_retired', 'total_rides_retired',
'last_expansion_date', 'last_expansion_date',

View File

@@ -31,6 +31,14 @@ Migrating the design from Django to Laravel implementation
- Tracked asset organization - Tracked asset organization
- Maintained migration progress - Maintained migration progress
5. Parks List Component
- Implemented ParkListComponent matching Django design
- Added grid/list view toggle functionality
- Implemented filtering and sorting controls
- Created responsive card layout for parks
- Added location display to park cards
- Ensured visual parity with Django implementation
### Current State ### Current State
- Base layout template is ready - Base layout template is ready
- Core styling system is in place - Core styling system is in place
@@ -41,10 +49,11 @@ Migrating the design from Django to Laravel implementation
- Mobile menu - Mobile menu
- User menu - User menu
- Auth menu - Auth menu
- Park list with filtering and view modes
### Next Steps ### Next Steps
1. Component Migration 1. Component Migration
- Start with high-priority components (forms, modals, cards) - Continue with remaining components (forms, modals, cards)
- Convert Django partials to Blade components - Convert Django partials to Blade components
- Implement Livewire interactive components - Implement Livewire interactive components
- Test component functionality - Test component functionality

View File

@@ -59,12 +59,20 @@ Features:
- Size - Size
3. Display Features 3. Display Features
- Responsive grid layout - Responsive grid/list layout with toggle
- Status badges with colors - Status badges with colors
- Key statistics display - Key statistics display
- Quick access to edit/view - Quick access to edit/view
- Website links - Website links
- Operator information - Operator information
- Location information display
4. Django Parity Implementation
- Matches the original Django implementation's UI/UX
- Uses the same filter controls and layout
- Implements identical view mode toggle (grid/list)
- Displays the same park information cards
- Maintains consistent styling with the original
### 3. ParkAreaFormComponent ### 3. ParkAreaFormComponent
Located in `app/Livewire/ParkAreaFormComponent.php` Located in `app/Livewire/ParkAreaFormComponent.php`

2
package-lock.json generated
View File

@@ -1,5 +1,5 @@
{ {
"name": "laravel", "name": "thrillwiki_laravel",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {

View File

@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ $title ?? 'ThrillWiki' }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles
</head>
<body class="min-h-screen bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
<header class="bg-white dark:bg-gray-800 shadow">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div class="flex justify-between items-center">
<div class="flex items-center">
<a href="{{ route('home') }}" class="text-xl font-bold text-indigo-600 dark:text-indigo-400">
ThrillWiki
</a>
<nav class="ml-10 space-x-4 hidden md:flex">
<a href="{{ route('parks.index') }}" class="px-3 py-2 rounded-md text-sm font-medium {{ request()->routeIs('parks.*') ? 'bg-indigo-100 dark:bg-indigo-900 text-indigo-700 dark:text-indigo-300' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700' }}">
Parks
</a>
<a href="{{ route('rides.index') }}" class="px-3 py-2 rounded-md text-sm font-medium {{ request()->routeIs('rides.*') ? 'bg-indigo-100 dark:bg-indigo-900 text-indigo-700 dark:text-indigo-300' : 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700' }}">
Rides
</a>
</nav>
</div>
<div class="flex items-center space-x-4">
@livewire('theme-toggle-component')
@auth
@livewire('user-menu-component')
@else
@livewire('auth-menu-component')
@endauth
@livewire('mobile-menu-component')
</div>
</div>
</div>
</header>
<main>
{{ $slot }}
</main>
<footer class="bg-white dark:bg-gray-800 shadow mt-auto">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div class="flex justify-between items-center">
<div class="text-sm text-gray-500 dark:text-gray-400">
&copy; {{ date('Y') }} ThrillWiki. All rights reserved.
</div>
<div class="flex space-x-4">
<a href="{{ route('terms') }}" class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">
Terms
</a>
<a href="{{ route('privacy') }}" class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300">
Privacy
</a>
</div>
</div>
</div>
</footer>
@livewireScripts
</body>
</html>

View File

@@ -1,17 +1,62 @@
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="container mx-auto px-4 py-8">
<!-- Filters and Search --> <div class="flex justify-between items-center mb-6">
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl p-4 mb-6"> <div class="flex items-center space-x-4">
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> <h1 class="text-2xl font-bold text-gray-900">Parks</h1>
<!-- Search -->
<div> <div class="flex items-center space-x-2 bg-gray-100 rounded-lg p-1" role="group" aria-label="View mode selection">
<label for="search" class="block text-sm font-medium text-gray-700">Search</label> <button wire:click="$set('viewMode', 'grid')"
<div class="mt-1"> class="p-2 rounded transition-colors duration-200 {{ $viewMode == 'grid' ? 'bg-white shadow-sm' : '' }}"
<input type="text" wire:model.live.debounce.300ms="search" id="search" aria-label="Grid view"
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" aria-pressed="{{ $viewMode == 'grid' ? 'true' : 'false' }}">
placeholder="Search parks..."> <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>
</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 --> <!-- Status Filter -->
<div> <div>
<label for="status" class="block text-sm font-medium text-gray-700">Status</label> <label for="status" class="block text-sm font-medium text-gray-700">Status</label>
@@ -52,85 +97,90 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<!-- Parks Grid --> <!-- Parks Grid/List -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"> <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) @forelse($parks as $park)
<div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-xl overflow-hidden"> <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' : '' }}"
<div class="p-6"> data-testid="park-card"
<div class="flex items-center justify-between mb-4"> data-park-id="{{ $park->id }}"
<h3 class="text-lg font-semibold text-gray-900"> data-view-mode="{{ $viewMode }}">
<a href="{{ route('parks.show', $park) }}" class="hover:text-indigo-600">
<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 }} {{ $park->name }}
</a>
</h3> </h3>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ $park->status_classes }}">
<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() }} {{ $park->status->label() }}
</span> </span>
</div>
<div class="text-sm text-gray-500 mb-4"> @if($park->opening_date)
{{ $park->brief_description }} <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800"
</div> data-testid="park-opening-date">
Opened {{ date('Y', strtotime($park->opening_date)) }}
</span>
@endif
<div class="grid grid-cols-2 gap-4 text-sm"> @if($park->ride_count)
<div> <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800"
<span class="text-gray-500">Operator:</span> data-testid="park-ride-count">
<span class="text-gray-900">{{ $park->operator?->name ?? 'Unknown' }}</span> {{ $park->ride_count }} ride{{ $park->ride_count != 1 ? 's' : '' }}
</div> </span>
@if($park->opening_year)
<div>
<span class="text-gray-500">Opened:</span>
<span class="text-gray-900">{{ $park->opening_year }}</span>
</div>
@endif @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->coaster_count)
@if($park->website) <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800"
<a href="{{ $park->website_url }}" target="_blank" rel="noopener" data-testid="park-coaster-count">
class="text-sm text-gray-500 hover:text-gray-900"> {{ $park->coaster_count }} coaster{{ $park->coaster_count != 1 ? 's' : '' }}
Visit Website </span>
</a>
@endif @endif
<a href="{{ route('parks.edit', $park) }}"
class="text-sm text-indigo-600 hover:text-indigo-900">
Edit
</a>
</div>
</div> </div>
</div> </div>
</article>
@empty @empty
<div class="col-span-full text-center py-12"> <div class="{{ $viewMode == 'grid' ? 'col-span-full' : '' }} p-4 text-sm text-gray-500 text-center" data-testid="no-parks-found">
<h3 class="text-lg font-medium text-gray-900">No parks found</h3> @if($search)
<p class="mt-2 text-sm text-gray-500">Try adjusting your filters or search terms.</p> 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> </div>
@endforelse @endforelse
</div> </div>
</div>
<!-- Pagination --> <!-- Pagination -->
<div class="mt-6"> <div class="mt-6">
{{ $parks->links() }} {{ $parks->links() }}
</div> </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> </div>

View File

@@ -10,9 +10,16 @@ Route::get('/', function () {
Route::get('/counter', Counter::class); Route::get('/counter', Counter::class);
// Parks routes // Parks routes
Route::get('/parks', function () { Route::get('/parks', \App\Livewire\ParkListComponent::class)->name('parks.index');
return 'Parks Index'; Route::get('/parks/create', function () {
})->name('parks.index'); return 'Create Park';
})->name('parks.create');
Route::get('/parks/{park}', function () {
return 'Show Park';
})->name('parks.show');
Route::get('/parks/{park}/edit', function () {
return 'Edit Park';
})->name('parks.edit');
// Rides routes // Rides routes
Route::get('/rides', function () { Route::get('/rides', function () {