mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 11:51:11 -05:00
Add enums for ReviewStatus, TrackMaterial, LaunchType, RideCategory, and RollerCoasterType; implement Designer and RideModel models; create migrations for ride_models and helpful_votes tables; enhance RideGalleryComponent documentation
This commit is contained in:
88
app/Livewire/RideDetailComponent.php
Normal file
88
app/Livewire/RideDetailComponent.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Ride;
|
||||
use Livewire\Component;
|
||||
|
||||
class RideDetailComponent extends Component
|
||||
{
|
||||
/** @var Ride */
|
||||
public Ride $ride;
|
||||
|
||||
/** @var bool */
|
||||
public bool $showCoasterStats = false;
|
||||
|
||||
public function mount(Ride $ride): void
|
||||
{
|
||||
$this->ride = $ride->load([
|
||||
'park',
|
||||
'parkArea',
|
||||
'manufacturer',
|
||||
'designer',
|
||||
'rideModel',
|
||||
'coasterStats',
|
||||
]);
|
||||
|
||||
$this->showCoasterStats = $ride->category === 'RC' && $ride->coasterStats !== null;
|
||||
}
|
||||
|
||||
public function toggleCoasterStats(): void
|
||||
{
|
||||
$this->showCoasterStats = !$this->showCoasterStats;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.ride-detail');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the opening year of the ride.
|
||||
*/
|
||||
public function getOpeningYearAttribute(): ?string
|
||||
{
|
||||
return $this->ride->opening_date?->format('Y');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted date range for the ride's operation.
|
||||
*/
|
||||
public function getOperatingPeriodAttribute(): string
|
||||
{
|
||||
$start = $this->ride->opening_date?->format('Y');
|
||||
$end = $this->ride->closing_date?->format('Y');
|
||||
|
||||
if (!$start) {
|
||||
return 'Unknown dates';
|
||||
}
|
||||
|
||||
return $end ? "{$start} - {$end}" : "{$start} - Present";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ride's status badge color classes.
|
||||
*/
|
||||
public function getStatusColorClasses(): string
|
||||
{
|
||||
return match($this->ride->status) {
|
||||
'OPERATING' => 'bg-green-100 text-green-800',
|
||||
'CLOSED_TEMP' => 'bg-yellow-100 text-yellow-800',
|
||||
'SBNO' => 'bg-red-100 text-red-800',
|
||||
'CLOSING' => 'bg-orange-100 text-orange-800',
|
||||
'CLOSED_PERM' => 'bg-gray-100 text-gray-800',
|
||||
'UNDER_CONSTRUCTION' => 'bg-blue-100 text-blue-800',
|
||||
'DEMOLISHED' => 'bg-gray-100 text-gray-800',
|
||||
'RELOCATED' => 'bg-purple-100 text-purple-800',
|
||||
default => 'bg-gray-100 text-gray-800',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a measurement value with units.
|
||||
*/
|
||||
public function formatMeasurement(?float $value, string $unit): string
|
||||
{
|
||||
return $value !== null ? number_format($value, 2) . ' ' . $unit : 'N/A';
|
||||
}
|
||||
}
|
||||
140
app/Livewire/RideFormComponent.php
Normal file
140
app/Livewire/RideFormComponent.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Ride;
|
||||
use App\Models\Park;
|
||||
use App\Models\ParkArea;
|
||||
use App\Models\RideModel;
|
||||
use App\Models\Designer;
|
||||
use App\Models\Manufacturer;
|
||||
use App\Enums\RideCategory;
|
||||
use App\Enums\RideStatus;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
|
||||
class RideFormComponent extends Component
|
||||
{
|
||||
public ?Ride $ride = null;
|
||||
|
||||
/** @var array<string, mixed> */
|
||||
public array $state = [];
|
||||
|
||||
/** @var array<string, mixed>|null */
|
||||
public ?array $coasterStats = null;
|
||||
|
||||
/** @var int|null */
|
||||
public ?int $parkId = null;
|
||||
|
||||
protected function rules(): array
|
||||
{
|
||||
return [
|
||||
'state.name' => ['required', 'string', 'max:255'],
|
||||
'state.park_id' => ['required', 'exists:parks,id'],
|
||||
'state.park_area_id' => ['nullable', 'exists:park_areas,id'],
|
||||
'state.manufacturer_id' => ['nullable', 'exists:manufacturers,id'],
|
||||
'state.designer_id' => ['nullable', 'exists:designers,id'],
|
||||
'state.ride_model_id' => ['nullable', 'exists:ride_models,id'],
|
||||
'state.category' => ['required', new Enum(RideCategory::class)],
|
||||
'state.status' => ['required', new Enum(RideStatus::class)],
|
||||
'state.post_closing_status' => ['nullable', new Enum(RideStatus::class)],
|
||||
'state.description' => ['nullable', 'string'],
|
||||
'state.opening_date' => ['nullable', 'date'],
|
||||
'state.closing_date' => ['nullable', 'date'],
|
||||
'state.status_since' => ['nullable', 'date'],
|
||||
'state.min_height_in' => ['nullable', 'integer', 'min:0'],
|
||||
'state.max_height_in' => ['nullable', 'integer', 'min:0'],
|
||||
'state.capacity_per_hour' => ['nullable', 'integer', 'min:0'],
|
||||
'state.ride_duration_seconds' => ['nullable', 'integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
|
||||
public function mount(?Ride $ride = null): void
|
||||
{
|
||||
$this->ride = $ride;
|
||||
|
||||
if ($ride) {
|
||||
$this->state = $ride->only([
|
||||
'name', 'park_id', 'park_area_id', 'manufacturer_id',
|
||||
'designer_id', 'ride_model_id', 'category', 'status',
|
||||
'post_closing_status', 'description', 'opening_date',
|
||||
'closing_date', 'status_since', 'min_height_in',
|
||||
'max_height_in', 'capacity_per_hour', 'ride_duration_seconds',
|
||||
]);
|
||||
|
||||
$this->parkId = $ride->park_id;
|
||||
|
||||
if ($ride->coasterStats) {
|
||||
$this->coasterStats = $ride->coasterStats->toArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function updated(string $field): void
|
||||
{
|
||||
$this->validateOnly($field);
|
||||
|
||||
if ($field === 'state.category') {
|
||||
$this->coasterStats = $this->state['category'] === RideCategory::ROLLER_COASTER->value
|
||||
? ($this->coasterStats ?? [])
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
if (!$this->ride) {
|
||||
$this->ride = new Ride();
|
||||
}
|
||||
|
||||
$this->ride->fill($this->state)->save();
|
||||
|
||||
// Handle coaster stats if applicable
|
||||
if ($this->state['category'] === RideCategory::ROLLER_COASTER->value && $this->coasterStats) {
|
||||
$this->ride->coasterStats()->updateOrCreate(
|
||||
['ride_id' => $this->ride->id],
|
||||
$this->coasterStats
|
||||
);
|
||||
} elseif ($this->ride->coasterStats) {
|
||||
$this->ride->coasterStats->delete();
|
||||
}
|
||||
|
||||
session()->flash('message', 'Ride saved successfully.');
|
||||
|
||||
$this->redirect(route('rides.show', $this->ride));
|
||||
}
|
||||
|
||||
public function getParksProperty()
|
||||
{
|
||||
return Park::orderBy('name')->get();
|
||||
}
|
||||
|
||||
public function getParkAreasProperty()
|
||||
{
|
||||
return $this->parkId
|
||||
? ParkArea::where('park_id', $this->parkId)->orderBy('name')->get()
|
||||
: collect();
|
||||
}
|
||||
|
||||
public function getManufacturersProperty()
|
||||
{
|
||||
return Manufacturer::orderBy('name')->get();
|
||||
}
|
||||
|
||||
public function getDesignersProperty()
|
||||
{
|
||||
return Designer::orderBy('name')->get();
|
||||
}
|
||||
|
||||
public function getRideModelsProperty()
|
||||
{
|
||||
return RideModel::orderBy('name')->get();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.ride-form');
|
||||
}
|
||||
}
|
||||
92
app/Livewire/RideGalleryComponent.php
Normal file
92
app/Livewire/RideGalleryComponent.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\Ride;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use Livewire\Attributes\Rule;
|
||||
|
||||
class RideGalleryComponent extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
/** @var Ride */
|
||||
public Ride $ride;
|
||||
|
||||
/** @var \Illuminate\Http\UploadedFile */
|
||||
#[Rule('image|max:10240')] // 10MB Max
|
||||
public $photo;
|
||||
|
||||
/** @var string|null */
|
||||
public ?string $caption = null;
|
||||
|
||||
/** @var bool */
|
||||
public bool $showUploadForm = false;
|
||||
|
||||
public function mount(Ride $ride): void
|
||||
{
|
||||
$this->ride = $ride;
|
||||
}
|
||||
|
||||
public function toggleUploadForm(): void
|
||||
{
|
||||
$this->showUploadForm = !$this->showUploadForm;
|
||||
$this->reset(['photo', 'caption']);
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$this->validate([
|
||||
'photo' => 'required|image|max:10240',
|
||||
'caption' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$path = $this->photo->store('ride-photos', 'public');
|
||||
|
||||
$this->ride->photos()->create([
|
||||
'path' => $path,
|
||||
'caption' => $this->caption,
|
||||
'uploaded_by' => Auth::id(),
|
||||
]);
|
||||
|
||||
$this->reset(['photo', 'caption']);
|
||||
$this->showUploadForm = false;
|
||||
|
||||
session()->flash('message', 'Photo uploaded successfully.');
|
||||
}
|
||||
|
||||
public function deletePhoto(int $photoId): void
|
||||
{
|
||||
$photo = $this->ride->photos()->findOrFail($photoId);
|
||||
|
||||
if ($photo->uploaded_by === Auth::id() || Gate::allows('delete-any-photo')) {
|
||||
Storage::disk('public')->delete($photo->path);
|
||||
$photo->delete();
|
||||
session()->flash('message', 'Photo deleted successfully.');
|
||||
} else {
|
||||
session()->flash('error', 'You do not have permission to delete this photo.');
|
||||
}
|
||||
}
|
||||
|
||||
public function setFeaturedPhoto(int $photoId): void
|
||||
{
|
||||
if (Gate::allows('edit', $this->ride)) {
|
||||
$this->ride->featured_photo_id = $photoId;
|
||||
$this->ride->save();
|
||||
session()->flash('message', 'Featured photo updated.');
|
||||
} else {
|
||||
session()->flash('error', 'You do not have permission to set the featured photo.');
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.ride-gallery', [
|
||||
'photos' => $this->ride->photos()->latest()->paginate(12),
|
||||
]);
|
||||
}
|
||||
}
|
||||
101
app/Livewire/RideListComponent.php
Normal file
101
app/Livewire/RideListComponent.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Enums\RideCategory;
|
||||
use App\Models\Ride;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class RideListComponent extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
/** @var string */
|
||||
public $search = '';
|
||||
|
||||
/** @var string */
|
||||
public $category = '';
|
||||
|
||||
/** @var string */
|
||||
public $sortField = 'name';
|
||||
|
||||
/** @var string */
|
||||
public $sortDirection = 'asc';
|
||||
|
||||
/** @var string */
|
||||
public $viewMode = 'grid';
|
||||
|
||||
/**
|
||||
* Update search term and reset pagination.
|
||||
*/
|
||||
public function updatingSearch(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update category filter and reset pagination.
|
||||
*/
|
||||
public function updatingCategory(): void
|
||||
{
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort results by the given field.
|
||||
*/
|
||||
public function sortBy(string $field): void
|
||||
{
|
||||
if ($this->sortField === $field) {
|
||||
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
$this->sortDirection = 'asc';
|
||||
}
|
||||
|
||||
$this->sortField = $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle between grid and list views.
|
||||
*/
|
||||
public function toggleView(): void
|
||||
{
|
||||
$this->viewMode = $this->viewMode === 'grid' ? 'list' : 'grid';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available ride categories.
|
||||
*/
|
||||
public function getCategoriesProperty(): array
|
||||
{
|
||||
return RideCategory::options();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filtered and sorted rides query.
|
||||
*/
|
||||
private function getRidesQuery()
|
||||
{
|
||||
return Ride::query()
|
||||
->when($this->search, fn($query, $search) =>
|
||||
$query->where('name', 'like', "%{$search}%")
|
||||
)
|
||||
->when($this->category, fn($query, $category) =>
|
||||
$query->where('category', $category)
|
||||
)
|
||||
->orderBy($this->sortField, $this->sortDirection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$rides = $this->getRidesQuery()->paginate(12);
|
||||
|
||||
return view('livewire.ride-list', [
|
||||
'rides' => $rides,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user