Add Livewire components for parks, rides, and manufacturers

- 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.
This commit is contained in:
pacnpal
2025-06-23 21:31:05 -04:00
parent 5caa148a89
commit 97a7682eb7
62 changed files with 10532 additions and 210 deletions

View File

@@ -3,13 +3,17 @@
namespace App\Models;
use App\Traits\HasSlugHistory;
use App\Traits\HasLocation;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\DB;
class Operator extends Model
{
use HasFactory, HasSlugHistory;
use HasFactory, HasSlugHistory, HasLocation, SoftDeletes;
/**
* The attributes that are mass assignable.
@@ -20,14 +24,43 @@ class Operator extends Model
'name',
'slug',
'website',
'headquarters',
'description',
'industry_sector',
'founded_year',
'employee_count',
'annual_revenue',
'market_cap',
'stock_symbol',
'headquarters_location',
'geographic_presence',
'company_type',
'parent_company_id',
'is_public',
'is_active',
'total_parks',
'total_rides',
'total_rides_manufactured',
'total_rides_designed',
];
/**
* Get the parks operated by this company.
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'founded_year' => 'integer',
'employee_count' => 'integer',
'annual_revenue' => 'decimal:2',
'market_cap' => 'decimal:2',
'is_public' => 'boolean',
'is_active' => 'boolean',
'total_parks' => 'integer',
'total_rides_manufactured' => 'integer',
'total_rides_designed' => 'integer',
];
/**
* Get the parks operated by this operator.
*/
public function parks(): HasMany
{
@@ -35,21 +68,60 @@ class Operator extends Model
}
/**
* Update park statistics.
* Get the rides manufactured by this operator.
*/
public function manufactured_rides(): HasMany
{
return $this->hasMany(Ride::class, 'manufacturer_id');
}
/**
* Get the rides designed by this operator.
*/
public function designed_rides(): HasMany
{
return $this->hasMany(Ride::class, 'designer_id');
}
/**
* Get the parent company if this is a subsidiary.
*/
public function parent_company(): BelongsTo
{
return $this->belongsTo(Operator::class, 'parent_company_id');
}
/**
* Get the subsidiary companies.
*/
public function subsidiaries(): HasMany
{
return $this->hasMany(Operator::class, 'parent_company_id');
}
/**
* Update comprehensive statistics.
*/
public function updateStatistics(): void
{
$this->total_parks = $this->parks()->count();
$this->total_rides = $this->parks()->sum('ride_count');
$this->total_rides_manufactured = $this->manufactured_rides()->count();
$this->total_rides_designed = $this->designed_rides()->count();
$this->save();
}
/**
* Get the operator's name with total parks.
* Get the operator's display name with role indicators.
*/
public function getDisplayNameAttribute(): string
{
return "{$this->name} ({$this->total_parks} parks)";
$roles = [];
if ($this->total_parks > 0) $roles[] = 'Operator';
if ($this->total_rides_manufactured > 0) $roles[] = 'Manufacturer';
if ($this->total_rides_designed > 0) $roles[] = 'Designer';
$roleText = empty($roles) ? '' : ' (' . implode(', ', $roles) . ')';
return $this->name . $roleText;
}
/**
@@ -69,6 +141,98 @@ class Operator extends Model
return $website;
}
/**
* Get company size category based on employee count.
*/
public function getCompanySizeCategoryAttribute(): string
{
if (!$this->employee_count) return 'unknown';
return match (true) {
$this->employee_count <= 100 => 'small',
$this->employee_count <= 1000 => 'medium',
$this->employee_count <= 10000 => 'large',
default => 'enterprise'
};
}
/**
* Get geographic presence level.
*/
public function getGeographicPresenceLevelAttribute(): string
{
$countries = $this->parks()
->join('locations', 'parks.location_id', '=', 'locations.id')
->distinct('locations.country')
->count('locations.country');
return match (true) {
$countries <= 1 => 'regional',
$countries <= 3 => 'national',
default => 'international'
};
}
/**
* Get market influence score.
*/
public function getMarketInfluenceScoreAttribute(): float
{
$score = 0;
// Parks operated (40% weight)
$score += min($this->total_parks * 10, 40);
// Rides manufactured (30% weight)
$score += min($this->total_rides_manufactured * 2, 30);
// Revenue influence (20% weight)
if ($this->annual_revenue) {
$score += min(($this->annual_revenue / 1000000000) * 10, 20);
}
// Geographic presence (10% weight)
$countries = $this->parks()
->join('locations', 'parks.location_id', '=', 'locations.id')
->distinct('locations.country')
->count('locations.country');
$score += min($countries * 2, 10);
return round($score, 1);
}
/**
* Scope a query to only include active operators.
*/
public function scopeActive($query)
{
return $query->where('is_active', true);
}
/**
* Scope a query to only include park operators.
*/
public function scopeParkOperators($query)
{
return $query->whereHas('parks');
}
/**
* Scope a query to only include ride manufacturers.
*/
public function scopeManufacturers($query)
{
return $query->whereHas('manufactured_rides');
}
/**
* Scope a query to only include ride designers.
*/
public function scopeDesigners($query)
{
return $query->whereHas('designed_rides');
}
/**
* Scope a query to only include major operators (with multiple parks).
*/
@@ -77,6 +241,75 @@ class Operator extends Model
return $query->where('total_parks', '>=', $minParks);
}
/**
* Scope a query to filter by industry sector.
*/
public function scopeIndustrySector($query, string $sector)
{
return $query->where('industry_sector', $sector);
}
/**
* Scope a query to filter by company size.
*/
public function scopeCompanySize($query, string $size)
{
$ranges = [
'small' => [1, 100],
'medium' => [101, 1000],
'large' => [1001, 10000],
'enterprise' => [10001, PHP_INT_MAX]
];
if (isset($ranges[$size])) {
return $query->whereBetween('employee_count', $ranges[$size]);
}
return $query;
}
/**
* Scope a query to filter by founded year range.
*/
public function scopeFoundedBetween($query, int $startYear, int $endYear)
{
return $query->whereBetween('founded_year', [$startYear, $endYear]);
}
/**
* Scope a query to filter by revenue range.
*/
public function scopeRevenueBetween($query, float $minRevenue, float $maxRevenue)
{
return $query->whereBetween('annual_revenue', [$minRevenue, $maxRevenue]);
}
/**
* Scope a query to filter by dual roles.
*/
public function scopeDualRole($query, array $roles)
{
return $query->where(function ($q) use ($roles) {
if (in_array('park_operator', $roles)) {
$q->orWhereHas('parks');
}
if (in_array('ride_manufacturer', $roles)) {
$q->orWhereHas('manufactured_rides');
}
if (in_array('ride_designer', $roles)) {
$q->orWhereHas('designed_rides');
}
});
}
/**
* Scope a query for optimized loading with counts.
*/
public function scopeWithCounts($query)
{
return $query->withCount(['parks', 'manufactured_rides', 'designed_rides']);
}
/**
* Get the route key for the model.
*/